UNPKG

@ethereum-js/multicall

Version:

Multicall allows multiple smart contract constant function calls to be grouped into a single call and the results aggregated into a single result

499 lines (498 loc) 19.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Multicall = exports.getContractAddressFromChainId = exports.Networks = void 0; const ethers_1 = require("ethers"); const ethers_v5_1 = require("ethers-v5"); var Networks; (function (Networks) { Networks[Networks["mainnet"] = 1] = "mainnet"; Networks[Networks["ropsten"] = 3] = "ropsten"; Networks[Networks["rinkeby"] = 4] = "rinkeby"; Networks[Networks["goerli"] = 5] = "goerli"; Networks[Networks["optimism"] = 10] = "optimism"; Networks[Networks["kovan"] = 42] = "kovan"; Networks[Networks["matic"] = 137] = "matic"; Networks[Networks["kovanOptimism"] = 69] = "kovanOptimism"; Networks[Networks["xdai"] = 100] = "xdai"; Networks[Networks["goerliOptimism"] = 420] = "goerliOptimism"; Networks[Networks["arbitrum"] = 42161] = "arbitrum"; Networks[Networks["rinkebyArbitrum"] = 421611] = "rinkebyArbitrum"; Networks[Networks["goerliArbitrum"] = 421613] = "goerliArbitrum"; Networks[Networks["mumbai"] = 80001] = "mumbai"; Networks[Networks["sepolia"] = 11155111] = "sepolia"; Networks[Networks["avalancheMainnet"] = 43114] = "avalancheMainnet"; Networks[Networks["avalancheFuji"] = 43113] = "avalancheFuji"; Networks[Networks["fantomTestnet"] = 4002] = "fantomTestnet"; Networks[Networks["fantom"] = 250] = "fantom"; Networks[Networks["bsc"] = 56] = "bsc"; Networks[Networks["bsc_testnet"] = 97] = "bsc_testnet"; Networks[Networks["moonbeam"] = 1284] = "moonbeam"; Networks[Networks["moonriver"] = 1285] = "moonriver"; Networks[Networks["moonbaseAlphaTestnet"] = 1287] = "moonbaseAlphaTestnet"; Networks[Networks["harmony"] = 1666600000] = "harmony"; Networks[Networks["cronos"] = 25] = "cronos"; Networks[Networks["fuse"] = 122] = "fuse"; Networks[Networks["songbirdCanaryNetwork"] = 19] = "songbirdCanaryNetwork"; Networks[Networks["costonTestnet"] = 16] = "costonTestnet"; Networks[Networks["boba"] = 288] = "boba"; Networks[Networks["aurora"] = 1313161554] = "aurora"; Networks[Networks["astar"] = 592] = "astar"; Networks[Networks["okc"] = 66] = "okc"; Networks[Networks["heco"] = 128] = "heco"; Networks[Networks["metis"] = 1088] = "metis"; Networks[Networks["rsk"] = 30] = "rsk"; Networks[Networks["rskTestnet"] = 31] = "rskTestnet"; Networks[Networks["evmos"] = 9001] = "evmos"; Networks[Networks["evmosTestnet"] = 9000] = "evmosTestnet"; Networks[Networks["thundercore"] = 108] = "thundercore"; Networks[Networks["thundercoreTestnet"] = 18] = "thundercoreTestnet"; Networks[Networks["oasis"] = 26863] = "oasis"; Networks[Networks["celo"] = 42220] = "celo"; Networks[Networks["godwoken"] = 71402] = "godwoken"; Networks[Networks["godwokentestnet"] = 71401] = "godwokentestnet"; Networks[Networks["klatyn"] = 8217] = "klatyn"; Networks[Networks["milkomeda"] = 2001] = "milkomeda"; Networks[Networks["kcc"] = 321] = "kcc"; Networks[Networks["etherlite"] = 111] = "etherlite"; Networks[Networks["lineaTestnet"] = 59140] = "lineaTestnet"; })(Networks || (exports.Networks = Networks = {})); const getContractAddressFromChainId = (chainId) => { switch (chainId) { case Networks.mainnet: case Networks.ropsten: case Networks.rinkeby: case Networks.goerli: case Networks.optimism: case Networks.kovan: case Networks.matic: case Networks.kovanOptimism: case Networks.xdai: case Networks.goerliOptimism: case Networks.arbitrum: case Networks.rinkebyArbitrum: case Networks.goerliArbitrum: case Networks.mumbai: case Networks.sepolia: case Networks.avalancheMainnet: case Networks.avalancheFuji: case Networks.fantomTestnet: case Networks.fantom: case Networks.bsc: case Networks.bsc_testnet: case Networks.moonbeam: case Networks.moonriver: case Networks.moonbaseAlphaTestnet: case Networks.harmony: case Networks.cronos: case Networks.fuse: case Networks.songbirdCanaryNetwork: case Networks.costonTestnet: case Networks.boba: case Networks.aurora: case Networks.astar: case Networks.okc: case Networks.heco: case Networks.metis: case Networks.rsk: case Networks.rskTestnet: case Networks.evmos: case Networks.evmosTestnet: case Networks.thundercore: case Networks.thundercoreTestnet: case Networks.oasis: case Networks.celo: case Networks.godwoken: case Networks.godwokentestnet: case Networks.klatyn: case Networks.milkomeda: case Networks.kcc: case Networks.lineaTestnet: return "0xcA11bde05977b3631167028862bE2a173976CA11"; case Networks.etherlite: return "0x21681750D7ddCB8d1240eD47338dC984f94AF2aC"; default: throw new Error(`Chain ${chainId} doesn't have a multicall contract address`); } }; exports.getContractAddressFromChainId = getContractAddressFromChainId; class Ethers { static getInterface(abi) { let iface; if (this.isV5) { iface = new ethers_v5_1.ethers.utils.Interface(JSON.stringify(abi)); } else { iface = ethers_1.ethers.Interface.from(abi); } return iface; } static getOutput(abi, method) { var _a, _b, _c; const iface = this.getInterface(abi); if ((_a = iface.getFunction(method)) === null || _a === void 0 ? void 0 : _a.outputs) { return (_b = iface.getFunction(method)) === null || _b === void 0 ? void 0 : _b.outputs; } for (let i = 0; i < abi.length; i++) { if (((_c = abi[i].name) === null || _c === void 0 ? void 0 : _c.trim()) === method) { return abi[i].outputs; } } return null; } static abiDecode(types, data) { if (this.isV5) { return ethers_v5_1.ethers.utils.defaultAbiCoder.decode(types, data); } else { return ethers_1.ethers.AbiCoder.defaultAbiCoder().decode(types, data); } } } Ethers.isV5 = !ethers_1.ethers.ZeroAddress; Ethers.v5 = ethers_v5_1.ethers; Ethers.v6 = ethers_1.ethers; exports.default = Ethers; const abi = [ { inputs: [ { components: [ { internalType: "address", name: "target", type: "address" }, { internalType: "bytes", name: "callData", type: "bytes" }, ], internalType: "struct Multicall3.Call[]", name: "calls", type: "tuple[]", }, ], name: "aggregate", outputs: [ { internalType: "uint256", name: "blockNumber", type: "uint256" }, { internalType: "bytes[]", name: "returnData", type: "bytes[]" }, ], stateMutability: "payable", type: "function", }, { inputs: [ { components: [ { internalType: "address", name: "target", type: "address" }, { internalType: "bool", name: "allowFailure", type: "bool" }, { internalType: "bytes", name: "callData", type: "bytes" }, ], internalType: "struct Multicall3.Call3[]", name: "calls", type: "tuple[]", }, ], name: "aggregate3", outputs: [ { components: [ { internalType: "bool", name: "success", type: "bool" }, { internalType: "bytes", name: "returnData", type: "bytes" }, ], internalType: "struct Multicall3.Result[]", name: "returnData", type: "tuple[]", }, ], stateMutability: "payable", type: "function", }, { inputs: [ { components: [ { internalType: "address", name: "target", type: "address" }, { internalType: "bool", name: "allowFailure", type: "bool" }, { internalType: "uint256", name: "value", type: "uint256" }, { internalType: "bytes", name: "callData", type: "bytes" }, ], internalType: "struct Multicall3.Call3Value[]", name: "calls", type: "tuple[]", }, ], name: "aggregate3Value", outputs: [ { components: [ { internalType: "bool", name: "success", type: "bool" }, { internalType: "bytes", name: "returnData", type: "bytes" }, ], internalType: "struct Multicall3.Result[]", name: "returnData", type: "tuple[]", }, ], stateMutability: "payable", type: "function", }, { inputs: [ { components: [ { internalType: "address", name: "target", type: "address" }, { internalType: "bytes", name: "callData", type: "bytes" }, ], internalType: "struct Multicall3.Call[]", name: "calls", type: "tuple[]", }, ], name: "blockAndAggregate", outputs: [ { internalType: "uint256", name: "blockNumber", type: "uint256" }, { internalType: "bytes32", name: "blockHash", type: "bytes32" }, { components: [ { internalType: "bool", name: "success", type: "bool" }, { internalType: "bytes", name: "returnData", type: "bytes" }, ], internalType: "struct Multicall3.Result[]", name: "returnData", type: "tuple[]", }, ], stateMutability: "payable", type: "function", }, { inputs: [], name: "getBasefee", outputs: [{ internalType: "uint256", name: "basefee", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [{ internalType: "uint256", name: "blockNumber", type: "uint256" }], name: "getBlockHash", outputs: [{ internalType: "bytes32", name: "blockHash", type: "bytes32" }], stateMutability: "view", type: "function", }, { inputs: [], name: "getBlockNumber", outputs: [ { internalType: "uint256", name: "blockNumber", type: "uint256" }, ], stateMutability: "view", type: "function", }, { inputs: [], name: "getChainId", outputs: [{ internalType: "uint256", name: "chainid", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [], name: "getCurrentBlockCoinbase", outputs: [{ internalType: "address", name: "coinbase", type: "address" }], stateMutability: "view", type: "function", }, { inputs: [], name: "getCurrentBlockDifficulty", outputs: [{ internalType: "uint256", name: "difficulty", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [], name: "getCurrentBlockGasLimit", outputs: [{ internalType: "uint256", name: "gaslimit", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [], name: "getCurrentBlockTimestamp", outputs: [{ internalType: "uint256", name: "timestamp", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [{ internalType: "address", name: "addr", type: "address" }], name: "getEthBalance", outputs: [{ internalType: "uint256", name: "balance", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [], name: "getLastBlockHash", outputs: [{ internalType: "bytes32", name: "blockHash", type: "bytes32" }], stateMutability: "view", type: "function", }, { inputs: [ { internalType: "bool", name: "requireSuccess", type: "bool" }, { components: [ { internalType: "address", name: "target", type: "address" }, { internalType: "bytes", name: "callData", type: "bytes" }, ], internalType: "struct Multicall3.Call[]", name: "calls", type: "tuple[]", }, ], name: "tryAggregate", outputs: [ { components: [ { internalType: "bool", name: "success", type: "bool" }, { internalType: "bytes", name: "returnData", type: "bytes" }, ], internalType: "struct Multicall3.Result[]", name: "returnData", type: "tuple[]", }, ], stateMutability: "payable", type: "function", }, { inputs: [ { internalType: "bool", name: "requireSuccess", type: "bool" }, { components: [ { internalType: "address", name: "target", type: "address" }, { internalType: "bytes", name: "callData", type: "bytes" }, ], internalType: "struct Multicall3.Call[]", name: "calls", type: "tuple[]", }, ], name: "tryBlockAndAggregate", outputs: [ { internalType: "uint256", name: "blockNumber", type: "uint256" }, { internalType: "bytes32", name: "blockHash", type: "bytes32" }, { components: [ { internalType: "bool", name: "success", type: "bool" }, { internalType: "bytes", name: "returnData", type: "bytes" }, ], internalType: "struct Multicall3.Result[]", name: "returnData", type: "tuple[]", }, ], stateMutability: "payable", type: "function", }, ]; class Multicall { constructor(setup) { if (!setup.provider) { throw Error("Provider is required"); } this._setup = setup; } call(contractsCallData, callOptions = {}) { return __awaiter(this, void 0, void 0, function* () { const executeData = []; for (let callDataIndex = 0; callDataIndex < contractsCallData.length; callDataIndex++) { const callData = contractsCallData[callDataIndex]; const iface = Ethers.getInterface(callData.abi); executeData.push({ callData: iface.encodeFunctionData(callData.method, callData.parameters), target: callData.contractAddress, outputTypes: Ethers.getOutput(callData.abi, callData.method), }); } return this.execute(executeData, callOptions); }); } execute(executeData, callOptions) { return __awaiter(this, void 0, void 0, function* () { let response; if (Ethers.isV5) { response = yield this.executeEtherV5(executeData, callOptions); } else { response = yield this.executeEtherV6(executeData, callOptions); } const results = []; response.returnData.forEach((result, index) => { if (result.success) { if (executeData[index].outputTypes) { const decoded = Ethers.abiDecode(executeData[index].outputTypes, result.returnData); const data = decoded.length === 1 ? decoded[0] : decoded; results.push(data); } else { results.push(result.returnData); } } else { results.push(null); } }); return { blockNumber: parseInt(`${response.blockNumber}`), results, }; }); } executeEtherV5(executeData, callOptions) { return __awaiter(this, void 0, void 0, function* () { const provider = this._setup.provider; let contractMulticallAddress = this._setup.contractMulticall; if (!contractMulticallAddress) { const network = yield provider.getNetwork(); contractMulticallAddress = (0, exports.getContractAddressFromChainId)(parseInt(`${network.chainId}`)); } const contract = new Ethers.v5.Contract(contractMulticallAddress, abi, provider); let options = {}; if (callOptions.blockNumber) { options = Object.assign(Object.assign({}, options), { blockTag: parseInt(`${callOptions.blockNumber}`) }); } const response = yield contract.callStatic.tryBlockAndAggregate(false, executeData.map((ele) => ({ target: ele.target, callData: ele.callData, })), options); return response; }); } executeEtherV6(executeData, callOptions) { return __awaiter(this, void 0, void 0, function* () { const provider = this._setup.provider; let contractMulticallAddress = this._setup.contractMulticall; if (!contractMulticallAddress) { const network = yield provider.getNetwork(); contractMulticallAddress = (0, exports.getContractAddressFromChainId)(parseInt(`${network.chainId}`)); } const contract = new Ethers.v6.Contract(contractMulticallAddress, abi, { provider, }); let options = {}; if (callOptions.blockNumber) { options = Object.assign(Object.assign({}, options), { blockTag: parseInt(`${callOptions.blockNumber}`) }); } const response = yield contract.tryBlockAndAggregate.staticCall(false, executeData.map((ele) => ({ target: ele.target, callData: ele.callData, })), options); return response; }); } } exports.Multicall = Multicall;