XRPL Exact Scheme
Detailed specification for the XRPL presigned payment scheme used by x402.
Overview
The XRPL Exact scheme uses presigned Payment transactions to enable trustless, atomic payments between clients and resource servers.
Network Identifiers (CAIP-2)
x402 uses CAIP-2 network identifiers. For XRPL, the format is xrpl:{network_id}
Mainnet
xrpl:0Testnet
xrpl:1Devnet
xrpl:2x402 v2 HTTP Headers
| Direction | Header | Description |
|---|---|---|
| Server → Client | PAYMENT-REQUIRED | Payment challenge (base64 JSON) |
| Client → Server | PAYMENT-SIGNATURE | Signed payment payload (base64 JSON) |
| Server → Client | PAYMENT-RESPONSE | Settlement result (base64 JSON) |
Payment Payload Format
The PAYMENT-SIGNATURE header contains a base64-encoded JSON payload.
Example: XRP Payment
{
"x402Version": 2,
"accepted": {
"scheme": "exact",
"network": "xrpl:1",
"asset": "XRP",
"payTo": "rDestinationAddress...",
"amount": "1000000",
"maxTimeoutSeconds": 600,
"extra": {
"sourceTag": 804681468,
"invoiceId": "INV-abc123"
}
},
"payload": {
"signedTxBlob": "1200002280000000..."
}
}Example: IOU Payment (e.g., RLUSD)
{
"x402Version": 2,
"accepted": {
"scheme": "exact",
"network": "xrpl:1",
"asset": "524C555344000000000000000000000000000000",
"payTo": "rDestinationAddress...",
"amount": "0.01",
"maxTimeoutSeconds": 600,
"extra": {
"sourceTag": 804681468,
"issuer": "rIssuerAddress...",
"invoiceId": "INV-abc123"
}
},
"payload": {
"signedTxBlob": "1200002280000000..."
}
}For raw paymentRequirements.asset, use the canonical XRPL currency code and include the issuer in extra.issuer. RLUSD uses 524C555344000000000000000000000000000000 as its canonical 40-hex currency code.
A symbol such as RLUSD is accepted only when your code explicitly converts it before building payment requirements:
from x402_xrpl.xrpl_currency import resolve_currency_code
asset = resolve_currency_code("RLUSD", allow_utf8_symbol=True)import { resolveCurrencyCode } from "x402-xrpl";
const asset = resolveCurrencyCode("RLUSD", { allowUtf8Symbol: true });Asset Types
| Asset | Amount Format | Example | Extra Fields |
|---|---|---|---|
"XRP" | Drops (string integer) | "1000000" = 1 XRP | None |
| Canonical currency code | Decimal string | 524C555344000000000000000000000000000000 for RLUSD | issuer (required) |
Note: 1 XRP = 1,000,000 drops. When pricing in XRP, always specify the amount in drops as a string.
Payment Requirements Fields
The PAYMENT-REQUIRED header advertises these fields in the accepts array:
| Field | Required | Description |
|---|---|---|
scheme | Yes | Always "exact" |
network | Yes | CAIP-2 identifier (e.g., "xrpl:1") |
asset | Yes | "XRP" or canonical XRPL currency code |
payTo | Yes | XRPL classic address to receive payment |
amount | Yes | Amount (drops for XRP, decimal for IOU) |
maxTimeoutSeconds | Yes | Max validity window for payment |
extra.invoiceId | Yes | Unique invoice identifier |
extra.sourceTag | Yes | XRPL SourceTag (default 804681468) |
extra.issuer | IOU only | Issuer address for IOUs |
extra.destinationTag | Optional | Destination tag if required |
Invoice Binding (Security)
To prevent replay attacks, the signed transaction MUST commit to the invoice. The facilitator accepts two binding methods:
Method A: Memos
Include a memo where MemoData equals the invoice ID (hex-encoded UTF-8).
MemoData = HEX(UTF-8(invoiceId))Method B: InvoiceID
Set the transaction's InvoiceID field to SHA-256 of the invoice ID.
InvoiceID = SHA256(invoiceId)Important: Without invoice binding, a single valid payment could be replayed against multiple invoices. The facilitator rejects transactions missing valid binding.
Verification Process
The facilitator performs these checks before accepting a payment:
Envelope checks
x402Version = 2, scheme = "exact", network supported
Decode transaction
Decode signedTxBlob from hex, parse XRPL binary format
Transaction type
Must be a Payment transaction
Destination
tx.Destination must match payTo address
Network binding
NetworkID field must match CAIP-2 network rules
Amount matching
DeliverMax/Amount must match required amount
Expiry check
LastLedgerSequence must be present; SDK helpers set bounded expiry
Invoice binding
Memos or InvoiceID must bind to invoiceId
Policy checks
Fee limits, no partial payments, no cross-currency
Settlement Response
On successful settlement, the PAYMENT-RESPONSE header contains:
{
"success": true,
"transaction": "ABC123...txhash",
"network": "xrpl:1",
"payer": "rPayerAddress..."
}Common Error Codes
| Code | Note |
|---|---|
invalid_network | The network is mismatched, unsupported, or not a valid xrpl:<network_id> value. |
payment_requirements_mismatch | The accepted payment terms do not exactly match the server's required terms. |
invalid_tx_blob | The signedTxBlob cannot be decoded as a valid XRPL transaction. |
not_payment_tx | The decoded transaction is not a Payment transaction. |
destination_mismatch | The transaction destination does not match the required payTo address. |
source_tag_mismatch | SourceTag is missing, invalid, or different from the required SourceTag. |
amount_mismatch | The transaction amount field/value is missing or does not match the required amount. |
invoice_binding_missing | No invoice binding is present in MemoData or InvoiceID. |
invoice_binding_mismatch | The invoice binding exists, but it points to a different invoice. |
missing_last_ledger_sequence | LastLedgerSequence is missing, so the payment has no bounded expiry. |
Security Considerations
- • Invoice replay: Always enforce invoice binding to prevent cross-resource reuse
- • Network replay: Bind to intended network via CAIP-2 and NetworkID rules
- • Expiry: Require LastLedgerSequence; SDK helpers set bounded expiry
- • Idempotency: Consume invoices once settlement succeeds