Skip to content

Commit

Permalink
chore: erc permits first pass
Browse files Browse the repository at this point in the history
  • Loading branch information
Keyrxng committed Feb 23, 2024
1 parent f307223 commit 2d96f10
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 44 deletions.
67 changes: 67 additions & 0 deletions scripts/typescript/generate-permit2-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,72 @@ async function generate() {
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_PROVIDER_URL);
const myWallet = new ethers.Wallet(process.env.UBIQUIBOT_PRIVATE_KEY || "", provider);

const erc721TransferFromData: PermitTransferFrom = {
permitted: {
token: process.env.NFT_TOKEN_ADDRESS || "0x5FbDB2315678afecb367f032d93F642f64180aa3", // anvil no salt first acc NFT deployment
amount: 1, // this could be the tokenId if the permit is identified as an NFT via permitType
},
spender: process.env.BENEFICIARY_ADDRESS || "",
nonce: BigNumber.from(`0x${randomBytes(32).toString("hex")}`),
deadline: MaxUint256,
};

const { domain: domain721, types: types721, values: values721 } = SignatureTransfer.getPermitData(
erc721TransferFromData,
PERMIT2_ADDRESS,
process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : 1
);

const signature721 = await myWallet._signTypedData(domain721, types721, values721);

const GITHUB_CONTRIBUTION_TYPE = process.env.GITHUB_CONTRIBUTION_TYPE || "issue";
const GITHUB_ISSUE_ID = process.env.GITHUB_ISSUE_ID || "1";
const GITHUB_ORGANIZATION_NAME = process.env.GITHUB_ORGANIZATION_NAME || "ubiquity";
const GITHUB_REPOSITORY_NAME = process.env.GITHUB_REPOSITORY_NAME || "pay.ubq.fi";
const GITHUB_USERNAME = process.env.GITHUB_USERNAME || "keyrxng";

const txData721 = [
{
type: "erc721-permit",
permit: {
permitted: {
token: erc721TransferFromData.permitted.token,
amount: erc721TransferFromData.permitted.amount.toString(),
},
nonce: erc721TransferFromData.nonce.toString(),
deadline: erc721TransferFromData.deadline.toString(),
},
transferDetails: {
to: erc721TransferFromData.spender,
requestedAmount: erc721TransferFromData.permitted.amount.toString(),
},
owner: myWallet.address,
signature: signature721,
networkId: Number(process.env.CHAIN_ID),
nftMetadata: {
GITHUB_ORGANIZATION_NAME,
GITHUB_REPOSITORY_NAME,
GITHUB_ISSUE_ID,
GITHUB_USERNAME,
GITHUB_CONTRIBUTION_TYPE
},
request: {
beneficiary: process.env.BENEFICIARY_ADDRESS ?? "",
deadline: erc721TransferFromData.deadline.toString(),
keys: ["GITHUB_ORGANIZATION_NAME", "GITHUB_REPOSITORY_NAME", "GITHUB_ISSUE_ID", "GITHUB_USERNAME", "GITHUB_CONTRIBUTION_TYPE"],
nonce: erc721TransferFromData.nonce.toString(),
values: [GITHUB_ORGANIZATION_NAME, GITHUB_REPOSITORY_NAME, GITHUB_ISSUE_ID, GITHUB_USERNAME, GITHUB_CONTRIBUTION_TYPE],
},
},
];

const base64encodedTxData721 = Buffer.from(JSON.stringify(txData721)).toString("base64");
log.ok("Testing URL:");
console.log(`${process.env.FRONTEND_URL}?claim=${base64encodedTxData721}`);
log.ok("Public URL:");
console.log(`https://pay.ubq.fi?claim=${base64encodedTxData721}`);
console.log();

