diff --git a/.github/ISSUE_TEMPLATE/2-addImageForm.yml b/.github/ISSUE_TEMPLATE/2-addImageForm.yml index 5b643b9d..c496942b 100644 --- a/.github/ISSUE_TEMPLATE/2-addImageForm.yml +++ b/.github/ISSUE_TEMPLATE/2-addImageForm.yml @@ -36,14 +36,18 @@ body: id: address attributes: label: Address + description: "Must be a valid 0x-prefixed address (40 hexadecimal characters). Example: 0x1234...abcd" placeholder: 0x... validations: required: true + pattern: "^0x[a-fA-F0-9]{40}$" + - type: input id: imageUrl attributes: label: Image URL - description: Ideally a 256x256 PNG or SVG file. But we'll take care of optimizing it later. - placeholder: https://gateway.pinata.cloud/ipfs/Qme9B6jRpGtZsRFcPjHvA5T4ugFuL4c3SzWfxyMPa59AMo + description: "Ideally a 256x256 PNG or SVG file. But we'll take care of optimizing it later." + placeholder: "https://gateway.pinata.cloud/ipfs/Qme9B6jRpGtZsRFcPjHvA5T4ugFuL4c3SzWfxyMPa59AMo" validations: required: true + pattern: "^(https?://[\\w.-]+(?:/[\\w\\-.~!$&'()*+,;=:@%]*)*)$" diff --git a/.github/workflows/validate-token-address.yml b/.github/workflows/validate-token-address.yml new file mode 100644 index 00000000..128b4263 --- /dev/null +++ b/.github/workflows/validate-token-address.yml @@ -0,0 +1,135 @@ +name: Validate Token Address + +on: + issues: + types: [opened, edited] + +permissions: + contents: read + issues: write + +jobs: + validate-address: + if: ${{ contains(join(github.event.issue.labels.*.name, ','), 'addImage') }} + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + with: + node-version: 20 + + - name: Install ethers.js + run: npm install ethers + + - name: Extract address and network + id: extract + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: | + const body = context.payload.issue.body; + + // Extract Network + const networkMatch = body.match(/Network\s*\n\s*(.*)/); + const network = networkMatch ? networkMatch[1].trim() : null; + + // Extract Address + const addressMatch = body.match(/Address\s*\n\s*(0x[a-fA-F0-9]{40})/); + const address = addressMatch ? addressMatch[1].trim() : null; + + core.setOutput('network', network || ''); + core.setOutput('address', address || ''); + + - name: Validate on-chain address + id: validate + timeout-minutes: 2 + env: + NETWORK: ${{ steps.extract.outputs.network }} + ADDRESS: ${{ steps.extract.outputs.address }} + run: | + node -e " + (() => { + const {ethers} = import('ethers') + const network = process.env.NETWORK; + const address = process.env.ADDRESS; + + if (!address) { + console.log("No address provided."); + process.exit(1); + } + + if (!ethers.isAddress(address)) { + console.log("Address is not valid"); + process.exit(1); + } + + const rpcMap = { + MAINNET: "https://rpc.mevblocker.io", + GNOSIS_CHAIN: "https://gnosis.oat.farm", + ARBITRUM_ONE: "https://arbitrum-one.public.blastapi.io", + BASE: "https://base.drpc.org", + POLYGON: "https://polygon-bor-rpc.publicnode.com", + AVALANCHE: "https://api.avax.network/ext/bc/C/rpc", + BNB: "https://public-bsc-mainnet.fastnode.io", + LENS: "https://lens.drpc.org", + }; + + const rpcUrl = rpcMap[network]; + if (!rpcUrl) { + console.log("No RPC for network, skipping check."); + process.exit(0); + } + + const provider = new ethers.JsonRpcProvider(rpcUrl); + + provider + .getCode(address) + .then((code) => { + if (code === "0x") { + console.log("Address not found on chain."); + process.exit(1); + } else { + console.log("Address exists on chain."); + } + }) + .catch((err) => { + if ( + err && + (err.code === "INVALID_ARGUMENT" || /invalid address/i.test(String(err))) + ) { + console.log("Address not found on chain."); + process.exit(1); + } + console.error("Error checking address:", err); + process.exit(1); + }); + })() + " + + - name: Add comment and label if invalid + if: failure() + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: | + const issue_number = context.issue.number; + const address = '${{ steps.extract.outputs.address }}'; + const network = '${{ steps.extract.outputs.network }}'; + + // Add comment + await github.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + body: `⚠️ The address \`${address || 'N/A'}\` does not exist or is invalid on network \`${network || 'N/A'}\`. Please verify.` + }); + + // Add label + await github.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['invalid-address'] + });