From 2d96f1088daff7c27b8ea233ee7e2a8ef40babaf Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Fri, 23 Feb 2024 04:53:22 +0000 Subject: [PATCH] chore: erc permits first pass --- scripts/typescript/generate-permit2-url.ts | 67 +++++++++++++++++++ .../render-transaction/insert-table-data.ts | 8 +-- .../read-claim-data-from-url.ts | 4 +- .../render-transaction/render-transaction.ts | 4 +- .../rewards/render-transaction/tx-type.ts | 55 ++++++++------- static/scripts/rewards/web3/erc20-permit.ts | 6 +- static/scripts/rewards/web3/erc721-permit.ts | 9 ++- 7 files changed, 109 insertions(+), 44 deletions(-) diff --git a/scripts/typescript/generate-permit2-url.ts b/scripts/typescript/generate-permit2-url.ts index 7abf435d..5642921b 100644 --- a/scripts/typescript/generate-permit2-url.ts +++ b/scripts/typescript/generate-permit2-url.ts @@ -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 @@ -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", diff --git a/static/scripts/rewards/render-transaction/insert-table-data.ts b/static/scripts/rewards/render-transaction/insert-table-data.ts index 60ff1c01..8ed783a0 100644 --- a/static/scripts/rewards/render-transaction/insert-table-data.ts +++ b/static/scripts/rewards/render-transaction/insert-table-data.ts @@ -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: `${permit.nftAddress}`, + value: `${permit.permit.permitted.token}`, }, { 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", diff --git a/static/scripts/rewards/render-transaction/read-claim-data-from-url.ts b/static/scripts/rewards/render-transaction/read-claim-data-from-url.ts index 0763b8fc..58d9b189 100644 --- a/static/scripts/rewards/render-transaction/read-claim-data-from-url.ts +++ b/static/scripts/rewards/render-transaction/read-claim-data-from-url.ts @@ -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); } diff --git a/static/scripts/rewards/render-transaction/render-transaction.ts b/static/scripts/rewards/render-transaction/render-transaction.ts index 768aa04c..bc531637 100644 --- a/static/scripts/rewards/render-transaction/render-transaction.ts +++ b/static/scripts/rewards/render-transaction/render-transaction.ts @@ -62,7 +62,7 @@ export async function renderTransaction(nextTx?: boolean): Promise { table.setAttribute(`data-claim`, "ok"); renderNftSymbol({ - tokenAddress: app.permit.nftAddress, + tokenAddress: app.permit.permit.permitted.token, explorerUrl: networkExplorers[app.permit.networkId], table, requestedAmountElement, @@ -70,7 +70,7 @@ export async function renderTransaction(nextTx?: boolean): Promise { }).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)); } diff --git a/static/scripts/rewards/render-transaction/tx-type.ts b/static/scripts/rewards/render-transaction/tx-type.ts index 8891dcaf..dcaa0cf3 100644 --- a/static/scripts/rewards/render-transaction/tx-type.ts +++ b/static/scripts/rewards/render-transaction/tx-type.ts @@ -23,18 +23,18 @@ 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(), }); @@ -42,13 +42,22 @@ export type Erc20Permit = StaticDecode; 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(), @@ -56,22 +65,12 @@ const erc721Permit = T.Object({ 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()), }), }); diff --git a/static/scripts/rewards/web3/erc20-permit.ts b/static/scripts/rewards/web3/erc20-permit.ts index f6121862..edef4fe3 100644 --- a/static/scripts/rewards/web3/erc20-permit.ts +++ b/static/scripts/rewards/web3/erc20-permit.ts @@ -153,9 +153,9 @@ export async function checkPermitClaimable(app: AppState): Promise { 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; } @@ -169,7 +169,7 @@ export async function checkPermitClaimable(app: AppState): Promise { } 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); diff --git a/static/scripts/rewards/web3/erc721-permit.ts b/static/scripts/rewards/web3/erc721-permit.ts index ffd04ff9..e6893a0c 100644 --- a/static/scripts/rewards/web3/erc721-permit.ts +++ b/static/scripts/rewards/web3/erc721-permit.ts @@ -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(); @@ -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; } @@ -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...`); @@ -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); } @@ -56,6 +55,6 @@ export function claimErc721PermitHandler(permit: Erc721Permit) { } export async function isNonceRedeemed(nftMint: Erc721Permit, provider: JsonRpcProvider): Promise { - 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); }