@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,
transport: 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,
transport: rateLimit(http(process.env.PONDER_RPC_URL_1), {
requestsPerSecond: 25,
}),
},
},
contracts: {
// ...
},
});