Chains & tokens
Introduction
Section titled “Introduction”PipRail charges and pays the same way everywhere — requirePayment({ chain, token, amount, payTo })
to charge, new PipRailClient({ chain, wallet }).fetch(url) to pay. A single chain value
selects the chain, its tokens, and the settlement family behind it. There is no allowlist:
the popular chains ship as presets for convenience, and any other EVM chain works by passing
its details directly.
What’s covered
Section titled “What’s covered”Every major EVM chain plus a range of non-EVM families, each reached through the same chain
parameter:
| Family | chain values |
|---|---|
| EVM | ethereum, base, arbitrum, optimism, polygon, bnb, avalanche, mantle, sonic, linea, scroll, celo, zksync, unichain, worldchain, sei, injective, hyperevm, monad, kaia — plus any other EVM chain by viem Chain or { id, rpcUrl } |
| Solana | solana |
| TON | ton |
| Tron | tron |
| NEAR | near |
| Sui | sui |
| Aptos | aptos |
| Algorand | algorand |
| Stellar | stellar |
| XRP Ledger | xrpl |
The authoritative, always-current list and the per-chain receive prerequisites live in Chains overview. Mainnets only — there are no testnet presets.
// On the server you charge; pass your own receiving address as payTo.requirePayment({ chain: 'base', token: 'USDC', amount: '0.10', payTo: '0xYourWallet' }) // EVM presetrequirePayment({ chain: 'solana', token: 'USDC', amount: '0.10', payTo: 'YourSolanaAddr' })The token rule
Section titled “The token rule”You name a token by symbol and PipRail resolves the canonical contract for that chain. The coverage follows a simple rule:
| Token | Where it’s built in |
|---|---|
| USDC | Almost everywhere Circle issues it natively. |
| USDT | Most chains — omitted where the chain’s “USDT” is a bridged LayerZero/USDT0 token rather than Tether-native. |
| EURC | Where Circle issues it — on EVM that’s Ethereum, Base, and Avalanche; also Stellar. |
| RLUSD | XRP Ledger. |
| native coin | Valid on every family via token: 'native'. |
The native coin (ETH, SOL, XRP, …) is the chain’s gas token and is always a valid payment asset. Anything else works by address — there’s no allowlist for tokens either:
requirePayment({ chain: 'base', token: 'native', amount: '0.10', payTo: '0xYourWallet' })requirePayment({ chain: 'arbitrum', token: { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', decimals: 6 }, amount: '0.10', payTo: '0xYourWallet',})The custom-token shape is per-family — an EVM { address, decimals }, a Solana
{ mint, decimals }, a TON { master, decimals }, and so on. The full set of shapes is on
Chains overview.
Per-chain setup & caveats — at a glance
Section titled “Per-chain setup & caveats — at a glance”Most chains are zero-setup: install, name the chain, add a wallet. A few have one-time caveats worth knowing before you ship — the peer library to install, what the recipient must do to receive a token, and which native coin pays gas. Each family page has the full detail.
| Chain | Install (lazy peer) | To receive, the recipient needs… | Gas / notes |
|---|---|---|---|
| EVM (all presets) | viem (core dep) | nothing | native coin (ETH/BNB/POL/AVAX/…) |
| Solana | @solana/web3.js @solana/spl-token bs58 | nothing (the SDK auto-creates the token account) | SOL |
| TON | @ton/ton @ton/core @ton/crypto | nothing (jetton wallet auto-deploys on first receipt) | TON · needs a free toncenter RPC key (@tonapibot on Telegram, in rpcUrl); ships USD₮, no native USDC |
| Tron | tronweb | nothing | real TRX for energy/bandwidth; ships USD₮ (native TRX also works) |
| NEAR | near-api-js | a NEP-141 token (USDC/USDT): one-time storage_deposit (~0.00125 NEAR). Native NEAR: nothing | NEAR · set accountId |
| Sui | @mysten/sui | nothing | SUI · USDC only (no native USDT) |
| Aptos | @aptos-labs/ts-sdk | nothing (primary FA store auto-creates) | APT |
| Algorand | algosdk | USDC: one-time ASA opt-in (a 0-amount self-transfer). Native ALGO: nothing | ALGO · USDC only |
| Stellar | @stellar/stellar-sdk | for USDC/EURC: a trustline + the account funded above its base reserve. Native XLM: nothing | XLM · USDC + EURC |
| XRP Ledger | xrpl | for USDC/RLUSD: a trustline + the account activated (≥1 XRP reserve). Native XRP: nothing | XRP · USDC + RLUSD |
A recipient that isn’t set up surfaces as a typed RecipientNotReadyError
(or a RECIPIENT_NOT_READY blocker from planPayment()) with the
exact fix — never a silent failure. The base-reserve XLM/XRP is locked, not spent.
The built-in EVM registry — CHAINS
Section titled “The built-in EVM registry — CHAINS”CHAINS is the exported registry of built-in EVM presets. Each preset carries the underlying
viem Chain and a tokens map keyed by uppercase symbol, with the canonical address and
decimals pre-filled — so you never paste a token address for a supported chain:
import { CHAINS } from '@piprail/sdk'
CHAINS.base.tokens.USDC// → { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6, symbol: 'USDC' }A ChainPreset is shaped like this:
interface ChainPreset { chain: Chain // the viem chain (id, name, native coin, RPCs) defaultRpc?: string // override only where viem's default is unreliable tokens: Record<string, TokenInfo> // canonical tokens, keyed by UPPERCASE symbol}
interface TokenInfo { address: `0x${string}` decimals: number symbol: string}Iterate CHAINS to build a chain picker, or read a preset directly when you want the canonical
address for a known chain. ChainName is the union of built-in preset keys ('base' | 'ethereum' | …).
Any other EVM chain
Section titled “Any other EVM chain”You’re not limited to the presets. For an EVM chain PipRail doesn’t ship, pass a viem Chain
or a bare { id, rpcUrl }, plus the token you want paid in. If viem can reach the RPC, PipRail
works on it:
requirePayment({ chain: { id: 1313161554, rpcUrl: 'https://mainnet.aurora.dev' }, token: { address: '0xB12BFcA5A55806AaF64E99521918A4bf0fC40802', decimals: 6 }, amount: '0.10', payTo: '0xYourWallet',})The accepted chain types are captured by ChainInput:
| Form | Use when |
|---|---|
ChainName ('base', …) | A built-in EVM preset. |
viem Chain | An EVM chain you already have a viem definition for. |
{ id, rpcUrl, name?, nativeCurrency? } | Any EVM chain by id + RPC. Native coin defaults to 18-decimal ETH; override nativeCurrency if it isn’t. |
When you pass a raw viem Chain or { id, rpcUrl } that happens to match a built-in chain id,
symbol resolution still works — the preset’s tokens are looked up by chain id.
RPC endpoints
Section titled “RPC endpoints”The public default RPC on every chain is rate-limited. Pass your own rpcUrl in
production. There is no separate API-key field — fold any key into the URL:
requirePayment({ chain: 'base', token: 'USDC', amount: '0.10', payTo: '0xYourWallet', rpcUrl: 'https://base-mainnet.example.com/v2/YOUR_KEY',})Each entry in a multi-rail accept[] can carry its own rpcUrl. A few non-EVM families
effectively require a keyed endpoint (TON’s keyless toncenter is too slow for
verify()) — those caveats are on each chain’s page under Chains.