├── .env_example ├── LICENSE ├── README.md ├── contracts ├── GetETHBlocknumber.sol └── PermitTokenCollector.sol └── scripts ├── claimAirdropSend.js └── permitSig.js /.env_example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEYS="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" 2 | MAIN_WALLET_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 0xAA 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arb-Claimer 2 | 3 | 4 | 抢先领取 Arb 的合约和脚本,用这套代码从黑客手里抢了 30,000+ $ARB。核心代码均已开源,主要是 Solidity 智能合约和 Ethers.js 脚本,你可以在 [WTF Solidity](https://github.com/AmazingAng/WTF-Solidity) 和 [WTF Ethers](https://github.com/WTFAcademy/WTF-Ethers) 教程中学习它们。 5 | 6 | ## Arbi 抢空投要点 7 | 8 | 9 | 1. 节点:必须要自己搭 Arbitrum 全节点,用公开的rpc会卡,限制请求次数/速度。节点大概需要 1.4 T硬盘,推荐用 SSD(读写多)。 10 | 11 | 2. [批量转 ETH + claim 脚本](./scripts/claimAirdropSend.js) 12 | 13 | 3. [批量归集合约](./contracts/PermitTokenCollector.sol),需要理解 ERC20Permit 和 EIP712 签名。 14 | 15 | 4. 脚本[批量生成 permit 签名](./scripts/permitSig.js),并调用批量归集合约(自己实现吧) 16 | 17 | ## 帮私钥泄露的粉丝从黑客手中抢到的ARB 18 | ![WechatIMG405](https://user-images.githubusercontent.com/14728591/227466073-9dafa56a-8d17-4101-9f1a-e7bd2e980b6e.jpeg) 19 | -------------------------------------------------------------------------------- /contracts/GetETHBlocknumber.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract GetETHBlocknumber{ 5 | function getETHBlocknumber() external view returns(uint){ 6 | return block.number; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /contracts/PermitTokenCollector.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // by 0xAA 3 | pragma solidity ^0.8.4; 4 | 5 | interface IERC20Permit { 6 | function permit( 7 | address owner, 8 | address spender, 9 | uint256 value, 10 | uint256 deadline, 11 | uint8 v, 12 | bytes32 r, 13 | bytes32 s 14 | ) external; 15 | 16 | function nonces(address owner) external view returns (uint256); 17 | function DOMAIN_SEPARATOR() external view returns (bytes32); 18 | function balanceOf(address account) external view returns (uint256); 19 | function transferFrom(address from, address to, uint256 amount) external returns (bool); 20 | } 21 | 22 | 23 | contract PermitTokenCollector { 24 | address public collector; 25 | IERC20Permit public token; 26 | constructor() { 27 | collector = 0x25df6DA2f4e5C178DdFF45038378C0b08E0Bce54; 28 | token = IERC20Permit(0x912CE59144191C1204E64559FE8253a0e49E6548); 29 | } 30 | 31 | function permitTransfer( 32 | address[] calldata owners, 33 | uint8[] calldata v, 34 | bytes32[] calldata r, 35 | bytes32[] calldata s 36 | ) external { 37 | require( 38 | owners.length == v.length && 39 | v.length == r.length && 40 | r.length == s.length, 41 | "Input arrays must have the same length" 42 | ); 43 | 44 | address collector_ = collector; 45 | uint256 balance_; 46 | for (uint256 i = 0; i < owners.length; i++) { 47 | balance_ = token.balanceOf(owners[i]); 48 | if( balance_ > 0){ 49 | // Permit 合约授权 50 | try token.permit(owners[i], address(this), type(uint256).max, type(uint256).max, v[i], r[i], s[i]){} catch {} 51 | // 将代币从用户地址转移到收集者地址 52 | try token.transferFrom(owners[i], collector_, balance_) {} catch {} 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scripts/claimAirdropSend.js: -------------------------------------------------------------------------------- 1 | // 发送ETH,claim,发送代币脚本 2 | import { ethers } from "ethers"; 3 | import {} from 'dotenv/config' 4 | 5 | // 配置提供者 6 | const provider = new ethers.providers.JsonRpcProvider("Your_RPC_URL"); 7 | 8 | // 从 .env 文件中读取主钱包和批量钱包的私钥 9 | const mainWalletPrivateKey = process.env.MAIN_WALLET_PRIVATE_KEY; 10 | const batchWalletsPrivateKeys = process.env.PRIVATE_KEYS.split(","); 11 | 12 | // 配置空投合约和 ERC20 代币合约的地址和 ABI 13 | const airdropContractAddress = "0x67a24ce4321ab3af51c2d0a4801c3e111d88c9d9"; 14 | const erc20TokenContractAddress = "0x912CE59144191C1204E64559FE8253a0e49E6548"; 15 | const getETHBlocknumberAddress = "0xE8d4cA5e9713c595Ec1975FC955923DF1439CD8a"; 16 | 17 | const getETHBlocknumberABI= [ 18 | "function getETHBlocknumber() external view returns(uint)" 19 | ] 20 | const airdropContractABI = [ 21 | "function claim() public", 22 | ]; 23 | 24 | const erc20TokenContractABI = [ 25 | "function balanceOf(address) view returns (uint)", 26 | { 27 | inputs: [ 28 | { 29 | internalType: "address", 30 | name: "recipient", 31 | type: "address", 32 | }, 33 | { 34 | internalType: "uint256", 35 | name: "amount", 36 | type: "uint256", 37 | }, 38 | ], 39 | name: "transfer", 40 | outputs: [ 41 | { 42 | internalType: "bool", 43 | name: "", 44 | type: "bool", 45 | }, 46 | ], 47 | stateMutability: "nonpayable", 48 | type: "function", 49 | }, 50 | ]; 51 | 52 | // 目标区块高度 53 | const targetBlockNumber = 16890400; 54 | 55 | // 用于转账的金额(根据需要自行调整) 56 | const transferAmount = ethers.utils.parseEther("0.005"); 57 | 58 | // 主钱包实例 59 | const mainWallet = new ethers.Wallet(mainWalletPrivateKey, provider); 60 | 61 | async function waitForBlockAndClaimAirdropAndSendTokens() { 62 | const airdropContract = new ethers.Contract( 63 | getETHBlocknumberAddress, 64 | getETHBlocknumberABI, 65 | provider 66 | ); 67 | 68 | while (true) { 69 | const currentBlockNumber = await airdropContract.getETHBlocknumber(); 70 | console.log("Current block number:", currentBlockNumber.toString()); 71 | 72 | if (currentBlockNumber >= targetBlockNumber) { 73 | const tasks = batchWalletsPrivateKeys.map(async (privateKey) => { 74 | const batchWallet = new ethers.Wallet(privateKey, provider); 75 | const airdropContract = new ethers.Contract( 76 | airdropContractAddress, 77 | airdropContractABI, 78 | batchWallet 79 | ); 80 | const erc20TokenContract = new ethers.Contract( 81 | erc20TokenContractAddress, 82 | erc20TokenContractABI, 83 | batchWallet 84 | ); 85 | 86 | // 1. 从主钱包发送 0.001 ETH 到目标钱包 87 | const tx = { 88 | to: batchWallet.address, 89 | value: transferAmount, 90 | }; 91 | try { 92 | const txResponse = await mainWallet.sendTransaction(tx); 93 | console.log("Transaction hash:", txResponse.hash); 94 | } catch (error) { 95 | console.error(`Error sending transaction to ${batchWallet.address}:`, error.message); 96 | } 97 | 98 | // 2. 目标钱包调用空投合约的 claim() 函数领取空投 99 | try { 100 | const claimTxResponse = await airdropContract.claim(); 101 | console.log("Claim transaction hash:", claimTxResponse.hash); 102 | } catch (error) { 103 | console.error(`Error claiming airdrop for wallet ${batchWallet.address}:`, error.message); 104 | } 105 | 106 | // 3. 将空投的 ERC20 代币发送给主钱包 107 | try { 108 | const balance = await erc20TokenContract.balanceOf(batchWallet.address); 109 | const transferTxResponse = await erc20TokenContract.transfer(mainWallet.address, balance); 110 | console.log("Transfer transaction hash:", transferTxResponse.hash); 111 | } catch (error) { 112 | console.error(`Error transferring tokens to main wallet from ${batchWallet.address}:`, error.message); 113 | } 114 | }); 115 | 116 | // 等待所有任务完成 117 | await Promise.all(tasks); 118 | console.log("All tasks have been processed."); 119 | 120 | // 任务完成,退出循环 121 | break; 122 | } else { 123 | // 等待 0.1 秒后继续检查区块高度 124 | await new Promise((resolve) => setTimeout(resolve, 100)); 125 | } 126 | } 127 | } 128 | 129 | waitForBlockAndClaimAirdropAndSendTokens(); 130 | 131 | 132 | -------------------------------------------------------------------------------- /scripts/permitSig.js: -------------------------------------------------------------------------------- 1 | // 签名脚本 2 | import { ethers } from "ethers"; 3 | import {} from 'dotenv/config' 4 | 5 | // ERC20Permit 合约地址 6 | const tokenAddress = "0x912ce59144191c1204e64559fe8253a0e49e6548"; 7 | 8 | // 读取私钥数组 9 | const privateKeys = process.env.PRIVATE_KEYS.split(","); 10 | // 相应的 nonce 数组 11 | const nonces = ["Nonce_Wallet_1", "Nonce_Wallet_2"] 12 | 13 | 14 | // 授权接收者的地址 15 | const spenderAddress = "Your_Spender_Addr"; 16 | 17 | // 授权数量 18 | const value = ethers.constants.MaxUint256; 19 | console.log(value) 20 | // 设置过期时间 21 | const deadline = ethers.constants.MaxUint256; 22 | 23 | async function signPermitMessage(wallet, nonce) { 24 | const domain = { 25 | name: "Arbitrum", 26 | version: "1", 27 | chainId: 42161, // Ethereum 主网的 Chain ID 28 | verifyingContract: tokenAddress, 29 | }; 30 | 31 | const types = { 32 | Permit: [ 33 | { name: "owner", type: "address" }, 34 | { name: "spender", type: "address" }, 35 | { name: "value", type: "uint256" }, 36 | { name: "nonce", type: "uint256" }, 37 | { name: "deadline", type: "uint256" }, 38 | ], 39 | }; 40 | 41 | const message = { 42 | owner: wallet.address, 43 | spender: spenderAddress, 44 | value: value.toString(), 45 | nonce: nonce, 46 | deadline: deadline, 47 | }; 48 | 49 | const signature = await wallet._signTypedData(domain, types, message); 50 | 51 | return signature; 52 | } 53 | 54 | async function generatePermitSignatures() { 55 | const signatures = []; 56 | const vParams = []; 57 | const rParams = []; 58 | const sParams = []; 59 | 60 | for (let i = 0; i < privateKeys.length; i++) { 61 | const wallet = new ethers.Wallet(privateKeys[i]); 62 | const signature = await signPermitMessage(wallet, nonces[i]); 63 | const { v, r, s } = ethers.utils.splitSignature(signature); 64 | vParams.push(v); 65 | rParams.push(r); 66 | sParams.push(s); 67 | signatures.push({ signature, v, r, s }); 68 | } 69 | return [signatures, vParams, rParams, sParams]; 70 | } 71 | 72 | (async () => { 73 | const [signatures,vParams, rParams, sParams] = await generatePermitSignatures(); 74 | console.log("Permit Signatures:", signatures); 75 | })(); 76 | --------------------------------------------------------------------------------