├── .gitignore ├── README.md ├── contract.json ├── package-lock.json ├── package.json ├── src ├── contracts │ ├── Distributor.sol │ └── abi.json ├── core │ ├── abi │ │ └── abi.json │ ├── balance.js │ ├── bridge.js │ ├── deploy.js │ └── distribute.js ├── scripts │ ├── clean.js │ └── compile.js └── utils │ ├── config.js │ ├── helpers.js │ └── logger.js └── start.js /.gitignore: -------------------------------------------------------------------------------- 1 | # 私钥和地址文件 2 | pk.txt 3 | address.txt 4 | 5 | # Node.js 6 | node_modules/ 7 | package-lock.json 8 | 9 | # 编译输出 10 | contract.json 11 | 12 | # 环境文件 13 | .env 14 | 15 | # 系统文件 16 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ETH 工具 2 | 3 | 一个用于测试网ETH 跨链和批量分发的工具。 4 | 5 | ## 功能 6 | 7 | - 购买测试币:从 Arbitrum 跨链到 Sepolia 8 | - 重试购买:重试失败的测试币购买记录 9 | - 批量分发:将 ETH 批量分发给多个地址 10 | - 重试分发:重试失败的分发记录 11 | - 查询余额:查询所有钱包的余额 12 | - 数据清理:清理所有数据文件 13 | 14 | ## 使用说明 15 | 16 | ### 1. 安装依赖 17 | 18 | ```bash 19 | npm install 20 | ``` 21 | 22 | ### 2. 配置文件 23 | 24 | 在 `data` 目录下: 25 | - `pk.txt`: 存放用于发送的钱包私钥,每行一个 26 | - `address.txt`: 存放接收资金的钱包地址,每行一个 27 | 28 | ### 3. 运行程序 29 | 30 | ```bash 31 | node start.js 32 | ``` 33 | 34 | ## 注意事项 35 | 36 | 1. 如果跨链失败,请查看该地址跨链交易是否在Arb区块浏览器中存在并失败,如果出现这种问题,说明预留的layerZeroFee不足,需要检查最新的layerZeroFee,直接点击 https://arbiscan.io/address/0xfca99f4b5186d4bfbdbd2c542dca2eca4906ba45 查看最新成功的跨链交易中的layerZeroFee,然后修改 `src/utils/config.js` 中的 `layerZeroFee` 为最新值。 37 | 38 | ## 错误处理 39 | 40 | - 交易失败会自动保存到对应的失败记录文件,包括私钥和地址 41 | - 可以使用重试功能处理失败的记录,重试成功后会自动删除失败记录文件,包括私钥和地址 42 | - 查看 logs 目录下的日志文件获取详细错误信息 43 | 44 | ## 配置说明 45 | 46 | 在 `src/utils/config.js` 中可以配置: 47 | 48 | - RPC 节点地址 49 | - Gas 限制 50 | - 默认金额 51 | - 文件路径 52 | - 跨链参数 -------------------------------------------------------------------------------- /contract.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [], 5 | "stateMutability": "nonpayable", 6 | "type": "constructor" 7 | }, 8 | { 9 | "anonymous": false, 10 | "inputs": [ 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "from", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "address[]", 20 | "name": "to", 21 | "type": "address[]" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "uint256[]", 26 | "name": "amounts", 27 | "type": "uint256[]" 28 | } 29 | ], 30 | "name": "Distribution", 31 | "type": "event" 32 | }, 33 | { 34 | "anonymous": false, 35 | "inputs": [ 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "from", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "amount", 46 | "type": "uint256" 47 | } 48 | ], 49 | "name": "EthReceived", 50 | "type": "event" 51 | }, 52 | { 53 | "inputs": [ 54 | { 55 | "internalType": "address[]", 56 | "name": "_recipients", 57 | "type": "address[]" 58 | }, 59 | { 60 | "internalType": "uint256[]", 61 | "name": "_amounts", 62 | "type": "uint256[]" 63 | } 64 | ], 65 | "name": "distributeEth", 66 | "outputs": [], 67 | "stateMutability": "payable", 68 | "type": "function" 69 | }, 70 | { 71 | "inputs": [], 72 | "name": "emergencyWithdraw", 73 | "outputs": [], 74 | "stateMutability": "nonpayable", 75 | "type": "function" 76 | }, 77 | { 78 | "inputs": [], 79 | "name": "getBalance", 80 | "outputs": [ 81 | { 82 | "internalType": "uint256", 83 | "name": "", 84 | "type": "uint256" 85 | } 86 | ], 87 | "stateMutability": "view", 88 | "type": "function" 89 | }, 90 | { 91 | "inputs": [], 92 | "name": "owner", 93 | "outputs": [ 94 | { 95 | "internalType": "address", 96 | "name": "", 97 | "type": "address" 98 | } 99 | ], 100 | "stateMutability": "view", 101 | "type": "function" 102 | }, 103 | { 104 | "stateMutability": "payable", 105 | "type": "receive" 106 | } 107 | ], 108 | "bytecode": "6080604052348015600e575f5ffd5b50335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610f5a8061005b5f395ff3fe608060405260043610610042575f3560e01c806312065fe01461009b578063401bd3fe146100c55780638da5cb5b146100e1578063db2e21bc1461010b57610097565b36610097573373ffffffffffffffffffffffffffffffffffffffff167f85177f287940f2f05425a4029951af0e047a7f9c4eaa9a6e6917bcd869f866953460405161008d91906106d6565b60405180910390a2005b5f5ffd5b3480156100a6575f5ffd5b506100af610121565b6040516100bc91906106d6565b60405180910390f35b6100df60048036038101906100da91906107ad565b610128565b005b3480156100ec575f5ffd5b506100f56104fa565b604051610102919061086a565b60405180910390f35b348015610116575f5ffd5b5061011f61051e565b005b5f47905090565b818190508484905014610170576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161016790610903565b60405180910390fd5b5f84849050116101b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101ac9061096b565b60405180910390fd5b5f5f90505f5f90505b838390508110156101fd578383828181106101dc576101db610989565b5b90506020020135826101ee91906109e3565b915080806001019150506101be565b5080341015610241576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023890610a60565b60405180910390fd5b5f5f90505b858590508110156103dc575f73ffffffffffffffffffffffffffffffffffffffff1686868381811061027b5761027a610989565b5b90506020020160208101906102909190610aa8565b73ffffffffffffffffffffffffffffffffffffffff16036102e6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102dd90610b1d565b60405180910390fd5b5f8686838181106102fa576102f9610989565b5b905060200201602081019061030f9190610aa8565b73ffffffffffffffffffffffffffffffffffffffff1685858481811061033857610337610989565b5b9050602002013560405161034b90610b68565b5f6040518083038185875af1925050503d805f8114610385576040519150601f19603f3d011682016040523d82523d5f602084013e61038a565b606091505b50509050806103ce576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103c590610bc6565b60405180910390fd5b508080600101915050610246565b505f81346103ea9190610be4565b90505f81111561049e575f3373ffffffffffffffffffffffffffffffffffffffff168260405161041990610b68565b5f6040518083038185875af1925050503d805f8114610453576040519150601f19603f3d011682016040523d82523d5f602084013e610458565b606091505b505090508061049c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161049390610c61565b60405180910390fd5b505b3373ffffffffffffffffffffffffffffffffffffffff167fea1823f879bc0cdf8f0b498e336723ace2adf2f7ea9c0a0257b0ba3e5783a956878787876040516104ea9493929190610db3565b60405180910390a2505050505050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105a390610e36565b60405180910390fd5b5f4790505f81116105f2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105e990610e9e565b60405180910390fd5b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168260405161063790610b68565b5f6040518083038185875af1925050503d805f8114610671576040519150601f19603f3d011682016040523d82523d5f602084013e610676565b606091505b50509050806106ba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106b190610f06565b60405180910390fd5b5050565b5f819050919050565b6106d0816106be565b82525050565b5f6020820190506106e95f8301846106c7565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610718576107176106f7565b5b8235905067ffffffffffffffff811115610735576107346106fb565b5b602083019150836020820283011115610751576107506106ff565b5b9250929050565b5f5f83601f84011261076d5761076c6106f7565b5b8235905067ffffffffffffffff81111561078a576107896106fb565b5b6020830191508360208202830111156107a6576107a56106ff565b5b9250929050565b5f5f5f5f604085870312156107c5576107c46106ef565b5b5f85013567ffffffffffffffff8111156107e2576107e16106f3565b5b6107ee87828801610703565b9450945050602085013567ffffffffffffffff811115610811576108106106f3565b5b61081d87828801610758565b925092505092959194509250565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6108548261082b565b9050919050565b6108648161084a565b82525050565b5f60208201905061087d5f83018461085b565b92915050565b5f82825260208201905092915050565b7f526563697069656e747320616e6420616d6f756e7473206c656e677468206d695f8201527f736d617463680000000000000000000000000000000000000000000000000000602082015250565b5f6108ed602683610883565b91506108f882610893565b604082019050919050565b5f6020820190508181035f83015261091a816108e1565b9050919050565b7f456d70747920726563697069656e7473206172726179000000000000000000005f82015250565b5f610955601683610883565b915061096082610921565b602082019050919050565b5f6020820190508181035f83015261098281610949565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6109ed826106be565b91506109f8836106be565b9250828201905080821115610a1057610a0f6109b6565b5b92915050565b7f496e73756666696369656e74204554482073656e7400000000000000000000005f82015250565b5f610a4a601583610883565b9150610a5582610a16565b602082019050919050565b5f6020820190508181035f830152610a7781610a3e565b9050919050565b610a878161084a565b8114610a91575f5ffd5b50565b5f81359050610aa281610a7e565b92915050565b5f60208284031215610abd57610abc6106ef565b5b5f610aca84828501610a94565b91505092915050565b7f496e76616c696420726563697069656e742061646472657373000000000000005f82015250565b5f610b07601983610883565b9150610b1282610ad3565b602082019050919050565b5f6020820190508181035f830152610b3481610afb565b9050919050565b5f81905092915050565b50565b5f610b535f83610b3b565b9150610b5e82610b45565b5f82019050919050565b5f610b7282610b48565b9150819050919050565b7f5472616e73666572206661696c656400000000000000000000000000000000005f82015250565b5f610bb0600f83610883565b9150610bbb82610b7c565b602082019050919050565b5f6020820190508181035f830152610bdd81610ba4565b9050919050565b5f610bee826106be565b9150610bf9836106be565b9250828203905081811115610c1157610c106109b6565b5b92915050565b7f526566756e64206661696c6564000000000000000000000000000000000000005f82015250565b5f610c4b600d83610883565b9150610c5682610c17565b602082019050919050565b5f6020820190508181035f830152610c7881610c3f565b9050919050565b5f82825260208201905092915050565b5f819050919050565b610ca18161084a565b82525050565b5f610cb28383610c98565b60208301905092915050565b5f610ccc6020840184610a94565b905092915050565b5f602082019050919050565b5f610ceb8385610c7f565b9350610cf682610c8f565b805f5b85811015610d2e57610d0b8284610cbe565b610d158882610ca7565b9750610d2083610cd4565b925050600181019050610cf9565b5085925050509392505050565b5f82825260208201905092915050565b5f5ffd5b82818337505050565b5f610d638385610d3b565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115610d9657610d95610d4b565b5b602083029250610da7838584610d4f565b82840190509392505050565b5f6040820190508181035f830152610dcc818688610ce0565b90508181036020830152610de1818486610d58565b905095945050505050565b7f4f6e6c79206f776e6572000000000000000000000000000000000000000000005f82015250565b5f610e20600a83610883565b9150610e2b82610dec565b602082019050919050565b5f6020820190508181035f830152610e4d81610e14565b9050919050565b7f4e6f2062616c616e636520746f207769746864726177000000000000000000005f82015250565b5f610e88601683610883565b9150610e9382610e54565b602082019050919050565b5f6020820190508181035f830152610eb581610e7c565b9050919050565b7f5769746864726177616c206661696c65640000000000000000000000000000005f82015250565b5f610ef0601183610883565b9150610efb82610ebc565b602082019050919050565b5f6020820190508181035f830152610f1d81610ee4565b905091905056fea264697066735822122089407a97a09d990d9322fd97f01a13283101810e2724b7f4d1a8dc758f94a0ff64736f6c634300081c0033" 109 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-tools", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "eth-tools", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "chalk": "^4.1.2", 12 | "ethers": "^6.0.0", 13 | "solc": "^0.8.0" 14 | } 15 | }, 16 | "node_modules/@adraffy/ens-normalize": { 17 | "version": "1.10.1", 18 | "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", 19 | "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", 20 | "license": "MIT" 21 | }, 22 | "node_modules/@noble/curves": { 23 | "version": "1.2.0", 24 | "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", 25 | "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", 26 | "license": "MIT", 27 | "dependencies": { 28 | "@noble/hashes": "1.3.2" 29 | }, 30 | "funding": { 31 | "url": "https://paulmillr.com/funding/" 32 | } 33 | }, 34 | "node_modules/@noble/hashes": { 35 | "version": "1.3.2", 36 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", 37 | "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", 38 | "license": "MIT", 39 | "engines": { 40 | "node": ">= 16" 41 | }, 42 | "funding": { 43 | "url": "https://paulmillr.com/funding/" 44 | } 45 | }, 46 | "node_modules/@types/node": { 47 | "version": "22.7.5", 48 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", 49 | "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", 50 | "license": "MIT", 51 | "dependencies": { 52 | "undici-types": "~6.19.2" 53 | } 54 | }, 55 | "node_modules/aes-js": { 56 | "version": "4.0.0-beta.5", 57 | "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", 58 | "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", 59 | "license": "MIT" 60 | }, 61 | "node_modules/ansi-styles": { 62 | "version": "4.3.0", 63 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 64 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 65 | "license": "MIT", 66 | "dependencies": { 67 | "color-convert": "^2.0.1" 68 | }, 69 | "engines": { 70 | "node": ">=8" 71 | }, 72 | "funding": { 73 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 74 | } 75 | }, 76 | "node_modules/chalk": { 77 | "version": "4.1.2", 78 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 79 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 80 | "license": "MIT", 81 | "dependencies": { 82 | "ansi-styles": "^4.1.0", 83 | "supports-color": "^7.1.0" 84 | }, 85 | "engines": { 86 | "node": ">=10" 87 | }, 88 | "funding": { 89 | "url": "https://github.com/chalk/chalk?sponsor=1" 90 | } 91 | }, 92 | "node_modules/color-convert": { 93 | "version": "2.0.1", 94 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 95 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 96 | "license": "MIT", 97 | "dependencies": { 98 | "color-name": "~1.1.4" 99 | }, 100 | "engines": { 101 | "node": ">=7.0.0" 102 | } 103 | }, 104 | "node_modules/color-name": { 105 | "version": "1.1.4", 106 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 107 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 108 | "license": "MIT" 109 | }, 110 | "node_modules/command-exists": { 111 | "version": "1.2.9", 112 | "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", 113 | "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", 114 | "license": "MIT" 115 | }, 116 | "node_modules/commander": { 117 | "version": "8.3.0", 118 | "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", 119 | "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", 120 | "license": "MIT", 121 | "engines": { 122 | "node": ">= 12" 123 | } 124 | }, 125 | "node_modules/ethers": { 126 | "version": "6.13.4", 127 | "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", 128 | "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", 129 | "funding": [ 130 | { 131 | "type": "individual", 132 | "url": "https://github.com/sponsors/ethers-io/" 133 | }, 134 | { 135 | "type": "individual", 136 | "url": "https://www.buymeacoffee.com/ricmoo" 137 | } 138 | ], 139 | "license": "MIT", 140 | "dependencies": { 141 | "@adraffy/ens-normalize": "1.10.1", 142 | "@noble/curves": "1.2.0", 143 | "@noble/hashes": "1.3.2", 144 | "@types/node": "22.7.5", 145 | "aes-js": "4.0.0-beta.5", 146 | "tslib": "2.7.0", 147 | "ws": "8.17.1" 148 | }, 149 | "engines": { 150 | "node": ">=14.0.0" 151 | } 152 | }, 153 | "node_modules/follow-redirects": { 154 | "version": "1.15.9", 155 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 156 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 157 | "funding": [ 158 | { 159 | "type": "individual", 160 | "url": "https://github.com/sponsors/RubenVerborgh" 161 | } 162 | ], 163 | "license": "MIT", 164 | "engines": { 165 | "node": ">=4.0" 166 | }, 167 | "peerDependenciesMeta": { 168 | "debug": { 169 | "optional": true 170 | } 171 | } 172 | }, 173 | "node_modules/has-flag": { 174 | "version": "4.0.0", 175 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 176 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 177 | "license": "MIT", 178 | "engines": { 179 | "node": ">=8" 180 | } 181 | }, 182 | "node_modules/js-sha3": { 183 | "version": "0.8.0", 184 | "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", 185 | "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", 186 | "license": "MIT" 187 | }, 188 | "node_modules/memorystream": { 189 | "version": "0.3.1", 190 | "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", 191 | "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", 192 | "engines": { 193 | "node": ">= 0.10.0" 194 | } 195 | }, 196 | "node_modules/os-tmpdir": { 197 | "version": "1.0.2", 198 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 199 | "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", 200 | "license": "MIT", 201 | "engines": { 202 | "node": ">=0.10.0" 203 | } 204 | }, 205 | "node_modules/semver": { 206 | "version": "5.7.2", 207 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", 208 | "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", 209 | "license": "ISC", 210 | "bin": { 211 | "semver": "bin/semver" 212 | } 213 | }, 214 | "node_modules/solc": { 215 | "version": "0.8.28", 216 | "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.28.tgz", 217 | "integrity": "sha512-AFCiJ+b4RosyyNhnfdVH4ZR1+TxiL91iluPjw0EJslIu4LXGM9NYqi2z5y8TqochC4tcH9QsHfwWhOIC9jPDKA==", 218 | "license": "MIT", 219 | "dependencies": { 220 | "command-exists": "^1.2.8", 221 | "commander": "^8.1.0", 222 | "follow-redirects": "^1.12.1", 223 | "js-sha3": "0.8.0", 224 | "memorystream": "^0.3.1", 225 | "semver": "^5.5.0", 226 | "tmp": "0.0.33" 227 | }, 228 | "bin": { 229 | "solcjs": "solc.js" 230 | }, 231 | "engines": { 232 | "node": ">=10.0.0" 233 | } 234 | }, 235 | "node_modules/supports-color": { 236 | "version": "7.2.0", 237 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 238 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 239 | "license": "MIT", 240 | "dependencies": { 241 | "has-flag": "^4.0.0" 242 | }, 243 | "engines": { 244 | "node": ">=8" 245 | } 246 | }, 247 | "node_modules/tmp": { 248 | "version": "0.0.33", 249 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 250 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 251 | "license": "MIT", 252 | "dependencies": { 253 | "os-tmpdir": "~1.0.2" 254 | }, 255 | "engines": { 256 | "node": ">=0.6.0" 257 | } 258 | }, 259 | "node_modules/tslib": { 260 | "version": "2.7.0", 261 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", 262 | "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", 263 | "license": "0BSD" 264 | }, 265 | "node_modules/undici-types": { 266 | "version": "6.19.8", 267 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 268 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 269 | "license": "MIT" 270 | }, 271 | "node_modules/ws": { 272 | "version": "8.17.1", 273 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", 274 | "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", 275 | "license": "MIT", 276 | "engines": { 277 | "node": ">=10.0.0" 278 | }, 279 | "peerDependencies": { 280 | "bufferutil": "^4.0.1", 281 | "utf-8-validate": ">=5.0.2" 282 | }, 283 | "peerDependenciesMeta": { 284 | "bufferutil": { 285 | "optional": true 286 | }, 287 | "utf-8-validate": { 288 | "optional": true 289 | } 290 | } 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-tools", 3 | "version": "1.0.0", 4 | "description": "ETH工具集", 5 | "main": "start.js", 6 | "scripts": { 7 | "start": "node start.js", 8 | "compile": "node src/scripts/compile.js", 9 | "clean": "node src/scripts/clean.js" 10 | }, 11 | "dependencies": { 12 | "ethers": "^6.0.0", 13 | "solc": "^0.8.0", 14 | "chalk": "^4.1.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/contracts/Distributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract Distributor { 5 | address public owner; 6 | 7 | event Distribution(address indexed from, address[] to, uint256[] amounts); 8 | event EthReceived(address indexed from, uint256 amount); 9 | 10 | constructor() { 11 | owner = msg.sender; 12 | } 13 | 14 | // 接收ETH的回调函数 15 | receive() external payable { 16 | emit EthReceived(msg.sender, msg.value); 17 | } 18 | 19 | // 批量分发ETH 20 | function distributeEth(address[] calldata _recipients, uint256[] calldata _amounts) external payable { 21 | require(_recipients.length == _amounts.length, "Recipients and amounts length mismatch"); 22 | require(_recipients.length > 0, "Empty recipients array"); 23 | 24 | uint256 totalAmount = 0; 25 | for(uint256 i = 0; i < _amounts.length; i++) { 26 | totalAmount += _amounts[i]; 27 | } 28 | 29 | require(msg.value >= totalAmount, "Insufficient ETH sent"); 30 | 31 | for(uint256 i = 0; i < _recipients.length; i++) { 32 | require(_recipients[i] != address(0), "Invalid recipient address"); 33 | (bool success, ) = _recipients[i].call{value: _amounts[i]}(""); 34 | require(success, "Transfer failed"); 35 | } 36 | 37 | // 如果发送的ETH超过需要分发的金额,将剩余的退回 38 | uint256 remaining = msg.value - totalAmount; 39 | if (remaining > 0) { 40 | (bool success, ) = msg.sender.call{value: remaining}(""); 41 | require(success, "Refund failed"); 42 | } 43 | 44 | emit Distribution(msg.sender, _recipients, _amounts); 45 | } 46 | 47 | // 查询合约ETH余额 48 | function getBalance() external view returns (uint256) { 49 | return address(this).balance; 50 | } 51 | 52 | // 紧急提款函数(仅限owner) 53 | function emergencyWithdraw() external { 54 | require(msg.sender == owner, "Only owner"); 55 | uint256 balance = address(this).balance; 56 | require(balance > 0, "No balance to withdraw"); 57 | 58 | (bool success, ) = owner.call{value: balance}(""); 59 | require(success, "Withdrawal failed"); 60 | } 61 | } -------------------------------------------------------------------------------- /src/contracts/abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "anonymous": false, 9 | "inputs": [ 10 | { 11 | "indexed": true, 12 | "internalType": "address", 13 | "name": "from", 14 | "type": "address" 15 | }, 16 | { 17 | "indexed": false, 18 | "internalType": "address[]", 19 | "name": "to", 20 | "type": "address[]" 21 | }, 22 | { 23 | "indexed": false, 24 | "internalType": "uint256[]", 25 | "name": "amounts", 26 | "type": "uint256[]" 27 | } 28 | ], 29 | "name": "Distribution", 30 | "type": "event" 31 | }, 32 | { 33 | "anonymous": false, 34 | "inputs": [ 35 | { 36 | "indexed": true, 37 | "internalType": "address", 38 | "name": "from", 39 | "type": "address" 40 | }, 41 | { 42 | "indexed": false, 43 | "internalType": "uint256", 44 | "name": "amount", 45 | "type": "uint256" 46 | } 47 | ], 48 | "name": "EthReceived", 49 | "type": "event" 50 | }, 51 | { 52 | "inputs": [ 53 | { 54 | "internalType": "address[]", 55 | "name": "_recipients", 56 | "type": "address[]" 57 | }, 58 | { 59 | "internalType": "uint256[]", 60 | "name": "_amounts", 61 | "type": "uint256[]" 62 | } 63 | ], 64 | "name": "distributeEth", 65 | "outputs": [], 66 | "stateMutability": "payable", 67 | "type": "function" 68 | }, 69 | { 70 | "inputs": [], 71 | "name": "emergencyWithdraw", 72 | "outputs": [], 73 | "stateMutability": "nonpayable", 74 | "type": "function" 75 | }, 76 | { 77 | "inputs": [], 78 | "name": "getBalance", 79 | "outputs": [ 80 | { 81 | "internalType": "uint256", 82 | "name": "", 83 | "type": "uint256" 84 | } 85 | ], 86 | "stateMutability": "view", 87 | "type": "function" 88 | }, 89 | { 90 | "inputs": [], 91 | "name": "owner", 92 | "outputs": [ 93 | { 94 | "internalType": "address", 95 | "name": "", 96 | "type": "address" 97 | } 98 | ], 99 | "stateMutability": "view", 100 | "type": "function" 101 | }, 102 | { 103 | "stateMutability": "payable", 104 | "type": "receive" 105 | } 106 | ] -------------------------------------------------------------------------------- /src/core/abi/abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_weth", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "address", 11 | "name": "_oft", 12 | "type": "address" 13 | }, 14 | { 15 | "internalType": "address", 16 | "name": "_universalRouter", 17 | "type": "address" 18 | } 19 | ], 20 | "stateMutability": "nonpayable", 21 | "type": "constructor" 22 | }, 23 | { 24 | "inputs": [], 25 | "name": "oft", 26 | "outputs": [ 27 | { 28 | "internalType": "contract IOFTCore", 29 | "name": "", 30 | "type": "address" 31 | } 32 | ], 33 | "stateMutability": "view", 34 | "type": "function" 35 | }, 36 | { 37 | "inputs": [], 38 | "name": "poolFee", 39 | "outputs": [ 40 | { 41 | "internalType": "uint24", 42 | "name": "", 43 | "type": "uint24" 44 | } 45 | ], 46 | "stateMutability": "view", 47 | "type": "function" 48 | }, 49 | { 50 | "inputs": [ 51 | { 52 | "internalType": "uint256", 53 | "name": "amountIn", 54 | "type": "uint256" 55 | }, 56 | { 57 | "internalType": "uint256", 58 | "name": "amountOutMin", 59 | "type": "uint256" 60 | }, 61 | { 62 | "internalType": "uint16", 63 | "name": "dstChainId", 64 | "type": "uint16" 65 | }, 66 | { 67 | "internalType": "address", 68 | "name": "to", 69 | "type": "address" 70 | }, 71 | { 72 | "internalType": "address payable", 73 | "name": "refundAddress", 74 | "type": "address" 75 | }, 76 | { 77 | "internalType": "address", 78 | "name": "zroPaymentAddress", 79 | "type": "address" 80 | }, 81 | { 82 | "internalType": "bytes", 83 | "name": "adapterParams", 84 | "type": "bytes" 85 | } 86 | ], 87 | "name": "swapAndBridge", 88 | "outputs": [], 89 | "stateMutability": "payable", 90 | "type": "function" 91 | }, 92 | { 93 | "inputs": [], 94 | "name": "universalRouter", 95 | "outputs": [ 96 | { 97 | "internalType": "contract IUniversalRouter", 98 | "name": "", 99 | "type": "address" 100 | } 101 | ], 102 | "stateMutability": "view", 103 | "type": "function" 104 | }, 105 | { 106 | "inputs": [], 107 | "name": "weth", 108 | "outputs": [ 109 | { 110 | "internalType": "address", 111 | "name": "", 112 | "type": "address" 113 | } 114 | ], 115 | "stateMutability": "view", 116 | "type": "function" 117 | } 118 | ] -------------------------------------------------------------------------------- /src/core/balance.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('ethers'); 2 | const { readFileLines } = require('../utils/helpers'); 3 | const { networks, paths } = require('../utils/config'); 4 | const Logger = require('../utils/logger'); 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | async function checkBalance(address, provider) { 9 | try { 10 | const balance = await provider.getBalance(address); 11 | return { 12 | address, 13 | balance: ethers.formatEther(balance), 14 | raw: balance 15 | }; 16 | } catch (error) { 17 | Logger.error(`查询失败: ${error.message}`); 18 | Logger.error(`地址: ${Logger.address(address)}`); 19 | return { 20 | address, 21 | balance: '查询失败', 22 | error: error.message 23 | }; 24 | } 25 | } 26 | 27 | function saveZeroBalanceAddresses(addresses) { 28 | try { 29 | const filePath = path.join(paths.data, 'zero_balance_addresses.txt'); 30 | fs.writeFileSync(filePath, addresses.join('\n') + '\n'); 31 | Logger.success(`已将余额为0的地址保存到: zero_balance_addresses.txt`); 32 | Logger.info(`文件路径: ${filePath}`); 33 | } catch (error) { 34 | Logger.error('保存余额为0的地址失败'); 35 | Logger.error(`错误信息: ${error.message}`); 36 | } 37 | } 38 | 39 | async function checkAllBalances() { 40 | try { 41 | // 连接到网络 42 | const provider = new ethers.JsonRpcProvider(networks.sepolia.rpc); 43 | 44 | // 读取私钥文件中的地址 45 | const privateKeys = readFileLines(path.join(paths.data, 'pk.txt')); 46 | const pkAddresses = privateKeys.map(pk => { 47 | const wallet = new ethers.Wallet(pk); 48 | return wallet.address; 49 | }); 50 | 51 | // 读取地址文件中的地址 52 | const addresses = readFileLines(path.join(paths.data, 'address.txt')); 53 | 54 | // 合并所有地址并去重 55 | const allAddresses = [...new Set([...pkAddresses, ...addresses])]; 56 | 57 | Logger.title('开始查询钱包余额'); 58 | Logger.info(`总共 ${allAddresses.length} 个地址`); 59 | Logger.divider(); 60 | 61 | // 查询所有地址余额 62 | Logger.processing('正在查询余额...'); 63 | const results = await Promise.all(allAddresses.map(addr => checkBalance(addr, provider))); 64 | 65 | // 计算总余额 66 | let totalBalance = 0n; 67 | let validResults = results.filter(r => !r.error); 68 | validResults.forEach(r => totalBalance += ethers.parseEther(r.balance)); 69 | 70 | // 找出余额为0的地址 71 | const zeroBalanceAddresses = validResults 72 | .filter(r => r.balance === '0.0') 73 | .map(r => r.address); 74 | 75 | // 输出结果 76 | Logger.title('余额查询结果'); 77 | results.forEach(({ address, balance, error }) => { 78 | if (error) { 79 | Logger.error(`查询失败: ${error}`); 80 | Logger.error(`地址: ${Logger.address(address)}`); 81 | } else { 82 | Logger.info(`${Logger.address(address)}: ${Logger.amount(balance)}`); 83 | } 84 | }); 85 | 86 | Logger.title('汇总信息'); 87 | Logger.divider(); 88 | Logger.info(`总钱包数量: ${allAddresses.length}`); 89 | Logger.info(`成功查询数量: ${validResults.length}`); 90 | Logger.info(`余额为0的地址数量: ${zeroBalanceAddresses.length}`); 91 | 92 | // 如果有余额为0的地址,保存到文件 93 | if (zeroBalanceAddresses.length > 0) { 94 | Logger.divider(); 95 | Logger.processing('正在保存余额为0的地址...'); 96 | saveZeroBalanceAddresses(zeroBalanceAddresses); 97 | } 98 | 99 | return results; 100 | } catch (error) { 101 | Logger.error('查询失败'); 102 | Logger.error(`错误信息: ${error.message}`); 103 | throw error; 104 | } 105 | } 106 | 107 | // 如果直接运行此脚本 108 | if (require.main === module) { 109 | checkAllBalances() 110 | .then(() => process.exit(0)) 111 | .catch(error => { 112 | Logger.error(`程序执行出错: ${error.message}`); 113 | process.exit(1); 114 | }); 115 | } 116 | 117 | module.exports = { checkAllBalances }; -------------------------------------------------------------------------------- /src/core/bridge.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('ethers'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const { networks, paths, bridge } = require('../utils/config'); 5 | const { readFileLines } = require('../utils/helpers'); 6 | const Logger = require('../utils/logger'); 7 | 8 | function saveFailedBridge(privateKey, error) { 9 | try { 10 | // 确保logs目录存在 11 | if (!fs.existsSync(paths.logs)) { 12 | fs.mkdirSync(paths.logs, { recursive: true }); 13 | } 14 | 15 | const failedKeysPath = path.join(paths.logs, 'failed_bridge_keys.txt'); 16 | const failedLogsPath = path.join(paths.logs, 'failed_bridge_logs.txt'); 17 | 18 | // 保存失败的私钥 19 | fs.appendFileSync(failedKeysPath, privateKey + '\n'); 20 | 21 | // 保存失败日志 22 | const logEntry = `时间: ${new Date().toISOString()}\n` + 23 | `钱包: ${new ethers.Wallet(privateKey).address}\n` + 24 | `错误: ${error}\n` + 25 | '----------------------------------------\n'; 26 | fs.appendFileSync(failedLogsPath, logEntry); 27 | 28 | Logger.info('失败记录已保存'); 29 | } catch (error) { 30 | Logger.error(`保存失败记录时出错: ${error.message}`); 31 | } 32 | } 33 | 34 | async function swapAndBridge(privateKey, ethAmount) { 35 | let wallet; 36 | try { 37 | // 读取完整的 ABI 38 | const abi = JSON.parse(fs.readFileSync(path.join(__dirname, 'abi/abi.json'), 'utf8')); 39 | 40 | // 设置provider和wallet 41 | const provider = new ethers.JsonRpcProvider(networks.arbitrum.rpc); 42 | wallet = new ethers.Wallet(privateKey.trim(), provider); 43 | 44 | const contractAddress = '0xfca99f4b5186d4bfbdbd2c542dca2eca4906ba45'; 45 | 46 | // 创建合约实例 47 | const contract = new ethers.Contract( 48 | contractAddress, 49 | abi, 50 | wallet 51 | ); 52 | 53 | const address = wallet.address; 54 | Logger.title('处理钱包'); 55 | Logger.info(`地址: ${Logger.address(address)}`); 56 | 57 | // 将ETH金额转换为Wei 58 | const amountInWei = ethers.parseEther(ethAmount.toString()); 59 | const layerZeroFeeWei = ethers.parseEther(bridge.layerZeroFee); 60 | 61 | // 使用BigInt进行计算 62 | const totalAmount = amountInWei + layerZeroFeeWei; 63 | 64 | // 获取当前 gas 价格 65 | const feeData = await provider.getFeeData(); 66 | const gasPrice = feeData.gasPrice * 2n; 67 | const gasLimit = 5000000n; 68 | Logger.processing('发送交易...'); 69 | 70 | // 发送交易 71 | const tx = await contract.swapAndBridge( 72 | amountInWei, 73 | '0x0', 74 | 161n, 75 | wallet.address, 76 | wallet.address, 77 | '0x0000000000000000000000000000000000000000', 78 | '0x', 79 | { 80 | gasPrice: gasPrice, 81 | gasLimit: gasLimit, 82 | value: totalAmount 83 | } 84 | ); 85 | 86 | // 等待交易确认 87 | const receipt = await tx.wait(); 88 | Logger.success(`交易已确认,区块号: ${receipt.blockNumber},哈希: ${Logger.hash(tx.hash)}`); 89 | 90 | return { 91 | success: true, 92 | hash: tx.hash, 93 | blockNumber: receipt.blockNumber, 94 | address: address 95 | }; 96 | } catch (error) { 97 | Logger.error(`交易失败: ${error.message}`); 98 | Logger.error(`钱包地址: ${Logger.address(wallet?.address)}`); 99 | saveFailedBridge(privateKey, error.message); 100 | return { 101 | success: false, 102 | error: error.message, 103 | details: error, 104 | address: wallet?.address 105 | }; 106 | } 107 | } 108 | 109 | async function retryFailedBridge(ethAmount) { 110 | try { 111 | const failedKeysPath = path.join(paths.logs, 'failed_bridge_keys.txt'); 112 | 113 | // 检查是否存在失败记录 114 | if (!fs.existsSync(failedKeysPath)) { 115 | Logger.warning('没有找到失败记录'); 116 | return; 117 | } 118 | 119 | // 读取失败的私钥 120 | const failedKeys = readFileLines(failedKeysPath); 121 | if (failedKeys.length === 0) { 122 | Logger.info('没有需要重试的交易'); 123 | return; 124 | } 125 | 126 | Logger.title('开始重试失败的购买任务'); 127 | Logger.info(`找到 ${failedKeys.length} 个失败记录`); 128 | Logger.info(`每个钱包将购买 ${Logger.amount(ethAmount)} ETH`); 129 | Logger.divider(); 130 | 131 | for (const privateKey of failedKeys) { 132 | const result = await swapAndBridge(privateKey, ethAmount); 133 | if (result.success) { 134 | Logger.success(`钱包 ${Logger.address(result.address)} 重试成功`); 135 | } else { 136 | Logger.error(`钱包 ${Logger.address(result.address)} 重试失败: ${result.error}`); 137 | } 138 | 139 | // 添加随机延迟 140 | if (privateKey !== failedKeys[failedKeys.length - 1]) { 141 | const delay = Math.floor(Math.random() * 30000) + 1000; 142 | Logger.processing(`等待 ${Math.floor(delay/1000)} 秒后继续...`); 143 | await new Promise(resolve => setTimeout(resolve, delay)); 144 | } 145 | } 146 | 147 | // 清理已处理的失败记录 148 | fs.unlinkSync(failedKeysPath); 149 | Logger.success('重试任务完成'); 150 | } catch (error) { 151 | Logger.error(`重试任务失败: ${error.message}`); 152 | } 153 | } 154 | 155 | async function processAllWallets(ethAmount) { 156 | try { 157 | // 读取所有私钥 158 | const privateKeys = readFileLines(path.join(paths.data, 'pk.txt')); 159 | 160 | Logger.title('开始处理跨链任务'); 161 | Logger.info(`找到 ${privateKeys.length} 个钱包`); 162 | Logger.info(`每个钱包将跨链 ${Logger.amount(ethAmount)}`); 163 | Logger.divider(); 164 | 165 | // 依次处理每个钱包 166 | for (const privateKey of privateKeys) { 167 | try { 168 | const result = await swapAndBridge(privateKey, ethAmount); 169 | if (result.success) { 170 | } else { 171 | Logger.error(`钱包 ${Logger.address(result.address)} 交易失败: ${result.error}`); 172 | } 173 | 174 | // 添加随机延迟 175 | const delay = Math.floor(Math.random() * 30000) + 1000; 176 | Logger.processing(`等待 ${Math.floor(delay/1000)} 秒后继续...`); 177 | await new Promise(resolve => setTimeout(resolve, delay)); 178 | } catch (error) { 179 | Logger.error(`处理钱包时发生错误: ${error.message}`); 180 | } 181 | } 182 | } catch (error) { 183 | Logger.error(`读取私钥文件失败: ${error.message}`); 184 | } 185 | } 186 | 187 | async function main() { 188 | try { 189 | Logger.title('ETH 跨链任务'); 190 | // 使用环境变量中的金额,如果没有则使用置文件中的默认值 191 | const amount = Number(process.env.BRIDGE_AMOUNT) || bridge.amount; 192 | 193 | if (process.argv.includes('retry')) { 194 | await retryFailedBridge(amount); 195 | } else { 196 | Logger.info(`每个钱包将跨链 ${Logger.amount(amount)}`); 197 | Logger.divider(); 198 | await processAllWallets(amount); 199 | Logger.success('所有钱包处理完成'); 200 | } 201 | } catch (error) { 202 | Logger.error(`程序运行出错: ${error.message}`); 203 | } 204 | } 205 | 206 | // 如果直接运行此脚本 207 | if (require.main === module) { 208 | main().catch(error => Logger.error(`程序执行出错: ${error.message}`)); 209 | } 210 | 211 | module.exports = { 212 | swapAndBridge, 213 | processAllWallets, 214 | retryFailedBridge 215 | }; 216 | -------------------------------------------------------------------------------- /src/core/deploy.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('ethers'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const { networks, paths } = require('../utils/config'); 5 | const Logger = require('../utils/logger'); 6 | 7 | async function deployContract() { 8 | try { 9 | Logger.title('开始部署合约'); 10 | // 读取编译后的合约数据 11 | const contractPath = path.join(paths.contracts, 'contract.json'); 12 | const contractData = JSON.parse(fs.readFileSync(contractPath, 'utf8')); 13 | 14 | // 从文件读取部署用的私钥 15 | const deployerPrivateKey = fs.readFileSync(path.join(paths.data, 'deployer-pk.txt'), 'utf8').trim(); 16 | 17 | // 连接到Sepolia网络 18 | const provider = new ethers.JsonRpcProvider(networks.sepolia.rpc); 19 | const wallet = new ethers.Wallet(deployerPrivateKey, provider); 20 | 21 | Logger.info(`部署钱包: ${Logger.address(wallet.address)}`); 22 | 23 | // 获取钱包余额 24 | const balance = await provider.getBalance(wallet.address); 25 | Logger.info(`钱包余额: ${Logger.amount(ethers.formatEther(balance))}`); 26 | Logger.divider(); 27 | 28 | // 创建合约工厂 29 | const factory = new ethers.ContractFactory( 30 | contractData.abi, 31 | contractData.bytecode, 32 | wallet 33 | ); 34 | 35 | // 部署合约 36 | Logger.processing('正在部署合约...'); 37 | const contract = await factory.deploy(); 38 | Logger.processing(`合约创建交易已发送,Hash: ${Logger.hash(contract.deploymentTransaction().hash)}`); 39 | await contract.waitForDeployment(); 40 | 41 | const contractAddress = await contract.getAddress(); 42 | Logger.success(`合约已部署成功`); 43 | Logger.info(`合约地址: ${Logger.address(contractAddress)}`); 44 | Logger.info(`区块号: ${contract.deploymentTransaction().blockNumber}`); 45 | 46 | // 保存合约地址到文件 47 | fs.writeFileSync(path.join(paths.contracts, 'contract-address.txt'), contractAddress); 48 | Logger.success('合约地址已保存到 contract-address.txt'); 49 | 50 | } catch (error) { 51 | Logger.error('部署失败'); 52 | Logger.error(`错误信息: ${error.message}`); 53 | } 54 | } 55 | 56 | // 如果直接运行此脚本 57 | if (require.main === module) { 58 | deployContract() 59 | .then(() => { 60 | Logger.divider(); 61 | Logger.success('部署任务完成'); 62 | }) 63 | .catch(error => Logger.error(error.message)); 64 | } 65 | 66 | module.exports = { deployContract }; -------------------------------------------------------------------------------- /src/core/distribute.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('ethers'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const { networks, paths, transaction } = require('../utils/config'); 5 | const { readFileLines } = require('../utils/helpers'); 6 | const { exec } = require('child_process'); 7 | const Logger = require('../utils/logger'); 8 | 9 | // 参数 10 | const amountPerAddress = process.env.DISTRIBUTION_AMOUNT || 0.2; 11 | const addressGroupSize = parseInt(process.env.GROUP_SIZE) || 4; 12 | 13 | function getAddressGroups() { 14 | const addresses = readFileLines(path.join(paths.data, 'address.txt')); 15 | 16 | // 钱包地址分组 17 | const groups = []; 18 | for (let i = 0; i < addresses.length; i += addressGroupSize) { 19 | const group = addresses.slice(i, Math.min(i + addressGroupSize, addresses.length)); 20 | if (group.length > 0) { 21 | groups.push(group); 22 | } 23 | } 24 | 25 | groups.forEach((group, index) => { 26 | Logger.info(`第 ${index + 1} 组地址数量: ${group.length}`); 27 | }); 28 | 29 | return groups; 30 | } 31 | 32 | function getPrivateKeys() { 33 | const keys = readFileLines(path.join(paths.data, 'pk.txt')); 34 | return keys; 35 | } 36 | 37 | function saveFailedDistribution(privateKey, recipients, error) { 38 | try { 39 | // 确保logs目录存在 40 | if (!fs.existsSync(paths.logs)) { 41 | fs.mkdirSync(paths.logs, { recursive: true }); 42 | } 43 | 44 | const failedKeysPath = path.join(paths.logs, 'failed_distribute_keys.txt'); 45 | const failedAddressesPath = path.join(paths.logs, 'failed_distribute_addresses.txt'); 46 | const failedLogsPath = path.join(paths.logs, 'failed_distribute_logs.txt'); 47 | 48 | // 保存失败的私钥 49 | fs.appendFileSync(failedKeysPath, privateKey + '\n'); 50 | 51 | // 保存失败的接收地址组 52 | fs.appendFileSync(failedAddressesPath, recipients.join('\n') + '\n---\n'); 53 | 54 | // 保存失败日志 55 | const logEntry = `时间: ${new Date().toISOString()}\n` + 56 | `钱包: ${new ethers.Wallet(privateKey).address}\n` + 57 | `错误: ${error}\n` + 58 | `接收地址: ${recipients.join(', ')}\n` + 59 | '----------------------------------------\n'; 60 | fs.appendFileSync(failedLogsPath, logEntry); 61 | 62 | Logger.info('失败记录已保存'); 63 | } catch (error) { 64 | Logger.error(`保存失败记录时出错: ${error.message}`); 65 | } 66 | } 67 | 68 | async function retryFailedDistributions(amountPerAddress) { 69 | try { 70 | const failedKeysPath = path.join(paths.logs, 'failed_distribute_keys.txt'); 71 | const failedAddressesPath = path.join(paths.logs, 'failed_distribute_addresses.txt'); 72 | 73 | // 检查是否存在失败记录 74 | if (!fs.existsSync(failedKeysPath) || !fs.existsSync(failedAddressesPath)) { 75 | Logger.warning('没有找到失败记录'); 76 | return; 77 | } 78 | 79 | // 读取失败的私钥和地址组 80 | const failedKeys = readFileLines(failedKeysPath); 81 | const addressesContent = fs.readFileSync(failedAddressesPath, 'utf8'); 82 | const addressGroups = addressesContent.split('---\n') 83 | .filter(group => group.trim()) 84 | .map(group => group.trim().split('\n').filter(addr => addr.trim())); 85 | 86 | if (failedKeys.length === 0 || addressGroups.length === 0) { 87 | Logger.info('没有需要重试的交易'); 88 | return; 89 | } 90 | 91 | if (failedKeys.length !== addressGroups.length) { 92 | throw new Error('私钥数量与地址组数量不匹配'); 93 | } 94 | 95 | Logger.title('开始重试失败的分发任务'); 96 | Logger.info(`找到 ${failedKeys.length} 个失败记录`); 97 | Logger.info(`每个地址将收到 ${Logger.amount(amountPerAddress)} ETH`); 98 | Logger.divider(); 99 | 100 | for (let i = 0; i < failedKeys.length; i++) { 101 | const result = await deployAndDistribute( 102 | failedKeys[i], 103 | addressGroups[i], 104 | amountPerAddress 105 | ); 106 | 107 | if (result.success) { 108 | Logger.success(`钱包 ${Logger.address(result.from)} 重试成功`); 109 | } else { 110 | Logger.error(`钱包 ${Logger.address(result.from)} 重试失败: ${result.error}`); 111 | } 112 | 113 | // 添加随机延迟 114 | if (i < failedKeys.length - 1) { 115 | const delay = Math.floor(Math.random() * 30000) + 1000; 116 | Logger.processing(`等待 ${Math.floor(delay/1000)} 秒后继续...`); 117 | await new Promise(resolve => setTimeout(resolve, delay)); 118 | } 119 | } 120 | 121 | // 清理已处理的失败记录 122 | fs.unlinkSync(failedKeysPath); 123 | fs.unlinkSync(failedAddressesPath); 124 | } catch (error) { 125 | Logger.error(`重试任务失败: ${error.message}`); 126 | } 127 | } 128 | 129 | async function deployAndDistribute(privateKey, recipients, amountPerAddress) { 130 | let wallet; 131 | try { 132 | // 读取合约数据 133 | const contractData = JSON.parse(fs.readFileSync(path.join(paths.contracts, 'contract.json'), 'utf8')); 134 | const abi = contractData.abi; 135 | const bytecode = contractData.bytecode; 136 | 137 | // 连接到Sepolia网络 138 | const provider = new ethers.JsonRpcProvider(networks.sepolia.rpc); 139 | wallet = new ethers.Wallet(privateKey, provider); 140 | 141 | // 获取钱包余额 142 | const balance = await provider.getBalance(wallet.address); 143 | 144 | Logger.info(`开始处理钱包: ${Logger.address(wallet.address)},余额: ${Logger.amount(ethers.formatEther(balance))} ETH`); 145 | Logger.info(`接收地址: ${recipients.join(', ')}`); 146 | 147 | // 为每个接收地址准备相同金额 148 | const amount = ethers.parseEther(amountPerAddress.toString()); 149 | const amounts = recipients.map(() => amount); 150 | 151 | // 计算总金额 152 | const totalAmount = amount * BigInt(recipients.length); 153 | 154 | Logger.info(`每个地址金额: ${Logger.amount(amountPerAddress)} ETH`); 155 | Logger.info(`总金额: ${Logger.amount(ethers.formatEther(totalAmount))} ETH`); 156 | 157 | // 检查余额是否足够 158 | if (balance < totalAmount) { 159 | Logger.error('余额不足'); 160 | Logger.error(`需要: ${Logger.amount(ethers.formatEther(totalAmount))} ETH`); 161 | Logger.error(`实际: ${Logger.amount(ethers.formatEther(balance))} ETH`); 162 | saveFailedDistribution(privateKey, recipients, '余额不足'); 163 | return { 164 | success: false, 165 | error: '余额不足', 166 | from: wallet.address, 167 | recipientCount: recipients.length 168 | }; 169 | } 170 | 171 | // 部署合约 172 | Logger.info('正在部署合约...'); 173 | const factory = new ethers.ContractFactory(abi, bytecode, wallet); 174 | const contract = await factory.deploy(); 175 | await contract.waitForDeployment(); 176 | const contractAddress = await contract.getAddress(); 177 | Logger.info(`合约已部署成功,合约地址: ${Logger.address(contractAddress)}`); 178 | 179 | // 发送分发交易 180 | Logger.info('准备发送分发交易...'); 181 | const tx = await contract.distributeEth(recipients, amounts, { 182 | value: totalAmount, 183 | gasLimit: transaction.gasLimit 184 | }); 185 | // 等待交易确认 186 | const receipt = await tx.wait(); 187 | Logger.info(`分发交易已确认,交易哈希: ${Logger.hash(tx.hash)}`); 188 | 189 | return { 190 | success: true, 191 | hash: tx.hash, 192 | contractAddress, 193 | from: wallet.address, 194 | recipientCount: recipients.length 195 | }; 196 | 197 | } catch (error) { 198 | Logger.error('操作失败:', error); 199 | saveFailedDistribution(privateKey, recipients, error.message); 200 | return { 201 | success: false, 202 | error: error.message, 203 | from: wallet?.address, 204 | recipientCount: recipients.length 205 | }; 206 | } 207 | } 208 | 209 | // 编译并部署合约 210 | async function prepareContract() { 211 | return new Promise((resolve, reject) => { 212 | // 1. 编译合约 213 | Logger.info('\n开始编译合约...'); 214 | const compileProcess = exec('node src/scripts/compile.js'); 215 | 216 | compileProcess.stdout.on('data', (data) => { 217 | process.stdout.write(data); 218 | }); 219 | 220 | compileProcess.stderr.on('data', (data) => { 221 | process.stderr.write(data); 222 | }); 223 | 224 | compileProcess.on('close', (code) => { 225 | if (code !== 0) { 226 | reject(new Error('合约编译失败')); 227 | return; 228 | } 229 | resolve(); 230 | }); 231 | }); 232 | } 233 | 234 | async function processAllWallets(amountPerAddress) { 235 | try { 236 | // 首先编译合约 237 | await prepareContract(); 238 | 239 | const privateKeys = getPrivateKeys(); 240 | const addressGroups = getAddressGroups(); 241 | 242 | Logger.info(`\n准备处理 ${addressGroups.length} 组地址`); 243 | 244 | if (privateKeys.length < addressGroups.length) { 245 | throw new Error(`私钥数量(${privateKeys.length})小于地址组数量(${addressGroups.length})!`); 246 | } 247 | 248 | for (let i = 0; i < addressGroups.length; i++) { 249 | const result = await deployAndDistribute( 250 | privateKeys[i], 251 | addressGroups[i], 252 | amountPerAddress 253 | ); 254 | 255 | if (result.success) { 256 | Logger.success(`钱包 ${result.from} 分发成功`); 257 | } else { 258 | Logger.error(`钱包 ${result.from} 操作失败: ${result.error}`); 259 | } 260 | 261 | // 添加随机延迟 262 | if (i < addressGroups.length - 1) { 263 | const delay = Math.floor(Math.random() * 30000) + 1000; 264 | Logger.info(`等待 ${Math.floor(delay/1000)} 秒后处理下一组...`); 265 | await new Promise(resolve => setTimeout(resolve, delay)); 266 | } 267 | } 268 | } catch (error) { 269 | Logger.error('程序执行错误:', error); 270 | } 271 | } 272 | 273 | // 如果直接运行此脚本 274 | if (require.main === module) { 275 | Logger.info(`\n开始执行分发任务,每个地址将收到 ${amountPerAddress} ETH`); 276 | 277 | if (process.argv.includes('retry')) { 278 | retryFailedDistributions(amountPerAddress) 279 | .then(() => Logger.success('重试任务完成')) 280 | .catch(error => Logger.error('��序执行出错:', error)); 281 | } else { 282 | processAllWallets(amountPerAddress) 283 | .then(() => Logger.success('所有分发任务完成')) 284 | .catch(error => Logger.error('程序执行出错:', error)); 285 | } 286 | } 287 | 288 | module.exports = { 289 | processAllWallets, 290 | deployAndDistribute, 291 | retryFailedDistributions 292 | }; -------------------------------------------------------------------------------- /src/scripts/clean.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { paths } = require('../utils/config'); 4 | const Logger = require('../utils/logger'); 5 | 6 | Logger.title('清理数据'); 7 | 8 | try { 9 | // 要清理的文件列表 10 | const files = [ 11 | path.join(paths.data, 'pk.txt'), 12 | path.join(paths.data, 'address.txt'), 13 | path.join(paths.logs, 'failed_bridge_keys.txt'), 14 | path.join(paths.logs, 'failed_bridge_logs.txt'), 15 | path.join(paths.logs, 'failed_distribute_keys.txt'), 16 | path.join(paths.logs, 'failed_distribute_logs.txt'), 17 | path.join(paths.logs, 'failed_distribute_addresses.txt') 18 | ]; 19 | 20 | // 清理每个文件 21 | files.forEach(file => { 22 | if (fs.existsSync(file)) { 23 | fs.unlinkSync(file); 24 | Logger.success(`已删除: ${path.basename(file)}`); 25 | } 26 | }); 27 | 28 | // 确保 data 目录存在 29 | if (!fs.existsSync(paths.data)) { 30 | fs.mkdirSync(paths.data, { recursive: true }); 31 | } 32 | 33 | // 创建必要的文件 34 | fs.writeFileSync( 35 | path.join(paths.data, 'pk.txt'), 36 | '// 私钥,多个私钥用换行符分隔\n' 37 | ); 38 | Logger.info('已创建: pk.txt'); 39 | 40 | fs.writeFileSync( 41 | path.join(paths.data, 'address.txt'), 42 | '// 接收资金的钱包地址,多个钱包地址用换行符分隔\n' 43 | ); 44 | Logger.info('已创建: address.txt'); 45 | 46 | Logger.success('清理完成'); 47 | } catch (error) { 48 | Logger.error('清理失败'); 49 | Logger.error(`错误信息: ${error.message}`); 50 | process.exit(1); 51 | } -------------------------------------------------------------------------------- /src/scripts/compile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const solc = require('solc'); 3 | const path = require('path'); 4 | const { paths } = require('../utils/config'); 5 | const Logger = require('../utils/logger'); 6 | 7 | Logger.title('Solidity 合约编译'); 8 | 9 | // 读取 Solidity 文件内容 10 | const contractPath = path.join(paths.contracts, 'Distributor.sol'); 11 | 12 | // 确保contracts目录存在 13 | if (!fs.existsSync(paths.contracts)) { 14 | fs.mkdirSync(paths.contracts, { recursive: true }); 15 | } 16 | 17 | const source = fs.readFileSync(contractPath, 'utf8'); 18 | Logger.divider(); 19 | 20 | Logger.processing('正在编译合约...'); 21 | 22 | // 准备编译配置 23 | const input = { 24 | language: 'Solidity', 25 | sources: { 26 | 'Distributor.sol': { 27 | content: source 28 | } 29 | }, 30 | settings: { 31 | outputSelection: { 32 | '*': { 33 | '*': ['*'] 34 | } 35 | } 36 | } 37 | }; 38 | 39 | try { 40 | // 编译合约 41 | const output = JSON.parse(solc.compile(JSON.stringify(input))); 42 | 43 | // 检查编译错误 44 | if (output.errors) { 45 | let hasError = false; 46 | output.errors.forEach(error => { 47 | if (error.severity === 'error') { 48 | Logger.error('编译错误'); 49 | Logger.error(`位置: ${error.sourceLocation?.file || '未知'}`); 50 | Logger.error(`信息: ${error.message}`); 51 | hasError = true; 52 | } else { 53 | Logger.warning('编译警告'); 54 | Logger.warning(`位置: ${error.sourceLocation?.file || '未知'}`); 55 | Logger.warning(`信息: ${error.message}`); 56 | } 57 | }); 58 | 59 | if (hasError) { 60 | Logger.error('编译失败,存在错误'); 61 | process.exit(1); 62 | } 63 | } 64 | 65 | const contract = output.contracts['Distributor.sol']['Distributor']; 66 | 67 | // 保存编译结果 68 | const outputPath = path.join(paths.contracts, 'contract.json'); 69 | fs.writeFileSync( 70 | outputPath, 71 | JSON.stringify({ 72 | abi: contract.abi, 73 | bytecode: contract.evm.bytecode.object 74 | }, null, 2) 75 | ); 76 | 77 | // 同时保存ABI到单独的文件 78 | const abiPath = path.join(paths.contracts, 'abi.json'); 79 | fs.writeFileSync(abiPath, JSON.stringify(contract.abi, null, 2)); 80 | 81 | Logger.success('合约编译成功'); 82 | } catch (error) { 83 | Logger.error('编译失败'); 84 | Logger.error(`错误信息: ${error.message}`); 85 | process.exit(1); 86 | } -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | // 确保目录存在 5 | function ensureDirectoryExists(dirPath) { 6 | if (!fs.existsSync(dirPath)) { 7 | fs.mkdirSync(dirPath, { recursive: true }); 8 | } 9 | } 10 | 11 | // 项目根目录 12 | const rootDir = path.join(__dirname, '../..'); 13 | 14 | // 定义路径配置 15 | const pathConfig = { 16 | contracts: path.join(rootDir, 'src/contracts'), 17 | data: path.join(rootDir, 'data'), 18 | logs: path.join(rootDir, 'logs') 19 | }; 20 | 21 | // 初始化目录 22 | Object.values(pathConfig).forEach(ensureDirectoryExists); 23 | 24 | module.exports = { 25 | // 网络配置 26 | networks: { 27 | sepolia: { 28 | rpc: 'https://sepolia.infura.io/v3/f4b6a411058a463082a46bbb9a5f3d9a', 29 | chainId: 11155111 30 | }, 31 | arbitrum: { 32 | rpc: 'https://arbitrum-mainnet.infura.io/v3/f4b6a411058a463082a46bbb9a5f3d9a', 33 | chainId: 42161 34 | } 35 | }, 36 | 37 | // 文件路径配置 38 | paths: pathConfig, 39 | 40 | // 交易配置 41 | transaction: { 42 | gasLimit: 5000000n, 43 | confirmations: 1 44 | }, 45 | 46 | // 分发配置 47 | distribution: { 48 | defaultAmount: 0.2, 49 | defaultGroupSize: 4 50 | }, 51 | 52 | // 跨链配置 53 | bridge: { 54 | amount: 0.00005, 55 | layerZeroFee: '0.000008879862610065' 56 | } 57 | }; -------------------------------------------------------------------------------- /src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const config = require('./config'); 4 | 5 | // 文件读取辅助函数 6 | function readFileLines(filePath) { 7 | return fs.readFileSync(filePath, 'utf8') 8 | .split('\n') 9 | .map(line => line.trim()) 10 | .filter(line => line.length > 0); 11 | } 12 | 13 | // 分组辅助函数 14 | function groupAddresses(addresses, groupSize) { 15 | const groups = []; 16 | for (let i = 0; i < addresses.length; i += groupSize) { 17 | const group = addresses.slice(i, Math.min(i + groupSize, addresses.length)); 18 | if (group.length > 0) { 19 | groups.push(group); 20 | } 21 | } 22 | return groups; 23 | } 24 | 25 | // 失败记录处理 26 | function saveFailedTransaction(privateKey, addresses, error) { 27 | const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); 28 | const logPath = config.paths.logs; 29 | 30 | fs.appendFileSync(path.join(logPath, 'failed_keys.txt'), `${privateKey}\n`); 31 | fs.appendFileSync(path.join(logPath, 'failed_addresses.txt'), addresses.join('\n') + '\n'); 32 | fs.appendFileSync( 33 | path.join(logPath, 'failed_logs.txt'), 34 | `\n[${timestamp}]\nPrivate Key: ${privateKey}\nAddresses: ${addresses.join(', ')}\nError: ${error}\n` 35 | ); 36 | } 37 | 38 | module.exports = { 39 | readFileLines, 40 | groupAddresses, 41 | saveFailedTransaction 42 | }; -------------------------------------------------------------------------------- /src/utils/logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | class Logger { 4 | static success(message) { 5 | console.log(chalk.green('✓ ') + message); 6 | } 7 | 8 | static error(message) { 9 | console.log(chalk.red('✗ ') + message); 10 | } 11 | 12 | static info(message) { 13 | console.log(chalk.blue('ℹ ') + message); 14 | } 15 | 16 | static warning(message) { 17 | console.log(chalk.yellow('⚠ ') + message); 18 | } 19 | 20 | static processing(message) { 21 | console.log(chalk.cyan('⋯ ') + message); 22 | } 23 | 24 | static title(message) { 25 | console.log('\n' + chalk.bold.blue('=== ' + message + ' ===')); 26 | } 27 | 28 | static divider() { 29 | console.log(chalk.gray('─'.repeat(50))); 30 | } 31 | 32 | static amount(eth, label = 'ETH') { 33 | return chalk.yellow(eth) + ' ' + chalk.gray(label); 34 | } 35 | 36 | static address(addr) { 37 | return chalk.cyan(addr); 38 | } 39 | 40 | static hash(hash) { 41 | return chalk.magenta(hash); 42 | } 43 | } 44 | 45 | module.exports = Logger; -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | const readline = require('readline'); 3 | 4 | const rl = readline.createInterface({ 5 | input: process.stdin, 6 | output: process.stdout 7 | }); 8 | 9 | function promptUser() { 10 | console.log('\n=== ETH 工具选择 ==='); 11 | console.log('1. 购买测试币'); 12 | console.log('2. 重试购买测试币'); 13 | console.log('3. 批量分发'); 14 | console.log('4. 重试失败转账'); 15 | console.log('5. 查询钱包余额'); 16 | console.log('6. 清理所有数据'); 17 | console.log('0. 退出程序'); 18 | console.log('──────────────────────────────────────────────────'); 19 | 20 | rl.question('请选择功能 (0-6): ', async (choice) => { 21 | switch (choice) { 22 | case '0': 23 | console.log('程序已退出'); 24 | process.exit(0); 25 | break; 26 | case '1': 27 | await handleBridge(); 28 | break; 29 | case '2': 30 | await handleRetryBridge(); 31 | break; 32 | case '3': 33 | await handleDistribution(); 34 | break; 35 | case '4': 36 | await handleRetry(); 37 | break; 38 | case '5': 39 | await handleCheckBalance(); 40 | break; 41 | case '6': 42 | await handleClean(); 43 | break; 44 | default: 45 | console.log('无效的选择,请重试'); 46 | promptUser(); 47 | break; 48 | } 49 | }); 50 | } 51 | 52 | async function handleBridge() { 53 | rl.question('请输入购买测试币的ETH数量: ', (amount) => { 54 | if (isNaN(amount) || amount <= 0) { 55 | console.log('请输入有效的数字!'); 56 | handleBridge(); 57 | return; 58 | } 59 | 60 | console.log(`\n开始购买 ${amount} ETH的测试币...`); 61 | 62 | const bridgeProcess = exec('node src/core/bridge.js', { 63 | env: { 64 | ...process.env, 65 | BRIDGE_AMOUNT: amount 66 | } 67 | }); 68 | 69 | bridgeProcess.stdout.on('data', (data) => { 70 | console.log(data.toString()); 71 | }); 72 | 73 | bridgeProcess.stderr.on('data', (data) => { 74 | console.error(data.toString()); 75 | }); 76 | 77 | bridgeProcess.on('close', (code) => { 78 | console.log('\n执行完成,按回车键继续...'); 79 | rl.question('', () => { 80 | promptUser(); 81 | }); 82 | }); 83 | }); 84 | } 85 | 86 | async function handleRetryBridge() { 87 | rl.question('请输入重试购买的ETH数量: ', (amount) => { 88 | if (isNaN(amount) || amount <= 0) { 89 | console.log('请输入有效的数字!'); 90 | handleRetryBridge(); 91 | return; 92 | } 93 | 94 | console.log(`\n开始重试购买 ${amount} ETH的测试币...`); 95 | 96 | const bridgeProcess = exec('node src/core/bridge.js retry', { 97 | env: { 98 | ...process.env, 99 | BRIDGE_AMOUNT: amount 100 | } 101 | }); 102 | 103 | bridgeProcess.stdout.on('data', (data) => { 104 | console.log(data.toString()); 105 | }); 106 | 107 | bridgeProcess.stderr.on('data', (data) => { 108 | console.error(data.toString()); 109 | }); 110 | 111 | bridgeProcess.on('close', (code) => { 112 | console.log('\n执行完成,按回车键继续...'); 113 | rl.question('', () => { 114 | promptUser(); 115 | }); 116 | }); 117 | }); 118 | } 119 | 120 | async function handleDistribution() { 121 | rl.question('请输入每个地址分发的ETH数量: ', (amount) => { 122 | if (isNaN(amount) || amount <= 0) { 123 | console.log('请输入有效的数字!'); 124 | handleDistribution(); 125 | return; 126 | } 127 | 128 | rl.question('请输入每组地址的数量 (默认为4): ', (groupSize) => { 129 | groupSize = parseInt(groupSize) || 4; 130 | 131 | if (isNaN(groupSize) || groupSize <= 0) { 132 | console.log('使用默认组大小: 4'); 133 | groupSize = 4; 134 | } 135 | 136 | process.env.DISTRIBUTION_AMOUNT = amount; 137 | process.env.GROUP_SIZE = groupSize; 138 | 139 | console.log(`\n开始分发,每个地址 ${amount} ETH,每组 ${groupSize} 个地址...`); 140 | 141 | const distributeProcess = exec('node src/core/distribute.js', { 142 | env: { ...process.env } 143 | }); 144 | 145 | distributeProcess.stdout.on('data', (data) => { 146 | process.stdout.write(data); 147 | }); 148 | 149 | distributeProcess.stderr.on('data', (data) => { 150 | process.stderr.write(data); 151 | }); 152 | 153 | distributeProcess.on('close', (code) => { 154 | console.log('\n执行完成,按回车键继续...'); 155 | rl.question('', () => { 156 | promptUser(); 157 | }); 158 | }); 159 | }); 160 | }); 161 | } 162 | 163 | async function handleRetry() { 164 | rl.question('请输入重试分发的ETH数量: ', (amount) => { 165 | if (isNaN(amount) || amount <= 0) { 166 | console.log('请输入有效的数字!'); 167 | handleRetry(); 168 | return; 169 | } 170 | 171 | console.log(`开始重��失败的分发任务,每个地址 ${amount} ETH...`); 172 | 173 | const retryProcess = exec('node src/core/distribute.js retry', { 174 | env: { 175 | ...process.env, 176 | DISTRIBUTION_AMOUNT: amount 177 | } 178 | }); 179 | 180 | retryProcess.stdout.on('data', (data) => { 181 | console.log(data.toString()); 182 | }); 183 | 184 | retryProcess.stderr.on('data', (data) => { 185 | console.error(data.toString()); 186 | }); 187 | 188 | retryProcess.on('close', (code) => { 189 | console.log('\n执行完成,按回车键继续...'); 190 | rl.question('', () => { 191 | promptUser(); 192 | }); 193 | }); 194 | }); 195 | } 196 | 197 | async function handleCheckBalance() { 198 | console.log('开始查询钱包余额...'); 199 | const checkProcess = exec('node src/core/balance.js', { 200 | env: { ...process.env } 201 | }); 202 | 203 | checkProcess.stdout.on('data', (data) => { 204 | console.log(data.toString()); 205 | }); 206 | 207 | checkProcess.stderr.on('data', (data) => { 208 | console.error(data.toString()); 209 | }); 210 | 211 | checkProcess.on('close', (code) => { 212 | console.log('\n执行完成,按回车键继续...'); 213 | rl.question('', () => { 214 | promptUser(); 215 | }); 216 | }); 217 | } 218 | 219 | async function handleClean() { 220 | rl.question('确定要清理所有数据吗?这将清空所有私钥和地址记录。(y/n): ', (answer) => { 221 | if (answer.toLowerCase() === 'y') { 222 | console.log('开始清理数据...'); 223 | 224 | const cleanProcess = exec('node src/scripts/clean.js', { 225 | env: { ...process.env } 226 | }); 227 | 228 | cleanProcess.stdout.on('data', (data) => { 229 | console.log(data.toString()); 230 | }); 231 | 232 | cleanProcess.stderr.on('data', (data) => { 233 | console.error(data.toString()); 234 | }); 235 | 236 | cleanProcess.on('close', (code) => { 237 | console.log('\n执行完成,按回车键继续...'); 238 | rl.question('', () => { 239 | promptUser(); 240 | }); 241 | }); 242 | } else { 243 | console.log('已取消清理操作'); 244 | promptUser(); 245 | } 246 | }); 247 | } 248 | 249 | console.log('欢迎使用 ETH 工具!'); 250 | promptUser(); 251 | 252 | // 处理程序退出 253 | process.on('SIGINT', () => { 254 | console.log('\n程序已终止'); 255 | process.exit(0); 256 | }); --------------------------------------------------------------------------------