Docs / Guides
Run the scenarios yourself
The demo agent is a short script on top of the buyer SDK. Point it at a reference service, choose a fault, and it prints each step with a transaction hash of its own. A scheduled catalogue runs the same scenarios on a timer and records every run for the demo and proofs pages.
The demo agent
AGENT_SECRET=S... SERVICE_URL=https://fx.example.com FAULT=BAD_PRICE pnpm --filter @lopw/demo-agent start
# optional
RESOURCE=/v1/ledger/latest # another paid endpoint (default /v1/quote?pair=EUR-USD)
REQUIRE=3,4,5 # predicate IDs the agent insists on (default 6)
RECEIPT_STORE=./receipts.json # where receipts are kept until their claim windows closeAGENT_SECRET is the agent’s Stellar secret key; the account needs USDC on the same network as the contracts (LOPW_NETWORK, default testnet). The agent reads the service’s bond, pays $0.01 through x402 with the policy minStake: 100 USDC, requiredPredicates: REQUIRE, minMaxClaim: 1 USDC, verifies the receipt and claims if a predicate is breached.
FAULT is sent as the x-lopw-fault request header. A reference service honours it only when its operator has enabled per-request fault injection; otherwise its own configured mode applies.
| Fault mode | What the seller does | Expected result |
|---|---|---|
NORMAL | Serves correctly | Receipt verified; warranty kept; no claim |
BAD_PRICE | Quotes 10% below the oracle, citing the real observation | P6 claim paid (FX service) |
STALE_DATA | Serves data an hour old with an honest data_as_of | P3 claim paid |
WRONG_TERMS | Signs the receipt with a different terms_hash | P2 claim paid |
DUPLICATE_RECEIPT | Signs a second receipt with a different response_hash for the same payment | P1 claim paid |
BAD_STATUS | Charges for HTTP 203 when only 200 is allowed | P4 claim paid (ledger service) |
OVERSIZE | Returns more than the 4 KiB promised | P5 claim paid (ledger service) |
BAD_SIGNATURE | Sends a receipt with a broken signature | SDK flags the response untrusted; no claim; no penalty |
The reference services
| Service | What it sells | Warranty |
|---|---|---|
| FX quote API | EUR/USD quoted against USD, $0.01 per call | P1, P2, P3 (600 s), P4 ([200]), P6 (EUR on the fiat-rates oracle, 50 bps, 600 s); claim window 1 h |
| Ledger data API | Latest-ledger facts and account balances | P1, P2, P3 (30 s), P4 ([200]), P5 (4096 bytes); claim window 1 h |
The scheduled catalogue
Eleven named scenarios run against the live reference services with real x402 payments, on a schedule (every six hours by default), and every run is judged against the scenario’s expected outcome and recorded with its payment and claim transactions, payout and stake before and after. Payouts are recycled into the seller’s stake so the services stay funded.
| Scenario | Expected |
|---|---|
| Honest FX quote | Receipt verified; warranty kept; no claim; stake unchanged |
| Quote 10% off the oracle | P6 claim paid 5 USDC |
| Data older than promised | P3 claim paid 5 USDC |
| Receipt cites different terms | P2 claim paid 5 USDC |
| Two different receipts for one payment | P1 claim paid 5 USDC |
| Broken receipt signature | Untrusted; no claim; no penalty |
| Charged for an unpromised status | P4 claim paid 1 USDC |
| Response larger than promised | P5 claim paid 1 USDC |
| Same claim filed twice | Second submission fails with AlreadyClaimed |
| Claim against an honest receipt | Rejected as NotProven; no payout |
| Agent policy refuses an under-staked seller | Payment refused by policy; no transaction |
GET /v1/testnet/scenarios?runs=5
// [ { "id": "bad-price", "title": "…", "summary": "…", "why": "…", "steps": [ … ], "expected": "…", "service": "fx",
// "totals": { "runs": 12, "passed": 12 },
// "runs": [ { "id": 1, "started_at": …, "finished_at": …, "outcome": "pass", "observed": "…", "payment_tx": "…", "claim_tx": "…", "payout": "50000000", "stake_before": "…", "stake_after": "…", "service_id": "…" } ] } ]The demo and proofs pages are built from this feed. What each scenario demonstrates and why it matters is in Proofs and scenarios.