@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
JavaScript
;
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;