Repository: ponder-sh/ponder
Pull request #1710 (2025-04-30 15:06:48)

`getLogsRetryRanges` for swell chain


Patch:
diff --git a/.changeset/blue-lions-talk.md b/.changeset/blue-lions-talk.md
new file mode 100644
index 00000000..11128791
--- /dev/null
+++ b/.changeset/blue-lions-talk.md
@@ -0,0 +1,5 @@
+---
+"@ponder/utils": patch
+---
+
+Updated eth_getLogs retry helper to support swell chain.
diff --git a/docs/pages/docs/getting-started/new-project.mdx b/docs/pages/docs/getting-started/new-project.mdx
index f3e630b6..47acec38 100644
--- a/docs/pages/docs/getting-started/new-project.mdx
+++ b/docs/pages/docs/getting-started/new-project.mdx
@@ -64,7 +64,7 @@ npm run dev
 
 ### Add an RPC URL
 
-Ponder fetches data using the standard Ethereum RPC API. To get started, you'll need an RPC URL from a provider like Alchemy or Infura.
+Ponder fetches data using the standard Ethereum JSON-RPC API. To get started, you'll need an RPC URL from a provider like Alchemy or Infura.
 
 Open `.env.local` and paste in RPC URLs for any networks that your project uses.
 
diff --git a/packages/utils/src/getLogsRetryHelper.ts b/packages/utils/src/getLogsRetryHelper.ts
index bf2b4222..641779fa 100644
--- a/packages/utils/src/getLogsRetryHelper.ts
+++ b/packages/utils/src/getLogsRetryHelper.ts
@@ -22,10 +22,10 @@ export type GetLogsRetryHelperParameters = {
 export type GetLogsRetryHelperReturnType =
   | {
       shouldRetry: true;
-      /** Suggested values to use for (fromBlock, toBlock) in follow-up eth_getLogs requests. */
-      ranges: { fromBlock: Hex; toBlock: Hex }[];
       /** `true` if the error message suggested to use this range on retry. */
       isSuggestedRange: boolean;
+      /** Suggested values to use for (fromBlock, toBlock) in follow-up eth_getLogs requests. */
+      ranges: { fromBlock: Hex; toBlock: Hex }[];
     }
   | {
       shouldRetry: false;
@@ -149,8 +149,8 @@ export const getLogsRetryHelper = ({
 
     return {
       shouldRetry: true,
-      ranges,
       isSuggestedRange: true,
+      ranges,
     } as const;
   }
 
@@ -215,7 +215,7 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
-  // llamarpc
+  // llamarpc, ankr
   match = sError.match(/query exceeds max results/);
   if (match !== null) {
     const ranges = chunk({
@@ -297,6 +297,27 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
+  // base
+  match = sError.match(/no backend is currently healthy to serve traffic/);
+  if (match !== null) {
+    const ranges = chunk({
+      params,
+      range:
+        (hexToBigInt(params[0].toBlock) - hexToBigInt(params[0].fromBlock)) /
+        2n,
+    });
+
+    if (isRangeUnchanged(params, ranges)) {
+      return { shouldRetry: false } as const;
+    }
+
+    return {
+      shouldRetry: true,
+      isSuggestedRange: false,
+      ranges,
+    } as const;
+  }
+
   // arbitrum
   match = sError.match(/logs matched by query exceeds limit of 10000/);
   if (match !== null) {
@@ -416,6 +437,25 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
+  // swell
+  match = sError.match(/block range greater than ([\d,.]+) max/);
+  if (match !== null) {
+    const ranges = chunk({
+      params,
+      range: BigInt(match[1]!.replace(/[,.]/g, "")),
+    });
+
+    if (isRangeUnchanged(params, ranges)) {
+      return { shouldRetry: false } as const;
+    }
+
+    return {
+      shouldRetry: true,
+      ranges,
+      isSuggestedRange: true,
+    } as const;
+  }
+
   // No match found
   return {
     shouldRetry: false,


Test patch:
diff --git a/packages/utils/src/_test/1rpc.test.ts b/packages/utils/src/_test/1rpc.test.ts
index bd24cedb..e2034770 100644
--- a/packages/utils/src/_test/1rpc.test.ts
+++ b/packages/utils/src/_test/1rpc.test.ts
@@ -50,6 +50,7 @@ test("1rpc block range", async () => {
 
   expect(retry).toStrictEqual({
     shouldRetry: true,
+    isSuggestedRange: true,
     ranges: [
       {
         fromBlock: numberToHex(fromBlock),
diff --git a/packages/utils/src/_test/ankr.test.ts b/packages/utils/src/_test/ankr.test.ts
index a66dd500..adcfd4ce 100644
--- a/packages/utils/src/_test/ankr.test.ts
+++ b/packages/utils/src/_test/ankr.test.ts
@@ -3,7 +3,7 @@ import { expect, test } from "vitest";
 import { getLogsRetryHelper } from "../getLogsRetryHelper.js";
 import { type Params, UNI, fromBlock, getRequest } from "./utils.js";
 
-const request = getRequest("https://rpc.ankr.com/eth");
+const request = getRequest(process.env.RPC_URL_ANKR_1!);
 const maxBlockRange = 3000n;
 
 test("ankr success", async () => {
@@ -21,7 +21,7 @@ test("ankr success", async () => {
   expect(logs).toHaveLength(13);
 });
 
-test("ankr block range", async () => {
+test("ankr response size", async () => {
   const params: Params = [
     {
       fromBlock: numberToHex(fromBlock),
@@ -35,24 +35,17 @@ test("ankr block range", async () => {
   }).catch((error) => error);
 
   expect(error).toBeInstanceOf(RpcError);
-  expect(JSON.stringify(error)).includes("block range is too wide");
+  expect(JSON.stringify(error)).includes("query exceeds max results");
 
   const retry = getLogsRetryHelper({
     params,
     error: error,
   });
 
-  expect(retry).toStrictEqual({
-    shouldRetry: true,
-    ranges: [
-      {
-        fromBlock: numberToHex(fromBlock),
-        toBlock: numberToHex(fromBlock + maxBlockRange),
-      },
-      {
-        fromBlock: numberToHex(fromBlock + maxBlockRange + 1n),
-        toBlock: numberToHex(fromBlock + maxBlockRange + 1n),
-      },
-    ],
+  expect(retry.shouldRetry).toBe(true);
+  expect(retry.ranges).toHaveLength(2);
+  expect(retry.ranges![0]).toStrictEqual({
+    fromBlock: numberToHex(fromBlock),
+    toBlock: numberToHex(fromBlock + maxBlockRange / 2n),
   });
 });
diff --git a/packages/utils/src/_test/base.test.ts b/packages/utils/src/_test/base.test.ts
index b1523343..3107563a 100644
--- a/packages/utils/src/_test/base.test.ts
+++ b/packages/utils/src/_test/base.test.ts
@@ -1,11 +1,10 @@
-import { InvalidInputRpcError, numberToHex } from "viem";
+import { numberToHex } from "viem";
 import { expect, test } from "vitest";
 import { getLogsRetryHelper } from "../getLogsRetryHelper.js";
 import { type Params, getRequest } from "./utils.js";
 
 const request = getRequest("https://mainnet.base.org");
 const fromBlock = 10_000_000n;
-const maxBlockRange = 2000n;
 
 test(
   "base success",
@@ -16,12 +15,12 @@ test(
         {
           address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22",
           fromBlock: numberToHex(fromBlock),
-          toBlock: numberToHex(fromBlock + maxBlockRange),
+          toBlock: numberToHex(fromBlock + 1_000n),
         },
       ],
     });
 
-    expect(logs).toHaveLength(218);
+    expect(logs).toHaveLength(77);
   },
   { timeout: 15_000 },
 );
@@ -33,7 +32,7 @@ test(
       {
         address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22",
         fromBlock: numberToHex(fromBlock),
-        toBlock: numberToHex(fromBlock + maxBlockRange + 1n),
+        toBlock: numberToHex(fromBlock + 20_000n),
       },
     ];
 
@@ -42,26 +41,20 @@ test(
       params,
     }).catch((error) => error);
 
-    expect(error).toBeInstanceOf(InvalidInputRpcError);
-    expect(JSON.stringify(error)).includes("block range too large");
+    expect(JSON.stringify(error)).includes(
+      "no backend is currently healthy to serve traffic",
+    );
 
     const retry = getLogsRetryHelper({
       params,
       error,
     });
 
-    expect(retry).toStrictEqual({
-      shouldRetry: true,
-      ranges: [
-        {
-          fromBlock: numberToHex(fromBlock),
-          toBlock: numberToHex(fromBlock + maxBlockRange),
-        },
-        {
-          fromBlock: numberToHex(fromBlock + maxBlockRange + 1n),
-          toBlock: numberToHex(fromBlock + maxBlockRange + 1n),
-        },
-      ],
+    expect(retry.shouldRetry).toBe(true);
+    expect(retry.ranges).toHaveLength(2);
+    expect(retry.ranges![0]).toStrictEqual({
+      fromBlock: numberToHex(fromBlock),
+      toBlock: numberToHex(fromBlock + 10_000n),
     });
   },
   { timeout: 15_000 },
diff --git a/packages/utils/src/_test/blast.test.ts b/packages/utils/src/_test/blast.test.ts
index 05cfbc45..ab124b92 100644
--- a/packages/utils/src/_test/blast.test.ts
+++ b/packages/utils/src/_test/blast.test.ts
@@ -54,6 +54,7 @@ test(
 
     expect(retry).toStrictEqual({
       shouldRetry: true,
+      isSuggestedRange: true,
       ranges: [
         {
           fromBlock: numberToHex(fromBlock),
diff --git a/packages/utils/src/_test/blockpi.test.ts b/packages/utils/src/_test/blockpi.test.ts
index e73e2429..1ada89bd 100644
--- a/packages/utils/src/_test/blockpi.test.ts
+++ b/packages/utils/src/_test/blockpi.test.ts
@@ -53,6 +53,7 @@ test(
 
     expect(retry).toStrictEqual({
       shouldRetry: true,
+      isSuggestedRange: true,
       ranges: [
         {
           fromBlock: numberToHex(fromBlock),
diff --git a/packages/utils/src/_test/chainstack.test.ts b/packages/utils/src/_test/chainstack.test.ts
index 8455d8f7..f58fcd06 100644
--- a/packages/utils/src/_test/chainstack.test.ts
+++ b/packages/utils/src/_test/chainstack.test.ts
@@ -51,6 +51,7 @@ test(
 
     expect(retry).toStrictEqual({
       shouldRetry: true,
+      isSuggestedRange: true,
       ranges: [
         {
           fromBlock: numberToHex(fromBlock),
diff --git a/packages/utils/src/_test/cloudflare.test.ts b/packages/utils/src/_test/cloudflare.test.ts
index 3b981bf8..a88f0f1e 100644
--- a/packages/utils/src/_test/cloudflare.test.ts
+++ b/packages/utils/src/_test/cloudflare.test.ts
@@ -6,7 +6,7 @@ import { type Params, UNI, fromBlock, getRequest } from "./utils.js";
 const request = getRequest("https://cloudflare-eth.com");
 const maxBlockRange = 799n;
 
-test("cloudflare success", async () => {
+test.skip("cloudflare success", async () => {
   const logs = await request({
     method: "eth_getLogs",
     params: [
@@ -44,6 +44,7 @@ test("cloudflare block range", async () => {
 
   expect(retry).toStrictEqual({
     shouldRetry: true,
+    isSuggestedRange: true,
     ranges: [
       {
         fromBlock: numberToHex(fromBlock),
diff --git a/packages/utils/src/_test/coinbase.test.ts b/packages/utils/src/_test/coinbase.test.ts
index 78ffd5e8..8960cad2 100644
--- a/packages/utils/src/_test/coinbase.test.ts
+++ b/packages/utils/src/_test/coinbase.test.ts
@@ -50,6 +50,7 @@ test(
 
     expect(retry).toStrictEqual({
       shouldRetry: true,
+      isSuggestedRange: true,
       ranges: [
         {
           fromBlock: numberToHex(fromBlock),
diff --git a/packages/utils/src/_test/hyperliquid.test.ts b/packages/utils/src/_test/hyperliquid.test.ts
index 2bcba24b..46941ad4 100644
--- a/packages/utils/src/_test/hyperliquid.test.ts
+++ b/packages/utils/src/_test/hyperliquid.test.ts
@@ -3,11 +3,11 @@ import { expect, test } from "vitest";
 import { getLogsRetryHelper } from "../getLogsRetryHelper.js";
 import { type Params, getRequest } from "./utils.js";
 
-const request = getRequest("https://api.hyperliquid-testnet.xyz/evm");
+const request = getRequest("https://rpc.hyperliquid.xyz/evm");
 const fromBlock = 9419400;
 const maxBlockRange = 50;
 
-test("hyperliquid success", async () => {
+test.skip("hyperliquid success", async () => {
   const logs = await request({
     method: "eth_getLogs",
     params: [
@@ -21,7 +21,7 @@ test("hyperliquid success", async () => {
   expect(logs).toHaveLength(5);
 });
 
-test("hyperliquid block range", async () => {
+test.skip("hyperliquid block range", async () => {
   const params: Params = [
     {
       fromBlock: numberToHex(fromBlock),
diff --git a/packages/utils/src/_test/quicknode.test.ts b/packages/utils/src/_test/quicknode.test.ts
index 4a0f34aa..3678d093 100644
--- a/packages/utils/src/_test/quicknode.test.ts
+++ b/packages/utils/src/_test/quicknode.test.ts
@@ -46,6 +46,7 @@ test("quicknode block range", async () => {
 
   expect(retry).toStrictEqual({
     shouldRetry: true,
+    isSuggestedRange: true,
     ranges: [
       {
         fromBlock: numberToHex(fromBlock),
diff --git a/packages/utils/src/_test/swell.test.ts b/packages/utils/src/_test/swell.test.ts
new file mode 100644
index 00000000..171a5a22
--- /dev/null
+++ b/packages/utils/src/_test/swell.test.ts
@@ -0,0 +1,59 @@
+import { numberToHex } from "viem";
+import { expect, test } from "vitest";
+import { getLogsRetryHelper } from "../getLogsRetryHelper.js";
+import { type Params, getRequest } from "./utils.js";
+
+const request = getRequest("https://swell-mainnet.alt.technology");
+const fromBlock = 6002000n;
+const maxBlockRange = 1000n;
+
+test("swell success", async () => {
+  const logs = await request({
+    method: "eth_getLogs",
+    params: [
+      {
+        // address: "0x259813b665c8f6074391028ef782e27b65840d89",
+        fromBlock: numberToHex(fromBlock),
+        toBlock: numberToHex(fromBlock + maxBlockRange),
+      },
+    ],
+  });
+
+  expect(logs).toHaveLength(58);
+});
+
+test("swell block range", async () => {
+  const params: Params = [
+    {
+      fromBlock: numberToHex(fromBlock),
+      toBlock: numberToHex(fromBlock + maxBlockRange + 1n),
+    },
+  ];
+
+  const error = await request({
+    method: "eth_getLogs",
+    params,
+  }).catch((error) => error);
+
+  expect(JSON.stringify(error)).includes("block range greater than 1000 max");
+
+  const retry = getLogsRetryHelper({
+    params,
+    error,
+  });
+
+  expect(retry).toStrictEqual({
+    shouldRetry: true,
+    isSuggestedRange: true,
+    ranges: [
+      {
+        fromBlock: numberToHex(fromBlock),
+        toBlock: numberToHex(fromBlock + maxBlockRange),
+      },
+      {
+        fromBlock: numberToHex(fromBlock + maxBlockRange + 1n),
+        toBlock: numberToHex(fromBlock + maxBlockRange + 1n),
+      },
+    ],
+  });
+});
diff --git a/packages/utils/src/_test/thirdweb.test.ts b/packages/utils/src/_test/thirdweb.test.ts
index 6bd45ebe..9f12e91f 100644
--- a/packages/utils/src/_test/thirdweb.test.ts
+++ b/packages/utils/src/_test/thirdweb.test.ts
@@ -1,4 +1,4 @@
-import { MethodNotFoundRpcError, numberToHex } from "viem";
+import { LimitExceededRpcError, numberToHex } from "viem";
 import { expect, test } from "vitest";
 import { getLogsRetryHelper } from "../getLogsRetryHelper.js";
 import { type Params, UNI, WETH, fromBlock, getRequest } from "./utils.js";
@@ -35,7 +35,7 @@ test("thirdweb response size", async () => {
     params,
   }).catch((error) => error);
 
-  expect(error).toBeInstanceOf(MethodNotFoundRpcError);
+  expect(error).toBeInstanceOf(LimitExceededRpcError);
   expect(JSON.stringify(error)).includes(
     "Maximum allowed number of requested blocks is 1000",
   );
diff --git a/packages/utils/src/_test/zkevm.test.ts b/packages/utils/src/_test/zkevm.test.ts
index e91c1bdd..88a1c9af 100644
--- a/packages/utils/src/_test/zkevm.test.ts
+++ b/packages/utils/src/_test/zkevm.test.ts
@@ -21,11 +21,11 @@ test("zkevm success", async () => {
   expect(logs).toHaveLength(2979);
 });
 
-test("zkevm response size", async () => {
+test.skip("zkevm response size", async () => {
   const params: Params = [
     {
       fromBlock: numberToHex(fromBlock),
-      toBlock: numberToHex(fromBlock + 5_000n),
+      toBlock: numberToHex(fromBlock + 10_000n),
     },
   ];
 
diff --git a/packages/utils/src/_test/zksync.test.ts b/packages/utils/src/_test/zksync.test.ts
index 560b5857..c6d0060e 100644
--- a/packages/utils/src/_test/zksync.test.ts
+++ b/packages/utils/src/_test/zksync.test.ts
@@ -46,6 +46,7 @@ test("zksync block range", async () => {
 
   expect(retry).toStrictEqual({
     shouldRetry: true,
+    isSuggestedRange: true,
     ranges: [
       {
         fromBlock: numberToHex(fromBlock),