diff --git a/.github/workflows/cypress-testing.yml b/.github/workflows/cypress-testing.yml new file mode 100644 index 00000000..b338e0a1 --- /dev/null +++ b/.github/workflows/cypress-testing.yml @@ -0,0 +1,66 @@ +name: test + +on: + push: + branches: + - main + - development + pull_request: + branches: + - main + - development + +env: + FOUNDRY_PROFILE: ci + +jobs: + tests: + name: Cypress tests + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: 20.10.0 + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Install dependencies + run: yarn install + + - name: Start Anvil + run: yarn test:anvil & + + - name: Cypress run + uses: cypress-io/github-action@v6 + with: + build: yarn run build + start: yarn test:fund, yarn start + + env: + SUPABASE_URL: "https://wfzpewmlyiozupulbuur.supabase.co" + SUPABASE_ANON_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndmenBld21seWlvenVwdWxidXVyIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTU2NzQzMzksImV4cCI6MjAxMTI1MDMzOX0.SKIL3Q0NOBaMehH0ekFspwgcu3afp3Dl9EDzPqs1nKs" + AMOUNT_IN_ETH: "0.1" + BENEFICIARY_ADDRESS: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + CHAIN_ID: "31337" + FRONTEND_URL: "http://localhost:8080" + PAYMENT_TOKEN_ADDRESS: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d" + RPC_PROVIDER_URL: "http://localhost:8545" + UBIQUIBOT_PRIVATE_KEY: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: cypress-screenshots + path: cypress/screenshots + if-no-files-found: ignore + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: cypress-videos + path: cypress/videos + if-no-files-found: ignore diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9b78ea45..09b6dad8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -58,7 +58,7 @@ jobs: script: | const { owner, repo } = context.repo; const sha = "${{ github.event.workflow_run.head_sha }}"; - + const response = await github.rest.search.issuesAndPullRequests({ q: `repo:${owner}/${repo} is:pr sha:${sha}`, per_page: 1, @@ -82,11 +82,11 @@ jobs: repo, issue_number, }); - + // Find the comment to update or create a new one if not found let existingComment = comments.data.find(comment => comment.user.login === 'github-actions[bot]'); let body = '| Preview Deployment |\n| ------------------ |\n'; - + // If the comment exists, update its body if (existingComment) { // Check if the SHA already exists in the comment body to avoid duplicates diff --git a/.gitignore b/.gitignore index d01f79ae..704a6abe 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ commit.txt # yarn2 .pnp.cjs .pnp.loader.mjs -static/dist \ No newline at end of file +static/dist + +cypress/screenshots +cypress/videos \ No newline at end of file diff --git a/.knip.jsonc b/.knip.jsonc index 3b28c2af..27f07c24 100644 --- a/.knip.jsonc +++ b/.knip.jsonc @@ -1,5 +1,5 @@ { "$schema": "https://unpkg.com/knip@5/schema.json", "entry": ["static/scripts/rewards/init.ts"], - "ignore": ["lib/**"] + "ignore": ["lib/**"], } diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 00000000..8eee7a78 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "cypress"; +import { config } from "dotenv"; + +config(); + +export default defineConfig({ + e2e: { + setupNodeEvents() {}, + baseUrl: "http://localhost:8080", + experimentalStudio: true, + }, + viewportHeight: 900, + viewportWidth: 1440, + watchForFileChanges: false, + video: true, +}); diff --git a/cypress/e2e/claim-portal-failure.cy.ts b/cypress/e2e/claim-portal-failure.cy.ts new file mode 100644 index 00000000..e1305274 --- /dev/null +++ b/cypress/e2e/claim-portal-failure.cy.ts @@ -0,0 +1,186 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { JsonRpcProvider, JsonRpcSigner } from "@ethersproject/providers"; + +const beneficiary = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + +describe("Claims Portal Failures", () => { + describe("No connection to wallet provider", () => { + beforeEach(() => { + cy.clearAllCookies(); + cy.clearAllLocalStorage(); + cy.clearAllSessionStorage(); + + setupIntercepts(); + stubEthereum(beneficiary); + + cy.visit(`/${claimUrl}`); + cy.wait(2000); + }); + + it("should handle no connected signer", () => { + /** + * This covers a user declining to connect their wallet + */ + cy.get("#additionalDetails", { timeout: 15000 }).should("be.visible").invoke("click"); + + cy.get("button[id='make-claim']").should("be.visible").click(); + cy.get("#invalidator").should("not.be.visible"); + cy.get("#claim-loader").should("not.be.visible"); + cy.get("#view-claim").should("not.be.visible").and("include.text", "View Claim"); + + cy.get("body").should("contain.text", "This reward is not for you"); + }); + }); + + describe("Failed transactions", () => { + const provider = new JsonRpcProvider("http://127.0.0.1:8545"); + const signer = provider.getSigner(); + + beforeEach(() => { + cy.clearAllCookies(); + cy.clearAllLocalStorage(); + cy.clearAllSessionStorage(); + + setupIntercepts(); + stubEthereum(beneficiary, signer); + + cy.visit(`/${claimUrl}`); + cy.wait(2000); + }); + + it("should handle feedback for a failed wallet provider transaction", () => { + cy.get("#additionalDetails", { timeout: 15000 }).should("be.visible").invoke("click"); + + cy.get("button[id='make-claim']").should("be.visible").click(); + cy.get("#claim-loader").should("be.visible"); + cy.get("#invalidator").should("not.be.visible"); + // cy.get("#claim-loader").should("not.be.visible"); // gets stuck here + }); + }); +}); + +function setupIntercepts() { + cy.intercept("POST", "*", (req) => { + // return a 404 for rpc optimization meaning no successful RPC + // to return our balanceOf and allowance calls + if (req.body.method == "eth_getBlockByNumber") { + req.reply({ + statusCode: 404, + body: { + jsonrpc: "2.0", + error: { + code: -32601, + message: "Method not found", + }, + id: 1, + }, + }); + } + + if (req.body.method == "eth_sendTransaction") { + req.reply({ + statusCode: 404, + body: { + jsonrpc: "2.0", + id: 44, + result: "0x", + }, + }); + } + + if (req.body.method == "eth_call") { + const selector = req.body.params[0].data.slice(0, 10); + + // balanceOf + if (selector == "0x70a08231") { + req.reply({ + statusCode: 200, + body: { + jsonrpc: "2.0", + id: 45, + result: "0x00000000000000000000000000000000000000000000478cf7610f95b9e70000", + }, + }); + } else if (selector == "0xdd62ed3e") { + // allowance + + req.reply({ + statusCode: 200, + body: { + jsonrpc: "2.0", + id: 46, + result: "0x0000000000000000000000000000000000c097ce7bc906e58377f59a8306ffff", + }, + }); + } + } + }); + + cy.intercept("POST", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); + cy.intercept("PATCH", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); + cy.intercept("GET", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); +} + +function stubEthereum(address?: string, signer?: JsonRpcSigner) { + cy.on("window:before:load", (win) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ((win as any).ethereum = { + isMetaMask: true, + enable: cy.stub().resolves([address]), + request: cy.stub().callsFake(async (method) => providerFunctions(method)), + on: cy.stub().callsFake((event, cb) => { + if (event == "accountsChanged") { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (win as any).ethereum.onAccountsChanged = cb; + } + }), + autoRefreshOnNetworkChange: false, + chainId: "0x7a69", + selectedAddress: address, + requestAccounts: cy.stub().resolves([address]), + send: cy.stub().callsFake(async (method) => providerFunctions(method)), + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + signer ? ((win as any).signer = signer) : null; + }); +} + +function providerFunctions(method: string) { + switch (method) { + case "eth_requestAccounts": + return [beneficiary]; + case "wallet_sendDomainMetadata": + return true; + case "wallet_addEthereumChain": + return true; + case "wallet_switchEthereumChain": + return true; + case "wallet_watchAsset": + return true; + case "eth_chainId": + return "0x7a69"; + case "eth_accounts": + return [beneficiary]; + case "eth_signTypedData_v4": + return "address"; + case "eth_estimateGas": + return "0x00"; + case "eth_sendTransaction": + return "0x"; + case "eth_call": + return "0x"; + } +} + +// placed here due to length +const claimUrl = + "?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweGU5MUQxNTNFMGI0MTUxOEEyQ2U4RGQzRDc5NDRGYTg2MzQ2M2E5N2QiLCJhbW91bnQiOiIwIn0sIm5vbmNlIjoiNTM2ODA1OTA4ODcxOTE5NzkzMTM3NDczMTk1MTc3NTIyOTQ0ODQ1MTkwNDE4NjUxMzY5Mjk1Mjg3OTQyNjA5NzI3NDg5MzU2MDY1NTAiLCJkZWFkbGluZSI6IjExNTc5MjA4OTIzNzMxNjE5NTQyMzU3MDk4NTAwODY4NzkwNzg1MzI2OTk4NDY2NTY0MDU2NDAzOTQ1NzU4NDAwNzkxMzEyOTYzOTkzNSJ9LCJ0cmFuc2ZlckRldGFpbHMiOnsidG8iOiIweGYzOUZkNmU1MWFhZDg4RjZGNGNlNmFCODgyNzI3OWNmZkZiOTIyNjYiLCJyZXF1ZXN0ZWRBbW91bnQiOiIwIn0sIm93bmVyIjoiMHg3MDk5Nzk3MEM1MTgxMmRjM0EwMTBDN2QwMWI1MGUwZDE3ZGM3OUM4Iiwic2lnbmF0dXJlIjoiMHgzMTBkOGFmYWRkY2VlYTZmNWNhNGRkMmMzNGM5ZDBhNmVlMDQzYTJhMjExZDU4Y2E4ZDMxM2I4MmViZDc0YWU4MGJiYzc5ODE4Mjc2MmU1N2M3ODE2MTljZjlhYmE3Y2ZmYTJlZjJmNTBlYzk5ZThjMjY2YWEzMzA1NjdkZTI5MjFiIiwibmV0d29ya0lkIjozMTMzN30seyJ0eXBlIjoiZXJjMjAtcGVybWl0IiwicGVybWl0Ijp7InBlcm1pdHRlZCI6eyJ0b2tlbiI6IjB4ZTkxRDE1M0UwYjQxNTE4QTJDZThEZDNENzk0NEZhODYzNDYzYTk3ZCIsImFtb3VudCI6IjkwMDAwMDAwMDAwMDAwMDAwMDAifSwibm9uY2UiOiI2Nzk4MzU5OTA4NDY0NDc4MDExODU5ODU4ODIyOTc2NDQ5NTk4MzkwNzA2MDYxMTIzODM2Nzg3NzUyMzAxNjk0NzQ5MzcyNjY4NjU5OCIsImRlYWRsaW5lIjoiMTE1NzkyMDg5MjM3MzE2MTk1NDIzNTcwOTg1MDA4Njg3OTA3ODUzMjY5OTg0NjY1NjQwNTY0MDM5NDU3NTg0MDA3OTEzMTI5NjM5OTM1In0sInRyYW5zZmVyRGV0YWlscyI6eyJ0byI6IjB4ZjM5RmQ2ZTUxYWFkODhGNkY0Y2U2YUI4ODI3Mjc5Y2ZmRmI5MjI2NiIsInJlcXVlc3RlZEFtb3VudCI6IjkwMDAwMDAwMDAwMDAwMDAwMDAifSwib3duZXIiOiIweDcwOTk3OTcwQzUxODEyZGMzQTAxMEM3ZDAxYjUwZTBkMTdkYzc5QzgiLCJzaWduYXR1cmUiOiIweDMxMGQ4YWZhZGRjZWVhNmY1Y2E0ZGQyYzM0YzlkMGE2ZWUwNDNhMmEyMTFkNThjYThkMzEzYjgyZWJkNzRhZTgwYmJjNzk4MTgyNzYyZTU3Yzc4MTYxOWNmOWFiYTdjZmZhMmVmMmY1MGVjOTllOGMyNjZhYTMzMDU2N2RlMjkyMWIiLCJuZXR3b3JrSWQiOjMxMzM3fV0="; diff --git a/cypress/e2e/claim-portal-non-web3.cy.ts b/cypress/e2e/claim-portal-non-web3.cy.ts new file mode 100644 index 00000000..3b9da74c --- /dev/null +++ b/cypress/e2e/claim-portal-non-web3.cy.ts @@ -0,0 +1,75 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +describe("Claims Portal Non-Web3", () => { + beforeEach(() => { + cy.clearAllCookies(); + cy.clearAllLocalStorage(); + cy.clearAllSessionStorage(); + + setupIntercepts(); + + cy.visit(`/${claimUrl}`); + cy.wait(2000); + }); + + describe("No window.ethereum", () => { + it("Should toast and hide buttons in a non-web3 env", () => { + cy.get("#invalidator").should("not.be.visible"); + cy.get("#claim-loader").should("not.be.visible"); + cy.get("#view-claim").should("not.be.visible"); + + cy.get("body", { timeout: 3000 }).should("contain.text", "Please use a web3 enabled browser to collect this reward."); + }); + }); + + describe("Mobile: No window.ethereum", () => { + beforeEach(() => { + cy.viewport("iphone-6"); + cy.reload(); + }); + + it("Should toast and hide buttons in a non-web3 env", () => { + cy.get("#invalidator").should("not.be.visible"); + cy.get("#claim-loader").should("not.be.visible"); + cy.get("#view-claim").should("not.be.visible"); + + cy.get("body", { timeout: 3000 }).should("contain.text", "Please use a web3 enabled browser to collect this reward."); + }); + }); +}); + +function setupIntercepts() { + cy.intercept("POST", "*", (req) => { + // return a 404 for rpc optimization meaning no successful RPC + // to return our balanceOf and allowance calls + if (req.body.method === "eth_getBlockByNumber") { + req.reply({ + statusCode: 404, + body: { + jsonrpc: "2.0", + error: { + code: -32601, + message: "Method not found", + }, + id: 1, + }, + }); + } + }); + + cy.intercept("POST", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); + cy.intercept("PATCH", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); + cy.intercept("GET", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); +} + +// placed here due to length +const claimUrl = + "?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweGU5MUQxNTNFMGI0MTUxOEEyQ2U4RGQzRDc5NDRGYTg2MzQ2M2E5N2QiLCJhbW91bnQiOiIxMDAwMDAwMDAwMDAwMDAwMCJ9LCJub25jZSI6IjEwODc2OTM3ODM4MTQ4OTY1NTIxMDM2ODQ4NzgzNzgzMDA2MDU0MjAwMzcxOTM0NTY0MzYzMjQ5MDIzMTQ1MTcyOTczMTgzNDgwMTM5MiIsImRlYWRsaW5lIjoiMTE1NzkyMDg5MjM3MzE2MTk1NDIzNTcwOTg1MDA4Njg3OTA3ODUzMjY5OTg0NjY1NjQwNTY0MDM5NDU3NTg0MDA3OTEzMTI5NjM5OTM1In0sInRyYW5zZmVyRGV0YWlscyI6eyJ0byI6IjB4ZjM5RmQ2ZTUxYWFkODhGNkY0Y2U2YUI4ODI3Mjc5Y2ZmRmI5MjI2NiIsInJlcXVlc3RlZEFtb3VudCI6IjEwMDAwMDAwMDAwMDAwMDAwIn0sIm93bmVyIjoiMHg3MDk5Nzk3MEM1MTgxMmRjM0EwMTBDN2QwMWI1MGUwZDE3ZGM3OUM4Iiwic2lnbmF0dXJlIjoiMHg4YWZmYWU1ZTA5YTkyN2QwYjUzNDQ1M2Y4NTE5ZWVlZDE5MzY5MTBkZWFhOGY5YTA0OTM1ODQzNDMzNDA5NmExMTg5ZmVkM2MxNzgyZmU0ZGI5ZTNhMDg2NWVkYjc3ZDczYzliMDliOTgxMTBmN2Q0ZWEyY2Y5ZDBhM2Q1YjhjYzFjIiwibmV0d29ya0lkIjozMTMzN30seyJ0eXBlIjoiZXJjMjAtcGVybWl0IiwicGVybWl0Ijp7InBlcm1pdHRlZCI6eyJ0b2tlbiI6IjB4ZTkxRDE1M0UwYjQxNTE4QTJDZThEZDNENzk0NEZhODYzNDYzYTk3ZCIsImFtb3VudCI6IjkwMDAwMDAwMDAwMDAwMDAwMDAifSwibm9uY2UiOiI1NjQzNjc4ODI2MzUwOTQ3NTY2NzAwNzA4MDA5ODQ5MDM0MDE1OTExMzYxMjM5NTUyMTA3Mjk3NDkxNzcyNDA2Mzg0NDY2Mjc0NDEzMiIsImRlYWRsaW5lIjoiMTE1NzkyMDg5MjM3MzE2MTk1NDIzNTcwOTg1MDA4Njg3OTA3ODUzMjY5OTg0NjY1NjQwNTY0MDM5NDU3NTg0MDA3OTEzMTI5NjM5OTM1In0sInRyYW5zZmVyRGV0YWlscyI6eyJ0byI6IjB4ZjM5RmQ2ZTUxYWFkODhGNkY0Y2U2YUI4ODI3Mjc5Y2ZmRmI5MjI2NiIsInJlcXVlc3RlZEFtb3VudCI6IjkwMDAwMDAwMDAwMDAwMDAwMDAifSwib3duZXIiOiIweDcwOTk3OTcwQzUxODEyZGMzQTAxMEM3ZDAxYjUwZTBkMTdkYzc5QzgiLCJzaWduYXR1cmUiOiIweDhhZmZhZTVlMDlhOTI3ZDBiNTM0NDUzZjg1MTllZWVkMTkzNjkxMGRlYWE4ZjlhMDQ5MzU4NDM0MzM0MDk2YTExODlmZWQzYzE3ODJmZTRkYjllM2EwODY1ZWRiNzdkNzNjOWIwOWI5ODExMGY3ZDRlYTJjZjlkMGEzZDViOGNjMWMiLCJuZXR3b3JrSWQiOjMxMzM3fV0="; diff --git a/cypress/e2e/claim-portal-success.cy.ts b/cypress/e2e/claim-portal-success.cy.ts new file mode 100644 index 00000000..16f13255 --- /dev/null +++ b/cypress/e2e/claim-portal-success.cy.ts @@ -0,0 +1,155 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { JsonRpcProvider, JsonRpcSigner } from "@ethersproject/providers"; +import { Wallet } from "ethers"; + +const beneficiary = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; // anvil +const SENDER_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; // anvil + +describe("Claims Portal Success", () => { + beforeEach(() => { + cy.clearAllCookies(); + cy.clearAllLocalStorage(); + cy.clearAllSessionStorage(); + setupStubs(); + + setupIntercepts(); + + cy.visit(`/${claimUrl}`); + cy.wait(2000); + }); + describe("Success", () => { + it("should successfully claim a permit", () => { + cy.get("#additionalDetails", { timeout: 15000 }).should("be.visible").invoke("click"); + + cy.get('table[data-make-claim="ok"]').should("exist").and("include.text", "337888.4 WXDAI"); + + cy.get("button[id='make-claim']").invoke("click"); + + cy.get("#invalidator").should("not.be.visible"); + + cy.get("#claim-loader").should("be.visible").as("loader"); + + cy.wait(5000); // required for the action to complete + + cy.get("@loader").should("not.be.visible"); + + cy.get("#view-claim").should("be.visible").and("include.text", "View Claim"); + + // anvil confirms it instantly so there is two notifications + cy.get("body").should("contain.text", "Transaction sent"); + cy.get("body").should("contain.text", "Claim Complete"); + }); + }); + + describe("Not meant for you", () => { + it("should fail to claim a permit not for meant for them", () => { + cy.visit(`/${notMeantForYouPermit}`).then(() => { + cy.wait(2000); + }); + cy.get("#additionalDetails", { timeout: 15000 }).should("be.visible").invoke("click"); + + cy.get('table[data-make-claim="ok"]').should("exist"); + + cy.get("button[id='make-claim']").invoke("click"); + + cy.get("#invalidator").should("not.be.visible"); + + cy.get("#claim-loader").should("be.visible"); + + cy.get("#view-claim").should("not.be.visible"); + + cy.get("body").should("contain.text", "This reward is not for you"); + }); + }); +}); + +function setupStubs() { + const provider = new JsonRpcProvider("http://localhost:8545"); + const signer = provider.getSigner(beneficiary); + const wallet = new Wallet(SENDER_PRIVATE_KEY, provider); + + stubEthereum(signer); + + return { provider, signer, wallet }; +} + +function setupIntercepts() { + cy.intercept("POST", "*", (req) => { + // capturing the RPC optimization calls + if (req.body.method === "eth_getBlockByNumber") { + req.reply({ + statusCode: 200, + body: cy.fixture("eth_getBlockByNumber.json"), + }); + } + }); + + cy.intercept("POST", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); + cy.intercept("PATCH", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); + cy.intercept("GET", "https://wfzpewmlyiozupulbuur.supabase.co/rest/v1/*", { + statusCode: 200, + body: {}, + }); +} + +function stubEthereum(signer: JsonRpcSigner) { + // Stubbing the ethereum object + cy.on("window:before:load", (win) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ((win as any).ethereum = { + isMetaMask: true, + enable: cy.stub().resolves(["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]), + request: cy.stub().callsFake(async (method) => providerFunctions(method)), + on: cy.stub().callsFake((event, cb) => { + if (event === "accountsChanged") { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (win as any).ethereum.onAccountsChanged = cb; + } + }), + autoRefreshOnNetworkChange: false, + chainId: "0x7a69", + selectedAddress: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + requestAccounts: cy.stub().resolves(["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]), + send: cy.stub().callsFake(async (method) => providerFunctions(method)), + getSigner: () => signer, + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ((win as any).signer = signer); + }); +} + +function providerFunctions(method: string) { + switch (method) { + case "eth_requestAccounts": + return ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]; + case "wallet_sendDomainMetadata": + return true; + case "wallet_addEthereumChain": + return true; + case "wallet_switchEthereumChain": + return true; + case "wallet_watchAsset": + return true; + case "eth_chainId": + return "0x7a69"; + case "eth_accounts": + return ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]; + case "eth_signTypedData_v4": + return "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + case "eth_estimateGas": + return "0x7a69"; + } +} + +// placed here due to length +const claimUrl = + "?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweGU5MUQxNTNFMGI0MTUxOEEyQ2U4RGQzRDc5NDRGYTg2MzQ2M2E5N2QiLCJhbW91bnQiOiIxMDAwMDAwMDAwMDAwMDAwMCJ9LCJub25jZSI6IjEwODc2OTM3ODM4MTQ4OTY1NTIxMDM2ODQ4NzgzNzgzMDA2MDU0MjAwMzcxOTM0NTY0MzYzMjQ5MDIzMTQ1MTcyOTczMTgzNDgwMTM5MiIsImRlYWRsaW5lIjoiMTE1NzkyMDg5MjM3MzE2MTk1NDIzNTcwOTg1MDA4Njg3OTA3ODUzMjY5OTg0NjY1NjQwNTY0MDM5NDU3NTg0MDA3OTEzMTI5NjM5OTM1In0sInRyYW5zZmVyRGV0YWlscyI6eyJ0byI6IjB4ZjM5RmQ2ZTUxYWFkODhGNkY0Y2U2YUI4ODI3Mjc5Y2ZmRmI5MjI2NiIsInJlcXVlc3RlZEFtb3VudCI6IjEwMDAwMDAwMDAwMDAwMDAwIn0sIm93bmVyIjoiMHg3MDk5Nzk3MEM1MTgxMmRjM0EwMTBDN2QwMWI1MGUwZDE3ZGM3OUM4Iiwic2lnbmF0dXJlIjoiMHg4YWZmYWU1ZTA5YTkyN2QwYjUzNDQ1M2Y4NTE5ZWVlZDE5MzY5MTBkZWFhOGY5YTA0OTM1ODQzNDMzNDA5NmExMTg5ZmVkM2MxNzgyZmU0ZGI5ZTNhMDg2NWVkYjc3ZDczYzliMDliOTgxMTBmN2Q0ZWEyY2Y5ZDBhM2Q1YjhjYzFjIiwibmV0d29ya0lkIjozMTMzN30seyJ0eXBlIjoiZXJjMjAtcGVybWl0IiwicGVybWl0Ijp7InBlcm1pdHRlZCI6eyJ0b2tlbiI6IjB4ZTkxRDE1M0UwYjQxNTE4QTJDZThEZDNENzk0NEZhODYzNDYzYTk3ZCIsImFtb3VudCI6IjkwMDAwMDAwMDAwMDAwMDAwMDAifSwibm9uY2UiOiI1NjQzNjc4ODI2MzUwOTQ3NTY2NzAwNzA4MDA5ODQ5MDM0MDE1OTExMzYxMjM5NTUyMTA3Mjk3NDkxNzcyNDA2Mzg0NDY2Mjc0NDEzMiIsImRlYWRsaW5lIjoiMTE1NzkyMDg5MjM3MzE2MTk1NDIzNTcwOTg1MDA4Njg3OTA3ODUzMjY5OTg0NjY1NjQwNTY0MDM5NDU3NTg0MDA3OTEzMTI5NjM5OTM1In0sInRyYW5zZmVyRGV0YWlscyI6eyJ0byI6IjB4ZjM5RmQ2ZTUxYWFkODhGNkY0Y2U2YUI4ODI3Mjc5Y2ZmRmI5MjI2NiIsInJlcXVlc3RlZEFtb3VudCI6IjkwMDAwMDAwMDAwMDAwMDAwMDAifSwib3duZXIiOiIweDcwOTk3OTcwQzUxODEyZGMzQTAxMEM3ZDAxYjUwZTBkMTdkYzc5QzgiLCJzaWduYXR1cmUiOiIweDhhZmZhZTVlMDlhOTI3ZDBiNTM0NDUzZjg1MTllZWVkMTkzNjkxMGRlYWE4ZjlhMDQ5MzU4NDM0MzM0MDk2YTExODlmZWQzYzE3ODJmZTRkYjllM2EwODY1ZWRiNzdkNzNjOWIwOWI5ODExMGY3ZDRlYTJjZjlkMGEzZDViOGNjMWMiLCJuZXR3b3JrSWQiOjMxMzM3fV0="; + +const notMeantForYouPermit = + "?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweGU5MUQxNTNFMGI0MTUxOEEyQ2U4RGQzRDc5NDRGYTg2MzQ2M2E5N2QiLCJhbW91bnQiOiIxMDAwMDAwMDAwMDAwMDAwMCJ9LCJub25jZSI6IjkxOTk3MjEyMjMyMTcxMDcyMTI5OTIwODIzNjMwOTY3ODE5ODgwNTcyNjcyMTc2ODcwNjU4MzE2Nzk4MjUxNzU4OTQ2MzQ1NDY2OTA1IiwiZGVhZGxpbmUiOiIxMTU3OTIwODkyMzczMTYxOTU0MjM1NzA5ODUwMDg2ODc5MDc4NTMyNjk5ODQ2NjU2NDA1NjQwMzk0NTc1ODQwMDc5MTMxMjk2Mzk5MzUifSwidHJhbnNmZXJEZXRhaWxzIjp7InRvIjoiMHhiYTEyMjIyMjIyMjI4ZDhiYTQ0NTk1OGE3NWEwNzA0ZDU2NmJmMmM4IiwicmVxdWVzdGVkQW1vdW50IjoiMTAwMDAwMDAwMDAwMDAwMDAifSwib3duZXIiOiIweDcwOTk3OTcwQzUxODEyZGMzQTAxMEM3ZDAxYjUwZTBkMTdkYzc5QzgiLCJzaWduYXR1cmUiOiIweDNiYzBjOTA5NzA1NmRhNmJkMzA4NmM4MGRiM2RmZDAzODNjNjgxN2FlZTAwMDExZDFlYTI3NzFkZWVlYjUxNjg1MWE3ZmYyY2UzNGUxNmI1ZjFkNTY1NGRmYzQ5MTk1YjQ4YmE5YmY1YmY0YTllMGRlOGY4ODc3YjBkMTY4NGRmMWMiLCJuZXR3b3JrSWQiOjMxMzM3fSx7InR5cGUiOiJlcmMyMC1wZXJtaXQiLCJwZXJtaXQiOnsicGVybWl0dGVkIjp7InRva2VuIjoiMHhlOTFEMTUzRTBiNDE1MThBMkNlOERkM0Q3OTQ0RmE4NjM0NjNhOTdkIiwiYW1vdW50IjoiOTAwMDAwMDAwMDAwMDAwMDAwMCJ9LCJub25jZSI6Ijc3MDE3MDM2NzU4NzI2MzU5MTc4ODExMjM0Njk4NTMxMjE2NjYwODc4NzU0NjMwMDc4NzAzMDY4NzA4NzM3MDEzNTYxODIxMDQwODkwIiwiZGVhZGxpbmUiOiIxMTU3OTIwODkyMzczMTYxOTU0MjM1NzA5ODUwMDg2ODc5MDc4NTMyNjk5ODQ2NjU2NDA1NjQwMzk0NTc1ODQwMDc5MTMxMjk2Mzk5MzUifSwidHJhbnNmZXJEZXRhaWxzIjp7InRvIjoiMHhiYTEyMjIyMjIyMjI4ZDhiYTQ0NTk1OGE3NWEwNzA0ZDU2NmJmMmM4IiwicmVxdWVzdGVkQW1vdW50IjoiOTAwMDAwMDAwMDAwMDAwMDAwMCJ9LCJvd25lciI6IjB4NzA5OTc5NzBDNTE4MTJkYzNBMDEwQzdkMDFiNTBlMGQxN2RjNzlDOCIsInNpZ25hdHVyZSI6IjB4M2JjMGM5MDk3MDU2ZGE2YmQzMDg2YzgwZGIzZGZkMDM4M2M2ODE3YWVlMDAwMTFkMWVhMjc3MWRlZWViNTE2ODUxYTdmZjJjZTM0ZTE2YjVmMWQ1NjU0ZGZjNDkxOTViNDhiYTliZjViZjRhOWUwZGU4Zjg4NzdiMGQxNjg0ZGYxYyIsIm5ldHdvcmtJZCI6MzEzMzd9XQ=="; diff --git a/cypress/fixtures/erc20-permits.json b/cypress/fixtures/erc20-permits.json new file mode 100644 index 00000000..fb056520 --- /dev/null +++ b/cypress/fixtures/erc20-permits.json @@ -0,0 +1,38 @@ +[ + { + "type": "erc20-permit", + "permit": { + "permitted": { + "token": "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", + "amount": "95255555000000000000" + }, + "nonce": "36367598329506163629701178851688339747065676639093029479054969572911320530479", + "deadline": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + }, + "transferDetails": { + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "requestedAmount": "95255555000000000000" + }, + "owner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "signature": "0x02fcd7e46df6f4e0437f8a309bd8dbcdbd66de04ed47f308db9548cf609909ce0b8e4d2e5031ff5daf959fc09cbfc95d606493d0ecd7ec0e9d6def4300a909f81b", + "networkId": 100 + }, + { + "type": "erc20-permit", + "permit": { + "permitted": { + "token": "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", + "amount": "95255555000000000000" + }, + "nonce": "36367598329506163629701178851688339747065676639093029479054969572911320530479", + "deadline": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + }, + "transferDetails": { + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "requestedAmount": "95255555000000000000" + }, + "owner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "signature": "0x02fcd7e46df6f4e0437f8a309bd8dbcdbd66de04ed47f308db9548cf609909ce0b8e4d2e5031ff5daf959fc09cbfc95d606493d0ecd7ec0e9d6def4300a909f81b", + "networkId": 100 + } +] diff --git a/cypress/fixtures/erc721-permits.json b/cypress/fixtures/erc721-permits.json new file mode 100644 index 00000000..4e581b8a --- /dev/null +++ b/cypress/fixtures/erc721-permits.json @@ -0,0 +1,78 @@ +[ + { + "type": "erc721-permit", + "permit": { + "permitted": { + "token": "0xAa1bfC0e51969415d64d6dE74f27CDa0587e645b", + "amount": "1" + }, + "nonce": "313327", + "deadline": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + }, + "transferDetails": { + "to": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "requestedAmount": "1" + }, + "owner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "signature": "0xfc56bee3c8f90cb0a5a528808f37883ac1c88e21b59647e6fab2640c34692a9636914cbc98b15b4f43d95d2c69c6832ce19e150c809cf14dc9f06e6025ee649f1b", + "networkId": 100, + "nftMetadata": { + "GITHUB_ORGANIZATION_NAME": "ubiquity", + "GITHUB_REPOSITORY_NAME": "pay.ubq.fi", + "GITHUB_ISSUE_ID": "1", + "GITHUB_USERNAME": "testing", + "GITHUB_CONTRIBUTION_TYPE": "issue" + }, + "request": { + "beneficiary": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "deadline": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "keys": [ + "0x1c474488c03c83ad98714cfe3a60c752036f92ab8378227adfcb13585f115c5c", + "0x0480f34997cc2d3abc2dafd652b25652ba845458fc3e3692f28acbd4920a37ea", + "0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", + "0x5f16f4c7f149ac4f9510d9cf8cf384038ad348b3bcdc01915f95de12df9d1b02", + "0xa33d0fabbfddf3db8e1458550edc0ab7e061990c85809d25341eab0885973d7d" + ], + "nonce": "313327", + "values": ["ubiquity", "pay.ubq.fi", "1", "testing", "issue"] + } + }, + { + "type": "erc721-permit", + "permit": { + "permitted": { + "token": "0xAa1bfC0e51969415d64d6dE74f27CDa0587e645b", + "amount": "1" + }, + "nonce": "3137", + "deadline": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + }, + "transferDetails": { + "to": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "requestedAmount": "1" + }, + "owner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "signature": "0x49ef799bc3e859d8b850f58f696dd44a89c4209ec5677ce1aa6cb0b1312a3a1b14a594ef7af293efffeef0669c2817e7c4031cffe035ce084e34dd5a4d32ea5f1b", + "networkId": 100, + "nftMetadata": { + "GITHUB_ORGANIZATION_NAME": "ubiquity", + "GITHUB_REPOSITORY_NAME": "pay.ubq.fi", + "GITHUB_ISSUE_ID": "1", + "GITHUB_USERNAME": "testing", + "GITHUB_CONTRIBUTION_TYPE": "issue" + }, + "request": { + "beneficiary": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "deadline": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "keys": [ + "0x1c474488c03c83ad98714cfe3a60c752036f92ab8378227adfcb13585f115c5c", + "0x0480f34997cc2d3abc2dafd652b25652ba845458fc3e3692f28acbd4920a37ea", + "0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", + "0x5f16f4c7f149ac4f9510d9cf8cf384038ad348b3bcdc01915f95de12df9d1b02", + "0xa33d0fabbfddf3db8e1458550edc0ab7e061990c85809d25341eab0885973d7d" + ], + "nonce": "3137", + "values": ["ubiquity", "pay.ubq.fi", "1", "testing", "issue"] + } + } +] diff --git a/cypress/fixtures/get-block-number.json b/cypress/fixtures/get-block-number.json new file mode 100644 index 00000000..a86ead3e --- /dev/null +++ b/cypress/fixtures/get-block-number.json @@ -0,0 +1,96 @@ +{ + "jsonrpc": "2.0", + "result": { + "author": "0x54e191b01aa9c1f61aa5c3bce8d00956f32d3e71", + "difficulty": "0x0", + "extraData": "0x4e65746865726d696e64", + "gasLimit": "0x1036640", + "gasUsed": "0x6763e5", + "hash": "0x407c72c5ae29ac6ffe6e3cefc592bf8d2bf4a8e0057ccfd1c4c7f11ab365977e", + "logsBloom": "0x640beb0d6578149cea2f950699258ce63d056392a4d0d2701834000a48381453bb508c109686c16aa69094201a0a45092c35a83549352240e42020b8560ff8e8027b89170c0713a4829dd68d009c805ada3edf8f9b3659464f0b4481408a6539006de405824712c52e5017a82d83ea100200ce70617cb003a5d397d00ae5044bb21ec5d0a152ad6168e463941a539082441dc04bfa5405c3031ab821b6d2565605e2c444a798ca0e5a82222b1723c0030340250187cc5905604527122707c500a00f951648950e600490a29601d3430224b87b03680a55d44080913ca64d6e3212c44645088c4194ade9576565a57c31f6005c1351783059a51969601c56b4c9", + "miner": "0x54e191b01aa9c1f61aa5c3bce8d00956f32d3e71", + "mixHash": "0x285fc6bec5ed8d335f2fe4f1e8526cfebf2f650de390d24a80d7a26353a86263", + "nonce": "0x0000000000000000", + "number": "0x1faad3c", + "parentHash": "0x2b0482bcb01362281ea62af8f424020c60b5b2d091ba3a91c2c5409facf3f4b5", + "receiptsRoot": "0x19b2d7960b3aa6d85bb89c796eed7e563f98879bb2923781514bc1a2e9afcab2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x3dd5", + "stateRoot": "0x4bbbf4dab0b363cd3a469ee23f2ba34c04fb9531a7812f35d19a456b95ce2df3", + "totalDifficulty": "0x182cd9fffffffffffffffffffffffffea9528a2", + "timestamp": "0x66093cb8", + "baseFeePerGas": "0x49", + "transactions": [ + "0x6dd7181abf5c33ef988a846e13c3cd079ae9070aacef2235885c95ecbf659dd2", + "0x5841c70fd3471baece987021f09b5c7fbb8eb5abd6b69a80be0a9e47a6bb2a7c", + "0xc0cb042f01e692db2ea8b1910220cdc6184f365f6e0dfe3938c225f556017d02", + "0xb51a6e7a02566b2ef0a85c688adf020c8a247e7d408bf7793c888c011eea0d8a", + "0x1bb4985d19175fc3431526c9ce01f423e47a49c674de58cad75950e343e90c69", + "0x9144a9e63da9787961a61ee26df25b3c468fc7da40e12e9b80d5c398e9d4728a", + "0xc176772b1f8280dd86239d435f4485c4d7041d61f5d1cf9787632c80775f38e8", + "0xccb751a6ec7f12c5213c0f3293573754c096d02f88599f9814fd285ec5c4769c", + "0x39b88eda15263cc35866775a2575f6aa537507e60e1e871ea9780fc304ac9200", + "0x8bf8a95ba856c20e5d0fc01d9b2625fb0b3b56c9773969903eb9d94b87bdfa5c", + "0x47509dd87254961d30e28941bffcf3d670a7b5e16076b06f38e636cf5246a61b", + "0xe7fd644dc604ecdaa81e1f956b98ad870f526c30fca6c6581afa2639b199d554", + "0x418a3b92316cf510ed8a015bd2d95c1989c504eea5952da98e6b0677ef30bbae" + ], + "transactionsRoot": "0xb4a1895451a4b3a9b7ff72f7226604289aa54043c22141d29d7e697311fe8f71", + "uncles": [], + "withdrawals": [ + { + "index": "0x1e3b6bd", + "validatorIndex": "0x2eea4", + "address": "0x9f03bfbdf1e026cedb7f606e740a0b3aa16044e8", + "amount": "0xb710e7" + }, + { + "index": "0x1e3b6be", + "validatorIndex": "0x2eea5", + "address": "0x9f03bfbdf1e026cedb7f606e740a0b3aa16044e8", + "amount": "0xb78afa" + }, + { + "index": "0x1e3b6bf", + "validatorIndex": "0x2eea6", + "address": "0x9f03bfbdf1e026cedb7f606e740a0b3aa16044e8", + "amount": "0xb6f51c" + }, + { + "index": "0x1e3b6c0", + "validatorIndex": "0x2eea7", + "address": "0x9f03bfbdf1e026cedb7f606e740a0b3aa16044e8", + "amount": "0xb6de93" + }, + { + "index": "0x1e3b6c1", + "validatorIndex": "0x2eea8", + "address": "0x9f03bfbdf1e026cedb7f606e740a0b3aa16044e8", + "amount": "0xb7ab98" + }, + { + "index": "0x1e3b6c2", + "validatorIndex": "0x2eea9", + "address": "0x9f03bfbdf1e026cedb7f606e740a0b3aa16044e8", + "amount": "0xb7a5f6" + }, + { + "index": "0x1e3b6c3", + "validatorIndex": "0x2eeaa", + "address": "0x9f03bfbdf1e026cedb7f606e740a0b3aa16044e8", + "amount": "0xb6f80a" + }, + { + "index": "0x1e3b6c4", + "validatorIndex": "0x2eeab", + "address": "0x9f03bfbdf1e026cedb7f606e740a0b3aa16044e8", + "amount": "0xb73e9b" + } + ], + "withdrawalsRoot": "0xa22437a0b85a24e7844bdacd756525c8868ff16f707fe55fcd7e714467cce022", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0xe7d34f48290317240c3ef8f58427fa95f641bd3b56b405c1142301e62ab58e9f" + }, + "id": 1 +} diff --git a/cypress/scripts/anvil.ts b/cypress/scripts/anvil.ts new file mode 100644 index 00000000..d8c22635 --- /dev/null +++ b/cypress/scripts/anvil.ts @@ -0,0 +1,80 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { spawn } from "child_process"; + +const url = "http://localhost:8545"; + +const anvil = spawn("anvil", ["--chain-id", "31337", "--fork-url", "https://gnosis.drpc.org", "--host", "127.0.0.1", "--port", "8545"], { + stdio: "inherit", +}); + +setTimeout(() => { + console.log(`\n\n Anvil setup complete \n\n`); +}, 5000); + +// anvil --chain-id 31337 --fork-url https://eth.llamarpc.com --host 127.0.0.1 --port 8546 + +spawn("cast", ["rpc", "--rpc-url", url, "anvil_impersonateAccount", "0xba12222222228d8ba445958a75a0704d566bf2c8"], { + stdio: "inherit", +}); +spawn( + "cast", + [ + "send", + "--rpc-url", + url, + "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", + "--unlocked", + "--from", + "0xba12222222228d8ba445958a75a0704d566bf2c8", + "transfer(address,uint256)(bool)", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "337888400000000000000000", + ], + { + stdio: "inherit", + } +); +spawn( + "cast", + [ + "send", + "--rpc-url", + url, + "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", + "--unlocked", + "--from", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "approve(address,uint256)(bool)", + "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "9999999999999991111111119999999999999999", + ], + { + stdio: "inherit", + } +); +spawn( + "cast", + [ + "send", + "--rpc-url", + url, + "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", + "--unlocked", + "--from", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "approve(address,uint256)(bool)", + "0x000000000022D473030F116dDEE9F6B43aC78BA3", + "999999999999999111119999999999999999", + ], + { + stdio: "inherit", + } +); + +anvil.on("close", (code) => { + console.log(`Anvil exited with code ${code}`); +}); + +anvil.on("error", (err) => { + console.error("Failed to start Anvil", err); +}); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 00000000..95857aea --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,37 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 00000000..6a173d6f --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import "./commands"; + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/package.json b/package.json index e78109ca..67095103 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,12 @@ "format:prettier": "prettier --write .", "format:cspell": "cspell **/*", "prepare": "husky install", - "postinstall": "git submodule update --init --recursive" + "postinstall": "git submodule update --init --recursive", + "test:anvil": "tsx cypress/scripts/anvil.ts", + "test:start": "yarn start", + "test:run": "cypress run", + "test:open": "cypress open", + "test:fund": "cast rpc --rpc-url http://127.0.0.1:8545 anvil_impersonateAccount 0xba12222222228d8ba445958a75a0704d566bf2c8 & cast send --rpc-url http://127.0.0.1:8545 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d --unlocked --from 0xba12222222228d8ba445958a75a0704d566bf2c8 'transfer(address,uint256)(bool)' 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 337888400000000000000000 & cast send --rpc-url http://127.0.0.1:8545 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d --unlocked --from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 'approve(address,uint256)(bool)' 0x000000000022D473030F116dDEE9F6B43aC78BA3 9999999999999991111111119999999999999999 & cast send --rpc-url http://127.0.0.1:8545 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d --unlocked --from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 'approve(address,uint256)(bool)' 0x000000000022D473030F116dDEE9F6B43aC78BA3 999999999999999111119999999999999999" }, "keywords": [ "typescript", @@ -40,6 +45,7 @@ "axios": "^1.6.7", "dotenv": "^16.4.4", "ethers": "^5.7.2", + "node-fetch": "^3.3.2", "npm-run-all": "^4.1.5" }, "devDependencies": { @@ -53,6 +59,7 @@ "@typescript-eslint/eslint-plugin": "^7.0.1", "@typescript-eslint/parser": "^7.0.1", "cspell": "^8.3.2", + "cypress": "13.7.0", "esbuild": "^0.20.0", "eslint": "^8.56.0", "eslint-plugin-sonarjs": "^0.24.0", diff --git a/scripts/typescript/generate-erc20-permit-url.ts b/scripts/typescript/generate-erc20-permit-url.ts index 36dc3a5f..fb23fec0 100644 --- a/scripts/typescript/generate-erc20-permit-url.ts +++ b/scripts/typescript/generate-erc20-permit-url.ts @@ -61,10 +61,10 @@ export async function generateERC20Permit() { const permitTransferFromData = createPermitTransferFromData(process.env.AMOUNT_IN_ETH); const signature = await signTypedData(myWallet, permitTransferFromData); - // const permitTransferFromData2 = createPermitTransferFromData("9"); + const permitTransferFromData2 = createPermitTransferFromData("9"); const sig = await signTypedData(myWallet, permitTransferFromData); - const txData = [createTxData(myWallet, permitTransferFromData, signature), createTxData(myWallet, permitTransferFromData, sig)]; + const txData = [createTxData(myWallet, permitTransferFromData, signature), createTxData(myWallet, permitTransferFromData2, sig)]; const base64encodedTxData = Buffer.from(JSON.stringify(txData)).toString("base64"); 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 b7f885a6..b6654236 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 @@ -35,6 +35,7 @@ export async function readClaimDataFromUrl(app: AppState) { } catch (e) { toaster.create("error", `${e}`); } + if (window.ethereum) { try { app.signer = await connectWallet(); diff --git a/static/scripts/rewards/web3/connect-wallet.ts b/static/scripts/rewards/web3/connect-wallet.ts index 81a29cb7..93167bb4 100644 --- a/static/scripts/rewards/web3/connect-wallet.ts +++ b/static/scripts/rewards/web3/connect-wallet.ts @@ -20,6 +20,10 @@ export async function connectWallet(): Promise { return signer; } catch (error: unknown) { + // For testing purposes + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (window.location.href.includes("localhost") && (window as any).signer) return (window as any).signer; + if (error instanceof Error) { console.error(error); if (error?.message?.includes("missing provider")) { diff --git a/static/scripts/rewards/web3/erc20-permit.ts b/static/scripts/rewards/web3/erc20-permit.ts index 116140df..1a9a54c7 100644 --- a/static/scripts/rewards/web3/erc20-permit.ts +++ b/static/scripts/rewards/web3/erc20-permit.ts @@ -119,6 +119,8 @@ export function claimErc20PermitHandlerWrapper(app: AppState) { const isPermitClaimable = await checkPermitClaimability(app); if (!isPermitClaimable) return; + if (!app.signer) return; + const permit2Contract = new ethers.Contract(permit2Address, permit2Abi, app.signer); if (!permit2Contract) return; @@ -177,9 +179,10 @@ async function checkPermitClaimable(app: AppState): Promise { return false; } - let user: string; + let user: string | undefined; try { - user = (await app.signer.getAddress()).toLowerCase(); + const address = await app.signer?.getAddress(); + user = address?.toLowerCase(); } catch (error: unknown) { console.error("Error in signer.getAddress: ", error); return false; @@ -197,8 +200,8 @@ async function checkPermitClaimable(app: AppState): Promise { export async function checkRenderMakeClaimControl(app: AppState) { try { - const address = await app.signer.getAddress(); - const user = address.toLowerCase(); + const address = await app.signer?.getAddress(); + const user = address?.toLowerCase(); if (app.reward) { const beneficiary = app.reward.beneficiary.toLowerCase(); @@ -216,8 +219,8 @@ export async function checkRenderMakeClaimControl(app: AppState) { export async function checkRenderInvalidatePermitAdminControl(app: AppState) { try { - const address = await app.signer.getAddress(); - const user = address.toLowerCase(); + const address = await app.signer?.getAddress(); + const user = address?.toLowerCase(); if (app.reward) { const owner = app.reward.owner.toLowerCase(); @@ -243,7 +246,9 @@ invalidateButton.addEventListener("click", async function invalidateButtonClickH buttonController.hideInvalidator(); return; } - await invalidateNonce(app.signer, app.reward.nonce); + + if (!app.signer) return; + await invalidateNonce(app.signer, app.reward.permit.nonce); } catch (error: unknown) { if (error instanceof Error) { const e = error as unknown as MetaMaskError; diff --git a/tsconfig.json b/tsconfig.json index d22ebe77..18ac9fe6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src", "static", "build", "scripts/typescript", "globals.d.ts"], + "include": ["src", "static", "build", "scripts/typescript", "globals.d.ts", "cypress/**/*.ts", "cypress.config.ts"], "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ @@ -25,12 +25,12 @@ /* Modules */ "module": "commonjs" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "Node16", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "moduleResolution": "Node16", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + "types": ["cypress"] /* Specify type package names to be included without being referenced in a source file. */, // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */