import { JsonFragment, JsonFragmentType, Result } from '@ethersproject/abi'; import { hexConcat } from '@ethersproject/bytes'; import { Contract } from '@ethersproject/contracts'; import { BaseProvider } from '@ethersproject/providers'; import Abi, { Params } from './abi'; import deploylessMulticallAbi from './abi/deploylessMulticall.json'; import deploylessMulticall2Abi from './abi/deploylessMulticall2.json'; import deploylessMulticall3Abi from './abi/deploylessMulticall3.json'; import multicallAbi from './abi/multicall.json'; import multicall2Abi from './abi/multicall2.json'; import multicall3Abi from './abi/multicall3.json'; import { Multicall, deploylessMulticallBytecode, deploylessMulticall2Bytecode, deploylessMulticall3Bytecode, } from './multicall'; import { BlockTag } from './provider'; interface CallRequest { target: string; callData: string; } interface Call { contract: { address: string; }; name: string; inputs: JsonFragmentType[]; outputs: JsonFragmentType[]; params: Params; } interface FailableCall extends Call { canFail: boolean; } interface CallResult { success: boolean; returnData: string; } async function all<T>( provider: BaseProvider, multicall: Multicall | null, calls: Call[], block?: BlockTag, ): Promise<T[]> { const contract = multicall ? new Contract(multicall.address, multicallAbi, provider) : null; const callRequests = calls.map((call) => { const callData = Abi.encode(call.name, call.inputs, call.params); return { target: call.contract.address, callData, }; }); const overrides = { blockTag: block, }; const response = contract ? await contract.aggregate(callRequests, overrides) : await callDeployless(provider, callRequests, block); const callCount = calls.length; const callResult: T[] = []; for (let i = 0; i < callCount; i++) { const name = calls[i].name; const outputs = calls[i].outputs; const returnData = response.returnData[i]; const params = Abi.decode(name, outputs, returnData); const result = outputs.length === 1 ? params[0] : params; callResult.push(result); } return callResult; } async function tryAll<T>( provider: BaseProvider, multicall2: Multicall | null, calls: Call[], block?: BlockTag, ): Promise<(T | null)[]> { const contract = multicall2 ? new Contract(multicall2.address, multicall2Abi, provider) : null; const callRequests = calls.map((call) => { const callData = Abi.encode(call.name, call.inputs, call.params); return { target: call.contract.address, callData, }; }); const overrides = { blockTag: block, }; const response: CallResult[] = contract ? await contract.tryAggregate(false, callRequests, overrides) : await callDeployless2(provider, callRequests, block); const callCount = calls.length; const callResult: (T | null)[] = []; for (let i = 0; i < callCount; i++) { const name = calls[i].name; const outputs = calls[i].outputs; const result = response[i]; if (!result.success) { callResult.push(null); } else { const params = Abi.decode(name, outputs, result.returnData); const data = outputs.length === 1 ? params[0] : params; callResult.push(data); } } return callResult; } async function tryEach<T>( provider: BaseProvider, multicall3: Multicall | null, calls: FailableCall[], block?: BlockTag, ): Promise<(T | null)[]> { const contract = multicall3 ? new Contract(multicall3.address, multicall3Abi, provider) : null; const callRequests = calls.map((call) => { const callData = Abi.encode(call.name, call.inputs, call.params); return { target: call.contract.address, allowFailure: call.canFail, callData, }; }); const overrides = { blockTag: block, }; const response: CallResult[] = contract ? await contract.aggregate3(callRequests, overrides) : await callDeployless3(provider, callRequests, block); const callCount = calls.length; const callResult: (T | null)[] = []; for (let i = 0; i < callCount; i++) { const name = calls[i].name; const outputs = calls[i].outputs; const result = response[i]; if (!result.success) { callResult.push(null); } else { const params = Abi.decode(name, outputs, result.returnData); const data = outputs.length === 1 ? params[0] : params; callResult.push(data); } } return callResult; } async function callDeployless( provider: BaseProvider, callRequests: CallRequest[], block?: BlockTag, ): Promise<Result> { const inputAbi: JsonFragment[] = deploylessMulticallAbi; const constructor = inputAbi.find((f) => f.type === 'constructor'); const inputs = constructor?.inputs || []; const args = Abi.encodeConstructor(inputs, [callRequests]); const data = hexConcat([deploylessMulticallBytecode, args]); const callData = await provider.call( { data, }, block, ); const outputAbi: JsonFragment[] = multicallAbi; const outputFunc = outputAbi.find( (f) => f.type === 'function' && f.name === 'aggregate', ); const name = outputFunc?.name || ''; const outputs = outputFunc?.outputs || []; const response = Abi.decode(name, outputs, callData); return response; } async function callDeployless2( provider: BaseProvider, callRequests: CallRequest[], block?: BlockTag, ): Promise<Result> { const inputAbi: JsonFragment[] = deploylessMulticall2Abi; const constructor = inputAbi.find((f) => f.type === 'constructor'); const inputs = constructor?.inputs || []; const args = Abi.encodeConstructor(inputs, [false, callRequests]); const data = hexConcat([deploylessMulticall2Bytecode, args]); const callData = await provider.call( { data, }, block, ); const outputAbi: JsonFragment[] = multicall2Abi; const outputFunc = outputAbi.find( (f) => f.type === 'function' && f.name === 'tryAggregate', ); const name = outputFunc?.name || ''; const outputs = outputFunc?.outputs || []; // Note "[0]": low-level calls don't automatically unwrap tuple output const response = Abi.decode(name, outputs, callData)[0]; return response as CallResult[]; } async function callDeployless3( provider: BaseProvider, callRequests: CallRequest[], block?: BlockTag, ): Promise<Result> { const inputAbi: JsonFragment[] = deploylessMulticall3Abi; const constructor = inputAbi.find((f) => f.type === 'constructor'); const inputs = constructor?.inputs || []; const args = Abi.encodeConstructor(inputs, [callRequests]); const data = hexConcat([deploylessMulticall3Bytecode, args]); const callData = await provider.call( { data, }, block, ); const outputAbi: JsonFragment[] = multicall3Abi; const outputFunc = outputAbi.find( (f) => f.type === 'function' && f.name === 'aggregate3', ); const name = outputFunc?.name || ''; const outputs = outputFunc?.outputs || []; // Note "[0]": low-level calls don't automatically unwrap tuple output const response = Abi.decode(name, outputs, callData)[0]; return response as CallResult[]; } export { Call, CallResult, all, tryAll, tryEach };