diff --git a/.vscode/settings.json b/.vscode/settings.json index 30dc850..88ca7c2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "editor.defaultFormatter": "biomejs.biome", - "editor.formatOnSave": true + "editor.defaultFormatter": "biomejs.biome", + "editor.formatOnSave": true } diff --git a/biome.json b/biome.json index 2ce3d21..2af4e50 100644 --- a/biome.json +++ b/biome.json @@ -1,30 +1,31 @@ { - "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json", - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "ignore": ["out/"], - "rules": { - "recommended": true, - "suspicious": { - "noExplicitAny": "off" - }, - "performance": { - "noDelete": "off" - }, - "style": { - "useTemplate": "off" - } - } - }, - "formatter": { - "enabled": true, - "formatWithErrors": true, - "indentStyle": "space", - "indentWidth": 4, - "lineWidth": 120, - "ignore": [] + "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "ignore": ["out/"], + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off" + }, + "performance": { + "noDelete": "off" + }, + "style": { + "useTemplate": "off", + "noNonNullAssertion": "off" + } } + }, + "formatter": { + "enabled": true, + "formatWithErrors": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 120, + "ignore": [] + } } diff --git a/bun.lockb b/bun.lockb index f1f4634..96dcbed 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 05fa051..47d9cf1 100644 --- a/package.json +++ b/package.json @@ -1,43 +1,44 @@ { - "name": "catapulta-verify", - "version": "1.1.1", - "author": "@kartojal (https://catapulta.sh/)", - "repository": { - "type": "git", - "url": "git+https://github.com/catapulta-sh/catapulta-verify.git" - }, - "main": "./out/index.mjs", - "private": false, - "publishConfig": { - "access": "public" - }, - "files": [ - "out/index.mjs" - ], - "devDependencies": { - "@biomejs/biome": "1.4.1", - "@types/chalk": "^2.2.0", - "bun-types": "latest", - "chalk": "^5.3.0", - "dotenv": "^16.3.1", - "ts-command-line-args": "^2.5.1" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "bin": { - "catapulta-verify": "out/index.mjs" - }, - "bugs": { - "url": "https://github.com/catapulta-sh/catapulta-verify/issues", - "email": "david@web3ops.co" - }, - "homepage": "https://github.com/catapulta-sh/catapulta-verify#readme", - "license": "MIT", - "scripts": { - "build": "bun build ./src/index.ts --outfile=out/index.mjs --target node", - "postbuild": "bunx rexreplace '^(#!.+\\n)?' '$1import { createRequire as createImportMetaRequire } from \"module\"; import.meta.require ||= (id) => createImportMetaRequire(import.meta.url)(id);\\n' -GM out/index.mjs", - "lint:fix": "bunx @biomejs/biome check . --apply", - "lint": "bunx @biomejs/biome check ." - } + "name": "catapulta-verify", + "version": "1.1.1", + "author": "@kartojal (https://catapulta.sh/)", + "repository": { + "type": "git", + "url": "git+https://github.com/catapulta-sh/catapulta-verify.git" + }, + "main": "./out/index.mjs", + "private": false, + "publishConfig": { + "access": "public" + }, + "files": ["out/index.mjs"], + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@types/chalk": "^2.2.0", + "bun-types": "1.1.36", + "chalk": "^5.3.0", + "dotenv": "^16.4.5", + "ts-command-line-args": "^2.5.1", + "typescript": "^5.6.3", + "vitest": "^2.1.5" + }, + "bin": { + "catapulta-verify": "out/index.mjs" + }, + "bugs": { + "url": "https://github.com/catapulta-sh/catapulta-verify/issues", + "email": "david@web3ops.co" + }, + "homepage": "https://github.com/catapulta-sh/catapulta-verify#readme", + "license": "MIT", + "scripts": { + "build": "bun build ./src/index.ts --outfile=out/index.mjs --target node", + "postbuild": "bunx rexreplace '^(#!.+\\n)?' '$1import { createRequire as createImportMetaRequire } from \"module\"; import.meta.require ||= (id) => createImportMetaRequire(import.meta.url)(id);\\n' -GM out/index.mjs", + "lint:fix": "bunx @biomejs/biome check . --write", + "lint": "bunx @biomejs/biome check .", + "test": "vitest" + }, + "dependencies": { + "@bgd-labs/rpc-env": "^2.0.1" + } } diff --git a/prepare.ts b/prepare.ts new file mode 100644 index 0000000..23e25ca --- /dev/null +++ b/prepare.ts @@ -0,0 +1,40 @@ +import { writeFileSync } from "node:fs"; + +type RouteScanResponse = { + items: { + workspace: string; + chainId: string; + }[]; +}; + +async function getRoutescan() { + const result = await fetch("https://cdn-canary.routescan.io/api/evm/all/explorers"); + const data = (await result.json()) as RouteScanResponse; + const formatted = data.items.map((d) => ({ + api: `https://api.routescan.io/v2/network/${d.workspace}/evm/${d.chainId}/etherscan`, + explorer: `${d.chainId}.routescan.io`, + chainId: Number(d.chainId), + })); + writeFileSync("src/explorers/routescan.json", JSON.stringify(formatted, null, 2)); +} + +type EtherscanResponse = { + result: { + blockexplorer: string; + chainid: string; + apiurl: string; + }[]; +}; +async function getEtherscan() { + const result = await fetch("https://api.etherscan.io/v2/chainlist"); + const data = (await result.json()) as EtherscanResponse; + const formatted = data.result.map((d) => ({ + api: d.apiurl, + explorer: d.blockexplorer, + chainId: Number(d.chainid), + })); + writeFileSync("src/explorers/etherscan.json", JSON.stringify(formatted, null, 2)); +} + +getRoutescan(); +getEtherscan(); diff --git a/src/cli-args.ts b/src/cli-args.ts index 512f40f..522a5dd 100644 --- a/src/cli-args.ts +++ b/src/cli-args.ts @@ -2,47 +2,47 @@ import { parse } from "ts-command-line-args"; import type { InputParams } from "./types"; export const args = parse( - { - broadcastPath: { - type: String, - alias: "b", - description: - "Path of the broadcast report json file to analyze the txs in search of smart contracts deployments.", - }, - help: { - type: Boolean, - optional: true, - alias: "h", - description: "Prints this usage guide", - }, - rpcUrl: { - type: String, - optional: true, - alias: "r", - description: - "RPC URL to fetch transaction details, mandatory to support for debug_traceTransaction. You can use Alchemy or Quicknode providers if available.", - }, - explorerUrl: { - type: String, - optional: true, - alias: "e", - description: "Etherscan API endpoint", - }, - etherscanApiKey: { - type: String, - optional: true, - alias: "k", - description: "Etherscan API key", - }, + { + broadcastPath: { + type: String, + alias: "b", + description: + "Path of the broadcast report json file to analyze the txs in search of smart contracts deployments.", }, - { - helpArg: "help", - headerContentSections: [ - { - header: "catapulta-verify", - content: - 'Verify smart contracts at Etherscan using Forge broadcast reports and debug_traceTransaction. This is the "lite" open source version of Catapulta.sh, a zero config deployment tool for Foundry scripts.\n\nUsage:\n\n npx catapulta-verify -b broadcast/Script.sol/11155111/run-latest.json --etherscan-url --etherscan-api-key \n\nDocumentation\n\nCheck out https://github.com/catapulta-sh/verify repository to see the full documentation.\n\nCatapulta, plug-n-play deployment platform\n\nIf you want to remove all boilerplate configs like RPC Urls, Etherscan API keys, be able to automatize all verifications without long commands at deployment time, check out https://catapulta.sh website to discover the full version of Catapulta.', - }, - ], + help: { + type: Boolean, + optional: true, + alias: "h", + description: "Prints this usage guide", }, + rpcUrl: { + type: String, + optional: true, + alias: "r", + description: + "RPC URL to fetch transaction details, mandatory to support for debug_traceTransaction. You can use Alchemy or Quicknode providers if available.", + }, + explorerUrl: { + type: String, + optional: true, + alias: "e", + description: "Etherscan API endpoint", + }, + etherscanApiKey: { + type: String, + optional: true, + alias: "k", + description: "Etherscan API key", + }, + }, + { + helpArg: "help", + headerContentSections: [ + { + header: "catapulta-verify", + content: + 'Verify smart contracts at Etherscan using Forge broadcast reports and debug_traceTransaction. This is the "lite" open source version of Catapulta.sh, a zero config deployment tool for Foundry scripts.\n\nUsage:\n\n npx catapulta-verify -b broadcast/Script.sol/11155111/run-latest.json --etherscan-url --etherscan-api-key \n\nDocumentation\n\nCheck out https://github.com/catapulta-sh/verify repository to see the full documentation.\n\nCatapulta, plug-n-play deployment platform\n\nIf you want to remove all boilerplate configs like RPC Urls, Etherscan API keys, be able to automatize all verifications without long commands at deployment time, check out https://catapulta.sh website to discover the full version of Catapulta.', + }, + ], + }, ); diff --git a/src/config.ts b/src/config.ts index 8af7ece..5155259 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,159 +6,17 @@ export const VERIFY_VERSION = packageJson.version; export const VERBOSE = !!process.env.VERBOSE; export type ExplorerConfig = { - API_URL: string; - SITE_URL?: string; - API_KEY?: string; + API_URL: string; + SITE_URL?: string; + API_KEY?: string; }; -export type NetworkConfig = { - RPC: string; - explorers: ExplorerConfig[]; -}; - -export const NETWORK_CONFIGS: Record = { - [Networks.polygon]: { - RPC: process.env.RPC_POLYGON || "", - explorers: [ - { - API_URL: "https://api.polygonscan.com/api", - API_KEY: process.env.ETHERSCAN_API_KEY_POLYGON || "", - SITE_URL: "https://polygonscan.com", - }, - ], - }, - [Networks.main]: { - RPC: process.env.RPC_MAINNET || "", - explorers: [ - { - API_URL: "https://api.etherscan.io/api", - API_KEY: process.env.ETHERSCAN_API_KEY_MAINNET || "", - SITE_URL: "https://etherscan.io", - }, - ], - }, - [Networks.arbitrum]: { - RPC: process.env.RPC_ARBITRUM || "", - explorers: [ - { - API_URL: "https://api.arbiscan.io/api", - API_KEY: process.env.ETHERSCAN_API_KEY_ARBITRUM || "", - SITE_URL: "https://arbiscan.io", - }, - ], - }, - [Networks.avalanche]: { - RPC: process.env.RPC_AVALANCHE || "", - explorers: [ - { - API_URL: "https://api.snowscan.xyz/api", - API_KEY: process.env.ETHERSCAN_API_KEY_AVALANCHE || "", - SITE_URL: "https://snowscan.xyz", - }, - { - API_URL: "https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan", - SITE_URL: "https://snowtrace.io", - }, - ], - }, - [Networks.optimism]: { - RPC: process.env.RPC_OPTIMISM || "", - explorers: [ - { - API_URL: "https://api-optimistic.etherscan.io/api", - API_KEY: process.env.ETHERSCAN_API_KEY_OPTIMISM || "", - SITE_URL: "https://optimistic.etherscan.io", - }, - ], - }, - [Networks.sepolia]: { - RPC: process.env.RPC_SEPOLIA || "", - explorers: [ - { - API_URL: "https://api-sepolia.etherscan.io/api", - API_KEY: process.env.ETHERSCAN_API_KEY_SEPOLIA || "", - SITE_URL: "https://sepolia.etherscan.io", - }, - ], - }, - [Networks.bnb]: { - RPC: process.env.RPC_BNB || "", - explorers: [ - { - API_URL: "https://api.bscscan.com/api", - API_KEY: process.env.ETHERSCAN_API_KEY_BNB || "", - SITE_URL: "https://bscscan.com", - }, - ], - }, - [Networks.bnbTestnet]: { - RPC: process.env.RPC_BNB_TESTNET || "", - explorers: [ - { - API_URL: "https://api-testnet.bscscan.com/api", - API_KEY: process.env.ETHERSCAN_API_KEY_BNB_TESTNET || "", - SITE_URL: "https://testnet.bscscan.com", - }, - ], - }, - [Networks.base]: { - RPC: process.env.RPC_BASE || "", - explorers: [ - { - API_URL: "https://api.basescan.org/api", - API_KEY: process.env.ETHERSCAN_API_KEY_BASE || "", - SITE_URL: "https://testnet.bscscan.com", - }, - ], - }, - [Networks.gnosis]: { - RPC: process.env.RPC_GNOSIS || "", - explorers: [ - { - API_URL: "https://api.gnosisscan.io/api", - API_KEY: process.env.ETHERSCAN_API_KEY_GNOSIS || "", - SITE_URL: "https://gnosisscan.io", - }, - ], - }, - [Networks.scroll]: { - RPC: process.env.RPC_SCROLL || "", - explorers: [ - { - API_URL: "https://api.scrollscan.com/api", - API_KEY: process.env.ETHERSCAN_API_KEY_SCROLL || "", - SITE_URL: "https://scrollscan.com", - }, - ], - }, - [Networks.zkevm]: { - RPC: process.env.RPC_ZKEVM || "", - explorers: [ - { - API_URL: "https://api-zkevm.polygonscan.com/api", - API_KEY: process.env.ETHERSCAN_API_KEY_ZKEVM || "", - SITE_URL: "https://zkevm.polygonscan.com", - }, - ], - }, - [Networks.celo]: { - RPC: process.env.RPC_CELO || "", - explorers: [ - { - API_URL: "https://api.celoscan.io/api", - API_KEY: process.env.ETHERSCAN_API_KEY_CELO || "", - SITE_URL: "https://celoscan.io", - }, - ], - }, - [Networks.metis]: { - RPC: process.env.RPC_METIS || "", - explorers: [ - { - API_URL: "https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan", - SITE_URL: "https://explorer.metis.io", - }, - { API_URL: "https://andromeda-explorer.metis.io/api ", SITE_URL: "https://andromeda-explorer.metis.io" }, - ], - }, +// custom explorer configs +export const EXPLORER_CONFIGS: Record = { + [Networks.metis]: [ + { + API_URL: "https://andromeda-explorer.metis.io/api ", + SITE_URL: "https://andromeda-explorer.metis.io", + }, + ], }; diff --git a/src/explorers/etherscan.json b/src/explorers/etherscan.json new file mode 100644 index 0000000..eac4a2b --- /dev/null +++ b/src/explorers/etherscan.json @@ -0,0 +1,257 @@ +[ + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://etherscan.io", + "chainId": 1 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.etherscan.io", + "chainId": 11155111 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.etherscan.io", + "chainId": 17000 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://bscscan.com", + "chainId": 56 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://testnet.bscscan.com", + "chainId": 97 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://polygonscan.com", + "chainId": 137 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://amoy.polygonscan.com/", + "chainId": 80002 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://zkevm.polygonscan.com/", + "chainId": 1101 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://cardona-zkevm.polygonscan.com/", + "chainId": 2442 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://basescan.org", + "chainId": 8453 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.basescan.org/", + "chainId": 84532 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://arbiscan.io", + "chainId": 42161 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://nova.arbiscan.io/", + "chainId": 42170 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.arbiscan.io/", + "chainId": 421614 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://lineascan.build/", + "chainId": 59144 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.lineascan.build/", + "chainId": 59141 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://ftmscan.com", + "chainId": 250 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://testnet.ftmscan.com/", + "chainId": 4002 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://blastscan.io/", + "chainId": 81457 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.blastscan.io/", + "chainId": 168587773 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://optimistic.etherscan.io/", + "chainId": 10 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia-optimism.etherscan.io/", + "chainId": 11155420 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://snowscan.xyz/", + "chainId": 43114 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://testnet.snowscan.xyz/", + "chainId": 43113 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://bttcscan.com/", + "chainId": 199 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://testnet.bttcscan.com/", + "chainId": 1028 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://celoscan.io", + "chainId": 42220 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://alfajores.celoscan.io/", + "chainId": 44787 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://cronoscan.com", + "chainId": 25 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://fraxscan.com/", + "chainId": 252 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://holesky.fraxscan.com/", + "chainId": 2522 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://gnosisscan.io/", + "chainId": 100 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://kromascan.com/", + "chainId": 255 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.kromascan.com/", + "chainId": 2358 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://mantlescan.xyz/", + "chainId": 5000 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.mantlescan.xyz/", + "chainId": 5003 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://moonbeam.moonscan.io/", + "chainId": 1284 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://moonriver.moonscan.io/", + "chainId": 1285 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://moonbase.moonscan.io/", + "chainId": 1287 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://opbnb.bscscan.com/", + "chainId": 204 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://opbnb-testnet.bscscan.com/", + "chainId": 5611 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://scrollscan.com/", + "chainId": 534352 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.scrollscan.com/", + "chainId": 534351 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://taikoscan.io/", + "chainId": 167000 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://hekla.taikoscan.io/", + "chainId": 167009 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://wemixscan.com/", + "chainId": 1111 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://testnet.wemixscan.com/", + "chainId": 1112 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://era.zksync.network/", + "chainId": 324 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia-era.zksync.network/", + "chainId": 300 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://xaiscan.io/", + "chainId": 660279 + }, + { + "api": "https://api.etherscan.io/v2/api", + "explorer": "https://sepolia.xaiscan.io/", + "chainId": 37714555429 + } +] diff --git a/src/explorers/etherscan.ts b/src/explorers/etherscan.ts new file mode 100644 index 0000000..394eeb2 --- /dev/null +++ b/src/explorers/etherscan.ts @@ -0,0 +1,10 @@ +import etherscan from "./etherscan.json"; + +export function getEtherscan(chainId: number) { + const explorer = etherscan.find((e) => e.chainId === chainId); + if (explorer) + return { + API_URL: "https://api.etherscan.io/v2/api", + API_KEY: process.env.ETHERSCAN_API_KEY, + }; +} diff --git a/src/explorers/routescan.json b/src/explorers/routescan.json new file mode 100644 index 0000000..761d722 --- /dev/null +++ b/src/explorers/routescan.json @@ -0,0 +1,767 @@ +[ + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/1010/etherscan", + "explorer": "1010.routescan.io", + "chainId": 1010 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/56/etherscan", + "explorer": "56.routescan.io", + "chainId": 56 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/97/etherscan", + "explorer": "97.routescan.io", + "chainId": 97 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/43288/etherscan", + "explorer": "43288.routescan.io", + "chainId": 43288 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/301/etherscan", + "explorer": "301.routescan.io", + "chainId": 301 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/1294/etherscan", + "explorer": "1294.routescan.io", + "chainId": 1294 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/719/etherscan", + "explorer": "719.routescan.io", + "chainId": 719 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/7700/etherscan", + "explorer": "7700.routescan.io", + "chainId": 7700 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/169/etherscan", + "explorer": "169.routescan.io", + "chainId": 169 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/13068200/etherscan", + "explorer": "13068200.routescan.io", + "chainId": 13068200 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/999999/etherscan", + "explorer": "999999.routescan.io", + "chainId": 999999 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/debug/etherscan", + "explorer": "debug.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan", + "explorer": "43114.routescan.io", + "chainId": 43114 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/53935/etherscan", + "explorer": "53935.routescan.io", + "chainId": 53935 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/432204/etherscan", + "explorer": "432204.routescan.io", + "chainId": 432204 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/8888/etherscan", + "explorer": "8888.routescan.io", + "chainId": 8888 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/1234/etherscan", + "explorer": "1234.routescan.io", + "chainId": 1234 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/10507/etherscan", + "explorer": "10507.routescan.io", + "chainId": 10507 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/7979/etherscan", + "explorer": "7979.routescan.io", + "chainId": 7979 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/2044/etherscan", + "explorer": "2044.routescan.io", + "chainId": 2044 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/333000333/etherscan", + "explorer": "333000333.routescan.io", + "chainId": 333000333 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/6119/etherscan", + "explorer": "6119.routescan.io", + "chainId": 6119 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/3011/etherscan", + "explorer": "3011.routescan.io", + "chainId": 3011 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan", + "explorer": "1088.routescan.io", + "chainId": 1088 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/324/etherscan", + "explorer": "324.routescan.io", + "chainId": 324 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/1/etherscan", + "explorer": "1.routescan.io", + "chainId": 1 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/5000/etherscan", + "explorer": "5000.routescan.io", + "chainId": 5000 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/288/etherscan", + "explorer": "288.routescan.io", + "chainId": 288 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/14/etherscan", + "explorer": "14.routescan.io", + "chainId": 14 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/8453/etherscan", + "explorer": "8453.routescan.io", + "chainId": 8453 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/19/etherscan", + "explorer": "19.routescan.io", + "chainId": 19 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/10/etherscan", + "explorer": "10.routescan.io", + "chainId": 10 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/56288/etherscan", + "explorer": "56288.routescan.io", + "chainId": 56288 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/88888/etherscan", + "explorer": "88888.routescan.io", + "chainId": 88888 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/424/etherscan", + "explorer": "424.routescan.io", + "chainId": 424 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/7777777/etherscan", + "explorer": "7777777.routescan.io", + "chainId": 7777777 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/4337/etherscan", + "explorer": "4337.routescan.io", + "chainId": 4337 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/291/etherscan", + "explorer": "291.routescan.io", + "chainId": 291 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/34443/etherscan", + "explorer": "34443.routescan.io", + "chainId": 34443 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/81457/etherscan", + "explorer": "81457.routescan.io", + "chainId": 81457 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/62707/etherscan", + "explorer": "62707.routescan.io", + "chainId": 62707 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/70953/etherscan", + "explorer": "70953.routescan.io", + "chainId": 70953 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/7887/etherscan", + "explorer": "7887.routescan.io", + "chainId": 7887 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/7560/etherscan", + "explorer": "7560.routescan.io", + "chainId": 7560 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/252/etherscan", + "explorer": "252.routescan.io", + "chainId": 252 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/167000/etherscan", + "explorer": "167000.routescan.io", + "chainId": 167000 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/185/etherscan", + "explorer": "185.routescan.io", + "chainId": 185 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/8008/etherscan", + "explorer": "8008.routescan.io", + "chainId": 8008 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/254/etherscan", + "explorer": "254.routescan.io", + "chainId": 254 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/888888888/etherscan", + "explorer": "888888888.routescan.io", + "chainId": 888888888 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/33979_2/etherscan", + "explorer": "33979_2.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/65536_2/etherscan", + "explorer": "65536_2.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/183/etherscan", + "explorer": "183.routescan.io", + "chainId": 183 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/10849/etherscan", + "explorer": "10849.routescan.io", + "chainId": 10849 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/504441/etherscan", + "explorer": "504441.routescan.io", + "chainId": 504441 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/5566/etherscan", + "explorer": "5566.routescan.io", + "chainId": 5566 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/20240603/etherscan", + "explorer": "20240603.routescan.io", + "chainId": 20240603 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/357/etherscan", + "explorer": "357.routescan.io", + "chainId": 357 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/480/etherscan", + "explorer": "480.routescan.io", + "chainId": 480 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/151/etherscan", + "explorer": "151.routescan.io", + "chainId": 151 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/1853/etherscan", + "explorer": "1853.routescan.io", + "chainId": 1853 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/710420/etherscan", + "explorer": "710420.routescan.io", + "chainId": 710420 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/42026/etherscan", + "explorer": "42026.routescan.io", + "chainId": 42026 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/303/etherscan", + "explorer": "303.routescan.io", + "chainId": 303 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/2818/etherscan", + "explorer": "2818.routescan.io", + "chainId": 2818 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/166/etherscan", + "explorer": "166.routescan.io", + "chainId": 166 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/255/etherscan", + "explorer": "255.routescan.io", + "chainId": 255 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/43113/etherscan", + "explorer": "43113.routescan.io", + "chainId": 43113 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/432201/etherscan", + "explorer": "432201.routescan.io", + "chainId": 432201 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/335/etherscan", + "explorer": "335.routescan.io", + "chainId": 335 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/9270/etherscan", + "explorer": "9270.routescan.io", + "chainId": 9270 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/3012/etherscan", + "explorer": "3012.routescan.io", + "chainId": 3012 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/3939/etherscan", + "explorer": "3939.routescan.io", + "chainId": 3939 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/16/etherscan", + "explorer": "16.routescan.io", + "chainId": 16 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/114/etherscan", + "explorer": "114.routescan.io", + "chainId": 114 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/9728/etherscan", + "explorer": "9728.routescan.io", + "chainId": 9728 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/88882/etherscan", + "explorer": "88882.routescan.io", + "chainId": 88882 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/167008/etherscan", + "explorer": "167008.routescan.io", + "chainId": 167008 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/17000/etherscan", + "explorer": "17000.routescan.io", + "chainId": 17000 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/686669576/etherscan", + "explorer": "686669576.routescan.io", + "chainId": 686669576 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/431234/etherscan", + "explorer": "431234.routescan.io", + "chainId": 431234 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/31335/etherscan", + "explorer": "31335.routescan.io", + "chainId": 31335 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/80085/etherscan", + "explorer": "80085.routescan.io", + "chainId": 80085 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/11155111/etherscan", + "explorer": "11155111.routescan.io", + "chainId": 11155111 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/168587773/etherscan", + "explorer": "168587773.routescan.io", + "chainId": 168587773 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/919/etherscan", + "explorer": "919.routescan.io", + "chainId": 919 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/80002/etherscan", + "explorer": "80002.routescan.io", + "chainId": 80002 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/4202/etherscan", + "explorer": "4202.routescan.io", + "chainId": 4202 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/4460/etherscan", + "explorer": "4460.routescan.io", + "chainId": 4460 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/999999999/etherscan", + "explorer": "999999999.routescan.io", + "chainId": 999999999 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/49321/etherscan", + "explorer": "49321.routescan.io", + "chainId": 49321 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/779672/etherscan", + "explorer": "779672.routescan.io", + "chainId": 779672 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/173750/etherscan", + "explorer": "173750.routescan.io", + "chainId": 173750 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/28882/etherscan", + "explorer": "28882.routescan.io", + "chainId": 28882 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/10880/etherscan", + "explorer": "10880.routescan.io", + "chainId": 10880 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/167009/etherscan", + "explorer": "167009.routescan.io", + "chainId": 167009 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/920637907288165/etherscan", + "explorer": "920637907288165.routescan.io", + "chainId": 920637907288165 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/749/etherscan", + "explorer": "749.routescan.io", + "chainId": 749 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/20241133/etherscan", + "explorer": "20241133.routescan.io", + "chainId": 20241133 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/80084/etherscan", + "explorer": "80084.routescan.io", + "chainId": 80084 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/2522/etherscan", + "explorer": "2522.routescan.io", + "chainId": 2522 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/233/etherscan", + "explorer": "233.routescan.io", + "chainId": 233 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/28122024/etherscan", + "explorer": "28122024.routescan.io", + "chainId": 28122024 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/84532/etherscan", + "explorer": "84532.routescan.io", + "chainId": 84532 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/11155420/etherscan", + "explorer": "11155420.routescan.io", + "chainId": 11155420 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/111557560/etherscan", + "explorer": "111557560.routescan.io", + "chainId": 111557560 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/1687/etherscan", + "explorer": "1687.routescan.io", + "chainId": 1687 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/55551/etherscan", + "explorer": "55551.routescan.io", + "chainId": 55551 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/80008/etherscan", + "explorer": "80008.routescan.io", + "chainId": 80008 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/164_4/etherscan", + "explorer": "164_4.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/2233/etherscan", + "explorer": "2233.routescan.io", + "chainId": 2233 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/153_2/etherscan", + "explorer": "153_2.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/26659/etherscan", + "explorer": "26659.routescan.io", + "chainId": 26659 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/3397901/etherscan", + "explorer": "3397901.routescan.io", + "chainId": 3397901 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/7222/etherscan", + "explorer": "7222.routescan.io", + "chainId": 7222 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/7589/etherscan", + "explorer": "7589.routescan.io", + "chainId": 7589 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/421614/etherscan", + "explorer": "421614.routescan.io", + "chainId": 421614 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/10888/etherscan", + "explorer": "10888.routescan.io", + "chainId": 10888 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/1946/etherscan", + "explorer": "1946.routescan.io", + "chainId": 1946 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/8082/etherscan", + "explorer": "8082.routescan.io", + "chainId": 8082 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/64165/etherscan", + "explorer": "64165.routescan.io", + "chainId": 64165 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/7210/etherscan", + "explorer": "7210.routescan.io", + "chainId": 7210 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/4801/etherscan", + "explorer": "4801.routescan.io", + "chainId": 4801 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/70805_2/etherscan", + "explorer": "70805_2.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/70800/etherscan", + "explorer": "70800.routescan.io", + "chainId": 70800 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/555666/etherscan", + "explorer": "555666.routescan.io", + "chainId": 555666 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/42069/etherscan", + "explorer": "42069.routescan.io", + "chainId": 42069 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/1301/etherscan", + "explorer": "1301.routescan.io", + "chainId": 1301 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/11124/etherscan", + "explorer": "11124.routescan.io", + "chainId": 11124 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/2358/etherscan", + "explorer": "2358.routescan.io", + "chainId": 2358 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/123456789_2/etherscan", + "explorer": "123456789_2.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/763373/etherscan", + "explorer": "763373.routescan.io", + "chainId": 763373 + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/3636/etherscan", + "explorer": "3636.routescan.io", + "chainId": 3636 + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/testnet/evm/all/etherscan", + "explorer": "all.routescan.io", + "chainId": null + }, + { + "api": "https://api.routescan.io/v2/network/mainnet/evm/bitcoin/etherscan", + "explorer": "bitcoin.routescan.io", + "chainId": null + } +] diff --git a/src/explorers/routescan.ts b/src/explorers/routescan.ts new file mode 100644 index 0000000..dcd0998 --- /dev/null +++ b/src/explorers/routescan.ts @@ -0,0 +1,6 @@ +import routescan from "./routescan.json"; + +export function getRouteScan(chainId: number) { + const explorer = routescan.find((r) => r.chainId === chainId); + if (explorer) return { API_URL: explorer.api }; +} diff --git a/src/index.ts b/src/index.ts index 036956d..54e1cf1 100755 --- a/src/index.ts +++ b/src/index.ts @@ -2,10 +2,13 @@ import { existsSync } from "node:fs"; import { exit } from "node:process"; +import { getRPCUrl } from "@bgd-labs/rpc-env"; import chalk from "chalk"; import "dotenv/config"; import { args } from "./cli-args"; -import { NETWORK_CONFIGS, VERIFY_VERSION } from "./config"; +import { EXPLORER_CONFIGS, VERIFY_VERSION } from "./config"; +import { getEtherscan } from "./explorers/etherscan"; +import { getRouteScan } from "./explorers/routescan"; import { callTraceVerifier } from "./utils/calltrace-verifier"; import { loadArtifacts, loadBuildInfo } from "./utils/foundry-ffi"; import { loadJson } from "./utils/json"; @@ -16,68 +19,73 @@ delete process.env.FOUNDRY_LIBRARIES; delete process.env.DAPP_LIBRARIES; const main = async () => { - const broadcastPath = args.broadcastPath; - if (!broadcastPath) { - } - if (!existsSync(broadcastPath)) { - console.log(`Broadcast report not found at path ${broadcastPath}. Exiting.`); - process.exit(404); - } - const parsedRun = await loadJson(broadcastPath); + const broadcastPath = args.broadcastPath; + if (!broadcastPath) { + } + if (!existsSync(broadcastPath)) { + console.log(`Broadcast report not found at path ${broadcastPath}. Exiting.`); + process.exit(404); + } + const parsedRun = await loadJson(broadcastPath); - if (parsedRun.transactions.length === 0) { - console.log(`No transactions found in the broadcast path provided: ${broadcastPath}`); - process.exit(1); - } + if (parsedRun.transactions.length === 0) { + console.log(`No transactions found in the broadcast path provided: ${broadcastPath}`); + process.exit(1); + } - greets(); + greets(); - const networkConfig = NETWORK_CONFIGS[parsedRun.chain] || {}; - if (args.rpcUrl) networkConfig.RPC = args.rpcUrl; - if (args.explorerUrl) networkConfig.explorers = [{ API_URL: args.explorerUrl, API_KEY: args.etherscanApiKey }]; + const networkConfig = EXPLORER_CONFIGS[parsedRun.chain] || []; + let rpc = ""; + if (args.rpcUrl) { + rpc = args.rpcUrl; + } else { + rpc = getRPCUrl(parsedRun.chain, process.env.ALCHEMY_API_KEY); + } - const chainId = await getChainId(networkConfig.RPC); - - try { - console.log("Chain Id:", chainId); - console.log(); - } catch (err) { - console.log("Could not connect to RPC endpoint", networkConfig.RPC); - process.exit(2); - } + if (args.explorerUrl) + networkConfig.push({ + API_URL: args.explorerUrl, + API_KEY: args.etherscanApiKey, + }); + const routescan = getRouteScan(parsedRun.chain); + if (routescan) networkConfig.push(routescan); + const etherscan = getEtherscan(parsedRun.chain); + if (etherscan) networkConfig.push(etherscan); - const buildInfos = await loadBuildInfo(parsedRun); - const artifacts = await loadArtifacts(); + const buildInfos = await loadBuildInfo(parsedRun); + const artifacts = await loadArtifacts(); - console.log("\nAnalyzing deployment transactions...\n"); - for (const tx of parsedRun.transactions) { - const trace = await getTxInternalCalls(tx.hash, networkConfig.RPC); - for (const explorer of networkConfig.explorers) { - try { - await callTraceVerifier(trace.result, artifacts, buildInfos, explorer); - } catch (error) { - console.error("[Verification Error]", error); - } - } + console.log("\nAnalyzing deployment transactions...\n"); + for (const tx of parsedRun.transactions) { + const trace = await getTxInternalCalls(tx.hash, rpc); + for (const explorer of networkConfig) { + console.log(explorer); + try { + await callTraceVerifier(trace.result, artifacts, buildInfos, parsedRun.chain, explorer); + } catch (error) { + console.error("[Verification Error]", error); + } } - console.log("\n[catapulta-verify] Verification finished."); - console.log( - "\n[catapulta-verify] Check out", - chalk.green("catapulta.sh"), - "for zero config deployments, automated verifications and deployment reports for Foundry projects. ", - ); + } + console.log("\n[catapulta-verify] Verification finished."); + console.log( + "\n[catapulta-verify] Check out", + chalk.green("catapulta.sh"), + "for zero config deployments, automated verifications and deployment reports for Foundry projects. ", + ); }; const greets = () => { - const greetings = ["Catapulta.sh", "Verify Lite", VERIFY_VERSION]; - const [intro, ...restMsg] = greetings; - console.log(""); - console.log(chalk.green(intro), ...restMsg); - console.log("=".repeat(greetings.join(" ").length)); + const greetings = ["Catapulta.sh", "Verify Lite", VERIFY_VERSION]; + const [intro, ...restMsg] = greetings; + console.log(""); + console.log(chalk.green(intro), ...restMsg); + console.log("=".repeat(greetings.join(" ").length)); }; main().catch((error) => { - console.error("[catapulta-verify] Exiting main handler, error:"); - console.error(error); - exit(2); + console.error("[catapulta-verify] Exiting main handler, error:"); + console.error(error); + exit(2); }); diff --git a/src/types.ts b/src/types.ts index 4924750..278fd65 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,73 +1,74 @@ export interface InputParams { - broadcastPath: string; - rpcUrl?: string; - explorerUrl?: string; - etherscanApiKey?: string; - help?: boolean; + broadcastPath: string; + rpcUrl?: string; + explorerUrl?: string; + etherscanApiKey?: string; + help?: boolean; } export interface BroadcastReportTx { - hash: string; - transactionType: string; - contractName: string; - contractAddress: string; - arguments: string[]; - rpc: string; + hash: string; + transactionType: string; + contractName: string; + contractAddress: string; + arguments: string[]; + rpc: string; } export interface BroadcastReceipt { - transactionHash: string; - from: string; - to: string | null; - cumulativeGasUsed: string; - gasUsed: string; - contractAddress: string; + transactionHash: string; + from: string; + to: string | null; + cumulativeGasUsed: string; + gasUsed: string; + contractAddress: string; } export interface BroadcastReport { - transactions: BroadcastReportTx[]; - receipts: BroadcastReceipt; - libraries: string[]; - path: string; - timestamp: number; - chain: number; - multi: boolean; - commit: string; + transactions: BroadcastReportTx[]; + receipts: BroadcastReceipt; + libraries: string[]; + path: string; + timestamp: number; + chain: number; + multi: boolean; + commit: string; } export interface EtherscanVerification { - apikey: string; - module: string; - action: string; - sourceCode: string; - contractaddress: string; - codeformat: string; - contractname: string; - compilerversion: string; - optimizationused: number; - runs?: number; - constructorArguements?: string; - evmversion?: string; - licenseType?: number; - libraryname?: string; - libraryaddress?: string; + apikey: string; + chainid: string; + module: string; + action: string; + sourceCode: string; + contractaddress: string; + codeformat: string; + contractname: string; + compilerversion: string; + optimizationused: number; + runs?: number; + constructorArguements?: string; + evmversion?: string; + licenseType?: number; + libraryname?: string; + libraryaddress?: string; } export enum Networks { - polygon = 137, - main = 1, - arbitrum = 42161, - avalanche = 43114, - avalancheFuji = 43113, - optimism = 10, - goerli = 5, - sepolia = 11155111, - bnb = 56, - bnbTestnet = 97, - base = 8453, - metis = 1088, - gnosis = 100, - scroll = 534352, - zkevm = 1101, - celo = 42220, + polygon = 137, + main = 1, + arbitrum = 42161, + avalanche = 43114, + avalancheFuji = 43113, + optimism = 10, + goerli = 5, + sepolia = 11155111, + bnb = 56, + bnbTestnet = 97, + base = 8453, + metis = 1088, + gnosis = 100, + scroll = 534352, + zkevm = 1101, + celo = 42220, } diff --git a/src/utils/api.spec.ts b/src/utils/api.spec.ts new file mode 100644 index 0000000..e889ccc --- /dev/null +++ b/src/utils/api.spec.ts @@ -0,0 +1,48 @@ +import "dotenv/config"; +import { describe, expect, it } from "vitest"; +import { getEtherscan } from "../explorers/etherscan"; +import { getRouteScan } from "../explorers/routescan"; +import { checkIfVerified, checkIfVisible, checkVerificationStatus, submitVerification } from "./api"; + +describe("api", { timeout: 30000 }, () => { + it("etherscan: checkVerificationStatus", async () => { + const mainnetEtherScan = getEtherscan(1)!; + const result = await checkVerificationStatus( + 1, + mainnetEtherScan, + "tdx4rpicxvzki2smtvwjyvzhbtvhnnyz2cvipj4zcdfgvkdlaq", + ); + console.log(result); + expect(result.status).toBe(1); + }); + + it("routescan: checkVerificationStatus", async () => { + const mainnetRouteScan = getRouteScan(1)!; + const result = await checkVerificationStatus( + 1, + mainnetRouteScan, + "tdx4rpicxvzki2smtvwjyvzhbtvhnnyz2cvipj4zcdfgvkdlaq", + ); + expect(result.status).toBe(1); + }); + + it("etherscan: checkIfVisible", async () => { + const mainnetEtherScan = getEtherscan(1)!; + const result = await checkIfVisible(1, mainnetEtherScan, "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"); + expect(result).toBe(true); + }); + + it("etherscan: checkIfVerified", async () => { + const mainnetEtherScan = getEtherscan(1)!; + const result = await checkIfVerified(1, mainnetEtherScan, "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"); + expect(result).toBe(true); + }); + + it.skip("etherscan: submitVerification", async () => { + const mainnetEtherScan = getEtherscan(1)!; + const result = await submitVerification(1, mainnetEtherScan, { + chainId: 1, + }); + expect(result).toBe(true); + }); +}); diff --git a/src/utils/api.ts b/src/utils/api.ts new file mode 100644 index 0000000..f6d7288 --- /dev/null +++ b/src/utils/api.ts @@ -0,0 +1,119 @@ +import type { ExplorerConfig } from "../config"; + +export async function checkVerificationStatus(chainId: number, explorer: ExplorerConfig, guid: string) { + const params = { + chainid: String(chainId), + guid, + module: "contract", + action: "checkverifystatus", + ...(explorer.API_KEY ? { apikey: explorer.API_KEY } : {}), + }; + + const formattedParams = new URLSearchParams(params).toString(); + try { + const request = await fetch(`${explorer.API_URL}?${formattedParams}`); + + const { status, result }: any = await request.json(); + if (result === "Pending in queue") { + return { + status: 2, + message: result, + }; + } + if (result !== "Fail - Unable to verify") { + if (status === "1") { + return { + status: 1, + message: result, + }; + } + } + return { + status: 0, + message: result, + }; + } catch (err) { + return { + status: 2, + message: `Couldn't check the verification status. Err: ${err}`, + }; + } +} + +export async function checkIfVisible(chainId: number, explorer: ExplorerConfig, address: string) { + const params = { + chainid: String(chainId), + contractaddresses: address, + module: "contract", + action: "getcontractcreation", + ...(explorer.API_KEY ? { apikey: explorer.API_KEY } : {}), + }; + + const formattedParams = new URLSearchParams(params).toString(); + + const request = await fetch(`${explorer.API_URL}?${formattedParams}`); + const { result }: any = await request.json(); + return Boolean(result); +} + +export async function checkIfVerified(chainId: number, explorer: ExplorerConfig, address: string) { + const params = { + chainid: String(chainId), + address: address, + module: "contract", + action: "getabi", + ...(explorer.API_KEY ? { apikey: explorer.API_KEY } : {}), + }; + + const formattedParams = new URLSearchParams(params).toString(); + + try { + const request = await fetch(`${explorer.API_URL}?${formattedParams}`); + + const { status, result }: any = await request.json(); + + if (status === "1") { + return true; + } + return false; + } catch (error) { + console.error(error); + process.exit(2); + } +} + +export async function submitVerification( + chainId: number, + explorer: ExplorerConfig, + verificationInfo: { + codeformat: string; + chainid: string; + sourceCode: string; + // typo in the official api v1 and v2 :shrug: + constructorArguements?: string; + contractaddress: string; + contractname: string; + compilerversion: string; + }, +) { + console.log("here", verificationInfo); + const queryParams = { + chainid: String(chainId), + ...(explorer.API_KEY ? { apikey: explorer.API_KEY } : {}), + module: "contract", + action: "verifysourcecode", + }; + const formattedParams = new URLSearchParams(queryParams).toString(); + const params = { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(verificationInfo).toString(), + }; + const url = `${explorer.API_URL}?${formattedParams}`; + console.log(url); + const request = await fetch(url, params); + + return await request.json(); +} diff --git a/src/utils/bytecode.ts b/src/utils/bytecode.ts index b31c5b5..dba6d32 100644 --- a/src/utils/bytecode.ts +++ b/src/utils/bytecode.ts @@ -1,57 +1,57 @@ export const isBytecodeInBuildInfo = (bytecode: string, buildInfo: any): boolean => { - const contractRefs = Object.keys(buildInfo.output.contracts); - for (let i = 0; i < contractRefs.length; i++) { - const contractNames = Object.keys(buildInfo.output.contracts[contractRefs[i]]); - for (let j = 0; j < contractNames.length; j++) { - const contractName = contractNames[j]; - const contractInfo = buildInfo.output.contracts[contractRefs[i]][contractName]; - if (contractInfo.evm.bytecode.object.toLowerCase().startsWith(bytecode.substring(2).toLowerCase())) { - return true; - } - } + const contractRefs = Object.keys(buildInfo.output.contracts); + for (let i = 0; i < contractRefs.length; i++) { + const contractNames = Object.keys(buildInfo.output.contracts[contractRefs[i]]); + for (let j = 0; j < contractNames.length; j++) { + const contractName = contractNames[j]; + const contractInfo = buildInfo.output.contracts[contractRefs[i]][contractName]; + if (contractInfo.evm.bytecode.object.toLowerCase().startsWith(bytecode.substring(2).toLowerCase())) { + return true; + } } - return false; + } + return false; }; export const isBytecodeInArtifact = (bytecodeAndParams: string, artifact: any): boolean => { - return !!bytecodeAndParams.toLowerCase().startsWith(artifact.bytecode.object.toLowerCase()); + return !!bytecodeAndParams.toLowerCase().startsWith(artifact.bytecode.object.toLowerCase()); }; export const getContractDataByArtifactAndBuildInfo = ( - artifact: any, - buildInfo: any, + artifact: any, + buildInfo: any, ): { - contractName: string; - contractInfo: any; - contractPath: string; + contractName: string; + contractInfo: any; + contractPath: string; } => { - const metadata: any = { sources: [], settings: {}, language: "" }; - metadata.settings = Object.assign({}, buildInfo.input.settings); - metadata.language = buildInfo.input.language; - metadata.sources = Object.fromEntries( - Object.keys(artifact.metadata.sources).map((key) => { - let buildInfoSource = buildInfo.input.sources[key]; - // if contract is duplicated is possible is in another similar path in other library - if (!buildInfoSource) { - const possibleKey = Object.keys(buildInfo.input.sources).find((x) => x.endsWith(key)); - if (possibleKey) { - buildInfoSource = buildInfo.input.sources[possibleKey]; - } else { - console.log("Missing smart contract path at build-info file", key); - } - } - const newValue = { - content: buildInfoSource.content, - }; - return [key, newValue]; - }), - ); + const metadata: any = { sources: [], settings: {}, language: "" }; + metadata.settings = Object.assign({}, buildInfo.input.settings); + metadata.language = buildInfo.input.language; + metadata.sources = Object.fromEntries( + Object.keys(artifact.metadata.sources).map((key) => { + let buildInfoSource = buildInfo.input.sources[key]; + // if contract is duplicated is possible is in another similar path in other library + if (!buildInfoSource) { + const possibleKey = Object.keys(buildInfo.input.sources).find((x) => x.endsWith(key)); + if (possibleKey) { + buildInfoSource = buildInfo.input.sources[possibleKey]; + } else { + console.log("Missing smart contract path at build-info file", key); + } + } + const newValue = { + content: buildInfoSource.content, + }; + return [key, newValue]; + }), + ); - return { - contractName: `${Object.keys(artifact.metadata.settings.compilationTarget)[0]}:${ - Object.values(artifact.metadata.settings.compilationTarget)[0] - }`, - contractInfo: metadata, - contractPath: Object.keys(artifact.metadata.settings.compilationTarget)[0], - }; + return { + contractName: `${Object.keys(artifact.metadata.settings.compilationTarget)[0]}:${ + Object.values(artifact.metadata.settings.compilationTarget)[0] + }`, + contractInfo: metadata, + contractPath: Object.keys(artifact.metadata.settings.compilationTarget)[0], + }; }; diff --git a/src/utils/calltrace-verifier.ts b/src/utils/calltrace-verifier.ts index 54b4bb0..4d36847 100644 --- a/src/utils/calltrace-verifier.ts +++ b/src/utils/calltrace-verifier.ts @@ -1,55 +1,69 @@ import type { ExplorerConfig } from "../config"; -import { checkIfVerified, checkVerificationStatus, submitVerification, waitTillVisible } from "./explorer-api"; +import { checkIfVerified, checkVerificationStatus, submitVerification } from "./api"; +import { waitTillVisible } from "./explorer-api"; import { getSettingsByArtifact } from "./foundry-ffi"; import { delay, renderExplorerUrl } from "./misc"; -export const callTraceVerifier = async (call: any, artifacts: any[], buildInfos: any[], explorer: ExplorerConfig) => { - const deployOpcodes = ["CREATE", "CREATE2"]; +export const callTraceVerifier = async ( + call: any, + artifacts: any[], + buildInfos: any[], + chainId: number, + explorer: ExplorerConfig, +) => { + const deployOpcodes = ["CREATE", "CREATE2"]; - // Perform nested call tracing verification in each internal call - if (call.calls) { - for (const c of call.calls) { - await callTraceVerifier(c, artifacts, buildInfos, explorer); - } + // Perform nested call tracing verification in each internal call + if (call.calls) { + for (const c of call.calls) { + await callTraceVerifier(c, artifacts, buildInfos, chainId, explorer); } + } - if (!deployOpcodes.includes(call.type)) return; + if (!deployOpcodes.includes(call.type)) return; - await waitTillVisible(call.to, explorer); + await waitTillVisible(chainId, explorer, call.to); - const verified = await checkIfVerified(call.to, explorer); + const verified = await checkIfVerified(chainId, explorer, call.to); - if (verified) { - console.log(`(${renderExplorerUrl(call.to, explorer)}) is already verified, skipping.`); - return; - } + if (verified) { + console.log(`(${renderExplorerUrl(call.to, explorer)}) is already verified, skipping.`); + return; + } - // if the tx has subdeployments get the deployed bytecode from the last deployment in order to - // compare the bytecode and deployed bytecode strings, resulting in the constructor params + // if the tx has subdeployments get the deployed bytecode from the last deployment in order to + // compare the bytecode and deployed bytecode strings, resulting in the constructor params - const verificationReq = await getSettingsByArtifact(artifacts, buildInfos, call.input, call.to, explorer.API_KEY); - if (!verificationReq) { - console.log("Couldn't get the params for the verification request"); - return; - } + const verificationReq = await getSettingsByArtifact( + artifacts, + buildInfos, + call.input, + call.to, + String(chainId), + explorer.API_KEY, + ); + if (!verificationReq) { + console.log("Couldn't get the params for the verification request"); + return; + } - const { result: guid, message, status }: any = await submitVerification(verificationReq, explorer.API_URL); + const { result: guid, message, status }: any = await submitVerification(chainId, explorer, verificationReq); - if (!status || guid.includes("Max rate limit reached")) { - console.log(`Couldn't verify ${renderExplorerUrl(call.to, explorer)} `, guid); - return; - } + if (!status || guid.includes("Max rate limit reached")) { + console.log(`Couldn't verify ${renderExplorerUrl(call.to, explorer)} `, guid); + return; + } - console.log(`Verifying contract ${renderExplorerUrl(call.to, explorer)}, with guid: ${guid}`); + console.log(`Verifying contract ${renderExplorerUrl(call.to, explorer)}, with guid: ${guid}`); - for (let i = 0; i < 30; i++) { - await delay(1000); - const { status, message } = await checkVerificationStatus(guid, explorer); + for (let i = 0; i < 30; i++) { + await delay(1000); + const { status, message } = await checkVerificationStatus(chainId, explorer, guid); - if (status !== 2) { - console.log(message); - break; - } - process.stdout.write("."); + if (status !== 2) { + console.log(message); + break; } + process.stdout.write("."); + } }; diff --git a/src/utils/explorer-api.ts b/src/utils/explorer-api.ts index aebcc84..da02f9f 100644 --- a/src/utils/explorer-api.ts +++ b/src/utils/explorer-api.ts @@ -1,130 +1,22 @@ -import { type ExplorerConfig, VERBOSE } from "../config"; +import type { ExplorerConfig } from "../config"; +import { checkIfVisible } from "./api"; import { delay } from "./misc"; -export const submitVerification = async (verificationInfo: any, explorerUrl: string) => { - const params = { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams(verificationInfo).toString(), - }; - // etherscan is not super stable, therefore sometimes there are issues and for reporting it's useful to log the request - if (VERBOSE) console.log("etherscan request", JSON.stringify(params)); - const request = await fetch(explorerUrl, params); - - return await request.json(); -}; - -// checks if the smart contract is already verified before trying to verify it -export const checkIfVerified = async (deploymentAddress: string, explorer: ExplorerConfig) => { - const params = { - apikey: explorer.API_KEY || "", - address: deploymentAddress, - module: "contract", - action: "getabi", - }; - - const formattedParams = new URLSearchParams(params).toString(); - - try { - const request = await fetch(`${explorer.API_URL}?${formattedParams}`); - - const { status, result }: any = await request.json(); - - if (status === "1") { - return true; - } - return false; - } catch (error) { - console.error(error); - process.exit(2); - } -}; - -export const checkIfVisible = async (deploymentAddress: string, explorer: ExplorerConfig) => { - const params = { - apikey: explorer.API_KEY || "", - contractaddresses: deploymentAddress, - module: "contract", - action: "getcontractcreation", - }; - - const formattedParams = new URLSearchParams(params).toString(); - - await delay(100); - const request = await fetch(`${explorer.API_URL}?${formattedParams}`); - const { result }: any = await request.json(); - return Boolean(result); -}; - /* Etherscan needs time to process the deployment, depending of the network load could take more or less time. */ -export const waitTillVisible = async (deploymentAddress: string, explorer: ExplorerConfig): Promise => { - let visible = false; - let logged = false; - - while (!visible) { - visible = await checkIfVisible(deploymentAddress, explorer); - if (!visible) { - if (!logged) { - console.log("Waiting for on-chain settlement..."); - logged = true; - } - await delay(5000); - } - } -}; - -/** - Status response: - 0: Error in the verification - 1: Verification completed - 2: Verification pending -*/ -export const checkVerificationStatus = async ( - GUID: string, - explorer: ExplorerConfig, -): Promise<{ - status: number; - message: string; -}> => { - const params = { - apikey: explorer.API_KEY || "", - guid: GUID, - module: "contract", - action: "checkverifystatus", - }; - - const formattedParams = new URLSearchParams(params).toString(); - - try { - const request = await fetch(`${explorer.API_URL}?${formattedParams}`); - - const { status, result }: any = await request.json(); - if (result === "Pending in queue") { - return { - status: 2, - message: result, - }; - } - if (result !== "Fail - Unable to verify") { - if (status === "1") { - return { - status: 1, - message: result, - }; - } - } - return { - status: 0, - message: result, - }; - } catch (err) { - return { - status: 2, - message: `Couldn't check the verification status. Err: ${err}`, - }; +export const waitTillVisible = async (chainId: number, explorer: ExplorerConfig, address: string): Promise => { + let visible = false; + let logged = false; + + while (!visible) { + visible = await checkIfVisible(chainId, explorer, address); + if (!visible) { + if (!logged) { + console.log("Waiting for on-chain settlement..."); + logged = true; + } + await delay(5000); } + } }; diff --git a/src/utils/foundry-ffi.ts b/src/utils/foundry-ffi.ts index ffd5921..9baea60 100644 --- a/src/utils/foundry-ffi.ts +++ b/src/utils/foundry-ffi.ts @@ -7,125 +7,125 @@ import { loadJson } from "./json.ts"; import { extractOneLicenseFromSourceFile } from "./license.ts"; function recFindByExt(base: string, ext: string, prevFiles?: string[], prevResult?: any): string[] { - const files = prevFiles || readdirSync(base); - let result = prevResult || []; - - for (const file of files) { - const newbase = path.join(base, file); - if (statSync(newbase).isDirectory()) { - result = recFindByExt(newbase, ext, readdirSync(newbase), result); - } else { - if (file.substr(-1 * (ext.length + 1)) === "." + ext) { - result.push(newbase); - } - } + const files = prevFiles || readdirSync(base); + let result = prevResult || []; + + for (const file of files) { + const newbase = path.join(base, file); + if (statSync(newbase).isDirectory()) { + result = recFindByExt(newbase, ext, readdirSync(newbase), result); + } else { + if (file.substr(-1 * (ext.length + 1)) === "." + ext) { + result.push(newbase); + } } - return result; + } + return result; } export const loadArtifacts = async (): Promise => { - const artifactsFiles: string[] = recFindByExt(path.normalize("out"), "json"); + const artifactsFiles: string[] = recFindByExt(path.normalize("out"), "json"); - const importedArtifacts = await Promise.all( - artifactsFiles.map(async (artifactFilePath) => { - const parsedJson = await loadJson(artifactFilePath); - return parsedJson; - }), - ); - const filteredArtifactsFiles = importedArtifacts.filter((y) => !!y?.bytecode && !(y?.bytecode?.object === "0x")); + const importedArtifacts = await Promise.all( + artifactsFiles.map(async (artifactFilePath) => { + const parsedJson = await loadJson(artifactFilePath); + return parsedJson; + }), + ); + const filteredArtifactsFiles = importedArtifacts.filter((y) => !!y?.bytecode && !(y?.bytecode?.object === "0x")); - return filteredArtifactsFiles; + return filteredArtifactsFiles; }; export const loadBuildInfo = async (parsedRun: BroadcastReport): Promise => { - console.log("Compiling contracts with build info..."); + console.log("Compiling contracts with build info..."); - // Remove FOUNDRY_LIBRARIES from .env, due it could mutate bytecode of deployments with different compilation contexts, .env will be restored back at exit - const dotEnvExists = existsSync(".env"); - if (dotEnvExists) { - execSync("cp .env .env.bk && sed -i.sedbak -r '/FOUNDRY_LIBRARIES/d' .env && rm .env.sedbak && sleep 1"); - } + // Remove FOUNDRY_LIBRARIES from .env, due it could mutate bytecode of deployments with different compilation contexts, .env will be restored back at exit + const dotEnvExists = existsSync(".env"); + if (dotEnvExists) { + execSync("cp .env .env.bk && sed -i.sedbak -r '/FOUNDRY_LIBRARIES/d' .env && rm .env.sedbak && sleep 1"); + } - let forgeBuildCmd = "forge build --skip test script --build-info"; + let forgeBuildCmd = "forge build --skip test script --build-info"; - if (parsedRun?.libraries?.length) { - forgeBuildCmd = `FOUNDRY_LIBRARIES="${parsedRun.libraries.join(",")}" ${forgeBuildCmd}`; - } + if (parsedRun?.libraries?.length) { + forgeBuildCmd = `FOUNDRY_LIBRARIES="${parsedRun.libraries.join(",")}" ${forgeBuildCmd}`; + } - try { - execSync(forgeBuildCmd, { stdio: "inherit" }); - } finally { - if (dotEnvExists) { - execSync("cp .env.bk .env && rm .env.bk", { stdio: "inherit" }); - } + try { + execSync(forgeBuildCmd, { stdio: "inherit" }); + } finally { + if (dotEnvExists) { + execSync("cp .env.bk .env && rm .env.bk", { stdio: "inherit" }); } + } - console.log("Compilation successful"); + console.log("Compilation successful"); - const buildInfos: any[] = []; + const buildInfos: any[] = []; - const buildInfosInDir = readdirSync(path.join("out", "build-info")).filter( - (file) => path.extname(file) === ".json", - ); + const buildInfosInDir = readdirSync(path.join("out", "build-info")).filter((file) => path.extname(file) === ".json"); - for (const file of buildInfosInDir) { - buildInfos.push(await loadJson(path.join("out", "build-info", file))); - } + for (const file of buildInfosInDir) { + buildInfos.push(await loadJson(path.join("out", "build-info", file))); + } - return buildInfos; + return buildInfos; }; export const getSettingsByArtifact = async ( - artifacts: any[], - buildInfos: any[], - bytecodeAndParams: string, - deploymentAddress: string, - etherscanApiKey?: string, + artifacts: any[], + buildInfos: any[], + bytecodeAndParams: string, + deploymentAddress: string, + chainId: string, + etherscanApiKey?: string, ): Promise => { - const settings: EtherscanVerification = {} as EtherscanVerification; - - const foundArtifact = artifacts.find((artifact) => isBytecodeInArtifact(bytecodeAndParams, artifact)); - - if (!foundArtifact) { - console.log(`Couldn't find artifact via bytecode for ${deploymentAddress}`); - return; - } - - const foundBuildInfo = buildInfos.find((buildInfo) => - isBytecodeInBuildInfo(foundArtifact.bytecode.object, buildInfo), - ); - - if (!foundBuildInfo) { - console.log(`Couldn't find build-info via bytecode for ${deploymentAddress}`); - console.log(bytecodeAndParams); - return; - } - - const contractData = getContractDataByArtifactAndBuildInfo(foundArtifact, foundBuildInfo); - - const { contractName, contractInfo, contractPath } = < - { contractName: string; contractInfo: any; contractPath: string } - >contractData; - const licenseType = extractOneLicenseFromSourceFile(contractInfo.sources[contractPath].content); - - if (!licenseType) return; - - const rawParams = bytecodeAndParams.split(foundArtifact.bytecode.object)[1] || ""; - - const metadata = foundArtifact.metadata; - settings.apikey = etherscanApiKey || ""; - settings.module = "contract"; - settings.action = "verifysourcecode"; - settings.sourceCode = JSON.stringify(contractData.contractInfo); - settings.contractaddress = deploymentAddress; - settings.codeformat = "solidity-standard-json-input"; - settings.contractname = contractName; - settings.compilerversion = "v" + metadata.compiler.version; - settings.optimizationused = Number(metadata.settings.optimizer.enabled); - settings.runs = metadata.settings.optimizer.runs; - settings.constructorArguements = rawParams; - settings.evmversion = metadata.settings.evmVersion || "None"; - settings.licenseType = licenseType || 1; - - return settings; + const settings: EtherscanVerification = {} as EtherscanVerification; + + const foundArtifact = artifacts.find((artifact) => isBytecodeInArtifact(bytecodeAndParams, artifact)); + + if (!foundArtifact) { + console.log(`Couldn't find artifact via bytecode for ${deploymentAddress}`); + return; + } + + const foundBuildInfo = buildInfos.find((buildInfo) => + isBytecodeInBuildInfo(foundArtifact.bytecode.object, buildInfo), + ); + + if (!foundBuildInfo) { + console.log(`Couldn't find build-info via bytecode for ${deploymentAddress}`); + console.log(bytecodeAndParams); + return; + } + + const contractData = getContractDataByArtifactAndBuildInfo(foundArtifact, foundBuildInfo); + + const { contractName, contractInfo, contractPath } = < + { contractName: string; contractInfo: any; contractPath: string } + >contractData; + const licenseType = extractOneLicenseFromSourceFile(contractInfo.sources[contractPath].content); + + if (!licenseType) return; + + const rawParams = bytecodeAndParams.split(foundArtifact.bytecode.object)[1] || ""; + + const metadata = foundArtifact.metadata; + if (etherscanApiKey) settings.apikey = etherscanApiKey; + settings.chainid = chainId; + settings.module = "contract"; + settings.action = "verifysourcecode"; + settings.sourceCode = JSON.stringify(contractData.contractInfo); + settings.contractaddress = deploymentAddress; + settings.codeformat = "solidity-standard-json-input"; + settings.contractname = contractName; + settings.compilerversion = "v" + metadata.compiler.version; + settings.optimizationused = Number(metadata.settings.optimizer.enabled); + settings.runs = metadata.settings.optimizer.runs; + settings.constructorArguements = rawParams; + settings.evmversion = metadata.settings.evmVersion || "None"; + settings.licenseType = licenseType || 1; + + return settings; }; diff --git a/src/utils/license.ts b/src/utils/license.ts index 203fa57..166d25f 100644 --- a/src/utils/license.ts +++ b/src/utils/license.ts @@ -1,48 +1,48 @@ export const getLicenseType = (license: string): undefined | number => { - switch (license) { - case "None": - return 1; - case "UNLICENSED": - return 2; - case "MIT": - return 3; - case "GPL-2.0": - return 4; - case "GPL-3.0": - return 5; - case "LGPL-2.1": - return 6; - case "LGPL-3.0": - return 7; - case "BSD-2-Clause": - return 8; - case "BSD-3-Clause": - return 9; - case "MPL-2.0": - return 10; - case "OSL-3.0": - return 11; - case "Apache-2.0": - return 12; - case "AGPL-3.0": - return 13; - case "BUSL-1.1": - return 14; - default: - console.error( - `The provided license: ${license} is not supported by Etherscan. List of supported licenses can be found here: https://etherscan.io/contract-license-types`, - ); - process.exit(4); - } + switch (license) { + case "None": + return 1; + case "UNLICENSED": + return 2; + case "MIT": + return 3; + case "GPL-2.0": + return 4; + case "GPL-3.0": + return 5; + case "LGPL-2.1": + return 6; + case "LGPL-3.0": + return 7; + case "BSD-2-Clause": + return 8; + case "BSD-3-Clause": + return 9; + case "MPL-2.0": + return 10; + case "OSL-3.0": + return 11; + case "Apache-2.0": + return 12; + case "AGPL-3.0": + return 13; + case "BUSL-1.1": + return 14; + default: + console.error( + `The provided license: ${license} is not supported by Etherscan. List of supported licenses can be found here: https://etherscan.io/contract-license-types`, + ); + process.exit(4); + } }; export const extractOneLicenseFromSourceFile = (source: string): number | undefined => { - const regex = /\/\/\s*\t*SPDX-License-Identifier:\s*\t*(.*?)[\s\\]/g; - const match = [...source.matchAll(regex)]; - if (!match) { - console.log("Please provide a license in the source contract."); - return; - } + const regex = /\/\/\s*\t*SPDX-License-Identifier:\s*\t*(.*?)[\s\\]/g; + const match = [...source.matchAll(regex)]; + if (!match) { + console.log("Please provide a license in the source contract."); + return; + } - return getLicenseType(match[0][1]); + return getLicenseType(match[0][1]); }; diff --git a/src/utils/misc.ts b/src/utils/misc.ts index f96d369..66d929e 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -1,20 +1,20 @@ import type { ExplorerConfig } from "../config"; export function delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } export function isHex(str: string) { - const regexp = /^0x[0-9a-fA-F]+$/; + const regexp = /^0x[0-9a-fA-F]+$/; - if (regexp.test(str)) { - return true; - } + if (regexp.test(str)) { + return true; + } - return false; + return false; } export function renderExplorerUrl(address: string, explorer: ExplorerConfig) { - if (!explorer.SITE_URL) return address; - return `${explorer.SITE_URL}/address/${address}`; + if (!explorer.SITE_URL) return address; + return `${explorer.SITE_URL}/address/${address}`; } diff --git a/src/utils/rpc.ts b/src/utils/rpc.ts index b0cde0f..2725a58 100644 --- a/src/utils/rpc.ts +++ b/src/utils/rpc.ts @@ -1,65 +1,65 @@ import { isHex } from "./misc"; export const getChainId = async (rpcUrl: string): Promise => { - try { - const request = await fetch(rpcUrl, { - method: "POST", - body: JSON.stringify({ - method: "eth_chainId", - params: [], - id: 1, - jsonrpc: "2.0", - }), - headers: { - "Content-type": "application/json; charset=UTF-8", - }, - }); - const response = await request.json(); + try { + const request = await fetch(rpcUrl, { + method: "POST", + body: JSON.stringify({ + method: "eth_chainId", + params: [], + id: 1, + jsonrpc: "2.0", + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, + }); + const response = await request.json(); - if (response.error) { - throw "RPC.chainId.ResponseError"; - } - if (response.result && isHex(response.result)) { - return Number(response.result); - } - throw "RPC.chainId.CantParse"; - } catch (error) { - console.log("[Error] Cant connect to RPC provider. Exiting."); - process.exit(2); + if (response.error) { + throw "RPC.chainId.ResponseError"; } + if (response.result && isHex(response.result)) { + return Number(response.result); + } + throw "RPC.chainId.CantParse"; + } catch (error) { + console.log("[Error] Cant connect to RPC provider. Exiting."); + process.exit(2); + } }; export const getTxInternalCalls = async (txHash: string, rpcUrl: string) => { - try { - const response = await fetch(rpcUrl, { - method: "POST", - body: JSON.stringify({ - jsonrpc: "2.0", - id: 1, - method: "debug_traceTransaction", - params: [ - txHash, - { - tracer: "callTracer", - }, - ], - }), - headers: { - "Content-type": "application/json; charset=UTF-8", - }, - }); - - const trace = await response.json(); + try { + const response = await fetch(rpcUrl, { + method: "POST", + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "debug_traceTransaction", + params: [ + txHash, + { + tracer: "callTracer", + }, + ], + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, + }); - if (!trace || trace.error) { - if (trace?.error?.message) console.error("RPC response:", trace.error.message); - console.error("[catapulta-verify] RPC does not support debug_traceTransaction. Exiting."); - process.exit(2); - } + const trace = await response.json(); - return trace; - } catch (error) { - console.error("[catapulta-verify] RPC does not support debug_traceTransaction. Exiting."); - process.exit(2); + if (!trace || trace.error) { + if (trace?.error?.message) console.error("RPC response:", trace.error.message); + console.error("[catapulta-verify] RPC does not support debug_traceTransaction. Exiting."); + process.exit(2); } + + return trace; + } catch (error) { + console.error("[catapulta-verify] RPC does not support debug_traceTransaction. Exiting."); + process.exit(2); + } }; diff --git a/tsconfig.json b/tsconfig.json index bec3011..7556e1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,22 +1,22 @@ { - "compilerOptions": { - "lib": ["ESNext"], - "module": "esnext", - "target": "esnext", - "moduleResolution": "bundler", - "moduleDetection": "force", - "allowImportingTsExtensions": true, - "noEmit": true, - "composite": true, - "strict": true, - "downlevelIteration": true, - "skipLibCheck": true, - "jsx": "react-jsx", - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "allowJs": true, - "types": [ - "bun-types" // add Bun global - ] - } + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } }