Skip to content

XRP Ledger

The XRP Ledger (XRPL) is a non-EVM family that’s payment-native: ~3–5s single-step finality, no reorgs, with a built-in Memo and DestinationTag the protocol uses for proof binding. Name it — chain: 'xrpl' — and the driver auto-mounts on first use, so a pure-EVM or Solana install never downloads the XRPL library. The protocol layer is unchanged; only the wallet shape and the receive prerequisite differ.

import { requirePayment } from '@piprail/sdk'
requirePayment({ chain: 'xrpl', token: 'USDC', amount: '0.10', payTo: 'r…' })

The XRPL library is an optional peer dep — install it once and the lazy import finds it:

Terminal window
npm install xrpl

An XRPL wallet is { seed } (an s… family secret seed) or a ready { wallet } (an xrpl.js Wallet, if you built it yourself). payTo is a classic r… address.

import { PipRailClient } from '@piprail/sdk'
const client = new PipRailClient({
chain: 'xrpl',
wallet: { seed: process.env.XRPL_SEED }, // 's…' secret seed
})

The shape is checked synchronously at bind time, so passing an EVM, Solana, TON, or Stellar wallet fails fast with a WrongFamilyError. See Wallets by family for the full set of shapes, and PipRailClient for the client surface.

Name the symbol; the SDK fills in the issuer and currency code. Both built-in issuers were verified live on mainnet before shipping (the on-ledger Domain and currency code read off gateway_balances).

TokenBuilt inNotes
'USDC'YesCircle USDC. Issuer Domain https://circle.com verified on mainnet.
'RLUSD'YesRipple’s RLUSD. Issuer Domain https://ripple.com/ verified on mainnet.
'native'YesXRP, 6 decimals (drops — 1 drop = 1e-6 XRP).
custom IOUAny other issued currency via { issuer, currencyHex, decimals }.
import { requirePayment } from '@piprail/sdk'
// A custom issued currency is { issuer, currencyHex, decimals }:
requirePayment({
chain: 'xrpl',
token: { issuer: 'r…', currencyHex: '5553444300000000000000000000000000000000', decimals: 6 },
amount: '0.10',
payTo: 'r…',
})

Receive prerequisite — activation + a trustline

Section titled “Receive prerequisite — activation + a trustline”

To receive an issued asset (USDC/RLUSD), the merchant (payTo) must (1) be an activated account — holding the ~1 XRP base reserve to exist — and (2) hold a trustline (TrustSet) to that issuer’s currency before its first IOU payment. Native XRP needs neither. The payer must be activated and trustlined too.

When the recipient isn’t ready, PipRail raises RecipientNotReadyError — a chain requirement, not an SDK bug — echoing the raw XRPL engine code and the fix:

Raw XRPL codeWhat it meansFix
tecNO_DST_INSUF_XRP / tecNO_DSTpayTo isn’t activated (needs ≥1 XRP base reserve to exist)send the recipient ≥1 XRP to activate it
tecNO_LINE / tecPATH_DRYrecipient has no trustline for the IOUadd the trustline on the recipient
tecDST_TAG_NEEDEDrecipient requires a DestinationTag (PipRail sets one automatically)

Reserves are locked, not spent (~1 XRP base + an owner reserve per trustline) — recoverable.

Check readiness before you spend with planPayment(): it surfaces a not-ready recipient as a RECIPIENT_NOT_READY blocker rather than throwing, so an agent branches on data instead of broadcasting into a stranded payment:

const url = 'https://api.example.com/report'
const plan = await client.planPayment(url)
// → PaymentPlan | null (null when the URL isn't 402-gated)
if (!plan) {
await client.fetch(url) // not payment-gated — just fetch it
} else if (plan.payable) {
await client.fetch(url) // safe — we checked
} else {
console.log(plan.fundingHint) // e.g. "recipient has no trustline for USDC"
}

See planPayment() for the full readiness model and the RECIPIENT_NOT_READY blocker.

XRPL uses Template A: the challenge nonce rides in a Memo — the cryptographic binding — plus a derived DestinationTag for deliverability (some accounts, like the RLUSD issuer, set requireDestinationTag). verify() re-derives every checked field from the trusted accept, never the client-supplied ref, so a forged echo can’t redirect it.

The default RPC is a public community cluster (https://xrplcluster.com/) — rate-limited and not for sustained business use. Pass your own rpcUrl in production:

import { requirePayment, PipRailClient } from '@piprail/sdk'
requirePayment({ chain: 'xrpl', token: 'USDC', amount: '0.10', payTo: 'r…', rpcUrl: 'https://your-xrpl-node/' })
const client = new PipRailClient({
chain: 'xrpl',
wallet: { seed: process.env.XRPL_SEED },
rpcUrl: 'https://your-xrpl-node/',
})