x402-xrpl
v0.2.0
Published
XRPL x402 v2 SDK (exact presigned Payment tx scheme)
Downloads
185
Readme
x402-xrpl
TypeScript SDK for x402 v2 payments over XRPL using the exact presigned Payment tx blob scheme from this repository.
This package is intended to mirror the ergonomics of the Python SDK (x402_xrpl/) so developers can easily build:
- Express x402-protected resource servers
- TypeScript buyer clients that automatically handle HTTP
402 Payment Required
Install
Requires Node.js 18+.
npm install x402-xrplIf you are building a resource server:
npm install expressExpress resource server
import express from "express";
import { requirePayment } from "x402-xrpl/express";
const app = express();
app.use(
requirePayment({
path: "/ai-news",
price: "1000", // XRP drops; for IOUs use the XRPL value string (e.g. "1.25")
payToAddress: "rhaDe3NBxgUSLL12N5Sxpii2xy8vSyXNG6",
network: "xrpl:1",
asset: "XRP",
facilitatorUrl: "http://127.0.0.1:8011",
resource: "demo:ai-news",
description: "AI news feed (paid)",
}),
);
app.get("/ai-news", (_req, res) => res.json({ ok: true }));
app.listen(8080, () => console.log("listening on http://127.0.0.1:8080"));XRPL SourceTag (analytics)
By default, this SDK issues extra.sourceTag = 804681468 in the 402 accepts[] requirements, 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, set extra.sourceTag:
app.use(
requirePayment({
// ...
extra: { sourceTag: 123 },
}),
);IOU notes (non-XRP assets)
- Set
assetto a canonical XRPL currency code: 3 chars or 40-hex.- A symbol like
RLUSDmust be provided as its 40-hex currency code, unless you explicitly opt into UTF-8 encoding using the currency helpers.
- A symbol like
- Provide the issuer as
issuerorextra.issuer. - Set
priceto the IOUvaluestring (e.g."1","1.25").
For RLUSD, raw payment requirements should use 524C555344000000000000000000000000000000 as asset and include the
issuer. If you want to start from a display symbol, convert it first:
import { resolveCurrencyCode } from "x402-xrpl";
const asset = resolveCurrencyCode("RLUSD", { allowUtf8Symbol: true });Buyer client (fetch wrapper)
x402Fetch implements the standard x402 flow:
- Make request
- If
402withaccepts[], select aPaymentRequirements - Build
PAYMENT-SIGNATURE - Retry once
Buyer clients call the protected resource URL and use XRPL WS plus network as a guard. They do not take a facilitator
URL.
import { x402Fetch } from "x402-xrpl";
import { Wallet } from "xrpl";
const wallet = Wallet.fromSeed(process.env.XRPL_SEED!);
const fetchPaid = x402Fetch({
wallet,
network: "xrpl:1",
// Optional: override the default public WS endpoint
// wsUrl: "wss://s.altnet.rippletest.net:51233",
});
const resp = await fetchPaid("http://127.0.0.1:8080/ai-news");
console.log(resp.status, await resp.text());Optional X402 Secure / Verifiable Intent
XRPL x402 payments can optionally carry an extensions.x402Secure envelope. When the hosted XRPL Facilitator receives
that envelope, it calls X402 Secure internally; X402 Secure calls Trustline; and settlement only proceeds if the risk
decision allows it. If a payment carries no x402Secure envelope, the SDK sends a normal XRPL x402 payment and the
Facilitator can skip X402 Secure in optional mode.
Learn more about the product at X402 Secure.
The SDK exposes a verifiableIntentProvider seam. The provider receives the Facilitator-issued x402Secure envelope
from the 402 Payment Required response and may return an x402SecurePatch. For full L1-L3 Verifiable Intent, attach
the chain under verifiableIntentChain.
import { LocalProducerProvider, x402Fetch } from "x402-xrpl";
import { Wallet } from "xrpl";
const provider = new LocalProducerProvider({
// Load a cached L1/L2 delegation from your trusted surface.
// L1 is issued by Trustline for your org; L2 is owner-signed; L3 is signed fresh per payment.
delegation: async () => ({
l1Credential: { format: "sd+jwt", sdJwt: cachedL1, kid: issuerKid },
l2Delegation: { format: "kb-sd-jwt+kb", sdJwt: cachedL2 },
agentPrivateJwk,
agentKid,
}),
});
const fetchPaid = x402Fetch({
wallet: Wallet.fromSeed(process.env.XRPL_SEED!),
network: "xrpl:0",
verifiableIntentProvider: provider,
});If your agent cannot hold a Trustline issuer secret, use RemoteIssuerProvider with your own backend or the T54 issuer
broker. The remote endpoint only issues L1. The SDK still signs L2 with the owner key and L3 with the agent key locally:
import { RemoteIssuerProvider, RemoteIssuerSource, x402Fetch } from "x402-xrpl";
const provider = new RemoteIssuerProvider({
issuer: new RemoteIssuerSource({
endpoint: "https://agent.example/api/vi/issue-l1",
headers: () => ({ authorization: `Bearer ${shortLivedIssuerToken}` }),
}),
issueRequest: {
subject: ownerAccountRef,
ownerPublicJwk,
allowedChains: ["xrpl"],
allowedAssets: ["XRP"],
spendingCeiling: "0.001",
validitySeconds: 3600,
ownerProof,
},
ownerPrivateJwk,
ownerKid,
agentPrivateJwk,
agentPublicJwk,
agentKid,
constraints: { allowed_chains: ["xrpl"], allowed_assets: ["XRP"], per_transaction_max: "0.001" },
});Important boundaries:
- Do not put Trustline API keys or issuer secrets into the x402 payment payload. L1 issuance should happen in your trusted backend or issuer surface.
- Keep issuer and verifier environments aligned. A staging-issued L1 should be verified by staging Trustline/X402 Secure; a production-issued L1 should be verified by production Trustline/X402 Secure.
- The provider patch cannot overwrite Facilitator-issued fields such as
requirementToken,decisionToken, ortrustlineEvidenceSessionId. - If the provider returns
requires_confirmation, the SDK does not sign or retry. This is the right response when the owner has not yet approved an L2 delegation.
Local development
From this package directory:
npm install
npm run build
npm test