diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml index 6b3066e5..a6bba39b 100644 --- a/.github/workflows/conventional-commits.yml +++ b/.github/workflows/conventional-commits.yml @@ -2,11 +2,12 @@ name: Conventional Commits on: push: + pull_request: jobs: conventional-commits: name: Conventional Commits runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ubiquity/action-conventional-commits@master diff --git a/static/scripts/rewards/app-state.ts b/static/scripts/rewards/app-state.ts index 002b0ee6..4d5d7ebd 100644 --- a/static/scripts/rewards/app-state.ts +++ b/static/scripts/rewards/app-state.ts @@ -1,14 +1,23 @@ import { JsonRpcProvider } from "@ethersproject/providers"; import { networkExplorers } from "./constants"; -import { ClaimTx } from "./render-transaction/tx-type"; +import { RewardPermit } from "./render-transaction/tx-type"; export class AppState { - public claims: ClaimTx[] = []; + public claims: RewardPermit[] = []; private _provider!: JsonRpcProvider; private _currentIndex = 0; + private _signer; + + get signer() { + return this._signer; + } + + set signer(value) { + this._signer = value; + } get networkId(): number | null { - return this.transaction?.networkId || null; + return this.permit?.networkId || null; } get provider(): JsonRpcProvider { @@ -19,33 +28,33 @@ export class AppState { this._provider = value; } - get transactionIndex(): number { + get permitIndex(): number { return this._currentIndex; } - get transaction(): ClaimTx | null { - return this.transactionIndex < this.claims.length ? this.claims[this.transactionIndex] : null; + get permit(): RewardPermit { + return this.permitIndex < this.claims.length ? this.claims[this.permitIndex] : this.claims[0]; } - get transactionNetworkId() { - return this.transaction?.networkId; + get permitNetworkId() { + return this.permit?.networkId; } get currentExplorerUrl(): string { - if (!this.transaction) { + if (!this.permit) { return "https://etherscan.io"; } - return networkExplorers[this.transaction.networkId] || "https://etherscan.io"; + return networkExplorers[this.permit.networkId] || "https://etherscan.io"; } - nextTx(): ClaimTx | null { + nextPermit(): RewardPermit | null { this._currentIndex = Math.min(this.claims.length - 1, this._currentIndex + 1); - return this.transaction; + return this.permit; } - previousTx(): ClaimTx | null { + previousPermit(): RewardPermit | null { this._currentIndex = Math.max(0, this._currentIndex - 1); - return this.transaction; + return this.permit; } } diff --git a/static/scripts/rewards/init.ts b/static/scripts/rewards/init.ts index 8fd646ea..07141cf3 100644 --- a/static/scripts/rewards/init.ts +++ b/static/scripts/rewards/init.ts @@ -1,10 +1,11 @@ +import { app } from "./app-state"; import { readClaimDataFromUrl } from "./render-transaction/read-claim-data-from-url"; import { grid } from "./the-grid"; displayCommitHash(); // @DEV: display commit hash in footer grid(document.getElementById("grid") as HTMLElement); // @DEV: display grid background -readClaimDataFromUrl().catch(console.error); // @DEV: read claim data from URL +readClaimDataFromUrl(app).catch(console.error); // @DEV: read claim data from URL declare const commitHash: string; // @DEV: passed in at build time check build/esbuild-build.ts function displayCommitHash() { diff --git a/static/scripts/rewards/render-transaction/claim-rewards-pagination.ts b/static/scripts/rewards/render-transaction/claim-rewards-pagination.ts index 60f6d9c1..1559a1c9 100644 --- a/static/scripts/rewards/render-transaction/claim-rewards-pagination.ts +++ b/static/scripts/rewards/render-transaction/claim-rewards-pagination.ts @@ -6,15 +6,15 @@ import { setPagination } from "./set-pagination"; import { removeAllEventListeners } from "./utils"; export function claimRewardsPagination(rewardsCount: HTMLElement) { - rewardsCount.innerHTML = `${app.transactionIndex + 1}/${app.claims.length} reward`; + rewardsCount.innerHTML = `${app.permitIndex + 1}/${app.claims.length} reward`; const nextTxButton = document.getElementById("nextTx"); if (nextTxButton) { nextTxButton.addEventListener("click", () => { claimButton.element = removeAllEventListeners(claimButton.element) as HTMLButtonElement; - app.nextTx(); - rewardsCount.innerHTML = `${app.transactionIndex + 1}/${app.claims.length} reward`; - table.setAttribute(`data-claim`, "none"); + app.nextPermit(); + rewardsCount.innerHTML = `${app.permitIndex + 1}/${app.claims.length} reward`; + table.setAttribute(`data-claim`, "error"); renderTransaction(true).catch(console.error); }); } @@ -23,9 +23,9 @@ export function claimRewardsPagination(rewardsCount: HTMLElement) { if (prevTxButton) { prevTxButton.addEventListener("click", () => { claimButton.element = removeAllEventListeners(claimButton.element) as HTMLButtonElement; - app.previousTx(); - rewardsCount.innerHTML = `${app.transactionIndex + 1}/${app.claims.length} reward`; - table.setAttribute(`data-claim`, "none"); + app.previousPermit(); + rewardsCount.innerHTML = `${app.permitIndex + 1}/${app.claims.length} reward`; + table.setAttribute(`data-claim`, "error"); renderTransaction(true).catch(console.error); }); } diff --git a/static/scripts/rewards/render-transaction/insert-table-data.ts b/static/scripts/rewards/render-transaction/insert-table-data.ts index e19a306f..60ff1c01 100644 --- a/static/scripts/rewards/render-transaction/insert-table-data.ts +++ b/static/scripts/rewards/render-transaction/insert-table-data.ts @@ -1,16 +1,17 @@ import { BigNumber, ethers } from "ethers"; -import { app } from "../app-state"; -import { Erc20Permit, Erc721Permit } from "./tx-type"; +import { AppState, app } from "../app-state"; +import { Erc721Permit } from "./tx-type"; export function shortenAddress(address: string): string { return `${address.slice(0, 10)}...${address.slice(-8)}`; } export function insertErc20PermitTableData( - permit: Erc20Permit, + app: AppState, table: Element, treasury: { balance: BigNumber; allowance: BigNumber; decimals: number; symbol: string } ): Element { + const permit = app.permit; const requestedAmountElement = document.getElementById("rewardAmount") as Element; renderToFields(permit.transferDetails.to, app.currentExplorerUrl); renderTokenFields(permit.permit.permitted.token, app.currentExplorerUrl); @@ -18,7 +19,10 @@ export function insertErc20PermitTableData( { name: "From", value: `${permit.owner}` }, { name: "Expiry", - value: permit.permit.deadline.lte(Number.MAX_SAFE_INTEGER.toString()) ? new Date(permit.permit.deadline.toNumber()).toLocaleString() : undefined, + value: (() => { + const deadline = BigNumber.isBigNumber(permit.permit.deadline) ? permit.permit.deadline : BigNumber.from(permit.permit.deadline); + return deadline.lte(Number.MAX_SAFE_INTEGER.toString()) ? new Date(deadline.toNumber()).toLocaleString() : undefined; + })(), }, { name: "Balance", value: treasury.balance.gte(0) ? `${ethers.utils.formatUnits(treasury.balance, treasury.decimals)} ${treasury.symbol}` : "N/A" }, { name: "Allowance", value: treasury.allowance.gte(0) ? `${ethers.utils.formatUnits(treasury.allowance, treasury.decimals)} ${treasury.symbol}` : "N/A" }, 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 327e4a65..0763b8fc 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 @@ -1,44 +1,46 @@ import { Type } from "@sinclair/typebox"; import { Value } from "@sinclair/typebox/value"; -import { app } from "../app-state"; -import { setClaimMessage } from "./set-claim-message"; -import { claimTxT } from "./tx-type"; +import { AppState, app } from "../app-state"; +import { useFastestRpc } from "../rpc-optimization/get-optimal-provider"; +import { connectWallet } from "../web3/connect-wallet"; +import { verifyCurrentNetwork } from "../web3/verify-current-network"; import { claimRewardsPagination } from "./claim-rewards-pagination"; import { renderTransaction } from "./render-transaction"; -import { getOptimalProvider } from "../rpc-optimization/get-optimal-provider"; +import { setClaimMessage } from "./set-claim-message"; +import { claimTxT } from "./tx-type"; export const table = document.getElementsByTagName(`table`)[0]; const urlParams = new URLSearchParams(window.location.search); const base64encodedTxData = urlParams.get("claim"); -export async function readClaimDataFromUrl() { +export async function readClaimDataFromUrl(app: AppState) { if (!base64encodedTxData) { // No claim data found setClaimMessage({ type: "Notice", message: `No claim data found.` }); - table.setAttribute(`data-claim`, "none"); + table.setAttribute(`data-claim`, "error"); return; } - decodeClaimData(base64encodedTxData); - - await getOptimalProvider(app); - + app.claims = decodeClaimData(base64encodedTxData); + app.provider = await useFastestRpc(app); + const networkId = app.permit?.networkId || app.networkId; + app.signer = await connectWallet(networkId).catch(console.error); displayRewardDetails(); displayRewardPagination(); renderTransaction(true) - // .then(() => verifyCurrentNetwork(app.transaction?.networkId || app.networkId)) // @todo: verifyCurrentNetwork + .then(() => verifyCurrentNetwork(networkId)) .catch(console.error); } function decodeClaimData(base64encodedTxData: string) { try { - const claimTxs = Value.Decode(Type.Array(claimTxT), JSON.parse(atob(base64encodedTxData))); - app.claims = claimTxs; + return Value.Decode(Type.Array(claimTxT), JSON.parse(atob(base64encodedTxData))); } catch (error) { console.error(error); setClaimMessage({ type: "Error", message: `Invalid claim data passed in URL` }); table.setAttribute(`data-claim`, "error"); + throw error; } } @@ -56,7 +58,6 @@ function displayRewardPagination() { function displayRewardDetails() { let isDetailsVisible = false; table.setAttribute(`data-details-visible`, isDetailsVisible.toString()); - const additionalDetails = document.getElementById(`additionalDetails`) as HTMLElement; additionalDetails.addEventListener("click", () => { isDetailsVisible = !isDetailsVisible; diff --git a/static/scripts/rewards/render-transaction/render-transaction.ts b/static/scripts/rewards/render-transaction/render-transaction.ts index 960230ab..768aa04c 100644 --- a/static/scripts/rewards/render-transaction/render-transaction.ts +++ b/static/scripts/rewards/render-transaction/render-transaction.ts @@ -1,7 +1,7 @@ import { app } from "../app-state"; import { networkExplorers } from "../constants"; import { claimButton, hideLoader } from "../toaster"; -import { claimErc20PermitHandlerWrapper, fetchTreasury, generateInvalidatePermitAdminControl } from "../web3/erc20-permit"; +import { claimErc20PermitHandlerWrapper, fetchFundingWallet, generateInvalidatePermitAdminControl } from "../web3/erc20-permit"; import { claimErc721PermitHandler } from "../web3/erc721-permit"; import { verifyCurrentNetwork } from "../web3/verify-current-network"; import { insertErc20PermitTableData, insertErc721PermitTableData } from "./insert-table-data"; @@ -15,64 +15,64 @@ export async function renderTransaction(nextTx?: boolean): Promise { const table = document.getElementsByTagName(`table`)[0]; if (nextTx) { - app.nextTx(); + app.nextPermit(); if (!app.claims || app.claims.length <= 1) { // already hidden } else { setPagination(document.getElementById("nextTx"), document.getElementById("previousTx")); const rewardsCount = document.getElementById("rewardsCount") as Element; - rewardsCount.innerHTML = `${app.transactionIndex + 1}/${app.claims.length} reward`; - table.setAttribute(`data-claim`, "none"); + rewardsCount.innerHTML = `${app.permitIndex + 1}/${app.claims.length} reward`; + table.setAttribute(`data-claim`, "error"); } } - if (!app.transaction) { + if (!app.permit) { hideLoader(); return false; } - verifyCurrentNetwork(app.transaction.networkId).catch(console.error); + verifyCurrentNetwork(app.permit.networkId).catch(console.error); - if (app.transaction.type === "erc20-permit") { - const treasury = await fetchTreasury(app.transaction, app.provider); + if (app.permit.type === "erc20-permit") { + const treasury = await fetchFundingWallet(app); // insert tx data into table - const requestedAmountElement = insertErc20PermitTableData(app.transaction, table, treasury); - table.setAttribute(`data-claim`, "ok"); + const requestedAmountElement = insertErc20PermitTableData(app, table, treasury); renderTokenSymbol({ - tokenAddress: app.transaction.permit.permitted.token, - ownerAddress: app.transaction.owner, - amount: app.transaction.transferDetails.requestedAmount, - explorerUrl: networkExplorers[app.transaction.networkId], + tokenAddress: app.permit.permit.permitted.token, + ownerAddress: app.permit.owner, + amount: app.permit.transferDetails.requestedAmount, + 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.transaction.transferDetails.to }).catch(console.error); + renderEnsName({ element: toElement, address: app.permit.transferDetails.to }).catch(console.error); - generateInvalidatePermitAdminControl(app.transaction).catch(console.error); + generateInvalidatePermitAdminControl(app).catch(console.error); - claimButton.element.addEventListener("click", claimErc20PermitHandlerWrapper(app.transaction)); - } else if (app.transaction.type === "erc721-permit") { - const requestedAmountElement = insertErc721PermitTableData(app.transaction, table); + claimButton.element.addEventListener("click", claimErc20PermitHandlerWrapper(app)); + table.setAttribute(`data-claim`, "ok"); + } else if (app.permit.type === "erc721-permit") { + const requestedAmountElement = insertErc721PermitTableData(app.permit, table); table.setAttribute(`data-claim`, "ok"); renderNftSymbol({ - tokenAddress: app.transaction.nftAddress, - explorerUrl: networkExplorers[app.transaction.networkId], + tokenAddress: app.permit.nftAddress, + 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.transaction.request.beneficiary }).catch(console.error); + renderEnsName({ element: toElement, address: app.permit.request.beneficiary }).catch(console.error); - claimButton.element.addEventListener("click", claimErc721PermitHandler(app.transaction)); + claimButton.element.addEventListener("click", claimErc721PermitHandler(app.permit)); } return true; diff --git a/static/scripts/rewards/render-transaction/tx-type.ts b/static/scripts/rewards/render-transaction/tx-type.ts index 23df1341..8891dcaf 100644 --- a/static/scripts/rewards/render-transaction/tx-type.ts +++ b/static/scripts/rewards/render-transaction/tx-type.ts @@ -23,19 +23,19 @@ const erc20PermitT = T.Object({ type: T.Literal("erc20-permit"), permit: T.Object({ permitted: T.Object({ - token: addressT, - amount: bigNumberT, + token: T.RegExp(/^0x[a-fA-F0-9]{40}$/), + amount: T.Union([T.RegExp(/^\d+$/), T.Number()]), }), - nonce: bigNumberT, - deadline: bigNumberT, + nonce: T.Union([T.RegExp(/^\d+$/), T.Number()]), + deadline: T.Union([T.RegExp(/^\d+$/), T.Number()]), }), transferDetails: T.Object({ - to: addressT, - requestedAmount: bigNumberT, + to: T.RegExp(/^0x[a-fA-F0-9]{40}$/), + requestedAmount: T.Union([T.RegExp(/^\d+$/), T.Number()]), }), - owner: addressT, - signature: signatureT, - networkId: networkIdT, + owner: T.RegExp(/^0x[a-fA-F0-9]{40}$/), + signature: T.RegExp(/^0x[a-fA-F0-9]+$/), + networkId: T.Number(), }); export type Erc20Permit = StaticDecode; @@ -59,10 +59,24 @@ const erc721Permit = T.Object({ 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, + deadline: bigNumberT, + }), + transferDetails: T.Object({ + to: T.RegExp(/^0x[a-fA-F0-9]{40}$/), + requestedAmount: T.Union([T.RegExp(/^\d+$/), T.Number()]), + }), }); export type Erc721Permit = StaticDecode; export const claimTxT = T.Union([erc20PermitT, erc721Permit]); -export type ClaimTx = StaticDecode; +export type RewardPermit = StaticDecode; diff --git a/static/scripts/rewards/rpc-optimization/get-optimal-provider.ts b/static/scripts/rewards/rpc-optimization/get-optimal-provider.ts index 76910d92..970ca565 100644 --- a/static/scripts/rewards/rpc-optimization/get-optimal-provider.ts +++ b/static/scripts/rewards/rpc-optimization/get-optimal-provider.ts @@ -3,24 +3,19 @@ import { AppState } from "../app-state"; import { getFastestRpcProvider } from "./get-fastest-rpc-provider"; import { testRpcPerformance } from "./test-rpc-performance"; -let optimalProvider: JsonRpcProvider; let isTestStarted = false; let isTestCompleted = false; -export async function getOptimalProvider(app: AppState): Promise { - const networkId = app.transactionNetworkId; +export async function useFastestRpc(app: AppState): Promise { + const networkId = app.permitNetworkId; + if (!networkId) throw new Error("Network ID not found"); if (!isTestCompleted && !isTestStarted) { isTestStarted = true; - testRpcPerformance(networkId) - .then(() => (isTestCompleted = true)) - .catch(console.error); + await testRpcPerformance(networkId).catch(console.error); + isTestCompleted = true; } - if (!optimalProvider) { - optimalProvider = getFastestRpcProvider(networkId); - } - app.provider = optimalProvider; - return optimalProvider; + return getFastestRpcProvider(networkId); } diff --git a/static/scripts/rewards/rpc-optimization/test-rpc-performance.ts b/static/scripts/rewards/rpc-optimization/test-rpc-performance.ts index 700ffb94..11d42b80 100644 --- a/static/scripts/rewards/rpc-optimization/test-rpc-performance.ts +++ b/static/scripts/rewards/rpc-optimization/test-rpc-performance.ts @@ -32,33 +32,36 @@ const RPC_HEADER = { "Content-Type": "application/json", }; +function raceUntilSuccess(promises: Promise[]) { + return new Promise((resolve) => { + promises.forEach((promise: Promise) => { + promise.then(resolve).catch(() => {}); + }); + }); +} + export async function testRpcPerformance(networkId: number) { const latencies: Record = JSON.parse(localStorage.getItem("rpcLatencies") || "{}"); const promises = networkRpcs[networkId].map(async (baseURL: string) => { - try { - const startTime = performance.now(); - const API = axios.create({ - baseURL, - headers: RPC_HEADER, - }); + const startTime = performance.now(); + const API = axios.create({ + baseURL, + headers: RPC_HEADER, + }); - const { data } = await API.post("", RPC_BODY).catch(() => ({ data: null })); - const endTime = performance.now(); - const latency = endTime - startTime; - if (verifyBlock(data)) { - // Save the latency in localStorage - latencies[`${baseURL}_${networkId}`] = latency; - } else { - // Save -1 in localStorage to indicate an error - latencies[`${baseURL}_${networkId}`] = -1; - } - } catch (error) { - // Save -1 in localStorage to indicate an error - latencies[`${baseURL}_${networkId}`] = -1; + const { data } = await API.post("", RPC_BODY); + const endTime = performance.now(); + const latency = endTime - startTime; + if (verifyBlock(data)) { + // Save the latency in localStorage + latencies[`${baseURL}_${networkId}`] = latency; + localStorage.setItem("rpcLatencies", JSON.stringify(latencies)); + } else { + // Throw an error to indicate an invalid block data + throw new Error(`Invalid block data from ${baseURL}`); } }); - await Promise.race(promises); - localStorage.setItem("rpcLatencies", JSON.stringify(latencies)); + await raceUntilSuccess(promises); } diff --git a/static/scripts/rewards/web3/connect-wallet.ts b/static/scripts/rewards/web3/connect-wallet.ts index ad596df6..b1353134 100644 --- a/static/scripts/rewards/web3/connect-wallet.ts +++ b/static/scripts/rewards/web3/connect-wallet.ts @@ -4,11 +4,6 @@ import { claimButton, toaster } from "../toaster"; export async function connectWallet(): Promise { try { - if (!window.ethereum) { - console.error("Ethereum provider not found"); - return null; - } - const wallet = new ethers.providers.Web3Provider(window.ethereum); const signer = wallet.getSigner(); const address = await signer.getAddress(); diff --git a/static/scripts/rewards/web3/erc20-permit.ts b/static/scripts/rewards/web3/erc20-permit.ts index ac08916a..f6121862 100644 --- a/static/scripts/rewards/web3/erc20-permit.ts +++ b/static/scripts/rewards/web3/erc20-permit.ts @@ -1,23 +1,19 @@ -import { JsonRpcProvider, JsonRpcSigner, TransactionResponse } from "@ethersproject/providers"; +import { JsonRpcSigner, TransactionResponse } from "@ethersproject/providers"; import { BigNumber, BigNumberish, Contract, ethers } from "ethers"; import { permit2Abi } from "../abis"; -import { app } from "../app-state"; +import { AppState } from "../app-state"; import { permit2Address } from "../constants"; import invalidateButton from "../invalidate-component"; import { tokens } from "../render-transaction/render-token-symbol"; import { renderTransaction } from "../render-transaction/render-transaction"; -import { Erc20Permit } from "../render-transaction/tx-type"; import { getErc20Contract } from "../rpc-optimization/getErc20Contract"; import { MetaMaskError, claimButton, errorToast, showLoader, toaster } from "../toaster"; -import { connectWallet } from "./connect-wallet"; -export async function fetchTreasury( - permit: Erc20Permit, - provider: JsonRpcProvider -): Promise<{ balance: BigNumber; allowance: BigNumber; decimals: number; symbol: string }> { +export async function fetchFundingWallet(app: AppState): Promise<{ balance: BigNumber; allowance: BigNumber; decimals: number; symbol: string }> { + const permit = app.permit; try { const tokenAddress = permit.permit.permitted.token.toLowerCase(); - const tokenContract = await getErc20Contract(tokenAddress, provider); + const tokenContract = await getErc20Contract(tokenAddress, app.provider); if (tokenAddress === tokens[0].address || tokenAddress === tokens[1].address) { const decimals = tokenAddress === tokens[0].address ? 18 : tokenAddress === tokens[1].address ? 18 : -1; @@ -42,27 +38,10 @@ export async function fetchTreasury( } } -async function connectToWallet() { - let signer: JsonRpcSigner | null = null; - try { - signer = await connectWallet(); - if (!signer) { - return null; - } - } catch (error: unknown) { - if (error instanceof Error) { - const e = error as unknown as MetaMaskError; - console.error("Error in connectWallet: ", e); - errorToast(e, e.reason); - } - } - return signer; -} - -async function checkPermitClaimability(permit: Erc20Permit, signer: JsonRpcSigner | null) { +async function checkPermitClaimability(app: AppState): Promise { let isPermitClaimable = false; try { - isPermitClaimable = await checkPermitClaimable(permit, signer, app.provider); + isPermitClaimable = await checkPermitClaimable(app); } catch (error: unknown) { if (error instanceof Error) { const e = error as unknown as MetaMaskError; @@ -87,9 +66,10 @@ async function createEthersContract(signer: JsonRpcSigner) { return permit2Contract; } -async function transferFromPermit(permit2Contract: Contract, permit: Erc20Permit) { +async function transferFromPermit(permit2Contract: Contract, app: AppState) { + const permit = app.permit; try { - const tx = await permit2Contract.permitTransferFrom(permit.permit, permit.transferDetails, permit.owner, permit.signature); + const tx = await permit2Contract.permitTransferFrom(permit, permit.transferDetails, permit.owner, permit.signature); toaster.create("info", `Transaction sent`); return tx; } catch (error: unknown) { @@ -137,20 +117,17 @@ async function renderTx() { } } -export function claimErc20PermitHandlerWrapper(permit: Erc20Permit) { +export function claimErc20PermitHandlerWrapper(app: AppState) { return async function claimErc20PermitHandler() { showLoader(); - const signer = await connectToWallet(); - if (!signer) return; - - const isPermitClaimable = await checkPermitClaimability(permit, signer); + const isPermitClaimable = await checkPermitClaimability(app); if (!isPermitClaimable) return; - const permit2Contract = await createEthersContract(signer); + const permit2Contract = await createEthersContract(app.signer); if (!permit2Contract) return; - const tx = await transferFromPermit(permit2Contract, permit); + const tx = await transferFromPermit(permit2Contract, app); if (!tx) return; const receipt = await waitForTransaction(tx); @@ -162,10 +139,10 @@ export function claimErc20PermitHandlerWrapper(permit: Erc20Permit) { }; } -export async function checkPermitClaimable(permit: Erc20Permit, signer: JsonRpcSigner | null, provider: JsonRpcProvider) { +export async function checkPermitClaimable(app: AppState): Promise { let isClaimed; try { - isClaimed = await isNonceClaimed(permit); + isClaimed = await isNonceClaimed(app); } catch (error: unknown) { console.error("Error in isNonceClaimed: ", error); return false; @@ -176,21 +153,23 @@ export async function checkPermitClaimable(permit: Erc20Permit, signer: JsonRpcS return false; } - if (permit.permit.deadline.lt(Math.floor(Date.now() / 1000))) { + const permit = app.permit.permit; + + if (permit.deadline.lt(Math.floor(Date.now() / 1000))) { toaster.create("error", `This reward has expired.`); return false; } let treasury; try { - treasury = await fetchTreasury(permit, provider); + treasury = await fetchFundingWallet(app); } catch (error: unknown) { console.error("Error in fetchTreasury: ", error); return false; } const { balance, allowance } = treasury; - const permitted = BigNumber.from(permit.permit.permitted.amount); + const permitted = BigNumber.from(permit.permitted.amount); const isSolvent = balance.gte(permitted); const isAllowed = allowance.gte(permitted); @@ -203,38 +182,33 @@ export async function checkPermitClaimable(permit: Erc20Permit, signer: JsonRpcS return false; } - if (signer) { - let user; - try { - user = (await signer.getAddress()).toLowerCase(); - } catch (error: unknown) { - console.error("Error in signer.getAddress: ", error); - return false; - } + let user; + try { + user = (await app.signer.getAddress()).toLowerCase(); + } catch (error: unknown) { + console.error("Error in signer.getAddress: ", error); + return false; + } - const beneficiary = permit.transferDetails.to.toLowerCase(); - if (beneficiary !== user) { - toaster.create("warning", `This reward is not for you.`); - return false; - } + const beneficiary = permit.transferDetails.to.toLowerCase(); + if (beneficiary !== user) { + toaster.create("warning", `This reward is not for you.`); + return false; } return true; } -export async function generateInvalidatePermitAdminControl(permit: Erc20Permit) { - const signer = await connectWallet(); - if (!signer) { - console.log("Wallet not connected"); - return; - } - +export async function generateInvalidatePermitAdminControl(app: AppState) { try { - const address = await signer.getAddress(); + const address = await app.signer.getAddress(); const user = address.toLowerCase(); - const owner = permit.owner.toLowerCase(); - if (owner !== user) { - return; + + if (app.permit) { + const owner = app.permit.owner.toLowerCase(); + if (owner !== user) { + return; + } } } catch (error) { console.error("Error getting address from signer"); @@ -246,16 +220,12 @@ export async function generateInvalidatePermitAdminControl(permit: Erc20Permit) invalidateButton.addEventListener("click", async function invalidateButtonClickHandler() { try { - const signer = await connectWallet(); - if (!signer) { - return; - } - const isClaimed = await isNonceClaimed(permit); + const isClaimed = await isNonceClaimed(app); if (isClaimed) { toaster.create("error", `This reward has already been claimed or invalidated.`); return; } - await invalidateNonce(signer, permit.permit.nonce); + await invalidateNonce(app.signer, app.permit.permit.nonce); } catch (error: unknown) { if (error instanceof Error) { const e = error as unknown as MetaMaskError; @@ -269,14 +239,14 @@ export async function generateInvalidatePermitAdminControl(permit: Erc20Permit) } //mimics https://github.com/Uniswap/permit2/blob/a7cd186948b44f9096a35035226d7d70b9e24eaf/src/SignatureTransfer.sol#L150 -export async function isNonceClaimed(permit: Erc20Permit): Promise { +export async function isNonceClaimed(app: AppState): Promise { const provider = app.provider; const permit2Contract = new ethers.Contract(permit2Address, permit2Abi, provider); - const { wordPos, bitPos } = nonceBitmap(BigNumber.from(permit.permit.nonce)); + const { wordPos, bitPos } = nonceBitmap(BigNumber.from(app.permit.permit.nonce)); - const bitmap = await permit2Contract.nonceBitmap(permit.owner, wordPos).catch((error: MetaMaskError) => { + const bitmap = await permit2Contract.nonceBitmap(app.permit.owner, wordPos).catch((error: MetaMaskError) => { console.error("Error in nonceBitmap method: ", error); throw error; }); diff --git a/static/scripts/rewards/web3/not-on-correct-network.ts b/static/scripts/rewards/web3/not-on-correct-network.ts index 137fb421..aff4c909 100644 --- a/static/scripts/rewards/web3/not-on-correct-network.ts +++ b/static/scripts/rewards/web3/not-on-correct-network.ts @@ -1,7 +1,6 @@ import { ethers } from "ethers"; import { getNetworkName } from "../constants"; -import invalidateButton from "../invalidate-component"; -import { showLoader, toaster } from "../toaster"; +import { toaster } from "../toaster"; import { switchNetwork } from "./switch-network"; export function notOnCorrectNetwork(currentNetworkId: number, desiredNetworkId: number, web3provider: ethers.providers.Web3Provider) { @@ -13,8 +12,6 @@ export function notOnCorrectNetwork(currentNetworkId: number, desiredNetworkId: if (!networkName) { toaster.create("error", `This dApp currently does not support payouts for network ID ${desiredNetworkId}`); } - showLoader(); - invalidateButton.disabled = true; switchNetwork(web3provider, desiredNetworkId).catch((error) => { console.error(error); toaster.create("error", `Please switch to the ${networkName} network to claim this reward.`); diff --git a/static/styles/rewards/claim-table.css b/static/styles/rewards/claim-table.css index 6c965bb4..be7eb874 100644 --- a/static/styles/rewards/claim-table.css +++ b/static/styles/rewards/claim-table.css @@ -260,28 +260,19 @@ table thead { table tbody { display: none; } -table[data-claim="none"] thead { - display: table-row-group; -} table[data-claim="error"] thead { display: table-row-group; } table[data-claim="ok"] thead { display: none; } -table[data-claim="none"] tbody { - display: none; -} table[data-claim="error"] tbody { display: none; } table[data-claim="ok"] tbody { display: table-row-group; } -/* -table[data-claim-rendered="true"][data-claim="none"][data-contract-loaded="true"][data-details-visible="false"] { - border: none; -} */ + #rewardRecipient a div { opacity: 0.66; }