Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

x402 XRPL Python SDK

This package provides a small, spec‑aligned SDK for working with x402 payments over XRPL. It is designed to be reusable by both buyer clients (creating PAYMENT-SIGNATURE headers) and seller/resource servers (verifying and settling via a facilitator).

It covers three schemes:

  • exact: one presigned Payment tx blob per request.
  • upto: one CheckCreate per request, cashed for the metered amount.
  • batch-settlement (beta): a Check session, in which one Check pays for many signed requests and is cashed once when the session closes. See "Check sessions" below.

Overview and links: https://xrpl-ai.org/build.


Install (PyPI)

Requires Python 3.11+.

pip install x402-xrpl

Note: the PyPI name uses a hyphen, but the import name uses an underscore:

import x402_xrpl

Quickstart: Buyer Client (requests-style)

If you want the UX of x402_requests(...) (auto-handle 402), use x402_xrpl.clients.requests:

Buyer clients call the protected resource URL and use XRPL RPC plus optional filters. They do not take a facilitator URL.

import requests

from x402_xrpl.clients.requests import x402_requests
from x402_xrpl.clients.base import decode_payment_response
from x402_xrpl.xrpl_wallet import wallet_from_seed

XRPL_RPC = "https://s.altnet.rippletest.net:51234/"
RESOURCE_URL = "http://127.0.0.1:8080/xrpl-demo/resource"

# Not xrpl-py's Wallet.from_seed(seed): it reads a secp256k1 seed (`s…`, not `sEd…`) as Ed25519,
# which gives another key pair and address. wallet_from_seed uses the key type the seed encodes.
wallet = wallet_from_seed("…demo seed…")

session: requests.Session = x402_requests(
    wallet,
    rpc_url=XRPL_RPC,
    # Optional filters so most users don't write a custom selector:
    network_filter="xrpl:1",
    scheme_filter="exact",
)

resp = session.get(RESOURCE_URL, timeout=180)
print(resp.status_code, resp.text)

if "PAYMENT-RESPONSE" in resp.headers:
    settlement = decode_payment_response(resp.headers["PAYMENT-RESPONSE"])
    print("settled tx:", settlement.get("transaction"))

Safe retries for verify-stage outages

When the resource server returns a typed transient response with HTTP 503, retryable=true, phase=verify, and settlementAttempted=false, the buyer session reuses the same invoice and PAYMENT-SIGNATURE for at most two retries while the payment requirement remains within maxTimeoutSeconds. Retry-After controls the delay. It never retries a response where settlement may have been attempted.

FastAPI/Starlette resource servers using require_payment propagate facilitator verify-stage 408, 429, 5xx, and transport timeouts as this typed 503 response. The response includes retryAfter, correlationId, and the two safety fields above. This is fail-closed: the protected handler and /settle are not called. Policy denials and other 4xx responses remain non-retryable.

Cross-currency payments (opt-in)

By default the payer funds the exact quoted asset: an XRP quote is paid in XRP (no SendMax), and an IOU quote is paid with a same-asset SendMax equal to Amount. When the merchant advertises extra["crossCurrency"] = True, you may fund from a different asset by passing pay_with to prepare_payment as an XRP drops string or an IssuedCurrencyAmount:

# XRP -> IOU: fund a USD quote with XRP (a drops-string SendMax)
payer.prepare_payment(req, pay_with="1000000")

# IOU -> XRP, or IOU -> a different IOU: fund with an issued-currency SendMax
from xrpl.models.amounts import IssuedCurrencyAmount
payer.prepare_payment(req, pay_with=IssuedCurrencyAmount(currency="USD", issuer="rISSUER", value="5"))

The destination Amount is always pinned to the quote (cross-currency only relaxes the SendMax asset). The signed SendMax is your own spend ceiling. Before signing, the client refuses: a pay_with when the merchant did not opt in, a drops-string pay_with for an XRP destination (XRP→XRP is not cross-currency), and any non-positive or malformed SendMax — matching the facilitator's verifier so a bad value never wastes a signed transaction.

To protect a route (e.g. /ai-news) with XRPL x402 payments using an ergonomic wrapper:

from fastapi import FastAPI

from x402_xrpl.server import require_payment

app = FastAPI()

app.middleware("http")(
    require_payment(
        path="/ai-news",
        price="1000",  # XRP drops; for IOUs use the XRPL value string (e.g. "1.25")
        pay_to_address="rhaDe3NBxgUSLL12N5Sxpii2xy8vSyXNG6",
        network="xrpl:1",
        asset="XRP",
        facilitator_url="http://127.0.0.1:8011",
        resource="demo:ai-news",
        description="AI news feed (paid)",
    )
)

