Skip to content

Viem integration ​

Both SDK modes plug into Viem's createBundlerClient. The example below uses public prepaid USDC; see Native sponsorship for the authenticated ERC-7677 client.

ts
import {createPublicClient, defineChain, http} from "viem";
import {createBundlerClient} from "viem/account-abstraction";
import {
  createInkPublicPaymasterClient,
  createInkViemPaymaster,
  INK_PUBLIC_PAYMASTER,
} from "@hellomoon/ink-paymaster";

// Supply a production-grade Ink RPC from your application's configuration.
const inkRpcUrl = process.env.INK_RPC_URL!;
const ink = defineChain({
  id: INK_PUBLIC_PAYMASTER.chainId,
  name: INK_PUBLIC_PAYMASTER.chainName,
  nativeCurrency: {name: "Ether", symbol: "ETH", decimals: 18},
  rpcUrls: {default: {http: [inkRpcUrl]}},
});

const publicClient = createPublicClient({chain: ink, transport: http(inkRpcUrl)});
const service = createInkPublicPaymasterClient();
const bundlerClient = createBundlerClient({
  account,
  chain: ink,
  client: publicClient,
  transport: http(service.bundlerUrl),
  paymaster: createInkViemPaymaster(service),
  paymasterContext: {
    maxTokenCost: 100_000n,
    idempotencyKey: "checkout-order-123",
  },
});

Where callData comes from ​

When you pass calls to sendUserOperation, Viem invokes the smart account's encodeCalls implementation. This produces the account-specific callData; it does not come from the paymaster.

ts
const hash = await bundlerClient.sendUserOperation({
  calls: [{to: destination, value: 0n, data: transferCalldata}],
});

This matters because different accounts expose different execution ABIs. A SimpleAccount might encode execute(target, value, data), while another account may encode a batch executor.

Choosing maxTokenCost ​

maxTokenCost is the maximum amount of the selected reimbursement token that the quote may precharge, in that token's base units. It is not necessarily the final cost: unused precharge becomes refund credit after settlement.

Choose a ceiling high enough for the operation's estimated gas, but keep it bounded to the amount your application is prepared to reimburse. The SDK rejects zero and negative values.

Idempotency ​

Use one stable idempotency key for all network retries of the same logical UserOperation. Use a new key after changing the sender, nonce, account call, or reimbursement ceiling. Keys must contain 8–128 letters, numbers, dots, underscores, colons, or hyphens.

Sponsored mode accepts the same optional paymasterContext.idempotencyKey. If it is omitted, the service derives a stable identity from the UserOperation fields. Reuse an explicit key for retries of one logical operation and change it when the operation changes.

ERC-4337 paymaster clients for Ink Mainnet