Skip to content

Defining accepts

RequirePaymentOptions is the one object you pass to requirePayment or createPaymentGate. It declares what you accept: which chain, which token, how much, and where the money lands. There are two forms — a single-rail shorthand and a multi-rail accept[] — and you pass exactly one of them.

The common case: name the chain, token, amount, and payTo at the top level. One rail, one line.

import { requirePayment } from '@piprail/sdk'
requirePayment({ chain: 'base', token: 'USDC', amount: '0.10', payTo: '0xYourWallet' })

token is required — every gate states exactly what it takes, so there’s never any doubt whether a route wants USDC, USDT, or the native coin. Name a built-in symbol ('USDC', 'USDT'), use 'native' for the chain’s own coin, or pass a custom token by address. The symbol is all you write; the SDK fills in the contract address and decimals.

requirePayment({ chain: 'bnb', token: 'USDT', amount: '1', payTo: '0xYourWallet' })
requirePayment({ chain: 'base', token: 'native', amount: '0.001', payTo: '0xYourWallet' }) // ETH
requirePayment({ chain: 'solana', token: 'USDC', amount: '0.10', payTo: 'YourSolanaAddr' })

amount is human units, given as a string — '0.10' is ten cents of a 6-decimal USDC, not ten base units. The SDK scales it to base units against the token’s decimals, so you never write 100000. See Chains and tokens for the full token grammar and custom-token descriptors per family.

To offer several rails in one challenge, pass accept[]. The agent pays with whatever it holds, on whichever of the offered chains it can settle.

import { requirePayment } from '@piprail/sdk'
const BASE_RPC = process.env.BASE_RPC // your own RPC per chain (public ones are rate-limited)
const TRON_RPC = process.env.TRON_RPC
const SOL_RPC = process.env.SOL_RPC
requirePayment({
payTo: '0xYourWallet',
accept: [
{ chain: 'base', token: 'USDC', amount: '0.10', payTo: '0xYourWallet', rpcUrl: BASE_RPC },
{ chain: 'tron', token: 'USDT', amount: '0.10', payTo: 'T…', rpcUrl: TRON_RPC },
{ chain: 'solana', token: 'USDC', amount: '0.10', payTo: 'YourSolanaAddr', rpcUrl: SOL_RPC },
],
})

The two forms are mutually exclusive — pass accept[] or top-level chain/token/amount, never both. Passing both, or neither in full, throws a clear Error on the first request (the gate resolves lazily, not at construction time) — under requirePayment that error is forwarded to next(err).

Each AcceptOption carries its own chain, token, and amount, and may override payTo and rpcUrl for its chain. When omitted, both fall back to the top-level value.

FieldPer-rail behaviour
chain / token / amountRequired on every entry; describe that one rail.
payToFalls back to the top-level payTo when omitted.
rpcUrlFalls back to the top-level rpcUrl when omitted.

Each rail also resolves through its own driver with its own rpcUrl, so one throttled public RPC can’t take down verification for the others. In production, set rpcUrl on every rail — public endpoints are rate-limited. The rpcUrl is used server-side only; it is never leaked into the challenge.

A PipRailClient is bound to one chain — its own chain plus wallet. When it receives a multi-rail challenge it picks the offered rail whose network it supports and whose spend policy allows, pays that one, and ignores the rest. With autoRoute on it pays the cheapest settleable rail; planPayment() shows you the ranking first. To compare cost across chains yourself, point one client per chain at the same URL and compare their estimateCost() results.

On the gate side, verification selects the matching rail by network + asset and re-derives every checked field from its own trusted spec — a forged echo of the offered terms can’t redirect it. See Verifying payments.

description is shown to the agent inside the challenge — a short human label for what the payment buys.

requirePayment({
chain: 'base', token: 'USDC', amount: '0.10', payTo: '0xYourWallet',
description: 'One market-data report',
})
OptionDefaultPurpose
maxTimeoutSeconds600Max age of an accepted payment, in seconds — how long a challenge stays valid.
minConfirmations1Confirmations required on-chain before access is granted.
requirePayment({
chain: 'base', token: 'USDC', amount: '0.10', payTo: '0xYourWallet',
maxTimeoutSeconds: 120, // tighten the recency window
minConfirmations: 2, // wait for two confirmations
})

A tighter maxTimeoutSeconds shrinks the replay window; pair it with a persistent replay store for multi-instance deploys.

The remaining RequirePaymentOptions fields are covered on their own pages: onPaid on Receipts and onPaid, isUsed/markUsed on Replay protection, exact on the exact rail, and discovery on Discover and register. The full type lives in the API reference.