/
multicall.ts
79 lines (72 loc) · 2.36 KB
/
multicall.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import type { Provider } from '@ethersproject/providers';
import { IMulticall3__factory } from '@xfai-labs/dex';
import { CallOverrides, Contract, Signer } from 'ethers';
import { Xfai } from './xfai';
type Factory<T extends Contract> = {
createInterface: () => T['interface'];
connect: (address: string, signerOrProvider: Signer | Provider) => T;
};
type RemoveCallOverrides<T> = T extends [...args: infer O, overrides?: CallOverrides] ? O : never;
type CallArg<
T extends Contract,
F extends keyof ReturnType<T['connect']>['callStatic'] & string,
> = {
contractAddress: string;
function_name: F;
arguments: Readonly<
[...RemoveCallOverrides<Parameters<ReturnType<T['connect']>['callStatic'][F]>>]
>;
allowFailure?: boolean;
};
export async function multicall<
T extends Contract,
B extends boolean,
F extends keyof T['callStatic'] & string,
>(
xfai: Xfai,
factory: Factory<T>,
calls: Array<CallArg<T, F>>,
options?: {
key?: (arg: CallArg<T, F>, index: number) => Promise<string | number> | (string | number);
allowFailure?: B;
callOverrides?: CallOverrides;
},
) {
const {
key = (arg: CallArg<T, F>) => arg.contractAddress,
allowFailure = false,
callOverrides = {},
} = options ?? {};
const Interface = factory.createInterface();
const results = await IMulticall3__factory.connect(
xfai.multicallAddress,
xfai.provider,
).callStatic.aggregate3(
calls.map((arg) => ({
target: arg.contractAddress,
callData: Interface.encodeFunctionData(arg.function_name, arg.arguments),
allowFailure: arg.allowFailure ?? allowFailure,
})),
callOverrides,
);
if (results.length !== calls.length) {
throw new Error('Multicall failed');
}
type Return = Awaited<ReturnType<ReturnType<T['connect']>['callStatic'][F]>>;
return Object.fromEntries(
await Promise.all(
results.map(async (r, i) => {
if (!r.success) {
[await key(calls[i], i), undefined];
}
try {
const decoded = Interface.decodeFunctionResult(calls[i].function_name, r.returnData);
return [await key(calls[i], i), decoded.length === 1 ? decoded[0] : decoded];
} catch {
/* empty */
}
return [await key(calls[i], i), undefined];
}),
),
) as Record<Awaited<ReturnType<typeof key>>, B extends true ? undefined | Return : Return>;
}