@ponder/utils
The @ponder/utils package provides utility functions for common tasks in Ponder apps.
mergeAbis
Combines many ABIs into one. Removes duplicate items if necessary.
Usage
import { mergeAbis } from "@ponder/utils";
import { erc20Abi, erc4626Abi } from "viem";
const tokenAbi = mergeAbis([erc20Abi, erc4626Abi]);Parameters
| Parameter | Type | Description |
|---|---|---|
ABIs | Abi[] | A list of ABIs to merge. |
Returns
A new ABI including all items from the input list, with duplicates removed.
Usage in Ponder
Use mergeAbis to include multiple ABIs for a single contract. This is especially useful for proxy contracts that have had several different implementation ABIs.
For convenience, ponder re-exports mergeAbis from @ponder/utils.
import { createConfig, mergeAbis } from "ponder";
import { ERC1967ProxyAbi } from "./abis/ERC1967Proxy";
import { NameRegistryAbi } from "./abis/NameRegistry";
import { NameRegistry2Abi } from "./abis/NameRegistry2";
export default createConfig({
contracts: {
FarcasterNameRegistry: {
abi: mergeAbis([ERC1967ProxyAbi, NameRegistryAbi, NameRegistry2Abi]),
// ...
},
},
// ...
});replaceBigInts
Replaces all BigInt values in an object (deep traversal) with a new value, specified by a replacer function.
Usage
This example simply converts BigInt values to a string.
import { replaceBigInts } from "@ponder/utils";
const obj = { a: 100n, b: [-12n, 3_000_000_000n] };
const result = replaceBigInts(obj, (v) => String(v));
// ?^ { a: '100', b: [ '-12', '3000000000' ] }Parameters
| Parameter | Type | Description |
|---|---|---|
value | any | The scalar, array, or object containing BigInt values to be replaced. |
replacer | (value: bigint) => JSONSerializable | A custom replacer function that will be called for each BigInt value. |
Returns
The scalar, array, or object with all BigInt values replaced.
Replacer functions
Here are three common ways to replace BigInt values.
| Encoding | Replacer type | Replacer function |
|---|---|---|
| Hex | 0x${string} | numberToHex |
| String | string | String |
| Lossless string | #bigint.${string} | (x) => #bigint.${String(x)} |
See the Wagmi FAQ for more information on BigInt serialization.
Usage in Ponder: json columns
The json column type does not support BigInt values. Use replaceBigInts to prepare objects containing BigInt values for insertion.
import { ponder } from "ponder:registry";
import { userOperations } from "ponder:schema";
import { replaceBigInts } from "@ponder/utils";
import { toHex } from "viem";
ponder.on("EntryPoint:UserOp", async ({ event, context }) => {
await context.db.insert(userOperations).values({
id: event.log.id,
receipt: replaceBigInts(event.transactionReceipt, toHex),
});
});To maintain type safety for column values, use the ReplaceBigInts helper type in the column $type annotation.
import { onchainTable } from "ponder";
import type { ReplaceBigInts } from "@ponder/utils";
import type { TransactionReceipt, Hex } from "viem";
export const userOperations = onchainTable("user_operations", (t) => ({
id: t.text().primaryKey(),
receipt: t.json<ReplaceBigInts<TransactionReceipt, Hex>>(),
}));Usage in Ponder: API endpoints
The GraphQL API automatically serializes BigInt values to strings before returning them in HTTP responses. In custom API endpoints, you need to handle this serialization process manually.
import { ponder } from "ponder:registry";
import { accounts } from "ponder:schema";
import { replaceBigInts } from "@ponder/utils";
import { numberToHex } from "viem";
ponder.get("/whale-balances", async (c) => {
const rows = await c.db
.select({
address: accounts.address,
ethBalance: accounts.ethBalance,
dogeBalance: accounts.dogeBalance,
})
.from(accounts)
.where(eq(accounts.address, address));
const result = replaceBigInts(rows, (v) => numberToHex(v));
return c.json(result);
});Transports
The @ponder/utils package exports two new Viem transports, loadBalance and rateLimit. These transports are useful for managing the RPC request workloads that Ponder apps commonly encounter.
loadBalance
The loadBalance Transport distributes requests across a list of inner Transports in a simple round-robin scheme.
Usage
import { loadBalance } from "@ponder/utils";
import { createPublicClient, fallback, http, webSocket } from "viem";
import { mainnet } from "viem/chains";
const transport = loadBalance([
http("https://cloudflare-eth.com"),
webSocket("wss://ethereum-rpc.publicnode.com"),
rateLimit(http("https://rpc.ankr.com/eth"), { requestsPerSecond: 5 }),
]),
const client = createPublicClient({ chain: mainnet, transport });Parameters
| Parameter | Type | Description |
|---|---|---|
Transports | Transport[] | A list of Transports to load balance requests across. |
Usage in Ponder
For convenience, ponder re-exports loadBalance from @ponder/utils.
import { createConfig, loadBalance } from "ponder";
import { http, webSocket, rateLimit } from "viem";
export default createConfig({
chains: {
mainnet: {
id: 1,
rpc: loadBalance([
http("https://cloudflare-eth.com"),
http("https://eth-mainnet.public.blastapi.io"),
webSocket("wss://ethereum-rpc.publicnode.com"),
rateLimit(http("https://rpc.ankr.com/eth"), { requestsPerSecond: 5 }),
]),
},
},
// ...
});rateLimit
The rateLimit Transport limits the number of requests per second submitted to an inner Transport using a first-in-first-out queue.
Usage
import { rateLimit } from "@ponder/utils";
import { createPublicClient, fallback, http } from "viem";
import { mainnet } from "viem/chains";
const client = createPublicClient({
chain: mainnet,
transport: rateLimit(http("https://eth-mainnet.g.alchemy.com/v2/..."), {
requestsPerSecond: 25,
}),
});Parameters
| Parameter | Type | Description |
|---|---|---|
Transport | Transport | An inner transport to rate limit. |
requestsPerSecond | number | The maximum number of requests per second to allow. |
browser | boolean (default: true) | If false, the internal queue will use the Node.js-specific process.nextTick() API to schedule requests. This leads to more predictable behavior in Node.js, but is not available in the browser. |
Usage in Ponder
For convenience, ponder re-exports rateLimit from @ponder/utils.
import { createConfig, rateLimit } from "ponder";
import { http } from "viem";
export default createConfig({
chains: {
mainnet: {
id: 1,
rpc: rateLimit(http(process.env.PONDER_RPC_URL_1), {
requestsPerSecond: 25,
}),
},
},
contracts: {
// ...
},
});