import type { JsonFragment, JsonFragmentType, Provider } from 'ethers'; import { Contract, Result, concat } from 'ethers'; import deploylessMulticallAbi from './abi/deploylessMulticall.json' assert { type: 'json' }; import deploylessMulticall2Abi from './abi/deploylessMulticall2.json' assert { type: 'json' }; import deploylessMulticall3Abi from './abi/deploylessMulticall3.json' assert { type: 'json' }; import multicallAbi from './abi/multicall.json' assert { type: 'json' }; import multicall2Abi from './abi/multicall2.json' assert { type: 'json' }; import multicall3Abi from './abi/multicall3.json' assert { type: 'json' }; import type { Params } from './abi.js'; import Abi from './abi.js'; import type { Multicall } from './multicall.js'; import { deploylessMulticall2Bytecode, deploylessMulticall3Bytecode, deploylessMulticallBytecode, } from './multicall.js'; import type { BlockTag } from './provider.js'; interface CallRequest { target: string; callData: string; } interface CallOverrides { blockTag?: BlockTag; from?: string; } interface Call { contract: { address: string; }; name: string; inputs: readonly JsonFragmentType[]; outputs: readonly JsonFragmentType[]; params: Params; } interface FailableCall extends Call { canFail: boolean; } interface CallResult { success: boolean; returnData: string; } async function all( provider: Provider, multicall: Multicall | null, calls: Call[], overrides?: CallOverrides, ): Promise { 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 response = contract && contract.aggregate ? await contract.aggregate(callRequests, overrides || {}) : await callDeployless(provider, callRequests, overrides?.blockTag); const callCount = calls.length; const callResult: T[] = []; for (let i = 0; i < callCount; i++) { const call = calls[i]; if (!call) { throw new Error('Unable to access the call'); } const name = call.name; const outputs = call.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( provider: Provider, multicall2: Multicall | null, calls: Call[], overrides?: CallOverrides, ): 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 response: CallResult[] = contract && contract.tryAggregate ? await contract.tryAggregate(false, callRequests, overrides || {}) : await callDeployless2(provider, callRequests, overrides?.blockTag); const callCount = calls.length; const callResult: (T | null)[] = []; for (let i = 0; i < callCount; i++) { const call = calls[i]; if (!call) { throw new Error('Unable to access the call'); } const name = call.name; const outputs = call.outputs; const result = response[i]; if (!result) { throw new Error('Unable to access the result'); } if (!result.success) { callResult.push(null); } else { try { const params = Abi.decode(name, outputs, result.returnData); const data = outputs.length === 1 ? params[0] : params; callResult.push(data); } catch (e) { // Failed to decode the data: most likely calling non-existing contract callResult.push(null); } } } return callResult; } async function tryEach( provider: Provider, multicall3: Multicall | null, calls: FailableCall[], overrides?: CallOverrides, ): 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 response: CallResult[] = contract && contract.aggregate3 ? await contract.aggregate3(callRequests, overrides || {}) : await callDeployless3(provider, callRequests, overrides?.blockTag); const callCount = calls.length; const callResult: (T | null)[] = []; for (let i = 0; i < callCount; i++) { const call = calls[i]; if (!call) { throw new Error('Unable to access the call'); } const name = call.name; const outputs = call.outputs; const result = response[i]; if (!result) { throw new Error('Unable to access the result'); } if (!result.success) { callResult.push(null); } else { try { const params = Abi.decode(name, outputs, result.returnData); const data = outputs.length === 1 ? params[0] : params; callResult.push(data); } catch (e) { // Failed to decode the data: most likely calling non-existing contract callResult.push(null); } } } return callResult; } async function callDeployless( provider: Provider, callRequests: CallRequest[], block?: BlockTag, ): Promise { const inputAbi: JsonFragment[] = deploylessMulticallAbi; const constructor = inputAbi.find((f) => f.type === 'constructor'); const inputs = constructor?.inputs || []; const args = Abi.encodeConstructor(inputs, [callRequests]); const data = concat([deploylessMulticallBytecode, args]); const callData = await provider.call({ data, blockTag: 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: Provider, callRequests: CallRequest[], block?: BlockTag, ): Promise { 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 = concat([deploylessMulticall2Bytecode, args]); const callData = await provider.call({ data, blockTag: 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; } async function callDeployless3( provider: Provider, callRequests: CallRequest[], block?: BlockTag, ): Promise { const inputAbi: JsonFragment[] = deploylessMulticall3Abi; const constructor = inputAbi.find((f) => f.type === 'constructor'); const inputs = constructor?.inputs || []; const args = Abi.encodeConstructor(inputs, [callRequests]); const data = concat([deploylessMulticall3Bytecode, args]); const callData = await provider.call({ data, blockTag: 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; } export { all, tryAll, tryEach }; export type { Call, CallOverrides, CallResult };