From b15a8fb040a53d38fb2ed71cecea917c9fb467de Mon Sep 17 00:00:00 2001 From: Cesare Naldi <3353250+cesarenaldi@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:28:12 +0200 Subject: [PATCH 1/2] fix(client): split rejected rpc batches --- packages/client/src/rpc.test.ts | 44 +++++++++++++++++++++++++++++++++ packages/client/src/rpc.ts | 30 ++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/packages/client/src/rpc.test.ts b/packages/client/src/rpc.test.ts index c5e1695b..5c8706b9 100644 --- a/packages/client/src/rpc.test.ts +++ b/packages/client/src/rpc.test.ts @@ -79,6 +79,50 @@ describe('JsonRpcClient', () => { ).resolves.toEqual(['0xaaaa', '0xbbbb']); }); + it('recovers failed eth_call batches while preserving result order', async () => { + const to = expectEvmAddress('0x0000000000000000000000000000000000000001'); + let requestCount = 0; + + server.use( + http.post(root, async ({ request }) => { + requestCount += 1; + + const body = (await request.json()) as + | { + id: number; + params: [{ data: string }]; + } + | unknown[]; + + if (Array.isArray(body)) { + return new HttpResponse(null, { status: 500 }); + } + + return HttpResponse.json({ + jsonrpc: '2.0', + id: body.id, + result: body.params[0].data, + }); + }), + ); + const client = new JsonRpcClient({ url: root }); + + await expect( + client.ethCallBatch([ + { to, data: '0x11111111' }, + { to, data: '0x22222222' }, + { to, data: '0x33333333' }, + { to, data: '0x44444444' }, + ]), + ).resolves.toEqual([ + '0x11111111', + '0x22222222', + '0x33333333', + '0x44444444', + ]); + expect(requestCount).toBe(7); + }); + it('wraps JSON-RPC errors as rejected requests', async () => { const to = expectEvmAddress('0x0000000000000000000000000000000000000001'); server.use( diff --git a/packages/client/src/rpc.ts b/packages/client/src/rpc.ts index 776278e1..4e377c20 100644 --- a/packages/client/src/rpc.ts +++ b/packages/client/src/rpc.ts @@ -80,6 +80,36 @@ export class JsonRpcClient { return []; } + return this.#ethCallBatchWithSplit(requests); + } + + async #ethCallBatchWithSplit( + requests: readonly EthCallRequest[], + ): Promise { + if (requests.length === 1) { + return [await this.ethCall(requests[0] as EthCallRequest)]; + } + + try { + return await this.#postEthCallBatch(requests); + } catch (error) { + if (!(error instanceof RequestRejectedError) || error.status < 500) { + throw error; + } + + const midpoint = Math.ceil(requests.length / 2); + const [left, right] = await Promise.all([ + this.#ethCallBatchWithSplit(requests.slice(0, midpoint)), + this.#ethCallBatchWithSplit(requests.slice(midpoint)), + ]); + + return [...left, ...right]; + } + } + + async #postEthCallBatch( + requests: readonly EthCallRequest[], + ): Promise { const responses = await this.#postBatch( requests.map((request, index) => ({ jsonrpc: '2.0', From e41ec200477627131fde20c721d5a240082bc1f1 Mon Sep 17 00:00:00 2001 From: Cesare Naldi <3353250+cesarenaldi@users.noreply.github.com> Date: Wed, 17 Jun 2026 09:57:49 +0200 Subject: [PATCH 2/2] chore: add rpc batch retry changeset --- .changeset/rpc-batch-split-retry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rpc-batch-split-retry.md diff --git a/.changeset/rpc-batch-split-retry.md b/.changeset/rpc-batch-split-retry.md new file mode 100644 index 00000000..ab046b52 --- /dev/null +++ b/.changeset/rpc-batch-split-retry.md @@ -0,0 +1,5 @@ +--- +"@polymarket/client": patch +--- + +Retry rejected JSON-RPC `eth_call` batches by recursively splitting them into smaller batches.