Skip to main content
Reins’ offline purchase-recovery example separates payment state from resource delivery. You can restore a synthetic uncertain attempt, see a second payment attempt refused, then retry failed delivery using the same retained record. It runs locally without a wallet, HTTP request or ledger connection.
This is a synthetic orchestration exercise, not an x402 adapter, transaction builder, signer or receipt verifier. It spends no XRP and adds no ledger enforcement. All 11 example tests, the demo and strict TypeScript checks passed. The separate product suite has 154 passing tests and the build passed. Internal agent review is not an external security assessment.

Run the copyable example

In a fresh local directory, create this package.json. Use Node.js 22.12 or later:
Install the runner and test tool versions used for this example:
Copy the three complete files at the end of this page into purchase.ts, demo.ts and purchase.test.ts, in the same directory. Then run:
The demo writes a synthetic JSON checkpoint into a new temporary directory, restores it and removes that directory afterwards. Its output is:
If you already have the updated source checkout, run node --import tsx examples/x402-offline/demo.ts and npx vitest run examples/x402-offline/purchase.test.ts. The regular product npm run test:unit command selects packages/, so the example tests must be run explicitly.

Read the state

The strict local quote binds identifier, resource, GET method, facilitator label, network, XRP asset, destination, integer drops, SourceTag and expiry. It is not an x402 wire-format parser. Fixture labels are not XRPL addresses. The example uses xrpl:1 for testnet and numeric SourceTag 58900000. Its clock uses arbitrary integer seconds, not ledger sequences or protocol timestamps. beginSubmission records uncertainty before any hypothetical transport. A real caller would have to persist the checkpoint before sending anything. reconcile accepts only the retained hash and a definitive fixture outcome; it does not query or verify the ledger. A timeout is not a definitive unpaid result. recordDelivery changes delivery state only and cannot start payment.

Keep the limitation visible

The at-most-one-attempt property applies only to the same retained and restored record. New records, older ready checkpoints, concurrent processes, tampering or lost files can bypass it. The checkpoint hash checks consistency, not authenticity. This exercise does not implement atomic persistence, fsync, durable journaling, uniqueness locks, trusted clocks or actual submission reconciliation. Synthetic receipts are deliberately labelled unverified. Delivery retry does not ensure merchant delivery, correctness or refunds. These are toolkit orchestration rules, not ledger guarantees or independent co-signer enforcement. Do not attach a wallet to this fixture and treat it as a payment engine.

Resolve protocol compatibility first

On November 11, 2026, the x402 Foundation’s XRPL exact-scheme specification requires rejecting a transaction containing Delegate. That conflicts with Reins’ current delegated allowance payment topology. This is a specification-level compatibility gate, not a claim that XRPL itself rejects delegation. T54’s v2 exact-scheme documentation describes facilitator submission of presigned Payments. Its hosted acceptance of a custom delegated Payment remains unverified. Inspection of the checksum-verified Python SDK 0.3.4 source archive default payer builder found no delegation parameter; that does not establish hosted verifier behavior or behavior of other versions. This example therefore stays independent of any transport, SDK or facilitator. Do not remove Delegate or change signing topology simply to pass a facilitator check. A future real adapter needs a pinned protocol and implementation, compatible authority model, trusted settlement evidence and a separately reviewed durable recovery design. The example is available here as source; it is not a new npm release. See the exposure inspector for the current published read-only CLI.

Copy the complete files

The example follows the project’s Apache-2.0 license. Save each block with the indicated filename.