Stellar
Introduction
Section titled “Introduction”Stellar is payment-native: ~5s single-step finality, sub-cent fees, and a built-in transaction
memo the proof binds to. Name chain: 'stellar' and the driver auto-mounts on first use —
the family’s library loads lazily, so a pure-EVM install never downloads it.
npm install @stellar/stellar-sdkThis is an optional peer dependency. Install it only if you actually touch Stellar; the import is dynamic, fired the first time you name the chain.
Wallet
Section titled “Wallet”A Stellar wallet is an { secret } (an S… Ed25519 secret seed) or a ready { keypair } (a
stellar-sdk Keypair you built yourself). payTo is a G… account address.
import { PipRailClient } from '@piprail/sdk'
const client = new PipRailClient({ chain: 'stellar', wallet: { secret: process.env.STELLAR_SECRET }, // 'S…' seed})Pass anything that looks like another family’s wallet — privateKey, secretKey, signer,
mnemonic — and it’s rejected up front with a WrongFamilyError.
See Wallets by family for the full set of shapes.
Tokens
Section titled “Tokens”USDC and EURC are built in — both Circle issuers verified live on Horizon mainnet before
shipping — and native XLM works with token: 'native'.
token | Asset |
|---|---|
'USDC' | Circle USDC (issuer pre-filled) |
'EURC' | Circle EURC (issuer pre-filled) |
'native' | XLM (Lumens) |
Stellar assets are universally 7 decimal places — there’s no per-asset decimals like an
ERC-20; USDC, EURC, and XLM are all 7dp. Any other classic asset works by passing it
explicitly as { issuer, code, decimals }:
import { requirePayment } from '@piprail/sdk'
requirePayment({ chain: 'stellar', token: { issuer: 'G…', code: 'XYZ', decimals: 7 }, amount: '0.10', payTo: 'G…',})Internally an asset is identified across the wire as CODE:ISSUER (or 'native' for XLM) — a
contract-address-free scheme native to Stellar.
Receiving — the trustline prerequisite
Section titled “Receiving — the trustline prerequisite”Each trustline locks an additional +0.5 XLM of reserve, and the reserves are locked, not spent — recoverable if you later remove the trustline. The driver sends a payment; it does not create accounts, so both ends must already be funded above reserve. The payer likewise needs its own trustline to hold and send the asset.
When payTo isn’t ready, planPayment() surfaces this as a
RECIPIENT_NOT_READY blocker before you spend — rather than letting you pay into a void — and a
payment to an unready recipient raises a
RecipientNotReadyError:
import { PipRailClient } from '@piprail/sdk'
const client = new PipRailClient({ chain: 'stellar', wallet: { secret: process.env.STELLAR_SECRET }, // 'S…' seed})
const url = 'https://api.example.com/report'const plan = await client.planPayment(url)
if (!plan) { // not payment-gated — fetch it for free await client.fetch(url)} else if (plan.payable) { await client.fetch(url) // safe — we checked} else { console.log(plan.fundingHint) // "recipient has no trustline for the asset" etc.}// → PaymentPlan | nullSee planPayment() for the full readiness model.
Proof binding
Section titled “Proof binding”Stellar uses Template A (memo-bound): the challenge nonce is written on-chain as a
MEMO_HASH = sha256(nonce), so a proof is cryptographically bound to its challenge and can’t
be replayed against a different one. Verification reads the payment to payTo on Horizon and
matches the memo hash, the amount, and the asset’s CODE:ISSUER — every field re-derived from
the trusted accept, never the client-supplied ref.
import { requirePayment } from '@piprail/sdk'
requirePayment({ chain: 'stellar', token: 'USDC', amount: '0.10', payTo: 'G…' })See Proof binding for how Template A compares to the digest-bound template, and Verifying payments for the verify path.
Endpoint
Section titled “Endpoint”The default Horizon endpoint (https://horizon.stellar.org) is public and rate-limited — pass
your own rpcUrl in production:
requirePayment({ chain: 'stellar', token: 'USDC', amount: '0.10', payTo: 'G…', rpcUrl: 'https://…' })