├── .gitignore ├── LICENSE ├── README.md ├── bytecode ├── creationcode_evm_paris.txt ├── creationcode_evm_shanghai.txt ├── runtimecode_evm_paris.txt └── runtimecode_evm_shanghai.txt ├── foundry.toml ├── frontend ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── App.css │ ├── App.jsx │ ├── artifacts │ │ ├── BatchTransfer.json │ │ └── ERC20.json │ ├── assets │ │ └── ethereum.svg │ ├── components │ │ ├── Confirm.jsx │ │ ├── ConfirmEther.jsx │ │ ├── Connect.jsx │ │ ├── Ether.jsx │ │ ├── Headers.jsx │ │ ├── Payment.jsx │ │ ├── Recipients.jsx │ │ ├── Status.jsx │ │ ├── WalletInfo.jsx │ │ └── Warn.jsx │ ├── favicon.svg │ ├── index.css │ ├── logo.svg │ ├── main.jsx │ ├── reducers │ │ └── index.js │ └── utils │ │ ├── constants.js │ │ └── index.js ├── tailwind.config.js └── vite.config.js ├── funding.json ├── img ├── FoundryTest.png └── logo.png ├── interface └── IBatchTransfer.sol ├── src └── BatchTransfer.huff └── test ├── BatchTransferTest.t.sol └── USDTTransferTest.t.sol /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | /frontend/node_modules 4 | /frontend/dist 5 | /lib 6 | /out 7 | 8 | # Production 9 | /build 10 | 11 | 12 | # Generated files 13 | .docusaurus 14 | .cache-loader 15 | /cache 16 | src/artifacts/ 17 | 18 | # Misc 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | -------------------------------------------------------------------------------- /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 | 2 | 3 | # Batch.Money 4 | 5 | Efficiently transfer ether or tokens to many addresses in batch, powered by [Huff](https://github.com/huff-language/huff-rs) and [WTF Academy](https://wtf.academy), supporting 15 chains! 6 | 7 | ## Key Features 8 | 9 | 1. Efficiently transfer ether or tokens in batch. Saves 2~3% gas compared to Disperse App. 10 | 11 | 2. Support non-standard ERC20 (i.e. [USDT](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code)). 12 | 13 | 3. Small contract size, saves ~80% gas on deployment. 14 | 15 | ## Supported Networks 16 | 17 | website: [batch.money](https://batch.money) 18 | 19 | | Network | Contract address | 20 | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 21 | | ETH | [0xD35a289c1D5F6f6a604d6026111109694e51BA25](https://etherscan.io/address/0xD35a289c1D5F6f6a604d6026111109694e51BA25) | 22 | | Optimism | [0x3484593c456D9C598C47754341718062318066Ba](https://optimistic.etherscan.io/address/0x3484593c456D9C598C47754341718062318066Ba) | 23 | | Scroll | [0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d](https://blockscout.scroll.io/address/0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d) | 24 | | Base | [0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d](https://basescan.org/address/0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d) | 25 | | Arbitrum One | [0x63d9C12865336322Ca981E5d1392acde4fAdD3Dc](https://arbiscan.io/address/0x63d9C12865336322Ca981E5d1392acde4fAdD3Dc) | 26 | | Linea | [0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d](https://lineascan.build/address/0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d) | 27 | | BNB Chain | [0x0D69079B60484ae97EA7DEaad370B61f9Da401F8](https://bscscan.com/address/0x0D69079B60484ae97EA7DEaad370B61f9Da401F8) | 28 | | opBNB Chain | [0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d](https://opbnbscan.com/address/0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d) | 29 | | Polygon | [0x927ec65329636525a5B00103De1c00d8Da9b08aD](https://polygonscan.com/address/0x927ec65329636525a5B00103De1c00d8Da9b08aD) | 30 | | Polygon zkevm | [0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d](https://zkevm.polygonscan.com/address/0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d) | 31 | | Public Goods Network | [0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d](https://explorer.publicgoods.network/address/0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d) | 32 | | Gnosis Chain | [0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d](https://gnosis.blockscout.com/address/0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d) | 33 | | Goerli | [0xDC7a1993196d63db926c3B2e1C42682f39885B96](https://goerli.etherscan.io/address/0xdc7a1993196d63db926c3b2e1c42682f39885b96) | 34 | | Holesky | [0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d](https://holesky.etherscan.io/address/0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d) | 35 | | Sepolia | [0x800A2bE9B6259E252eDE4a5a041C23ab994F2962](https://sepolia.etherscan.io/address/0x800A2bE9B6259E252eDE4a5a041C23ab994F2962) | 36 | | Scroll Sepolia | [0x1726348d59697D19Ce307E662da6a631381dB8dD](https://sepolia-blockscout.scroll.io/address/0x1726348d59697D19Ce307E662da6a631381dB8dD) | 37 | | Merlin Chain | [0x6e4e372baF0C3Ba74CdA7E7E6Ef4bF526A209Ec2](https://scan.merlinchain.io/address/0x6e4e372baF0C3Ba74CdA7E7E6Ef4bF526A209Ec2) | 38 | | RSS3 VSL Mainnet | [0xC44c86E0Aaa90d914b194c347aaF9cf97823E3C8](https://scan.rss3.io/address/0xC44c86E0Aaa90d914b194c347aaF9cf97823E3C8) | 39 | 40 | ## Test Results 41 | 42 | ### Transfer ERC20 to 100 addresses on Goerli 43 | 44 | To test the gas consumption fairly, we created a new tokens for different methods. For normal transfer, we record the gas used by transfering token to 1 address, and then multiply it by 100. 45 | 46 | | type | gas consumption | txn proof| 47 | | -------- | -------- | -------- | 48 | | Normal Transfer | 5,212,400 | [link](https://goerli.etherscan.io/tx/0x35549e3c4e4f2116515b3f4a2496ff8d2c455d2cc1a2fce3b97b193ef838e3cd) single txn | 49 | | Disperse | 2,754,920 | [link](https://goerli.etherscan.io/tx/0x9d20b73d7b102aacc63dadf01ed7767cbbfd1c3f92302b08f6741be4bd8fb6cf) | 50 | | BatchMoney | 2,694,098 ✅ | [link](https://goerli.etherscan.io/tx/0xdfd94600c57f72dc54e8741c084ab2e5544556e76baa0d6413b5189a6872f35a) | 51 | 52 | ### Transfer ERC20/ETH to 1,000 addresses in Foundry 53 | 54 | Compared to Dipserse, BatchMoney saves 80% on deployment and 2~3% on transfer ERC20/ETH to 1,000 addresses. 55 | 56 | ![](./img/FoundryTest.png) 57 | 58 | ## Quick Start 59 | 60 | 1. Clone this repo or use template 61 | 62 | ```shell 63 | git clone https://github.com/AmazingAng/BatchMoney 64 | cd BatchMoney 65 | ``` 66 | 67 | 2. Install dependencies 68 | 69 | ```shell 70 | forge install 71 | ``` 72 | 73 | 3. Build & Test 74 | 75 | ```shell 76 | forge build 77 | forge test 78 | ``` 79 | 80 | ## Details 81 | 82 | 1. Contracts: check `src` folder. 83 | 2. Tests: check `test` folder. 84 | 3. Frontend: check `frontend` folder. 85 | 4. Runtime bytecode: 86 | - evm-shanghai: `0x5f3560e01c8063ae4edb2c1461001e578063a0ce91d8146100be575f5ffd5b600435602435306323b872dd5f52336020528060405281606052602060606064601c5f875af13d156060516001141716610056575f80fd5b60443560040180356064356004018035828114610071575f80fd5b600163a9059cbb5f525b8181116100bc578481602002013560205282816020020135604052602060606044601c5f8c5af13d1560605160011417166100b4575f80fd5b60010161007b565b005b600435600401803560243560040180358281146100d9575f80fd5b60015b818111610107575f80808086856020020135898660200201355af16100ff575f80fd5b6001016100dc565b00` 87 | - evm-paris(`PUSH0` not available): `60003560e01c8063ae4edb2c14610021578063a0ce91d8146100c85760006000fd5b600435602435306323b872dd600052336020528060405281606052602060606064601c6000875af13d15606051600114171661005c57600080fd5b6044356004018035606435600401803582811461007857600080fd5b600163a9059cbb6000525b8181116100c6578481602002013560205282816020020135604052602060606044601c60008c5af13d1560605160011417166100be57600080fd5b600101610083565b005b600435600401803560243560040180358281146100e457600080fd5b60015b81811161011457600080808086856020020135898660200201355af161010c57600080fd5b6001016100e7565b00` 88 | 89 | ## Disclamer 90 | 91 | These contracts are **unaudited** and are not recommended for use in production. 92 | 93 | The contract is **experimental software** and is provided on an "as is" and "as available" basis. 94 | 95 | We **do not give any warranties** and **will not be liable for any loss** incurred through any use of this codebase and product. 96 | 97 | 98 | ## Reference 99 | 100 | 1. [disperse research](https://github.com/banteg/disperse-research) 101 | 2. [disperse clone](https://github.com/rajkharvar/disperse-clone) 102 | 3. [huffmate](https://github.com/huff-language/huffmate) 103 | 4. [TxRouter](https://github.com/wangshouh/TxRouter) 104 | -------------------------------------------------------------------------------- /bytecode/creationcode_evm_paris.txt: -------------------------------------------------------------------------------- 1 | 61011680600a3d393df360003560e01c8063ae4edb2c14610021578063a0ce91d8146100c85760006000fd5b600435602435306323b872dd600052336020528060405281606052602060606064601c6000875af13d15606051600114171661005c57600080fd5b6044356004018035606435600401803582811461007857600080fd5b600163a9059cbb6000525b8181116100c6578481602002013560205282816020020135604052602060606044601c60008c5af13d1560605160011417166100be57600080fd5b600101610083565b005b600435600401803560243560040180358281146100e457600080fd5b60015b81811161011457600080808086856020020135898660200201355af161010c57600080fd5b6001016100e7565b00 -------------------------------------------------------------------------------- /bytecode/creationcode_evm_shanghai.txt: -------------------------------------------------------------------------------- 1 | 61010980600a3d393df35f3560e01c8063ae4edb2c1461001e578063a0ce91d8146100be575f5ffd5b600435602435306323b872dd5f52336020528060405281606052602060606064601c5f875af13d156060516001141716610056575f80fd5b60443560040180356064356004018035828114610071575f80fd5b600163a9059cbb5f525b8181116100bc578481602002013560205282816020020135604052602060606044601c5f8c5af13d1560605160011417166100b4575f80fd5b60010161007b565b005b600435600401803560243560040180358281146100d9575f80fd5b60015b818111610107575f80808086856020020135898660200201355af16100ff575f80fd5b6001016100dc565b00 -------------------------------------------------------------------------------- /bytecode/runtimecode_evm_paris.txt: -------------------------------------------------------------------------------- 1 | 60003560e01c8063ae4edb2c14610021578063a0ce91d8146100c85760006000fd5b600435602435306323b872dd600052336020528060405281606052602060606064601c6000875af13d15606051600114171661005c57600080fd5b6044356004018035606435600401803582811461007857600080fd5b600163a9059cbb6000525b8181116100c6578481602002013560205282816020020135604052602060606044601c60008c5af13d1560605160011417166100be57600080fd5b600101610083565b005b600435600401803560243560040180358281146100e457600080fd5b60015b81811161011457600080808086856020020135898660200201355af161010c57600080fd5b6001016100e7565b00 -------------------------------------------------------------------------------- /bytecode/runtimecode_evm_shanghai.txt: -------------------------------------------------------------------------------- 1 | 5f3560e01c8063ae4edb2c1461001e578063a0ce91d8146100be575f5ffd5b600435602435306323b872dd5f52336020528060405281606052602060606064601c5f875af13d156060516001141716610056575f80fd5b60443560040180356064356004018035828114610071575f80fd5b600163a9059cbb5f525b8181116100bc578481602002013560205282816020020135604052602060606044601c5f8c5af13d1560605160011417166100b4575f80fd5b60010161007b565b005b600435600401803560243560040180358281146100d9575f80fd5b60015b818111610107575f80808086856020020135898660200201355af16100ff575f80fd5b6001016100dc565b00 -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | 2 | [profile.default] 3 | solc_version = '0.8.20' 4 | evm_version = 'shanghai' 5 | auto_detect_solc = false 6 | optimizer = true 7 | optimizer_runs = 200 # Default amount 8 | ffi = true 9 | fuzz_runs = 1_000 10 | remappings = [ 11 | "forge-std=lib/forge-std/src/", 12 | "foundry-huff=lib/foundry-huff/src/", 13 | "@openzeppelin=lib/openzeppelin-contracts/", 14 | ] 15 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | Batch Money 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "ethers": "^5.5.2", 11 | "react": "^17.0.0", 12 | "react-dom": "^17.0.0", 13 | "web3modal": "^1.9.4" 14 | }, 15 | "devDependencies": { 16 | "@vitejs/plugin-react": "^1.0.0", 17 | "autoprefixer": "^10.4.0", 18 | "postcss": "^8.4.4", 19 | "tailwindcss": "^2.2.19", 20 | "vite": "^2.6.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmazingAng/BatchMoney/e7b2f8261769855a511d04e54217cea9d85c740b/frontend/src/App.css -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { createContext, useEffect, useReducer, useState } from "react"; 3 | 4 | import "./App.css"; 5 | import Header from "./components/Headers"; 6 | import Payment from "./components/Payment"; 7 | import WalletInfo from "./components/WalletInfo"; 8 | import Warn from "./components/Warn"; 9 | import Web3Modal from "web3modal"; 10 | import Connect from "./components/Connect"; 11 | import { initState, reducer } from "./reducers/index"; 12 | import { getNetworkInfo, isChainSupported } from "./utils"; 13 | 14 | export const NetworkContext = createContext(); 15 | 16 | function App() { 17 | const [isMetamaskConnected, setIsMetamaskConnected] = useState(); 18 | const [isMetamaskInstalled, setIsMetamaskInstalled] = useState(); 19 | const [address, setAddress] = useState(null); 20 | const [isLoading, setIsLoading] = useState(true); 21 | const [state, dispatch] = useReducer(reducer, initState); 22 | 23 | useEffect(() => { 24 | if (window.ethereum) { 25 | window.ethereum.on("chainChanged", () => { 26 | window.location.reload(); 27 | }); 28 | window.ethereum.on("accountsChanged", () => { 29 | window.location.reload(); 30 | }); 31 | } 32 | }); 33 | 34 | const fetchNetworkDetails = async () => { 35 | try { 36 | const { ethereum } = window; 37 | const provider = new ethers.providers.Web3Provider(ethereum); 38 | const { chainId } = await provider.getNetwork(); 39 | const signer = provider.getSigner(); 40 | const address = await signer.getAddress(); 41 | 42 | if (!isChainSupported(chainId)) { 43 | dispatch({ type: "SET_NETWORK", payload: null }); 44 | } else { 45 | const networkInfo = getNetworkInfo(chainId); 46 | if (networkInfo) { 47 | dispatch({ type: "SET_NETWORK", payload: networkInfo.name }); 48 | } 49 | } 50 | 51 | dispatch({ type: "SET_CHAIN_ID", payload: chainId }); 52 | setAddress(address); 53 | setIsLoading(false); 54 | } catch (error) { 55 | console.log(error); 56 | } 57 | }; 58 | 59 | const checkAccountConnected = async () => { 60 | const provider = new ethers.providers.Web3Provider(ethereum); 61 | const accounts = await provider.listAccounts(); 62 | if (!accounts.length) { 63 | setIsMetamaskConnected(false); 64 | return; 65 | } 66 | setIsMetamaskConnected(true); 67 | }; 68 | 69 | const connect = async () => { 70 | try { 71 | const web3modal = new Web3Modal(); 72 | const result = await web3modal.connect(); 73 | if (result) { 74 | setIsMetamaskConnected(true); 75 | fetchNetworkDetails(); 76 | } 77 | } catch (error) { 78 | console.log(error); 79 | } 80 | }; 81 | 82 | useEffect(() => { 83 | const { ethereum } = window; 84 | if (ethereum) { 85 | setIsMetamaskInstalled(true); 86 | checkAccountConnected(); 87 | fetchNetworkDetails(ethereum); 88 | } else { 89 | setIsMetamaskInstalled(false); 90 | } 91 | }, []); 92 | return ( 93 |
94 | 100 |
101 | {isMetamaskInstalled ? ( 102 | !isMetamaskConnected && 103 | ) : ( 104 | 105 | )} 106 | {!isLoading && address && ( 107 | <> 108 | 109 | 110 | 111 | )} 112 | 113 |
114 | ); 115 | } 116 | 117 | export default App; 118 | -------------------------------------------------------------------------------- /frontend/src/artifacts/BatchTransfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "BatchTransfer", 4 | "sourceName": "contracts/BatchTransfer.huff", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "address", 10 | "name": "token", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "uint256", 15 | "name": "total", 16 | "type": "uint256" 17 | }, 18 | { 19 | "internalType": "address[]", 20 | "name": "recipients", 21 | "type": "address[]" 22 | }, 23 | { 24 | "internalType": "uint256[]", 25 | "name": "amounts", 26 | "type": "uint256[]" 27 | } 28 | ], 29 | "name": "batchTransferERC20", 30 | "outputs": [], 31 | "stateMutability": "nonpayable", 32 | "type": "function" 33 | }, 34 | { 35 | "inputs": [ 36 | { 37 | "internalType": "address[]", 38 | "name": "recipients", 39 | "type": "address[]" 40 | }, 41 | { 42 | "internalType": "uint256[]", 43 | "name": "amounts", 44 | "type": "uint256[]" 45 | } 46 | ], 47 | "name": "batchTransferETH", 48 | "outputs": [], 49 | "stateMutability": "payable", 50 | "type": "function" 51 | } 52 | ], 53 | "bytecode": "61010980600a3d393df35f3560e01c8063ae4edb2c1461001e578063a0ce91d8146100be575f5ffd5b600435602435306323b872dd5f52336020528060405281606052602060606064601c5f875af13d156060516001141716610056575f80fd5b60443560040180356064356004018035828114610071575f80fd5b600163a9059cbb5f525b8181116100bc578481602002013560205282816020020135604052602060606044601c5f8c5af13d1560605160011417166100b4575f80fd5b60010161007b565b005b600435600401803560243560040180358281146100d9575f80fd5b60015b818111610107575f80808086856020020135898660200201355af16100ff575f80fd5b6001016100dc565b00", 54 | "deployedBytecode": "5f3560e01c8063ae4edb2c1461001e578063a0ce91d8146100be575f5ffd5b600435602435306323b872dd5f52336020528060405281606052602060606064601c5f875af13d156060516001141716610056575f80fd5b60443560040180356064356004018035828114610071575f80fd5b600163a9059cbb5f525b8181116100bc578481602002013560205282816020020135604052602060606044601c5f8c5af13d1560605160011417166100b4575f80fd5b60010161007b565b005b600435600401803560243560040180358281146100d9575f80fd5b60015b818111610107575f80808086856020020135898660200201355af16100ff575f80fd5b6001016100dc565b00", 55 | "linkReferences": {}, 56 | "deployedLinkReferences": {} 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/artifacts/ERC20.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "ERC20", 4 | "sourceName": "@openzeppelin/contracts/token/ERC20/ERC20.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "string", 10 | "name": "name_", 11 | "type": "string" 12 | }, 13 | { 14 | "internalType": "string", 15 | "name": "symbol_", 16 | "type": "string" 17 | } 18 | ], 19 | "stateMutability": "nonpayable", 20 | "type": "constructor" 21 | }, 22 | { 23 | "anonymous": false, 24 | "inputs": [ 25 | { 26 | "indexed": true, 27 | "internalType": "address", 28 | "name": "owner", 29 | "type": "address" 30 | }, 31 | { 32 | "indexed": true, 33 | "internalType": "address", 34 | "name": "spender", 35 | "type": "address" 36 | }, 37 | { 38 | "indexed": false, 39 | "internalType": "uint256", 40 | "name": "value", 41 | "type": "uint256" 42 | } 43 | ], 44 | "name": "Approval", 45 | "type": "event" 46 | }, 47 | { 48 | "anonymous": false, 49 | "inputs": [ 50 | { 51 | "indexed": true, 52 | "internalType": "address", 53 | "name": "from", 54 | "type": "address" 55 | }, 56 | { 57 | "indexed": true, 58 | "internalType": "address", 59 | "name": "to", 60 | "type": "address" 61 | }, 62 | { 63 | "indexed": false, 64 | "internalType": "uint256", 65 | "name": "value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "Transfer", 70 | "type": "event" 71 | }, 72 | { 73 | "inputs": [ 74 | { 75 | "internalType": "address", 76 | "name": "owner", 77 | "type": "address" 78 | }, 79 | { 80 | "internalType": "address", 81 | "name": "spender", 82 | "type": "address" 83 | } 84 | ], 85 | "name": "allowance", 86 | "outputs": [ 87 | { 88 | "internalType": "uint256", 89 | "name": "", 90 | "type": "uint256" 91 | } 92 | ], 93 | "stateMutability": "view", 94 | "type": "function" 95 | }, 96 | { 97 | "inputs": [ 98 | { 99 | "internalType": "address", 100 | "name": "spender", 101 | "type": "address" 102 | }, 103 | { 104 | "internalType": "uint256", 105 | "name": "amount", 106 | "type": "uint256" 107 | } 108 | ], 109 | "name": "approve", 110 | "outputs": [ 111 | { 112 | "internalType": "bool", 113 | "name": "", 114 | "type": "bool" 115 | } 116 | ], 117 | "stateMutability": "nonpayable", 118 | "type": "function" 119 | }, 120 | { 121 | "inputs": [ 122 | { 123 | "internalType": "address", 124 | "name": "account", 125 | "type": "address" 126 | } 127 | ], 128 | "name": "balanceOf", 129 | "outputs": [ 130 | { 131 | "internalType": "uint256", 132 | "name": "", 133 | "type": "uint256" 134 | } 135 | ], 136 | "stateMutability": "view", 137 | "type": "function" 138 | }, 139 | { 140 | "inputs": [], 141 | "name": "decimals", 142 | "outputs": [ 143 | { 144 | "internalType": "uint8", 145 | "name": "", 146 | "type": "uint8" 147 | } 148 | ], 149 | "stateMutability": "view", 150 | "type": "function" 151 | }, 152 | { 153 | "inputs": [ 154 | { 155 | "internalType": "address", 156 | "name": "spender", 157 | "type": "address" 158 | }, 159 | { 160 | "internalType": "uint256", 161 | "name": "subtractedValue", 162 | "type": "uint256" 163 | } 164 | ], 165 | "name": "decreaseAllowance", 166 | "outputs": [ 167 | { 168 | "internalType": "bool", 169 | "name": "", 170 | "type": "bool" 171 | } 172 | ], 173 | "stateMutability": "nonpayable", 174 | "type": "function" 175 | }, 176 | { 177 | "inputs": [ 178 | { 179 | "internalType": "address", 180 | "name": "spender", 181 | "type": "address" 182 | }, 183 | { 184 | "internalType": "uint256", 185 | "name": "addedValue", 186 | "type": "uint256" 187 | } 188 | ], 189 | "name": "increaseAllowance", 190 | "outputs": [ 191 | { 192 | "internalType": "bool", 193 | "name": "", 194 | "type": "bool" 195 | } 196 | ], 197 | "stateMutability": "nonpayable", 198 | "type": "function" 199 | }, 200 | { 201 | "inputs": [], 202 | "name": "name", 203 | "outputs": [ 204 | { 205 | "internalType": "string", 206 | "name": "", 207 | "type": "string" 208 | } 209 | ], 210 | "stateMutability": "view", 211 | "type": "function" 212 | }, 213 | { 214 | "inputs": [], 215 | "name": "symbol", 216 | "outputs": [ 217 | { 218 | "internalType": "string", 219 | "name": "", 220 | "type": "string" 221 | } 222 | ], 223 | "stateMutability": "view", 224 | "type": "function" 225 | }, 226 | { 227 | "inputs": [], 228 | "name": "totalSupply", 229 | "outputs": [ 230 | { 231 | "internalType": "uint256", 232 | "name": "", 233 | "type": "uint256" 234 | } 235 | ], 236 | "stateMutability": "view", 237 | "type": "function" 238 | }, 239 | { 240 | "inputs": [ 241 | { 242 | "internalType": "address", 243 | "name": "recipient", 244 | "type": "address" 245 | }, 246 | { 247 | "internalType": "uint256", 248 | "name": "amount", 249 | "type": "uint256" 250 | } 251 | ], 252 | "name": "transfer", 253 | "outputs": [ 254 | { 255 | "internalType": "bool", 256 | "name": "", 257 | "type": "bool" 258 | } 259 | ], 260 | "stateMutability": "nonpayable", 261 | "type": "function" 262 | }, 263 | { 264 | "inputs": [ 265 | { 266 | "internalType": "address", 267 | "name": "sender", 268 | "type": "address" 269 | }, 270 | { 271 | "internalType": "address", 272 | "name": "recipient", 273 | "type": "address" 274 | }, 275 | { 276 | "internalType": "uint256", 277 | "name": "amount", 278 | "type": "uint256" 279 | } 280 | ], 281 | "name": "transferFrom", 282 | "outputs": [ 283 | { 284 | "internalType": "bool", 285 | "name": "", 286 | "type": "bool" 287 | } 288 | ], 289 | "stateMutability": "nonpayable", 290 | "type": "function" 291 | } 292 | ], 293 | "bytecode": "0x60806040523480156200001157600080fd5b506040516200171b3803806200171b833981810160405281019062000037919062000193565b81600390805190602001906200004f92919062000071565b5080600490805190602001906200006892919062000071565b50505062000376565b8280546200007f906200029b565b90600052602060002090601f016020900481019282620000a35760008555620000ef565b82601f10620000be57805160ff1916838001178555620000ef565b82800160010185558215620000ef579182015b82811115620000ee578251825591602001919060010190620000d1565b5b509050620000fe919062000102565b5090565b5b808211156200011d57600081600090555060010162000103565b5090565b60006200013862000132846200022f565b62000206565b9050828152602081018484840111156200015157600080fd5b6200015e84828562000265565b509392505050565b600082601f8301126200017857600080fd5b81516200018a84826020860162000121565b91505092915050565b60008060408385031215620001a757600080fd5b600083015167ffffffffffffffff811115620001c257600080fd5b620001d08582860162000166565b925050602083015167ffffffffffffffff811115620001ee57600080fd5b620001fc8582860162000166565b9150509250929050565b60006200021262000225565b9050620002208282620002d1565b919050565b6000604051905090565b600067ffffffffffffffff8211156200024d576200024c62000336565b5b620002588262000365565b9050602081019050919050565b60005b838110156200028557808201518184015260208101905062000268565b8381111562000295576000848401525b50505050565b60006002820490506001821680620002b457607f821691505b60208210811415620002cb57620002ca62000307565b5b50919050565b620002dc8262000365565b810181811067ffffffffffffffff82111715620002fe57620002fd62000336565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b61139580620003866000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006012905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea264697066735822122036881ea618e60d1c6a027b6c44365a9f280e729865bbe35641a70fdfe7c7bc8564736f6c63430008040033", 294 | "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006012905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea264697066735822122036881ea618e60d1c6a027b6c44365a9f280e729865bbe35641a70fdfe7c7bc8564736f6c63430008040033", 295 | "linkReferences": {}, 296 | "deployedLinkReferences": {} 297 | } 298 | -------------------------------------------------------------------------------- /frontend/src/assets/ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/Confirm.jsx: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { useState, useEffect, useContext } from "react"; 3 | import Status from "./Status"; 4 | 5 | const Confirm = ({ 6 | recipientsData, 7 | total, 8 | tokenBalance, 9 | decimals, 10 | remaining, 11 | approve, 12 | batchTransfer, 13 | txStatus, 14 | approveStatus, 15 | }) => { 16 | const [isDisabled, setIsDisabled] = useState(false); 17 | 18 | useEffect(() => { 19 | if (total && tokenBalance) { 20 | setIsDisabled(ethers.utils.parseUnits(tokenBalance, decimals).lt(total)); 21 | } 22 | }, [total, tokenBalance]); 23 | 24 | return ( 25 |
26 |

confirm

27 | 70 |
71 |

allowance

72 |
73 | 86 | {isDisabled &&

total exceeds balance

} 87 | {approveStatus && } 88 |
89 |
90 | 103 | {isDisabled &&

total exceeds balance

} 104 | {txStatus && } 105 |
106 |
107 |
108 | ); 109 | }; 110 | 111 | export default Confirm; 112 | -------------------------------------------------------------------------------- /frontend/src/components/ConfirmEther.jsx: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { useContext, useEffect, useState } from "react"; 3 | import { NetworkContext } from "../App"; 4 | import Status from "./Status"; 5 | 6 | const ConfirmEther = ({ 7 | recipientsData, 8 | total, 9 | tokenBalance, 10 | remaining, 11 | batchTransfer, 12 | txStatus, 13 | }) => { 14 | const [isDisabled, setIsDisabled] = useState(false); 15 | const { chainId } = useContext(NetworkContext); 16 | 17 | useEffect(() => { 18 | if (total && tokenBalance) { 19 | setIsDisabled(!ethers.utils.parseUnits(tokenBalance).gt(total)); 20 | } 21 | }, [total, tokenBalance]); 22 | 23 | return ( 24 |
25 |

confirm

26 | 69 | {total && tokenBalance && ( 70 |
71 |
72 | 85 | {isDisabled &&

total exceeds balance

} 86 | {txStatus && } 87 |
88 |
89 | )} 90 |
91 | ); 92 | }; 93 | 94 | export default ConfirmEther; 95 | -------------------------------------------------------------------------------- /frontend/src/components/Connect.jsx: -------------------------------------------------------------------------------- 1 | const Connect = ({ connect }) => { 2 | return ( 3 |
4 |

connect to wallet

5 | 15 |

please unlock metamask

16 |
17 | ); 18 | }; 19 | 20 | export default Connect; 21 | -------------------------------------------------------------------------------- /frontend/src/components/Ether.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from "react"; 2 | import { ethers } from "ethers"; 3 | 4 | import BatchTransfer from "../artifacts/BatchTransfer.json"; 5 | import { getNetworkInfo, parseText } from "../utils/index"; 6 | import Recipients from "./Recipients"; 7 | import ConfirmEther from "./ConfirmEther"; 8 | import { NetworkContext } from "../App"; 9 | 10 | const Ether = ({ address }) => { 11 | const [ethBalance, setEthBalance] = useState(null); 12 | const [textValue, setTextValue] = useState(""); 13 | const [total, setTotal] = useState(null); 14 | const [recipientsData, setRecipientsData] = useState([]); 15 | const [remaining, setRemaining] = useState(null); 16 | const { chainId } = useContext(NetworkContext); 17 | const [txStatus, setTxStatus] = useState(null); 18 | const networkInfo = getNetworkInfo(chainId); 19 | const batchTransferAddress = networkInfo?.batchTransferAddress; 20 | 21 | const getEthBalance = async () => { 22 | const { ethereum } = window; 23 | if (!ethBalance) { 24 | const provider = new ethers.providers.Web3Provider(ethereum); 25 | let ethBalance = await provider.getBalance(address); 26 | ethBalance = ethers.utils.formatEther(ethBalance); 27 | setEthBalance(ethBalance); 28 | } 29 | }; 30 | 31 | useEffect(() => { 32 | getEthBalance(); 33 | }, []); 34 | 35 | useEffect(() => { 36 | setRecipientsData(parseText(textValue)); 37 | }, [textValue]); 38 | 39 | useEffect(() => { 40 | if (recipientsData.length > 0) { 41 | let newTotal = recipientsData[0].value; 42 | for (let i = 1; i < recipientsData.length; i++) { 43 | newTotal = newTotal.add(recipientsData[i].value); 44 | } 45 | setTotal(newTotal); 46 | } else { 47 | setTotal(null); 48 | } 49 | }, [recipientsData]); 50 | 51 | const batchTransferETH = async () => { 52 | try { 53 | setTxStatus(null); 54 | const { ethereum } = window; 55 | if (ethereum && batchTransferAddress) { 56 | const provider = new ethers.providers.Web3Provider(ethereum); 57 | const signer = provider.getSigner(); 58 | const batchTransferContract = new ethers.Contract( 59 | batchTransferAddress, 60 | BatchTransfer.abi, 61 | signer 62 | ); 63 | 64 | const recipients = recipientsData.map((recipient) => recipient.address); 65 | const values = recipientsData.map((recipient) => recipient.value); 66 | 67 | console.log("Batch Transfer ETH now"); 68 | console.log(total); 69 | const txn = await batchTransferContract.batchTransferETH(recipients, values, { 70 | value: total, 71 | }); 72 | setTxStatus({ 73 | hash: txn.hash, 74 | status: "pending", 75 | }); 76 | await txn.wait(); 77 | setTxStatus({ 78 | hash: txn.hash, 79 | status: "success", 80 | }); 81 | console.log("Completed Batch Transfer ETH"); 82 | } 83 | } catch (error) { 84 | console.log("error occured while batch transfer ETH"); 85 | console.log(error); 86 | } 87 | }; 88 | 89 | useEffect(() => { 90 | if (ethBalance && total) { 91 | const tokenBalance = ethers.utils.parseEther(ethBalance); 92 | const remaining = tokenBalance.sub(total); 93 | setRemaining(ethers.utils.formatEther(remaining)); 94 | } else { 95 | setRemaining(null); 96 | } 97 | }, [total]); 98 | 99 | return ( 100 |

101 | you have {ethBalance} ETH 102 | 107 | {recipientsData.length > 0 && ( 108 | 116 | )} 117 |

118 | ); 119 | }; 120 | 121 | export default Ether; 122 | -------------------------------------------------------------------------------- /frontend/src/components/Headers.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { NetworkContext } from "../App"; 3 | import EthereumSVG from "../assets/ethereum.svg"; 4 | 5 | const Header = ({ address }) => { 6 | const networkContext = useContext(NetworkContext); 7 | return ( 8 |
9 |
10 | Ethereum SVG 19 |

Batch Money

20 | {address && ( 21 | 22 | {networkContext.network || "unsupported network"} 23 | 24 | )} 25 |
26 |
27 |

28 | Efficiently transfer ether or tokens in Batch. 29 |

30 |
31 | ); 32 | }; 33 | 34 | export default Header; 35 | -------------------------------------------------------------------------------- /frontend/src/components/Payment.jsx: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { useContext, useEffect, useState } from "react"; 3 | import ERC20 from "../artifacts/ERC20.json"; 4 | import BatchTransfer from "../artifacts/BatchTransfer.json"; 5 | import Confirm from "./Confirm"; 6 | import Recipients from "./Recipients"; 7 | import { NetworkContext } from "../App"; 8 | import { getNetworkInfo, parseText, getWarnMessage } from "../utils/index"; 9 | import Ether from "./Ether"; 10 | 11 | const Payment = ({ address }) => { 12 | const defaultTokenDetails = { 13 | name: null, 14 | symbol: null, 15 | balance: null, 16 | decimals: null, 17 | }; 18 | const { chainId } = useContext(NetworkContext); 19 | const [currentLink, setCurrentLink] = useState(null); 20 | const [ethBalance, setEthBalance] = useState(null); 21 | const [tokenAddress, setTokenAddress] = useState(""); 22 | const [tokenDetails, setTokenDetails] = useState(defaultTokenDetails); 23 | const [textValue, setTextValue] = useState(""); 24 | const [isTokenLoading, setIsTokenLoading] = useState(false); 25 | const [recipientsData, setRecipientsData] = useState([]); 26 | const [total, setTotal] = useState(null); 27 | const [remaining, setRemaining] = useState(null); 28 | const [warn, setWarn] = useState(null); 29 | const [txStatus, setTxStatus] = useState(null); 30 | const [approveStatus, setApproveStatus] = useState(null); 31 | const [isInvalidToken, setIsInvalidToken] = useState(false); 32 | const networkInfo = getNetworkInfo(chainId); 33 | const batchTransferAddress = networkInfo?.batchTransferAddress; 34 | 35 | const getEthBalance = async (ethereum) => { 36 | if (!ethBalance) { 37 | const provider = new ethers.providers.Web3Provider(ethereum); 38 | let ethBalance = await provider.getBalance(address); 39 | ethBalance = ethers.utils.formatEther(ethBalance); 40 | setEthBalance(ethBalance); 41 | } 42 | }; 43 | 44 | const loadToken = async () => { 45 | setIsInvalidToken(false); 46 | setTokenDetails(defaultTokenDetails); 47 | try { 48 | setIsTokenLoading(true); 49 | const { ethereum } = window; 50 | if (ethereum && tokenAddress !== "") { 51 | const provider = new ethers.providers.Web3Provider(window.ethereum); 52 | const signer = provider.getSigner(); 53 | const erc20 = new ethers.Contract(tokenAddress, ERC20.abi, signer); 54 | const name = await erc20.name(); 55 | const symbol = await erc20.symbol(); 56 | const balance = await erc20.balanceOf(address); 57 | const decimals = await erc20.decimals(); 58 | 59 | setTokenDetails({ 60 | name, 61 | symbol, 62 | balance: ethers.utils.formatUnits(balance, decimals), 63 | decimals: decimals, 64 | }); 65 | } 66 | 67 | if (!networkInfo) { 68 | const warnMessage = getWarnMessage(); 69 | setWarn(warnMessage); 70 | } 71 | } catch (error) { 72 | setIsInvalidToken(true); 73 | console.log(error); 74 | } finally { 75 | setIsTokenLoading(false); 76 | } 77 | }; 78 | 79 | useEffect(() => { 80 | const { ethereum } = window; 81 | if (ethereum) { 82 | getEthBalance(ethereum); 83 | } 84 | }, [currentLink]); 85 | 86 | const approve = async () => { 87 | setApproveStatus(null); 88 | try { 89 | const { ethereum } = window; 90 | if (ethereum && tokenAddress !== "" && total && batchTransferAddress) { 91 | const provider = new ethers.providers.Web3Provider(window.ethereum); 92 | const signer = provider.getSigner(); 93 | const erc20 = new ethers.Contract(tokenAddress, ERC20.abi, signer); 94 | 95 | const txn = await erc20.approve(batchTransferAddress, total); 96 | setApproveStatus({ 97 | status: "pending", 98 | hash: txn.hash, 99 | }); 100 | 101 | await txn.wait(); 102 | setApproveStatus({ 103 | status: "success", 104 | hash: txn.hash, 105 | }); 106 | } 107 | } catch (error) { 108 | console.log(error); 109 | } 110 | }; 111 | 112 | const batchTransfer = async () => { 113 | try { 114 | const { ethereum } = window; 115 | if ( 116 | ethereum && 117 | tokenAddress !== "" && 118 | recipientsData.length > 0 && 119 | batchTransferAddress 120 | ) { 121 | const provider = new ethers.providers.Web3Provider(window.ethereum); 122 | const signer = provider.getSigner(); 123 | 124 | const batchTransfer = new ethers.Contract( 125 | batchTransferAddress, 126 | BatchTransfer.abi, 127 | signer 128 | ); 129 | 130 | const recipients = recipientsData.map((recipient) => recipient.address); 131 | const values = recipientsData.map((recipient) => recipient.value); 132 | 133 | const txn = await batchTransfer.batchTransferERC20( 134 | tokenAddress, 135 | total, 136 | recipients, 137 | values 138 | ); 139 | setTxStatus({ 140 | status: "pending", 141 | hash: txn.hash, 142 | }); 143 | 144 | await txn.wait(); 145 | setTxStatus({ 146 | status: "success", 147 | hash: txn.hash, 148 | }); 149 | } 150 | } catch (error) { 151 | console.log(error); 152 | } 153 | }; 154 | 155 | useEffect(() => { 156 | if (textValue !== "") { 157 | if(tokenDetails.balance){ 158 | // erc20 159 | const updatedRecipients = parseText(textValue, tokenDetails.decimals); 160 | setRecipientsData(updatedRecipients); 161 | }else{ 162 | // ether 163 | const updatedRecipients = parseText(textValue); 164 | setRecipientsData(updatedRecipients); 165 | } 166 | } 167 | }, [textValue]); 168 | 169 | useEffect(() => { 170 | if (recipientsData.length > 0) { 171 | let newTotal = recipientsData[0].value; 172 | for (let i = 1; i < recipientsData.length; i++) { 173 | newTotal = newTotal.add(recipientsData[i].value); 174 | } 175 | setTotal(newTotal); 176 | } else { 177 | setTotal(null); 178 | } 179 | }, [recipientsData]); 180 | 181 | useEffect(() => { 182 | if (tokenDetails.balance && total) { 183 | const tokenBalance = ethers.utils.parseUnits(tokenDetails.balance, tokenDetails.decimals); 184 | const remaining = tokenBalance.sub(total); 185 | setRemaining(ethers.utils.formatUnits(remaining, tokenDetails.decimals)); 186 | } else { 187 | setRemaining(null); 188 | } 189 | }, [total]); 190 | 191 | return ( 192 |
193 |

194 | send 195 | setCurrentLink("ether")} 197 | className={`border-gray-600 border-b-2 ${ 198 | currentLink !== "ether" ? "text-gray-500" : "" 199 | }`} 200 | > 201 | {" "} 202 | ether{" "} 203 | 204 | or 205 | setCurrentLink("token")} 207 | className={`border-gray-600 border-b-2 ${ 208 | currentLink !== "token" ? "text-gray-500" : "" 209 | }`} 210 | > 211 | {" "} 212 | token 213 | 214 |

215 | 216 | {currentLink === "ether" && } 217 | {currentLink === "token" && ( 218 |
219 |

token address

220 |
221 | setTokenAddress(e.target.value)} 227 | style={{ 228 | width: "100%", 229 | background: "aquamarine", 230 | color: "#768882", 231 | }} 232 | /> 233 | 243 |
244 | {isTokenLoading && ( 245 |

246 | loading token info ... 247 |

248 | )} 249 | {isInvalidToken && ( 250 |

251 | unsupported token 252 |

253 | )} 254 | {!isTokenLoading && tokenDetails.name && ( 255 | <> 256 |

257 | you have {tokenDetails.balance}{" "} 258 | {tokenDetails.symbol} ( 259 | {tokenDetails.name}) 260 |

261 | {warn &&

{warn}

} 262 | {!warn && ( 263 | 268 | )} 269 | {recipientsData.length > 0 && ( 270 | 281 | )} 282 | 283 | )} 284 |
285 | )} 286 |
287 | ); 288 | }; 289 | 290 | export default Payment; 291 | -------------------------------------------------------------------------------- /frontend/src/components/Recipients.jsx: -------------------------------------------------------------------------------- 1 | const Recipients = ({ tokenSymbol, textValue, setTextValue }) => { 2 | return ( 3 |
4 |

recipients and amounts

5 |

6 | enter one address and amount in {tokenSymbol} on each line. supports any 7 | format. 8 |

9 | 23 |
24 | ); 25 | }; 26 | 27 | export default Recipients; 28 | -------------------------------------------------------------------------------- /frontend/src/components/Status.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { NetworkContext } from "../App"; 3 | import { getNetworkInfo } from "../utils"; 4 | 5 | const Status = ({ txnStatus }) => { 6 | const { chainId } = useContext(NetworkContext); 7 | const networkInfo = getNetworkInfo(chainId); 8 | 9 | return ( 10 |
15 |

20 | transaction {txnStatus.status} 21 |

22 | 27 | {txnStatus.hash} 28 | 29 |
30 | ); 31 | }; 32 | 33 | export default Status; 34 | -------------------------------------------------------------------------------- /frontend/src/components/WalletInfo.jsx: -------------------------------------------------------------------------------- 1 | const WalletInfo = ({ address, provider }) => { 2 | return ( 3 |
4 |

connect to wallet

5 |

logged in as {address}

6 |
7 | ); 8 | }; 9 | 10 | export default WalletInfo; 11 | -------------------------------------------------------------------------------- /frontend/src/components/Warn.jsx: -------------------------------------------------------------------------------- 1 | const Warn = () => { 2 | return ( 3 |
4 |

metamask required

5 |

6 | non-ethereum browser, consider installing metamask. 7 |

8 |
9 | ); 10 | }; 11 | 12 | export default Warn; 13 | -------------------------------------------------------------------------------- /frontend/src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | font-family: "Roboto"; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './App' 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /frontend/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | export const initState = { 2 | chainId: null, 3 | network: null, 4 | }; 5 | 6 | export const reducer = (state, action) => { 7 | switch (action.type) { 8 | case "SET_CHAIN_ID": { 9 | return { 10 | ...state, 11 | chainId: action.payload, 12 | }; 13 | } 14 | case "SET_NETWORK": { 15 | return { 16 | ...state, 17 | network: action.payload, 18 | }; 19 | } 20 | default: 21 | return state; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /frontend/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const supportedChains = [ 2 | { 3 | chainId: 1, 4 | batchTransferAddress: "0xD35a289c1D5F6f6a604d6026111109694e51BA25", 5 | blockExplorer: "https://etherscan.io/", 6 | name: "eth mainnet", 7 | }, 8 | { 9 | chainId: 5, 10 | batchTransferAddress: "0xDC7a1993196d63db926c3B2e1C42682f39885B96", 11 | blockExplorer: "https://goerli.etherscan.io/", 12 | name: "goerli", 13 | }, 14 | { 15 | chainId: 10, 16 | batchTransferAddress: "0x3484593c456D9C598C47754341718062318066Ba", 17 | blockExplorer: "https://optimistic.etherscan.io/", 18 | name: "optimism", 19 | }, 20 | { 21 | chainId: 56, 22 | batchTransferAddress: "0x0D69079B60484ae97EA7DEaad370B61f9Da401F8", 23 | blockExplorer: "https://bscscan.com/", 24 | name: "bnb chain", 25 | }, 26 | { 27 | chainId: 100, 28 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 29 | blockExplorer: "https://gnosis.blockscout.com/", 30 | name: "gnosis", 31 | }, 32 | { 33 | chainId: 137, 34 | batchTransferAddress: "0x927ec65329636525a5B00103De1c00d8Da9b08aD", 35 | blockExplorer: "https://polygonscan.com/", 36 | name: "polygon", 37 | }, 38 | { 39 | chainId: 204, 40 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 41 | blockExplorer: "https://opbnbscan.com/", 42 | name: "opbnb", 43 | }, 44 | { 45 | chainId: 424, 46 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 47 | blockExplorer: "https://explorer.publicgoods.network/", 48 | name: "public goods network", 49 | }, 50 | { 51 | chainId: 1101, 52 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 53 | blockExplorer: "https://zkevm.polygonscan.com/", 54 | name: "polygon zkevm", 55 | }, 56 | { 57 | chainId: 8453, 58 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 59 | blockExplorer: "https://basescan.org/", 60 | name: "base", 61 | }, 62 | { 63 | chainId: 12553, 64 | batchTransferAddress: "0xC44c86E0Aaa90d914b194c347aaF9cf97823E3C8", 65 | blockExplorer: "https://scan.rss3.io/", 66 | name: "rss3 vsl mainnet", 67 | }, 68 | { 69 | chainId: 17000, 70 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 71 | blockExplorer: "https://holesky.etherscan.io/", 72 | name: "holesky", 73 | }, 74 | { 75 | chainId: 42161, 76 | batchTransferAddress: "0x63d9C12865336322Ca981E5d1392acde4fAdD3Dc", 77 | blockExplorer: "https://arbiscan.io/", 78 | name: "arbitrum one", 79 | }, 80 | { 81 | chainId: 59144, 82 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 83 | blockExplorer: "https://lineascan.build/", 84 | name: "linea", 85 | }, 86 | { 87 | chainId: 534351, 88 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 89 | blockExplorer: "https://sepolia-blockscout.scroll.io/", 90 | name: "scroll sepolia", 91 | }, 92 | { 93 | chainId: 534352, 94 | batchTransferAddress: "0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d", 95 | blockExplorer: "https://blockscout.scroll.io/", 96 | name: "scroll", 97 | }, 98 | { 99 | chainId: 11155111, 100 | batchTransferAddress: "0x800A2bE9B6259E252eDE4a5a041C23ab994F2962", 101 | blockExplorer: "https://sepolia.etherscan.io/", 102 | name: "sepolia", 103 | }, 104 | { 105 | chainId: 4200, 106 | batchTransferAddress: "0x6e4e372baF0C3Ba74CdA7E7E6Ef4bF526A209Ec2", 107 | blockExplorer: "https://scan.merlinchain.io/", 108 | name: "merlin chain", 109 | }, 110 | ]; 111 | 112 | 0x1b285Ffd0B19805947E7F285A922d2fFe5c4FE4d 113 | -------------------------------------------------------------------------------- /frontend/src/utils/index.js: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { supportedChains } from "./constants"; 3 | 4 | export const isValidAddress = (address) => ethers.utils.isAddress(address); 5 | 6 | export const isValidValue = (value, decimals) => { 7 | try { 8 | return ethers.utils.parseUnits(value, decimals); 9 | } catch (err) { 10 | return false; 11 | } 12 | }; 13 | 14 | export const isChainSupported = (chainId) => 15 | supportedChains.some((chain) => chain.chainId === chainId); 16 | 17 | export const getNetworkInfo = (chainId) => 18 | supportedChains.find((chain) => chain.chainId === chainId); 19 | 20 | export const getWarnMessage = () => { 21 | let networks = ``; 22 | supportedChains.map((chain, i) => { 23 | if (i === 0) { 24 | networks += `${chain.name}`; 25 | } else if (i === supportedChains.length - 1) { 26 | networks += ` and ${chain.name}`; 27 | } else { 28 | networks += `, ${chain.name}`; 29 | } 30 | }); 31 | return `*Supports ${networks}*`; 32 | }; 33 | 34 | export const parseText = (textValue, decimals = 18) => { 35 | const lines = textValue.split("\n"); 36 | let updatedRecipients = []; 37 | 38 | lines.map((line) => { 39 | if ( 40 | line.includes(" ") || 41 | line.includes(",") || 42 | line.includes("=") || 43 | line.includes("\t") 44 | ) { 45 | const [address, value] = line.split(/[,= \t]+/); 46 | const validValue = isValidValue(value, decimals); 47 | if (isValidAddress(address) && validValue) { 48 | updatedRecipients.push({ 49 | address, 50 | value: validValue, 51 | }); 52 | } 53 | } 54 | }); 55 | 56 | return updatedRecipients; 57 | }; 58 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | opacity: ({ after }) => after(["disabled"]), 9 | cursor: ({ after }) => after(["disabled"]), 10 | extend: {}, 11 | }, 12 | plugins: [], 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /funding.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0x353f39586dfd543ced6534f59ff9f221d6c6e128f2bb95b153afce433d333faf" 4 | } 5 | } -------------------------------------------------------------------------------- /img/FoundryTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmazingAng/BatchMoney/e7b2f8261769855a511d04e54217cea9d85c740b/img/FoundryTest.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmazingAng/BatchMoney/e7b2f8261769855a511d04e54217cea9d85c740b/img/logo.png -------------------------------------------------------------------------------- /interface/IBatchTransfer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | interface IBatchTransfer { 5 | function batchTransferERC20(address token, uint256 total, address[] memory recipients, uint256[] memory amounts) external; 6 | function batchTransferETH(address[] memory recipients, uint256[] memory amounts) external payable; 7 | } 8 | -------------------------------------------------------------------------------- /src/BatchTransfer.huff: -------------------------------------------------------------------------------- 1 | // Batch Transfer v1 2 | /* Interface */ 3 | #define function batchTransferERC20(address token, uint256 total, address[] recipients, uint256[] amounts) nonpayable returns () 4 | #define function batchTransferETH(address[] recipients, uint256[] amounts) payable returns () 5 | #define function transfer(address,uint256) nonpayable returns (bool) 6 | #define function transferFrom(address,address,uint256) nonpayable returns () 7 | 8 | /* Method */ 9 | #define macro BATCH_TRANSFER_ERC20() = takes (0) returns (0) { 10 | // calldata: 11 | // 0x00: sig | 0x04: token | 0x24: total | 0x44: recipients[] | 0x64: amounts[] 12 | 13 | // 1. safeTransfer total amount of token from sender to this contract 14 | 0x04 calldataload // [token] 15 | 0x24 calldataload // [total, token] 16 | address // [this, total, token] 17 | // store args in memory 18 | __FUNC_SIG(transferFrom) 0x00 mstore // [this, total, token] memory: [0x00: sig] 19 | caller 0x20 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this] 20 | dup1 0x40 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this] 21 | dup2 0x60 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this, 0x60: total] 22 | // call transferFrom 23 | // return size is 0x20, return offset is 0x60 24 | // arg size is 0x64, arg offset if 0x1c (where sig start) 25 | 0x20 // [ret_size, this, total, token] 26 | 0x60 // [ret_offset, ret_size, this, total, token] 27 | 0x64 // [args_size, ret_offset, ret_size, this, total, token] 28 | 0x1c // [args_offset, args_size, ret_offset, ret_size, this, total, token] 29 | 0x00 // [value, args_offset, args_size, ret_offset, ret_size, this, total, token] 30 | dup8 // [to, value, args_offset, args_size, ret_offset, ret_size, this, total, token] 31 | gas // [gas, to, value, args_offset, args_size, ret_offset, ret_size, this, total, token] 32 | call // [success, this, total, token] 33 | // safeTransferFrom ERC20 token 34 | returndatasize iszero // [returndatasize == 0, success, this, total, token] 35 | 0x60 // [ret_offset, returndatasize == 0, success, this, total, token] 36 | mload // [ret_data, returndatasize == 0, success, this, total, token] 37 | 0x01 eq // [ret_data == true, returndatasize == 0, success] 38 | or // [ret_data == true | returndatasize == 0, success, this, total, token] 39 | and // [success & (data == 0x01 | returndatasize == 0), this, total, token] 40 | transferFrom_success jumpi // [this, total, token] 41 | // revert if call failed 42 | 0x00 dup1 revert // 43 | // transferFrom sucess 44 | transferFrom_success: // [this, total, token] 45 | 46 | // 2. batch transfer from this contract to recipients in a loop 47 | // read recipients[] offset and length 48 | 0x44 calldataload 0x04 add // [rcp_offset, this, total, token] 49 | dup1 calldataload // [rcp_len, rcp_offset, this, total, token] 50 | // read amounts[] offset and length 51 | 0x64 calldataload 0x04 add // [amt_offset, rcp_len, rcp_offset, this, total, token] 52 | dup1 calldataload // [amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 53 | // check if length are equal 54 | dup3 dup2 eq // [amt_len==rcp_len?, amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 55 | arr_len_success jumpi // [amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 56 | // revert if call failed 57 | 0x00 dup1 revert 58 | // if amt_len==rcp_len 59 | arr_len_success: 60 | 0x01 // [index(1), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 61 | // store transfer sig in memory 62 | __FUNC_SIG(transfer) 0x00 mstore // memory: [0x00: sig] 63 | 64 | // Loop 65 | loop: 66 | // chech (index>len), if true, jump to end. 67 | dup2 dup2 gt end jumpi // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 68 | // read recipients[i-1], store to memory 0x20 69 | dup5 dup2 0x20 mul add calldataload 0x20 mstore // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 70 | // read amounts[i-1], store to memory 0x40 71 | dup3 dup2 0x20 mul add calldataload 0x40 mstore // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 72 | 73 | // call transfer 74 | // return size is 0x20, return offset is 0x60 75 | // arg size is 0x64, arg offset if 0x1c (where sig start) 76 | 0x20 // [ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 77 | 0x60 // [ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 78 | 0x44 // [args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 79 | 0x1c // [args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 80 | 0x00 // [value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 81 | dup13 // [to, value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 82 | gas // [gas, to, value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 83 | call // [success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 84 | 85 | // safeTransfer ERC20 token 86 | returndatasize iszero // [returndatasize == 0, success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 87 | 0x60 // [ret_offset, returndatasize == 0, success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 88 | mload // [ret_data, returndatasize == 0, success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 89 | 0x01 eq // [ret_data == true, returndatasize == 0, successindex(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 90 | or // [ret_data == true | returndatasize == 0, success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 91 | and // [success & (data == 0x01 | returndatasize == 0), index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 92 | transfer_success jumpi // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 93 | // revert if call failed 94 | 0x00 dup1 revert // 95 | // transferF sucess 96 | transfer_success: // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 97 | // update i 98 | 0x01 add // [index(i+1), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token] 99 | // continue loop 100 | loop jump 101 | 102 | // end of the loop, stop 103 | end: 104 | stop 105 | } 106 | 107 | #define macro BATCH_TRANSFER_ETH() = takes (0) returns (0) { 108 | // calldata: 109 | // 0x00: sig | 0x04: recipients[] | 0x24: amounts[] 110 | 111 | // 1. batch transfer ETH from this contract to recipients in a loop 112 | // read recipients[] offset and length 113 | 0x04 calldataload 0x04 add // [rcp_offset] 114 | dup1 calldataload // [rcp_len, rcp_offset] 115 | // read amounts[] offset and length 116 | 0x24 calldataload 0x04 add // [amt_offset, rcp_len, rcp_offset] 117 | dup1 calldataload // [amt_len, amt_offset, rcp_len, rcp_offset] 118 | // check if length are equal 119 | dup3 dup2 eq // [amt_len==rcp_len?, amt_len, amt_offset, rcp_len, rcp_offset] 120 | arr_len_success jumpi // [amt_len, amt_offset, rcp_len, rcp_offset] 121 | // revert if call failed 122 | 0x00 dup1 revert 123 | // if amt_len==rcp_len 124 | arr_len_success: 125 | 0x01 // [index(1), amt_len, amt_offset, rcp_len, rcp_offset] 126 | 127 | // loop 128 | loop: 129 | // chech (index>len), if true, jump to end. 130 | dup2 dup2 gt end jumpi // [index(i), amt_len, amt_offset, rcp_len, rcp_offset] 131 | 132 | // call transfer 133 | 0x00 // [ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset] 134 | dup1 // [ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset] 135 | dup1 // [args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset] 136 | dup1 // [args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset] 137 | dup7 dup6 0x20 mul add calldataload // [value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset] 138 | dup10 dup7 0x20 mul add calldataload // [to, value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset] 139 | gas // [gas, to, value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset] 140 | call // [success, index(i), amt_len, amt_offset, rcp_len, rcp_offset] 141 | 142 | // safeTransfer ERC20 token 143 | transfer_success jumpi // [index(i), amt_len, amt_offset, rcp_len, rcp_offset] 144 | // revert if call failed 145 | 0x00 dup1 revert // 146 | // transferF sucess 147 | transfer_success: // [index(i), amt_len, amt_offset, rcp_len, rcp_offset] 148 | // update i 149 | 0x01 add // [index(i+1), amt_len, amt_offset, rcp_len, rcp_offset] 150 | // continue loop 151 | loop jump 152 | 153 | // end of the loop, stop 154 | end: 155 | stop 156 | } 157 | 158 | #define macro MAIN() = takes (0) returns (0) { 159 | // call function according to the selector 160 | 0x00 calldataload 0xE0 shr 161 | dup1 __FUNC_SIG(batchTransferERC20) eq batch_transfer_erc20 jumpi 162 | dup1 __FUNC_SIG(batchTransferETH) eq batch_transfer_eth jumpi 163 | 164 | // if no matched function, revert 165 | 0x00 0x00 revert 166 | 167 | batch_transfer_erc20: 168 | BATCH_TRANSFER_ERC20() 169 | batch_transfer_eth: 170 | BATCH_TRANSFER_ETH() 171 | } 172 | -------------------------------------------------------------------------------- /test/BatchTransferTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | import "forge-std/console.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 8 | 9 | contract BatchTransferTest is Test { 10 | IBatchTransfer iBatch; 11 | 12 | MyToken token0; 13 | MyToken token1; 14 | MyToken token2; 15 | 16 | Disperse disperse; 17 | address address0 = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045; 18 | address[] recipients; 19 | uint[] amounts; 20 | uint total; 21 | 22 | /// @dev Setup the testing environment. 23 | function setUp() public { 24 | token0 = new MyToken("Token","TKN"); 25 | token1 = new MyToken("Token","TKN"); 26 | token2 = new MyToken("Token","TKN"); 27 | iBatch = IBatchTransfer(HuffDeployer.deploy("BatchTransfer")); 28 | disperse = new Disperse(); 29 | 30 | for(uint i; i < 1000; i++){ 31 | recipients.push(address(uint160(address0)+uint160(i))); 32 | amounts.push(i); 33 | total += i; 34 | } 35 | vm.deal(address0, 1000 ether); 36 | 37 | token0.mint(address0, total * 10); 38 | token1.mint(address0, total * 10); 39 | token2.mint(address0, total * 10); 40 | vm.startPrank(address0); 41 | token0.approve(address(iBatch), total); 42 | token1.approve(address(disperse), total); 43 | vm.stopPrank(); 44 | } 45 | 46 | function testBatchTransferDeploy() public { 47 | deploy(hex"61010980600a3d393df35f3560e01c8063ae4edb2c1461001e578063a0ce91d8146100be575f5ffd5b600435602435306323b872dd5f52336020528060405281606052602060606064601c5f875af13d156060516001141716610056575f80fd5b60443560040180356064356004018035828114610071575f80fd5b600163a9059cbb5f525b8181116100bc578481602002013560205282816020020135604052602060606044601c5f8c5af13d1560605160011417166100b4575f80fd5b60010161007b565b005b600435600401803560243560040180358281146100d9575f80fd5b60015b818111610107575f80808086856020020135898660200201355af16100ff575f80fd5b6001016100dc565b00"); 48 | } 49 | 50 | function testBatchTransferETH() public { 51 | vm.prank(address0); 52 | //console.log(address0.balance); 53 | iBatch.batchTransferETH{value: total}(recipients, amounts); 54 | } 55 | 56 | function testBatchTransferERC20() public { 57 | vm.prank(address0); 58 | //console.log(address0.balance); 59 | iBatch.batchTransferERC20(address(token0), total, recipients, amounts); 60 | } 61 | 62 | function testDisperseDeploy() public { 63 | deploy(type(Disperse).creationCode); 64 | } 65 | 66 | function testDisperseETH() public { 67 | vm.prank(address0); 68 | //console.log(address0.balance); 69 | disperse.disperseEther{value: total}(recipients, amounts); 70 | } 71 | 72 | function testDisperseERC20() public { 73 | vm.prank(address0); 74 | //console.log(address0.balance); 75 | disperse.disperseToken(IERC20(address(token1)), recipients, amounts); 76 | } 77 | 78 | function testSingleTransferERC20() public{ 79 | vm.prank(address0); 80 | token2.transfer(recipients[100],1); 81 | } 82 | 83 | function deploy(bytes memory code) public payable returns (address addr) { 84 | assembly { 85 | // create(v, p, n) 86 | // v = amount of ETH to send 87 | // p = pointer in memory to start of code 88 | // n = size of code 89 | addr := create(callvalue(), add(code, 0x20), mload(code)) 90 | } 91 | // return address 0 on error 92 | require(addr != address(0), "deploy failed"); 93 | } 94 | } 95 | 96 | interface IBatchTransfer { 97 | function batchTransferERC20(address token, uint256 total, address[] memory recipients, uint256[] memory amounts) external; 98 | function batchTransferETH(address[] memory recipients, uint256[] memory amounts) external payable; 99 | } 100 | 101 | contract MyToken is ERC20{ 102 | constructor (string memory _name, string memory _symbol) ERC20 (_name,_symbol){ 103 | } 104 | 105 | function mint(address to, uint256 amount) public virtual { 106 | _mint(to,amount); 107 | } 108 | 109 | function burn(address form, uint amount) public virtual { 110 | _burn(form, amount); 111 | } 112 | } 113 | 114 | contract Disperse { 115 | function disperseEther(address[] memory recipients, uint256[] memory values) external payable { 116 | for (uint256 i = 0; i < recipients.length; i++){ 117 | payable(recipients[i]).transfer(values[i]); 118 | } 119 | uint256 balance = address(this).balance; 120 | if (balance > 0) 121 | payable(msg.sender).transfer(balance); 122 | } 123 | 124 | function disperseToken(IERC20 token, address[] memory recipients, uint256[] memory values) external { 125 | uint256 total = 0; 126 | for (uint256 i = 0; i < recipients.length; i++){ 127 | total += values[i]; 128 | } 129 | require(token.transferFrom(msg.sender, address(this), total)); 130 | for (uint256 i = 0; i < recipients.length; i++){ 131 | require(token.transfer(recipients[i], values[i])); 132 | } 133 | } 134 | 135 | function disperseTokenSimple(IERC20 token, address[] memory recipients, uint256[] memory values) external { 136 | for (uint256 i = 0; i < recipients.length; i++) 137 | require(token.transferFrom(msg.sender, recipients[i], values[i])); 138 | } 139 | } -------------------------------------------------------------------------------- /test/USDTTransferTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | import "forge-std/console.sol"; 7 | 8 | 9 | contract USDTTransferTest is Test { 10 | IBatchTransfer iBatch; 11 | USDT usdt; 12 | 13 | address address0 = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045; 14 | address[] recipients; 15 | uint[] amounts; 16 | uint total; 17 | 18 | /// @dev Setup the testing environment. 19 | function setUp() public { 20 | iBatch = IBatchTransfer(HuffDeployer.deploy("BatchTransfer")); 21 | usdt = new USDT("USDT","USDT"); 22 | 23 | for(uint i; i < 1000; i++){ 24 | recipients.push(address(uint160(address0)+uint160(i))); 25 | amounts.push(i); 26 | total += i; 27 | } 28 | vm.deal(address0, 1000 ether); 29 | usdt.mint(address0, total); 30 | } 31 | 32 | 33 | function testBatchTransferUSDT() public { 34 | vm.startPrank(address0); 35 | usdt.approve(address(iBatch), total); 36 | iBatch.batchTransferERC20(address(usdt), total, recipients, amounts); 37 | vm.stopPrank(); 38 | } 39 | } 40 | 41 | interface IBatchTransfer { 42 | function batchTransferERC20(address token, uint256 total, address[] memory recipients, uint256[] memory amounts) external; 43 | function batchTransferETH(address[] memory recipients, uint256[] memory amounts) external payable; 44 | } 45 | 46 | // no return data from transfer/transferFrom/approve 47 | interface IUSDT { 48 | event Transfer(address indexed from, address indexed to, uint256 value); 49 | event Approval(address indexed owner, address indexed spender, uint256 value); 50 | 51 | function totalSupply() external view returns (uint256); 52 | function balanceOf(address account) external view returns (uint256); 53 | function transfer(address to, uint256 amount) external; 54 | function allowance(address owner, address spender) external view returns (uint256); 55 | function approve(address spender, uint256 amount) external; 56 | function transferFrom(address from, address to, uint256 amount) external; 57 | } 58 | 59 | contract USDT is IUSDT { 60 | 61 | mapping(address => uint256) public override balanceOf; 62 | 63 | mapping(address => mapping(address => uint256)) public override allowance; 64 | 65 | uint256 public override totalSupply; // 代币总供给 66 | 67 | string public name; // 名称 68 | string public symbol; // 符号 69 | 70 | uint8 public decimals = 18; // 小数位数 71 | 72 | // @dev 在合约部署的时候实现合约名称和符号 73 | constructor(string memory name_, string memory symbol_){ 74 | name = name_; 75 | symbol = symbol_; 76 | } 77 | 78 | // @dev 实现`transfer`函数,代币转账逻辑 79 | function transfer(address recipient, uint amount) external override { 80 | balanceOf[msg.sender] -= amount; 81 | balanceOf[recipient] += amount; 82 | emit Transfer(msg.sender, recipient, amount); 83 | } 84 | 85 | // @dev 实现 `approve` 函数, 代币授权逻辑 86 | function approve(address spender, uint amount) external override{ 87 | allowance[msg.sender][spender] = amount; 88 | emit Approval(msg.sender, spender, amount); 89 | } 90 | 91 | // @dev 实现`transferFrom`函数,代币授权转账逻辑 92 | function transferFrom( 93 | address sender, 94 | address recipient, 95 | uint amount 96 | ) external override{ 97 | allowance[sender][msg.sender] -= amount; 98 | balanceOf[sender] -= amount; 99 | balanceOf[recipient] += amount; 100 | emit Transfer(sender, recipient, amount); 101 | } 102 | 103 | // @dev 铸造代币,从 `0` 地址转账给 调用者地址 104 | function mint(address to, uint amount) external { 105 | balanceOf[to] += amount; 106 | totalSupply += amount; 107 | emit Transfer(address(0), to, amount); 108 | } 109 | 110 | // @dev 销毁代币,从 调用者地址 转账给 `0` 地址 111 | function burn(address from, uint amount) external { 112 | balanceOf[from] -= amount; 113 | totalSupply -= amount; 114 | emit Transfer(from, address(0), amount); 115 | } 116 | 117 | } --------------------------------------------------------------------------------