Wallets by family
Introduction
Section titled “Introduction”A PipRailClient takes one wallet and one chain. The chain selector routes to a driver
family, and each family validates its own key format — so the wallet shape you pass depends on
the chain you name. Pass the wrong shape for the chain and you get a clear
WrongFamilyError on first use, before any funds move.
import { PipRailClient } from '@piprail/sdk'
const client = new PipRailClient({ chain: 'base', wallet: { privateKey: process.env.AGENT_KEY } })// → a PipRailClient bound to Base, ready to quote / plan / payThe shape per family
Section titled “The shape per family”Each family accepts a primary secret-key form and (where it makes sense) a ready-built native wallet object. The selector picks the family; the wallet must match it.
| Family | Wallet shape |
|---|---|
EVM ('base', 'bnb', … any EVM chain) | { privateKey } (0x… hex) or a viem { walletClient } |
| Tron | { privateKey } (32-byte hex — secp256k1, like EVM) |
| Sui | { privateKey } (suiprivkey1… bech32) or { keypair } |
| Aptos | { privateKey } (AIP-80 ed25519-priv-0x… or raw 0x… hex) or { account } |
| Solana | { secretKey } (Uint8Array or base58 string) or { signer } |
| TON | { mnemonic } (24 words) or { keyPair } (+ version: 'v5r1' for W5) |
| Algorand | { mnemonic } (25 words) or { account } (algosdk { addr, sk }) |
| Stellar | { secret } (S… seed) or { keypair } |
| XRPL | { seed } (s… seed) or { wallet } |
| NEAR | { accountId, privateKey } (privateKey = ed25519:… secret) |
EVM, Tron, Sui, Aptos — { privateKey }
Section titled “EVM, Tron, Sui, Aptos — { privateKey }”The secp256k1 and Ed25519 families take a raw private key string. EVM and Tron both use secp256k1 (Tron’s key is the same 32-byte hex), while Sui and Aptos take their own encoded forms.
new PipRailClient({ chain: 'base', wallet: { privateKey: process.env.AGENT_KEY } }) // 0x… hexnew PipRailClient({ chain: 'tron', wallet: { privateKey: process.env.TRON_KEY } }) // 32-byte hexnew PipRailClient({ chain: 'sui', wallet: { privateKey: process.env.SUI_KEY } }) // suiprivkey1…new PipRailClient({ chain: 'aptos', wallet: { privateKey: process.env.APTOS_KEY } }) // ed25519-priv-0x…Sui also accepts a ready { keypair } (an Ed25519Keypair); Aptos accepts a ready { account }.
Solana — { secretKey }
Section titled “Solana — { secretKey }”Solana takes a secretKey as either a Uint8Array or a base58 string, or a ready { signer }.
new PipRailClient({ chain: 'solana', wallet: { secretKey: process.env.SOLANA_SECRET } }) // base58TON & Algorand — { mnemonic }
Section titled “TON & Algorand — { mnemonic }”Both take a mnemonic — TON uses 24 words, Algorand uses 25. The mnemonic may be one
space-separated string or a string[].
new PipRailClient({ chain: 'ton', wallet: { mnemonic: process.env.TON_MNEMONIC } }) // 24 wordsnew PipRailClient({ chain: 'algorand', wallet: { mnemonic: process.env.ALGO_MNEMONIC } }) // 25 wordsOn TON, add version: 'v5r1' for a W5 wallet — the default is v4. Algorand also accepts a
ready { account } (an algosdk { addr, sk }).
Stellar & XRPL — { secret } / { seed }
Section titled “Stellar & XRPL — { secret } / { seed }”Stellar takes a secret (an S… seed); XRPL takes a seed (an s… seed). Each also accepts
its native wallet object.
new PipRailClient({ chain: 'stellar', wallet: { secret: process.env.STELLAR_SECRET } }) // S…new PipRailClient({ chain: 'xrpl', wallet: { seed: process.env.XRPL_SEED } }) // s…Stellar accepts a ready { keypair } (a stellar-sdk Keypair); XRPL accepts a ready
{ wallet } (an xrpl.js Wallet).
NEAR — { accountId, privateKey }
Section titled “NEAR — { accountId, privateKey }”NEAR is the one family that needs two fields: the named account it signs as plus its key.
The privateKey is an ed25519:… secret.
new PipRailClient({ chain: 'near', wallet: { accountId: 'agent.near', privateKey: process.env.NEAR_KEY },})Injected browser wallet — { walletClient }
Section titled “Injected browser wallet — { walletClient }”On EVM you can hand the client an injected viem walletClient instead of a raw key — the visitor
signs with their own wallet, and no secret ever touches your page source.
import { createWalletClient, custom } from 'viem'
const walletClient = createWalletClient({ transport: custom(window.ethereum) })const client = new PipRailClient({ chain: 'base', wallet: { walletClient } })Wrong shape → WrongFamilyError
Section titled “Wrong shape → WrongFamilyError”The chain picks the family, and the family validates the wallet on first use. If the shape (or a
payTo, or a token) is given in another family’s form — an 0x… address on Solana, a { mint }
token on a Stellar chain, a { seed } wallet on EVM — the bind throws a typed
WrongFamilyError (.code === 'WRONG_FAMILY') rather than failing
obscurely later.
import { PipRailClient, WrongFamilyError } from '@piprail/sdk'
// chain says EVM, wallet is an XRPL seed → WrongFamilyError on first requestconst client = new PipRailClient({ chain: 'base', wallet: { seed: 's…' } })
try { await client.fetch('https://api.example.com/report')} catch (err) { if (err instanceof WrongFamilyError) { console.error(err.code, err.message) // → 'WRONG_FAMILY' '…an XRPL seed on an EVM chain…' } else { throw err }}The error is raised lazily — the driver auto-mounts and binds the wallet on the first call (see the PaymentDriver architecture), so a misconfigured client constructs fine but throws the moment it tries to act.
Per-chain receive prerequisites, token coverage, and proof binding live on each chain’s page —
start at the chains overview. Once a wallet is bound, the read-only
planPayment() tells you whether it can actually settle a given
402.