@xrpljapan/x402-xrpl
v0.1.0
Published
x402 Payment Protocol XRPL (XRP Ledger) implementation
Maintainers
Readme
@xrpljapan/x402-xrpl
Community XRPL mechanism for the x402 payment protocol.
⚠️ Public Beta. This package is
0.1.xand under active development. APIs, wire format details, and defaults may change without notice between releases. It has not been independently audited. Use at your own risk and pin an exact version in production. See the Disclaimer below.
Implements the exact payment scheme on the XRP Ledger (XRPL) for x402 v2. Its center of gravity is the resource server — the side that prices and accepts payments. ExactXrplServerScheme parses dollar and asset prices, enriches payment requirements (invoiceId, sourceTag, issuer), and resolves USD to a configured IOU, so an existing HTTP API can charge for XRP and IOU (RLUSD, USDC, etc.) payments with a few lines. The matching client and self-hostable facilitator roles complete the loop. Follows the t54 XRPL facilitator's wire format (XRP interop verified end-to-end on testnet).
Why the resource server is the main value. In an x402 deployment the client just signs a transaction and the facilitator just verifies and submits it — both are largely mechanical and interchangeable (you can even point at the hosted t54 facilitator and never run one yourself). The resource server is where the integration work and the business logic live: it decides what to charge, in which asset, binds each payment to an invoice for replay protection, and turns a plain HTTP endpoint into a paid one. That is the role this package is built to make easy.
This package is maintained by XRPL Japan. It is not yet part of the official x402 TypeScript monorepo (
@x402/evm,@x402/svm, …). It follows the sameregister(network, scheme)integration model as those packages.
Features
- Server-first integration —
ExactXrplServerSchemeturns an HTTP endpoint into a paid one: parses"$0.10"and explicit asset prices, enriches requirements (invoiceId,sourceTag,issuer), and resolves USD to your configured IOU - Invoice binding — replay protection via
InvoiceID(SHA-256) orMemoData, enforced end-to-end - Assets — native XRP (drops) and IOU tokens (RLUSD, USDC, custom issuers)
- Three x402 roles —
SchemeNetworkServer,SchemeNetworkClient,SchemeNetworkFacilitator - No-custody facilitator — verifies and submits presigned
Paymenttransactions; never holds payer keys - t54 wire format — payload shape follows the t54 XRPL facilitator conventions (XRP interop verified E2E; IOU not yet supported by t54)
- Dual build — ESM + CJS with subpath exports
- Test coverage — unit tests + optional XRPL testnet integration tests
Requirements
- Node.js ≥ 22
- Peer dependency:
@x402/core≥ 2 - Runtime dependency:
xrpl^5
Installation
npm install @x402/core @xrpljapan/x402-xrpl xrpl
# or
pnpm add @x402/core @xrpljapan/x402-xrpl xrplQuick start
Resource server
The primary integration point. Parse prices and enrich payment requirements (invoiceId, sourceTag, issuer):
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
import { ExactXrplServerScheme } from "@xrpljapan/x402-xrpl";
const facilitator = new HTTPFacilitatorClient({
url: process.env.XRPL_FACILITATOR_URL!, // e.g. t54 or your own
});
const server = new x402ResourceServer(facilitator)
.register("xrpl:*", new ExactXrplServerScheme({
defaultUsdIou: {
asset: "524C555344000000000000000000000000000000", // RLUSD
issuer: "rIssuer...",
},
}));
await server.initialize();Dollar-string prices (e.g. "$0.10") resolve 1:1 to the configured USD IOU. Explicit { asset: "XRP", amount: "1000000" } amounts pass through unchanged. XRP-denominated dollar pricing is not supported — use a USD-pegged IOU instead.
The snippet above uses the core @x402/core/server API with a single facilitator. For an Express-integrated server that fans out across multiple facilitators (one per network) and mixes XRPL with EVM rails, see examples/multichain, which wires x402ResourceServer through @x402/express and an array of HTTPFacilitatorClients.
Client
Build and sign an XRPL Payment transaction from x402 payment requirements:
import { x402Client } from "@x402/core/client";
import { ExactXrplClientScheme, XrplWalletSigner } from "@xrpljapan/x402-xrpl";
const signer = new XrplWalletSigner(process.env.XRPL_SEED!);
const client = new x402Client();
client.register("xrpl:1", new ExactXrplClientScheme(signer));
// or register all XRPL networks:
// client.register("xrpl:*", new ExactXrplClientScheme(signer));Self-hosted facilitator
Run your own facilitator for redundancy, privacy, or fallback:
import { x402Facilitator } from "@x402/core/facilitator";
import { ExactXrplFacilitator } from "@xrpljapan/x402-xrpl";
const facilitator = new x402Facilitator();
facilitator.register(["xrpl:0", "xrpl:1"], new ExactXrplFacilitator());Override the rippled WebSocket endpoint if needed:
new ExactXrplFacilitator({ rpcConfig: { rpcUrl: "wss://your-node.example.com" } });ExactXrplFacilitator keeps one pooled WebSocket connection per rippled URL and
reconnects automatically if it drops. Long-running servers reuse the connection
across requests. Call await close() on the ExactXrplFacilitator instance on shutdown — an open
WebSocket otherwise keeps the Node.js process alive, which matters for
short-lived scripts. Unexpected RPC/transport failures propagate as exceptions
(they are not payment rejections); catch them at your HTTP boundary and decide
between a protocol-shaped error response, a 503 + retry, or RPC failover.
If you supply a custom connect implementation (instead of the default xrpl.js-backed one), note that XrplAccountView.getTransferRate is a required method and XrplAccountState.sequence is a required field — custom implementations must supply them.
Examples
examples/multichain— one resource server, three payment rails: XRP and RLUSD on XRPL testnet (self-hosted facilitator from this package) plus USDC on Base Sepolia (official@x402/evm+x402.orgfacilitator). Includes an unattended XRP-rail smoke test.
Networks
| CAIP-2 ID | Network | Default rippled URL |
|-----------|---------|---------------------|
| xrpl:0 | XRPL mainnet | wss://xrplcluster.com |
| xrpl:1 | XRPL testnet | wss://s.altnet.rippletest.net:51233 |
| xrpl:* | CAIP family wildcard | — |
Assets
| Type | asset | amount | extra |
|------|---------|----------|---------|
| XRP | "XRP" | drops as integer string (1 XRP = 1,000,000 drops) | invoiceId, sourceTag |
| IOU | 40-hex currency code | decimal string (e.g. "0.01") | issuer required |
RLUSD currency code: 524C555344000000000000000000000000000000
For IOUs whose issuer sets a TransferRate (transfer fee), the client automatically adds SendMax = amount × rate to the signed transaction; without it rippled rejects the payment with tecPATH_PARTIAL.
Facilitator options
Facilitator URLs are not hardcoded in this library. Configure them in your application.
Hosted (t54)
| Environment | Example URL | Network |
|-------------|-------------|---------|
| Mainnet | https://xrpl-facilitator-mainnet.t54.ai | xrpl:0 |
| Testnet | https://xrpl-facilitator-testnet.t54.ai | xrpl:1 |
Pass the URL to HTTPFacilitatorClient on the resource server. Any facilitator implementing the standard x402 HTTP contract (/verify, /settle, /supported) works.
Self-hosted
Use ExactXrplFacilitator in-process (see Quick start). No HTTP URL required — only rippled connectivity.
Multi-network coexistence
x402 is designed for multiple chains side by side. Register XRPL alongside EVM, Solana, or other mechanisms:
import { x402Client } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { ExactXrplClientScheme, XrplWalletSigner } from "@xrpljapan/x402-xrpl";
const client = new x402Client()
.register("eip155:*", new ExactEvmScheme(evmSigner))
.register("xrpl:*", new ExactXrplClientScheme(new XrplWalletSigner(xrplSeed)));Resource servers can expose multiple accepts options (one per network) and route to different facilitator URLs.
Wire format
The x402 payload for XRPL exact:
{
"signedTxBlob": "1200002280000000...",
"invoiceId": "INV-abc123"
}signedTxBlob— hex-encoded, presigned XRPLPaymenttransactioninvoiceId— required; echoed for t54 wire compatibility
The cryptographic binding is inside the signed transaction (InvoiceID = SHA-256 of invoiceId, or MemoData). That on-chain binding is the source of truth for verification.
The facilitator verifies the single signature cryptographically and accepts it when the signing key is the account's master key (unless disabled via lsfDisableMaster) or its on-ledger RegularKey. Multi-signed transactions are rejected.
Note: For failures detected before signature verification (
invalid_payload,amount_mismatch,destination_tag_mismatch,invoice_binding_missing, etc.), thepayerfield — when present — is read from the unverified blob'sAccountfield and is unauthenticated: anyone can craft a blob naming any account. Do not use it for attribution, rate limiting, or blocklists. Once verification reaches the signature check,payeris cryptographically authenticated.
Additional checks enforced by verify:
LastLedgerSequenceis required (reasonmissing_last_ledger_sequence) — reliable submission needs a deadline.- When
extra.destinationTagis set, the signed tx must carry the sameDestinationTag(reasondestination_tag_mismatch). - The XRP balance check accounts for the ledger reserve (
reserve_base + reserve_inc × OwnerCount) and the tx fee, not just the raw balance. - Non-existent payer accounts return
account_not_foundinstead of an error. - The payload's
invoiceIdecho must equalextra.invoiceId. - A tx
Sequencealready consumed on-ledger is rejected up front (reasonsequence_too_old) instead of surfacing assubmit_error:tefPAST_SEQat settle. Ticketed payments (Sequence: 0+TicketSequence) are exempt. - For IOUs whose issuer sets a
TransferRate, the signed tx must carry aSendMaxin the same asset coveringamount × rate(reasonsmissing_send_max/insufficient_send_max), and the trustline balance is checked against the fee-inclusive debit. This catches third-party clients that skipSendMaxbefore they fail at settle withtecPATH_PARTIAL.
Settlement finality
settle waits for a validated ledger. A not_validated result means the transaction was submitted but not confirmed in a validated ledger before timeout. It is reported as success: false but includes the transaction hash — the payment may still settle on-chain later. Reconcile by transaction hash rather than treating not_validated as "no funds moved".
If submission itself throws (e.g. the transaction expired before validation, or a tem/tef rejection), settle reports success: false with errorReason: "submit_error:<message>" rather than raising.
Package exports
import { ... } from "@xrpljapan/x402-xrpl"; // all public APIs
import { ... } from "@xrpljapan/x402-xrpl/exact/client";
import { ... } from "@xrpljapan/x402-xrpl/exact/server";
import { ... } from "@xrpljapan/x402-xrpl/exact/facilitator";Public API
| Export | Role |
|--------|------|
| ExactXrplServerScheme | Server — parse prices, enrich requirements |
| ExactXrplClientScheme | Client — build and sign payment payloads |
| ExactXrplFacilitator | Facilitator — verify and settle on-chain |
| XrplWalletSigner | Default XrplSigner (family seed or Wallet) |
| XrplSigner | Signer interface for custom wallet integrations |
| encodeCurrencyCode, invoiceIdHash, … | Shared helpers |
Development
pnpm install
pnpm build # ESM + CJS to dist/
pnpm test # unit tests
pnpm test:coverage # unit tests + V8 coverage (thresholds enforced)
pnpm lint
pnpm format
pnpm build && pnpm check:package # publint + arethetypeswrong + pack/import smoke
# XRPL testnet e2e (requires network + faucet)
RUN_XRPL_INTEGRATION=1 pnpm test:integration
# Local deterministic e2e (no faucet, no public testnet):
docker compose up -d
RUN_XRPL_STANDALONE=1 pnpm test:integrationt54 facilitator smoke test
node scripts/t54-facilitator-check.mjs
FACILITATOR_URL=https://xrpl-facilitator-testnet.t54.ai NETWORK=xrpl:1 node scripts/t54-facilitator-check.mjsCI
GitHub Actions run lint, typecheck, unit tests with coverage thresholds, build, and package checks on every push/PR (.github/workflows/ci.yml). Testnet e2e runs weekly or on manual dispatch (.github/workflows/integration.yml) since the public testnet/faucet are rate-limited. Workflows activate once the repo is pushed to GitHub.
Interoperability status
| Scenario | Status |
|----------|--------|
| Self-hosted facilitator — XRP on testnet | Verified E2E |
| Self-hosted facilitator — IOU on testnet | Verified E2E |
| t54 testnet facilitator — XRP (/verify, /settle) | Verified E2E 2026-07-04 with a faucet-funded wallet: /verify returned isValid:true, /settle returned success:true — network-gated test in test/integration/xrp-e2e.test.ts |
| t54 testnet facilitator — IOU (/verify, /settle) | Rejected 2026-07-04: /verify returns HTTP 200 with {"isValid":false,"invalidReason":"unsupported_payment_features"} for a well-formed IOU payload (XRP against the same facilitator passes) — t54 testnet does not yet support IOU assets; test skipped in test/integration/iou-e2e.test.ts pending t54 support |
Documentation
- x402 protocol — official specification and
@x402/coreSDK - t54 XRPL facilitator — hosted facilitator service
Relationship to official x402
This package follows the official x402 protocol and @x402/core interfaces.
Contributing
Contributions are welcome. Please open an issue or pull request on this repository.
- Fork the repository
- Create a feature branch
- Run
pnpm testandpnpm lint - Submit a pull request with a clear description
For larger changes, open an issue first to discuss the approach.
Public beta
@xrpljapan/x402-xrpl is currently in public beta (0.1.x). This means:
- APIs may change. Public interfaces, option shapes, and defaults can change
between minor releases without a deprecation cycle. Pin an exact version
(
@xrpljapan/[email protected]) if you depend on current behavior. - Wire format may evolve. The XRPL
exactpayload tracks the t54 de-facto format and any future upstream x402 spec; it is not frozen. - Not independently audited. The cryptographic and settlement logic has not undergone a third-party security audit.
- Interop is partial. See Interoperability status for what is verified end-to-end today (XRP is; t54 IOU is not yet supported upstream).
Bug reports and feedback are welcome — please open an issue.
Disclaimer
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT, as set out in the Apache License 2.0.
This package handles the construction, signing, and submission of on-chain payment transactions. You are solely responsible for reviewing and testing it before handling real value. The maintainers (XRPL Japan) and contributors accept no liability for any loss of funds, failed or duplicated settlements, or other damages arising from its use. Nothing here is financial, legal, or tax advice. Test thoroughly on XRPL testnet before touching mainnet funds, and pin an exact version in production.