const permitTransferFromData: PermitTransferFrom = {
permitted: {
// token we are permitting to be transferred
Expand All @@ -37,6 +103,7 @@ async function generate() {
process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : 1
);
const signature = await myWallet._signTypedData(domain, types, values);

const txData = [
{
type: "erc20-permit",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ export function insertErc20PermitTableData(

export function insertErc721PermitTableData(permit: Erc721Permit, table: Element): Element {
const requestedAmountElement = document.getElementById("rewardAmount") as Element;
renderToFields(permit.request.beneficiary, app.currentExplorerUrl);
renderTokenFields(permit.nftAddress, app.currentExplorerUrl);
renderToFields(permit.transferDetails.to, app.currentExplorerUrl);
renderTokenFields(permit.permit.permitted.token, app.currentExplorerUrl);
const { GITHUB_REPOSITORY_NAME, GITHUB_CONTRIBUTION_TYPE, GITHUB_ISSUE_ID, GITHUB_ORGANIZATION_NAME, GITHUB_USERNAME } = permit.nftMetadata;
renderDetailsFields([
{
name: "NFT address",
value: `<a target="_blank" rel="noopener noreferrer" href="${app.currentExplorerUrl}/address/${permit.nftAddress}">${permit.nftAddress}</a>`,
value: `<a target="_blank" rel="noopener noreferrer" href="${app.currentExplorerUrl}/address/${permit.permit.permitted.token}">${permit.permit.permitted.token}</a>`,
},
{
name: "Expiry",
value: permit.request.deadline.lte(Number.MAX_SAFE_INTEGER.toString()) ? new Date(permit.request.deadline.toNumber()).toLocaleString() : undefined,
value: permit.permit.deadline.lte(Number.MAX_SAFE_INTEGER.toString()) ? new Date(permit.permit.deadline.toNumber()).toLocaleString() : undefined,
},
{
name: "GitHub Organization",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export async function readClaimDataFromUrl(app: AppState) {
app.claims = decodeClaimData(base64encodedTxData);
app.provider = await useFastestRpc(app);
const networkId = app.permit?.networkId || app.networkId;
app.signer = await connectWallet(networkId).catch(console.error);
app.signer = await connectWallet().catch(console.error);
displayRewardDetails();
displayRewardPagination();

renderTransaction(true)
.then(() => verifyCurrentNetwork(networkId))
.then(() => verifyCurrentNetwork(networkId as number))
.catch(console.error);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ export async function renderTransaction(nextTx?: boolean): Promise<Success> {
table.setAttribute(`data-claim`, "ok");

renderNftSymbol({
tokenAddress: app.permit.nftAddress,
tokenAddress: app.permit.permit.permitted.token,
explorerUrl: networkExplorers[app.permit.networkId],
table,
requestedAmountElement,
provider: app.provider,
}).catch(console.error);

const toElement = document.getElementById(`rewardRecipient`) as Element;
renderEnsName({ element: toElement, address: app.permit.request.beneficiary }).catch(console.error);
renderEnsName({ element: toElement, address: app.permit.transferDetails.to }).catch(console.error);

claimButton.element.addEventListener("click", claimErc721PermitHandler(app.permit));
}
Expand Down
55 changes: 27 additions & 28 deletions static/scripts/rewards/render-transaction/tx-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,55 +23,54 @@ const erc20PermitT = T.Object({
type: T.Literal("erc20-permit"),
permit: T.Object({
permitted: T.Object({
token: T.RegExp(/^0x[a-fA-F0-9]{40}$/),
amount: T.Union([T.RegExp(/^\d+$/), T.Number()]),
token: addressT,
amount: bigNumberT,
}),
nonce: T.Union([T.RegExp(/^\d+$/), T.Number()]),
deadline: T.Union([T.RegExp(/^\d+$/), T.Number()]),
nonce: bigNumberT,
deadline: bigNumberT,
}),
transferDetails: T.Object({
to: T.RegExp(/^0x[a-fA-F0-9]{40}$/),
requestedAmount: T.Union([T.RegExp(/^\d+$/), T.Number()]),
to: addressT,
requestedAmount: bigNumberT,
}),
owner: T.RegExp(/^0x[a-fA-F0-9]{40}$/),
signature: T.RegExp(/^0x[a-fA-F0-9]+$/),
owner: addressT,
signature: signatureT,
networkId: T.Number(),
});

export type Erc20Permit = StaticDecode<typeof erc20PermitT>;

const erc721Permit = T.Object({
type: T.Literal("erc721-permit"),
request: T.Object({
beneficiary: addressT,
deadline: bigNumberT,
keys: T.Array(T.String()),
permit: T.Object({
permitted: T.Object({
token: addressT,
// explicitly state tokenId or keep as "amount" but pass in the tokenId anyway? I'm passing amount in test case
amount: bigNumberT,
}),
nonce: bigNumberT,
values: T.Array(T.String()),
deadline: bigNumberT,
}),
transferDetails: T.Object({
to: addressT,
requestedAmount: bigNumberT,
}),
owner: addressT,
signature: signatureT,
networkId: networkIdT,
nftMetadata: T.Object({
GITHUB_ORGANIZATION_NAME: T.String(),
GITHUB_REPOSITORY_NAME: T.String(),
GITHUB_ISSUE_ID: T.String(),
GITHUB_USERNAME: T.String(),
GITHUB_CONTRIBUTION_TYPE: T.String(),
}),
nftAddress: addressT,
networkId: networkIdT,
signature: signatureT,
// @whilefoo: they should have matching key names.
owner: addressT,
permit: T.Object({
permitted: T.Object({
token: addressT,
amount: bigNumberT,
}),
nonce: bigNumberT,
request: T.Object({
beneficiary: addressT,
deadline: bigNumberT,
}),
transferDetails: T.Object({
to: T.RegExp(/^0x[a-fA-F0-9]{40}$/),
requestedAmount: T.Union([T.RegExp(/^\d+$/), T.Number()]),
keys: T.Array(T.String()),
nonce: bigNumberT,
values: T.Array(T.String()),
}),
});

Expand Down
6 changes: 3 additions & 3 deletions static/scripts/rewards/web3/erc20-permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ export async function checkPermitClaimable(app: AppState): Promise<boolean> {
return false;
}

const permit = app.permit.permit;
const permit = app.permit;

if (permit.deadline.lt(Math.floor(Date.now() / 1000))) {
if (permit.permit.deadline.lt(Math.floor(Date.now() / 1000))) {
toaster.create("error", `This reward has expired.`);
return false;
}
Expand All @@ -169,7 +169,7 @@ export async function checkPermitClaimable(app: AppState): Promise<boolean> {
}

const { balance, allowance } = treasury;
const permitted = BigNumber.from(permit.permitted.amount);
const permitted = BigNumber.from(permit.permit.permitted.amount);
const isSolvent = balance.gte(permitted);
const isAllowed = allowance.gte(permitted);

Expand Down
9 changes: 4 additions & 5 deletions static/scripts/rewards/web3/erc721-permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { renderTransaction } from "../render-transaction/render-transaction";
import { Erc721Permit } from "../render-transaction/tx-type";
import { claimButton, errorToast, showLoader, toaster } from "../toaster";
import { connectWallet } from "./connect-wallet";

export function claimErc721PermitHandler(permit: Erc721Permit) {
return async function claimButtonHandler() {
const signer = await connectWallet();
Expand All @@ -19,7 +18,7 @@ export function claimErc721PermitHandler(permit: Erc721Permit) {
return;
}

if (permit.request.deadline.lt(Math.floor(Date.now() / 1000))) {
if (permit.permit.deadline.lt(Math.floor(Date.now() / 1000))) {
toaster.create("error", `This NFT has expired.`);
return;
}
Expand All @@ -32,7 +31,7 @@ export function claimErc721PermitHandler(permit: Erc721Permit) {

showLoader();
try {
const nftContract = new ethers.Contract(permit.nftAddress, nftRewardAbi, signer);
const nftContract = new ethers.Contract(permit.permit.permitted.token, nftRewardAbi, signer);

const tx: TransactionResponse = await nftContract.safeMint(permit.request, permit.signature);
toaster.create("info", `Transaction sent. Waiting for confirmation...`);
Expand All @@ -47,7 +46,7 @@ export function claimErc721PermitHandler(permit: Erc721Permit) {
toaster.create("error", `Error rendering transaction: ${error.message}`);
});
} catch (error: unknown) {
if (error instanceof Error) {
if (error instanceof MetaMaskError) {
console.error(error);
errorToast(error, error.message ?? error);
}
Expand All @@ -56,6 +55,6 @@ export function claimErc721PermitHandler(permit: Erc721Permit) {
}

export async function isNonceRedeemed(nftMint: Erc721Permit, provider: JsonRpcProvider): Promise<boolean> {
const nftContract = new ethers.Contract(nftMint.nftAddress, nftRewardAbi, provider);
const nftContract = new ethers.Contract(nftMint.permit.permitted.token, nftRewardAbi, provider);
return nftContract.nonceRedeemed(nftMint.request.nonce);
}

0 comments on commit 2d96f10

Please sign in to comment.