├── contracts ├── artifacts │ ├── Multicall_sol_ReverseRegistrar.bin │ ├── Multicall_sol_ReverseRegistrar.abi │ ├── Multicall_sol_Multicall.abi │ └── Multicall_sol_Multicall.bin └── Multicall.sol ├── export-bin.js ├── lib ├── index.d.ts ├── index.js.map └── index.js ├── tsconfig.json ├── package.json ├── deploy.html ├── README.md ├── contract.json └── src.ts └── index.ts /contracts/artifacts/Multicall_sol_ReverseRegistrar.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contracts/artifacts/Multicall_sol_ReverseRegistrar.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"setName","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /export-bin.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require("fs"); 3 | const { resolve } = require("path"); 4 | 5 | const { ethers } = require("ethers"); 6 | 7 | const initcode = fs.readFileSync(resolve(__dirname, "contracts/artifacts/Multicall_sol_Multicall.bin")).toString(); 8 | const abi = fs.readFileSync(resolve(__dirname, "contracts/artifacts/Multicall_sol_Multicall.abi")).toString(); 9 | 10 | const iface = new ethers.utils.Interface(abi); 11 | const output = { 12 | initcode: ("0x" + initcode), 13 | abi: iface.format("full") 14 | }; 15 | 16 | fs.writeFileSync(resolve(__dirname, "contract.json"), JSON.stringify(output, null, 2)); 17 | -------------------------------------------------------------------------------- /contracts/artifacts/Multicall_sol_Multicall.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"reverseRegistrar","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"sizeLimit","type":"uint256"},{"internalType":"address[]","name":"addrs","type":"address[]"},{"internalType":"bytes[]","name":"datas","type":"bytes[]"}],"name":"execute","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"uint256[]","name":"statuses","type":"uint256[]"},{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | export declare class Multicaller { 3 | readonly provider: ethers.providers.Provider; 4 | private _hijackProvider; 5 | gasLimitPerCall: number; 6 | resultLimitPerCall: number; 7 | private _pendingRequests; 8 | constructor(provider: ethers.providers.Provider); 9 | queue(address: string, fragment: string, values?: Array): Promise; 10 | queueResult(address: string, fragment: string, values?: Array): Promise; 11 | _queue(address: string, method: string, values: Array, dereference: boolean): Promise; 12 | flush(): Promise; 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib/", 4 | "rootDir": "./src.ts", 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "lib": [ 9 | "es2015", 10 | "es5", 11 | "dom" 12 | ], 13 | "module": "commonjs", 14 | "moduleResolution": "node", 15 | "noEmitOnError": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noUnusedLocals": true, 20 | "preserveSymlinks": true, 21 | "preserveWatchOutput": true, 22 | "pretty": false, 23 | "sourceMap": true, 24 | "strict": true, 25 | "target": "es2015" 26 | }, 27 | "include": [ 28 | "./src.ts/*.ts" 29 | ], 30 | "exclude": [ ] 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ethers-ancillary/multicall", 3 | "version": "0.0.1", 4 | "description": "Multicall library for ethers.", 5 | "main": "./lib/index.js", 6 | "devDependencies": { 7 | "@types/node": "^15.6.1", 8 | "solc": "^0.8.4", 9 | "typescript": "^4.2.2" 10 | }, 11 | "dependencies": { 12 | "ethers": "5.3.0" 13 | }, 14 | "scripts": { 15 | "auto-build": "npm run build -- -w", 16 | "build": "tsc --build ./tsconfig.json", 17 | "build-contracts": "pushd contracts && npx solcjs --optimize --bin --abi Multicall.sol --output-dir ./artifacts && popd && node export-bin.js", 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "keywords": [ 21 | "ethers", 22 | "multicall" 23 | ], 24 | "author": "Richard Moore ", 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /deploy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Deploy Multicall 4 | 5 | 6 | 7 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Multicall 2 | ========= 3 | 4 | A multicall contract enables a single eth_call to be used to 5 | aggregate multiple constant contract method calls to a variety 6 | of addresses. 7 | 8 | The ethers Multicall contract is located at `multicall.eth` 9 | and is verified on [Etherscan](https://etherscan.io/address/multicall.eth). 10 | 11 | 12 | JavaScript API 13 | -------------- 14 | 15 | ```javascript 16 | const multicaller = Multicaller(provider); 17 | 18 | // The maximum amount of gas to allow each sub-call to consume 19 | multicaller.gasLimtPerCall = 50000; 20 | 21 | // The maximum result length to allow each call to return 22 | multicaller.resultLimitPerCall = 1024; 23 | 24 | multicaller.queue(address, fragment [ , values ]) => Promise 25 | multicaller.queueResult(address, fragment [ , values ]) => Promise 26 | multicall.flush() => Promise 27 | ``` 28 | 29 | 30 | Contract API 31 | ------------ 32 | 33 | ```javascript 34 | const ABI = [ 35 | "function execute(uint gasLimit, uint maxResultSize, address[] addrs, bytes[] datas) view returns (uint blockNumber, uint[] statuses, bytes[] results)" 36 | ]; 37 | 38 | const contract = new Contract("multicall.eth", ABI, provider); 39 | ``` 40 | 41 | There is a single method, `execute` which will throttle the 42 | amount of gas per external call and limit the resulting output 43 | from each call to prevent malicious contracts from breaking the 44 | aggregate call. 45 | 46 | Each result includes a status (0 for failure, 1 for successs) along 47 | with any result. A status of 0 may still return data, which will be 48 | the result of the revert. 49 | 50 | 51 | License 52 | ------- 53 | 54 | MIT License. 55 | -------------------------------------------------------------------------------- /contracts/Multicall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.4; 4 | 5 | interface ReverseRegistrar { 6 | function setName(string memory name) external returns (bytes32); 7 | } 8 | 9 | function getBytes(uint gasLimit, uint sizeLimit, address addr, bytes memory data) view returns (uint status, bytes memory result) { 10 | assembly { 11 | // Allocate a new slot for the output 12 | result := mload(0x40) 13 | 14 | // Initialize the output as length 0 (in case things go wrong) 15 | mstore(result, 0) 16 | mstore(0x40, add(result, 32)) 17 | 18 | // Call the target address with the data, limiting gas usage 19 | status := staticcall(gasLimit, addr, add(data, 32), mload(data), 0, 0) 20 | 21 | // If the result (return or revert) is a reasonable length... 22 | if lt(returndatasize(), sizeLimit) { 23 | 24 | // Allocate enough space to store the ceil_32(len_32(result) + result) 25 | mstore(0x40, add(result, and(add(add(returndatasize(), 0x20), 0x1f), not(0x1f)))) 26 | 27 | // Place the length of the result value into the output 28 | mstore(result, returndatasize()) 29 | 30 | // Copy the result value into the output 31 | returndatacopy(add(result, 32), 0, returndatasize()) 32 | } 33 | } 34 | } 35 | 36 | contract Multicall { 37 | 38 | // Call this with the result of ens.owner(namehash("addr.reverse")) 39 | constructor(address reverseRegistrar) { 40 | 41 | // Make sure the reverse record is correct 42 | ReverseRegistrar(reverseRegistrar).setName("multicall.eth"); 43 | } 44 | 45 | function execute(uint gasLimit, uint sizeLimit, address[] calldata addrs, bytes[] calldata datas) external view returns (uint blockNumber, uint[] memory statuses, bytes[] memory results) { 46 | require(addrs.length == datas.length); 47 | 48 | statuses = new uint256[](addrs.length); 49 | results = new bytes[](addrs.length); 50 | 51 | for (uint256 i = 0; i < addrs.length; i++) { 52 | (statuses[i], results[i]) = getBytes(gasLimit, sizeLimit, addrs[i], datas[i]); 53 | } 54 | 55 | return (block.number, statuses, results); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/artifacts/Multicall_sol_Multicall.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b5060405161062438038061062483398101604081905261002f916100cd565b60405163c47f002760e01b815260206004820152600d60248201526c0daead8e8d2c6c2d8d85ccae8d609b1b60448201526001600160a01b0382169063c47f002790606401602060405180830381600087803b15801561008e57600080fd5b505af11580156100a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100c691906100fb565b5050610113565b6000602082840312156100de578081fd5b81516001600160a01b03811681146100f4578182fd5b9392505050565b60006020828403121561010c578081fd5b5051919050565b610502806101226000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80634c0770b914610030575b600080fd5b61004361003e366004610309565b61005b565b60405161005293929190610389565b60405180910390f35b600060608085841461006c57600080fd5b8567ffffffffffffffff81111561009357634e487b7160e01b600052604160045260246000fd5b6040519080825280602002602001820160405280156100bc578160200160208202803683370190505b5091508567ffffffffffffffff8111156100e657634e487b7160e01b600052604160045260246000fd5b60405190808252806020026020018201604052801561011957816020015b60608152602001906001900390816101045790505b50905060005b86811015610235576101cd8a8a8a8a8581811061014c57634e487b7160e01b600052603260045260246000fd5b905060200201602081019061016191906102db565b89898681811061018157634e487b7160e01b600052603260045260246000fd5b90506020028101906101939190610460565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061024592505050565b8483815181106101ed57634e487b7160e01b600052603260045260246000fd5b6020026020010184848151811061021457634e487b7160e01b600052603260045260246000fd5b6020908102919091010191909152528061022d816104a5565b91505061011f565b5043925096509650969350505050565b6000606060405190506000815260208101604052600080845160208601878afa9150843d101561028857603f3d01601f191681016040523d81523d6000602083013e5b94509492505050565b60008083601f8401126102a2578182fd5b50813567ffffffffffffffff8111156102b9578182fd5b6020830191508360208260051b85010111156102d457600080fd5b9250929050565b6000602082840312156102ec578081fd5b81356001600160a01b0381168114610302578182fd5b9392505050565b60008060008060008060808789031215610321578182fd5b8635955060208701359450604087013567ffffffffffffffff80821115610346578384fd5b6103528a838b01610291565b9096509450606089013591508082111561036a578384fd5b5061037789828a01610291565b979a9699509497509295939492505050565b60006060820185835260206060818501528186518084526080860191508288019350845b818110156103c9578451835293830193918301916001016103ad565b5050848103604086015285518082528282019350600581901b82018301838801865b8381101561045057601f1980868503018852825180518086528a5b81811015610421578281018a01518782018b01528901610406565b81811115610431578b8a83890101525b5098880198601f019091169390930186019250908501906001016103eb565b50909a9950505050505050505050565b6000808335601e19843603018112610476578283fd5b83018035915067ffffffffffffffff821115610490578283fd5b6020019150368190038213156102d457600080fd5b60006000198214156104c557634e487b7160e01b81526011600452602481fd5b506001019056fea264697066735822122083b5dc25b3c9256aa4244eddaf9e4b5fccd09a45ec4e0174f2c900de7144602d64736f6c63430008040033 -------------------------------------------------------------------------------- /contract.json: -------------------------------------------------------------------------------- 1 | { 2 | "initcode": "0x608060405234801561001057600080fd5b5060405161062438038061062483398101604081905261002f916100cd565b60405163c47f002760e01b815260206004820152600d60248201526c0daead8e8d2c6c2d8d85ccae8d609b1b60448201526001600160a01b0382169063c47f002790606401602060405180830381600087803b15801561008e57600080fd5b505af11580156100a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100c691906100fb565b5050610113565b6000602082840312156100de578081fd5b81516001600160a01b03811681146100f4578182fd5b9392505050565b60006020828403121561010c578081fd5b5051919050565b610502806101226000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80634c0770b914610030575b600080fd5b61004361003e366004610309565b61005b565b60405161005293929190610389565b60405180910390f35b600060608085841461006c57600080fd5b8567ffffffffffffffff81111561009357634e487b7160e01b600052604160045260246000fd5b6040519080825280602002602001820160405280156100bc578160200160208202803683370190505b5091508567ffffffffffffffff8111156100e657634e487b7160e01b600052604160045260246000fd5b60405190808252806020026020018201604052801561011957816020015b60608152602001906001900390816101045790505b50905060005b86811015610235576101cd8a8a8a8a8581811061014c57634e487b7160e01b600052603260045260246000fd5b905060200201602081019061016191906102db565b89898681811061018157634e487b7160e01b600052603260045260246000fd5b90506020028101906101939190610460565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061024592505050565b8483815181106101ed57634e487b7160e01b600052603260045260246000fd5b6020026020010184848151811061021457634e487b7160e01b600052603260045260246000fd5b6020908102919091010191909152528061022d816104a5565b91505061011f565b5043925096509650969350505050565b6000606060405190506000815260208101604052600080845160208601878afa9150843d101561028857603f3d01601f191681016040523d81523d6000602083013e5b94509492505050565b60008083601f8401126102a2578182fd5b50813567ffffffffffffffff8111156102b9578182fd5b6020830191508360208260051b85010111156102d457600080fd5b9250929050565b6000602082840312156102ec578081fd5b81356001600160a01b0381168114610302578182fd5b9392505050565b60008060008060008060808789031215610321578182fd5b8635955060208701359450604087013567ffffffffffffffff80821115610346578384fd5b6103528a838b01610291565b9096509450606089013591508082111561036a578384fd5b5061037789828a01610291565b979a9699509497509295939492505050565b60006060820185835260206060818501528186518084526080860191508288019350845b818110156103c9578451835293830193918301916001016103ad565b5050848103604086015285518082528282019350600581901b82018301838801865b8381101561045057601f1980868503018852825180518086528a5b81811015610421578281018a01518782018b01528901610406565b81811115610431578b8a83890101525b5098880198601f019091169390930186019250908501906001016103eb565b50909a9950505050505050505050565b6000808335601e19843603018112610476578283fd5b83018035915067ffffffffffffffff821115610490578283fd5b6020019150368190038213156102d457600080fd5b60006000198214156104c557634e487b7160e01b81526011600452602481fd5b506001019056fea264697066735822122083b5dc25b3c9256aa4244eddaf9e4b5fccd09a45ec4e0174f2c900de7144602d64736f6c63430008040033", 3 | "abi": [ 4 | "constructor(address reverseRegistrar)", 5 | "function execute(uint256 gasLimit, uint256 sizeLimit, address[] addrs, bytes[] datas) view returns (uint256 blockNumber, uint256[] statuses, bytes[] results)" 6 | ] 7 | } -------------------------------------------------------------------------------- /lib/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src.ts/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,mCAAgC;AAEhC,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAE1D,qDAAqD;AACrD,uDAAuD;AACvD,8BAA8B;AAC9B,MAAM,cAAe,SAAQ,eAAM,CAAC,SAAS,CAAC,YAAY;IAItD,YAAY,QAAmC;QAC3C,KAAK,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QAE7B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,EAAG,CAAC;IACzB,CAAC;IAEK,WAAW,CAAC,IAAY;;YAC1B,IAAI;gBACA,OAAO,eAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;aACxC;YAAC,OAAO,CAAC,EAAE,GAAG;YAEf,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;gBACvB,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;gBAChC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;oBACrE,IAAI,OAAO,IAAI,IAAI,EAAE;wBACjB,MAAM,CAAC,UAAU,CAAC,kBAAkB,EAAE,eAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE;4BACpF,SAAS,EAAE,aAAa;4BACxB,IAAI;yBACP,CAAC,CAAC;qBACN;oBACD,OAAO,OAAO,CAAC;gBACnB,CAAC,CAAC,CAAC;aACN;YAED,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;KAAA;CACJ;AAMA,CAAC;AAEF,MAAa,WAAW;IAapB,YAAY,QAAmC;QAC3C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,eAAe,GAAG,IAAI,cAAc,CAAC,QAAQ,CAAC,CAAC;QAEpD,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC7B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAE/B,IAAI,CAAC,gBAAgB,GAAG,EAAG,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,QAAgB,EAAE,MAAmB;QACxD,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;IAChE,CAAC;IAED,WAAW,CAAC,OAAe,EAAE,QAAgB,EAAE,MAAmB;QAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,CAAC,OAAe,EAAE,MAAc,EAAE,MAAkB,EAAE,WAAoB;QAC5E,IAAI,MAAM,IAAI,IAAI,EAAE;YAAE,MAAM,GAAG,EAAG,CAAC;SAAE;QAErC,MAAM,QAAQ,GAAG,eAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,IAAI,eAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAE,QAAQ,CAAE,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,IAAI,eAAM,CAAC,QAAQ,CAAC,eAAM,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QAEhG,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;gBACvB,OAAO;gBACP,OAAO,EAAE,GAAG,EAAE;oBACV,OAAO,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;wBAC1E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;wBAChB,OAAO,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC;oBAC3B,CAAC,CAAC,CAAC;gBACP,CAAC;gBACD,MAAM,EAAE,CAAC,KAAmB,EAAE,OAAgB,EAAE,MAAW,EAAE,EAAE;oBAC3D,IAAI,KAAK,EAAE;wBACP,MAAM,CAAC,KAAK,CAAC,CAAC;qBAEjB;yBAAM,IAAI,OAAO,EAAE;wBAChB,IAAI,MAAM,GAAG,KAAK,CAAC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;wBAC1D,IAAI,WAAW,IAAI,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;4BAClE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;yBACtB;wBACD,OAAO,CAAC,MAAM,CAAC,CAAC;qBAEnB;yBAAM;wBACH,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,aAAa,EAAE,eAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE;4BACrF,SAAS,EAAE,KAAK;yBACnB,CAAC,CAAC;wBACH,MAAM,CAAC,KAAK,CAAC,CAAC;qBACjB;gBACL,CAAC;aACJ,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC;IAEK,KAAK;;YACP,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC;YACtC,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC;YAE5C,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC;YACtC,IAAI,CAAC,gBAAgB,GAAG,EAAG,CAAC;YAE5B,MAAM,SAAS,GAAG,IAAI,eAAM,CAAC,QAAQ,CAAC,eAAe,EAAE;gBACnD,oHAAoH;aACvH,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAElB,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,MAAM,eAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC;gBAC9D,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;gBACvF,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;aACtD,CAAC,CAAC;YAEH,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAsB,CAAC,MAAM,SAAS,CAAC,OAAO,CAClF,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,CACR,CAAC,CAAC;YAEH,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;gBAC/B,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAClE,CAAC,CAAC,CAAC;YAEH,OAAO,WAAW,CAAC,QAAQ,EAAE,CAAC;QAClC,CAAC;KAAA;CACJ;AAlGD,kCAkGC;AACD,CAAC;;QACG,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,eAAM,CAAC,kBAAkB,EAAE,CAAC,CAAC;QAE5D,MAAM,GAAG,GAAG,uBAAuB,CAAC;QACpC,MAAM,MAAM,GAAG,oBAAoB,CAAC;QAEpC,MAAM,MAAM,GAAG;YACX,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,iCAAiC,CAAC;YAC9D,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,8BAA8B,CAAC;YACvD,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,2CAA2C,EAAE,CAAE,MAAM,CAAE,CAAC;SACtF,CAAC;QACF,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;CAAA,CAAC,EAAE,CAAC"} -------------------------------------------------------------------------------- /src.ts/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ethers } from "ethers"; 3 | 4 | const logger = new ethers.utils.Logger("multicall/0.0.1"); 5 | 6 | // This provider cached ENS names, since if used they 7 | // are probably highly related. This could also be done 8 | // over the multicall contract 9 | class HijackProvider extends ethers.providers.BaseProvider { 10 | private _ensCache: Record>; 11 | private _provider: ethers.providers.Provider; 12 | 13 | constructor(provider: ethers.providers.Provider) { 14 | super(provider.getNetwork()); 15 | 16 | this._provider = provider; 17 | this._ensCache = { }; 18 | } 19 | 20 | async resolveName(name: string) { 21 | try { 22 | return ethers.utils.getAddress(name); 23 | } catch (e) { } 24 | 25 | if (!this._ensCache[name]) { 26 | console.log("Looking up", name); 27 | this._ensCache[name] = this._provider.resolveName(name).then((address) => { 28 | if (address == null) { 29 | logger.throwError("ENS name not set", ethers.utils.Logger.errors.UNSUPPORTED_OPERATION, { 30 | operation: "resolveName", 31 | name 32 | }); 33 | } 34 | return address; 35 | }); 36 | } 37 | 38 | return await this._ensCache[name]; 39 | } 40 | } 41 | 42 | interface MulticallResponse { 43 | blockNumber: ethers.BigNumber; 44 | statuses: Array; 45 | results: Array; 46 | }; 47 | 48 | export class Multicaller { 49 | readonly provider: ethers.providers.Provider; 50 | private _hijackProvider: ethers.providers.Provider; 51 | 52 | gasLimitPerCall: number; 53 | resultLimitPerCall: number; 54 | 55 | private _pendingRequests: Array<{ 56 | address: string, 57 | getData: () => Promise, 58 | commit: (error: null | Error, status: boolean, result: any) => void; 59 | }> 60 | 61 | constructor(provider: ethers.providers.Provider) { 62 | this.provider = provider; 63 | this._hijackProvider = new HijackProvider(provider); 64 | 65 | this.gasLimitPerCall = 60000; 66 | this.resultLimitPerCall = 1024; 67 | 68 | this._pendingRequests = [ ]; 69 | } 70 | 71 | queue(address: string, fragment: string, values?: Array): Promise { 72 | return this._queue(address, fragment, (values || []), true); 73 | } 74 | 75 | queueResult(address: string, fragment: string, values?: Array): Promise { 76 | return this._queue(address, fragment, (values || []), false); 77 | } 78 | 79 | _queue(address: string, method: string, values: Array, dereference: boolean): Promise { 80 | if (values == null) { values = [ ]; } 81 | 82 | const fragment = ethers.utils.FunctionFragment.from(method); 83 | const iface = new ethers.utils.Interface([ fragment ]); 84 | const contract = new ethers.Contract(ethers.constants.AddressZero, iface, this._hijackProvider); 85 | 86 | return new Promise((resolve, reject) => { 87 | this._pendingRequests.push({ 88 | address, 89 | getData: () => { 90 | return contract.populateTransaction[fragment.format()](...values).then((tx) => { 91 | console.log(tx); 92 | return tx.data || "0x"; 93 | }); 94 | }, 95 | commit: (error: null | Error, success: boolean, result: any) => { 96 | if (error) { 97 | reject(error); 98 | 99 | } else if (success) { 100 | let output = iface.decodeFunctionResult(fragment, result); 101 | if (dereference && fragment.outputs && fragment.outputs.length === 1) { 102 | output = output[0]; 103 | } 104 | resolve(output); 105 | 106 | } else { 107 | const error = logger.makeError("call failed", ethers.utils.Logger.errors.CALL_EXCEPTION, { 108 | something: "123" 109 | }); 110 | reject(error); 111 | } 112 | } 113 | }); 114 | }); 115 | } 116 | 117 | async flush(): Promise { 118 | const gasLimit = this.gasLimitPerCall; 119 | const resultLimit = this.resultLimitPerCall; 120 | 121 | const pending = this._pendingRequests; 122 | this._pendingRequests = [ ]; 123 | 124 | const multicall = new ethers.Contract("multicall.eth", [ 125 | "function execute(uint, uint, address[], bytes[]) view returns (uint blockNumber, uint[] statuses, bytes[] results)" 126 | ], this.provider); 127 | 128 | const { addresses, datas } = await ethers.utils.resolveProperties({ 129 | addresses: Promise.all(pending.map((r) => this._hijackProvider.resolveName(r.address))), 130 | datas: Promise.all(pending.map((r) => r.getData())) 131 | }); 132 | 133 | const { blockNumber, statuses, results } = (await multicall.execute( 134 | gasLimit, 135 | resultLimit, 136 | addresses, 137 | datas 138 | )); 139 | 140 | statuses.forEach((status, index) => { 141 | pending[index].commit(null, !status.isZero(), results[index]); 142 | }); 143 | 144 | return blockNumber.toNumber(); 145 | } 146 | } 147 | (async function() { 148 | const tester = new Multicaller(ethers.getDefaultProvider()); 149 | 150 | const dai = "dai.tokens.ethers.eth"; 151 | const ricmoo = "ricmoo.firefly.eth"; 152 | 153 | const result = { 154 | decimals: tester.queue(dai, "decimals() view returns (uint8)"), 155 | name: tester.queue(dai, "name() view returns (string)"), 156 | balance: tester.queue(dai, "balanceOf(address) view returns (uint256)", [ ricmoo ]), 157 | }; 158 | const blockNumber = await tester.flush(); 159 | console.log("BlockNumber", blockNumber); 160 | console.log(result); 161 | })(); 162 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.Multicaller = void 0; 13 | const ethers_1 = require("ethers"); 14 | const logger = new ethers_1.ethers.utils.Logger("multicall/0.0.1"); 15 | // This provider cached ENS names, since if used they 16 | // are probably highly related. This could also be done 17 | // over the multicall contract 18 | class HijackProvider extends ethers_1.ethers.providers.BaseProvider { 19 | constructor(provider) { 20 | super(provider.getNetwork()); 21 | this._provider = provider; 22 | this._ensCache = {}; 23 | } 24 | resolveName(name) { 25 | return __awaiter(this, void 0, void 0, function* () { 26 | try { 27 | return ethers_1.ethers.utils.getAddress(name); 28 | } 29 | catch (e) { } 30 | if (!this._ensCache[name]) { 31 | console.log("Looking up", name); 32 | this._ensCache[name] = this._provider.resolveName(name).then((address) => { 33 | if (address == null) { 34 | logger.throwError("ENS name not set", ethers_1.ethers.utils.Logger.errors.UNSUPPORTED_OPERATION, { 35 | operation: "resolveName", 36 | name 37 | }); 38 | } 39 | return address; 40 | }); 41 | } 42 | return yield this._ensCache[name]; 43 | }); 44 | } 45 | } 46 | ; 47 | class Multicaller { 48 | constructor(provider) { 49 | this.provider = provider; 50 | this._hijackProvider = new HijackProvider(provider); 51 | this.gasLimitPerCall = 60000; 52 | this.resultLimitPerCall = 1024; 53 | this._pendingRequests = []; 54 | } 55 | queue(address, fragment, values) { 56 | return this._queue(address, fragment, (values || []), true); 57 | } 58 | queueResult(address, fragment, values) { 59 | return this._queue(address, fragment, (values || []), false); 60 | } 61 | _queue(address, method, values, dereference) { 62 | if (values == null) { 63 | values = []; 64 | } 65 | const fragment = ethers_1.ethers.utils.FunctionFragment.from(method); 66 | const iface = new ethers_1.ethers.utils.Interface([fragment]); 67 | const contract = new ethers_1.ethers.Contract(ethers_1.ethers.constants.AddressZero, iface, this._hijackProvider); 68 | return new Promise((resolve, reject) => { 69 | this._pendingRequests.push({ 70 | address, 71 | getData: () => { 72 | return contract.populateTransaction[fragment.format()](...values).then((tx) => { 73 | console.log(tx); 74 | return tx.data || "0x"; 75 | }); 76 | }, 77 | commit: (error, success, result) => { 78 | if (error) { 79 | reject(error); 80 | } 81 | else if (success) { 82 | let output = iface.decodeFunctionResult(fragment, result); 83 | if (dereference && fragment.outputs && fragment.outputs.length === 1) { 84 | output = output[0]; 85 | } 86 | resolve(output); 87 | } 88 | else { 89 | const error = logger.makeError("call failed", ethers_1.ethers.utils.Logger.errors.CALL_EXCEPTION, { 90 | something: "123" 91 | }); 92 | reject(error); 93 | } 94 | } 95 | }); 96 | }); 97 | } 98 | flush() { 99 | return __awaiter(this, void 0, void 0, function* () { 100 | const gasLimit = this.gasLimitPerCall; 101 | const resultLimit = this.resultLimitPerCall; 102 | const pending = this._pendingRequests; 103 | this._pendingRequests = []; 104 | const multicall = new ethers_1.ethers.Contract("multicall.eth", [ 105 | "function execute(uint, uint, address[], bytes[]) view returns (uint blockNumber, uint[] statuses, bytes[] results)" 106 | ], this.provider); 107 | const { addresses, datas } = yield ethers_1.ethers.utils.resolveProperties({ 108 | addresses: Promise.all(pending.map((r) => this._hijackProvider.resolveName(r.address))), 109 | datas: Promise.all(pending.map((r) => r.getData())) 110 | }); 111 | const { blockNumber, statuses, results } = (yield multicall.execute(gasLimit, resultLimit, addresses, datas)); 112 | statuses.forEach((status, index) => { 113 | pending[index].commit(null, !status.isZero(), results[index]); 114 | }); 115 | return blockNumber.toNumber(); 116 | }); 117 | } 118 | } 119 | exports.Multicaller = Multicaller; 120 | (function () { 121 | return __awaiter(this, void 0, void 0, function* () { 122 | const tester = new Multicaller(ethers_1.ethers.getDefaultProvider()); 123 | const dai = "dai.tokens.ethers.eth"; 124 | const ricmoo = "ricmoo.firefly.eth"; 125 | const result = { 126 | decimals: tester.queue(dai, "decimals() view returns (uint8)"), 127 | name: tester.queue(dai, "name() view returns (string)"), 128 | balance: tester.queue(dai, "balanceOf(address) view returns (uint256)", [ricmoo]), 129 | }; 130 | const blockNumber = yield tester.flush(); 131 | console.log("BlockNumber", blockNumber); 132 | console.log(result); 133 | }); 134 | })(); 135 | //# sourceMappingURL=index.js.map --------------------------------------------------------------------------------