Migrate from 0.1.x to 0.2.0¶
Version 0.2.0 is an intentional clean break. Upgrade all five repository packages together and pin the official runtime:
x402==2.21.0
xrpl-x402-core==0.2.0
xrpl-x402-client==0.2.0
xrpl-x402-middleware==0.2.0
xrpl-x402-facilitator==0.2.0
xrpl-x402-payer==0.2.0
Wire changes¶
Before, a requirement could contain a nested amount object and custom XRPL metadata:
{"maxAmountRequired":"1000","asset":{"assetId":"XRP:native"}}
Now it is an official PaymentRequirements object:
{
"scheme":"exact",
"network":"xrpl:1",
"asset":"XRP",
"amount":"1000",
"payTo":"rMerchant",
"maxTimeoutSeconds":60,
"extra":{"areFeesSponsored":false,"assetTransferMethod":"sequence"}
}
The client submits an official PaymentPayload with accepted and a scheme
payload containing only signedTxBlob. Facilitator calls use
x402Version, paymentPayload, and paymentRequirements. The standard
VerifyResponse and SettleResponse replace all custom response bodies.
Legacy bodies and snake-case protocol aliases are rejected. There is no body
fallback for HTTP: use the PAYMENT-REQUIRED, PAYMENT-SIGNATURE, and
PAYMENT-RESPONSE base64 JSON headers.
Registration¶
from x402 import x402Client
from xrpl_x402_client import register_exact_xrpl_client
client = register_exact_xrpl_client(x402Client(), signer)
Resource servers normally use PaymentMiddlewareASGI; paid MCP tools use
create_xrpl_mcp_payment_wrapper. Both register the exact XRPL server mechanism.
Asset and spend configuration¶
- XRP:
asset: "XRP", integer drops amount, no issuer. - IOU: currency code in
asset, account inextra.issuer, decimal amount. - RLUSD uses the current official mainnet/testnet issuer table.
- The former Testnet RLUSD issuer causes a configuration error.
- XRP, USDC, and non-default IOUs require explicit payer allowlists and caps.
For those explicit assets, configure the issuer-aware cap on the upstream client directly:
from xrpl_x402_client import (
XRPLAssetSpendLimit,
apply_xrpl_spend_limits,
)
limits = [
XRPLAssetSpendLimit(network="xrpl:1", asset="XRP", max_amount="1000000")
]
apply_xrpl_spend_limits(client, limits)
Tickets¶
Set extra.assetTransferMethod to ticketSequence in the accepted requirement.
The payer uses Sequence: 0 plus an unconsumed TicketSequence. Ticket creation
is opt-in; inventory targets are capped at 250.
Configure a nonzero inventory target to opt into ticket creation:
from xrpl_x402_client import XRPLPaymentSigner
signer = XRPLPaymentSigner(
wallet,
network="xrpl:1",
ticket_inventory_target=8,
)
A target of 0 disables automatic creation and requires an existing ticket;
targets above 250 are rejected.
Extensions and idempotency¶
Unknown extension entries are echoed unchanged. Unsafe HTTP methods and non-idempotent MCP tools require payment-identifier by default. Reuse the same identifier and signed payload while reconciling a pending settlement. Reusing an identifier for different requirements returns HTTP 409.
For unsafe HTTP routes, connect the existing Redis service to the response store and leave Bazaar enabled:
from redis.asyncio import Redis
from xrpl_x402_middleware import (
PaymentMiddlewareASGI,
RedisResourceResponseStore,
)
redis = Redis.from_url("redis://127.0.0.1:6379/0")
response_store = RedisResourceResponseStore(redis)
app.add_middleware(
PaymentMiddlewareASGI,
routes={"POST /orders": route_config},
response_store=response_store,
enable_bazaar=True,
)
This automatically requires the payment-identifier extension for the route
Settlement and receipts¶
Remove SETTLEMENT_MODE entirely; startup fails if it is present. A payment is
successful only after a validated tesSUCCESS. settlement_pending contains a
transaction hash and network and must be retried with the identical envelope.
Receipts now store the standard SettleResponse plus immutable accepted
requirements and an exact HTTP/MCP request fingerprint. Signed payloads and
payment identifiers moved to the atomic attempt sidecar beside the receipt file;
they are no longer embedded in terminal or pending receipt records. Pending
receipts are not paid receipts.
Direct MCP callers must now supply a stable recovery scope:
paid = wrap_mcp_client_with_xrpl_payment(
session,
signer,
recovery_scope="https://mcp.example/sse|principal:user-42",
)
Use a non-secret endpoint/principal label. Do not use the authentication token itself; the wrapper persists only the scope's SHA-256 digest.