diff --git a/src/multicall-provider.ts b/src/multicall-provider.ts index 952de3c..b2b8823 100644 --- a/src/multicall-provider.ts +++ b/src/multicall-provider.ts @@ -25,10 +25,19 @@ export interface ContractCall { export interface MulticallProviderOverload { _multicallDelay: number; multicallDelay: number; - _performMulticall: DebouncedFunc<() => Promise>; + maxMulticallDataLength: number; + _performMulticall: () => Promise; + _debouncedPerformMulticall: DebouncedFunc<() => Promise>; } export class MulticallProvider { + /** + * Wraps a given ethers provider to enable automatic call batching. + * @param provider The underlying provider to use to batch calls. + * @param delay The delay (in milliseconds) to wait before performing the ongoing batch of calls. Defaults to 16ms. + * @param maxMulticallDataLength The maximum total calldata length allowed in a multicall batch, to avoid having the RPC backend to revert because of too large (or too long) request. Set to 0 to disable this behavior. Defaults to 200k. + * @returns The multicall provider, which is a proxy to the given provider, automatically batching any call performed with it. + */ public static wrap( provider: T, delay = 16, @@ -62,7 +71,7 @@ export class MulticallProvider { let queuedCalls: ContractCall[] = []; - const _performMulticall = async () => { + multicallProvider._performMulticall = async function () { const _queuedCalls = [...queuedCalls]; if (queuedCalls.length === 0) return; @@ -93,7 +102,7 @@ export class MulticallProvider { callStructs.forEach((callStruct) => { const newLength = currentLength + callStruct.callData.length; - if (newLength > maxMulticallDataLength) { + if (this.maxMulticallDataLength > 0 && newLength > this.maxMulticallDataLength) { currentLength = callStruct.callData.length; calls.push([]); } else currentLength = newLength; @@ -135,15 +144,16 @@ export class MulticallProvider { return this._multicallDelay; }, set: function (delay: number) { - this._performMulticall?.flush(); + this._debouncedPerformMulticall?.flush(); this._multicallDelay = delay; - this._performMulticall = _debounce(_performMulticall, delay); + this._debouncedPerformMulticall = _debounce(this._performMulticall, delay); }, enumerable: true, }); multicallProvider.multicallDelay = delay; + multicallProvider.maxMulticallDataLength = maxMulticallDataLength; // Overload `BaseProvider.perform` @@ -166,7 +176,7 @@ export class MulticallProvider { if (!to || !data || multicallVersion == null || multicallAddresses.has(to.toLowerCase())) return _perform(method, params); - this._performMulticall(); + this._debouncedPerformMulticall(); return new Promise((resolve, reject) => { queuedCalls.push({ diff --git a/test/multicall-provider.spec.ts b/test/multicall-provider.spec.ts index ea9ae04..6e18b1a 100644 --- a/test/multicall-provider.spec.ts +++ b/test/multicall-provider.spec.ts @@ -46,6 +46,16 @@ describe("ethers-multicall-provider", () => { expect(multicallProvider.multicallDelay === newDelay); }); + it("should set maxMulticallDataLength", () => { + const multicallProvider = MulticallProvider.wrap(provider); + + const newMaxMulticallDataLength = multicallProvider.maxMulticallDataLength + 1; + + multicallProvider.maxMulticallDataLength = newMaxMulticallDataLength; + + expect(multicallProvider.maxMulticallDataLength === newMaxMulticallDataLength); + }); + it("should have properties shallow cloned", () => { const multicallProvider = MulticallProvider.wrap(provider);