XRPL SourceTag (analytics)

By default, this SDK issues PaymentRequirements.extra["sourceTag"] = 804681468 and buyer clients will sign XRPL Payment transactions with SourceTag = 804681468 (so XRPL can query/aggregate these payments on-ledger).

To override the tag for your app, pass your own value in extra:

app.middleware("http")(
    require_payment(
        # ...
        extra={"sourceTag": 123},
    )
)

IOU notes (non-XRP assets):

  • Set reqs.asset to the XRPL currency code (MUST be 3 chars or 40-hex).
  • Provide the issuer as reqs.extra["issuer"] (classic address).
  • Set reqs.amount to the XRPL issued-currency value string (e.g. "1", "1.25").

For RLUSD, raw payment requirements should use 524C555344000000000000000000000000000000 as reqs.asset and include the issuer in reqs.extra["issuer"].

If you want a human-friendly display string (or an opt-in conversion of a symbol like "RLUSD" into a 40-hex code), use the currency helpers:

from x402_xrpl.xrpl_currency import display_currency_code, resolve_currency_code

asset = "524C555344000000000000000000000000000000"
print(display_currency_code(asset))  # "RLUSD" (best-effort)

# Opt-in convenience (only use if your app has a trusted mapping/intent):
asset_hex = resolve_currency_code("RLUSD", allow_utf8_symbol=True)

Check sessions (batch-settlement, beta)

A Check session pays for many requests with one XRPL Check. The payer signs one CheckCreate up to a ceiling, signs each request with a session key (HTTP Message Signatures, RFC 9421), and the merchant cashes the Check once, for what the requests used, when the session closes. In x402 terms this is the batch-settlement scheme with the XRPL binding xrpl-check-session, version 1, in x402_xrpl.session. It is a beta: its API may still change before 0.4.0.

Paying with sessions

A client talks only to the resource server. It needs the payer's wallet and an XRPL JSON-RPC node.

from x402_xrpl.session.client import SessionClient
from x402_xrpl.session.ledger import HttpJsonRpc
from x402_xrpl.session.wire import decode_b64_json
from x402_xrpl.xrpl_wallet import wallet_from_seed

RESOURCE_URL = "https://rs.example/v1/chat/completions"
body = {"model": "…", "messages": [{"role": "user", "content": "Hello"}]}

client = SessionClient(
    wallet=wallet_from_seed("…payer seed…"),
    rpc=HttpJsonRpc("https://s.altnet.rippletest.net:51234/"),
)

# The unpaid request's 402 carries the session offer.
offer = client.discover(RESOURCE_URL, json_body=body)

# One CheckCreate for up to 1 unit of the offer's asset. The ceiling must lie between
# offer.min_ceiling and offer.max_ceiling, the duration within offer.max_session_seconds.
session = client.open(offer, ceiling="1", session_seconds=3600)

# A paid request is charged at most max_amount; its receipt is in PAYMENT-RESPONSE
# (session.receipts keeps them too).
response = client.request(session, "POST", RESOURCE_URL, max_amount="0.05", json_body=body)
receipt = decode_b64_json(response.headers["PAYMENT-RESPONSE"], reason="payment_response")

# After the close, the merchant cashes the Check once, for the sum of the receipts.
client.close(session)
  • AsyncSessionClient is the same client over httpx.AsyncClient, for many requests in flight on one session.
  • A client that lost a request's answer looks it up with client.lookup(session, request_id). To find a session again after a restart, open it with a key you can derive again and call client.restore(offer, session_id, key).
  • A platform whose clients must not hold the payer's key runs the payer service (python -m x402_xrpl.session.payer_service) and gives each client a payer_service.RemotePayer as payer=. The service can sign through a Privy server wallet (privy.PrivySigner).

Selling with sessions

A resource server needs a tenant API key from its facilitator's operator. The tenant is bound to the server's payTo accounts, origins and paths, decides which payers it admits and caps their ceilings, live sessions and request rate; a payer it does not admit is refused (payer_not_allowed) when it opens a session. The parts:

  • server.SessionGateway (FastAPI) serves the session endpoints and the 402 offer, reserves each signed request and commits its metered usage (SessionCharge).
  • facilitator.AsyncSessionFacilitator is the gateway's client for the facilitator.
  • pricing.PriceTable is the price table each session's terms freeze.
  • The merchant settles its own sessions: the facilitator never holds a key that can sign for it. python -m x402_xrpl.session.signer_service is the only process with the merchant's signing key, best a RegularKey (python -m x402_xrpl.session.custody moves signing to one), and python -m x402_xrpl.session.settlement_worker cashes the sessions that closed. The docstrings of signer_from_env, worker_from_env and payer_from_env list their settings.

