├── .solcover.js ├── .prettierrc.yml ├── .prettierignore ├── .solhintignore ├── contracts ├── Create2DeployerLocal.sol ├── mocks │ ├── ERC20Mock.sol │ └── ERC20ReturnFalseMock.sol └── BatchDistributor.sol ├── .editorconfig ├── .solhint.json ├── abis └── contracts │ └── BatchDistributor.sol │ └── BatchDistributor.json ├── renovate.json ├── slither.config.json ├── tsconfig.json ├── .github └── workflows │ ├── codeql.yml │ ├── test-contracts.yml │ └── checks.yml ├── LICENSE ├── scripts ├── deploy.ts └── interact.ts ├── eslint.config.js ├── .gitignore ├── deploy └── deploy-zksync.ts ├── SECURITY.md ├── README.md ├── package.json ├── test └── BatchDistributor.test.ts └── hardhat.config.ts /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ["Create2DeployerLocal.sol", "mocks/"], 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - "prettier-plugin-solidity" 3 | overrides: 4 | - files: "*.sol" 5 | options: 6 | printWidth: 100 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | pnpm-lock.yaml 3 | cache 4 | cache-zk 5 | artifacts 6 | artifacts-zk 7 | typechain-types 8 | coverage 9 | deployments 10 | deployments-zk 11 | deployments_tenderly 12 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | pnpm-lock.yaml 3 | cache 4 | cache-zk 5 | artifacts 6 | artifacts-zk 7 | typechain-types 8 | coverage 9 | deployments 10 | deployments-zk 11 | deployments_tenderly 12 | -------------------------------------------------------------------------------- /contracts/Create2DeployerLocal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {CreateX} from "xdeployer/src/contracts/CreateX.sol"; 5 | 6 | contract Create2DeployerLocal is CreateX {} 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = false 10 | 11 | [*.sol] 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": "off", 5 | "func-visibility": ["warn", { "ignoreConstructors": true }], 6 | "no-empty-blocks": "off", 7 | "use-natspec": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /abis/contracts/BatchDistributor.sol/BatchDistributor.json: -------------------------------------------------------------------------------- 1 | [ 2 | "constructor() payable", 3 | "error EtherTransferFail(address)", 4 | "error SafeERC20FailedOperation(address)", 5 | "function distributeEther(tuple(tuple(address,uint256)[])) payable", 6 | "function distributeToken(address,tuple(tuple(address,uint256)[]))" 7 | ] 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseBranches": ["main"], 3 | "labels": ["dependencies"], 4 | "assignees": ["pcaversaccio"], 5 | "separateMajorMinor": false, 6 | "extends": [ 7 | ":preserveSemverRanges", 8 | "group:all", 9 | "schedule:monthly", 10 | ":maintainLockFilesMonthly" 11 | ], 12 | "lockFileMaintenance": { 13 | "extends": ["group:all"], 14 | "commitMessageAction": "Update" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "compile_force_framework": "hardhat", 3 | "hardhat_ignore_compile": false, 4 | "detectors_to_exclude": "pragma,solc-version,arbitrary-send-eth,calls-loop,low-level-calls,uninitialized-local", 5 | "fail_on": "none", 6 | "exclude_informational": false, 7 | "exclude_low": false, 8 | "exclude_medium": false, 9 | "exclude_high": false, 10 | "filter_paths": "contracts/mocks|contracts/Create2DeployerLocal.sol|@openzeppelin|hardhat/console.sol|xdeployer" 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2024", 4 | "module": "commonjs", 5 | "allowJs": true, 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "outDir": "dist", 9 | "declaration": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "skipLibCheck": true, 12 | "resolveJsonModule": true 13 | }, 14 | "include": [ 15 | "./scripts/**/*.js", 16 | "./scripts/**/*.ts", 17 | "./deploy/**/*.js", 18 | "./deploy/**/*.ts", 19 | "./test/**/*.js", 20 | "./test/**/*.ts", 21 | "./typechain-types/**/*.ts" 22 | ], 23 | "files": ["hardhat.config.ts", "eslint.config.js", ".solcover.js"] 24 | } 25 | -------------------------------------------------------------------------------- /contracts/mocks/ERC20Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/token/ERC20Mock.sol 3 | pragma solidity ^0.8.33; 4 | 5 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | // Mock class using ERC20 8 | contract ERC20Mock is ERC20 { 9 | constructor() payable ERC20("MyToken", "MTK") { 10 | _mint(msg.sender, 100); 11 | } 12 | 13 | function mint(address account, uint256 amount) public { 14 | _mint(account, amount); 15 | } 16 | 17 | function burn(address account, uint256 amount) public { 18 | _burn(account, amount); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: 🔍️ CodeQL 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | analyse: 11 | runs-on: ${{ matrix.os }} 12 | permissions: 13 | security-events: write 14 | strategy: 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | language: 19 | - javascript-typescript 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v6 24 | 25 | - name: Initialise CodeQL 26 | uses: github/codeql-action/init@v4 27 | with: 28 | languages: ${{ matrix.language }} 29 | queries: +security-and-quality 30 | 31 | - name: Autobuild 32 | uses: github/codeql-action/autobuild@v4 33 | 34 | - name: Perform CodeQL analysis 35 | uses: github/codeql-action/analyze@v4 36 | with: 37 | category: "/language:${{ matrix.language }}" 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 0x6d61646520666f722074686520776f726c6421 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import hre from "hardhat"; 2 | 3 | // Colour codes for terminal prints 4 | const RESET = "\x1b[0m"; 5 | const GREEN = "\x1b[32m"; 6 | 7 | function delay(ms: number) { 8 | return new Promise((resolve) => setTimeout(resolve, ms)); 9 | } 10 | 11 | async function main() { 12 | const batchDistributor = await hre.ethers.deployContract("BatchDistributor"); 13 | 14 | await batchDistributor.waitForDeployment(); 15 | const batchDistributorAddress = await batchDistributor.getAddress(); 16 | 17 | console.log( 18 | "BatchDistributor deployed to: " + 19 | `${GREEN}${batchDistributorAddress}${RESET}\n`, 20 | ); 21 | 22 | console.log( 23 | "Waiting 30 seconds before beginning the contract verification to allow the block explorer to index the contract...\n", 24 | ); 25 | await delay(30000); // Wait for 30 seconds before verifying the contract 26 | 27 | await hre.run("verify:verify", { 28 | address: batchDistributorAddress, 29 | }); 30 | 31 | // await hre.tenderly.verify({ 32 | // name: "BatchDistributor", 33 | // address: batchDistributorAddress, 34 | // }); 35 | } 36 | 37 | main().catch((error) => { 38 | console.error(error); 39 | process.exitCode = 1; 40 | }); 41 | -------------------------------------------------------------------------------- /contracts/mocks/ERC20ReturnFalseMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.33; 3 | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract ERC20ReturnFalseMock is ERC20 { 7 | constructor() payable ERC20("MyToken", "MTK") { 8 | _mint(msg.sender, 100); 9 | } 10 | 11 | function mint(address account, uint256 amount) public { 12 | _mint(account, amount); 13 | } 14 | 15 | function burn(address account, uint256 amount) public { 16 | _burn(account, amount); 17 | } 18 | 19 | function transferFrom( 20 | address sender, 21 | address recipient, 22 | uint256 amount 23 | ) public override returns (bool) { 24 | if (sender == address(0)) { 25 | return false; 26 | } 27 | if (recipient == address(0)) { 28 | return false; 29 | } 30 | 31 | uint256 senderBalance = super.balanceOf(sender); 32 | if (senderBalance < amount) { 33 | return false; 34 | } 35 | 36 | uint256 currentAllowance = super.allowance(sender, _msgSender()); 37 | if (currentAllowance < amount) { 38 | return false; 39 | } 40 | 41 | return super.transferFrom(sender, recipient, amount); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | const eslint = require("@eslint/js"); 3 | const tseslint = require("typescript-eslint"); 4 | const eslintConfigPrettier = require("eslint-config-prettier"); 5 | const globals = require("globals"); 6 | const { defineConfig } = require("eslint/config"); 7 | /* eslint-enable @typescript-eslint/no-require-imports */ 8 | 9 | module.exports = defineConfig( 10 | { 11 | files: ["**/*.{js,ts}"], 12 | extends: [ 13 | eslint.configs.recommended, 14 | ...tseslint.configs.recommended, 15 | ...tseslint.configs.stylistic, 16 | eslintConfigPrettier, 17 | ], 18 | plugins: { 19 | "@typescript-eslint": tseslint.plugin, 20 | }, 21 | languageOptions: { 22 | ecmaVersion: "latest", 23 | parser: tseslint.parser, 24 | globals: { 25 | ...globals.node, 26 | }, 27 | parserOptions: { 28 | project: true, 29 | tsconfigRootDir: __dirname, 30 | }, 31 | }, 32 | }, 33 | { 34 | ignores: [ 35 | "node_modules/**", 36 | "pnpm-lock.yaml", 37 | "cache/**", 38 | "cache-zk/**", 39 | "artifacts/**", 40 | "artifacts-zk/**", 41 | "typechain-types/**", 42 | "coverage/**", 43 | "deployments/**", 44 | "deployments-zk/**", 45 | "deployments_tenderly/**", 46 | ], 47 | }, 48 | ); 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all Visual Studio Code settings 2 | .vscode/* 3 | !.vscode/settings.json 4 | !.vscode/tasks.json 5 | !.vscode/launch.json 6 | !.vscode/extensions.json 7 | !.vscode/*.code-snippets 8 | 9 | # Local history for Visual Studio Code 10 | .history 11 | 12 | # Built Visual Studio Code extensions 13 | *.vsix 14 | 15 | # Dependency directory 16 | node_modules 17 | 18 | # dotenv environment variable files 19 | .env 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .env.local 24 | 25 | # Hardhat configuration file 26 | vars.json 27 | 28 | # Log files 29 | logs 30 | *.log 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | .pnpm-debug.log* 35 | 36 | # Yarn integrity file 37 | .yarn-integrity 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Modern Yarn files 43 | .pnp.* 44 | .yarn/* 45 | !.yarn/cache 46 | !.yarn/patches 47 | !.yarn/plugins 48 | !.yarn/releases 49 | !.yarn/sdks 50 | !.yarn/versions 51 | 52 | # Hardhat files 53 | cache 54 | artifacts 55 | .deps 56 | 57 | # TypeScript bindings output directory 58 | typechain-types 59 | 60 | # solidity-coverage directory and output file 61 | coverage 62 | coverage.json 63 | 64 | # xdeployer output directory 65 | deployments 66 | 67 | # Tenderly fork deployment directory 68 | deployments_tenderly 69 | 70 | # ZKsync files 71 | cache-zk 72 | artifacts-zk 73 | deployments-zk 74 | 75 | # Miscellaneous files 76 | bin 77 | out 78 | -------------------------------------------------------------------------------- /deploy/deploy-zksync.ts: -------------------------------------------------------------------------------- 1 | // Note that the deployment scripts must be placed in the `deploy` folder for `hardhat deploy-zksync` 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import { Wallet } from "zksync-ethers"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | 6 | // Colour codes for terminal prints 7 | const RESET = "\x1b[0m"; 8 | const GREEN = "\x1b[32m"; 9 | 10 | function delay(ms: number) { 11 | return new Promise((resolve) => setTimeout(resolve, ms)); 12 | } 13 | 14 | export default async function main(hre: HardhatRuntimeEnvironment) { 15 | // Get the private key from the configured network 16 | // This assumes that a private key is configured for the selected network 17 | const accounts = hre.network.config.accounts; 18 | if (!Array.isArray(accounts)) { 19 | throw new Error( 20 | `No private key configured for network ${hre.network.name}`, 21 | ); 22 | } 23 | const PRIVATE_KEY = accounts[0]; 24 | if (typeof PRIVATE_KEY !== "string") { 25 | throw new Error( 26 | `No private key configured for network ${hre.network.name}`, 27 | ); 28 | } 29 | 30 | const wallet = new Wallet(PRIVATE_KEY); 31 | const deployer = new Deployer(hre, wallet); 32 | 33 | const artifact = await deployer.loadArtifact("BatchDistributor"); 34 | const batchDistributor = await deployer.deploy(artifact); 35 | 36 | await batchDistributor.waitForDeployment(); 37 | const batchDistributorAddress = await batchDistributor.getAddress(); 38 | 39 | console.log( 40 | "BatchDistributor deployed to: " + 41 | `${GREEN}${batchDistributorAddress}${RESET}\n`, 42 | ); 43 | 44 | console.log( 45 | "Waiting 30 seconds before beginning the contract verification to allow the block explorer to index the contract...\n", 46 | ); 47 | await delay(30000); // Wait for 30 seconds before verifying the contract 48 | 49 | await hre.run("verify:verify", { 50 | address: batchDistributorAddress, 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /scripts/interact.ts: -------------------------------------------------------------------------------- 1 | import hre from "hardhat"; 2 | 3 | // Colour codes for terminal prints 4 | const RESET = "\x1b[0m"; 5 | const GREEN = "\x1b[32m"; 6 | 7 | async function distributeEther() { 8 | const testnetAddress = "0xE710359D8E887afDF66053E6a9e044E0499e3446"; 9 | const batchDistributor = await hre.ethers.getContractAt( 10 | "BatchDistributor", 11 | testnetAddress, 12 | ); 13 | 14 | //////////////// 15 | // PAYLOAD // 16 | ////////////// 17 | 18 | // The `parseEther` method will parse a string representing ether 19 | // into a BigNumber in wei (1 ether = 10**18 wei). 20 | // Examples: 21 | // parseEther("1.0") => { BigNumber: "1000000000000000000" } 22 | // parseEther("-0.5") => { BigNumber: "-500000000000000000" } 23 | // Your payload: 24 | const txns = [ 25 | { 26 | recipient: "0x9F3f11d72d96910df008Cfe3aBA40F361D2EED03", 27 | amount: hre.ethers.parseEther("0.000000000000000001"), 28 | }, 29 | { 30 | recipient: "0x3854Ca47Abc62A3771fE06ab45622A42C4A438Cf", 31 | amount: hre.ethers.parseEther("0.000000000000000002"), 32 | }, 33 | ]; 34 | // In the event that excessive ether is sent, the residual amount is 35 | // returned back to the `msg.sender`. 36 | const msgValue = hre.ethers.parseEther("0.000000000000000004"); 37 | 38 | //////////////// 39 | // SENDING // 40 | ////////////// 41 | 42 | const tx = await batchDistributor.distributeEther( 43 | { txns: txns }, 44 | { value: msgValue }, 45 | ); 46 | console.log("The transaction hash is: " + `${GREEN}${tx.hash}${RESET}\n`); 47 | console.log("Waiting until the transaction is confirmed...\n"); 48 | const receipt = await tx.wait(); 49 | console.log( 50 | "The transaction returned the following transaction receipt:\n", 51 | receipt, 52 | ); 53 | } 54 | 55 | // To run it, invoke `npx hardhat run scripts/interact.ts --network ` 56 | distributeEther().catch((error) => { 57 | console.error(error); 58 | process.exitCode = 1; 59 | }); 60 | -------------------------------------------------------------------------------- /.github/workflows/test-contracts.yml: -------------------------------------------------------------------------------- 1 | name: 🕵️‍♂️ Test smart contracts 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | tests: 11 | runs-on: ${{ matrix.os }} 12 | permissions: 13 | contents: read 14 | security-events: write 15 | strategy: 16 | matrix: 17 | os: 18 | - ubuntu-latest 19 | node_version: 20 | - 24 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v6 25 | 26 | - name: Install pnpm 27 | uses: pnpm/action-setup@v4 28 | with: 29 | run_install: false 30 | 31 | - name: Get pnpm cache directory path 32 | id: pnpm-cache-dir-path 33 | run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT 34 | 35 | - name: Restore pnpm cache 36 | uses: actions/cache@v5 37 | id: pnpm-cache 38 | with: 39 | path: ${{ steps.pnpm-cache-dir-path.outputs.dir }} 40 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 41 | restore-keys: | 42 | ${{ runner.os }}-pnpm-store- 43 | 44 | - name: Use Node.js ${{ matrix.node_version }} 45 | uses: actions/setup-node@v6 46 | with: 47 | node-version: ${{ matrix.node_version }} 48 | 49 | - name: Install pnpm project with a clean slate 50 | run: pnpm install --prefer-offline --frozen-lockfile 51 | 52 | - name: Hardhat tests 53 | run: pnpm test 54 | 55 | - name: Slither static analyser 56 | uses: crytic/slither-action@v0.4.1 57 | id: slither 58 | with: 59 | node-version: ${{ matrix.node_version }} 60 | fail-on: config 61 | sarif: results.sarif 62 | 63 | - name: Upload SARIF file 64 | uses: github/codeql-action/upload-sarif@v4 65 | with: 66 | sarif_file: ${{ steps.slither.outputs.sarif }} 67 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: 👮‍♂️ Sanity checks 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | prettify-n-lint: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | node_version: 17 | - 24 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v6 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v4 25 | with: 26 | run_install: false 27 | 28 | - name: Get pnpm cache directory path 29 | id: pnpm-cache-dir-path 30 | run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT 31 | 32 | - name: Restore pnpm cache 33 | uses: actions/cache@v5 34 | id: pnpm-cache 35 | with: 36 | path: ${{ steps.pnpm-cache-dir-path.outputs.dir }} 37 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 38 | restore-keys: | 39 | ${{ runner.os }}-pnpm-store- 40 | 41 | - name: Use Node.js ${{ matrix.node_version }} 42 | uses: actions/setup-node@v6 43 | with: 44 | node-version: ${{ matrix.node_version }} 45 | 46 | - name: Install pnpm project with a clean slate 47 | run: pnpm install --prefer-offline --frozen-lockfile 48 | 49 | - name: Prettier and lint 50 | run: pnpm lint:check 51 | 52 | codespell: 53 | runs-on: ${{ matrix.os }} 54 | strategy: 55 | matrix: 56 | os: 57 | - ubuntu-latest 58 | 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v6 62 | 63 | - name: Run codespell 64 | uses: codespell-project/actions-codespell@v2 65 | with: 66 | check_filenames: true 67 | skip: ./.git,pnpm-lock.yaml 68 | ignore_words_list: superseed 69 | 70 | validate-links: 71 | runs-on: ${{ matrix.os }} 72 | strategy: 73 | matrix: 74 | os: 75 | - ubuntu-latest 76 | ruby_version: 77 | - 3.4 78 | 79 | steps: 80 | - name: Checkout 81 | uses: actions/checkout@v6 82 | 83 | - name: Set up Ruby 84 | uses: ruby/setup-ruby@v1 85 | with: 86 | ruby-version: ${{ matrix.ruby_version }} 87 | bundler-cache: true 88 | 89 | - name: Install awesome_bot 90 | run: gem install awesome_bot 91 | 92 | - name: Validate URLs 93 | run: awesome_bot ./*.md contracts/*.sol contracts/**/*.sol --request-delay 0.4 94 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # 🛡️ Security Policy 2 | 3 | ## 📬 Reporting a Vulnerability 4 | 5 | Please report any security issues you find to [pascal.caversaccio@hotmail.ch](mailto:pascal.caversaccio@hotmail.ch). My public PGP key is: 6 | 7 | ```sh 8 | -----BEGIN PGP PUBLIC KEY BLOCK----- 9 | Comment: Type: 3,072-bit RSA 10 | Comment: Fingerprint: 063E966C93AB4356492FE0327C3B4B4B7725111F 11 | 12 | 13 | mQGNBGBUv08BDADTYA0GjLhbKbCezlVAubakXh0jcIbkqZPF1wueSbSgDjlS6+d8 14 | 67V6ft4hNXJhpNxqr07LrcbUEDdB7WK8EUA9qsLtVRznR/B8y2HwrFs7jbYAUzl6 15 | lZ6UgzXl2QCeKI3B3foa7aGDeBkm1um3zXlR4+b8d4krO8pZTJepC5T+UF3C81Kb 16 | lV+6s+bSsHPtLHwBh+tJtSFF7hQoU1lhVW0hKVGUUwGfoFuYjWh47fLtiEvvtM2e 17 | EUZ/0v9nMTKg+tuk4nrR7J+ARdDxaqDWLNTwzGvuTAgkjw6I+zrzFmgsAbdFLFKE 18 | e4+wzE/fb3BAWVRDruhNJmSLqTpkLYyoLkf5GAf9X1gAdeCbiwBgeRnF4m8gLUul 19 | m7mixTW45TOAm9upCZaYWhYQpHosix5NLaBAGMCaTgzJS4Ij8BGh6Td+vv2RYIW+ 20 | j8tfg6uwGXYsclk609bXk6Z2S+DZalNtbSffwCKXqAU4jO194JYL471tB+OvkF6g 21 | KciJZOs+OB0fZc8AEQEAAbQ4UGFzY2FsIE1hcmNvIENhdmVyc2FjY2lvIDxwYXNj 22 | YWwuY2F2ZXJzYWNjaW9AaG90bWFpbC5jaD6JAc4EEwEIADgCGwMFCwkIBwIGFQoJ 23 | CAsCBBYCAwECHgECF4AWIQQGPpZsk6tDVkkv4DJ8O0tLdyURHwUCYvqMQAAKCRB8 24 | O0tLdyURH85gC/oCX3nn+TK/t/tkiOzVnPlCECbrayQlIEgenE7s+oHP6XouhtWJ 25 | UOq7vyssZ+QPqMVNXsXzQrzlf52KVUTHtaSysus4l5lMX1Uuj+Mzy/vAEnPi2+Qo 26 | XZE3xHv+H/kFnqnM6U4MB9OCvOC5fz/cx28141w7dbFTqquBJPIb0DUSTtWpIcqV 27 | zr0sHx59+BMyB3gNiaT6KXmcBCrLlszySuEtw9MMfrfBYuNUXQv2efl3ZjVaN5Ah 28 | LErIZwlJG1OJ+l5WPbPEceh5k7BIqksNBB6T5kkgNbsju22Bc8t2NkxKRHzUmwMz 29 | p1Z30kNNtLmkv3M+ZQZYyKtMGhFf9cll6x/Z8NATEo/rfzbUdlmmodl0Zt07Yla4 30 | jJ23oRSzgzsqiwyN3PA+iOtkNeR/UfrWFKss5RY+B5QTH/GSHvd5yKB7AWjTo2mM 31 | oo1uydwlboN6EfiK10+4sveVG/DdQIVkiu2wAYsS3UL7V0q5Yava0wQmhqNeyWcM 32 | 8xRq/K0pbJk8rwy5AY0EYFS/TwEMALgHOcvmfAc12vcYA+RjtLZPoqIBzPsyvEJi 33 | ryq45AdpSDKa+xLWXIyGCKdlaEZ0Ts9rGtXhW4Sg/YWZhBPbOftu5fsp8d9NWKTD 34 | Pbomob7UeAj9jxS8GCArkG0L6IagH4LopbKtOmh5JjavCjIWe3rRNe1hA1jVa2ES 35 | QSPfotHPm8728eO1lyOdGgukThvS3El5cUcwY0isAFKq6Z8JL2gawtR1J4ceQQGu 36 | b9PFUeSrWe05nQlgXTyZUyN6RPBTKAQwO0zEAcYhLMEizzfuiTOKQTUw5pDwFgEw 37 | nQ2tI+SYui+GTBlmd0XzBlPlgQMSCvSLZG4Vg8nK0hJdqxHadtKmnew9+vrcj4bP 38 | UG0qFesg9hbdj6UzKHUZL0Y76jNrgKwXFGmQJP0L+y+XqJGIX39ugOcccjntH7Ct 39 | xU1/Eqmcv3K0CXXAYu3OirVF51X72ynTTI5xcxHgSBGSPjPS57cNmqm6YslZ9NCE 40 | 4RDw9wtjzWmnUGQDK0nxY0X/t3pt9QARAQABiQG2BBgBCAAgAhsMFiEEBj6WbJOr 41 | Q1ZJL+AyfDtLS3clER8FAmX5RC0ACgkQfDtLS3clER+UAQv9Gzcb4Tu7ILduVOeZ 42 | /X0HQ0WTSngMV3kEtUGIriMJK/JvAqARrw+b/NMYqiGeIAxhGi6OGRorcn69oQHU 43 | 6DuEh5PMEH1rjp3yqoP12LNXJIFgBGSrvnp7adguxHqBAAfYk2PKId0pRoAiDQdh 44 | X2P0di47SnDyZ/I7dUVpHsrDLZ1OPZx+l5l+IblhGkDlzdMITqtdrYzZpmhJRVJG 45 | rSlRPB1qHyVQ6KLpYwKFcQOjbY+HrDTSjQxclS18I5TDSEz7/SOTyup4riAFxOc8 46 | qJSLrgBmKqeEuGn1MKyBMTSx+yUQYRzIzc9/gpKAjv7LMFYp6zHHZQx17QOAlOi/ 47 | tFyq1dUnUayvwx0USoztbNM1Etv1nn9v2+QsTT5omtDc/YsgmfG3hmUIz5MgPof7 48 | Ty5HlJbxax7AZEG6StNNGr77+w541SYxiyE8fVxrxtb70OUcmgVF/okaT5m4xFBK 49 | 2NMhuVpgP0fQXk98ALYofKHraidGA1NQQKzFb5AjwQqrzgo0 50 | =DwWq 51 | -----END PGP PUBLIC KEY BLOCK----- 52 | ``` 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Batch Distributor 2 | 3 | [![🕵️‍♂️ Test smart contracts](https://github.com/pcaversaccio/batch-distributor/actions/workflows/test-contracts.yml/badge.svg)](https://github.com/pcaversaccio/batch-distributor/actions/workflows/test-contracts.yml) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/license/mit) 5 | 6 | ## Installation 7 | 8 | It is recommended to install [`pnpm`](https://pnpm.io) through the `npm` package manager, which comes bundled with [Node.js](https://nodejs.org/en) when you install it on your system. It is recommended to use a Node.js version `>=24.2.0`. 9 | 10 | Once you have `npm` installed, you can run the following both to install and upgrade `pnpm`: 11 | 12 | ```console 13 | npm install -g pnpm 14 | ``` 15 | 16 | After having installed `pnpm`, simply run: 17 | 18 | ```console 19 | pnpm install 20 | ``` 21 | 22 | ## Unit Tests 23 | 24 | You can run the unit tests with 25 | 26 | ```console 27 | pnpm test 28 | ``` 29 | 30 | ## Test Coverage 31 | 32 | This repository implements a test coverage [plugin](https://github.com/sc-forks/solidity-coverage). Simply run: 33 | 34 | ```console 35 | pnpm coverage 36 | ``` 37 | 38 | ## Implemented Functionalities 39 | 40 | - **ETH Batch Transaction:** `distributeEther(batch (tuple))` 41 | - **ERC20 Batch Transaction:** `distributeToken(token (address), batch (tuple))` 42 | 43 | The parameter `batch` is a nested struct object that contains an array of tuples that contain each a recipient address & ETH/token amount. Please ensure that the amount for the ETH transactions is given in `wei` (1 wei = $10^{-18}$ ETH) and check the decimal digits for the ERC20 tokens. 44 | 45 | ```typescript 46 | { 47 | txns: [{ recipient: address, amount: amount }]; 48 | } 49 | ``` 50 | 51 | ## Caveats 52 | 53 | 1. Although the batch size is only theoretically limited to the size of `uint256`, sending too many transactions in a batch will cause the block `gasLimit` to be exceeded and therefore such a transaction will revert. A large number of transactions should be split into separate batches. 54 | 2. A low-level Solidity call will copy _any amount of bytes_ to local memory. When bytes are copied from `returndata` to `memory`, the [memory expansion cost](https://ethereum.stackexchange.com/questions/92546/what-is-the-memory-expansion-cost) is paid. This means that when using a standard Solidity call, the callee can **"returnbomb"** the caller, imposing an arbitrary gas cost. Because this gas is paid _by the caller_ and _in the caller's context_, it can cause the caller to run out of gas and halt execution. It is possible to prevent this attack (see e.g. [here](https://github.com/nomad-xyz/ExcessivelySafeCall)), but this contract contains no measures against it. If you need this kind of security, please do not use this contract. 55 | 56 | ## Test Deployments 57 | 58 | The smart contract [`BatchDistributor`](./contracts/BatchDistributor.sol) has been deployed to the following test networks: 59 | 60 | - **Sepolia:** [`0xE710359D8E887afDF66053E6a9e044E0499e3446`](https://sepolia.etherscan.io/address/0xE710359D8E887afDF66053E6a9e044E0499e3446) 61 | - **Hoodi:** [`0xE710359D8E887afDF66053E6a9e044E0499e3446`](https://hoodi.etherscan.io/address/0xE710359D8E887afDF66053E6a9e044E0499e3446) 62 | 63 | ### Examples 64 | 65 | - _Example 1:_ ETH distribution [`0x1a7345857f653944d5d555a81057a1ff0e364929542ab1db2a037496f2ba6f6b`](https://sepolia.etherscan.io/tx/0x1a7345857f653944d5d555a81057a1ff0e364929542ab1db2a037496f2ba6f6b) 66 | - Input tuple data `batch`: `[[["0x9F3f11d72d96910df008Cfe3aBA40F361D2EED03",1],["0x3854Ca47Abc62A3771fE06ab45622A42C4A438Cf",2]]]` 67 | - _Example 2:_ ERC-20 token distribution [`0x224448bdb43314f30236c147447e29e002515c0e285cc76132ac4a270e1f56a8`](https://sepolia.etherscan.io/tx/0x224448bdb43314f30236c147447e29e002515c0e285cc76132ac4a270e1f56a8) 68 | - Input `token` address (Wrapped Ether (WETH)): [`0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14`](https://sepolia.etherscan.io/address/0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14) 69 | - Input tuple data `batch`: `[[["0x9F3f11d72d96910df008Cfe3aBA40F361D2EED03",50],["0x3854Ca47Abc62A3771fE06ab45622A42C4A438Cf",50]]]` 70 | -------------------------------------------------------------------------------- /contracts/BatchDistributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.33; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | /** 8 | * @dev Error that occurs when transferring ether has failed. 9 | * @param emitter The contract that emits the error. 10 | */ 11 | error EtherTransferFail(address emitter); 12 | 13 | /** 14 | * @title Native and ERC-20 Token Batch Distributor 15 | * @author 0x7761676d69 16 | * @notice Helper smart contract for batch sending both 17 | * native and ERC-20 tokens. 18 | * @dev Since we use nested struct objects, we rely on the ABI coder v2. 19 | * The ABI coder v2 is activated by default since Solidity `v0.8.0`. 20 | */ 21 | contract BatchDistributor { 22 | using SafeERC20 for IERC20; 23 | 24 | /** 25 | * @dev Transaction struct for the transaction payload. 26 | */ 27 | struct Transaction { 28 | address payable recipient; 29 | uint256 amount; 30 | } 31 | 32 | /** 33 | * @dev Batch struct for the array of transactions. 34 | */ 35 | struct Batch { 36 | Transaction[] txns; 37 | } 38 | 39 | /** 40 | * @dev You can cut out 10 opcodes in the creation-time EVM bytecode 41 | * if you declare a constructor `payable`. 42 | * 43 | * For more in-depth information see here: 44 | * https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966/5. 45 | */ 46 | constructor() payable {} 47 | 48 | /** 49 | * @dev Distributes ether, denominated in wei, to a predefined batch 50 | * of recipient addresses. 51 | * @notice In the event that excessive ether is sent, the residual 52 | * amount is returned back to the `msg.sender`. 53 | * @param batch Nested struct object that contains an array of tuples that 54 | * contain each a recipient address & ether amount in wei. 55 | */ 56 | function distributeEther(Batch calldata batch) external payable { 57 | /** 58 | * @dev Caching the length in for loops saves 3 additional gas 59 | * for a `calldata` array for each iteration except for the first. 60 | */ 61 | uint256 length = batch.txns.length; 62 | 63 | /** 64 | * @dev If a variable is not set/initialised, it is assumed to have 65 | * the default value. The default value for the `uint` types is 0. 66 | */ 67 | for (uint256 i; i < length; ++i) { 68 | // solhint-disable-next-line avoid-low-level-calls 69 | (bool sent, ) = batch.txns[i].recipient.call{value: batch.txns[i].amount}(""); 70 | if (!sent) revert EtherTransferFail(address(this)); 71 | } 72 | 73 | uint256 balance = address(this).balance; 74 | if (balance != 0) { 75 | /** 76 | * @dev Any wei amount previously forced into this contract (e.g. by 77 | * using the `SELFDESTRUCT` opcode) will be part of the refund transaction. 78 | */ 79 | // solhint-disable-next-line avoid-low-level-calls 80 | (bool refunded, ) = payable(msg.sender).call{value: balance}(""); 81 | if (!refunded) revert EtherTransferFail(address(this)); 82 | } 83 | } 84 | 85 | /** 86 | * @dev Distributes ERC-20 tokens, denominated in their corresponding 87 | * lowest unit, to a predefined batch of recipient addresses. 88 | * @notice To deal with (potentially) non-compliant ERC-20 tokens that 89 | * do have no return value, we use the `SafeERC20` library for external calls. 90 | * Note: Since we cast the token address into the official ERC-20 interface, 91 | * the use of non-compliant ERC-20 tokens is prevented by design. Nevertheless, 92 | * we keep this guardrail for security reasons. 93 | * @param token ERC-20 token contract address. 94 | * @param batch Nested struct object that contains an array of tuples that 95 | * contain each a recipient address & token amount. 96 | */ 97 | function distributeToken(IERC20 token, Batch calldata batch) external { 98 | /** 99 | * @dev Caching the length in for loops saves 3 additional gas 100 | * for a `calldata` array for each iteration except for the first. 101 | */ 102 | uint256 length = batch.txns.length; 103 | 104 | /** 105 | * @dev If a variable is not set/initialised, it is assumed to have 106 | * the default value. The default value for the `uint` types is 0. 107 | */ 108 | uint256 total; 109 | for (uint256 i; i < length; ++i) { 110 | total += batch.txns[i].amount; 111 | } 112 | 113 | /** 114 | * @dev By combining a `transferFrom` call to itself and then 115 | * distributing the tokens from its own address using `transfer`, 116 | * 5'000 gas is saved on each transfer as `allowance` is only 117 | * touched once. 118 | */ 119 | token.safeTransferFrom(msg.sender, address(this), total); 120 | 121 | for (uint256 i; i < length; ++i) { 122 | token.safeTransfer(batch.txns[i].recipient, batch.txns[i].amount); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batch-distributor", 3 | "version": "1.0.0", 4 | "description": "Helper smart contract for batch sending both native and ERC-20 tokens.", 5 | "keywords": [ 6 | "ethereum", 7 | "solidity", 8 | "erc20", 9 | "ether", 10 | "smart-contract" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/pcaversaccio/batch-distributor.git" 15 | }, 16 | "homepage": "https://github.com/pcaversaccio/batch-distributor#readme", 17 | "bugs": { 18 | "url": "https://github.com/pcaversaccio/batch-distributor/issues" 19 | }, 20 | "author": "0x6d61646520666f722074686520776f726c6421", 21 | "license": "MIT", 22 | "packageManager": "pnpm@10.26.1", 23 | "scripts": { 24 | "help": "npx hardhat help", 25 | "accounts": "npx hardhat accounts", 26 | "balances": "npx hardhat balances", 27 | "node:hh": "npx hardhat node", 28 | "clean": "npx hardhat clean", 29 | "test": "npx hardhat test", 30 | "coverage": "npx hardhat coverage", 31 | "compile": "npx hardhat compile", 32 | "size": "npx hardhat size-contracts", 33 | "xdeploy": "npx hardhat xdeploy", 34 | "abi": "npx hardhat export-abi", 35 | "vars:path": "npx hardhat vars path", 36 | "evm": "npx hardhat evm", 37 | "deploy:hh": "npx hardhat run --network hardhat scripts/deploy.ts", 38 | "deploy:localhost": "npx hardhat run --network localhost scripts/deploy.ts", 39 | "deploy:tenderly": "npx hardhat run --network tenderly scripts/deploy.ts", 40 | "deploy:devnet": "npx hardhat run --network devnet scripts/deploy.ts", 41 | "deploy:sepolia": "npx hardhat run --network sepolia scripts/deploy.ts", 42 | "deploy:holesky": "npx hardhat run --network holesky scripts/deploy.ts", 43 | "deploy:hoodi": "npx hardhat run --network hoodi scripts/deploy.ts", 44 | "deploy:ethmain": "npx hardhat run --network ethMain scripts/deploy.ts", 45 | "deploy:bsctestnet": "npx hardhat run --network bscTestnet scripts/deploy.ts", 46 | "deploy:bscmain": "npx hardhat run --network bscMain scripts/deploy.ts", 47 | "deploy:optimismtestnet": "npx hardhat run --network optimismTestnet scripts/deploy.ts", 48 | "deploy:optimismsepolia": "npx hardhat run --network optimismSepolia scripts/deploy.ts", 49 | "deploy:optimismmain": "npx hardhat run --network optimismMain scripts/deploy.ts", 50 | "deploy:arbitrumsepolia": "npx hardhat run --network arbitrumSepolia scripts/deploy.ts", 51 | "deploy:arbitrummain": "npx hardhat run --network arbitrumMain scripts/deploy.ts", 52 | "deploy:arbitrumnova": "npx hardhat run --network arbitrumNova scripts/deploy.ts", 53 | "deploy:amoy": "npx hardhat run --network amoy scripts/deploy.ts", 54 | "deploy:polygonzkevmtestnet": "npx hardhat run --network polygonZkEVMTestnet scripts/deploy.ts", 55 | "deploy:polygon": "npx hardhat run --network polygon scripts/deploy.ts", 56 | "deploy:polygonzkevmmain": "npx hardhat run --network polygonZkEVMMain scripts/deploy.ts", 57 | "deploy:hecomain": "npx hardhat run --network hecoMain scripts/deploy.ts", 58 | "deploy:fantomtestnet": "npx hardhat run --network fantomTestnet scripts/deploy.ts", 59 | "deploy:fantommain": "npx hardhat run --network fantomMain scripts/deploy.ts", 60 | "deploy:fuji": "npx hardhat run --network fuji scripts/deploy.ts", 61 | "deploy:avalanche": "npx hardhat run --network avalanche scripts/deploy.ts", 62 | "deploy:chiado": "npx hardhat run --network chiado scripts/deploy.ts", 63 | "deploy:gnosis": "npx hardhat run --network gnosis scripts/deploy.ts", 64 | "deploy:moonbasealpha": "npx hardhat run --network moonbaseAlpha scripts/deploy.ts", 65 | "deploy:moonriver": "npx hardhat run --network moonriver scripts/deploy.ts", 66 | "deploy:moonbeam": "npx hardhat run --network moonbeam scripts/deploy.ts", 67 | "deploy:alfajores": "npx hardhat run --network alfajores scripts/deploy.ts", 68 | "deploy:celo": "npx hardhat run --network celo scripts/deploy.ts", 69 | "deploy:auroratestnet": "npx hardhat run --network auroraTestnet scripts/deploy.ts", 70 | "deploy:auroramain": "npx hardhat run --network auroraMain scripts/deploy.ts", 71 | "deploy:harmonytestnet": "npx hardhat run --network harmonyTestnet scripts/deploy.ts", 72 | "deploy:harmonymain": "npx hardhat run --network harmonyMain scripts/deploy.ts", 73 | "deploy:spark": "npx hardhat run --network spark scripts/deploy.ts", 74 | "deploy:fuse": "npx hardhat run --network fuse scripts/deploy.ts", 75 | "deploy:cronostestnet": "npx hardhat run --network cronosTestnet scripts/deploy.ts", 76 | "deploy:cronosmain": "npx hardhat run --network cronosMain scripts/deploy.ts", 77 | "deploy:evmostestnet": "npx hardhat run --network evmosTestnet scripts/deploy.ts", 78 | "deploy:evmosmain": "npx hardhat run --network evmosMain scripts/deploy.ts", 79 | "deploy:bobatestnet": "npx hardhat run --network bobaTestnet scripts/deploy.ts", 80 | "deploy:bobamain": "npx hardhat run --network bobaMain scripts/deploy.ts", 81 | "deploy:cantotestnet": "npx hardhat run --network cantoTestnet scripts/deploy.ts", 82 | "deploy:cantomain": "npx hardhat run --network cantoMain scripts/deploy.ts", 83 | "deploy:basetestnet": "npx hardhat run --network baseTestnet scripts/deploy.ts", 84 | "deploy:basesepolia": "npx hardhat run --network baseSepolia scripts/deploy.ts", 85 | "deploy:basemain": "npx hardhat run --network baseMain scripts/deploy.ts", 86 | "deploy:zksynctestnet": "npx hardhat deploy-zksync --network zkSyncTestnet --script deploy-zksync.ts", 87 | "deploy:zksyncmain": "npx hardhat deploy-zksync --network zkSyncMain --script deploy-zksync.ts", 88 | "deploy:mantletestnet": "npx hardhat run --network mantleTestnet scripts/deploy.ts", 89 | "deploy:mantlemain": "npx hardhat run --network mantleMain scripts/deploy.ts", 90 | "deploy:filecointestnet": "npx hardhat run --network filecoinTestnet scripts/deploy.ts", 91 | "deploy:filecoinmain": "npx hardhat run --network filecoinMain scripts/deploy.ts", 92 | "deploy:scrolltestnet": "npx hardhat run --network scrollTestnet scripts/deploy.ts", 93 | "deploy:scrollmain": "npx hardhat run --network scrollMain scripts/deploy.ts", 94 | "deploy:lineatestnet": "npx hardhat run --network lineaTestnet scripts/deploy.ts", 95 | "deploy:lineamain": "npx hardhat run --network lineaMain scripts/deploy.ts", 96 | "deploy:shimmerevmtestnet": "npx hardhat run --network shimmerEVMTestnet scripts/deploy.ts", 97 | "deploy:zoratestnet": "npx hardhat run --network zoraTestnet scripts/deploy.ts", 98 | "deploy:zoramain": "npx hardhat run --network zoraMain scripts/deploy.ts", 99 | "deploy:luksotestnet": "npx hardhat run --network luksoTestnet scripts/deploy.ts", 100 | "deploy:luksomain": "npx hardhat run --network luksoMain scripts/deploy.ts", 101 | "deploy:mantatestnet": "npx hardhat run --network mantaTestnet scripts/deploy.ts", 102 | "deploy:mantamain": "npx hardhat run --network mantaMain scripts/deploy.ts", 103 | "deploy:shardeumtestnet": "npx hardhat run --network shardeumTestnet scripts/deploy.ts", 104 | "deploy:artheratestnet": "npx hardhat run --network artheraTestnet scripts/deploy.ts", 105 | "deploy:frametestnet": "npx hardhat run --network frameTestnet scripts/deploy.ts", 106 | "deploy:endurancetestnet": "npx hardhat run --network enduranceTestnet scripts/deploy.ts", 107 | "deploy:opendurancetestnet": "npx hardhat run --network openduranceTestnet scripts/deploy.ts", 108 | "deploy:endurancemain": "npx hardhat run --network enduranceMain scripts/deploy.ts", 109 | "deploy:blasttestnet": "npx hardhat run --network blastTestnet scripts/deploy.ts", 110 | "deploy:blastmain": "npx hardhat run --network blastMain scripts/deploy.ts", 111 | "deploy:kromatestnet": "npx hardhat run --network kromaTestnet scripts/deploy.ts", 112 | "deploy:kromamain": "npx hardhat run --network kromaMain scripts/deploy.ts", 113 | "deploy:dostestnet": "npx hardhat run --network dosTestnet scripts/deploy.ts", 114 | "deploy:dosmain": "npx hardhat run --network dosMain scripts/deploy.ts", 115 | "deploy:fraxtaltestnet": "npx hardhat run --network fraxtalTestnet scripts/deploy.ts", 116 | "deploy:fraxtalmain": "npx hardhat run --network fraxtalMain scripts/deploy.ts", 117 | "deploy:kavamain": "npx hardhat run --network kavaMain scripts/deploy.ts", 118 | "deploy:metistestnet": "npx hardhat run --network metisTestnet scripts/deploy.ts", 119 | "deploy:metismain": "npx hardhat run --network metisMain scripts/deploy.ts", 120 | "deploy:modetestnet": "npx hardhat run --network modeTestnet scripts/deploy.ts", 121 | "deploy:modemain": "npx hardhat run --network modeMain scripts/deploy.ts", 122 | "deploy:seidevnet": "npx hardhat run --network seiDevnet scripts/deploy.ts", 123 | "deploy:seitestnet": "npx hardhat run --network seiTestnet scripts/deploy.ts", 124 | "deploy:seimain": "npx hardhat run --network seiMain scripts/deploy.ts", 125 | "deploy:xlayertestnet": "npx hardhat run --network xlayerTestnet scripts/deploy.ts", 126 | "deploy:xlayermain": "npx hardhat run --network xlayerMain scripts/deploy.ts", 127 | "deploy:bobtestnet": "npx hardhat run --network bobTestnet scripts/deploy.ts", 128 | "deploy:bobmain": "npx hardhat run --network bobMain scripts/deploy.ts", 129 | "deploy:coretestnet": "npx hardhat run --network coreTestnet scripts/deploy.ts", 130 | "deploy:coremain": "npx hardhat run --network coreMain scripts/deploy.ts", 131 | "deploy:telostestnet": "npx hardhat run --network telosTestnet scripts/deploy.ts", 132 | "deploy:telosmain": "npx hardhat run --network telosMain scripts/deploy.ts", 133 | "deploy:rootstocktestnet": "npx hardhat run --network rootstockTestnet scripts/deploy.ts", 134 | "deploy:rootstockmain": "npx hardhat run --network rootstockMain scripts/deploy.ts", 135 | "deploy:chiliztestnet": "npx hardhat run --network chilizTestnet scripts/deploy.ts", 136 | "deploy:chilizmain": "npx hardhat run --network chilizMain scripts/deploy.ts", 137 | "deploy:taraxatestnet": "npx hardhat run --network taraxaTestnet scripts/deploy.ts", 138 | "deploy:taraxamain": "npx hardhat run --network taraxaMain scripts/deploy.ts", 139 | "deploy:gravityalphatestnet": "npx hardhat run --network gravityAlphaTestnet scripts/deploy.ts", 140 | "deploy:gravityalphamain": "npx hardhat run --network gravityAlphaMain scripts/deploy.ts", 141 | "deploy:taikotestnet": "npx hardhat run --network taikoTestnet scripts/deploy.ts", 142 | "deploy:taikomain": "npx hardhat run --network taikoMain scripts/deploy.ts", 143 | "deploy:zetachaintestnet": "npx hardhat run --network zetaChainTestnet scripts/deploy.ts", 144 | "deploy:zetachainmain": "npx hardhat run --network zetaChainMain scripts/deploy.ts", 145 | "deploy:5irechaintestnet": "npx hardhat run --network 5ireChainTestnet scripts/deploy.ts", 146 | "deploy:5irechainmain": "npx hardhat run --network 5ireChainMain scripts/deploy.ts", 147 | "deploy:sapphiretestnet": "npx hardhat run --network sapphireTestnet scripts/deploy.ts", 148 | "deploy:sapphiremain": "npx hardhat run --network sapphireMain scripts/deploy.ts", 149 | "deploy:worldchaintestnet": "npx hardhat run --network worldChainTestnet scripts/deploy.ts", 150 | "deploy:worldchainmain": "npx hardhat run --network worldChainMain scripts/deploy.ts", 151 | "deploy:plumetestnet": "npx hardhat run --network plumeTestnet scripts/deploy.ts", 152 | "deploy:plumemain": "npx hardhat run --network plumeMain scripts/deploy.ts", 153 | "deploy:unichaintestnet": "npx hardhat run --network unichainTestnet scripts/deploy.ts", 154 | "deploy:unichainmain": "npx hardhat run --network unichainMain scripts/deploy.ts", 155 | "deploy:xdctestnet": "npx hardhat run --network xdcTestnet scripts/deploy.ts", 156 | "deploy:xdcmain": "npx hardhat run --network xdcMain scripts/deploy.ts", 157 | "deploy:sxtestnet": "npx hardhat run --network sxTestnet scripts/deploy.ts", 158 | "deploy:sxmain": "npx hardhat run --network sxMain scripts/deploy.ts", 159 | "deploy:lisktestnet": "npx hardhat run --network liskTestnet scripts/deploy.ts", 160 | "deploy:liskmain": "npx hardhat run --network liskMain scripts/deploy.ts", 161 | "deploy:metall2testnet": "npx hardhat run --network metalL2Testnet scripts/deploy.ts", 162 | "deploy:metall2main": "npx hardhat run --network metalL2Main scripts/deploy.ts", 163 | "deploy:superseedtestnet": "npx hardhat run --network superseedTestnet scripts/deploy.ts", 164 | "deploy:superseedmain": "npx hardhat run --network superseedMain scripts/deploy.ts", 165 | "deploy:storytestnet": "npx hardhat run --network storyTestnet scripts/deploy.ts", 166 | "deploy:sonictestnet": "npx hardhat run --network sonicTestnet scripts/deploy.ts", 167 | "deploy:sonicmain": "npx hardhat run --network sonicMain scripts/deploy.ts", 168 | "deploy:flowtestnet": "npx hardhat run --network flowTestnet scripts/deploy.ts", 169 | "deploy:flowmain": "npx hardhat run --network flowMain scripts/deploy.ts", 170 | "deploy:inktestnet": "npx hardhat run --network inkTestnet scripts/deploy.ts", 171 | "deploy:inkmain": "npx hardhat run --network inkMain scripts/deploy.ts", 172 | "deploy:morphtestnet": "npx hardhat run --network morphTestnet scripts/deploy.ts", 173 | "deploy:morphmain": "npx hardhat run --network morphMain scripts/deploy.ts", 174 | "deploy:shapetestnet": "npx hardhat run --network shapeTestnet scripts/deploy.ts", 175 | "deploy:shapemain": "npx hardhat run --network shapeMain scripts/deploy.ts", 176 | "deploy:etherlinktestnet": "npx hardhat run --network etherlinkTestnet scripts/deploy.ts", 177 | "deploy:etherlinkmain": "npx hardhat run --network etherlinkMain scripts/deploy.ts", 178 | "deploy:soneiumtestnet": "npx hardhat run --network soneiumTestnet scripts/deploy.ts", 179 | "deploy:soneiummain": "npx hardhat run --network soneiumMain scripts/deploy.ts", 180 | "deploy:swelltestnet": "npx hardhat run --network swellTestnet scripts/deploy.ts", 181 | "deploy:swellmain": "npx hardhat run --network swellMain scripts/deploy.ts", 182 | "deploy:hemitestnet": "npx hardhat run --network hemiTestnet scripts/deploy.ts", 183 | "deploy:hemimain": "npx hardhat run --network hemiMain scripts/deploy.ts", 184 | "deploy:berachaintestnet": "npx hardhat run --network berachainTestnet scripts/deploy.ts", 185 | "deploy:berachainmain": "npx hardhat run --network berachainMain scripts/deploy.ts", 186 | "deploy:monadtestnet": "npx hardhat run --network monadTestnet scripts/deploy.ts", 187 | "deploy:corntestnet": "npx hardhat run --network cornTestnet scripts/deploy.ts", 188 | "deploy:cornmain": "npx hardhat run --network cornMain scripts/deploy.ts", 189 | "deploy:arenaztestnet": "npx hardhat run --network arenazTestnet scripts/deploy.ts", 190 | "deploy:arenazmain": "npx hardhat run --network arenazMain scripts/deploy.ts", 191 | "deploy:iotextestnet": "npx hardhat run --network iotexTestnet scripts/deploy.ts", 192 | "deploy:iotexmain": "npx hardhat run --network iotexMain scripts/deploy.ts", 193 | "deploy:hychaintestnet": "npx hardhat run --network hychainTestnet scripts/deploy.ts", 194 | "deploy:hychainmain": "npx hardhat run --network hychainMain scripts/deploy.ts", 195 | "deploy:zircuittestnet": "npx hardhat run --network zircuitTestnet scripts/deploy.ts", 196 | "deploy:zircuitmain": "npx hardhat run --network zircuitMain scripts/deploy.ts", 197 | "deploy:megaethtestnet": "npx hardhat run --network megaETHTestnet scripts/deploy.ts", 198 | "deploy:bitlayertestnet": "npx hardhat run --network bitlayerTestnet scripts/deploy.ts", 199 | "deploy:bitlayermain": "npx hardhat run --network bitlayerMain scripts/deploy.ts", 200 | "deploy:ronintestnet": "npx hardhat run --network roninTestnet scripts/deploy.ts", 201 | "deploy:roninmain": "npx hardhat run --network roninMain scripts/deploy.ts", 202 | "deploy:immutablezkevmtestnet": "npx hardhat run --network immutableZkEVMTestnet scripts/deploy.ts", 203 | "deploy:immutablezkevmmain": "npx hardhat run --network immutableZkEVMMain scripts/deploy.ts", 204 | "deploy:abstracttestnet": "npx hardhat run --network abstractTestnet scripts/deploy.ts", 205 | "deploy:abstractmain": "npx hardhat run --network abstractMain scripts/deploy.ts", 206 | "deploy:hyperevmtestnet": "npx hardhat run --network hyperevmTestnet scripts/deploy.ts", 207 | "deploy:hyperevmmain": "npx hardhat run --network hyperevmMain scripts/deploy.ts", 208 | "deploy:kaiamain": "npx hardhat run --network kaiaMain scripts/deploy.ts", 209 | "deploy:apechaintestnet": "npx hardhat run --network apeChainTestnet scripts/deploy.ts", 210 | "deploy:apechainmain": "npx hardhat run --network apeChainMain scripts/deploy.ts", 211 | "prettier:check": "npx prettier -c \"**/*.{js,ts,md,sol,json,yml,yaml}\"", 212 | "prettier:fix": "npx prettier -w \"**/*.{js,ts,md,sol,json,yml,yaml}\"", 213 | "solhint:check": "npx solhint \"contracts/**/*.sol\"", 214 | "solhint:fix": "npx solhint \"contracts/**/*.sol\" --fix", 215 | "lint:check": "pnpm run prettier:check && pnpm run solhint:check && npx eslint .", 216 | "lint:fix": "pnpm run prettier:fix && pnpm run solhint:fix && npx eslint . --fix" 217 | }, 218 | "devDependencies": { 219 | "@eslint/js": "^9.39.2", 220 | "@matterlabs/hardhat-zksync-deploy": "^1.8.0", 221 | "@matterlabs/hardhat-zksync-ethers": "^1.4.0", 222 | "@matterlabs/hardhat-zksync-solc": "^1.5.1", 223 | "@matterlabs/hardhat-zksync-verify": "^1.9.1", 224 | "@nomicfoundation/hardhat-chai-matchers": "^2.1.0", 225 | "@nomicfoundation/hardhat-ethers": "^3.1.3", 226 | "@nomicfoundation/hardhat-ledger": "^1.2.2", 227 | "@nomicfoundation/hardhat-network-helpers": "^1.1.2", 228 | "@nomicfoundation/hardhat-verify": "^2.1.3", 229 | "@openzeppelin/contracts": "^5.4.0", 230 | "@tenderly/hardhat-tenderly": "^2.5.2", 231 | "@typechain/ethers-v6": "^0.5.1", 232 | "@typechain/hardhat": "^9.1.0", 233 | "@types/chai": "^4.3.20", 234 | "@types/mocha": "^10.0.10", 235 | "@types/node": "^25.0.3", 236 | "chai": "^4.5.0", 237 | "eslint": "^9.39.2", 238 | "eslint-config-prettier": "^10.1.8", 239 | "ethers": "^6.16.0", 240 | "globals": "^16.5.0", 241 | "hardhat": "^2.28.0", 242 | "hardhat-abi-exporter": "^2.11.0", 243 | "hardhat-contract-sizer": "^2.10.1", 244 | "hardhat-gas-reporter": "^2.3.0", 245 | "prettier": "^3.7.4", 246 | "prettier-plugin-solidity": "^2.2.1", 247 | "solhint": "^6.0.2", 248 | "solidity-coverage": "^0.8.17", 249 | "ts-node": "^10.9.2", 250 | "typechain": "^8.3.2", 251 | "typescript": "^5.9.3", 252 | "typescript-eslint": "^8.50.1", 253 | "xdeployer": "^3.1.20", 254 | "zksync-ethers": "^6.21.0" 255 | }, 256 | "pnpm": { 257 | "onlyBuiltDependencies": [ 258 | "bufferutil", 259 | "cpu-features", 260 | "keccak", 261 | "node-hid", 262 | "protobufjs", 263 | "secp256k1", 264 | "ssh2", 265 | "usb", 266 | "utf-8-validate" 267 | ] 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /test/BatchDistributor.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-expressions */ 2 | import { expect } from "chai"; 3 | import { HDNodeWallet } from "ethers"; 4 | import hre from "hardhat"; 5 | import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; 6 | import { 7 | BatchDistributor, 8 | ERC20Mock, 9 | ERC20ReturnFalseMock, 10 | } from "../typechain-types"; 11 | 12 | async function expectThrowsAsync( 13 | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 14 | method: Function, 15 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 16 | params: any[], 17 | message?: RegExp, 18 | ) { 19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 20 | let err: any = ""; 21 | try { 22 | await method(...params); 23 | } catch (error) { 24 | err = error; 25 | } 26 | if (message) { 27 | expect(err.message).to.match(message); 28 | } else { 29 | expect(err).to.be.an("Error"); 30 | } 31 | } 32 | 33 | describe("Distributor Contract", function () { 34 | let distributor: BatchDistributor; 35 | let distributorAddr: string; 36 | 37 | let erc20: ERC20Mock; 38 | let erc20Addr: string; 39 | 40 | let erc20ReturnFalse: ERC20ReturnFalseMock; 41 | let erc20ReturnFalseAddr: string; 42 | 43 | let sender: SignerWithAddress; 44 | let addr1: SignerWithAddress; 45 | let addr2: SignerWithAddress; 46 | let addr3: SignerWithAddress; 47 | let addr4: SignerWithAddress; 48 | 49 | beforeEach(async function () { 50 | await hre.ethers.provider.send("hardhat_reset", []); 51 | distributor = (await hre.ethers.deployContract( 52 | "BatchDistributor", 53 | )) as unknown as BatchDistributor; 54 | [sender, addr1, addr2, addr3, addr4] = await hre.ethers.getSigners(); 55 | await distributor.waitForDeployment(); 56 | distributorAddr = await distributor.getAddress(); 57 | }); 58 | 59 | describe("ETH Transactions", function () { 60 | it("Transfers ETH to the given address", async function () { 61 | const txAmount = hre.ethers.parseEther("5.0"); 62 | 63 | const initialRecipientBalance = await hre.ethers.provider.getBalance( 64 | addr1.address, 65 | ); 66 | 67 | const overrides = { 68 | value: hre.ethers.parseEther("5.0"), 69 | }; 70 | 71 | const batch = { 72 | txns: [{ recipient: addr1.address, amount: txAmount.toString() }], 73 | }; 74 | 75 | await distributor.distributeEther(batch, overrides); 76 | 77 | const finalRecipientBalance = await hre.ethers.provider.getBalance( 78 | addr1.address, 79 | ); 80 | 81 | expect(finalRecipientBalance).to.equal( 82 | initialRecipientBalance + txAmount, 83 | ); 84 | }); 85 | 86 | it("Transfers ETH to the multiple given addresses", async function () { 87 | const batch = { 88 | txns: [ 89 | { 90 | recipient: addr1.address, 91 | amount: hre.ethers.parseEther("0.2151").toString(), 92 | }, 93 | { 94 | recipient: addr2.address, 95 | amount: hre.ethers.parseEther("2.040194018").toString(), 96 | }, 97 | { 98 | recipient: addr3.address, 99 | amount: hre.ethers.parseEther("0.0003184").toString(), 100 | }, 101 | { 102 | recipient: addr4.address, 103 | amount: hre.ethers.parseEther("0.000000000001").toString(), 104 | }, 105 | ], 106 | }; 107 | 108 | const overrides = { 109 | value: hre.ethers.parseEther("5.0"), 110 | }; 111 | 112 | await distributor.distributeEther(batch, overrides); 113 | 114 | const finalRecipient1Balance = await hre.ethers.provider.getBalance( 115 | addr1.address, 116 | ); 117 | const finalRecipient2Balance = await hre.ethers.provider.getBalance( 118 | addr2.address, 119 | ); 120 | const finalRecipient3Balance = await hre.ethers.provider.getBalance( 121 | addr3.address, 122 | ); 123 | const finalRecipient4Balance = await hre.ethers.provider.getBalance( 124 | addr4.address, 125 | ); 126 | 127 | expect(finalRecipient1Balance).to.equal( 128 | hre.ethers.parseEther("10000") + hre.ethers.parseEther("0.2151"), 129 | ); 130 | expect(finalRecipient2Balance).to.equal( 131 | hre.ethers.parseEther("10000") + hre.ethers.parseEther("2.040194018"), 132 | ); 133 | expect(finalRecipient3Balance).to.equal( 134 | hre.ethers.parseEther("10000") + hre.ethers.parseEther("0.0003184"), 135 | ); 136 | expect(finalRecipient4Balance).to.equal( 137 | hre.ethers.parseEther("10000") + 138 | hre.ethers.parseEther("0.000000000001"), 139 | ); 140 | }); 141 | 142 | it("Sends back unused funds", async function () { 143 | const fundAmount = hre.ethers.parseEther("20.0"); 144 | const txAmount = hre.ethers.parseEther("5.0"); 145 | 146 | const initialSenderBalance = await hre.ethers.provider.getBalance( 147 | sender.address, 148 | ); 149 | 150 | const overrides = { 151 | value: fundAmount, 152 | }; 153 | 154 | await distributor.distributeEther( 155 | { txns: [{ recipient: addr1.address, amount: txAmount.toString() }] }, 156 | overrides, 157 | ); 158 | 159 | const finalSenderBalance = await hre.ethers.provider.getBalance( 160 | sender.address, 161 | ); 162 | 163 | expect(finalSenderBalance).to.lte(initialSenderBalance - txAmount); 164 | expect(finalSenderBalance).to.above(initialSenderBalance - fundAmount); 165 | expect(finalSenderBalance).to.above( 166 | initialSenderBalance - txAmount - txAmount, 167 | ); 168 | expect(await hre.ethers.provider.getBalance(distributorAddr)).to.equal(0); 169 | }); 170 | 171 | it("Fails if gas limit too low", async function () { 172 | const fundAmount = hre.ethers.parseEther("20.0"); 173 | const txAmount = hre.ethers.parseEther("5.0"); 174 | 175 | const overrides = { 176 | value: fundAmount, 177 | gasLimit: 10, 178 | }; 179 | 180 | await expectThrowsAsync( 181 | distributor.distributeEther, 182 | [ 183 | { txns: [{ recipient: addr1.address, amount: txAmount.toString() }] }, 184 | overrides, 185 | ], 186 | /Transaction requires at least \d* gas but got 10/, 187 | ); 188 | }); 189 | 190 | it("Reverts if it runs out of gas", async function () { 191 | const fundAmount = hre.ethers.parseEther("20.0"); 192 | const txAmount = hre.ethers.parseEther("5.0"); 193 | 194 | const overrides = { 195 | value: fundAmount, 196 | gasLimit: 25000, 197 | }; 198 | 199 | await expectThrowsAsync( 200 | distributor.distributeEther, 201 | [ 202 | { txns: [{ recipient: addr1.address, amount: txAmount.toString() }] }, 203 | overrides, 204 | ], 205 | /Transaction ran out of gas/, 206 | ); 207 | }); 208 | 209 | it("Reverts if funds are sent to a non-payable address", async function () { 210 | const erc20 = await hre.ethers.deployContract("ERC20Mock"); 211 | await erc20.waitForDeployment(); 212 | 213 | const txAmount = hre.ethers.parseEther("5.0"); 214 | 215 | const overrides = { 216 | value: txAmount, 217 | }; 218 | 219 | await expect( 220 | distributor.distributeEther( 221 | { 222 | txns: [ 223 | { 224 | recipient: await erc20.getAddress(), 225 | amount: txAmount.toString(), 226 | }, 227 | ], 228 | }, 229 | overrides, 230 | ), 231 | ) 232 | .to.be.revertedWithCustomError(distributor, "EtherTransferFail") 233 | .withArgs(distributorAddr); 234 | }); 235 | 236 | it("Reverts if unused funds are sent back to a non-payable address", async function () { 237 | const erc20 = await hre.ethers.deployContract("ERC20Mock"); 238 | await erc20.waitForDeployment(); 239 | const fundAmount = hre.ethers.parseEther("20.0"); 240 | const txAmount = hre.ethers.parseEther("5.0"); 241 | 242 | await hre.network.provider.send("hardhat_setCode", [ 243 | await sender.getAddress(), 244 | "0x11", 245 | ]); 246 | 247 | const overrides = { 248 | value: fundAmount, 249 | }; 250 | 251 | await expect( 252 | distributor.distributeEther( 253 | { txns: [{ recipient: addr1.address, amount: txAmount.toString() }] }, 254 | overrides, 255 | ), 256 | ) 257 | .to.be.revertedWithCustomError(distributor, "EtherTransferFail") 258 | .withArgs(distributorAddr); 259 | }); 260 | }); 261 | 262 | describe("ERC20Mock Transactions", function () { 263 | beforeEach(async function () { 264 | erc20 = (await hre.ethers.deployContract( 265 | "ERC20Mock", 266 | )) as unknown as ERC20Mock; 267 | await erc20.waitForDeployment(); 268 | erc20Addr = await erc20.getAddress(); 269 | await erc20.mint(sender.address, 1000000); 270 | }); 271 | 272 | describe("Allowance", function () { 273 | it("Reverts transactions when given not enough allowance", async function () { 274 | const batch = { txns: [{ recipient: addr1.address, amount: 1000 }] }; 275 | 276 | await expect(distributor.distributeToken(erc20Addr, batch)) 277 | .to.be.revertedWithCustomError(erc20, "ERC20InsufficientAllowance") 278 | .withArgs(distributorAddr, 0, 1000); 279 | }); 280 | 281 | it("Keeps any unspent allowance", async function () { 282 | const batch = { 283 | txns: [ 284 | { recipient: addr1.address, amount: 1000 }, 285 | { recipient: addr2.address, amount: 5000 }, 286 | ], 287 | }; 288 | 289 | // set allowance for distributor 290 | expect(await erc20.approve(distributorAddr, 10000)).to.be.ok; 291 | 292 | // make transactions 293 | expect(await distributor.distributeToken(erc20Addr, batch)).to.be.ok; 294 | 295 | // no balance should remain because we approved the exact amount 296 | expect(await erc20.allowance(sender.address, distributorAddr)).to.equal( 297 | 4000, 298 | ); 299 | }); 300 | }); 301 | 302 | describe("Safe Transfer from Owner", function () { 303 | it("Reverts if total balance overflows uint256", async function () { 304 | const batch = { 305 | txns: [ 306 | { recipient: addr1.address, amount: hre.ethers.MaxUint256 }, 307 | { recipient: addr2.address, amount: 1 }, 308 | ], 309 | }; 310 | 311 | // attempt to make transaction 312 | await expect( 313 | distributor.distributeToken(erc20Addr, batch), 314 | ).to.be.revertedWithPanic(0x11); 315 | }); 316 | 317 | it("Reverts if any parameter is negative", async function () { 318 | const batch = { 319 | txns: [ 320 | { recipient: addr1.address, amount: 5000 }, 321 | { recipient: addr2.address, amount: -3000 }, 322 | ], 323 | }; 324 | 325 | // attempt to make transaction 326 | await expectThrowsAsync( 327 | distributor.distributeToken, 328 | [erc20Addr, batch], 329 | /value out-of-bounds/, 330 | ); 331 | }); 332 | 333 | it("Continues if any parameter is zero", async function () { 334 | const batch = { 335 | txns: [ 336 | { recipient: addr1.address, amount: 5000 }, 337 | { recipient: addr2.address, amount: 0 }, 338 | ], 339 | }; 340 | 341 | await erc20.approve(distributorAddr, 5000); 342 | 343 | // attempt to make transaction 344 | expect(await distributor.distributeToken(erc20Addr, batch)).to.be.ok; 345 | }); 346 | }); 347 | 348 | describe("Transfer to Recipient", function () { 349 | it("Transfers token to the recipient address given enough allowance", async function () { 350 | const batch = { 351 | txns: [ 352 | { recipient: addr1.address, amount: 1000 }, 353 | { recipient: addr2.address, amount: 5000 }, 354 | ], 355 | }; 356 | 357 | await erc20.approve(distributorAddr, 6000); 358 | 359 | // make transactions 360 | expect(await distributor.distributeToken(erc20Addr, batch)).to.be.ok; 361 | 362 | // no balance should remain because we approved the exact amount 363 | expect(await erc20.allowance(sender.address, distributorAddr)).to.equal( 364 | 0, 365 | ); 366 | }); 367 | 368 | it("Continues when transferring to self or token contract", async function () { 369 | const batch = { 370 | txns: [ 371 | { recipient: sender.address, amount: 1000 }, 372 | { recipient: erc20Addr, amount: 5000 }, 373 | ], 374 | }; 375 | 376 | await erc20.approve(distributorAddr, 6000); 377 | 378 | // make transactions 379 | expect(await distributor.distributeToken(erc20Addr, batch)).to.be.ok; 380 | 381 | // no balance should remain because we approved the exact amount 382 | expect(await erc20.allowance(sender.address, distributorAddr)).to.equal( 383 | 0, 384 | ); 385 | }); 386 | 387 | it("Continues when given infinite allowance", async function () { 388 | const batch = { 389 | txns: [ 390 | { recipient: sender.address, amount: 1000 }, 391 | { recipient: erc20Addr, amount: 500000 }, 392 | ], 393 | }; 394 | 395 | await erc20.approve(distributorAddr, hre.ethers.MaxUint256); 396 | 397 | // make transactions 398 | expect(await distributor.distributeToken(erc20Addr, batch)).to.be.ok; 399 | }); 400 | }); 401 | }); 402 | 403 | describe("ERC20ReturnFalseMock Transactions", function () { 404 | beforeEach(async function () { 405 | erc20ReturnFalse = (await hre.ethers.deployContract( 406 | "ERC20ReturnFalseMock", 407 | )) as unknown as ERC20ReturnFalseMock; 408 | await erc20ReturnFalse.waitForDeployment(); 409 | erc20ReturnFalseAddr = await erc20ReturnFalse.getAddress(); 410 | await erc20ReturnFalse.mint(sender.address, 1000000); 411 | }); 412 | 413 | describe("Allowance", function () { 414 | it("Reverts transactions when given not enough allowance", async function () { 415 | const batch = { txns: [{ recipient: addr1.address, amount: 1000 }] }; 416 | 417 | await expect(distributor.distributeToken(erc20ReturnFalseAddr, batch)) 418 | .to.be.revertedWithCustomError( 419 | distributor, 420 | "SafeERC20FailedOperation", 421 | ) 422 | .withArgs(erc20ReturnFalseAddr); 423 | }); 424 | 425 | it("Keeps any unspent allowance", async function () { 426 | const batch = { 427 | txns: [ 428 | { recipient: addr1.address, amount: 1000 }, 429 | { recipient: addr2.address, amount: 5000 }, 430 | ], 431 | }; 432 | 433 | // set allowance for distributor 434 | expect(await erc20.approve(distributorAddr, 10000)).to.be.ok; 435 | 436 | // make transactions 437 | expect(await distributor.distributeToken(erc20ReturnFalseAddr, batch)) 438 | .to.be.ok; 439 | 440 | // no balance should remain because we approved the exact amount 441 | expect(await erc20.allowance(sender.address, distributorAddr)).to.equal( 442 | 4000, 443 | ); 444 | }); 445 | }); 446 | 447 | describe("Safe Transfer from Owner", function () { 448 | it("Continues if any parameter is zero", async function () { 449 | const batch = { 450 | txns: [ 451 | { recipient: addr1.address, amount: 5000 }, 452 | { recipient: addr2.address, amount: 0 }, 453 | ], 454 | }; 455 | 456 | await erc20.approve(distributorAddr, 5000); 457 | 458 | // attempt to make transaction 459 | expect(await distributor.distributeToken(erc20ReturnFalseAddr, batch)) 460 | .to.be.ok; 461 | }); 462 | }); 463 | 464 | describe("Transfer to Recipient", function () { 465 | it("Transfers token to the recipient address given enough allowance", async function () { 466 | const batch = { 467 | txns: [ 468 | { recipient: addr1.address, amount: 1000 }, 469 | { recipient: addr2.address, amount: 5000 }, 470 | ], 471 | }; 472 | 473 | await erc20.approve(distributorAddr, 6000); 474 | 475 | // make transactions 476 | expect(await distributor.distributeToken(erc20ReturnFalseAddr, batch)) 477 | .to.be.ok; 478 | 479 | // no balance should remain because we approved the exact amount 480 | expect(await erc20.allowance(sender.address, distributorAddr)).to.equal( 481 | 0, 482 | ); 483 | }); 484 | 485 | it("Continues when transferring to self or token contract", async function () { 486 | const batch = { 487 | txns: [ 488 | { recipient: sender.address, amount: 1000 }, 489 | { recipient: erc20ReturnFalseAddr, amount: 5000 }, 490 | ], 491 | }; 492 | 493 | await erc20.approve(distributorAddr, 6000); 494 | 495 | // make transactions 496 | expect(await distributor.distributeToken(erc20ReturnFalseAddr, batch)) 497 | .to.be.ok; 498 | 499 | // no balance should remain because we approved the exact amount 500 | expect(await erc20.allowance(sender.address, distributorAddr)).to.equal( 501 | 0, 502 | ); 503 | }); 504 | 505 | it("Continues when given infinite allowance", async function () { 506 | const batch = { 507 | txns: [ 508 | { recipient: sender.address, amount: 1000 }, 509 | { recipient: erc20ReturnFalseAddr, amount: 500000 }, 510 | ], 511 | }; 512 | 513 | await erc20.approve(distributorAddr, hre.ethers.MaxUint256); 514 | 515 | // make transactions 516 | expect(await distributor.distributeToken(erc20ReturnFalseAddr, batch)) 517 | .to.be.ok; 518 | }); 519 | }); 520 | }); 521 | 522 | describe("Mass Transaction Tests", function () { 523 | beforeEach(async function () { 524 | erc20 = (await hre.ethers.deployContract( 525 | "ERC20Mock", 526 | )) as unknown as ERC20Mock; 527 | await erc20.waitForDeployment(); 528 | erc20Addr = await erc20.getAddress(); 529 | await erc20.mint(sender.address, 1000000); 530 | }); 531 | 532 | it("Transfers ETH to a large number of addresses", async function () { 533 | const transactionCount = 150; 534 | const accounts: HDNodeWallet[] = []; 535 | 536 | for (let i = 0; i < transactionCount; i++) { 537 | accounts.push(hre.ethers.Wallet.createRandom()); 538 | } 539 | 540 | const batch: { 541 | txns: { 542 | recipient: string; 543 | amount: string; 544 | }[]; 545 | } = { txns: [] }; 546 | 547 | for (const account of accounts) { 548 | batch.txns.push({ 549 | recipient: account.address, 550 | amount: hre.ethers.parseEther("0.00001").toString(), 551 | }); 552 | } 553 | 554 | const overrides = { 555 | value: hre.ethers.parseEther("5.0"), 556 | }; 557 | 558 | expect(await distributor.distributeEther(batch, overrides)).to.be.ok; 559 | }); 560 | 561 | it("Transfers ERC20 to a large number of addresses", async function () { 562 | const transactionCount = 150; 563 | const accounts: HDNodeWallet[] = []; 564 | 565 | for (let i = 0; i < transactionCount; i++) { 566 | accounts.push(hre.ethers.Wallet.createRandom()); 567 | } 568 | 569 | const batch: { 570 | txns: { 571 | recipient: string; 572 | amount: string; 573 | }[]; 574 | } = { txns: [] }; 575 | 576 | for (const account of accounts) { 577 | batch.txns.push({ recipient: account.address, amount: "1" }); 578 | } 579 | 580 | await erc20.approve(distributorAddr, transactionCount); 581 | expect(await distributor.distributeToken(erc20Addr, batch)).to.be.ok; 582 | }); 583 | }); 584 | }); 585 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig, task, vars } from "hardhat/config"; 2 | 3 | import "@nomicfoundation/hardhat-ethers"; 4 | import "@nomicfoundation/hardhat-verify"; 5 | import "@nomicfoundation/hardhat-ledger"; 6 | import "@nomicfoundation/hardhat-chai-matchers"; 7 | import "@typechain/hardhat"; 8 | 9 | import "xdeployer"; 10 | import "@matterlabs/hardhat-zksync-solc"; 11 | import "@matterlabs/hardhat-zksync-deploy"; 12 | import "@matterlabs/hardhat-zksync-verify"; 13 | import "@matterlabs/hardhat-zksync-ethers"; 14 | import "hardhat-gas-reporter"; 15 | import "hardhat-abi-exporter"; 16 | import "solidity-coverage"; 17 | import "hardhat-contract-sizer"; 18 | // Uncomment if you want to use the Hardhat Tenderly module 19 | // You must also uncomment the subsequent `tenderly` configuration in this file accordingly 20 | // import "@tenderly/hardhat-tenderly"; 21 | 22 | const ethMainnetUrl = vars.get("ETH_MAINNET_URL", "https://rpc.ankr.com/eth"); 23 | const accounts = [ 24 | vars.get( 25 | "PRIVATE_KEY", 26 | // `keccak256("DEFAULT_VALUE")` 27 | "0x0d1706281056b7de64efd2088195fa8224c39103f578c9b84f951721df3fa71c", 28 | ), 29 | ]; 30 | const ledgerAccounts = [ 31 | vars.get( 32 | "LEDGER_ACCOUNT", 33 | // `bytes20(uint160(uint256(keccak256("DEFAULT_VALUE"))))` 34 | "0x8195fa8224c39103f578c9b84f951721df3fa71c", 35 | ), 36 | ]; 37 | 38 | task("accounts", "Prints the list of accounts", async (_, hre) => { 39 | const accounts = await hre.ethers.getSigners(); 40 | 41 | for (const account of accounts) { 42 | console.log(account.address); 43 | } 44 | }); 45 | 46 | task("evm", "Prints the configured EVM version", async (_, hre) => { 47 | console.log(hre.config.solidity.compilers[0].settings.evmVersion); 48 | }); 49 | 50 | task( 51 | "balances", 52 | "Prints the list of accounts and their balances", 53 | async (_, hre) => { 54 | const accounts = await hre.ethers.getSigners(); 55 | 56 | for (const account of accounts) { 57 | console.log( 58 | account.address + 59 | " " + 60 | (await hre.ethers.provider.getBalance(account.address)), 61 | ); 62 | } 63 | }, 64 | ); 65 | 66 | const config: HardhatUserConfig = { 67 | solidity: { 68 | // Only use Solidity default versions `>=0.8.31` for EVM networks that support the new `osaka` opcode `CLZ` and precompiled contract `P256VERIFY`: 69 | // https://github.com/ethereum/execution-specs/blob/forks/osaka/src/ethereum/forks/osaka/__init__.py 70 | // Only use Solidity default versions `>=0.8.30` for EVM networks that support the new `prague` precompiled contracts: 71 | // https://github.com/ethereum/execution-specs/blob/forks/osaka/src/ethereum/forks/prague/__init__.py 72 | // Only use Solidity default versions `>=0.8.25` for EVM networks that support the new `cancun` opcodes: 73 | // https://github.com/ethereum/execution-specs/blob/forks/osaka/src/ethereum/forks/cancun/__init__.py 74 | // Only use Solidity default versions `>=0.8.20` for EVM networks that support the new `shanghai` opcode `PUSH0`: 75 | // https://github.com/ethereum/execution-specs/blob/forks/osaka/src/ethereum/forks/shanghai/__init__.py 76 | // Otherwise, use the versions `<=0.8.19` 77 | version: "0.8.33", 78 | settings: { 79 | optimizer: { 80 | enabled: true, 81 | runs: 999_999, 82 | }, 83 | evmVersion: "osaka", 84 | }, 85 | }, 86 | zksolc: { 87 | version: "1.5.15", 88 | compilerSource: "binary", 89 | settings: { 90 | enableEraVMExtensions: false, 91 | forceEVMLA: false, 92 | optimizer: { 93 | enabled: true, 94 | mode: "3", 95 | fallback_to_optimizing_for_size: false, 96 | }, 97 | }, 98 | }, 99 | networks: { 100 | hardhat: { 101 | initialBaseFeePerGas: 0, 102 | chainId: 31337, 103 | hardfork: "osaka", 104 | forking: { 105 | url: vars.get("ETH_MAINNET_URL", ethMainnetUrl), 106 | // The Hardhat network will by default fork from the latest mainnet block 107 | // To pin the block number, specify it below 108 | // You will need access to a node with archival data for this to work! 109 | // blockNumber: 14743877, 110 | // If you want to do some forking, set `enabled` to true 111 | enabled: false, 112 | }, 113 | ledgerAccounts, 114 | // zksync: true, // Enable ZKsync in the Hardhat local network 115 | }, 116 | localhost: { 117 | url: "http://127.0.0.1:8545", 118 | ledgerAccounts, 119 | }, 120 | tenderly: { 121 | // Add your own Tenderly fork ID 122 | url: `https://rpc.tenderly.co/fork/${vars.get("TENDERLY_FORK_ID", "")}`, 123 | ledgerAccounts, 124 | }, 125 | devnet: { 126 | // Add your own Tenderly DevNet ID 127 | url: `https://rpc.vnet.tenderly.co/devnet/${vars.get( 128 | "TENDERLY_DEVNET_ID", 129 | "", 130 | )}`, 131 | accounts, 132 | ledgerAccounts, 133 | }, 134 | sepolia: { 135 | chainId: 11155111, 136 | url: vars.get("ETH_SEPOLIA_TESTNET_URL", "https://rpc.sepolia.org"), 137 | accounts, 138 | ledgerAccounts, 139 | }, 140 | holesky: { 141 | chainId: 17000, 142 | url: vars.get( 143 | "ETH_HOLESKY_TESTNET_URL", 144 | "https://holesky.rpc.thirdweb.com", 145 | ), 146 | accounts, 147 | ledgerAccounts, 148 | }, 149 | hoodi: { 150 | chainId: 560048, 151 | url: vars.get( 152 | "ETH_HOODI_TESTNET_URL", 153 | "https://rpc.hoodi.ethpandaops.io", 154 | ), 155 | accounts, 156 | ledgerAccounts, 157 | }, 158 | ethMain: { 159 | chainId: 1, 160 | url: ethMainnetUrl, 161 | accounts, 162 | ledgerAccounts, 163 | }, 164 | bscTestnet: { 165 | chainId: 97, 166 | url: vars.get( 167 | "BSC_TESTNET_URL", 168 | "https://data-seed-prebsc-1-s1.binance.org:8545", 169 | ), 170 | accounts, 171 | ledgerAccounts, 172 | }, 173 | bscMain: { 174 | chainId: 56, 175 | url: vars.get("BSC_MAINNET_URL", "https://bsc-dataseed1.binance.org"), 176 | accounts, 177 | ledgerAccounts, 178 | }, 179 | optimismTestnet: { 180 | chainId: 420, 181 | url: vars.get("OPTIMISM_TESTNET_URL", "https://goerli.optimism.io"), 182 | accounts, 183 | ledgerAccounts, 184 | }, 185 | optimismSepolia: { 186 | chainId: 11155420, 187 | url: vars.get("OPTIMISM_SEPOLIA_URL", "https://sepolia.optimism.io"), 188 | accounts, 189 | ledgerAccounts, 190 | }, 191 | optimismMain: { 192 | chainId: 10, 193 | url: vars.get("OPTIMISM_MAINNET_URL", "https://mainnet.optimism.io"), 194 | accounts, 195 | ledgerAccounts, 196 | }, 197 | arbitrumSepolia: { 198 | chainId: 421614, 199 | url: vars.get( 200 | "ARBITRUM_SEPOLIA_URL", 201 | "https://sepolia-rollup.arbitrum.io/rpc", 202 | ), 203 | accounts, 204 | ledgerAccounts, 205 | }, 206 | arbitrumMain: { 207 | chainId: 42161, 208 | url: vars.get("ARBITRUM_MAINNET_URL", "https://arb1.arbitrum.io/rpc"), 209 | accounts, 210 | ledgerAccounts, 211 | }, 212 | arbitrumNova: { 213 | chainId: 42170, 214 | url: vars.get("ARBITRUM_NOVA_URL", "https://nova.arbitrum.io/rpc"), 215 | accounts, 216 | ledgerAccounts, 217 | }, 218 | amoy: { 219 | chainId: 80002, 220 | url: vars.get( 221 | "POLYGON_TESTNET_URL", 222 | "https://rpc-amoy.polygon.technology", 223 | ), 224 | accounts, 225 | ledgerAccounts, 226 | }, 227 | polygonZkEVMTestnet: { 228 | chainId: 2442, 229 | url: vars.get( 230 | "POLYGON_ZKEVM_TESTNET_URL", 231 | "https://rpc.cardona.zkevm-rpc.com", 232 | ), 233 | accounts, 234 | ledgerAccounts, 235 | }, 236 | polygon: { 237 | chainId: 137, 238 | url: vars.get("POLYGON_MAINNET_URL", "https://polygon-rpc.com"), 239 | accounts, 240 | ledgerAccounts, 241 | }, 242 | polygonZkEVMMain: { 243 | chainId: 1101, 244 | url: vars.get("POLYGON_ZKEVM_MAINNET_URL", "https://zkevm-rpc.com"), 245 | accounts, 246 | ledgerAccounts, 247 | }, 248 | hecoMain: { 249 | chainId: 128, 250 | url: vars.get("HECO_MAINNET_URL", "https://http-mainnet.hecochain.com"), 251 | accounts, 252 | ledgerAccounts, 253 | }, 254 | fantomTestnet: { 255 | chainId: 4002, 256 | url: vars.get("FANTOM_TESTNET_URL", "https://rpc.testnet.fantom.network"), 257 | accounts, 258 | ledgerAccounts, 259 | }, 260 | fantomMain: { 261 | chainId: 250, 262 | url: vars.get("FANTOM_MAINNET_URL", "https://rpc.ankr.com/fantom"), 263 | accounts, 264 | ledgerAccounts, 265 | }, 266 | fuji: { 267 | chainId: 43113, 268 | url: vars.get( 269 | "AVALANCHE_TESTNET_URL", 270 | "https://api.avax-test.network/ext/bc/C/rpc", 271 | ), 272 | accounts, 273 | ledgerAccounts, 274 | }, 275 | avalanche: { 276 | chainId: 43114, 277 | url: vars.get( 278 | "AVALANCHE_MAINNET_URL", 279 | "https://api.avax.network/ext/bc/C/rpc", 280 | ), 281 | accounts, 282 | ledgerAccounts, 283 | }, 284 | chiado: { 285 | chainId: 10200, 286 | url: vars.get("GNOSIS_TESTNET_URL", "https://rpc.chiadochain.net"), 287 | accounts, 288 | ledgerAccounts, 289 | }, 290 | gnosis: { 291 | chainId: 100, 292 | url: vars.get("GNOSIS_MAINNET_URL", "https://rpc.gnosischain.com"), 293 | accounts, 294 | ledgerAccounts, 295 | }, 296 | moonbaseAlpha: { 297 | chainId: 1287, 298 | url: vars.get( 299 | "MOONBEAM_TESTNET_URL", 300 | "https://rpc.api.moonbase.moonbeam.network", 301 | ), 302 | accounts, 303 | ledgerAccounts, 304 | }, 305 | moonriver: { 306 | chainId: 1285, 307 | url: vars.get( 308 | "MOONRIVER_MAINNET_URL", 309 | "https://moonriver.public.blastapi.io", 310 | ), 311 | accounts, 312 | ledgerAccounts, 313 | }, 314 | moonbeam: { 315 | chainId: 1284, 316 | url: vars.get( 317 | "MOONBEAM_MAINNET_URL", 318 | "https://moonbeam.public.blastapi.io", 319 | ), 320 | accounts, 321 | ledgerAccounts, 322 | }, 323 | alfajores: { 324 | chainId: 44787, 325 | url: vars.get( 326 | "CELO_TESTNET_URL", 327 | "https://alfajores-forno.celo-testnet.org", 328 | ), 329 | accounts, 330 | ledgerAccounts, 331 | }, 332 | celo: { 333 | chainId: 42220, 334 | url: vars.get("CELO_MAINNET_URL", "https://forno.celo.org"), 335 | accounts, 336 | ledgerAccounts, 337 | }, 338 | auroraTestnet: { 339 | chainId: 1313161555, 340 | url: vars.get("AURORA_TESTNET_URL", "https://testnet.aurora.dev"), 341 | accounts, 342 | ledgerAccounts, 343 | }, 344 | auroraMain: { 345 | chainId: 1313161554, 346 | url: vars.get("AURORA_MAINNET_URL", "https://mainnet.aurora.dev"), 347 | accounts, 348 | ledgerAccounts, 349 | }, 350 | harmonyTestnet: { 351 | chainId: 1666700000, 352 | url: vars.get("HARMONY_TESTNET_URL", "https://api.s0.b.hmny.io"), 353 | accounts, 354 | ledgerAccounts, 355 | }, 356 | harmonyMain: { 357 | chainId: 1666600000, 358 | url: vars.get("HARMONY_MAINNET_URL", "https://api.harmony.one"), 359 | accounts, 360 | ledgerAccounts, 361 | }, 362 | spark: { 363 | chainId: 123, 364 | url: vars.get("FUSE_TESTNET_URL", "https://rpc.fusespark.io"), 365 | accounts, 366 | ledgerAccounts, 367 | }, 368 | fuse: { 369 | chainId: 122, 370 | url: vars.get("FUSE_MAINNET_URL", "https://rpc.fuse.io"), 371 | accounts, 372 | ledgerAccounts, 373 | }, 374 | cronosTestnet: { 375 | chainId: 338, 376 | url: vars.get("CRONOS_TESTNET_URL", "https://evm-t3.cronos.org"), 377 | accounts, 378 | ledgerAccounts, 379 | }, 380 | cronosMain: { 381 | chainId: 25, 382 | url: vars.get("CRONOS_MAINNET_URL", "https://evm.cronos.org"), 383 | accounts, 384 | ledgerAccounts, 385 | }, 386 | evmosTestnet: { 387 | chainId: 9000, 388 | url: vars.get("EVMOS_TESTNET_URL", "https://evmos-testnet.lava.build"), 389 | accounts, 390 | ledgerAccounts, 391 | }, 392 | evmosMain: { 393 | chainId: 9001, 394 | url: vars.get("EVMOS_MAINNET_URL", "https://evmos.lava.build"), 395 | accounts, 396 | ledgerAccounts, 397 | }, 398 | bobaTestnet: { 399 | chainId: 2888, 400 | url: vars.get("BOBA_TESTNET_URL", "https://goerli.boba.network"), 401 | accounts, 402 | ledgerAccounts, 403 | }, 404 | bobaMain: { 405 | chainId: 288, 406 | url: vars.get("BOBA_MAINNET_URL", "https://replica.boba.network"), 407 | accounts, 408 | ledgerAccounts, 409 | }, 410 | cantoTestnet: { 411 | chainId: 7701, 412 | url: vars.get("CANTO_TESTNET_URL", "https://canto-testnet.plexnode.wtf"), 413 | accounts, 414 | ledgerAccounts, 415 | }, 416 | cantoMain: { 417 | chainId: 7700, 418 | url: vars.get("CANTO_MAINNET_URL", "https://canto.slingshot.finance"), 419 | accounts, 420 | ledgerAccounts, 421 | }, 422 | baseTestnet: { 423 | chainId: 84531, 424 | url: vars.get("BASE_TESTNET_URL", "https://goerli.base.org"), 425 | accounts, 426 | ledgerAccounts, 427 | }, 428 | baseSepolia: { 429 | chainId: 84532, 430 | url: vars.get("BASE_SEPOLIA_URL", "https://sepolia.base.org"), 431 | accounts, 432 | ledgerAccounts, 433 | }, 434 | baseMain: { 435 | chainId: 8453, 436 | url: vars.get("BASE_MAINNET_URL", "https://mainnet.base.org"), 437 | accounts, 438 | ledgerAccounts, 439 | }, 440 | zkSyncTestnet: { 441 | chainId: 300, 442 | url: vars.get("ZKSYNC_TESTNET_URL", "https://sepolia.era.zksync.dev"), 443 | ethNetwork: "sepolia", 444 | zksync: true, 445 | verifyURL: 446 | "https://explorer.sepolia.era.zksync.dev/contract_verification", 447 | browserVerifyURL: "https://sepolia.explorer.zksync.io", 448 | enableVerifyURL: true, 449 | accounts, 450 | ledgerAccounts, 451 | }, 452 | zkSyncMain: { 453 | chainId: 324, 454 | url: vars.get("ZKSYNC_MAINNET_URL", "https://mainnet.era.zksync.io"), 455 | ethNetwork: "mainnet", 456 | zksync: true, 457 | verifyURL: 458 | "https://zksync2-mainnet-explorer.zksync.io/contract_verification", 459 | browserVerifyURL: "https://explorer.zksync.io", 460 | enableVerifyURL: true, 461 | accounts, 462 | ledgerAccounts, 463 | }, 464 | mantleTestnet: { 465 | chainId: 5003, 466 | url: vars.get("MANTLE_TESTNET_URL", "https://rpc.sepolia.mantle.xyz"), 467 | accounts, 468 | ledgerAccounts, 469 | }, 470 | mantleMain: { 471 | chainId: 5000, 472 | url: vars.get("MANTLE_MAINNET_URL", "https://rpc.mantle.xyz"), 473 | accounts, 474 | ledgerAccounts, 475 | }, 476 | filecoinTestnet: { 477 | chainId: 314159, 478 | url: vars.get( 479 | "FILECOIN_TESTNET_URL", 480 | "https://rpc.ankr.com/filecoin_testnet", 481 | ), 482 | accounts, 483 | ledgerAccounts, 484 | }, 485 | filecoinMain: { 486 | chainId: 314, 487 | url: vars.get("FILECOIN_MAINNET_URL", "https://rpc.ankr.com/filecoin"), 488 | accounts, 489 | ledgerAccounts, 490 | }, 491 | scrollTestnet: { 492 | chainId: 534351, 493 | url: vars.get("SCROLL_TESTNET_URL", "https://sepolia-rpc.scroll.io"), 494 | accounts, 495 | ledgerAccounts, 496 | }, 497 | scrollMain: { 498 | chainId: 534352, 499 | url: vars.get("SCROLL_MAINNET_URL", "https://rpc.scroll.io"), 500 | accounts, 501 | ledgerAccounts, 502 | }, 503 | lineaTestnet: { 504 | chainId: 59141, 505 | url: vars.get("LINEA_TESTNET_URL", "https://rpc.sepolia.linea.build"), 506 | accounts, 507 | ledgerAccounts, 508 | }, 509 | lineaMain: { 510 | chainId: 59144, 511 | url: vars.get("LINEA_MAINNET_URL", "https://rpc.linea.build"), 512 | accounts, 513 | ledgerAccounts, 514 | }, 515 | shimmerEVMTestnet: { 516 | chainId: 1071, 517 | url: vars.get( 518 | "SHIMMEREVM_TESTNET_URL", 519 | "https://json-rpc.evm.testnet.shimmer.network", 520 | ), 521 | accounts, 522 | ledgerAccounts, 523 | }, 524 | zoraTestnet: { 525 | chainId: 999999999, 526 | url: vars.get("ZORA_TESTNET_URL", "https://sepolia.rpc.zora.energy"), 527 | accounts, 528 | ledgerAccounts, 529 | }, 530 | zoraMain: { 531 | chainId: 7777777, 532 | url: vars.get("ZORA_MAINNET_URL", "https://rpc.zora.energy"), 533 | accounts, 534 | ledgerAccounts, 535 | }, 536 | luksoTestnet: { 537 | chainId: 4201, 538 | url: vars.get("LUKSO_TESTNET_URL", "https://rpc.testnet.lukso.network"), 539 | accounts, 540 | ledgerAccounts, 541 | }, 542 | luksoMain: { 543 | chainId: 42, 544 | url: vars.get("LUKSO_MAINNET_URL", "https://rpc.lukso.gateway.fm"), 545 | accounts, 546 | ledgerAccounts, 547 | }, 548 | mantaTestnet: { 549 | chainId: 3441006, 550 | url: vars.get( 551 | "MANTA_TESTNET_URL", 552 | "https://pacific-rpc.sepolia-testnet.manta.network/http", 553 | ), 554 | accounts, 555 | ledgerAccounts, 556 | }, 557 | mantaMain: { 558 | chainId: 169, 559 | url: vars.get( 560 | "MANTA_MAINNET_URL", 561 | "https://pacific-rpc.manta.network/http", 562 | ), 563 | accounts, 564 | ledgerAccounts, 565 | }, 566 | shardeumTestnet: { 567 | chainId: 8081, 568 | url: vars.get("SHARDEUM_TESTNET_URL", "https://dapps.shardeum.org"), 569 | accounts, 570 | ledgerAccounts, 571 | }, 572 | artheraTestnet: { 573 | chainId: 10243, 574 | url: vars.get("ARTHERA_TESTNET_URL", "https://rpc-test.arthera.net"), 575 | accounts, 576 | ledgerAccounts, 577 | }, 578 | frameTestnet: { 579 | chainId: 68840142, 580 | url: vars.get("FRAME_TESTNET_URL", "https://rpc.testnet.frame.xyz/http"), 581 | accounts, 582 | ledgerAccounts, 583 | }, 584 | enduranceTestnet: { 585 | chainId: 6480, 586 | url: vars.get( 587 | "ENDURANCE_TESTNET_URL", 588 | "https://myrpctestnet.fusionist.io", 589 | ), 590 | accounts, 591 | ledgerAccounts, 592 | }, 593 | openduranceTestnet: { 594 | chainId: 6480001001, 595 | url: vars.get( 596 | "OPENDURANCE_TESTNET_URL", 597 | "https://rpc-l2-testnet.fusionist.io", 598 | ), 599 | accounts, 600 | ledgerAccounts, 601 | }, 602 | enduranceMain: { 603 | chainId: 648, 604 | url: vars.get( 605 | "ENDURANCE_MAINNET_URL", 606 | "https://rpc-endurance.fusionist.io", 607 | ), 608 | accounts, 609 | ledgerAccounts, 610 | }, 611 | blastTestnet: { 612 | chainId: 168587773, 613 | url: vars.get("BLAST_TESTNET_URL", "https://sepolia.blast.io"), 614 | accounts, 615 | ledgerAccounts, 616 | }, 617 | blastMain: { 618 | chainId: 81457, 619 | url: vars.get("BLAST_MAINNET_URL", "https://rpc.blast.io"), 620 | accounts, 621 | ledgerAccounts, 622 | }, 623 | kromaTestnet: { 624 | chainId: 2358, 625 | url: vars.get("KROMA_TESTNET_URL", "https://api.sepolia.kroma.network"), 626 | accounts, 627 | ledgerAccounts, 628 | }, 629 | kromaMain: { 630 | chainId: 255, 631 | url: vars.get("KROMA_MAINNET_URL", "https://api.kroma.network"), 632 | accounts, 633 | ledgerAccounts, 634 | }, 635 | dosTestnet: { 636 | chainId: 3939, 637 | url: vars.get("DOS_TESTNET_URL", "https://test.doschain.com"), 638 | accounts, 639 | ledgerAccounts, 640 | }, 641 | dosMain: { 642 | chainId: 7979, 643 | url: vars.get("DOS_MAINNET_URL", "https://main.doschain.com"), 644 | accounts, 645 | ledgerAccounts, 646 | }, 647 | fraxtalTestnet: { 648 | chainId: 2522, 649 | url: vars.get("FRAXTAL_TESTNET_URL", "https://rpc.testnet.frax.com"), 650 | accounts, 651 | ledgerAccounts, 652 | }, 653 | fraxtalMain: { 654 | chainId: 252, 655 | url: vars.get("FRAXTAL_MAINNET_URL", "https://rpc.frax.com"), 656 | accounts, 657 | ledgerAccounts, 658 | }, 659 | kavaMain: { 660 | chainId: 2222, 661 | url: vars.get("KAVA_MAINNET_URL", "https://evm.kava-rpc.com"), 662 | accounts, 663 | ledgerAccounts, 664 | }, 665 | metisTestnet: { 666 | chainId: 59902, 667 | url: vars.get("METIS_TESTNET_URL", "https://sepolia.metisdevops.link"), 668 | accounts, 669 | ledgerAccounts, 670 | }, 671 | metisMain: { 672 | chainId: 1088, 673 | url: vars.get( 674 | "METIS_MAINNET_URL", 675 | "https://andromeda.metis.io/?owner=1088", 676 | ), 677 | accounts, 678 | ledgerAccounts, 679 | }, 680 | modeTestnet: { 681 | chainId: 919, 682 | url: vars.get("MODE_TESTNET_URL", "https://sepolia.mode.network"), 683 | accounts, 684 | ledgerAccounts, 685 | }, 686 | modeMain: { 687 | chainId: 34443, 688 | url: vars.get("MODE_MAINNET_URL", "https://mainnet.mode.network"), 689 | accounts, 690 | ledgerAccounts, 691 | }, 692 | seiDevnet: { 693 | chainId: 713715, 694 | url: vars.get("SEI_DEVNET_URL", "https://evm-rpc-arctic-1.sei-apis.com"), 695 | accounts, 696 | ledgerAccounts, 697 | }, 698 | seiTestnet: { 699 | chainId: 1328, 700 | url: vars.get("SEI_TESTNET_URL", "https://evm-rpc-testnet.sei-apis.com"), 701 | accounts, 702 | ledgerAccounts, 703 | }, 704 | seiMain: { 705 | chainId: 1329, 706 | url: vars.get("SEI_MAINNET_URL", "https://evm-rpc.sei-apis.com"), 707 | accounts, 708 | ledgerAccounts, 709 | }, 710 | xlayerTestnet: { 711 | chainId: 195, 712 | url: vars.get("XLAYER_TESTNET_URL", "https://testrpc.xlayer.tech"), 713 | accounts, 714 | ledgerAccounts, 715 | }, 716 | xlayerMain: { 717 | chainId: 196, 718 | url: vars.get("XLAYER_MAINNET_URL", "https://rpc.xlayer.tech"), 719 | accounts, 720 | ledgerAccounts, 721 | }, 722 | bobTestnet: { 723 | chainId: 111, 724 | url: vars.get("BOB_TESTNET_URL", "https://testnet.rpc.gobob.xyz"), 725 | accounts, 726 | ledgerAccounts, 727 | }, 728 | bobMain: { 729 | chainId: 60808, 730 | url: vars.get("BOB_MAINNET_URL", "https://rpc.gobob.xyz"), 731 | accounts, 732 | ledgerAccounts, 733 | }, 734 | coreTestnet: { 735 | chainId: 1115, 736 | url: vars.get("CORE_TESTNET_URL", "https://rpc.test.btcs.network"), 737 | accounts, 738 | ledgerAccounts, 739 | }, 740 | coreMain: { 741 | chainId: 1116, 742 | url: vars.get("CORE_MAINNET_URL", "https://rpc.coredao.org"), 743 | accounts, 744 | ledgerAccounts, 745 | }, 746 | telosTestnet: { 747 | chainId: 41, 748 | url: vars.get("TELOS_TESTNET_URL", "https://testnet.telos.net/evm"), 749 | accounts, 750 | ledgerAccounts, 751 | }, 752 | telosMain: { 753 | chainId: 40, 754 | url: vars.get("TELOS_MAINNET_URL", "https://mainnet.telos.net/evm"), 755 | accounts, 756 | ledgerAccounts, 757 | }, 758 | rootstockTestnet: { 759 | chainId: 31, 760 | url: vars.get( 761 | "ROOTSTOCK_TESTNET_URL", 762 | "https://public-node.testnet.rsk.co", 763 | ), 764 | accounts, 765 | ledgerAccounts, 766 | }, 767 | rootstockMain: { 768 | chainId: 30, 769 | url: vars.get("ROOTSTOCK_MAINNET_URL", "https://public-node.rsk.co"), 770 | accounts, 771 | ledgerAccounts, 772 | }, 773 | chilizTestnet: { 774 | chainId: 88882, 775 | url: vars.get("CHILIZ_TESTNET_URL", "https://spicy-rpc.chiliz.com"), 776 | accounts, 777 | ledgerAccounts, 778 | }, 779 | chilizMain: { 780 | chainId: 88888, 781 | url: vars.get("CHILIZ_MAINNET_URL", "https://rpc.ankr.com/chiliz"), 782 | accounts, 783 | ledgerAccounts, 784 | }, 785 | taraxaTestnet: { 786 | chainId: 842, 787 | url: vars.get("TARAXA_TESTNET_URL", "https://rpc.testnet.taraxa.io"), 788 | accounts, 789 | ledgerAccounts, 790 | }, 791 | taraxaMain: { 792 | chainId: 841, 793 | url: vars.get("TARAXA_MAINNET_URL", "https://rpc.mainnet.taraxa.io"), 794 | accounts, 795 | ledgerAccounts, 796 | }, 797 | gravityAlphaTestnet: { 798 | chainId: 13505, 799 | url: vars.get( 800 | "GRAVITY_ALPHA_TESTNET_URL", 801 | "https://rpc-sepolia.gravity.xyz", 802 | ), 803 | accounts, 804 | ledgerAccounts, 805 | }, 806 | gravityAlphaMain: { 807 | chainId: 1625, 808 | url: vars.get("GRAVITY_ALPHA_MAINNET_URL", "https://rpc.gravity.xyz"), 809 | accounts, 810 | ledgerAccounts, 811 | }, 812 | taikoTestnet: { 813 | chainId: 167009, 814 | url: vars.get("TAIKO_TESTNET_URL", "https://rpc.hekla.taiko.xyz"), 815 | accounts, 816 | ledgerAccounts, 817 | }, 818 | taikoMain: { 819 | chainId: 167000, 820 | url: vars.get("TAIKO_MAINNET_URL", "https://rpc.taiko.xyz"), 821 | accounts, 822 | ledgerAccounts, 823 | }, 824 | zetaChainTestnet: { 825 | chainId: 7001, 826 | url: vars.get("ZETA_CHAIN_TESTNET_URL", "https://7001.rpc.thirdweb.com"), 827 | accounts, 828 | ledgerAccounts, 829 | }, 830 | zetaChainMain: { 831 | chainId: 7000, 832 | url: vars.get("ZETA_CHAIN_MAINNET_URL", "https://7000.rpc.thirdweb.com"), 833 | accounts, 834 | ledgerAccounts, 835 | }, 836 | "5ireChainTestnet": { 837 | chainId: 997, 838 | url: vars.get( 839 | "5IRE_CHAIN_TESTNET_URL", 840 | "https://rpc.testnet.5ire.network", 841 | ), 842 | accounts, 843 | ledgerAccounts, 844 | }, 845 | "5ireChainMain": { 846 | chainId: 995, 847 | url: vars.get("5IRE_CHAIN_MAINNET_URL", "https://rpc.5ire.network"), 848 | accounts, 849 | ledgerAccounts, 850 | }, 851 | sapphireTestnet: { 852 | chainId: 23295, 853 | url: vars.get( 854 | "SAPPHIRE_TESTNET_URL", 855 | "https://testnet.sapphire.oasis.io", 856 | ), 857 | accounts, 858 | ledgerAccounts, 859 | }, 860 | sapphireMain: { 861 | chainId: 23294, 862 | url: vars.get("SAPPHIRE_MAINNET_URL", "https://sapphire.oasis.io"), 863 | accounts, 864 | ledgerAccounts, 865 | }, 866 | worldChainTestnet: { 867 | chainId: 4801, 868 | url: vars.get( 869 | "WORLD_CHAIN_TESTNET_URL", 870 | "https://worldchain-sepolia.g.alchemy.com/public", 871 | ), 872 | accounts, 873 | ledgerAccounts, 874 | }, 875 | worldChainMain: { 876 | chainId: 480, 877 | url: vars.get( 878 | "WORLD_CHAIN_MAINNET_URL", 879 | "https://worldchain-mainnet.g.alchemy.com/public", 880 | ), 881 | accounts, 882 | ledgerAccounts, 883 | }, 884 | plumeTestnet: { 885 | chainId: 98867, 886 | url: vars.get("PLUME_TESTNET_URL", "https://testnet-rpc.plume.org"), 887 | accounts, 888 | ledgerAccounts, 889 | }, 890 | plumeMain: { 891 | chainId: 98866, 892 | url: vars.get("PLUME_MAINNET_URL", "https://rpc.plume.org"), 893 | accounts, 894 | ledgerAccounts, 895 | }, 896 | unichainTestnet: { 897 | chainId: 1301, 898 | url: vars.get("UNICHAIN_TESTNET_URL", "https://sepolia.unichain.org"), 899 | accounts, 900 | ledgerAccounts, 901 | }, 902 | unichainMain: { 903 | chainId: 130, 904 | url: vars.get("UNICHAIN_MAINNET_URL", "https://mainnet.unichain.org"), 905 | accounts, 906 | ledgerAccounts, 907 | }, 908 | xdcTestnet: { 909 | chainId: 51, 910 | url: vars.get("XDC_TESTNET_URL", "https://erpc.apothem.network"), 911 | accounts, 912 | ledgerAccounts, 913 | }, 914 | xdcMain: { 915 | chainId: 50, 916 | url: vars.get("XDC_MAINNET_URL", "https://rpc.xinfin.network"), 917 | accounts, 918 | ledgerAccounts, 919 | }, 920 | sxTestnet: { 921 | chainId: 79479957, 922 | url: vars.get( 923 | "SX_TESTNET_URL", 924 | "https://rpc.sx-rollup-testnet.t.raas.gelato.cloud", 925 | ), 926 | accounts, 927 | ledgerAccounts, 928 | }, 929 | sxMain: { 930 | chainId: 4162, 931 | url: vars.get("SX_MAINNET_URL", "https://rpc.sx-rollup.gelato.digital"), 932 | accounts, 933 | ledgerAccounts, 934 | }, 935 | liskTestnet: { 936 | chainId: 4202, 937 | url: vars.get("LISK_TESTNET_URL", "https://rpc.sepolia-api.lisk.com"), 938 | accounts, 939 | ledgerAccounts, 940 | }, 941 | liskMain: { 942 | chainId: 1135, 943 | url: vars.get("LISK_MAINNET_URL", "https://rpc.api.lisk.com"), 944 | accounts, 945 | ledgerAccounts, 946 | }, 947 | metalL2Testnet: { 948 | chainId: 1740, 949 | url: vars.get("METALL2_TESTNET_URL", "https://testnet.rpc.metall2.com"), 950 | accounts, 951 | ledgerAccounts, 952 | }, 953 | metalL2Main: { 954 | chainId: 1750, 955 | url: vars.get("METALL2_MAINNET_URL", "https://rpc.metall2.com"), 956 | accounts, 957 | ledgerAccounts, 958 | }, 959 | superseedTestnet: { 960 | chainId: 53302, 961 | url: vars.get("SUPERSEED_TESTNET_URL", "https://sepolia.superseed.xyz"), 962 | accounts, 963 | ledgerAccounts, 964 | }, 965 | superseedMain: { 966 | chainId: 5330, 967 | url: vars.get("SUPERSEED_MAINNET_URL", "https://mainnet.superseed.xyz"), 968 | accounts, 969 | ledgerAccounts, 970 | }, 971 | storyTestnet: { 972 | chainId: 1315, 973 | url: vars.get("STORY_TESTNET_URL", "https://aeneid.storyrpc.io"), 974 | accounts, 975 | ledgerAccounts, 976 | }, 977 | sonicTestnet: { 978 | chainId: 57054, 979 | url: vars.get("SONIC_TESTNET_URL", "https://rpc.blaze.soniclabs.com"), 980 | accounts, 981 | ledgerAccounts, 982 | }, 983 | sonicMain: { 984 | chainId: 146, 985 | url: vars.get("SONIC_MAINNET_URL", "https://rpc.soniclabs.com"), 986 | accounts, 987 | ledgerAccounts, 988 | }, 989 | flowTestnet: { 990 | chainId: 545, 991 | url: vars.get("FLOW_TESTNET_URL", "https://testnet.evm.nodes.onflow.org"), 992 | accounts, 993 | ledgerAccounts, 994 | }, 995 | flowMain: { 996 | chainId: 747, 997 | url: vars.get("FLOW_MAINNET_URL", "https://mainnet.evm.nodes.onflow.org"), 998 | accounts, 999 | ledgerAccounts, 1000 | }, 1001 | inkTestnet: { 1002 | chainId: 763373, 1003 | url: vars.get( 1004 | "INK_TESTNET_URL", 1005 | "https://rpc-gel-sepolia.inkonchain.com", 1006 | ), 1007 | accounts, 1008 | ledgerAccounts, 1009 | }, 1010 | inkMain: { 1011 | chainId: 57073, 1012 | url: vars.get("INK_MAINNET_URL", "https://rpc-gel.inkonchain.com"), 1013 | accounts, 1014 | ledgerAccounts, 1015 | }, 1016 | morphTestnet: { 1017 | chainId: 2810, 1018 | url: vars.get( 1019 | "MORPH_TESTNET_URL", 1020 | "https://rpc-quicknode-holesky.morphl2.io", 1021 | ), 1022 | accounts, 1023 | ledgerAccounts, 1024 | }, 1025 | morphMain: { 1026 | chainId: 2818, 1027 | url: vars.get("MORPH_MAINNET_URL", "https://rpc-quicknode.morphl2.io"), 1028 | accounts, 1029 | ledgerAccounts, 1030 | }, 1031 | shapeTestnet: { 1032 | chainId: 11011, 1033 | url: vars.get("SHAPE_TESTNET_URL", "https://sepolia.shape.network"), 1034 | accounts, 1035 | ledgerAccounts, 1036 | }, 1037 | shapeMain: { 1038 | chainId: 360, 1039 | url: vars.get("SHAPE_MAINNET_URL", "https://mainnet.shape.network"), 1040 | accounts, 1041 | ledgerAccounts, 1042 | }, 1043 | etherlinkTestnet: { 1044 | chainId: 128123, 1045 | url: vars.get( 1046 | "ETHERLINK_TESTNET_URL", 1047 | "https://node.ghostnet.etherlink.com", 1048 | ), 1049 | accounts, 1050 | ledgerAccounts, 1051 | }, 1052 | etherlinkMain: { 1053 | chainId: 42793, 1054 | url: vars.get( 1055 | "ETHERLINK_MAINNET_URL", 1056 | "https://node.mainnet.etherlink.com", 1057 | ), 1058 | accounts, 1059 | ledgerAccounts, 1060 | }, 1061 | soneiumTestnet: { 1062 | chainId: 1946, 1063 | url: vars.get("SONEIUM_TESTNET_URL", "https://rpc.minato.soneium.org"), 1064 | accounts, 1065 | ledgerAccounts, 1066 | }, 1067 | soneiumMain: { 1068 | chainId: 1868, 1069 | url: vars.get("SONEIUM_MAINNET_URL", "https://rpc.soneium.org"), 1070 | accounts, 1071 | ledgerAccounts, 1072 | }, 1073 | swellTestnet: { 1074 | chainId: 1924, 1075 | url: vars.get( 1076 | "SWELL_TESTNET_URL", 1077 | "https://swell-testnet.alt.technology", 1078 | ), 1079 | accounts, 1080 | ledgerAccounts, 1081 | }, 1082 | swellMain: { 1083 | chainId: 1923, 1084 | url: vars.get( 1085 | "SWELL_MAINNET_URL", 1086 | "https://swell-mainnet.alt.technology", 1087 | ), 1088 | accounts, 1089 | ledgerAccounts, 1090 | }, 1091 | hemiTestnet: { 1092 | chainId: 743111, 1093 | url: vars.get("HEMI_TESTNET_URL", "https://testnet.rpc.hemi.network/rpc"), 1094 | accounts, 1095 | ledgerAccounts, 1096 | }, 1097 | hemiMain: { 1098 | chainId: 43111, 1099 | url: vars.get("HEMI_MAINNET_URL", "https://rpc.hemi.network/rpc"), 1100 | accounts, 1101 | ledgerAccounts, 1102 | }, 1103 | berachainTestnet: { 1104 | chainId: 80084, 1105 | url: vars.get("BERACHAIN_TESTNET_URL", "https://bartio.drpc.org"), 1106 | accounts, 1107 | ledgerAccounts, 1108 | }, 1109 | berachainMain: { 1110 | chainId: 80094, 1111 | url: vars.get("BERACHAIN_MAINNET_URL", "https://rpc.berachain.com"), 1112 | accounts, 1113 | ledgerAccounts, 1114 | }, 1115 | monadTestnet: { 1116 | chainId: 10143, 1117 | url: vars.get("MONAD_TESTNET_URL", "https://testnet-rpc.monad.xyz"), 1118 | accounts, 1119 | ledgerAccounts, 1120 | }, 1121 | cornTestnet: { 1122 | chainId: 21000001, 1123 | url: vars.get("CORN_TESTNET_URL", "https://testnet.corn-rpc.com"), 1124 | accounts, 1125 | ledgerAccounts, 1126 | }, 1127 | cornMain: { 1128 | chainId: 21000000, 1129 | url: vars.get("CORN_MAINNET_URL", "https://mainnet.corn-rpc.com"), 1130 | accounts, 1131 | ledgerAccounts, 1132 | }, 1133 | arenazTestnet: { 1134 | chainId: 9897, 1135 | url: vars.get( 1136 | "ARENAZ_TESTNET_URL", 1137 | "https://rpc.arena-z.t.raas.gelato.cloud", 1138 | ), 1139 | accounts, 1140 | ledgerAccounts, 1141 | }, 1142 | arenazMain: { 1143 | chainId: 7897, 1144 | url: vars.get("ARENAZ_MAINNET_URL", "https://rpc.arena-z.gg"), 1145 | accounts, 1146 | ledgerAccounts, 1147 | }, 1148 | iotexTestnet: { 1149 | chainId: 4690, 1150 | url: vars.get("IOTEX_TESTNET_URL", "https://babel-api.testnet.iotex.io"), 1151 | accounts, 1152 | ledgerAccounts, 1153 | }, 1154 | iotexMain: { 1155 | chainId: 4689, 1156 | url: vars.get("IOTEX_MAINNET_URL", "https://babel-api.mainnet.iotex.io"), 1157 | accounts, 1158 | ledgerAccounts, 1159 | }, 1160 | hychainTestnet: { 1161 | chainId: 29112, 1162 | url: vars.get( 1163 | "HYCHAIN_TESTNET_URL", 1164 | "https://testnet-rpc.hychain.com/http", 1165 | ), 1166 | accounts, 1167 | ledgerAccounts, 1168 | }, 1169 | hychainMain: { 1170 | chainId: 2911, 1171 | url: vars.get("HYCHAIN_MAINNET_URL", "https://rpc.hychain.com/http"), 1172 | accounts, 1173 | ledgerAccounts, 1174 | }, 1175 | zircuitTestnet: { 1176 | chainId: 48898, 1177 | url: vars.get( 1178 | "ZIRCUIT_TESTNET_URL", 1179 | "https://garfield-testnet.zircuit.com", 1180 | ), 1181 | accounts, 1182 | ledgerAccounts, 1183 | }, 1184 | zircuitMain: { 1185 | chainId: 48900, 1186 | url: vars.get("ZIRCUIT_MAINNET_URL", "https://zircuit-mainnet.drpc.org"), 1187 | accounts, 1188 | ledgerAccounts, 1189 | }, 1190 | megaETHTestnet: { 1191 | chainId: 6342, 1192 | url: vars.get("MEGAETH_TESTNET_URL", "https://carrot.megaeth.com/rpc"), 1193 | accounts, 1194 | ledgerAccounts, 1195 | }, 1196 | bitlayerTestnet: { 1197 | chainId: 200810, 1198 | url: vars.get("BITLAYER_TESTNET_URL", "https://testnet-rpc.bitlayer.org"), 1199 | accounts, 1200 | ledgerAccounts, 1201 | }, 1202 | bitlayerMain: { 1203 | chainId: 200901, 1204 | url: vars.get("BITLAYER_MAINNET_URL", "https://rpc.bitlayer.org"), 1205 | accounts, 1206 | ledgerAccounts, 1207 | }, 1208 | roninTestnet: { 1209 | chainId: 2021, 1210 | url: vars.get( 1211 | "RONIN_TESTNET_URL", 1212 | "https://saigon-testnet.roninchain.com/rpc", 1213 | ), 1214 | accounts, 1215 | ledgerAccounts, 1216 | }, 1217 | roninMain: { 1218 | chainId: 2020, 1219 | url: vars.get("RONIN_MAINNET_URL", "https://api.roninchain.com/rpc"), 1220 | accounts, 1221 | ledgerAccounts, 1222 | }, 1223 | immutableZkEVMTestnet: { 1224 | chainId: 13473, 1225 | url: vars.get( 1226 | "IMMUTABLEZKEVM_TESTNET_URL", 1227 | "https://rpc.testnet.immutable.com", 1228 | ), 1229 | accounts, 1230 | ledgerAccounts, 1231 | }, 1232 | immutableZkEVMMain: { 1233 | chainId: 13371, 1234 | url: vars.get("IMMUTABLEZKEVM_MAINNET_URL", "https://rpc.immutable.com"), 1235 | accounts, 1236 | ledgerAccounts, 1237 | }, 1238 | abstractTestnet: { 1239 | chainId: 11124, 1240 | url: vars.get("ABSTRACT_TESTNET_URL", "https://api.testnet.abs.xyz"), 1241 | accounts, 1242 | ledgerAccounts, 1243 | }, 1244 | abstractMain: { 1245 | chainId: 2741, 1246 | url: vars.get("ABSTRACT_MAINNET_URL", "https://api.mainnet.abs.xyz"), 1247 | accounts, 1248 | ledgerAccounts, 1249 | }, 1250 | hyperevmTestnet: { 1251 | chainId: 998, 1252 | url: vars.get( 1253 | "HYPEREVM_TESTNET_URL", 1254 | "https://rpc.hyperliquid-testnet.xyz/evm", 1255 | ), 1256 | accounts, 1257 | ledgerAccounts, 1258 | }, 1259 | hyperevmMain: { 1260 | chainId: 999, 1261 | url: vars.get("HYPEREVM_MAINNET_URL", "https://rpc.hyperliquid.xyz/evm"), 1262 | accounts, 1263 | ledgerAccounts, 1264 | }, 1265 | kaiaMain: { 1266 | chainId: 8217, 1267 | url: vars.get("KAIA_MAINNET_URL", "https://rpc.ankr.com/kaia"), 1268 | accounts, 1269 | ledgerAccounts, 1270 | }, 1271 | apeChainTestnet: { 1272 | chainId: 33111, 1273 | url: vars.get( 1274 | "APECHAIN_TESTNET_URL", 1275 | "https://curtis.rpc.caldera.xyz/http", 1276 | ), 1277 | accounts, 1278 | ledgerAccounts, 1279 | }, 1280 | apeChainMain: { 1281 | chainId: 33139, 1282 | url: vars.get( 1283 | "APECHAIN_MAINNET_URL", 1284 | "https://apechain.calderachain.xyz/http", 1285 | ), 1286 | accounts, 1287 | ledgerAccounts, 1288 | }, 1289 | }, 1290 | xdeploy: { 1291 | contract: "BatchDistributor", 1292 | constructorArgsPath: "", 1293 | salt: vars.get( 1294 | "SALT", 1295 | // `keccak256("SALT")` 1296 | "0x087ee6a43229fddc3e140062b42bcff0c6d1c5a3bba8123976a59688e7024c25", 1297 | ), 1298 | signer: accounts[0], 1299 | networks: ["sepolia", "hoodi"], 1300 | rpcUrls: [ 1301 | vars.get("ETH_SEPOLIA_TESTNET_URL", "https://rpc.sepolia.org"), 1302 | vars.get("ETH_HOODI_TESTNET_URL", "https://0xrpc.io/hoodi"), 1303 | ], 1304 | gasLimit: 1.6 * 10 ** 6, 1305 | }, 1306 | contractSizer: { 1307 | alphaSort: true, 1308 | runOnCompile: true, 1309 | disambiguatePaths: false, 1310 | strict: true, 1311 | only: [], 1312 | except: ["CreateX", "Create2DeployerLocal"], 1313 | }, 1314 | gasReporter: { 1315 | enabled: vars.has("REPORT_GAS") ? true : false, 1316 | currency: "USD", 1317 | }, 1318 | abiExporter: { 1319 | path: "./abis", 1320 | runOnCompile: true, 1321 | clear: true, 1322 | flat: false, 1323 | only: ["BatchDistributor"], 1324 | spacing: 2, 1325 | pretty: true, 1326 | }, 1327 | sourcify: { 1328 | // Enable Sourcify verification by default 1329 | enabled: true, 1330 | apiUrl: "https://sourcify.dev/server", 1331 | browserUrl: "https://repo.sourcify.dev", 1332 | }, 1333 | etherscan: { 1334 | // Add your own API key by getting an account at etherscan (https://etherscan.io), snowtrace (https://snowtrace.io) etc. 1335 | // This is used for verification purposes when you want to `npx hardhat verify` your contract using Hardhat 1336 | // The same API key works usually for both testnet and mainnet 1337 | apiKey: { 1338 | // For Ethereum testnets & mainnet 1339 | mainnet: vars.get("ETHERSCAN_API_KEY", ""), 1340 | goerli: vars.get("ETHERSCAN_API_KEY", ""), 1341 | sepolia: vars.get("ETHERSCAN_API_KEY", ""), 1342 | holesky: vars.get("ETHERSCAN_API_KEY", ""), 1343 | hoodi: vars.get("ETHERSCAN_API_KEY", ""), 1344 | // For BSC testnet & mainnet 1345 | bsc: vars.get("BSC_API_KEY", ""), 1346 | bscTestnet: vars.get("BSC_API_KEY", ""), 1347 | // For Heco mainnet 1348 | heco: vars.get("HECO_API_KEY", ""), 1349 | // For Fantom testnet & mainnet 1350 | opera: vars.get("FANTOM_API_KEY", ""), 1351 | ftmTestnet: vars.get("FANTOM_API_KEY", ""), 1352 | // For Optimism testnets & mainnet 1353 | optimisticEthereum: vars.get("OPTIMISM_API_KEY", ""), 1354 | optimisticGoerli: vars.get("OPTIMISM_API_KEY", ""), 1355 | optimisticSepolia: vars.get("OPTIMISM_API_KEY", ""), 1356 | // For Polygon testnets & mainnets 1357 | polygon: vars.get("POLYGON_API_KEY", ""), 1358 | polygonZkEVM: vars.get("POLYGON_ZKEVM_API_KEY", ""), 1359 | polygonAmoy: vars.get("POLYGON_API_KEY", ""), 1360 | polygonZkEVMTestnet: vars.get("POLYGON_ZKEVM_API_KEY", ""), 1361 | // For Arbitrum testnet & mainnets 1362 | arbitrumOne: vars.get("ARBITRUM_API_KEY", ""), 1363 | arbitrumNova: vars.get("ARBITRUM_API_KEY", ""), 1364 | arbitrumSepolia: vars.get("ARBITRUM_API_KEY", ""), 1365 | // For Avalanche testnet & mainnet 1366 | avalanche: vars.get("AVALANCHE_API_KEY", ""), 1367 | avalancheFujiTestnet: vars.get("AVALANCHE_API_KEY", ""), 1368 | // For Moonbeam testnet & mainnets 1369 | moonbeam: vars.get("MOONBEAM_API_KEY", ""), 1370 | moonriver: vars.get("MOONBEAM_API_KEY", ""), 1371 | moonbaseAlpha: vars.get("MOONBEAM_API_KEY", ""), 1372 | // For Celo testnet & mainnet 1373 | celo: vars.get("CELO_API_KEY", ""), 1374 | alfajores: vars.get("CELO_API_KEY", ""), 1375 | // For Harmony testnet & mainnet 1376 | harmony: vars.get("HARMONY_API_KEY", ""), 1377 | harmonyTestnet: vars.get("HARMONY_API_KEY", ""), 1378 | // For Aurora testnet & mainnet 1379 | aurora: vars.get("AURORA_API_KEY", ""), 1380 | auroraTestnet: vars.get("AURORA_API_KEY", ""), 1381 | // For Cronos testnet & mainnet 1382 | cronos: vars.get("CRONOS_API_KEY", ""), 1383 | cronosTestnet: vars.get("CRONOS_API_KEY", ""), 1384 | // For Gnosis/xDai testnet & mainnets 1385 | gnosis: vars.get("GNOSIS_API_KEY", ""), 1386 | xdai: vars.get("GNOSIS_API_KEY", ""), 1387 | chiado: vars.get("GNOSIS_API_KEY", ""), 1388 | // For Fuse testnet & mainnet 1389 | fuse: vars.get("FUSE_API_KEY", ""), 1390 | spark: vars.get("FUSE_API_KEY", ""), 1391 | // For Evmos testnet & mainnet 1392 | evmos: vars.get("EVMOS_API_KEY", ""), 1393 | evmosTestnet: vars.get("EVMOS_API_KEY", ""), 1394 | // For Boba network testnet & mainnet 1395 | boba: vars.get("BOBA_API_KEY", ""), 1396 | bobaTestnet: vars.get("BOBA_API_KEY", ""), 1397 | // For Canto testnet & mainnet 1398 | canto: vars.get("CANTO_API_KEY", ""), 1399 | cantoTestnet: vars.get("CANTO_API_KEY", ""), 1400 | // For Base testnets & mainnet 1401 | base: vars.get("BASE_API_KEY", ""), 1402 | baseTestnet: vars.get("BASE_API_KEY", ""), 1403 | baseSepolia: vars.get("BASE_API_KEY", ""), 1404 | // For Mantle testnet & mainnet 1405 | mantle: vars.get("MANTLE_API_KEY", ""), 1406 | mantleTestnet: vars.get("MANTLE_API_KEY", ""), 1407 | // For Filecoin testnet & mainnet 1408 | filecoin: vars.get("FILECOIN_API_KEY", ""), 1409 | filecoinTestnet: vars.get("FILECOIN_API_KEY", ""), 1410 | // For Scroll testnet & mainnet 1411 | scroll: vars.get("SCROLL_API_KEY", ""), 1412 | scrollTestnet: vars.get("SCROLL_API_KEY", ""), 1413 | // For Linea testnet & mainnet 1414 | linea: vars.get("LINEA_API_KEY", ""), 1415 | lineaTestnet: vars.get("LINEA_API_KEY", ""), 1416 | // For ShimmerEVM testnet 1417 | shimmerEVMTestnet: vars.get("SHIMMEREVM_API_KEY", ""), 1418 | // For Zora testnet & mainnet 1419 | zora: vars.get("ZORA_API_KEY", ""), 1420 | zoraTestnet: vars.get("ZORA_API_KEY", ""), 1421 | // For Lukso testnet & mainnet 1422 | lukso: vars.get("LUKSO_API_KEY", ""), 1423 | luksoTestnet: vars.get("LUKSO_API_KEY", ""), 1424 | // For Manta testnet & mainnet 1425 | manta: vars.get("MANTA_API_KEY", ""), 1426 | mantaTestnet: vars.get("MANTA_API_KEY", ""), 1427 | // For Arthera testnet 1428 | artheraTestnet: vars.get("ARTHERA_API_KEY", ""), 1429 | // For Endurance testnets & mainnet 1430 | endurance: vars.get("ENDURANCE_API_KEY", ""), 1431 | enduranceTestnet: vars.get("ENDURANCE_API_KEY", ""), 1432 | openduranceTestnet: vars.get("OPENDURANCE_API_KEY", ""), 1433 | // For Blast testnet & mainnet 1434 | blast: vars.get("BLAST_API_KEY", ""), 1435 | blastTestnet: vars.get("BLAST_API_KEY", ""), 1436 | // For Kroma testnet & mainnet 1437 | kroma: vars.get("KROMA_API_KEY", ""), 1438 | kromaTestnet: vars.get("KROMA_API_KEY", ""), 1439 | // For DOS Chain testnet & mainnet 1440 | dos: vars.get("DOS_API_KEY", ""), 1441 | dosTestnet: vars.get("DOS_API_KEY", ""), 1442 | // For Fraxtal testnet & mainnet 1443 | fraxtal: vars.get("FRAXTAL_API_KEY", ""), 1444 | fraxtalTestnet: vars.get("FRAXTAL_API_KEY", ""), 1445 | // For Kava mainnet 1446 | kava: vars.get("KAVA_API_KEY", ""), 1447 | // For Metis testnet & mainnet 1448 | metis: vars.get("METIS_API_KEY", ""), 1449 | metisTestnet: vars.get("METIS_API_KEY", ""), 1450 | // For Mode testnet & mainnet 1451 | mode: vars.get("MODE_API_KEY", ""), 1452 | modeTestnet: vars.get("MODE_API_KEY", ""), 1453 | // For X Layer testnet & mainnet 1454 | xlayer: vars.get("OKLINK_API_KEY", ""), 1455 | xlayerTestnet: vars.get("OKLINK_API_KEY", ""), 1456 | // For BOB testnet & mainnet 1457 | bob: vars.get("BOB_API_KEY", ""), 1458 | bobTestnet: vars.get("BOB_API_KEY", ""), 1459 | // For Core testnet & mainnet 1460 | core: vars.get("CORE_MAINNET_API_KEY", ""), 1461 | coreTestnet: vars.get("CORE_TESTNET_API_KEY", ""), 1462 | // For Telos testnet & mainnet 1463 | telos: vars.get("TELOS_API_KEY", ""), 1464 | telosTestnet: vars.get("TELOS_API_KEY", ""), 1465 | // For Rootstock testnet & mainnet 1466 | rootstock: vars.get("ROOTSTOCK_API_KEY", ""), 1467 | rootstockTestnet: vars.get("ROOTSTOCK_API_KEY", ""), 1468 | // For Chiliz testnet & mainnet 1469 | chiliz: vars.get("CHILIZ_API_KEY", ""), 1470 | chilizTestnet: vars.get("CHILIZ_API_KEY", ""), 1471 | // For Gravity Alpha testnet & mainnet 1472 | gravityAlpha: vars.get("GRAVITY_ALPHA_API_KEY", ""), 1473 | gravityAlphaTestnet: vars.get("GRAVITY_ALPHA_API_KEY", ""), 1474 | // For Taiko testnet & mainnet 1475 | taiko: vars.get("TAIKO_API_KEY", ""), 1476 | taikoTestnet: vars.get("TAIKO_API_KEY", ""), 1477 | // For ZetaChain testnet & mainnet 1478 | zetaChain: vars.get("ZETA_CHAIN_API_KEY", ""), 1479 | zetaChainTestnet: vars.get("ZETA_CHAIN_API_KEY", ""), 1480 | // For 5ireChain testnet & mainnet 1481 | "5ireChain": vars.get("5IRE_CHAIN_API_KEY", ""), 1482 | "5ireChainTestnet": vars.get("5IRE_CHAIN_API_KEY", ""), 1483 | // For Oasis Sapphire testnet & mainnet 1484 | sapphire: vars.get("SAPPHIRE_API_KEY", ""), 1485 | sapphireTestnet: vars.get("SAPPHIRE_API_KEY", ""), 1486 | // For World Chain testnet & mainnet 1487 | worldChain: vars.get("WORLD_CHAIN_API_KEY", ""), 1488 | worldChainTestnet: vars.get("WORLD_CHAIN_API_KEY", ""), 1489 | // For Plume testnet & mainnet 1490 | plume: vars.get("PLUME_API_KEY", ""), 1491 | plumeTestnet: vars.get("PLUME_API_KEY", ""), 1492 | // For Unichain testnet & mainnet 1493 | unichain: vars.get("UNICHAIN_API_KEY", ""), 1494 | unichainTestnet: vars.get("UNICHAIN_API_KEY", ""), 1495 | // For XDC testnet & mainnet 1496 | xdc: vars.get("XDC_API_KEY", ""), 1497 | xdcTestnet: vars.get("XDC_API_KEY", ""), 1498 | // For SX testnet & mainnet 1499 | sx: vars.get("SX_API_KEY", ""), 1500 | sxTestnet: vars.get("SX_API_KEY", ""), 1501 | // For ZKsync testnet & mainnet 1502 | zkSync: vars.get("ZKSYNC_API_KEY", ""), 1503 | zkSyncTestnet: vars.get("ZKSYNC_API_KEY", ""), 1504 | // For Lisk testnet & mainnet 1505 | lisk: vars.get("LISK_API_KEY", ""), 1506 | liskTestnet: vars.get("LISK_API_KEY", ""), 1507 | // For Metal L2 testnet & mainnet 1508 | metalL2: vars.get("METALL2_API_KEY", ""), 1509 | metalL2Testnet: vars.get("METALL2_API_KEY", ""), 1510 | // For Superseed testnet & mainnet 1511 | superseed: vars.get("SUPERSEED_API_KEY", ""), 1512 | superseedTestnet: vars.get("SUPERSEED_API_KEY", ""), 1513 | // For Story testnet 1514 | storyTestnet: vars.get("STORY_API_KEY", ""), 1515 | // For Sonic testnet & mainnet 1516 | sonic: vars.get("SONIC_API_KEY", ""), 1517 | sonicTestnet: vars.get("SONIC_API_KEY", ""), 1518 | // For EVM on Flow testnet & mainnet 1519 | flow: vars.get("FLOW_API_KEY", ""), 1520 | flowTestnet: vars.get("FLOW_API_KEY", ""), 1521 | // For Ink testnet & mainnet 1522 | ink: vars.get("INK_API_KEY", ""), 1523 | inkTestnet: vars.get("INK_API_KEY", ""), 1524 | // For Morph testnet & mainnet 1525 | morph: vars.get("MORPH_API_KEY", ""), 1526 | morphTestnet: vars.get("MORPH_API_KEY", ""), 1527 | // For Shape testnet & mainnet 1528 | shape: vars.get("SHAPE_API_KEY", ""), 1529 | shapeTestnet: vars.get("SHAPE_API_KEY", ""), 1530 | // For Etherlink testnet & mainnet 1531 | etherlink: vars.get("ETHERLINK_API_KEY", ""), 1532 | etherlinkTestnet: vars.get("ETHERLINK_API_KEY", ""), 1533 | // For Soneium testnet & mainnet 1534 | soneium: vars.get("SONEIUM_API_KEY", ""), 1535 | soneiumTestnet: vars.get("SONEIUM_API_KEY", ""), 1536 | // For Swellchain testnet & mainnet 1537 | swell: vars.get("SWELL_API_KEY", ""), 1538 | swellTestnet: vars.get("SWELL_API_KEY", ""), 1539 | // For Hemi testnet & mainnet 1540 | hemi: vars.get("HEMI_API_KEY", ""), 1541 | hemiTestnet: vars.get("HEMI_API_KEY", ""), 1542 | // For Berachain testnet & mainnet 1543 | berachain: vars.get("BERACHAIN_API_KEY", ""), 1544 | berachainTestnet: vars.get("BERACHAIN_API_KEY", ""), 1545 | // For Corn testnet & mainnet 1546 | corn: vars.get("CORN_API_KEY", ""), 1547 | cornTestnet: vars.get("CORN_API_KEY", ""), 1548 | // For Arena-Z testnet & mainnet 1549 | arenaz: vars.get("ARENAZ_API_KEY", ""), 1550 | arenazTestnet: vars.get("ARENAZ_API_KEY", ""), 1551 | // For IoTeX testnet & mainnet 1552 | iotex: vars.get("IOTEX_API_KEY", ""), 1553 | iotexTestnet: vars.get("IOTEX_API_KEY", ""), 1554 | // For HYCHAIN testnet & mainnet 1555 | hychain: vars.get("HYCHAIN_API_KEY", ""), 1556 | hychainTestnet: vars.get("HYCHAIN_API_KEY", ""), 1557 | // For Zircuit testnet & mainnet 1558 | zircuit: vars.get("ZIRCUIT_API_KEY", ""), 1559 | zircuitTestnet: vars.get("ZIRCUIT_API_KEY", ""), 1560 | // For Bitlayer testnet & mainnet 1561 | bitlayer: vars.get("BITLAYER_API_KEY", ""), 1562 | bitlayerTestnet: vars.get("BITLAYER_API_KEY", ""), 1563 | // For Immutable zkEVM testnet & mainnet 1564 | immutableZkEVM: vars.get("IMMUTABLEZKEVM_API_KEY", ""), 1565 | immutableZkEVMTestnet: vars.get("IMMUTABLEZKEVM_API_KEY", ""), 1566 | // For Abstract testnet & mainnet 1567 | abstract: vars.get("ABSTRACT_API_KEY", ""), 1568 | abstractTestnet: vars.get("ABSTRACT_API_KEY", ""), 1569 | // For Kaia mainnet 1570 | kaia: vars.get("OKLINK_API_KEY", ""), 1571 | // For ApeChain testnet & mainnet 1572 | apeChain: vars.get("APECHAIN_API_KEY", ""), 1573 | apeChainTestnet: vars.get("APECHAIN_API_KEY", ""), 1574 | }, 1575 | customChains: [ 1576 | { 1577 | network: "holesky", 1578 | chainId: 17000, 1579 | urls: { 1580 | apiURL: "https://api-holesky.etherscan.io/api", 1581 | browserURL: "https://holesky.etherscan.io", 1582 | }, 1583 | }, 1584 | { 1585 | network: "hoodi", 1586 | chainId: 560048, 1587 | urls: { 1588 | apiURL: "https://api-hoodi.etherscan.io/api", 1589 | browserURL: "https://hoodi.etherscan.io", 1590 | }, 1591 | }, 1592 | { 1593 | network: "optimisticSepolia", 1594 | chainId: 11155420, 1595 | urls: { 1596 | apiURL: "https://api-sepolia-optimistic.etherscan.io/api", 1597 | browserURL: "https://sepolia-optimism.etherscan.io", 1598 | }, 1599 | }, 1600 | { 1601 | network: "chiado", 1602 | chainId: 10200, 1603 | urls: { 1604 | apiURL: "https://gnosis-chiado.blockscout.com/api", 1605 | browserURL: "https://gnosis-chiado.blockscout.com", 1606 | }, 1607 | }, 1608 | { 1609 | network: "celo", 1610 | chainId: 42220, 1611 | urls: { 1612 | apiURL: "https://api.celoscan.io/api", 1613 | browserURL: "https://celoscan.io", 1614 | }, 1615 | }, 1616 | { 1617 | network: "alfajores", 1618 | chainId: 44787, 1619 | urls: { 1620 | apiURL: "https://api-alfajores.celoscan.io/api", 1621 | browserURL: "https://alfajores.celoscan.io", 1622 | }, 1623 | }, 1624 | { 1625 | network: "cronos", 1626 | chainId: 25, 1627 | urls: { 1628 | apiURL: "https://api.cronoscan.com/api", 1629 | browserURL: "https://cronoscan.com", 1630 | }, 1631 | }, 1632 | { 1633 | network: "cronosTestnet", 1634 | chainId: 338, 1635 | urls: { 1636 | apiURL: "https://cronos.org/explorer/testnet3/api", 1637 | browserURL: "https://cronos.org/explorer/testnet3", 1638 | }, 1639 | }, 1640 | { 1641 | network: "fuse", 1642 | chainId: 122, 1643 | urls: { 1644 | apiURL: "https://explorer.fuse.io/api", 1645 | browserURL: "https://explorer.fuse.io", 1646 | }, 1647 | }, 1648 | { 1649 | network: "spark", 1650 | chainId: 123, 1651 | urls: { 1652 | apiURL: "https://explorer.fusespark.io/api", 1653 | browserURL: "https://explorer.fusespark.io", 1654 | }, 1655 | }, 1656 | { 1657 | network: "evmos", 1658 | chainId: 9001, 1659 | urls: { 1660 | apiURL: "https://api.verify.mintscan.io/evm/api/0x2329", 1661 | browserURL: "https://www.mintscan.io/evmos", 1662 | }, 1663 | }, 1664 | { 1665 | network: "evmosTestnet", 1666 | chainId: 9000, 1667 | urls: { 1668 | apiURL: "https://api.verify.mintscan.io/evm/api/0x2328", 1669 | browserURL: "https://www.mintscan.io/evmos-testnet", 1670 | }, 1671 | }, 1672 | { 1673 | network: "boba", 1674 | chainId: 288, 1675 | urls: { 1676 | apiURL: 1677 | "https://api.routescan.io/v2/network/mainnet/evm/288/etherscan", 1678 | browserURL: "https://bobascan.com", 1679 | }, 1680 | }, 1681 | { 1682 | network: "bobaTestnet", 1683 | chainId: 2888, 1684 | urls: { 1685 | apiURL: 1686 | "https://api.routescan.io/v2/network/testnet/evm/2888/etherscan", 1687 | browserURL: "https://testnet.bobascan.com", 1688 | }, 1689 | }, 1690 | { 1691 | network: "arbitrumNova", 1692 | chainId: 42170, 1693 | urls: { 1694 | apiURL: "https://api-nova.arbiscan.io/api", 1695 | browserURL: "https://nova.arbiscan.io", 1696 | }, 1697 | }, 1698 | { 1699 | network: "arbitrumSepolia", 1700 | chainId: 421614, 1701 | urls: { 1702 | apiURL: "https://api-sepolia.arbiscan.io/api", 1703 | browserURL: "https://sepolia.arbiscan.io", 1704 | }, 1705 | }, 1706 | { 1707 | network: "canto", 1708 | chainId: 7700, 1709 | urls: { 1710 | apiURL: "https://tuber.build/api", 1711 | browserURL: "https://tuber.build", 1712 | }, 1713 | }, 1714 | { 1715 | network: "cantoTestnet", 1716 | chainId: 7701, 1717 | urls: { 1718 | apiURL: "https://testnet.tuber.build/api", 1719 | browserURL: "https://testnet.tuber.build", 1720 | }, 1721 | }, 1722 | { 1723 | network: "base", 1724 | chainId: 8453, 1725 | urls: { 1726 | apiURL: "https://api.basescan.org/api", 1727 | browserURL: "https://basescan.org", 1728 | }, 1729 | }, 1730 | { 1731 | network: "baseTestnet", 1732 | chainId: 84531, 1733 | urls: { 1734 | apiURL: "https://api-goerli.basescan.org/api", 1735 | browserURL: "https://goerli.basescan.org", 1736 | }, 1737 | }, 1738 | { 1739 | network: "baseSepolia", 1740 | chainId: 84532, 1741 | urls: { 1742 | apiURL: "https://api-sepolia.basescan.org/api", 1743 | browserURL: "https://sepolia.basescan.org", 1744 | }, 1745 | }, 1746 | { 1747 | network: "mantle", 1748 | chainId: 5000, 1749 | urls: { 1750 | apiURL: "https://api.mantlescan.xyz/api", 1751 | browserURL: "https://mantlescan.xyz", 1752 | }, 1753 | }, 1754 | { 1755 | network: "mantleTestnet", 1756 | chainId: 5003, 1757 | urls: { 1758 | apiURL: "https://api-sepolia.mantlescan.xyz/api", 1759 | browserURL: "https://sepolia.mantlescan.xyz", 1760 | }, 1761 | }, 1762 | { 1763 | network: "filecoin", 1764 | chainId: 314, 1765 | urls: { 1766 | apiURL: "https://filfox.info/api/v1/tools/verifyContract", 1767 | browserURL: "https://filfox.info/en", 1768 | }, 1769 | }, 1770 | { 1771 | network: "filecoinTestnet", 1772 | chainId: 314159, 1773 | urls: { 1774 | apiURL: "https://calibration.filfox.info/api/v1/tools/verifyContract", 1775 | browserURL: "https://calibration.filfox.info/en", 1776 | }, 1777 | }, 1778 | { 1779 | network: "scroll", 1780 | chainId: 534352, 1781 | urls: { 1782 | apiURL: "https://api.scrollscan.com/api", 1783 | browserURL: "https://scrollscan.com", 1784 | }, 1785 | }, 1786 | { 1787 | network: "scrollTestnet", 1788 | chainId: 534351, 1789 | urls: { 1790 | apiURL: "https://api-sepolia.scrollscan.com/api", 1791 | browserURL: "https://sepolia.scrollscan.com", 1792 | }, 1793 | }, 1794 | { 1795 | network: "polygonZkEVM", 1796 | chainId: 1101, 1797 | urls: { 1798 | apiURL: "https://api-zkevm.polygonscan.com/api", 1799 | browserURL: "https://zkevm.polygonscan.com", 1800 | }, 1801 | }, 1802 | { 1803 | network: "polygonAmoy", 1804 | chainId: 80002, 1805 | urls: { 1806 | apiURL: "https://api-amoy.polygonscan.com/api", 1807 | browserURL: "https://amoy.polygonscan.com", 1808 | }, 1809 | }, 1810 | { 1811 | network: "polygonZkEVMTestnet", 1812 | chainId: 2442, 1813 | urls: { 1814 | apiURL: "https://api-cardona-zkevm.polygonscan.com/api", 1815 | browserURL: "https://cardona-zkevm.polygonscan.com", 1816 | }, 1817 | }, 1818 | { 1819 | network: "linea", 1820 | chainId: 59144, 1821 | urls: { 1822 | apiURL: "https://api.lineascan.build/api", 1823 | browserURL: "https://lineascan.build", 1824 | }, 1825 | }, 1826 | { 1827 | network: "lineaTestnet", 1828 | chainId: 59141, 1829 | urls: { 1830 | apiURL: "https://api-sepolia.lineascan.build/api", 1831 | browserURL: "https://sepolia.lineascan.build", 1832 | }, 1833 | }, 1834 | { 1835 | network: "shimmerEVMTestnet", 1836 | chainId: 1071, 1837 | urls: { 1838 | apiURL: "https://explorer.evm.testnet.shimmer.network/api", 1839 | browserURL: "https://explorer.evm.testnet.shimmer.network", 1840 | }, 1841 | }, 1842 | { 1843 | network: "zora", 1844 | chainId: 7777777, 1845 | urls: { 1846 | apiURL: "https://explorer.zora.energy/api", 1847 | browserURL: "https://explorer.zora.energy", 1848 | }, 1849 | }, 1850 | { 1851 | network: "zoraTestnet", 1852 | chainId: 999999999, 1853 | urls: { 1854 | apiURL: "https://sepolia.explorer.zora.energy/api", 1855 | browserURL: "https://sepolia.explorer.zora.energy", 1856 | }, 1857 | }, 1858 | { 1859 | network: "lukso", 1860 | chainId: 42, 1861 | urls: { 1862 | apiURL: "https://explorer.execution.mainnet.lukso.network/api", 1863 | browserURL: "https://explorer.execution.mainnet.lukso.network", 1864 | }, 1865 | }, 1866 | { 1867 | network: "luksoTestnet", 1868 | chainId: 4201, 1869 | urls: { 1870 | apiURL: "https://explorer.execution.testnet.lukso.network/api", 1871 | browserURL: "https://explorer.execution.testnet.lukso.network", 1872 | }, 1873 | }, 1874 | { 1875 | network: "manta", 1876 | chainId: 169, 1877 | urls: { 1878 | apiURL: "https://pacific-explorer.manta.network/api", 1879 | browserURL: "https://pacific-explorer.manta.network", 1880 | }, 1881 | }, 1882 | { 1883 | network: "mantaTestnet", 1884 | chainId: 3441006, 1885 | urls: { 1886 | apiURL: "https://pacific-explorer.sepolia-testnet.manta.network/api", 1887 | browserURL: "https://pacific-explorer.sepolia-testnet.manta.network", 1888 | }, 1889 | }, 1890 | { 1891 | network: "artheraTestnet", 1892 | chainId: 10243, 1893 | urls: { 1894 | apiURL: "https://explorer-test.arthera.net/api", 1895 | browserURL: "https://explorer-test.arthera.net", 1896 | }, 1897 | }, 1898 | { 1899 | network: "endurance", 1900 | chainId: 648, 1901 | urls: { 1902 | apiURL: "https://explorer-endurance.fusionist.io/api", 1903 | browserURL: "https://explorer-endurance.fusionist.io", 1904 | }, 1905 | }, 1906 | { 1907 | network: "enduranceTestnet", 1908 | chainId: 6480, 1909 | urls: { 1910 | apiURL: "https://myexplorertestnet.fusionist.io/api", 1911 | browserURL: "https://myexplorertestnet.fusionist.io", 1912 | }, 1913 | }, 1914 | { 1915 | network: "openduranceTestnet", 1916 | chainId: 6480001001, 1917 | urls: { 1918 | apiURL: "https://explorer-l2-testnet.fusionist.io/api", 1919 | browserURL: "https://explorer-l2-testnet.fusionist.io", 1920 | }, 1921 | }, 1922 | { 1923 | network: "blast", 1924 | chainId: 81457, 1925 | urls: { 1926 | apiURL: "https://api.blastscan.io/api", 1927 | browserURL: "https://blastscan.io", 1928 | }, 1929 | }, 1930 | { 1931 | network: "blastTestnet", 1932 | chainId: 168587773, 1933 | urls: { 1934 | apiURL: "https://api-sepolia.blastscan.io/api", 1935 | browserURL: "https://sepolia.blastscan.io", 1936 | }, 1937 | }, 1938 | { 1939 | network: "kroma", 1940 | chainId: 255, 1941 | urls: { 1942 | apiURL: "https://api.kromascan.com/api", 1943 | browserURL: "https://kromascan.com", 1944 | }, 1945 | }, 1946 | { 1947 | network: "kromaTestnet", 1948 | chainId: 2358, 1949 | urls: { 1950 | apiURL: "https://api-sepolia.kromascan.com", 1951 | browserURL: "https://sepolia.kromascan.com", 1952 | }, 1953 | }, 1954 | { 1955 | network: "dos", 1956 | chainId: 7979, 1957 | urls: { 1958 | apiURL: "https://doscan.io/api", 1959 | browserURL: "https://doscan.io", 1960 | }, 1961 | }, 1962 | { 1963 | network: "dosTestnet", 1964 | chainId: 3939, 1965 | urls: { 1966 | apiURL: "https://test.doscan.io/api", 1967 | browserURL: "https://test.doscan.io", 1968 | }, 1969 | }, 1970 | { 1971 | network: "fraxtal", 1972 | chainId: 252, 1973 | urls: { 1974 | apiURL: "https://api.fraxscan.com/api", 1975 | browserURL: "https://fraxscan.com", 1976 | }, 1977 | }, 1978 | { 1979 | network: "fraxtalTestnet", 1980 | chainId: 2522, 1981 | urls: { 1982 | apiURL: "https://api-holesky.fraxscan.com/api", 1983 | browserURL: "https://holesky.fraxscan.com", 1984 | }, 1985 | }, 1986 | { 1987 | network: "kava", 1988 | chainId: 2222, 1989 | urls: { 1990 | apiURL: "https://kavascan.com/api", 1991 | browserURL: "https://kavascan.com", 1992 | }, 1993 | }, 1994 | { 1995 | network: "metis", 1996 | chainId: 1088, 1997 | urls: { 1998 | apiURL: "https://andromeda-explorer.metis.io/api", 1999 | browserURL: "https://andromeda-explorer.metis.io", 2000 | }, 2001 | }, 2002 | { 2003 | network: "metisTestnet", 2004 | chainId: 59902, 2005 | urls: { 2006 | apiURL: "https://sepolia-explorer.metisdevops.link/api", 2007 | browserURL: "https://sepolia-explorer.metisdevops.link", 2008 | }, 2009 | }, 2010 | { 2011 | network: "mode", 2012 | chainId: 34443, 2013 | urls: { 2014 | apiURL: "https://explorer.mode.network/api", 2015 | browserURL: "https://explorer.mode.network", 2016 | }, 2017 | }, 2018 | { 2019 | network: "modeTestnet", 2020 | chainId: 919, 2021 | urls: { 2022 | apiURL: "https://sepolia.explorer.mode.network/api", 2023 | browserURL: "https://sepolia.explorer.mode.network", 2024 | }, 2025 | }, 2026 | { 2027 | network: "xlayer", 2028 | chainId: 196, 2029 | urls: { 2030 | apiURL: 2031 | "https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/XLAYER", 2032 | browserURL: "https://www.oklink.com/x-layer", 2033 | }, 2034 | }, 2035 | { 2036 | network: "xlayerTestnet", 2037 | chainId: 195, 2038 | urls: { 2039 | apiURL: 2040 | "https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/XLAYER_TESTNET", 2041 | browserURL: "https://www.oklink.com/x-layer-testnet", 2042 | }, 2043 | }, 2044 | { 2045 | network: "bob", 2046 | chainId: 60808, 2047 | urls: { 2048 | apiURL: "https://explorer.gobob.xyz/api", 2049 | browserURL: "https://explorer.gobob.xyz", 2050 | }, 2051 | }, 2052 | { 2053 | network: "bobTestnet", 2054 | chainId: 111, 2055 | urls: { 2056 | apiURL: "https://testnet-explorer.gobob.xyz/api", 2057 | browserURL: "https://testnet-explorer.gobob.xyz", 2058 | }, 2059 | }, 2060 | { 2061 | network: "core", 2062 | chainId: 1116, 2063 | urls: { 2064 | apiURL: "https://openapi.coredao.org/api", 2065 | browserURL: "https://scan.coredao.org", 2066 | }, 2067 | }, 2068 | { 2069 | network: "coreTestnet", 2070 | chainId: 1115, 2071 | urls: { 2072 | apiURL: "https://api.test.btcs.network/api", 2073 | browserURL: "https://scan.test.btcs.network", 2074 | }, 2075 | }, 2076 | { 2077 | network: "telos", 2078 | chainId: 40, 2079 | urls: { 2080 | apiURL: "https://api.teloscan.io/api", 2081 | browserURL: "https://www.teloscan.io", 2082 | }, 2083 | }, 2084 | { 2085 | network: "telosTestnet", 2086 | chainId: 41, 2087 | urls: { 2088 | apiURL: "https://api.testnet.teloscan.io/api", 2089 | browserURL: "https://testnet.teloscan.io", 2090 | }, 2091 | }, 2092 | { 2093 | network: "rootstock", 2094 | chainId: 30, 2095 | urls: { 2096 | apiURL: "https://rootstock.blockscout.com/api", 2097 | browserURL: "https://rootstock.blockscout.com", 2098 | }, 2099 | }, 2100 | { 2101 | network: "rootstockTestnet", 2102 | chainId: 31, 2103 | urls: { 2104 | apiURL: "https://rootstock-testnet.blockscout.com/api", 2105 | browserURL: "https://rootstock-testnet.blockscout.com", 2106 | }, 2107 | }, 2108 | { 2109 | network: "chiliz", 2110 | chainId: 88888, 2111 | urls: { 2112 | apiURL: 2113 | "https://api.routescan.io/v2/network/mainnet/evm/88888/etherscan/api", 2114 | browserURL: "https://chiliscan.com", 2115 | }, 2116 | }, 2117 | { 2118 | network: "chilizTestnet", 2119 | chainId: 88882, 2120 | urls: { 2121 | apiURL: 2122 | "https://api.routescan.io/v2/network/testnet/evm/88882/etherscan/api", 2123 | browserURL: "https://testnet.chiliscan.com", 2124 | }, 2125 | }, 2126 | { 2127 | network: "harmony", 2128 | chainId: 1666600000, 2129 | urls: { 2130 | apiURL: "https://explorer.harmony.one/api", 2131 | browserURL: "https://explorer.harmony.one", 2132 | }, 2133 | }, 2134 | { 2135 | network: "harmonyTestnet", 2136 | chainId: 1666700000, 2137 | urls: { 2138 | apiURL: "https://explorer.testnet.harmony.one/api", 2139 | browserURL: "https://explorer.testnet.harmony.one", 2140 | }, 2141 | }, 2142 | { 2143 | network: "gravityAlpha", 2144 | chainId: 1625, 2145 | urls: { 2146 | apiURL: "https://explorer.gravity.xyz/api", 2147 | browserURL: "https://explorer.gravity.xyz", 2148 | }, 2149 | }, 2150 | { 2151 | network: "gravityAlphaTestnet", 2152 | chainId: 13505, 2153 | urls: { 2154 | apiURL: "https://explorer-sepolia.gravity.xyz/api", 2155 | browserURL: "https://explorer-sepolia.gravity.xyz", 2156 | }, 2157 | }, 2158 | { 2159 | network: "taiko", 2160 | chainId: 167000, 2161 | urls: { 2162 | apiURL: "https://api.taikoscan.io/api", 2163 | browserURL: "https://taikoscan.io", 2164 | }, 2165 | }, 2166 | { 2167 | network: "taikoTestnet", 2168 | chainId: 167009, 2169 | urls: { 2170 | apiURL: "https://api-hekla.taikoscan.io/api", 2171 | browserURL: "https://hekla.taikoscan.io", 2172 | }, 2173 | }, 2174 | { 2175 | network: "zetaChain", 2176 | chainId: 7000, 2177 | urls: { 2178 | apiURL: "https://zetachain.blockscout.com/api", 2179 | browserURL: "https://zetachain.blockscout.com", 2180 | }, 2181 | }, 2182 | { 2183 | network: "zetaChainTestnet", 2184 | chainId: 7001, 2185 | urls: { 2186 | apiURL: "https://zetachain-athens-3.blockscout.com/api", 2187 | browserURL: "https://zetachain-athens-3.blockscout.com", 2188 | }, 2189 | }, 2190 | { 2191 | network: "5ireChain", 2192 | chainId: 995, 2193 | urls: { 2194 | apiURL: "https://5irescan.io/api", 2195 | browserURL: "https://5irescan.io", 2196 | }, 2197 | }, 2198 | { 2199 | network: "5ireChainTestnet", 2200 | chainId: 997, 2201 | urls: { 2202 | apiURL: "https://testnet.5irescan.io/api", 2203 | browserURL: "https://testnet.5irescan.io", 2204 | }, 2205 | }, 2206 | { 2207 | network: "sapphire", 2208 | chainId: 23294, 2209 | urls: { 2210 | apiURL: "https://explorer.oasis.io/mainnet/sapphire/api", 2211 | browserURL: "https://explorer.oasis.io/mainnet/sapphire", 2212 | }, 2213 | }, 2214 | { 2215 | network: "sapphireTestnet", 2216 | chainId: 23295, 2217 | urls: { 2218 | apiURL: "https://explorer.oasis.io/testnet/sapphire/api", 2219 | browserURL: "https://explorer.oasis.io/testnet/sapphire", 2220 | }, 2221 | }, 2222 | { 2223 | network: "worldChain", 2224 | chainId: 480, 2225 | urls: { 2226 | apiURL: "https://worldchain-mainnet.explorer.alchemy.com/api", 2227 | browserURL: "https://worldchain-mainnet.explorer.alchemy.com", 2228 | }, 2229 | }, 2230 | { 2231 | network: "worldChainTestnet", 2232 | chainId: 4801, 2233 | urls: { 2234 | apiURL: "https://worldchain-sepolia.explorer.alchemy.com/api", 2235 | browserURL: "https://worldchain-sepolia.explorer.alchemy.com", 2236 | }, 2237 | }, 2238 | { 2239 | network: "plume", 2240 | chainId: 98866, 2241 | urls: { 2242 | apiURL: "https://explorer.plume.org/api", 2243 | browserURL: "https://explorer.plume.org", 2244 | }, 2245 | }, 2246 | { 2247 | network: "plumeTestnet", 2248 | chainId: 98867, 2249 | urls: { 2250 | apiURL: "https://testnet-explorer.plume.org/api", 2251 | browserURL: "https://testnet-explorer.plume.org", 2252 | }, 2253 | }, 2254 | { 2255 | network: "unichain", 2256 | chainId: 130, 2257 | urls: { 2258 | apiURL: "https://api.uniscan.xyz/api", 2259 | browserURL: "https://uniscan.xyz", 2260 | }, 2261 | }, 2262 | { 2263 | network: "unichainTestnet", 2264 | chainId: 1301, 2265 | urls: { 2266 | apiURL: "https://api-sepolia.uniscan.xyz/api", 2267 | browserURL: "https://sepolia.uniscan.xyz", 2268 | }, 2269 | }, 2270 | { 2271 | network: "xdc", 2272 | chainId: 50, 2273 | urls: { 2274 | apiURL: "https://api.xdcscan.com/api", 2275 | browserURL: "https://xdcscan.com", 2276 | }, 2277 | }, 2278 | { 2279 | network: "xdcTestnet", 2280 | chainId: 51, 2281 | urls: { 2282 | apiURL: "https://api-testnet.xdcscan.com/api", 2283 | browserURL: "https://testnet.xdcscan.com", 2284 | }, 2285 | }, 2286 | { 2287 | network: "sx", 2288 | chainId: 4162, 2289 | urls: { 2290 | apiURL: "https://explorerl2.sx.technology/api", 2291 | browserURL: "https://explorerl2.sx.technology", 2292 | }, 2293 | }, 2294 | { 2295 | network: "sxTestnet", 2296 | chainId: 79479957, 2297 | urls: { 2298 | apiURL: "https://explorerl2.toronto.sx.technology/api", 2299 | browserURL: "https://explorerl2.toronto.sx.technology", 2300 | }, 2301 | }, 2302 | { 2303 | network: "zkSync", 2304 | chainId: 324, 2305 | urls: { 2306 | apiURL: "https://api-era.zksync.network/api", 2307 | browserURL: "https://era.zksync.network", 2308 | }, 2309 | }, 2310 | { 2311 | network: "zkSyncTestnet", 2312 | chainId: 300, 2313 | urls: { 2314 | apiURL: "https://api-sepolia-era.zksync.network/api", 2315 | browserURL: "https://sepolia-era.zksync.network", 2316 | }, 2317 | }, 2318 | { 2319 | network: "lisk", 2320 | chainId: 1135, 2321 | urls: { 2322 | apiURL: "https://blockscout.lisk.com/api", 2323 | browserURL: "https://blockscout.lisk.com", 2324 | }, 2325 | }, 2326 | { 2327 | network: "liskTestnet", 2328 | chainId: 4202, 2329 | urls: { 2330 | apiURL: "https://sepolia-blockscout.lisk.com/api", 2331 | browserURL: "https://sepolia-blockscout.lisk.com", 2332 | }, 2333 | }, 2334 | { 2335 | network: "metalL2", 2336 | chainId: 1750, 2337 | urls: { 2338 | apiURL: "https://explorer.metall2.com/api", 2339 | browserURL: "https://explorer.metall2.com", 2340 | }, 2341 | }, 2342 | { 2343 | network: "metalL2Testnet", 2344 | chainId: 1740, 2345 | urls: { 2346 | apiURL: "https://testnet.explorer.metall2.com/api", 2347 | browserURL: "https://testnet.explorer.metall2.com", 2348 | }, 2349 | }, 2350 | { 2351 | network: "superseed", 2352 | chainId: 5330, 2353 | urls: { 2354 | apiURL: "https://explorer.superseed.xyz/api", 2355 | browserURL: "https://explorer.superseed.xyz", 2356 | }, 2357 | }, 2358 | { 2359 | network: "superseedTestnet", 2360 | chainId: 53302, 2361 | urls: { 2362 | apiURL: "https://sepolia-explorer.superseed.xyz/api", 2363 | browserURL: "https://sepolia-explorer.superseed.xyz", 2364 | }, 2365 | }, 2366 | { 2367 | network: "storyTestnet", 2368 | chainId: 1315, 2369 | urls: { 2370 | apiURL: "https://aeneid.storyscan.io/api", 2371 | browserURL: "https://aeneid.storyscan.io", 2372 | }, 2373 | }, 2374 | { 2375 | network: "sonic", 2376 | chainId: 146, 2377 | urls: { 2378 | apiURL: "https://api.sonicscan.org/api", 2379 | browserURL: "https://sonicscan.org", 2380 | }, 2381 | }, 2382 | { 2383 | network: "sonicTestnet", 2384 | chainId: 57054, 2385 | urls: { 2386 | apiURL: "https://api-testnet.sonicscan.org/api", 2387 | browserURL: "https://testnet.sonicscan.org", 2388 | }, 2389 | }, 2390 | { 2391 | network: "flow", 2392 | chainId: 747, 2393 | urls: { 2394 | apiURL: "https://evm.flowscan.io/api", 2395 | browserURL: "https://evm.flowscan.io", 2396 | }, 2397 | }, 2398 | { 2399 | network: "flowTestnet", 2400 | chainId: 545, 2401 | urls: { 2402 | apiURL: "https://evm-testnet.flowscan.io/api", 2403 | browserURL: "https://evm-testnet.flowscan.io", 2404 | }, 2405 | }, 2406 | { 2407 | network: "ink", 2408 | chainId: 57073, 2409 | urls: { 2410 | apiURL: "https://explorer.inkonchain.com/api", 2411 | browserURL: "https://explorer.inkonchain.com", 2412 | }, 2413 | }, 2414 | { 2415 | network: "inkTestnet", 2416 | chainId: 763373, 2417 | urls: { 2418 | apiURL: "https://explorer-sepolia.inkonchain.com/api", 2419 | browserURL: "https://explorer-sepolia.inkonchain.com", 2420 | }, 2421 | }, 2422 | { 2423 | network: "morph", 2424 | chainId: 2818, 2425 | urls: { 2426 | apiURL: "https://explorer.morphl2.io/api", 2427 | browserURL: "https://explorer.morphl2.io", 2428 | }, 2429 | }, 2430 | { 2431 | network: "morphTestnet", 2432 | chainId: 2810, 2433 | urls: { 2434 | apiURL: "https://explorer-holesky.morphl2.io/api", 2435 | browserURL: "https://explorer-holesky.morphl2.io", 2436 | }, 2437 | }, 2438 | { 2439 | network: "shape", 2440 | chainId: 360, 2441 | urls: { 2442 | apiURL: "https://shapescan.xyz/api", 2443 | browserURL: "https://shapescan.xyz", 2444 | }, 2445 | }, 2446 | { 2447 | network: "shapeTestnet", 2448 | chainId: 11011, 2449 | urls: { 2450 | apiURL: "https://sepolia.shapescan.xyz/api", 2451 | browserURL: "https://sepolia.shapescan.xyz", 2452 | }, 2453 | }, 2454 | { 2455 | network: "etherlink", 2456 | chainId: 42793, 2457 | urls: { 2458 | apiURL: "https://explorer.etherlink.com/api", 2459 | browserURL: "https://explorer.etherlink.com", 2460 | }, 2461 | }, 2462 | { 2463 | network: "etherlinkTestnet", 2464 | chainId: 128123, 2465 | urls: { 2466 | apiURL: "https://testnet.explorer.etherlink.com/api", 2467 | browserURL: "https://testnet.explorer.etherlink.com", 2468 | }, 2469 | }, 2470 | { 2471 | network: "soneium", 2472 | chainId: 1868, 2473 | urls: { 2474 | apiURL: "https://soneium.blockscout.com/api", 2475 | browserURL: "https://soneium.blockscout.com", 2476 | }, 2477 | }, 2478 | { 2479 | network: "soneiumTestnet", 2480 | chainId: 1946, 2481 | urls: { 2482 | apiURL: "https://soneium-minato.blockscout.com/api", 2483 | browserURL: "https://soneium-minato.blockscout.com", 2484 | }, 2485 | }, 2486 | { 2487 | network: "swell", 2488 | chainId: 1923, 2489 | urls: { 2490 | apiURL: "https://explorer.swellnetwork.io/api", 2491 | browserURL: "https://explorer.swellnetwork.io", 2492 | }, 2493 | }, 2494 | { 2495 | network: "swellTestnet", 2496 | chainId: 1924, 2497 | urls: { 2498 | apiURL: "https://swell-testnet-explorer.alt.technology/api", 2499 | browserURL: "https://swell-testnet-explorer.alt.technology", 2500 | }, 2501 | }, 2502 | { 2503 | network: "hemi", 2504 | chainId: 43111, 2505 | urls: { 2506 | apiURL: "https://explorer.hemi.xyz/api", 2507 | browserURL: "https://explorer.hemi.xyz", 2508 | }, 2509 | }, 2510 | { 2511 | network: "hemiTestnet", 2512 | chainId: 743111, 2513 | urls: { 2514 | apiURL: "https://testnet.explorer.hemi.xyz/api", 2515 | browserURL: "https://testnet.explorer.hemi.xyz", 2516 | }, 2517 | }, 2518 | { 2519 | network: "berachain", 2520 | chainId: 80094, 2521 | urls: { 2522 | apiURL: "https://api.berascan.com/api", 2523 | browserURL: "https://berascan.com", 2524 | }, 2525 | }, 2526 | { 2527 | network: "berachainTestnet", 2528 | chainId: 80084, 2529 | urls: { 2530 | apiURL: 2531 | "https://api.routescan.io/v2/network/testnet/evm/80084/etherscan", 2532 | browserURL: "https://bartio.beratrail.io", 2533 | }, 2534 | }, 2535 | { 2536 | network: "corn", 2537 | chainId: 21000000, 2538 | urls: { 2539 | apiURL: 2540 | "https://api.routescan.io/v2/network/mainnet/evm/21000000/etherscan", 2541 | browserURL: "https://cornscan.io", 2542 | }, 2543 | }, 2544 | { 2545 | network: "cornTestnet", 2546 | chainId: 21000001, 2547 | urls: { 2548 | apiURL: 2549 | "https://api.routescan.io/v2/network/testnet/evm/21000001/etherscan", 2550 | browserURL: "https://testnet.cornscan.io", 2551 | }, 2552 | }, 2553 | { 2554 | network: "arenaz", 2555 | chainId: 7897, 2556 | urls: { 2557 | apiURL: "https://explorer.arena-z.gg/api", 2558 | browserURL: "https://explorer.arena-z.gg", 2559 | }, 2560 | }, 2561 | { 2562 | network: "arenazTestnet", 2563 | chainId: 9897, 2564 | urls: { 2565 | apiURL: "https://arena-z.blockscout.com/api", 2566 | browserURL: "https://arena-z.blockscout.com", 2567 | }, 2568 | }, 2569 | { 2570 | network: "iotex", 2571 | chainId: 4689, 2572 | urls: { 2573 | apiURL: "https://iotexscout.io/api", 2574 | browserURL: "https://iotexscan.io", 2575 | }, 2576 | }, 2577 | { 2578 | network: "iotexTestnet", 2579 | chainId: 4690, 2580 | urls: { 2581 | apiURL: "https://testnet.iotexscan.io/api", 2582 | browserURL: "https://testnet.iotexscan.io", 2583 | }, 2584 | }, 2585 | { 2586 | network: "hychain", 2587 | chainId: 2911, 2588 | urls: { 2589 | apiURL: "https://explorer.hychain.com/api", 2590 | browserURL: "https://explorer.hychain.com", 2591 | }, 2592 | }, 2593 | { 2594 | network: "hychainTestnet", 2595 | chainId: 29112, 2596 | urls: { 2597 | apiURL: "https://testnet.explorer.hychain.com/api", 2598 | browserURL: "https://testnet.explorer.hychain.com", 2599 | }, 2600 | }, 2601 | { 2602 | network: "zircuit", 2603 | chainId: 48900, 2604 | urls: { 2605 | apiURL: "https://explorer.zircuit.com/api/contractVerifyHardhat", 2606 | browserURL: "https://explorer.zircuit.com", 2607 | }, 2608 | }, 2609 | { 2610 | network: "zircuitTestnet", 2611 | chainId: 48898, 2612 | urls: { 2613 | apiURL: 2614 | "https://explorer.garfield-testnet.zircuit.com/api/contractVerifyHardhat", 2615 | browserURL: "https://explorer.garfield-testnet.zircuit.com", 2616 | }, 2617 | }, 2618 | { 2619 | network: "bitlayer", 2620 | chainId: 200901, 2621 | urls: { 2622 | apiURL: "https://api.btrscan.com/scan/api", 2623 | browserURL: "https://www.btrscan.com", 2624 | }, 2625 | }, 2626 | { 2627 | network: "bitlayerTestnet", 2628 | chainId: 200810, 2629 | urls: { 2630 | apiURL: "https://api-testnet.btrscan.com/scan/api", 2631 | browserURL: "https://testnet.btrscan.com", 2632 | }, 2633 | }, 2634 | { 2635 | network: "immutableZkEVM", 2636 | chainId: 13371, 2637 | urls: { 2638 | apiURL: "https://explorer.immutable.com/api", 2639 | browserURL: "https://explorer.immutable.com", 2640 | }, 2641 | }, 2642 | { 2643 | network: "immutableZkEVMTestnet", 2644 | chainId: 13473, 2645 | urls: { 2646 | apiURL: "https://explorer.testnet.immutable.com/api", 2647 | browserURL: "https://explorer.testnet.immutable.com", 2648 | }, 2649 | }, 2650 | { 2651 | network: "abstract", 2652 | chainId: 2741, 2653 | urls: { 2654 | apiURL: "https://api.abscan.org/api", 2655 | browserURL: "https://abscan.org", 2656 | }, 2657 | }, 2658 | { 2659 | network: "abstractTestnet", 2660 | chainId: 11124, 2661 | urls: { 2662 | apiURL: "https://api-sepolia.abscan.org/api", 2663 | browserURL: "https://sepolia.abscan.org", 2664 | }, 2665 | }, 2666 | { 2667 | network: "kaia", 2668 | chainId: 8217, 2669 | urls: { 2670 | apiURL: 2671 | "https://www.oklink.com/api/v5/explorer/contract/verify-source-code-plugin/KAIA", 2672 | browserURL: "https://www.oklink.com/kaia", 2673 | }, 2674 | }, 2675 | { 2676 | network: "apeChain", 2677 | chainId: 33139, 2678 | urls: { 2679 | apiURL: "https://api.apescan.io/api", 2680 | browserURL: "https://apescan.io", 2681 | }, 2682 | }, 2683 | { 2684 | network: "apeChainTestnet", 2685 | chainId: 33111, 2686 | urls: { 2687 | apiURL: "https://api-curtis.apescan.io/api", 2688 | browserURL: "https://curtis.apescan.io", 2689 | }, 2690 | }, 2691 | ], 2692 | }, 2693 | // tenderly: { 2694 | // username: "pcaversaccio", 2695 | // project: "project", 2696 | // forkNetwork: "", 2697 | // privateVerification: false, 2698 | // deploymentsDir: "deployments_tenderly", 2699 | // }, 2700 | }; 2701 | 2702 | export default config; 2703 | --------------------------------------------------------------------------------