Repository: ponder-sh/ponder
Pull request #816 (2024-04-19 13:38:23)

adds support for automatically detecting blockpi retry ranges


Patch:
diff --git a/.changeset/cyan-shoes-sin.md b/.changeset/cyan-shoes-sin.md
new file mode 100644
index 00000000..068e65eb
--- /dev/null
+++ b/.changeset/cyan-shoes-sin.md
@@ -0,0 +1,6 @@
+---
+"create-ponder": patch
+"@ponder/core": patch
+---
+
+Added support for Windows via PowerShell and command prompt (cmd.exe).
diff --git a/.changeset/tame-suns-compete.md b/.changeset/tame-suns-compete.md
new file mode 100644
index 00000000..f304992d
--- /dev/null
+++ b/.changeset/tame-suns-compete.md
@@ -0,0 +1,5 @@
+---
+"@ponder/utils": patch
+---
+
+Added support for automatically detecting blockpi retry ranges.
diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
index 96911ad4..7c71f923 100644
--- a/.github/actions/setup/action.yml
+++ b/.github/actions/setup/action.yml
@@ -14,7 +14,7 @@ runs:
       uses: actions/setup-node@v3
       with:
         cache: pnpm
-        node-version: ${{ matrix.node-version == 'Node 18' && 18 || 20 }}
+        node-version: 20
 
     - name: Set up foundry
       uses: foundry-rs/foundry-toolchain@v1
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c81bf35d..ffc63699 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -14,10 +14,6 @@ jobs:
   lint:
     name: Lint
     runs-on: ubuntu-latest
-    strategy:
-      fail-fast: false
-      matrix:
-        node-version: ["Node 20"]
     steps:
       - name: Clone repository
         uses: actions/checkout@v4
@@ -37,10 +33,6 @@ jobs:
   build:
     name: Build
     runs-on: ubuntu-latest
-    strategy:
-      fail-fast: false
-      matrix:
-        node-version: ["Node 20"]
     steps:
       - name: Clone repository
         uses: actions/checkout@v4
@@ -53,13 +45,12 @@ jobs:
 
   test:
     name: Test [core]
-    runs-on: "ubuntu-latest"
+    runs-on: ubuntu-latest
     timeout-minutes: 5
     strategy:
       fail-fast: false
       matrix:
         database: [Postgres, SQLite]
-        node-version: ["Node 20"]
     steps:
       - name: Clone repository
         uses: actions/checkout@v4
@@ -82,12 +73,8 @@ jobs:
 
   test-create-ponder:
     name: Test [create-ponder]
-    runs-on: "ubuntu-latest"
+    runs-on: ubuntu-latest
     timeout-minutes: 5
-    strategy:
-      fail-fast: false
-      matrix:
-        node-version: ["Node 20"]
     steps:
       - name: Clone repository
         uses: actions/checkout@v4
@@ -102,7 +89,7 @@ jobs:
 
   test-common:
     name: Test [common]
-    runs-on: "ubuntu-latest"
+    runs-on: ubuntu-latest
     strategy:
       fail-fast: false
       matrix:
@@ -112,9 +99,9 @@ jobs:
         uses: actions/checkout@v4
 
       - name: Set up pnpm
-        uses: pnpm/action-setup@v2
+        uses: pnpm/action-setup@v3
         with:
-          version: latest
+          version: 8.6.10
 
       - name: Install Node
         if: ${{ matrix.runtime-version == 'Node' }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4ce0f0ca..cbb1423f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,9 +9,6 @@ jobs:
   release:
     name: Release
     runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        node-version: ["Node 20"]
     steps:
       - name: Clone repository
         uses: actions/checkout@v4.1.0
diff --git a/.github/workflows/utils.yml b/.github/workflows/utils.yml
index 44a598fa..628c894b 100644
--- a/.github/workflows/utils.yml
+++ b/.github/workflows/utils.yml
@@ -8,7 +8,7 @@ on:
 jobs:
   test-get-logs:
     name: Test getLotsRetryHelper
-    runs-on: "ubuntu-latest"
+    runs-on: ubuntu-latest
     strategy:
       fail-fast: false
       matrix: 
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
new file mode 100644
index 00000000..4e70bfb1
--- /dev/null
+++ b/.github/workflows/windows.yml
@@ -0,0 +1,51 @@
+name: Windows
+
+on:
+  push:
+    branches:
+      - main
+
+jobs:
+  test:
+    name: Test [core]
+    runs-on: windows-latest
+    timeout-minutes: 10
+    strategy:
+      fail-fast: false
+      matrix:
+        database: [Postgres, SQLite]
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v4
+
+      - name: Setup Postgres
+        if: ${{ matrix.database == 'Postgres' }}
+        uses: ikalnytskyi/action-setup-postgres@v4
+        id: postgres
+
+      - name: Setup
+        uses: ./.github/actions/setup
+
+      - name: Codegen
+        run: cd packages/core/ && pnpm wagmi generate
+
+      - name: Test
+        run: pnpm --filter core test:typecheck
+        env:
+          DATABASE_URL: ${{ matrix.database == 'Postgres' && steps.postgres.outputs.connection-uri || '' }}
+
+  test-create-ponder:
+    name: Test [create-ponder]
+    runs-on: windows-latest
+    timeout-minutes: 10
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v4
+
+      - name: Setup
+        uses: ./.github/actions/setup
+
+      - name: Test
+        run: pnpm --filter create-ponder test
+        env:
+          ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
\ No newline at end of file
diff --git a/docs/pages/docs/getting-started/installation.mdx b/docs/pages/docs/getting-started/installation.mdx
index 3c4d8349..9cb6a913 100644
--- a/docs/pages/docs/getting-started/installation.mdx
+++ b/docs/pages/docs/getting-started/installation.mdx
@@ -11,19 +11,15 @@ The [create-ponder CLI tool](/docs/getting-started/new-project) is the best way
 
 ## System requirements
 
-- MacOS, Linux, or Windows [__using WSL__](#windows).
+- MacOS, Linux, or Windows
 - [Node.js 18 or 20](https://nodejs.org/en)
-- npm, [yarn](https://yarnpkg.com/getting-started/install), or [pnpm](https://pnpm.io/installation) (recommended)
+- [pnpm](https://pnpm.io/installation) (recommended), [yarn](https://yarnpkg.com/getting-started/install), or npm
 
 ## FAQ
 
 ### Windows
 
-Ponder currently requires WSL (the Windows Subsystem for Linux) to run on Windows machines. To get started, we strongly recommend following this well-written guide for Node.js app development using WSL.
-
-Guide: [Install Node.js on Windows Subsystem for Linux (WSL2)](https://learn.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-wsl#install-nvm-nodejs-and-npm)
-
-<Callout type="info">We're keen to collaborate with an experienced open-source Windows developer on supporting Windows without WSL. If you'd like to contribute, please chime in on GitHub ([#421](https://github.com/ponder-sh/ponder/issues/421)).</Callout>
+As of `0.4.7`, Ponder supports Windows via PowerShell, command prompt (cmd.exe), or Windows Subsystem for Linux (WSL). If you encounter a Windows-specific problem, please [open an issue](https://github.com/ponder-sh/ponder/issues).
 
 ### `better-sqlite3` installation error
 
diff --git a/packages/core/package.json b/packages/core/package.json
index 40364ee8..075db13d 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -43,9 +43,9 @@
   },
   "dependencies": {
     "@babel/code-frame": "^7.23.4",
+    "@commander-js/extra-typings": "^12.0.1",
     "@hono/graphql-server": "^0.4.3",
     "@hono/node-server": "^1.8.2",
-    "@commander-js/extra-typings": "^12.0.1",
     "@ponder/utils": "workspace:*",
     "abitype": "^0.10.2",
     "async-mutex": "^0.4.0",
@@ -85,6 +85,7 @@
     "@types/react": "^18.2.38",
     "@viem/anvil": "^0.0.6",
     "@wagmi/cli": "^1.5.2",
+    "rimraf": "^5.0.5",
     "tsup": "^8.0.1",
     "vitest": "^1.0.2"
   },
diff --git a/packages/core/src/bin/commands/codegen.ts b/packages/core/src/bin/commands/codegen.ts
index 2ae10177..4cf5affa 100644
--- a/packages/core/src/bin/commands/codegen.ts
+++ b/packages/core/src/bin/commands/codegen.ts
@@ -21,6 +21,7 @@ export async function codegen({ cliOptions }: { cliOptions: CliOptions }) {
       service: "process",
       msg: `Invalid Node.js version. Expected >=18.14, detected ${major}.${minor}.`,
     });
+    logger.kill();
     process.exit(1);
   }
 
diff --git a/packages/core/src/bin/commands/dev.ts b/packages/core/src/bin/commands/dev.ts
index 38a1c6a9..0f5d7486 100644
--- a/packages/core/src/bin/commands/dev.ts
+++ b/packages/core/src/bin/commands/dev.ts
@@ -25,6 +25,7 @@ export async function dev({ cliOptions }: { cliOptions: CliOptions }) {
       service: "process",
       msg: `Invalid Node.js version. Expected >=18.14, detected ${major}.${minor}.`,
     });
+    logger.kill();
     process.exit(1);
   }
 
diff --git a/packages/core/src/bin/commands/serve.ts b/packages/core/src/bin/commands/serve.ts
index 5d92c332..54bb9495 100644
--- a/packages/core/src/bin/commands/serve.ts
+++ b/packages/core/src/bin/commands/serve.ts
@@ -25,6 +25,7 @@ export async function serve({ cliOptions }: { cliOptions: CliOptions }) {
       service: "process",
       msg: `Invalid Node.js version. Expected >=18.14, detected ${major}.${minor}.`,
     });
+    logger.kill();
     process.exit(1);
   }
 
diff --git a/packages/core/src/bin/commands/start.ts b/packages/core/src/bin/commands/start.ts
index feb1eec2..2c406ff3 100644
--- a/packages/core/src/bin/commands/start.ts
+++ b/packages/core/src/bin/commands/start.ts
@@ -22,6 +22,7 @@ export async function start({ cliOptions }: { cliOptions: CliOptions }) {
       service: "process",
       msg: `Invalid Node.js version. Expected >=18.14, detected ${major}.${minor}.`,
     });
+    logger.kill();
     process.exit(1);
   }
 
