├── .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 | 
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 |
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 |
28 | -
29 |
30 |
address
31 |
amount
32 |
33 |
34 | {recipientsData.length > 0 &&
35 | recipientsData.map((recipient) => (
36 | -
37 |
38 |
{recipient.address}
39 | {/* TODO: Add Horizontal line here */}
40 |
41 |
{ethers.utils.formatUnits(recipient.value, decimals)}
42 |
43 |
44 | ))}
45 | -
46 |
47 |
total
48 |
49 | {total ? ethers.utils.formatUnits(total, decimals) : ""}
50 |
51 |
52 |
53 | -
54 |
55 |
your balance
56 |
{tokenBalance}
57 |
58 |
59 | -
60 |
65 |
remaining
66 |
{remaining}
67 |
68 |
69 |
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 |
27 | -
28 |
29 |
address
30 |
amount
31 |
32 |
33 | {recipientsData.length > 0 &&
34 | recipientsData.map((recipient) => (
35 | -
36 |
37 |
{recipient.address}
38 | {/* TODO: Add Horizontal line here */}
39 |
40 |
{ethers.utils.formatEther(recipient.value)}
41 |
42 |
43 | ))}
44 | -
45 |
46 |
total
47 |
48 | {total ? ethers.utils.formatEther(total) : ""}
49 |
50 |
51 |
52 | -
53 |
54 |
your balance
55 |
{tokenBalance}
56 |
57 |
58 | -
59 |
64 |
remaining
65 |
{remaining}
66 |
67 |
68 |
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 |

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 |
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 |
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 |
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 | }
--------------------------------------------------------------------------------