Mainnet (xrpl:0) is opt-in wherever a service signs: MerchantSigner(mainnet=True) or MERCHANT_MAINNET_ENABLED=true for the merchant's signer, PAYER_MAINNET_ENABLED=true for the payer service, and --mainnet for custody.

Optional X402 Secure / Verifiable Intent

Buyer clients can attach a Verifiable Intent L1-L3 chain through the verifiable_intent_provider seam. When the hosted XRPL Facilitator receives extensions.x402Secure, it calls X402 Secure and Trustline before settlement. Plain XRPL x402 payments without the extension remain on the normal path.

Use RemoteIssuerProvider when the agent cannot hold the Trustline issuer secret. The remote endpoint only issues L1; the SDK still signs L2 with the owner key and L3 with the agent key locally:

from x402_xrpl.vi import RemoteIssuerProvider, RemoteIssuerSource

provider = RemoteIssuerProvider(
    issuer=RemoteIssuerSource(
        endpoint="https://agent.example/api/vi/issue-l1",
        headers=lambda: {"authorization": f"Bearer {short_lived_issuer_token}"},
    ),
    issue_request={
        "subject": owner_account_ref,
        "ownerPublicJwk": owner_public_jwk,
        "allowedChains": ["xrpl"],
        "allowedAssets": ["XRP"],
        "spendingCeiling": "0.001",
        "validitySeconds": 3600,
        "ownerProof": owner_proof,
    },
    owner_private_jwk=owner_private_jwk,
    owner_kid=owner_kid,
    agent_private_jwk=agent_private_jwk,
    agent_public_jwk=agent_public_jwk,
    agent_kid=agent_kid,
    constraints={"allowed_chains": ["xrpl"], "allowed_assets": ["XRP"], "per_transaction_max": "0.001"},
)

Do not put Trustline API keys, issuer secrets, wallet seeds, owner private keys, or agent private keys into the x402 payment payload.

Payment safety

  • Before signing, the payer requires its configured network, quote network, RPC server_info.info.network_id, and any autofilled network ID to agree. Missing RPC network information is rejected. Explicit invoice IDs must match the quote when both are present.
  • Set max_fee_drops on the payer options, x402_requests, or x402_purchase to choose your application's fee ceiling. The default is 10000 drops (0.01 XRP), checked after autofill and before signing. Existing signedTxBlob payloads and default both invoice binding are preserved.
  • Header helpers and payer methods accept optional resource; high-level clients forward the advertised resource. Existing custom header factories without this parameter still work.
  • isValid and success must be JSON booleans. x402_purchase returns settle_failed for a valid success: false. A malformed settlement header or unavailable paid-request response returns settlement_unknown, with retryable=False and original transaction recovery metadata when available. Query the original transaction before attempting another payment; this lookup is the application's responsibility. A true flag alone is not independent ledger proof.
  • Facilitator HTTP exception messages contain status and a validated request ID instead of the response body or request URL. The original response is still available explicitly.

Metadata

Release files for x402-xrpl 0.4.0b1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for x402-xrpl 0.4.0b1
File Size Uploaded Provenance
x402_xrpl-0.4.0b1.tar.gz 111.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for x402-xrpl 0.4.0b1
File Interpreter ABI Platform
x402_xrpl-0.4.0b1-py3-none-any.whl Python 3 none any Details

Total release size: 234.1 kB

Release files / x402_xrpl-0.4.0b1.tar.gz

Download URL x402_xrpl-0.4.0b1.tar.gz
Size 111.9 kB
Tags Source
SHA-256 checksum
How to use checksums
bca500e7564e8b1e9fe0894f04c7cb45a5cc6f00fe68cda1ffded8ebd40b9f0f
BLAKE2b-256 checksum
How to use checksums
e75fec57e9c5a04255cb0eb12b7261d4032ae7208f84d332e69351bca1924261
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.9

Release files / x402_xrpl-0.4.0b1-py3-none-any.whl

Download URL x402_xrpl-0.4.0b1-py3-none-any.whl
Size 122.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
23908912538ee6d86f439d1ef8305a31133615beb369b28f8a42c69c72a51b9d
BLAKE2b-256 checksum
How to use checksums
5582c9c10868481cd49f909efe991e51cceace83e8cf2a79d85c97e60166e2cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.9

Security for x402-xrpl 0.4.0b1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page