Repository: ponder-sh/ponder
Pull request #1238 (2024-11-12 20:33:48)

Revert "feat: `context.client` supports all rpc methods"
Reverts ponder-sh/ponder#1227


Patch:
diff --git a/.changeset/rotten-socks-agree.md b/.changeset/rotten-socks-agree.md
deleted file mode 100644
index 0fe5135b..00000000
--- a/.changeset/rotten-socks-agree.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@ponder/core": patch
----
-
-Added support for caching all available rpc methods. Several new client actions are available, including `context.client.simulateCall()`, `context.client.getTransactionCount()`, `context.client.getTransactionReceipt()`, and `context.client.getBlockTransactionCount()`. See [docs](https://ponder.sh/docs/indexing/read-contract-data#supported-actions) for all actions.
diff --git a/docs/pages/docs/indexing/read-contract-data.mdx b/docs/pages/docs/indexing/read-contract-data.mdx
index aeb2a9ae..e2132b3b 100644
--- a/docs/pages/docs/indexing/read-contract-data.mdx
+++ b/docs/pages/docs/indexing/read-contract-data.mdx
@@ -124,29 +124,13 @@ The custom client uses the transport you specify in `ponder.config.ts`.
 
 ### Supported actions
 
-| name                        | description                                                                                         | Viem docs                                                                                        |
-| :-------------------------- | :-------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------- |
-| readContract                | Returns the result of a read-only function on a contract.                                           | [readContract](https://viem.sh/docs/contract/readContract.html)                                  |
-| multicall                   | Similar to readContract, but batches requests.                                                      | [multicall](https://viem.sh/docs/contract/multicall.html)                                        |
-| simulateContract            | Simulates & validates a contract interaction.                                                       | [simulateContract](https://viem.sh/docs/contract/simulateContract)                               |
-| getBalance                  | Returns the balance of an address in wei.                                                           | [getBalance](https://viem.sh/docs/actions/public/getBalance.html)                                |
-| getBytecode                 | Returns the bytecode at an address.                                                                 | [getBytecode](https://viem.sh/docs/contract/getBytecode.html)                                    |
-| getStorageAt                | Returns the value from a storage slot at a given address.                                           | [getStorageAt](https://viem.sh/docs/contract/getStorageAt.html)                                  |
-| getBlock                    | Returns information about a block at a block number, hash or tag.                                   | [getBlock](https://viem.sh/docs/actions/public/getBlock)                                         |
-| getTransactionCount         | Returns the number of transactions an account has broadcast / sent.                                 | [getTransactionCount](https://viem.sh/docs/actions/public/getTransactionCount)                   |
-| getBlockTransactionCount    | Returns the number of Transactions at a block number, hash or tag.                                  | [getBlockTransactionCount](https://viem.sh/docs/actions/public/getBlockTransactionCount)         |
-| getTransaction              | Returns information about a transaction given a hash or block identifier.                           | [getTransaction](https://viem.sh/docs/actions/public/getTransaction)                             |
-| getTransactionReceipt       | Returns the transaction receipt given a transaction hash.                                           | [getTransactionReceipt](https://viem.sh/docs/actions/public/getTransactionReceipt)               |
-| getTransactionConfirmations | Returns the number of blocks passed (confirmations) since the transaction was processed on a block. | [getTransactionConfirmations](https://viem.sh/docs/actions/public/getTransactionConfirmations)   |
-| call                        | An Action for executing a new message call.                                                         | [call](https://viem.sh/docs/actions/public/call)                                                 |
-| estimateGas                 | An Action for estimating gas for a transaction.                                                     | [estimateGas](https://viem.sh/docs/actions/public/estimateGas)                                   |
-| getFeeHistory               | Returns a collection of historical gas information.                                                 | [getFeeHistory](https://viem.sh/docs/actions/public/getFeeHistory)                               |
-| getProof                    | Returns the account and storage values of the specified account including the Merkle-proof.         | [getProof](https://viem.sh/docs/actions/public/getProof)                                         |
-| getEnsAddress               | Gets address for ENS name.                                                                          | [getEnsAddress](https://viem.sh/docs/ens/actions/getEnsAddress)                                  |
-| getEnsAvatar                | Gets the avatar of an ENS name.                                                                     | [getEnsAvatar](https://viem.sh/docs/ens/actions/getEnsAvatar)                                    |
-| getEnsName                  | Gets primary name for specified address.                                                            | [getEnsName](https://viem.sh/docs/ens/actions/getEnsName)                                        |
-| getEnsResolver              | Gets resolver for ENS name.                                                                         | [getEnsResolver](https://viem.sh/docs/ens/actions/getEnsResolver)                                |
-| getEnsText                  | Gets a text record for specified ENS name.                                                          | [getEnsText](https://viem.sh/docs/ens/actions/getEnsText)                                        |
+| name         | description                                               | Viem docs                                                         |
+| :----------- | :-------------------------------------------------------- | :---------------------------------------------------------------- |
+| readContract | Returns the result of a read-only function on a contract. | [readContract](https://viem.sh/docs/contract/readContract.html)   |
+| multicall    | Similar to readContract, but batches requests.            | [multicall](https://viem.sh/docs/contract/multicall.html)         |
+| getBalance   | Returns the balance of an address in wei.                 | [getBalance](https://viem.sh/docs/actions/public/getBalance.html) |
+| getBytecode  | Returns the bytecode at an address.                       | [getBytecode](https://viem.sh/docs/contract/getBytecode.html)     |
+| getStorageAt | Returns the value from a storage slot at a given address. | [getStorageAt](https://viem.sh/docs/contract/getStorageAt.html)   |
 
 ### Block number
 
diff --git a/packages/core/src/indexing/ponderActions.ts b/packages/core/src/indexing/ponderActions.ts
index 3dd6be22..9b5b31a6 100644
--- a/packages/core/src/indexing/ponderActions.ts
+++ b/packages/core/src/indexing/ponderActions.ts
@@ -1,53 +1,37 @@
 import type { Prettify } from "@/types/utils.js";
-import {
-  type Abi,
-  type Account,
-  type Address,
-  type Chain,
-  type Client,
-  type ContractFunctionArgs,
-  type ContractFunctionName,
-  type GetBlockReturnType,
-  type GetBlockTransactionCountReturnType,
-  type GetTransactionCountReturnType,
-  type Hash,
-  type MulticallParameters,
-  type MulticallReturnType,
-  type PublicActions,
-  type PublicRpcSchema,
-  type ReadContractParameters,
-  type ReadContractReturnType,
-  type SimulateContractParameters,
-  type SimulateContractReturnType,
-  type Transport,
-  publicActions,
+import type {
+  Abi,
+  Account,
+  Chain,
+  Client,
+  ContractFunctionArgs,
+  ContractFunctionName,
+  GetBalanceParameters,
+  GetBalanceReturnType,
+  GetCodeParameters,
+  GetCodeReturnType,
+  GetEnsNameParameters,
+  GetEnsNameReturnType,
+  GetStorageAtParameters,
+  GetStorageAtReturnType,
+  MulticallParameters,
+  MulticallReturnType,
+  PublicRpcSchema,
+  ReadContractParameters,
+  ReadContractReturnType,
+  Transport,
 } from "viem";
+import {
+  getBalance as viemGetBalance,
+  getCode as viemGetCode,
+  getEnsName as viemGetEnsName,
+  getStorageAt as viemGetStorageAt,
+  multicall as viemMulticall,
+  readContract as viemReadContract,
+} from "viem/actions";
 import type { Service } from "./service.js";
 
-/** Viem actions where the `block` property is optional and implicit. */
-const blockDependentActions = [
-  "getBalance",
-  "call",
-  "estimateGas",
-  "getFeeHistory",
-  "getProof",
-  "getCode",
-  "getStorageAt",
-  "getEnsAddress",
-  "getEnsAvatar",
-  "getEnsName",
-  "getEnsResolver",
-  "getEnsText",
-] as const satisfies readonly (keyof ReturnType<typeof publicActions>)[];
-
-/** Viem actions where the `block` property is non-existent. */
-const nonBlockDependentActions = [
-  "getTransaction",
-  "getTransactionReceipt",
-  "getTransactionConfirmations",
-] as const satisfies readonly (keyof ReturnType<typeof publicActions>)[];
-
-type BlockOptions =
+export type BlockOptions =
   | {
       cache?: undefined;
       blockNumber?: undefined;
@@ -61,32 +45,17 @@ type BlockOptions =
       blockNumber: bigint;
     };
 
-type RequiredBlockOptions =
-  | {
-      /** Hash of the block. */
-      blockHash: Hash;
-      blockNumber?: undefined;
-    }
-  | {
-      blockHash?: undefined;
-      /** The block number. */
-      blockNumber: bigint;
-    };
-
-type BlockDependentAction<
-  fn extends (client: any, args: any) => unknown,
-  ///
-  params = Parameters<fn>[0],
-  returnType = ReturnType<fn>,
-> = (
-  args: Omit<params, "blockTag" | "blockNumber"> & BlockOptions,
-) => returnType;
-
 export type PonderActions = {
-  [action in (typeof blockDependentActions)[number]]: BlockDependentAction<
-    ReturnType<typeof publicActions>[action]
-  >;
-} & {
+  getBalance: (
+    args: Omit<GetBalanceParameters, "blockTag" | "blockNumber"> & BlockOptions,
+  ) => Promise<GetBalanceReturnType>;
+  getCode: (
+    args: Omit<GetCodeParameters, "blockTag" | "blockNumber"> & BlockOptions,
+  ) => Promise<GetCodeReturnType>;
+  getStorageAt: (
+    args: Omit<GetStorageAtParameters, "blockTag" | "blockNumber"> &
+      BlockOptions,
+  ) => Promise<GetStorageAtReturnType>;
   multicall: <
     const contracts extends readonly unknown[],
     allowFailure extends boolean = true,
@@ -108,60 +77,19 @@ export type PonderActions = {
     > &
       BlockOptions,
   ) => Promise<ReadContractReturnType<abi, functionName, args>>;
-  simulateContract: <
-    const abi extends Abi | readonly unknown[],
-    functionName extends ContractFunctionName<abi, "nonpayable" | "payable">,
-    const args extends ContractFunctionArgs<
-      abi,
-      "nonpayable" | "payable",
-      functionName
-    >,
-  >(
-    args: Omit<
-      SimulateContractParameters<abi, functionName, args>,
-      "blockTag" | "blockNumber"
-    > &
-      BlockOptions,
-  ) => Promise<SimulateContractReturnType<abi, functionName, args>>;
-  getBlock: <includeTransactions extends boolean = false>(
-    args: {
-      /** Whether or not to include transaction data in the response. */
-      includeTransactions?: includeTransactions | undefined;
-    } & RequiredBlockOptions,
-  ) => Promise<GetBlockReturnType<Chain | undefined, includeTransactions>>;
-  getTransactionCount: (
-    args: {
-      /** The account address. */
-      address: Address;
-    } & RequiredBlockOptions,
-  ) => Promise<GetTransactionCountReturnType>;
-  getBlockTransactionCount: (
-    args: RequiredBlockOptions,
-  ) => Promise<GetBlockTransactionCountReturnType>;
-} & Pick<PublicActions, (typeof nonBlockDependentActions)[number]>;
+  getEnsName: (
+    args: Omit<GetEnsNameParameters, "blockTag" | "blockNumber"> & BlockOptions,
+  ) => Promise<GetEnsNameReturnType>;
+};
 
 export type ReadOnlyClient<
   transport extends Transport = Transport,
   chain extends Chain | undefined = Chain | undefined,
 > = Prettify<
-  Omit<
-    Client<transport, chain, undefined, PublicRpcSchema, PonderActions>,
-    | "extend"
-    | "key"
-    | "batch"
-    | "cacheTime"
-    | "account"
-    | "type"
-    | "uid"
-    | "chain"
-    | "name"
-    | "pollingInterval"
-    | "transport"
-    | "ccipRead"
-  >
+  Client<transport, chain, undefined, PublicRpcSchema, PonderActions>
 >;
 
-export const getPonderActions = (
+export const buildCachedActions = (
   contextState: Pick<Service["currentEvent"]["contextState"], "blockNumber">,
 ) => {
   return <
@@ -170,58 +98,96 @@ export const getPonderActions = (
     TAccount extends Account | undefined = Account | undefined,
   >(
     client: Client<TTransport, TChain, TAccount>,
-  ): PonderActions => {
-    const actions = {} as PonderActions;
-    const _publicActions = publicActions(client);
-
-    const addAction = <
-      action extends
-        | (typeof blockDependentActions)[number]
-        | "multicall"
-        | "readContract"
-        | "simulateContract",
-    >(
-      action: action,
-    ) => {
-      // @ts-ignore
-      actions[action] = ({
-        cache,
-        blockNumber: userBlockNumber,
-        ...args
-      }: Parameters<PonderActions[action]>[0]) =>
-        // @ts-ignore
-        _publicActions[action]({
-          ...args,
-          ...(cache === "immutable"
-            ? { blockTag: "latest" }
-            : { blockNumber: userBlockNumber ?? contextState.blockNumber }),
-        } as Parameters<ReturnType<typeof publicActions>[action]>[0]);
-    };
-
-    for (const action of blockDependentActions) {
-      addAction(action);
-    }
-
-    addAction("multicall");
-    addAction("readContract");
-    addAction("simulateContract");
-
-    for (const action of nonBlockDependentActions) {
-      // @ts-ignore
-      actions[action] = _publicActions[action];
-    }
-
-    // required block actions
-
-    for (const action of [
-      "getBlock",
-      "getBlockTransactionCount",
-      "getTransactionCount",
-    ]) {
-      // @ts-ignore
-      actions[action] = _publicActions[action];
-    }
-
-    return actions;
-  };
+  ): PonderActions => ({
+    getBalance: ({
+      cache,
+      blockNumber: userBlockNumber,
+      ...args
+    }: Omit<GetBalanceParameters, "blockTag" | "blockNumber"> &
+      BlockOptions): Promise<GetBalanceReturnType> =>
+      viemGetBalance(client, {
+        ...args,
+        ...(cache === "immutable"
+          ? { blockTag: "latest" }
+          : { blockNumber: userBlockNumber ?? contextState.blockNumber }),
+      }),
+    getCode: ({
+      cache,
+      blockNumber: userBlockNumber,
+      ...args
+    }: Omit<GetCodeParameters, "blockTag" | "blockNumber"> &
+      BlockOptions): Promise<GetCodeReturnType> =>
+      viemGetCode(client, {
+        ...args,
+        ...(cache === "immutable"
+          ? { blockTag: "latest" }
+          : { blockNumber: userBlockNumber ?? contextState.blockNumber }),
+      }),
+    getStorageAt: ({
+      cache,
+      blockNumber: userBlockNumber,
+      ...args
+    }: Omit<GetStorageAtParameters, "blockTag" | "blockNumber"> &
+      BlockOptions): Promise<GetStorageAtReturnType> =>
+      viemGetStorageAt(client, {
+        ...args,
+        ...(cache === "immutable"
+          ? { blockTag: "latest" }
+          : { blockNumber: userBlockNumber ?? contextState.blockNumber }),
+      }),
+    multicall: <
+      const contracts extends readonly unknown[],
+      allowFailure extends boolean = true,
+    >({
+      cache,
+      blockNumber: userBlockNumber,
+      ...args
+    }: Omit<
+      MulticallParameters<contracts, allowFailure>,
+      "blockTag" | "blockNumber"
+    > &
+      BlockOptions): Promise<MulticallReturnType<contracts, allowFailure>> =>
+      viemMulticall(client, {
+        ...args,
+        ...(cache === "immutable"
+          ? { blockTag: "latest" }
+          : { blockNumber: userBlockNumber ?? contextState.blockNumber }),
+      }),
+    // @ts-ignore
+    readContract: <
+      const abi extends Abi | readonly unknown[],
+      functionName extends ContractFunctionName<abi, "pure" | "view">,
+      const args extends ContractFunctionArgs<
+        abi,
+        "pure" | "view",
+        functionName
+      >,
+    >({
+      cache,
+      blockNumber: userBlockNumber,
+      ...args
+    }: Omit<
+      ReadContractParameters<abi, functionName, args>,
+      "blockTag" | "blockNumber"
+    > &
+      BlockOptions): Promise<ReadContractReturnType<abi, functionName, args>> =>
+      viemReadContract(client, {
+        ...args,
+        ...(cache === "immutable"
+          ? { blockTag: "latest" }
+          : { blockNumber: userBlockNumber ?? contextState.blockNumber }),
+      } as ReadContractParameters<abi, functionName, args>),
+    getEnsName: ({
+      cache,
+      blockNumber: userBlockNumber,
+      ...args
+    }: Omit<GetEnsNameParameters, "blockTag" | "blockNumber"> &
+      BlockOptions): Promise<GetEnsNameReturnType> =>
+      viemGetEnsName(client, {
+        ...args,
+        ...(cache === "immutable"
+          ? { blockTag: "latest" }
+          : { blockNumber: userBlockNumber ?? contextState.blockNumber }),
+      }),
+  });
 };
diff --git a/packages/core/src/indexing/service.ts b/packages/core/src/indexing/service.ts
index 501d0922..f427ec37 100644
--- a/packages/core/src/indexing/service.ts
+++ b/packages/core/src/indexing/service.ts
@@ -29,7 +29,7 @@ import type {
   SetupEvent,
 } from "../sync/events.js";
 import { addStackTrace } from "./addStackTrace.js";
-import { type ReadOnlyClient, getPonderActions } from "./ponderActions.js";
+import { type ReadOnlyClient, buildCachedActions } from "./ponderActions.js";
 
 export type Context = {
   network: { chainId: number; name: string };
@@ -142,6 +142,9 @@ export const create = ({
     };
   }
 
+  // build cachedActions
+  const cachedActions = buildCachedActions(contextState);
+
   // build clientByChainId
   for (const network of networks) {
     const transport = sync.getCachedTransport(network);
@@ -149,7 +152,7 @@ export const create = ({
       transport,
       chain: network.chain,
       // @ts-ignore
-    }).extend(getPonderActions(contextState));
+    }).extend(cachedActions);
   }
 
   // build eventCount
diff --git a/packages/core/src/sync-store/encoding.ts b/packages/core/src/sync-store/encoding.ts
index 1b15ad12..23e665df 100644
--- a/packages/core/src/sync-store/encoding.ts
+++ b/packages/core/src/sync-store/encoding.ts
@@ -299,12 +299,8 @@ export function encodeCallTrace({
 
 type RpcRequestResultsTable = {
   request: string;
-  chain_id: number;
-  block_number: ColumnType<
-    string | undefined,
-    string | bigint | undefined,
-    string | bigint | undefined
-  >;
+  chainId: number;
+  blockNumber: ColumnType<string, string | bigint, string | bigint>;
   result: string;
 };
 
@@ -397,7 +393,7 @@ export type PonderSyncSchema = {
   transactionReceipts: TransactionReceiptsTable;
   callTraces: CallTracesTable;
 
-  rpc_request_results: RpcRequestResultsTable;
+  rpcRequestResults: RpcRequestResultsTable;
 
   logFilters: LogFiltersTable;
   logFilterIntervals: LogFilterIntervalsTable;
diff --git a/packages/core/src/sync-store/index.ts b/packages/core/src/sync-store/index.ts
index 8116e29b..f86be92b 100644
--- a/packages/core/src/sync-store/index.ts
+++ b/packages/core/src/sync-store/index.ts
@@ -108,14 +108,15 @@ export type SyncStore = {
   }): Promise<{ events: RawEvent[]; cursor: string }>;
   insertRpcRequestResult(args: {
     request: string;
+    blockNumber: bigint;
     chainId: number;
-    blockNumber: bigint | undefined;
     result: string;
   }): Promise<void>;
   getRpcRequestResult(args: {
     request: string;
+    blockNumber: bigint;
     chainId: number;
-  }): Promise<string | undefined>;
+  }): Promise<string | null>;
   pruneRpcRequestResult(args: {
     blocks: Pick<LightBlock, "number">[];
     chainId: number;
@@ -1228,28 +1229,31 @@ export const createSyncStore = ({
   insertRpcRequestResult: async ({ request, blockNumber, chainId, result }) =>
     db.wrap({ method: "insertRpcRequestResult" }, async () => {
       await db
-        .insertInto("rpc_request_results")
+        .insertInto("rpcRequestResults")
         .values({
           request,
-          block_number: blockNumber,
-          chain_id: chainId,
+          blockNumber,
+          chainId,
           result,
         })
         .onConflict((oc) =>
-          oc.columns(["request", "chain_id"]).doUpdateSet({ result }),
+          oc
+            .columns(["request", "chainId", "blockNumber"])
+            .doUpdateSet({ result }),
         )
         .execute();
     }),
-  getRpcRequestResult: async ({ request, chainId }) =>
+  getRpcRequestResult: async ({ request, blockNumber, chainId }) =>
     db.wrap({ method: "getRpcRequestResult" }, async () => {
       const result = await db
-        .selectFrom("rpc_request_results")
+        .selectFrom("rpcRequestResults")
         .select("result")
         .where("request", "=", request)
-        .where("chain_id", "=", chainId)
+        .where("chainId", "=", chainId)
+        .where("blockNumber", "=", blockNumber.toString())
         .executeTakeFirst();
 
-      return result?.result;
+      return result?.result ?? null;
     }),
   pruneRpcRequestResult: async ({ blocks, chainId }) =>
     db.wrap({ method: "pruneRpcRequestResult" }, async () => {
@@ -1260,9 +1264,9 @@ export const createSyncStore = ({
       );
 
       await db
-        .deleteFrom("rpc_request_results")
-        .where("chain_id", "=", chainId)
-        .where("block_number", "in", numbers)
+        .deleteFrom("rpcRequestResults")
+        .where("chainId", "=", chainId)
+        .where("blockNumber", "in", numbers)
         .execute();
     }),
   pruneByChain: async ({ fromBlock, chainId }) =>
@@ -1488,9 +1492,9 @@ export const createSyncStore = ({
           .where("number", ">=", fromBlock.toString())
           .execute();
         await tx
-          .deleteFrom("rpc_request_results")
-          .where("chain_id", "=", chainId)
-          .where("block_number", ">=", fromBlock.toString())
+          .deleteFrom("rpcRequestResults")
+          .where("chainId", "=", chainId)
+          .where("blockNumber", ">=", fromBlock.toString())
           .execute();
         await tx
           .deleteFrom("callTraces")
diff --git a/packages/core/src/sync-store/migrations.ts b/packages/core/src/sync-store/migrations.ts
index b8d6c871..16317575 100644
--- a/packages/core/src/sync-store/migrations.ts
+++ b/packages/core/src/sync-store/migrations.ts
@@ -840,70 +840,6 @@ const migrations: Record<string, Migration> = {
         .execute();
     },
   },
-  "2024_11_04_0_request_cache": {
-    async up(db: Kysely<any>) {
-      await db.schema
-        .createTable("rpc_request_results")
-        .addColumn("request", "text", (col) => col.notNull())
-        .addColumn("block_number", "numeric(78, 0)")
-        .addColumn("chain_id", "integer", (col) => col.notNull())
-        .addColumn("result", "text", (col) => col.notNull())
-        .addPrimaryKeyConstraint("rpc_request_result_primary_key", [
-          "request",
-          "chain_id",
-        ])
-        .execute();
-
-      await db.executeQuery(
-        sql`
-INSERT INTO ponder_sync.rpc_request_results (request, block_number, chain_id, result)
-SELECT 
-  CONCAT (
-    '{"method":"eth_getbalance","params":["',
-    LOWER(SUBSTRING(request, 16)),
-    '","0x',
-    to_hex("blockNumber"::bigint),
-    '"]}'
-  ) as request,
-  "blockNumber" as block_number,
-  "chainId" as chain_id,
-  result
-FROM ponder_sync."rpcRequestResults"
-WHERE ponder_sync."rpcRequestResults".request LIKE 'eth_getBalance_%'
-AND ponder_sync."rpcRequestResults"."blockNumber" <= 9223372036854775807;
-`.compile(db),
-      );
-
-      await db.executeQuery(
-        sql`
-INSERT INTO ponder_sync.rpc_request_results (request, block_number, chain_id, result)
-SELECT 
-  CONCAT (
-    '{"method":"eth_call","params":[{"address":"',
-    LOWER(SUBSTRING(request, 10, 42)),
-    '","data":"',
-    LOWER(SUBSTRING(request, 53)),
-    '"},"0x',
-    to_hex("blockNumber"::bigint),
-    '"]}'
-  ) as request,
-  "blockNumber" as block_number,
-  "chainId" as chain_id,
-  result
-FROM ponder_sync."rpcRequestResults"
-WHERE ponder_sync."rpcRequestResults".request LIKE 'eth_call_%'
-AND ponder_sync."rpcRequestResults"."blockNumber" <= 9223372036854775807;
-`.compile(db),
-      );
-    },
-    async down(db: Kysely<any>) {
-      await db.schema
-        .dropTable("rpc_request_results")
-        .ifExists()
-        .cascade()
-        .execute();
-    },
-  },
 };
 
 class StaticMigrationProvider implements MigrationProvider {
diff --git a/packages/core/src/sync/transport.ts b/packages/core/src/sync/transport.ts
index 2bd24829..a993a912 100644
--- a/packages/core/src/sync/transport.ts
+++ b/packages/core/src/sync/transport.ts
@@ -1,37 +1,15 @@
 import type { SyncStore } from "@/sync-store/index.js";
 import { toLowerCase } from "@/utils/lowercase.js";
-import { orderObject } from "@/utils/order.js";
 import type { RequestQueue } from "@/utils/requestQueue.js";
-import type { Hex, Transport } from "viem";
+import type { Address, Hex, Transport } from "viem";
 import { custom, hexToBigInt, maxUint256 } from "viem";
 
-/** RPC methods that reference a block. */
-const blockDependentMethods = new Set([
-  "eth_getBalance",
-  "eth_getTransactionCount",
-  "eth_getBlockByNumber",
-  "eth_getBlockTransactionCountByNumber",
-  "eth_getTransactionByBlockNumberAndIndex",
+const cachedMethods = [
   "eth_call",
-  "eth_estimateGas",
-  "eth_feeHistory",
-  "eth_getProof",
+  "eth_getBalance",
   "eth_getCode",
   "eth_getStorageAt",
-  "eth_getUncleByBlockNumberAndIndex",
-]);
-
-/** RPC methods that don't reference a block. */
-const nonBlockDependentMethods = new Set([
-  "eth_getBlockByHash",
-  "eth_getTransactionByHash",
-  "eth_getBlockTransactionCountByHash",
-  "eth_getTransactionByBlockHashAndIndex",
-  "eth_getTransactionConfirmations",
-  "eth_getTransactionReceipt",
-  "eth_getUncleByBlockHashAndIndex",
-  "eth_getUncleCountByBlockHash",
-]);
+] as const;
 
 export const cachedTransport = ({
   requestQueue,
@@ -45,54 +23,56 @@ export const cachedTransport = ({
       async request({ method, params }) {
         const body = { method, params };
 
-        if (
-          blockDependentMethods.has(method) ||
-          nonBlockDependentMethods.has(method)
-        ) {
-          const request = toLowerCase(JSON.stringify(orderObject(body)));
-          let blockNumber: Hex | "latest" | undefined = undefined;
+        if (cachedMethods.includes(method)) {
+          let request: string = undefined!;
+          let blockNumber: Hex | "latest" = undefined!;
+
+          if (method === "eth_call") {
+            const [{ data, to }, _blockNumber] = params as [
+              { data: Hex; to: Hex },
+              Hex | "latest",
+            ];
 
-          switch (method) {
-            case "eth_getBlockByNumber":
-            case "eth_getBlockTransactionCountByNumber":
-            case "eth_getTransactionByBlockNumberAndIndex":
-            case "eth_getUncleByBlockNumberAndIndex":
-              [blockNumber] = params;
-              break;
-            case "eth_getBalance":
-            case "eth_call":
-            case "eth_getCode":
-            case "eth_estimateGas":
-            case "eth_feeHistory":
-            case "eth_getTransactionCount":
-              [, blockNumber] = params;
-              break;
+            request = `${method as string}_${toLowerCase(to)}_${toLowerCase(data)}`;
+            blockNumber = _blockNumber;
+          } else if (method === "eth_getBalance") {
+            const [address, _blockNumber] = params as [Address, Hex | "latest"];
 
-            case "eth_getProof":
-            case "eth_getStorageAt":
-              [, , blockNumber] = params;
-              break;
+            request = `${method as string}_${toLowerCase(address)}`;
+            blockNumber = _blockNumber;
+          } else if (method === "eth_getCode") {
+            const [address, _blockNumber] = params as [Address, Hex | "latest"];
+
+            request = `${method as string}_${toLowerCase(address)}`;
+            blockNumber = _blockNumber;
+          } else if (method === "eth_getStorageAt") {
+            const [address, slot, _blockNumber] = params as [
+              Address,
+              Hex,
+              Hex | "latest",
+            ];
+
+            request = `${method as string}_${toLowerCase(address)}_${toLowerCase(slot)}`;
+            blockNumber = _blockNumber;
           }
 
-          const cacheKey = {
+          const blockNumberBigInt =
+            blockNumber === "latest" ? maxUint256 : hexToBigInt(blockNumber);
+
+          const cachedResult = await syncStore.getRpcRequestResult({
+            blockNumber: blockNumberBigInt,
             chainId: chain!.id,
             request,
-            blockNumber:
-              blockNumber === undefined
-                ? undefined
-                : blockNumber === "latest"
-                  ? maxUint256
-                  : hexToBigInt(blockNumber),
-          };
-
-          const cachedResult = await syncStore.getRpcRequestResult(cacheKey);
+          });
 
-          if (cachedResult !== undefined) return JSON.parse(cachedResult);
+          if (cachedResult !== null) return cachedResult;
           else {
             const response = await requestQueue.request(body);
             await syncStore.insertRpcRequestResult({
-              ...cacheKey,
-              result: JSON.stringify(response),
+              blockNumber: blockNumberBigInt,
+              chainId: chain!.id,
+              request,
+              result: response as string,
             });
             return response;
           }
diff --git a/packages/core/src/types/virtual.ts b/packages/core/src/types/virtual.ts
index f3096b30..8dfc0194 100644
--- a/packages/core/src/types/virtual.ts
+++ b/packages/core/src/types/virtual.ts
@@ -202,7 +202,23 @@ export namespace Virtual {
               keyof config["networks"]]["chainId"];
           };
         }[keyof sourceNetwork];
-    client: Prettify<ReadOnlyClient>;
+    client: Prettify<
+      Omit<
+        ReadOnlyClient,
+        | "extend"
+        | "key"
+        | "batch"
+        | "cacheTime"
+        | "account"
+        | "type"
+        | "uid"
+        | "chain"
+        | "name"
+        | "pollingInterval"
+        | "transport"
+        | "ccipRead"
+      >
+    >;
     db: Db<schema>;
   };
 
diff --git a/packages/core/src/utils/order.ts b/packages/core/src/utils/order.ts
deleted file mode 100644
index 61d7ce75..00000000
--- a/packages/core/src/utils/order.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export const orderObject = <T extends { [key: string]: any } | any[]>(
-  obj: T,
-): T => {
-  if (Array.isArray(obj)) return obj.map((x) => orderObject(x)) as T;
-
-  const newObj = {} as T;
-  for (const key of Object.keys(obj).sort()) {
-    const val = obj[key];
-    if (typeof val === "object") {
-      newObj[key as keyof T] = orderObject(obj[key]);
-    } else {
-      newObj[key as keyof T] = obj[key];
-    }
-  }
-
-  return newObj;
-};


Test patch:
diff --git a/packages/core/src/sync-store/index.test.ts b/packages/core/src/sync-store/index.test.ts
index 049e6146..1e676c9a 100644
--- a/packages/core/src/sync-store/index.test.ts
+++ b/packages/core/src/sync-store/index.test.ts
@@ -1043,7 +1043,7 @@ test("pruneRpcRequestResult", async (context) => {
   });
 
   const requestResults = await database.qb.sync
-    .selectFrom("rpc_request_results")
+    .selectFrom("rpcRequestResults")
     .selectAll()
     .execute();
 
diff --git a/packages/core/src/sync/transport.test.ts b/packages/core/src/sync/transport.test.ts
index d76a3a4f..9a48d439 100644
--- a/packages/core/src/sync/transport.test.ts
+++ b/packages/core/src/sync/transport.test.ts
@@ -6,7 +6,7 @@ import {
 } from "@/_test/setup.js";
 import { anvil, publicClient } from "@/_test/utils.js";
 import type { Transport } from "viem";
-import { toHex } from "viem";
+import { getFunctionSelector, toHex } from "viem";
 import { assertType, beforeEach, expect, test, vi } from "vitest";
 import { cachedTransport } from "./transport.js";
 
@@ -44,8 +44,8 @@ test("default", async (context) => {
   await cleanup();
 });
 
-test("request() block dependent method", async (context) => {
-  const { requestQueues } = context;
+test("eth_call", async (context) => {
+  const { erc20, requestQueues } = context;
   const { syncStore, cleanup } = await setupDatabaseServices(context);
   const blockNumber = await publicClient.getBlockNumber();
 
@@ -57,32 +57,59 @@ test("request() block dependent method", async (context) => {
   });
 
   const response1 = await transport.request({
-    method: "eth_getBlockByNumber",
-    params: [toHex(blockNumber), false],
+    method: "eth_call",
+    params: [
+      {
+        data: getFunctionSelector("totalSupply()"),
+        to: erc20.address,
+      },
+      toHex(blockNumber),
+    ],
   });
 
   expect(response1).toBeDefined();
 
   const insertSpy = vi.spyOn(syncStore, "insertRpcRequestResult");
-  const getSpy = vi.spyOn(syncStore, "getRpcRequestResult");
 
   const response2 = await transport.request({
-    method: "eth_getBlockByNumber",
-    params: [toHex(blockNumber), false],
+    method: "eth_call",
+    params: [
+      {
+        data: getFunctionSelector("totalSupply()"),
+        to: erc20.address,
+      },
+      toHex(blockNumber),
+    ],
   });
 
-  expect(response1).toStrictEqual(response2);
+  expect(response1).toBe(response2);
 
   expect(insertSpy).toHaveBeenCalledTimes(0);
+
+  const getSpy = vi.spyOn(syncStore, "getRpcRequestResult");
+
+  const response3 = await transport.request({
+    method: "eth_call",
+    params: [
+      {
+        data: getFunctionSelector("totalSupply()"),
+        to: erc20.address,
+      },
+      "latest",
+    ],
+  });
+
+  expect(response3).toBeDefined();
+
   expect(getSpy).toHaveBeenCalledTimes(1);
 
   await cleanup();
 });
 
-test("request() non-block dependent method", async (context) => {
-  const { requestQueues } = context;
+test("eth_getBalance", async (context) => {
+  const { erc20, requestQueues } = context;
   const { syncStore, cleanup } = await setupDatabaseServices(context);
-  const block = await publicClient.getBlock({ blockNumber: 2n });
+  const blockNumber = await publicClient.getBlockNumber();
 
   const transport = cachedTransport({
     requestQueue: requestQueues[0],
@@ -92,31 +119,42 @@ test("request() non-block dependent method", async (context) => {
   });
 
   const response1 = await transport.request({
-    method: "eth_getTransactionByHash",
-    params: [block.transactions[0]!],
+    method: "eth_getBalance",
+    params: [erc20.address, toHex(blockNumber)],
   });
 
-  expect(response1).toBeDefined;
+  expect(response1).toBeDefined();
 
   const insertSpy = vi.spyOn(syncStore, "insertRpcRequestResult");
-  const getSpy = vi.spyOn(syncStore, "getRpcRequestResult");
 
   const response2 = await transport.request({
-    method: "eth_getTransactionByHash",
-    params: [block.transactions[0]!],
+    method: "eth_getBalance",
+    params: [erc20.address, toHex(blockNumber)],
   });
 
-  expect(response1).toStrictEqual(response2);
+  expect(response1).toBe(response2);
 
   expect(insertSpy).toHaveBeenCalledTimes(0);
+
+  const getSpy = vi.spyOn(syncStore, "getRpcRequestResult");
+
+  const response3 = await transport.request({
+    method: "eth_getBalance",
+    params: [erc20.address, "latest"],
+  });
+
+  expect(response3).toBeDefined();
+
   expect(getSpy).toHaveBeenCalledTimes(1);
 
   await cleanup();
 });
 
-test("request() non-cached method", async (context) => {
-  const { requestQueues } = context;
+test("eth_getStorageAt", async (context) => {
+  const { erc20, requestQueues } = context;
   const { syncStore, cleanup } = await setupDatabaseServices(context);
+  const blockNumber = await publicClient.getBlockNumber();
+
   const transport = cachedTransport({
     requestQueue: requestQueues[0],
     syncStore,
@@ -124,13 +162,93 @@ test("request() non-cached method", async (context) => {
     chain: anvil,
   });
 
+  const response1 = await transport.request({
+    method: "eth_getStorageAt",
+    params: [erc20.address, toHex(3), toHex(blockNumber)],
+  });
+
+  expect(response1).toBeDefined();
+
   const insertSpy = vi.spyOn(syncStore, "insertRpcRequestResult");
+
+  const response2 = await transport.request({
+    method: "eth_getStorageAt",
+    params: [erc20.address, toHex(3), toHex(blockNumber)],
+  });
+
+  expect(response1).toBe(response2);
+
+  expect(insertSpy).toHaveBeenCalledTimes(0);
+
   const getSpy = vi.spyOn(syncStore, "getRpcRequestResult");
 
-  expect(await transport.request({ method: "eth_blockNumber" })).toBeDefined();
+  const response3 = await transport.request({
+    method: "eth_getStorageAt",
+    params: [erc20.address, toHex(3), "latest"],
+  });
+
+  expect(response3).toBeDefined();
+
+  expect(getSpy).toHaveBeenCalledTimes(1);
+
+  await cleanup();
+});
+
+test("eth_getCode", async (context) => {
+  const { erc20, requestQueues } = context;
+  const { syncStore, cleanup } = await setupDatabaseServices(context);
+  const blockNumber = await publicClient.getBlockNumber();
+
+  const transport = cachedTransport({
+    requestQueue: requestQueues[0],
+    syncStore,
+  })({
+    chain: anvil,
+  });
+
+  const response1 = await transport.request({
+    method: "eth_getCode",
+    params: [erc20.address, toHex(blockNumber)],
+  });
+
+  expect(response1).toBeDefined();
+
+  const insertSpy = vi.spyOn(syncStore, "insertRpcRequestResult");
+
+  const response2 = await transport.request({
+    method: "eth_getCode",
+    params: [erc20.address, toHex(blockNumber)],
+  });
+
+  expect(response1).toBe(response2);
 
   expect(insertSpy).toHaveBeenCalledTimes(0);
-  expect(getSpy).toHaveBeenCalledTimes(0);
+
+  const getSpy = vi.spyOn(syncStore, "getRpcRequestResult");
+
+  const response3 = await transport.request({
+    method: "eth_getCode",
+    params: [erc20.address, "latest"],
+  });
+
+  expect(response3).toBeDefined();
+
+  expect(getSpy).toHaveBeenCalledTimes(1);
+
+  await cleanup();
+});
+
+test("fallback method", async (context) => {
+  const { requestQueues } = context;
+  const { syncStore, cleanup } = await setupDatabaseServices(context);
+  const transport = cachedTransport({
+    requestQueue: requestQueues[0],
+    syncStore,
+  })({
+    chain: anvil,
+  });
+
+  expect(await transport.request({ method: "eth_blockNumber" })).toBeDefined();
 
   await cleanup();
 });
diff --git a/packages/core/src/types/virtual.test-d.ts b/packages/core/src/types/virtual.test-d.ts
index 160d8bef..4aae4300 100644
--- a/packages/core/src/types/virtual.test-d.ts
+++ b/packages/core/src/types/virtual.test-d.ts
@@ -272,6 +272,7 @@ test("Context client", () => {
     | "getEnsName";
 
   assertType<keyof a>({} as any as expectedFunctions);
+  assertType<expectedFunctions>({} as any as keyof a);
 });
 
 test("Context contracts", () => {