diff --git a/packages/core/src/bin/utils/shutdown.ts b/packages/core/src/bin/utils/shutdown.ts
index e3e4c2a1..35f09e7b 100644
--- a/packages/core/src/bin/utils/shutdown.ts
+++ b/packages/core/src/bin/utils/shutdown.ts
@@ -1,3 +1,5 @@
+import os from "node:os";
+import readline from "node:readline";
 import type { Common } from "@/common/common.js";
 
 const SHUTDOWN_GRACE_PERIOD_MS = 5_000;
@@ -25,6 +27,7 @@ export function setupShutdown({
         service: "process",
         msg: "Failed to shutdown within 5 seconds, terminating (exit code 1)",
       });
+      common.logger.kill();
       process.exit(1);
     }, SHUTDOWN_GRACE_PERIOD_MS);
 
@@ -47,37 +50,43 @@ export function setupShutdown({
       msg: `Finished shutdown sequence, terminating (exit code ${code})`,
     });
 
+    common.logger.kill();
     process.exit(code);
   };
 
-  process.on(
-    "SIGINT",
-    async () => await shutdown({ reason: "Received SIGINT", code: 0 }),
-  );
-  process.on(
-    "SIGTERM",
-    async () => await shutdown({ reason: "Received SIGTERM", code: 0 }),
+  if (os.platform() === "win32") {
+    const readlineInterface = readline.createInterface({
+      input: process.stdin,
+      output: process.stdout,
+    });
+    readlineInterface.on("SIGINT", () =>
+      shutdown({ reason: "Received SIGINT", code: 0 }),
+    );
+  }
+
+  process.on("SIGINT", () => shutdown({ reason: "Received SIGINT", code: 0 }));
+  process.on("SIGTERM", () =>
+    shutdown({ reason: "Received SIGTERM", code: 0 }),
   );
-  process.on(
-    "SIGQUIT",
-    async () => await shutdown({ reason: "Received SIGQUIT", code: 0 }),
+  process.on("SIGQUIT", () =>
+    shutdown({ reason: "Received SIGQUIT", code: 0 }),
   );
 
-  process.on("uncaughtException", async (error: Error) => {
+  process.on("uncaughtException", (error: Error) => {
     common.logger.error({
       service: "process",
       msg: "Caught uncaughtException event with error:",
       error,
     });
-    await shutdown({ reason: "Received uncaughtException", code: 1 });
+    shutdown({ reason: "Received uncaughtException", code: 1 });
   });
-  process.on("unhandledRejection", async (error: Error) => {
+  process.on("unhandledRejection", (error: Error) => {
     common.logger.error({
       service: "process",
       msg: "Caught unhandledRejection event with error:",
       error,
     });
-    await shutdown({ reason: "Received unhandledRejection", code: 1 });
+    shutdown({ reason: "Received unhandledRejection", code: 1 });
   });
 
   return shutdown;
diff --git a/packages/core/src/build/service.ts b/packages/core/src/build/service.ts
index 40e4cf89..1edbe39c 100644
--- a/packages/core/src/build/service.ts
+++ b/packages/core/src/build/service.ts
@@ -27,7 +27,7 @@ import { parseViteNodeError } from "./stacktrace.js";
 export type Service = {
   // static
   common: Common;
-  indexingFunctionRegex: RegExp;
+  srcRegex: RegExp;
 
   // vite
   viteDevServer: ViteDevServer;
@@ -64,12 +64,13 @@ export const create = async ({
 }: {
   common: Common;
 }): Promise<Service> => {
-  const indexingFunctionRegex = new RegExp(
-    `^${common.options.srcDir.replace(
-      /[.*+?^${}()|[\]\\]/g,
-      "\\$&",
-    )}/.*\\.(js|ts)$`,
-  );
+  const escapeRegex = /[.*+?^${}()|[\]\\]/g;
+  const escapedSrcDir = common.options.srcDir
+    // If on Windows, use a POSIX path for this regex.
+    .replace(/\\/g, "/")
+    // Escape special characters in the path.
+    .replace(escapeRegex, "\\$&");
+  const srcRegex = new RegExp(`^${escapedSrcDir}/.*\\.(ts|js)$`);
 
   const viteLogger = {
     warnedMessages: new Set<string>(),
@@ -121,7 +122,7 @@ export const create = async ({
 
   return {
     common,
-    indexingFunctionRegex,
+    srcRegex,
     viteDevServer,
     viteNodeServer,
     viteNodeRunner,
@@ -196,12 +197,14 @@ export const start = async (
     const onFileChange = async (_file: string) => {
       if (isFileIgnored(_file)) return;
 
+      // Note that `toFilePath` always returns a POSIX path, even if you pass a Windows path.
       const file = toFilePath(
         normalizeModuleId(_file),
         common.options.rootDir,
       ).path;
 
       // Invalidate all modules that depend on the updated files.
+      // Note that `invalidateDepTree` accepts and returns POSIX paths, even on Windows.
       const invalidated = [
         ...buildService.viteNodeRunner.moduleCache.invalidateDepTree([file]),
       ];
@@ -209,6 +212,25 @@ export const start = async (
       // If no files were invalidated, no need to reload.
       if (invalidated.length === 0) return;
 
+      // Note that the paths in `invalidated` are POSIX, so we need to
+      // convert the paths in `options` to POSIX for this comparison.
+      // The `srcDir` regex is already converted to POSIX.
+      const hasConfigUpdate = invalidated.includes(
+        common.options.configFile.replace(/\\/g, "/"),
+      );
+      const hasSchemaUpdate = invalidated.includes(
+        common.options.schemaFile.replace(/\\/g, "/"),
+      );
+      const hasIndexingFunctionUpdate = invalidated.some((file) =>
+        buildService.srcRegex.test(file),
+      );
+
+      // This branch could trigger if you change a `note.txt` file within `src/`.
+      // Note: We could probably do a better job filtering out files in `isFileIgnored`.
+      if (!hasConfigUpdate && !hasSchemaUpdate && !hasIndexingFunctionUpdate) {
+        return;
+      }
+
       common.logger.info({
         service: "build",
         msg: `Hot reload ${invalidated
@@ -216,39 +238,31 @@ export const start = async (
           .join(", ")}`,
       });
 
-      if (invalidated.includes(common.options.configFile)) {
-        const executeConfigResult = await executeConfig(buildService);
-        if (executeConfigResult.status === "error") {
-          onBuild({ status: "error", error: executeConfigResult.error });
+      if (hasConfigUpdate) {
+        const result = await executeConfig(buildService);
+        if (result.status === "error") {
+          onBuild({ status: "error", error: result.error });
           return;
         }
-        rawBuild.config = executeConfigResult.config;
+        rawBuild.config = result.config;
       }
 
-      if (invalidated.includes(common.options.schemaFile)) {
-        const executeSchemaResult = await executeSchema(buildService);
-        if (executeSchemaResult.status === "error") {
-          onBuild({ status: "error", error: executeSchemaResult.error });
+      if (hasSchemaUpdate) {
+        const result = await executeSchema(buildService);
+        if (result.status === "error") {
+          onBuild({ status: "error", error: result.error });
           return;
         }
-        rawBuild.schema = executeSchemaResult.schema;
+        rawBuild.schema = result.schema;
       }
 
-      const hasIndexingFunctionUpdate = invalidated.some((file) =>
-        buildService.indexingFunctionRegex.test(file),
-      );
       if (hasIndexingFunctionUpdate) {
-        const executeIndexingFunctionsResult =
-          await executeIndexingFunctions(buildService);
-        if (executeIndexingFunctionsResult.status === "error") {
-          onBuild({
-            status: "error",
-            error: executeIndexingFunctionsResult.error,
-          });
+        const result = await executeIndexingFunctions(buildService);
+        if (result.status === "error") {
+          onBuild({ status: "error", error: result.error });
           return;
         }
-        rawBuild.indexingFunctions =
-          executeIndexingFunctionsResult.indexingFunctions;
+        rawBuild.indexingFunctions = result.indexingFunctions;
       }
 
       const buildResult = await validateAndBuild(buildService, rawBuild);
@@ -313,9 +327,10 @@ const executeIndexingFunctions = async (
   | { status: "success"; indexingFunctions: RawIndexingFunctions }
   | { status: "error"; error: Error }
 > => {
-  const files = glob.sync(
-    path.join(buildService.common.options.srcDir, "**/*.{js,mjs,ts,mts}"),
-  );
+  const pattern = path
+    .join(buildService.common.options.srcDir, "**/*.{js,mjs,ts,mts}")
+    .replace(/\\/g, "/");
+  const files = glob.sync(pattern);
 
   const executeResults = await Promise.all(
     files.map((file) => executeFile(buildService, { file })),
diff --git a/packages/core/src/common/logger.ts b/packages/core/src/common/logger.ts
index e7ec12a5..816d457c 100644
--- a/packages/core/src/common/logger.ts
+++ b/packages/core/src/common/logger.ts
@@ -1,6 +1,11 @@
 import path from "node:path";
 import pc from "picocolors";
-import { type LevelWithSilent, type Logger, pino } from "pino";
+import {
+  type LevelWithSilent,
+  type Logger,
+  type StreamEntry,
+  pino,
+} from "pino";
 
 type LogOptions = { msg?: string; service?: string } & { [key: string]: any };
 
@@ -12,15 +17,16 @@ const timeFormatter = new Intl.DateTimeFormat(undefined, {
 
 export class LoggerService {
   private logger: Logger;
+  private cleanup = () => {};
 
   constructor({
     level = "info",
     dir,
   }: { level?: LevelWithSilent; dir?: string } = {}) {
-    const streams: (pino.DestinationStream | pino.StreamEntry)[] = [];
+    const streams: StreamEntry[] = [];
 
     if (level !== "silent") {
-      streams.push({
+      const stream: StreamEntry = {
         level,
         stream: {
           write(logString: string) {
@@ -42,16 +48,26 @@ export class LoggerService {
             // }
           },
         },
-      });
+      };
+
+      streams.push(stream);
     }
 
     if (dir) {
       const timestamp = new Date().toISOString().replace(/[-:.]/g, "_");
       const logFile = path.join(dir, `${timestamp}.log`);
-      streams.push({
-        level: "trace",
-        stream: pino.destination({ dest: logFile, sync: false, mkdir: true }),
+      const sonicBoom = pino.destination({
+        dest: logFile,
+        sync: false,
+        mkdir: true,
       });
+      const stream: StreamEntry = { level: "trace", stream: sonicBoom };
+      streams.push(stream);
+
+      this.cleanup = () => {
+        sonicBoom.flushSync();
+        sonicBoom.destroy();
+      };
     }
 
     this.logger = pino(
@@ -63,6 +79,10 @@ export class LoggerService {
     );
   }
 
+  kill = () => {
+    this.cleanup();
+  };
+
   fatal = (options: LogOptions & { error?: Error }) => {
     this.logger.fatal(options);
   };
diff --git a/packages/core/src/common/telemetry.ts b/packages/core/src/common/telemetry.ts
index 3404783e..54460545 100644
--- a/packages/core/src/common/telemetry.ts
+++ b/packages/core/src/common/telemetry.ts
@@ -199,11 +199,7 @@ export function createTelemetry({
   });
 
   const record = (event: TelemetryEvent) => {
-    if (isKilled) {
-      throw new Error(
-        "Invariant violation, attempted to record event after telemetry service was killed",
-      );
-    }
+    if (isKilled) return;
     queue.add(event);
   };
 
diff --git a/packages/core/src/indexing-store/store.bench.ts b/packages/core/src/indexing-store/store.bench.ts
index d3e04e53..9c839df8 100644
--- a/packages/core/src/indexing-store/store.bench.ts
+++ b/packages/core/src/indexing-store/store.bench.ts
@@ -1,5 +1,5 @@
 import {
-  setupContext,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
@@ -26,7 +26,7 @@ const schema = createSchema((p) => ({
 const setup = async () => {
   context = {} as TestContext;
 
-  setupContext(context);
+  setupCommon(context);
   const cleanupDatabase = await setupIsolatedDatabase(context);
   const { indexingStore: indexingStore_, cleanup: cleanupIndexingStore } =
     await setupDatabaseServices(context, {
diff --git a/packages/core/src/sync-historical/service.bench.ts b/packages/core/src/sync-historical/service.bench.ts
index e496d933..11ee0355 100644
--- a/packages/core/src/sync-historical/service.bench.ts
+++ b/packages/core/src/sync-historical/service.bench.ts
@@ -1,6 +1,6 @@
 import {
   setupAnvil,
-  setupContext,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
@@ -16,7 +16,7 @@ let cleanup = async () => {};
 
 const setup = async () => {
   context = {} as TestContext;
-  setupContext(context);
+  setupCommon(context);
   await setupAnvil(context);
   const teardownDatabase = await setupIsolatedDatabase(context);
   const { syncStore: syncStore_, cleanup: cleanupSyncStore } =
diff --git a/packages/core/src/sync-store/store.bench.ts b/packages/core/src/sync-store/store.bench.ts
index 5216cfd4..e306c9f7 100644
--- a/packages/core/src/sync-store/store.bench.ts
+++ b/packages/core/src/sync-store/store.bench.ts
@@ -2,7 +2,7 @@ import assert from "node:assert";
 import { randomBytes } from "node:crypto";
 import { describe } from "node:test";
 import {
-  setupContext,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
@@ -153,7 +153,7 @@ let cleanup: () => Promise<void>;
 
 const setupStore = async () => {
   context = {} as TestContext;
-  setupContext(context);
+  setupCommon(context);
 
   const cleanupDatabase = await setupIsolatedDatabase(context);
   const { syncStore: syncStore_, cleanup: cleanUpstore } =
diff --git a/packages/core/vite.config.ts b/packages/core/vite.config.ts
index ef08166b..43cbe282 100644
--- a/packages/core/vite.config.ts
+++ b/packages/core/vite.config.ts
@@ -1,5 +1,5 @@
+import os from "node:os";
 import path from "node:path";
-
 import { defineConfig } from "vitest/config";
 
 export default defineConfig({
@@ -20,5 +20,6 @@ export default defineConfig({
       },
     },
     sequence: { hooks: "stack" },
+    testTimeout: os.platform() === "win32" ? 30_000 : 10_000,
   },
 });
diff --git a/packages/create-ponder/package.json b/packages/create-ponder/package.json
index 446bc4bc..c2bd30dc 100644
--- a/packages/create-ponder/package.json
+++ b/packages/create-ponder/package.json
@@ -42,6 +42,7 @@
     "@types/validate-npm-package-name": "^4.0.2",
     "abitype": "^0.10.2",
     "dotenv": "^16.3.1",
+    "rimraf": "^5.0.5",
     "tsup": "^8.0.1",
     "vitest": "^1.0.2"
   },
diff --git a/packages/create-ponder/src/helpers/validate.ts b/packages/create-ponder/src/helpers/validate.ts
index 574c30a1..cea80d13 100644
--- a/packages/create-ponder/src/helpers/validate.ts
+++ b/packages/create-ponder/src/helpers/validate.ts
@@ -1,8 +1,7 @@
 import path from "path";
-import fs from "fs-extra";
+import { pathExists } from "fs-extra";
 import pico from "picocolors";
 import validatePackageName from "validate-npm-package-name";
-
 import type { Template } from "../index.js";
 
 type ValidationResult =
@@ -19,10 +18,8 @@ type ValidationResult =
 
 export async function validateProjectName({
   projectName,
-  projectPath,
 }: {
   projectName: string;
-  projectPath: string;
 }): Promise<ValidationResult> {
   // Validate project name
   const nameValidation = validatePackageName(projectName);
@@ -38,12 +35,22 @@ export async function validateProjectName({
     };
   }
 
+  return {
+    valid: true,
+  };
+}
+
+export async function validateProjectPath({
+  projectPath,
+}: { projectPath: string }): Promise<ValidationResult> {
   // Validate project target path
-  const targetPath = path.join(process.cwd(), projectPath);
-  if (await fs.pathExists(targetPath))
+  if (await pathExists(projectPath))
     return {
       valid: false,
-      message: `🙈 the directory "${projectPath}" already exists.`,
+      message: `🙈 the directory "${path.relative(
+        process.cwd(),
+        projectPath,
+      )}" already exists.`,
       problems: "👉 choose another name or delete the directory.",
     };
 
diff --git a/packages/create-ponder/src/index.ts b/packages/create-ponder/src/index.ts
index 5f97375a..5ad9cba6 100644
--- a/packages/create-ponder/src/index.ts
+++ b/packages/create-ponder/src/index.ts
@@ -1,8 +1,7 @@
 #!/usr/bin/env node
-import { writeFileSync } from "node:fs";
+import { mkdirSync, writeFileSync } from "node:fs";
 import path from "node:path";
 import { fileURLToPath } from "node:url";
-
 import type { Abi, AbiEvent } from "abitype";
 import { cac } from "cac";
 import cpy from "cpy";
@@ -12,7 +11,6 @@ import { oraPromise } from "ora";
 import pico from "picocolors";
 import prettier from "prettier";
 import { default as prompts } from "prompts";
-
 // NOTE: This is a workaround for tsconfig `rootDir` nonsense.
 // @ts-ignore
 import rootPackageJson from "../package.json" assert { type: "json" };
@@ -23,6 +21,7 @@ import { notifyUpdate } from "./helpers/notifyUpdate.js";
 import {
   ValidationError,
   validateProjectName,
+  validateProjectPath,
   validateTemplateName,
 } from "./helpers/validate.js";
 import { fromSubgraphId } from "./subgraph.js";
@@ -159,13 +158,22 @@ export async function run({
   });
   if (!templateValidation.valid) throw new ValidationError(templateValidation);
 
-  // Validate project name
+  // Project name.
   let projectName: string;
+  // Absolute path to project directory.
   let projectPath: string;
+
   if (args[0]) {
     projectPath = args[0].trim();
-    const splitPath = projectPath.split("/");
+    // If the provided path is not absolute, make it absolute.
+    if (!path.isAbsolute(projectPath)) projectPath = path.resolve(projectPath);
+    const splitPath = projectPath.split(path.sep);
+    // Use the last segment of the provided path as the project name.
     projectName = splitPath[splitPath.length - 1]?.trim() || "";
+
+    const nameValidation = await validateProjectName({ projectName });
+    if (!nameValidation.valid) throw new ValidationError(nameValidation);
+
     log(pico.green("✔"), pico.bold("Using project name:"), projectName);
   } else {
     const res = await prompts({
@@ -174,24 +182,21 @@ export async function run({
       message: "What's the name of your project?",
       type: "text",
       async validate(projectName) {
-        const validation = await validateProjectName({
-          projectName,
-          projectPath: projectName,
-        });
+        const validation = await validateProjectName({ projectName });
         if (!validation.valid) return validation.message;
         return true;
       },
     });
     projectName = res.projectName?.trim();
-    projectPath = projectName;
+    projectPath = path.resolve(projectName);
   }
 
-  // Validate project name
-  const nameValidation = await validateProjectName({
-    projectName,
-    projectPath,
-  });
-  if (!nameValidation.valid) throw new ValidationError(nameValidation);
+  // Validate project path
+  const pathValidation = await validateProjectPath({ projectPath });
+  if (!pathValidation.valid) throw new ValidationError(pathValidation);
+
+  // After validating that the directory does not already exist, create it.
+  mkdirSync(projectPath, { recursive: true });
 
   // Automatically set template if using shortcut.
   if (options.etherscan && !templateId) templateId = "etherscan";
@@ -225,8 +230,6 @@ export async function run({
 
   let config: SerializableConfig | undefined;
 
-  const targetPath = path.join(process.cwd(), projectPath);
-
   let url: string | undefined = options.etherscan;
   if (templateMeta.id === "etherscan") {
     if (!url) {
@@ -263,7 +266,7 @@ export async function run({
     const host = new URL(url!).host;
     const result = await oraPromise(
       fromEtherscan({
-        rootDir: targetPath,
+        rootDir: projectPath,
         etherscanLink: url!,
         etherscanApiKey: options.etherscanApiKey,
       }),
@@ -281,7 +284,7 @@ export async function run({
 
   if (templateMeta.id === "subgraph") {
     const result = await oraPromise(
-      fromSubgraphId({ rootDir: targetPath, subgraphId: subgraph! }),
+      fromSubgraphId({ rootDir: projectPath, subgraphId: subgraph! }),
       {
         text: "Fetching subgraph metadata. This may take a few seconds.",
         failText: "Failed to fetch subgraph metadata.",
@@ -294,7 +297,7 @@ export async function run({
 
   // Copy template contents into the target path
   const templatePath = path.join(templatesPath, templateMeta.id);
-  await cpy(path.join(templatePath, "**", "*"), targetPath, {
+  await cpy(path.join(templatePath, "**", "*"), projectPath, {
     rename: (name) => name.replace(/^_dot_/, "."),
   });
 
@@ -348,7 +351,7 @@ export async function run({
     `;
 
     writeFileSync(
-      path.join(targetPath, "ponder.config.ts"),
+      path.join(projectPath, "ponder.config.ts"),
       await prettier.format(configContent, { parser: "typescript" }),
     );
 
@@ -381,7 +384,7 @@ export async function run({
     `;
 
       writeFileSync(
-        path.join(targetPath, `./src/${name}.ts`),
+        path.join(projectPath, "src", `${name}.ts`),
         await prettier.format(indexingFunctionFileContents, {
           parser: "typescript",
         }),
@@ -390,14 +393,14 @@ export async function run({
   }
 
   // Create package.json for project
-  const packageJson = await fs.readJSON(path.join(targetPath, "package.json"));
+  const packageJson = await fs.readJSON(path.join(projectPath, "package.json"));
   packageJson.name = projectName;
   packageJson.dependencies["@ponder/core"] = `^${rootPackageJson.version}`;
   packageJson.devDependencies[
     "eslint-config-ponder"
   ] = `^${rootPackageJson.version}`;
   await fs.writeFile(
-    path.join(targetPath, "package.json"),
+    path.join(projectPath, "package.json"),
     JSON.stringify(packageJson, null, 2),
   );
 
@@ -410,7 +413,7 @@ export async function run({
   ];
   await oraPromise(
     execa(packageManager, installArgs, {
-      cwd: targetPath,
+      cwd: projectPath,
       env: {
         ...process.env,
         ADBLOCK: "1",
@@ -433,8 +436,8 @@ export async function run({
   if (!options.skipGit) {
     await oraPromise(
       async () => {
-        await execa("git", ["init"], { cwd: targetPath });
-        await execa("git", ["add", "."], { cwd: targetPath });
+        await execa("git", ["init"], { cwd: projectPath });
+        await execa("git", ["add", "."], { cwd: projectPath });
         await execa(
           "git",
           [
@@ -443,7 +446,7 @@ export async function run({
             "--message",
             "chore: initial commit from create-ponder",
           ],
-          { cwd: targetPath },
+          { cwd: projectPath },
         );
       },
       {
@@ -470,7 +473,7 @@ export async function run({
   log();
   log(
     `To start your app, run ${pico.bold(
-      pico.cyan(`cd ${projectPath}`),
+      pico.cyan(`cd ${path.relative(process.cwd(), projectPath)}`),
     )} and then ${pico.bold(
       pico.cyan(
         `${packageManager}${
diff --git a/packages/create-ponder/vite.config.ts b/packages/create-ponder/vite.config.ts
index 7c95f4f7..fee3724c 100644
--- a/packages/create-ponder/vite.config.ts
+++ b/packages/create-ponder/vite.config.ts
@@ -10,5 +10,6 @@ export default defineConfig({
   },
   test: {
     globalSetup: ["src/_test/globalSetup.ts"],
+    testTimeout: 60_000,
   },
 });
diff --git a/packages/utils/src/getLogsRetryHelper.ts b/packages/utils/src/getLogsRetryHelper.ts
index b7e6306c..b2d51758 100644
--- a/packages/utils/src/getLogsRetryHelper.ts
+++ b/packages/utils/src/getLogsRetryHelper.ts
@@ -74,21 +74,6 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
-  // thirdweb
-  match = sError.match(/bigger than range limit (\d+)/);
-  if (match !== null) {
-    const ranges = chunk({ params, range: BigInt(match[1]!) });
-
-    if (isRangeUnchanged(params, ranges)) {
-      return { shouldRetry: false } as const;
-    }
-
-    return {
-      shouldRetry: true,
-      ranges,
-    } as const;
-  }
-
   // ankr
   match = sError.match("block range is too wide");
   if (match !== null && error.code === -32600) {
@@ -143,6 +128,24 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
+  // blockpi
+  match = sError.match(/limited to ([\d,.]+) block/);
+  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,
+    } as const;
+  }
+
   // 1rpc
   match = sError.match(/response size should not greater than \d+ bytes/);
   if (match !== null) {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 011bf5e2..f55884f7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -634,6 +634,9 @@ importers:
       '@wagmi/cli':
         specifier: ^1.5.2
         version: 1.5.2(typescript@5.0.4)
+      rimraf:
+        specifier: ^5.0.5
+        version: 5.0.5
       tsup:
         specifier: ^8.0.1
         version: 8.0.1(typescript@5.0.4)
@@ -698,6 +701,9 @@ importers:
       dotenv:
         specifier: ^16.3.1
         version: 16.3.1
+      rimraf:
+        specifier: ^5.0.5
+        version: 5.0.5
       tsup:
         specifier: ^8.0.1
         version: 8.0.1(typescript@5.0.4)
@@ -12366,6 +12372,14 @@ packages:
       glob: 7.2.3
     dev: true
 
+  /rimraf@5.0.5:
+    resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==}
+    engines: {node: '>=14'}
+    hasBin: true
+    dependencies:
+      glob: 10.3.10
+    dev: true
+
   /ripemd160@2.0.2:
     resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==}
     dependencies:


Test patch:
diff --git a/packages/core/src/_test/e2e/erc20/erc20.test.ts b/packages/core/src/_test/e2e/erc20/erc20.test.ts
index f8aaeeee..eb933ed7 100644
--- a/packages/core/src/_test/e2e/erc20/erc20.test.ts
+++ b/packages/core/src/_test/e2e/erc20/erc20.test.ts
@@ -1,36 +1,36 @@
-import { rmSync } from "node:fs";
+import path from "node:path";
 import { ALICE, BOB } from "@/_test/constants.js";
-import { setupAnvil, setupIsolatedDatabase } from "@/_test/setup.js";
+import {
+  setupAnvil,
+  setupCommon,
+  setupIsolatedDatabase,
+} from "@/_test/setup.js";
 import { simulate } from "@/_test/simulate.js";
 import { getFreePort, postGraphql, waitForHealthy } from "@/_test/utils.js";
 import { serve } from "@/bin/commands/serve.js";
 import { start } from "@/bin/commands/start.js";
 import { range } from "@/utils/range.js";
 import { wait } from "@/utils/wait.js";
+import { rimrafSync } from "rimraf";
 import { zeroAddress } from "viem";
-import { afterEach, beforeEach, describe, expect, test } from "vitest";
+import { beforeEach, describe, expect, test } from "vitest";
 
+const rootDir = path.join(".", "src", "_test", "e2e", "erc20");
+beforeEach(() => {
+  rimrafSync(path.join(rootDir, ".ponder"));
+  rimrafSync(path.join(rootDir, "generated"));
+});
+
+beforeEach(setupCommon);
 beforeEach(setupAnvil);
 beforeEach(setupIsolatedDatabase);
 
-afterEach(() => {
-  rmSync("./src/_test/e2e/erc20/.ponder", {
-    recursive: true,
-    force: true,
-    retryDelay: 20,
-  });
-  rmSync("./src/_test/e2e/erc20/generated", {
-    recursive: true,
-    force: true,
-  });
-});
-
 test("erc20", async (context) => {
   const port = await getFreePort();
 
   const cleanup = await start({
     cliOptions: {
-      root: "./src/_test/e2e/erc20",
+      root: rootDir,
       config: "ponder.config.ts",
       port,
     },
diff --git a/packages/core/src/_test/e2e/factory/factory.test.ts b/packages/core/src/_test/e2e/factory/factory.test.ts
index 9a1d653c..5e94ab9c 100644
--- a/packages/core/src/_test/e2e/factory/factory.test.ts
+++ b/packages/core/src/_test/e2e/factory/factory.test.ts
@@ -1,27 +1,27 @@
-import { rmSync } from "node:fs";
+import path from "node:path";
 import { ALICE } from "@/_test/constants.js";
-import { setupAnvil, setupIsolatedDatabase } from "@/_test/setup.js";
+import {
+  setupAnvil,
+  setupCommon,
+  setupIsolatedDatabase,
+} from "@/_test/setup.js";
 import { simulatePairSwap } from "@/_test/simulate.js";
 import { getFreePort, postGraphql, waitForHealthy } from "@/_test/utils.js";
 import { start } from "@/bin/commands/start.js";
 import { wait } from "@/utils/wait.js";
-import { afterEach, beforeEach, expect, test } from "vitest";
+import { rimrafSync } from "rimraf";
+import { beforeEach, expect, test } from "vitest";
 
+const rootDir = path.join(".", "src", "_test", "e2e", "factory");
+beforeEach(() => {
+  rimrafSync(path.join(rootDir, ".ponder"));
+  rimrafSync(path.join(rootDir, "generated"));
+});
+
+beforeEach(setupCommon);
 beforeEach(setupAnvil);
 beforeEach(setupIsolatedDatabase);
 
-afterEach(() => {
-  rmSync("./src/_test/e2e/factory/.ponder", {
-    recursive: true,
-    force: true,
-    retryDelay: 20,
-  });
-  rmSync("./src/_test/e2e/factory/generated", {
-    recursive: true,
-    force: true,
-  });
-});
-
 test("factory", async (context) => {
   const port = await getFreePort();
 
diff --git a/packages/core/src/_test/setup.ts b/packages/core/src/_test/setup.ts
index d833cd6b..b2f0f290 100644
--- a/packages/core/src/_test/setup.ts
+++ b/packages/core/src/_test/setup.ts
@@ -1,5 +1,5 @@
 import { randomUUID } from "crypto";
-import fs from "fs";
+import { mkdirSync } from "fs";
 import path from "node:path";
 import os from "os";
 import type { Common } from "@/common/common.js";
@@ -22,8 +22,9 @@ import { SqliteSyncStore } from "@/sync-store/sqlite/store.js";
 import type { SyncStore } from "@/sync-store/store.js";
 import type { RequestQueue } from "@/utils/requestQueue.js";
 import pg from "pg";
+import { rimrafSync } from "rimraf";
 import type { Address } from "viem";
-import { type TestContext, beforeEach } from "vitest";
+import { type TestContext } from "vitest";
 import { deploy, simulate } from "./simulate.js";
 import { getConfig, getNetworkAndSources, testClient } from "./utils.js";
 
@@ -40,21 +41,19 @@ declare module "vitest" {
   }
 }
 
-beforeEach(setupContext);
-
-export function setupContext(context: TestContext) {
+export function setupCommon(context: TestContext) {
   const options = {
-    ...buildOptions({
-      cliOptions: { config: "", root: "" },
-    }),
+    ...buildOptions({ cliOptions: { config: "", root: "" } }),
     telemetryDisabled: true,
   };
   const logger = new LoggerService({ level: "silent" });
-  context.common = {
-    options,
-    logger,
-    metrics: new MetricsService(),
-    telemetry: createTelemetry({ options, logger }),
+  const metrics = new MetricsService();
+  const telemetry = createTelemetry({ options, logger });
+  context.common = { options, logger, metrics, telemetry };
+
+  return async () => {
+    await telemetry.kill();
+    logger.kill();
   };
 }
 
@@ -92,15 +91,15 @@ export async function setupIsolatedDatabase(context: TestContext) {
       schema: "public",
     };
 
-    return async () => {};
+    return () => {};
   } else {
     const tempDir = path.join(os.tmpdir(), randomUUID());
-    fs.mkdirSync(tempDir, { recursive: true });
+    mkdirSync(tempDir, { recursive: true });
 
     context.databaseConfig = { kind: "sqlite", directory: tempDir };
 
-    return async () => {
-      fs.rmSync(tempDir, { force: true, recursive: true });
+    return () => {
+      rimrafSync(tempDir);
     };
   }
 }
diff --git a/packages/core/src/bin/utils/run.test.ts b/packages/core/src/bin/utils/run.test.ts
index 78f3c006..efb5f202 100644
--- a/packages/core/src/bin/utils/run.test.ts
+++ b/packages/core/src/bin/utils/run.test.ts
@@ -1,4 +1,8 @@
-import { setupAnvil, setupIsolatedDatabase } from "@/_test/setup.js";
+import {
+  setupAnvil,
+  setupCommon,
+  setupIsolatedDatabase,
+} from "@/_test/setup.js";
 import type { Build } from "@/build/index.js";
 import * as codegen from "@/common/codegen.js";
 import { createSchema } from "@/schema/schema.js";
@@ -6,8 +10,9 @@ import { buildGraphqlSchema } from "@/server/graphql/buildGraphqlSchema.js";
 import { beforeEach, expect, test, vi } from "vitest";
 import { run } from "./run.js";
 
-beforeEach((context) => setupAnvil(context));
-beforeEach((context) => setupIsolatedDatabase(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
+beforeEach(setupIsolatedDatabase);
 
 const schema = createSchema((p) => ({
   TransferEvent: p.createTable({
diff --git a/packages/core/src/common/telemetry.test.ts b/packages/core/src/common/telemetry.test.ts
index 1ee0ef17..4d269fe0 100644
--- a/packages/core/src/common/telemetry.test.ts
+++ b/packages/core/src/common/telemetry.test.ts
@@ -1,9 +1,13 @@
 import { randomUUID } from "node:crypto";
-import { mkdirSync, rmSync } from "node:fs";
+import { mkdirSync } from "node:fs";
 import os from "node:os";
 import path from "node:path";
 import { createTelemetry } from "@/common/telemetry.js";
+import { wait } from "@/utils/wait.js";
+import { rimrafSync } from "rimraf";
 import { beforeEach, expect, test, vi } from "vitest";
+import type { Common } from "./common.js";
+import { LoggerService } from "./logger.js";
 
 const fetchSpy = vi.fn();
 
@@ -17,14 +21,17 @@ beforeEach((context) => {
   const tempDir = path.join(os.tmpdir(), randomUUID());
   mkdirSync(tempDir, { recursive: true });
 
-  context.common.options = {
-    ...context.common.options,
-    telemetryDisabled: false,
-    telemetryConfigDir: tempDir,
-  };
-
-  return async () => {
-    rmSync(tempDir, { force: true, recursive: true });
+  context.common = {
+    logger: new LoggerService({ level: "silent" }),
+    options: {
+      telemetryUrl: "https://ponder.sh/api/telemetry",
+      telemetryDisabled: false,
+      telemetryConfigDir: tempDir,
+    },
+  } as unknown as Common;
+
+  return () => {
+    rimrafSync(tempDir);
   };
 });
 
@@ -91,14 +98,12 @@ test("telemetry throws if event is submitted after kill", async (context) => {
 
   expect(fetchSpy).toHaveBeenCalledTimes(5);
 
-  expect(() =>
-    telemetry.record({
-      name: "lifecycle:heartbeat_send",
-      properties: { duration_seconds: process.uptime() },
-    }),
-  ).toThrow(
-    "Invariant violation, attempted to record event after telemetry service was killed",
-  );
+  telemetry.record({
+    name: "lifecycle:heartbeat_send",
+    properties: { duration_seconds: process.uptime() },
+  });
+
+  await wait(100);
 
   expect(fetchSpy).toHaveBeenCalledTimes(5);
 });
diff --git a/packages/core/src/database/postgres/service.test.ts b/packages/core/src/database/postgres/service.test.ts
index 842968e8..a683f429 100644
--- a/packages/core/src/database/postgres/service.test.ts
+++ b/packages/core/src/database/postgres/service.test.ts
@@ -1,4 +1,4 @@
-import { setupIsolatedDatabase } from "@/_test/setup.js";
+import { setupCommon, setupIsolatedDatabase } from "@/_test/setup.js";
 import { createSchema } from "@/schema/schema.js";
 import { encodeCheckpoint, zeroCheckpoint } from "@/utils/checkpoint.js";
 import { hash } from "@/utils/hash.js";
@@ -7,6 +7,7 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
 import type { HeadlessKysely } from "../kysely.js";
 import { PostgresDatabaseService } from "./service.js";
 
+beforeEach(setupCommon);
 beforeEach(setupIsolatedDatabase);
 
 const schema = createSchema((p) => ({
diff --git a/packages/core/src/database/sqlite/service.test.ts b/packages/core/src/database/sqlite/service.test.ts
index e21b617b..dcb81297 100644
--- a/packages/core/src/database/sqlite/service.test.ts
+++ b/packages/core/src/database/sqlite/service.test.ts
@@ -1,4 +1,4 @@
-import { setupIsolatedDatabase } from "@/_test/setup.js";
+import { setupCommon, setupIsolatedDatabase } from "@/_test/setup.js";
 import { createSchema } from "@/schema/schema.js";
 import { encodeCheckpoint, zeroCheckpoint } from "@/utils/checkpoint.js";
 import { hash } from "@/utils/hash.js";
@@ -7,6 +7,7 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
 import type { HeadlessKysely } from "../kysely.js";
 import { SqliteDatabaseService } from "./service.js";
 
+beforeEach(setupCommon);
 beforeEach(setupIsolatedDatabase);
 
 const schema = createSchema((p) => ({
diff --git a/packages/core/src/indexing-store/realtimeStore.test.ts b/packages/core/src/indexing-store/realtimeStore.test.ts
index bf3ea4d0..7a22b305 100644
--- a/packages/core/src/indexing-store/realtimeStore.test.ts
+++ b/packages/core/src/indexing-store/realtimeStore.test.ts
@@ -1,4 +1,8 @@
-import { setupDatabaseServices, setupIsolatedDatabase } from "@/_test/setup.js";
+import {
+  setupCommon,
+  setupDatabaseServices,
+  setupIsolatedDatabase,
+} from "@/_test/setup.js";
 import { createSchema } from "@/schema/schema.js";
 import {
   type Checkpoint,
@@ -8,6 +12,7 @@ import {
 import { hash } from "@/utils/hash.js";
 import { beforeEach, expect, test } from "vitest";
 
+beforeEach(setupCommon);
 beforeEach(setupIsolatedDatabase);
 
 const schema = createSchema((p) => ({
diff --git a/packages/core/src/indexing/events.test.ts b/packages/core/src/indexing/events.test.ts
index 1e6ebbcc..4af9d2fa 100644
--- a/packages/core/src/indexing/events.test.ts
+++ b/packages/core/src/indexing/events.test.ts
@@ -1,12 +1,13 @@
 import { ALICE, BOB } from "@/_test/constants.js";
-import { setupAnvil } from "@/_test/setup.js";
+import { setupAnvil, setupCommon } from "@/_test/setup.js";
 import { getEventsErc20 } from "@/_test/utils.js";
 import { parseEther, zeroAddress } from "viem";
 import { beforeEach, expect, test } from "vitest";
 import { decodeEvents } from "./events.js";
 import type { Service } from "./service.js";
 
-beforeEach((context) => setupAnvil(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
 
 test("decodeEvents()", async (context) => {
   const { common, sources } = context;
diff --git a/packages/core/src/indexing/service.test.ts b/packages/core/src/indexing/service.test.ts
index e425a138..ca0ca868 100644
--- a/packages/core/src/indexing/service.test.ts
+++ b/packages/core/src/indexing/service.test.ts
@@ -2,6 +2,7 @@ import { BOB } from "@/_test/constants.js";
 import { erc20ABI } from "@/_test/generated.js";
 import {
   setupAnvil,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
@@ -21,8 +22,9 @@ import {
   processSetupEvents,
 } from "./service.js";
 
-beforeEach((context) => setupAnvil(context));
-beforeEach((context) => setupIsolatedDatabase(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
+beforeEach(setupIsolatedDatabase);
 
 const schema = createSchema((p) => ({
   TransferEvent: p.createTable({
diff --git a/packages/core/src/server/graphql/buildGraphqlSchema.test.ts b/packages/core/src/server/graphql/buildGraphqlSchema.test.ts
index ed617fc1..60de387e 100644
--- a/packages/core/src/server/graphql/buildGraphqlSchema.test.ts
+++ b/packages/core/src/server/graphql/buildGraphqlSchema.test.ts
@@ -1,4 +1,8 @@
-import { setupDatabaseServices, setupIsolatedDatabase } from "@/_test/setup.js";
+import {
+  setupCommon,
+  setupDatabaseServices,
+  setupIsolatedDatabase,
+} from "@/_test/setup.js";
 import type { IndexingStore } from "@/indexing-store/store.js";
 import { createSchema } from "@/schema/schema.js";
 import { encodeCheckpoint, zeroCheckpoint } from "@/utils/checkpoint.js";
@@ -7,7 +11,8 @@ import { beforeEach, expect, test } from "vitest";
 import { buildGraphqlSchema } from "./buildGraphqlSchema.js";
 import { buildLoaderCache } from "./buildLoaderCache.js";
 
-beforeEach((context) => setupIsolatedDatabase(context));
+beforeEach(setupCommon);
+beforeEach(setupIsolatedDatabase);
 
 const create = async (id: string, indexingStore: IndexingStore) => {
   await indexingStore.create({
diff --git a/packages/core/src/server/service.test.ts b/packages/core/src/server/service.test.ts
index a348d33b..4ad5c29b 100644
--- a/packages/core/src/server/service.test.ts
+++ b/packages/core/src/server/service.test.ts
@@ -1,12 +1,19 @@
-import { setupDatabaseServices, setupIsolatedDatabase } from "@/_test/setup.js";
+import {
+  setupCommon,
+  setupDatabaseServices,
+  setupIsolatedDatabase,
+} from "@/_test/setup.js";
 import type { IndexingStore } from "@/indexing-store/store.js";
 import { createSchema } from "@/schema/schema.js";
 import { encodeCheckpoint, zeroCheckpoint } from "@/utils/checkpoint.js";
 import type { GraphQLSchema } from "graphql";
-import { expect, test, vi } from "vitest";
+import { beforeEach, expect, test, vi } from "vitest";
 import { buildGraphqlSchema } from "./graphql/buildGraphqlSchema.js";
 import { createServer } from "./service.js";
 
+beforeEach(setupCommon);
+beforeEach(setupIsolatedDatabase);
+
 test("port", async (context) => {
   const server1 = await createServer({
     graphqlSchema: {} as GraphQLSchema,
@@ -108,7 +115,6 @@ test("metrics PUT", async (context) => {
 });
 
 test("graphql", async (context) => {
-  const shutdown = await setupIsolatedDatabase(context);
   const schema = createSchema((p) => ({
     table: p.createTable({
       id: p.string(),
@@ -187,25 +193,9 @@ test("graphql", async (context) => {
   });
 
   await cleanup();
-
-  await shutdown();
-});
-
-test("graphql interactive", async (context) => {
-  const server = await createServer({
-    graphqlSchema: {} as GraphQLSchema,
-    common: context.common,
-    indexingStore: {} as IndexingStore,
-  });
-  server.setHealthy();
-
-  const response = await server.hono.request("/graphql");
-
-  expect(response.status).toBe(200);
 });
 
 test("graphql extra filter", async (context) => {
-  const shutdown = await setupIsolatedDatabase(context);
   const schema = createSchema((p) => ({
     table: p.createTable({
       id: p.string(),
@@ -256,8 +246,19 @@ test("graphql extra filter", async (context) => {
   expect(response.status).toBe(400);
 
   await cleanup();
+});
 
-  await shutdown();
+test("graphql interactive", async (context) => {
+  const server = await createServer({
+    graphqlSchema: {} as GraphQLSchema,
+    common: context.common,
+    indexingStore: {} as IndexingStore,
+  });
+  server.setHealthy();
+
+  const response = await server.hono.request("/graphql");
+
+  expect(response.status).toBe(200);
 });
 
 test("missing route", async (context) => {
diff --git a/packages/core/src/sync-historical/service.test.ts b/packages/core/src/sync-historical/service.test.ts
index 8b44d59a..00c6cda7 100644
--- a/packages/core/src/sync-historical/service.test.ts
+++ b/packages/core/src/sync-historical/service.test.ts
@@ -1,5 +1,6 @@
 import {
   setupAnvil,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
@@ -14,8 +15,9 @@ import { wait } from "@/utils/wait.js";
 import { beforeEach, expect, test, vi } from "vitest";
 import { HistoricalSyncService } from "./service.js";
 
-beforeEach((context) => setupAnvil(context));
-beforeEach((context) => setupIsolatedDatabase(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
+beforeEach(setupIsolatedDatabase);
 
 const getBlockNumbers = () =>
   publicClient.getBlockNumber().then((b) => ({
diff --git a/packages/core/src/sync-realtime/filter.test.ts b/packages/core/src/sync-realtime/filter.test.ts
index 138b269a..2f4b6828 100644
--- a/packages/core/src/sync-realtime/filter.test.ts
+++ b/packages/core/src/sync-realtime/filter.test.ts
@@ -1,5 +1,5 @@
 import { erc20ABI, pairABI } from "@/_test/generated.js";
-import { setupAnvil } from "@/_test/setup.js";
+import { setupAnvil, setupCommon } from "@/_test/setup.js";
 import { publicClient } from "@/_test/utils.js";
 import { toLowerCase } from "@/utils/lowercase.js";
 import { getAbiItem, getEventSelector, toHex } from "viem";
@@ -7,11 +7,12 @@ import { beforeEach, expect, test } from "vitest";
 import { filterLogs } from "./filter.js";
 import type { RealtimeLog } from "./format.js";
 
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
+
 const zeroHash =
   "0x0000000000000000000000000000000000000000000000000000000000000000";
 
-beforeEach((context) => setupAnvil(context));
-
 const AliceHex = toLowerCase(
   "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
 );
diff --git a/packages/core/src/sync-realtime/format.test.ts b/packages/core/src/sync-realtime/format.test.ts
index eb0b0db1..7ba8d396 100644
--- a/packages/core/src/sync-realtime/format.test.ts
+++ b/packages/core/src/sync-realtime/format.test.ts
@@ -1,8 +1,9 @@
-import { setupAnvil } from "@/_test/setup.js";
+import { setupAnvil, setupCommon } from "@/_test/setup.js";
 import { beforeEach, expect, test } from "vitest";
 import { type RealtimeLog, sortLogs } from "./format.js";
 
-beforeEach((context) => setupAnvil(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
 
 test("sort logs", async (context) => {
   const logs = await context.requestQueues[0].request({
diff --git a/packages/core/src/sync-realtime/service.test.ts b/packages/core/src/sync-realtime/service.test.ts
index 4469a226..903caae5 100644
--- a/packages/core/src/sync-realtime/service.test.ts
+++ b/packages/core/src/sync-realtime/service.test.ts
@@ -1,5 +1,6 @@
 import {
   setupAnvil,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
@@ -11,8 +12,9 @@ import { toLowerCase } from "@/utils/lowercase.js";
 import { beforeEach, expect, test, vi } from "vitest";
 import { RealtimeSyncService } from "./service.js";
 
-beforeEach((context) => setupAnvil(context));
-beforeEach((context) => setupIsolatedDatabase(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
+beforeEach(setupIsolatedDatabase);
 
 test("setup() returns block numbers", async (context) => {
   const { common, networks, requestQueues, sources } = context;
diff --git a/packages/core/src/sync-store/store.test.ts b/packages/core/src/sync-store/store.test.ts
index e5abf29f..00c0676d 100644
--- a/packages/core/src/sync-store/store.test.ts
+++ b/packages/core/src/sync-store/store.test.ts
@@ -2,6 +2,7 @@ import { ALICE, BOB } from "@/_test/constants.js";
 import { erc20ABI } from "@/_test/generated.js";
 import {
   setupAnvil,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
@@ -29,8 +30,9 @@ import {
 } from "viem";
 import { beforeEach, expect, test } from "vitest";
 
-beforeEach((context) => setupAnvil(context));
-beforeEach((context) => setupIsolatedDatabase(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
+beforeEach(setupIsolatedDatabase);
 
 test("setup creates tables", async (context) => {
   const { syncStore, cleanup } = await setupDatabaseServices(context);
diff --git a/packages/core/src/sync/service.test.ts b/packages/core/src/sync/service.test.ts
index 46db8ac7..c798222f 100644
--- a/packages/core/src/sync/service.test.ts
+++ b/packages/core/src/sync/service.test.ts
@@ -1,21 +1,20 @@
-import { type TestContext, beforeEach, expect, test, vi } from "vitest";
-
 import {
   setupAnvil,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
-
 import {
   type Checkpoint,
   maxCheckpoint,
   zeroCheckpoint,
 } from "@/utils/checkpoint.js";
-
+import { type TestContext, beforeEach, expect, test, vi } from "vitest";
 import { SyncService } from "./service.js";
 
-beforeEach((context) => setupAnvil(context));
-beforeEach((context) => setupIsolatedDatabase(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
+beforeEach(setupIsolatedDatabase);
 
 function getMultichainNetworksAndSources(context: TestContext) {
   const mainnet = context.networks[0];
diff --git a/packages/core/src/sync/transport.test.ts b/packages/core/src/sync/transport.test.ts
index e0134761..8985c623 100644
--- a/packages/core/src/sync/transport.test.ts
+++ b/packages/core/src/sync/transport.test.ts
@@ -1,5 +1,6 @@
 import {
   setupAnvil,
+  setupCommon,
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
@@ -9,8 +10,9 @@ import { getFunctionSelector, toHex } from "viem";
 import { assertType, beforeEach, expect, test, vi } from "vitest";
 import { cachedTransport } from "./transport.js";
 
-beforeEach((context) => setupAnvil(context));
-beforeEach((context) => setupIsolatedDatabase(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
+beforeEach(setupIsolatedDatabase);
 
 test("default", async (context) => {
   const { requestQueues } = context;
diff --git a/packages/core/src/utils/requestQueue.test.ts b/packages/core/src/utils/requestQueue.test.ts
index a2cb11cb..1f0831ac 100644
--- a/packages/core/src/utils/requestQueue.test.ts
+++ b/packages/core/src/utils/requestQueue.test.ts
@@ -1,10 +1,11 @@
-import { setupAnvil } from "@/_test/setup.js";
+import { setupAnvil, setupCommon } from "@/_test/setup.js";
 import type { Common } from "@/common/common.js";
 import type { Network } from "@/config/networks.js";
 import { beforeEach, expect, test } from "vitest";
 import { createRequestQueue } from "./requestQueue.js";
 
-beforeEach((context) => setupAnvil(context));
+beforeEach(setupCommon);
+beforeEach(setupAnvil);
 
 /** Creates a request queue with a `maxRequestsPerSecond` of 1. */
 const getQueue = (network: Network, common: Common) => {
diff --git a/packages/create-ponder/src/_test/cli.test.ts b/packages/create-ponder/src/_test/cli.test.ts
index 1e292086..5dd0dd33 100644
--- a/packages/create-ponder/src/_test/cli.test.ts
+++ b/packages/create-ponder/src/_test/cli.test.ts
@@ -1,25 +1,35 @@
-import fs from "node:fs";
-import { join } from "node:path";
-
-import { afterEach, beforeAll, expect, test } from "vitest";
-
+import { randomUUID } from "node:crypto";
+import { mkdirSync, readdirSync } from "node:fs";
+import os from "node:os";
+import path from "node:path";
 import { run } from "@/index.js";
+import { rimrafSync } from "rimraf";
+import { beforeEach, expect, test } from "vitest";
 
-const projectName = "test-app";
-const genPath = join(__dirname, projectName);
+const tempDir = path.join(os.tmpdir(), randomUUID());
 
-beforeAll(() => fs.rmSync(genPath, { recursive: true, force: true }));
-afterEach(() => fs.rmSync(genPath, { recursive: true, force: true }));
+beforeEach(() => {
+  mkdirSync(tempDir, { recursive: true });
+  return () => rimrafSync(tempDir);
+});
 
 test("create empty", async () => {
+  const rootDir = path.join(tempDir, "empty");
+
   await run({
-    args: [join("src", "_test", projectName)],
+    args: [rootDir],
     options: { template: "empty", skipGit: true },
   });
 
-  const templateFiles = fs
-    .readdirSync(join(__dirname, "..", "..", "templates", "empty"))
-    // _gitignore is renamed to .gitignore
+  const templateFiles = (
+    readdirSync(
+      path.join(__dirname, "..", "..", "templates", "empty"),
+    ) as string[]
+  )
+    .concat([
+      path.join("abis", "ExampleContractAbi.ts"),
+      path.join("src", "index.ts"),
+    ])
     .map((filePath) =>
       filePath === "_dot_env.local"
         ? ".env.local"
@@ -31,13 +41,17 @@ test("create empty", async () => {
     )
     .sort();
 
-  const generatedFiles = fs.readdirSync(genPath).sort();
-  expect(templateFiles).toStrictEqual(generatedFiles);
+  const generatedFiles = (readdirSync(rootDir, { recursive: true }) as string[])
+    .filter((f) => !f.startsWith("node_modules") && !f.startsWith("pnpm-lock"))
+    .sort();
+  expect(generatedFiles).toStrictEqual(templateFiles);
 });
 
 test("create subgraph id", async () => {
+  const rootDir = path.join(tempDir, "subgraph-id");
+
   await run({
-    args: [join("src", "_test", projectName)],
+    args: [rootDir],
     options: {
       template: "subgraph",
       skipGit: true,
@@ -46,18 +60,20 @@ test("create subgraph id", async () => {
   });
 
   const templateFiles = (
-    fs.readdirSync(join(__dirname, "..", "..", "templates", "subgraph"), {
+    readdirSync(path.join(__dirname, "..", "..", "templates", "subgraph"), {
       recursive: true,
     }) as string[]
   )
-    .concat("ponder.config.ts")
-    .concat("abis/ERC20Abi.ts")
-    .concat("abis/ERC721Abi.ts")
-    .concat("abis/EntryPointAbi.ts")
-    .concat("src/EntryPoint.ts")
-    .concat("src/EntryPointV0.6.0.ts")
-    .concat("abis")
-    .concat("src")
+    .concat([
+      "ponder.config.ts",
+      path.join("abis", "ERC20Abi.ts"),
+      path.join("abis", "ERC721Abi.ts"),
+      path.join("abis", "EntryPointAbi.ts"),
+      path.join("src", "EntryPoint.ts"),
+      path.join("src", "EntryPointV0.6.0.ts"),
+      "abis",
+      "src",
+    ])
     // _gitignore is renamed to .gitignore
     .map((filePath) =>
       filePath === "_dot_env.local"
@@ -70,13 +86,17 @@ test("create subgraph id", async () => {
     )
     .sort();
 
-  const generatedFiles = fs.readdirSync(genPath, { recursive: true }).sort();
+  const generatedFiles = (readdirSync(rootDir, { recursive: true }) as string[])
+    .filter((f) => !f.startsWith("node_modules") && !f.startsWith("pnpm-lock"))
+    .sort();
   expect(generatedFiles).toStrictEqual(templateFiles);
-}, 20_000);
+});
 
 test("create etherscan", async () => {
+  const rootDir = path.join(tempDir, "etherscan");
+
   await run({
-    args: [join("src", "_test", projectName)],
+    args: [rootDir],
     options: {
       template: "etherscan",
       skipGit: true,
@@ -87,15 +107,17 @@ test("create etherscan", async () => {
   });
 
   const templateFiles = (
-    fs.readdirSync(join(__dirname, "..", "..", "templates", "etherscan"), {
+    readdirSync(path.join(__dirname, "..", "..", "templates", "etherscan"), {
       recursive: true,
     }) as string[]
   )
-    .concat("ponder.config.ts")
-    .concat("abis/WETH9Abi.ts")
-    .concat("src/WETH9.ts")
-    .concat("abis")
-    .concat("src")
+    .concat([
+      "ponder.config.ts",
+      path.join("abis", "WETH9Abi.ts"),
+      path.join("src", "WETH9.ts"),
+      "abis",
+      "src",
+    ])
     // _gitignore is renamed to .gitignore
     .map((filePath) =>
       filePath === "_dot_env.local"
@@ -108,6 +130,8 @@ test("create etherscan", async () => {
     )
     .sort();
 
-  const generatedFiles = fs.readdirSync(genPath, { recursive: true }).sort();
+  const generatedFiles = (readdirSync(rootDir, { recursive: true }) as string[])
+    .filter((f) => !f.startsWith("node_modules") && !f.startsWith("pnpm-lock"))
+    .sort();
   expect(generatedFiles).toStrictEqual(templateFiles);
-}, 20_000);
+});
diff --git a/packages/utils/src/_test/blockpi.test.ts b/packages/utils/src/_test/blockpi.test.ts
new file mode 100644
index 00000000..0bdc84af
--- /dev/null
+++ b/packages/utils/src/_test/blockpi.test.ts
@@ -0,0 +1,69 @@
+import { InvalidParamsRpcError, numberToHex } from "viem";
+import { expect, test } from "vitest";
+import { getLogsRetryHelper } from "../getLogsRetryHelper.js";
+import { type Params, UNI, fromBlock, getRequest } from "./utils.js";
+
+const request = getRequest("https://ethereum.blockpi.network/v1/rpc/public");
+const maxBlockRange = 1024n;
+
+test(
+  "blast success",
+  async () => {
+    const logs = await request({
+      method: "eth_getLogs",
+      params: [
+        {
+          address: UNI,
+          fromBlock: numberToHex(fromBlock),
+          toBlock: numberToHex(fromBlock + maxBlockRange),
+        },
+      ],
+    });
+
+    expect(logs).toHaveLength(9);
+  },
+  { timeout: 15_000 },
+);
+
+test(
+  "blast block range",
+  async () => {
+    const params: Params = [
+      {
+        address: UNI,
+        fromBlock: numberToHex(fromBlock),
+        toBlock: numberToHex(fromBlock + maxBlockRange + 1n),
+      },
+    ];
+
+    const error = await request({
+      method: "eth_getLogs",
+      params,
+    }).catch((error) => error);
+
+    expect(error).toBeInstanceOf(InvalidParamsRpcError);
+    expect(JSON.stringify(error)).includes(
+      "eth_getLogs is limited to 1024 block range",
+    );
+
+    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),
+        },
+      ],
+    });
+  },
+  { timeout: 15_000 },
+);
diff --git a/packages/utils/src/_test/thirdweb.test.ts b/packages/utils/src/_test/thirdweb.test.ts
index 8412d668..c99b8919 100644
--- a/packages/utils/src/_test/thirdweb.test.ts
+++ b/packages/utils/src/_test/thirdweb.test.ts
@@ -4,7 +4,6 @@ import { getLogsRetryHelper } from "../getLogsRetryHelper.js";
 import { type Params, UNI, WETH, fromBlock, getRequest } from "./utils.js";
 
 const request = getRequest("https://1.rpc.thirdweb.com");
-const maxBlockRange = 2000n;
 
 test("thirdweb success", async () => {
   const logs = await request({
@@ -13,7 +12,7 @@ test("thirdweb success", async () => {
       {
         address: UNI,
         fromBlock: numberToHex(fromBlock),
-        toBlock: numberToHex(fromBlock + maxBlockRange),
+        toBlock: numberToHex(fromBlock + 2_000n),
       },
     ],
   });
@@ -26,7 +25,7 @@ test("thirdweb response size", async () => {
     {
       address: WETH,
       fromBlock: numberToHex(fromBlock),
-      toBlock: numberToHex(fromBlock + maxBlockRange),
+      toBlock: numberToHex(fromBlock + 2_000n),
     },
   ];
 
@@ -44,45 +43,9 @@ test("thirdweb response size", async () => {
   });
 
   expect(retry.shouldRetry).toBe(true);
-  expect(retry.ranges).toHaveLength(15);
+  expect(retry.ranges).toHaveLength(8);
   expect(retry.ranges![0]).toStrictEqual({
     fromBlock: "0x112a880",
-    toBlock: "0x112a907",
-  });
-});
-
-test("thirdweb 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(error).toBeInstanceOf(InvalidParamsRpcError);
-  expect(JSON.stringify(error)).includes("is bigger than range limit 2000");
-
-  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),
-      },
-    ],
+    toBlock: "0x112a993",
   });
 });