[{"t":"What is LOPW","u":"/docs/","s":"Start here","d":"LOPW in plain words: a seller puts money behind a promise, every paid answer comes with a signed receipt, and a broken promise pays the buyer automatically. Start here.","h":[["plain-words","In plain words"],["why","Why it exists"],["what-it-is-not","What LOPW is not"],["vocabulary","Five words you will meet everywhere"],["where-next","Where next"]],"x":"LOPW (Layer of Onchain Programmable Warranties) lets a paid API put real money behind what it promises. If a signed receipt, or a named price oracle, proves the promise was broken, the buyer is paid from that money automatically. x402 proves the agent paid; LOPW puts capital behind what the seller promised. How a warranty worksAn animated walk through offer, payment, receipt, check, claim, validator and payout.Getting startedThree tracks: agents that buy, APIs that sell, apps that read bonds.Protocol referenceEvery type, hash, predicate, function, error code and state, exactly as the contracts implement them.For AI agentsMarkdown twins, llms.txt, JSON Schemas and test vectors you can consume directly. Think of a contractor who leaves a deposit with a neutral bank before starting a job, together with a written list of exactly what they promise. When the job is done they hand you a signed receipt describing what they delivered. If that receipt itself shows a promise was broken, the bank pays you from the deposit straight away. No lawyers, no arguing, no waiting. The seller puts down a depositAn API provider locks USDC stake in a smart contract (the Bond Registry) and publishes the exact conditions it promises to meet: its terms. Every sale comes with a signed receiptThe buyer pays as usual over x402 and gets a receipt the seller signed, tied to that payment, that request and that response. Broken promise, automatic payoutIf the receipt, or a named price oracle, proves a breach, a contract pays the buyer from the deposit. Anything it cannot prove is rejected. Three everyday analogies help, and each has a limit worth knowing: Analogy What matches Where it stops A security deposit on a rental Money locked up front; returned when the promise was kept; kept by the other side when it was not A landlord argues about damage. LOPW pays only on breaches a contract can prove from the evidence alone, so there is nothing to argue A warranty card in the box The promise is written down before you buy, and it is the seller\u2019s promise, not a third party\u2019s A warranty card needs a claims department. LOPW\u2019s claims department is a contract that decides in one transaction A notarised receipt The seller signs exactly what it delivered, so it can be held to it later A notary checks identity, not truth. LOPW checks the receipt against the seller\u2019s own promise and, for prices, against an independent oracle Autonomous agents buy APIs, data, inference and compute without a human watching. Payment protocols such as x402 solve paying: the seller knows it was paid. They do not solve trusting: the buyer has no recourse if the data is stale, the response is padded, the status was wrong or the price was invented. LOPW adds that missing layer. Before paying, an agent can read what a service promises and how much stake stands behind it. After paying, it can verify what it received and enforce the warranty on its own, with no human in the loop. What LOPW guarantees A buyer can read a seller\u2019s stake and warranty terms before paying. Every paid response carries a seller-signed receipt bound to the payment, the request, the response and the terms. A breach that is provable from signed evidence, or from a named on-chain oracle, pays out without human review. Not escrow. It does not hold every payment until a service is completed. The x402 payment settles normally. Not arbitration. There are no judges, DAO votes or human dispute resolution for supported conditions. Not a quality judge. It does not decide whether an AI answer was good, useful or intelligent, nor latency or availability seen only by the buyer. LOPW focuses on conditions that can be proven deterministically from cryptographic evidence, contract state or a named on-chain oracle. Six such conditions, the predicates P1 to P6, exist in version 1. Word Meaning Stake (or bond) USDC a seller locks in the Bond Registry. It is the only money a claim can ever pay from. Terms The list of predicates a service promises, their limits, and a claim window. Hashed; the hash is pinned in every offer. Offer The seller\u2019s signed promise for one call, shown before payment in the 402 response. Receipt The seller\u2019s signed statement of what it served, returned with the paid response. Claim One transaction that submits an offer and a receipt to the Claims contract and ends paid or rejected. The full glossary has every term. If you prefer to see it move, go to How a warranty works. Who it is forAgents, API sellers and platforms: what each gets and what each must do.NetworksOne protocol on every chain: Stellar as the reference binding, the EVM networks, and the planned Solana binding.FAQShort answers to the questions people ask first.Security modelTrust assumptions, threats, mitigations and known limits.Proofs and scenariosWhat has been demonstrated on a live network, and how to rerun it. "},{"t":"How a warranty works","u":"/docs/start/how-it-works.html","s":"Start here","d":"An animated, step-by-step walk through a LOPW warranty: offer, payment, receipt, local check, claim, validator, and payout or rejection.","h":[["who-does-what","Who does what"],["happy-path","The happy path costs nothing extra"],["breach-path","When a promise is broken"],["what-cannot-be-claimed","What cannot be claimed"]],"x":"One paid API call, seven moments. Play the sequence, or step through it at your own pace. Every arrow below is something that really happens on the wire or on-chain; nothing is simplified away. How a warranty works, step by step PlayPreviousNextRestart Sequence of a LOPW warranty between agent, seller and contractsAgentSellerContracts402 + signed offer1x402 payment settles2response + signed receipt3verify and evaluate4claim(offer, receipt)5validator decides6payout from stake / rejected7 Warranty offerThe seller answers the first request with 402 Payment Required: the normal x402 payment requirements plus a signed warranty offer under extensions.lopw. The offer names the service, the registry, the exact terms (terms_hash), the price, the most one receipt can pay out (max_claim) and an expiry. PaymentThe agent verifies the offer signature against the key registered on-chain, reads the service\u2019s live state, stake and terms, applies its own policy, then pays through standard x402. The payment is settled on-chain by the facilitator; LOPW changes nothing here. Service receiptThe seller returns the response. The settlement response (PAYMENT-RESPONSE) carries a signed receipt under extensions.lopw that cites the offer, the settlement transaction, the buyer, the request and response hashes, the HTTP status, the time served and the data\u2019s freshness. Local checkThe agent verifies the receipt (signature, offer, buyer, payment, request and response hashes, status) and evaluates every predicate in the terms locally. A receipt that fails verification is reported as untrusted; it cannot be claimed. ClaimIf a predicate is breached, the agent (or anyone holding the evidence) submits the signed offer and receipt to the Claims contract inside the claim window. One transaction, one outcome. ValidatorThe Claims contract checks signatures, window, replay and terms, then calls the validator contract for that predicate. For P6 the validator reads the named oracle on-chain. Paid or rejectedA proven breach pays the buyer named in the receipt min(max_claim, stake remaining) from the seller\u2019s stake, in the same transaction. A claim the validator cannot prove is recorded as rejected and nothing is paid. With reduced motion enabled, the controls still step through the sequence; only the moving token is left out. Party What it does Already in x402? Seller Runs a paid API, stakes the bond, signs offers and receipts Yes Buyer An agent or app that pays per request and may file a claim Yes Facilitator The x402 service that verifies and settles the payment Yes Bond Registry Contract that holds stake and stores each service\u2019s terms and signing keys New Validators Contracts that each decide one kind of breach (one per predicate) New Claims contract Verifies evidence, calls the validator, pays the buyer or rejects New When the seller keeps its promise, steps 5 to 7 never happen. The agent pays once over x402, receives one receipt, verifies it locally and moves on. There is no escrow to release, no extra transaction and no waiting period. The warranty is a standing threat, not a per-call tax. The agent\u2019s local check finds a breached predicate, for example served_at \u2212 data_as_of above the promised max_data_age. Inside the claim window (at most 24 hours, one hour for oracle-backed services) the agent submits the signed offer and receipt to the Claims contract. The Claims contract verifies both signatures, checks the window and that no claim was paid for this payment yet, confirms the predicate is part of the terms, and calls the validator. The validator decides from the evidence alone. For P6 it reads the named oracle\u2019s observation on-chain. A proven breach pays min(max_claim, stake remaining) to the buyer named in the receipt, from the seller\u2019s stake, in the same transaction. A claim that is not proven is recorded as rejected and nothing is paid. Anyone may submit a claim, but the payout always goes to the buyer named in the receipt, so there is nothing to gain by front-running one. A receipt with an invalid signature, a missing receipt, and a receipt citing an offer the buyer never saw prove nothing about the seller: anyone could forge them. The buyer SDK reports each as an untrusted response. No claim is possible and no stake is at risk. Quality of an answer, and latency only the buyer witnessed, are out of scope by design. Next: Who it is for, or jump to the exact rules in the claim lifecycle. "},{"t":"Who it is for","u":"/docs/start/who-it-is-for.html","s":"Start here","d":"What LOPW gives agents, API sellers and platforms, what each has to do, and where each should start reading.","h":[["agents","Agents and the people who run them"],["sellers","API sellers"],["platforms","Platforms, dashboards and marketplaces"],["engineers","Protocol engineers and researchers"]],"x":"Three audiences meet in LOPW. An agent wants to pay only services that stand behind their promises. A seller wants to be that service. A platform wants to show or score bonds without a wallet. Your agent pays small amounts, often, to services nobody vetted by hand. With the buyer SDK it reads a seller\u2019s live state, stake and terms before paying, refuses anything outside its policy, verifies every receipt, and files a claim on its own when a promise is broken. You get You do A policy the SDK enforces before money moves: minStake, requiredPredicates, minMaxClaim, requireWarranty Install @lopw/buyer-sdk, create a LopwBuyer with a funded key, call agent.fetch(url) A warranty record with every check result, the breaches found and the time until which a claim is possible Keep receipts until their claim windows close (the FileReceiptStore does this for you) A payout from the seller\u2019s stake when a breach is proven, in one transaction Call agent.claim(warranty) inside the window Start with Getting started for agents. You already sell over x402. LOPW lets you say, in a form a machine can check, exactly what you promise and how much you are willing to lose if you break it. Agents with a policy read that before they pay; the stake is the signal. You get You do Offers and receipts signed automatically on every 402 and every paid response, with no change to verify or settle Register a service with its terms and a dedicated Ed25519 signing key; deposit at least 100 USDC Terms you choose: any subset of P1 to P6 with your own limits, and a claim window up to 24 hours Add the lopw extension to your x402 resource server and report freshness or oracle evidence through two response headers Protection against over-exposure: one receipt pays at most max_claim, and at most 50% of stake can leave in one epoch Keep stake above your minimum; rotate the signing key if it is ever exposed Start with Getting started for sellers. Everything LOPW does on-chain is public: every service\u2019s state and stake, every claim, every event. A read-only HTTP API serves it as JSON with cursor pagination, ETags and a live event stream, so an app, a mobile client or a directory can show bonds, rank services by stake, or alert on suspensions without running a node or holding a key. Start with Getting started for app builders and the HTTP API reference. The protocol reference describes every type, hash, predicate, function and error exactly as the contracts implement them, with shared test vectors. The deep dive covers the threat model, the economics and the reasoning behind each rule. "},{"t":"Glossary","u":"/docs/start/glossary.html","s":"Start here","d":"Every LOPW term in one place, from stake and terms to predicates, epochs and key versions, with links to where each is specified.","h":[],"x":"Short definitions, alphabetical. Each links to the page where the term is defined precisely. Active Service state: stake is at or above min_stake. The only state in which offers are issued and the buyer SDK pays. States allowed_status P4 parameter: the HTTP statuses the seller may charge for. A receipt with any other status proves a breach. P4 Bond Registry The contract that holds stake per service and stores each service\u2019s terms versions and signing keys. Only the Claims contract may slash. Reference Buyer The agent or app that pays per request. The receipt names the buyer, and any payout goes to that address. Chain binding The chain-specific parts of LOPW: encoding, signature scheme, contract functions and the payment-rail binding. Stellar is the first. Bindings Claim One transaction submitting a signed offer and receipt (plus a second receipt for P1) to the Claims contract. Outcome: paid, rejected, or an error with nothing recorded. Claims Claim window Seconds after served_at during which a claim may be filed. Part of the terms; at most 24 hours, at most one hour when P6 is offered. Claims contract Verifies evidence, applies the seven checks, calls the validator, and pays or rejects. Claims Closed Service state after a full withdrawal. No offers, no claims. Final. data_as_of Receipt field: Unix seconds the data was true as of; 0 when not applicable. Drives P3. Domain separation The type tag, network ID and registry address hashed in front of every signed body, so a signature is valid for one type, one network and one deployment only. Hashing and signing Epoch (slash epoch) A period one unbonding delay long during which total slashes may not exceed 50% of the stake held at its start. Slash rate limit Facilitator The x402 service that verifies and settles the payment. Unchanged by LOPW. Fee (protocol fees) A one-off registration fee and an annual fee on stake, in the stake token, paid to a treasury. Both may be zero. Fees Key version Index of a service\u2019s Ed25519 signing key. Rotation retires the old version with a timestamp; receipts served before it stay verifiable. Rotate a key max_claim Offer field: the most a single receipt can pay out. The payout is min(max_claim, stake remaining), and never above the launch limit max_payout if one is set. min_stake A service parameter set at registration: the stake below which the service is Suspended. At least 100 USDC. NotProven / NotVerifiable The two reasons a validator can give for not proving a breach. Both are recorded on-chain as a rejected claim; neither penalises anyone. Offer (warranty offer) The seller\u2019s signed promise for one call, carried in the 402 response under extensions.lopw. Fields offer_nonce 32 random bytes that make every offer hash unique. The buyer SDK refuses to pay an offer it has already paid. Oracle (SEP-40) An on-chain price feed with price(asset, timestamp) and decimals(). P6 reads one named in the terms. P6 payment_reference Receipt field: the settlement transaction hash. Together with service_id it keys the one-paid-claim-per-payment rule. Pending Service state after registration until deposits reach min_stake. No offers can exist, so stake can be withdrawn at once. Predicate One breach condition a contract can decide from the evidence alone. Six exist: P1 Equivocation, P2 Terms mismatch, P3 Stale data, P4 Bad status, P5 Size limit, P6 Oracle deviation. Predicates Receipt (service receipt) The seller\u2019s signed statement of what it served and under which offer, carried in PAYMENT-RESPONSE under extensions.lopw. Fields request_hash / response_hash SHA-256 bindings of the request (method, path with query, body hash) and of the response body. The buyer SDK recomputes both. Seller Runs a paid API, stakes the bond, signs offers and receipts with a dedicated signing key. Service One registered API with its own stake, terms versions, signing keys and state, identified by a 32-byte service_id. Signing key An Ed25519 key separate from the seller\u2019s account. It signs offers and receipts and cannot move stake. Slash The registry transferring part of a service\u2019s stake to a buyer on a proven claim. Callable only by the Claims contract. Stake (bond) USDC a seller locks per service in the Bond Registry. The only money a claim can pay from; a service\u2019s total payouts can never exceed it. Suspended Service state when a slash or a fee takes stake below min_stake. No new offers; claims still paid while stake remains. Terms A service\u2019s list of predicates with parameters plus a claim window. Hashed into terms_hash; old versions stay readable so old receipts can still be claimed. Fields Time lock The 26-hour delay on admin changes to who may slash, which validator decides a predicate, fees and loosened limits, so every seller can withdraw first. Time locks Unbonding Service state while a full exit waits out the unbonding delay: the largest claim window ever registered plus one hour. Stake stays slashable. Untrusted response The buyer SDK\u2019s verdict on a receipt that fails verification (bad signature, wrong offer, buyer, payment, hashes or status). Not claimable. Validator A contract with one function, validate(terms, offer, receipt, extra), that decides one predicate. The Claims contract keeps an allowlist of validators by predicate ID. x402 The HTTP payment protocol LOPW extends. The offer and receipt travel in its v2 extension slot named lopw; verify and settle are unchanged. x402 extension "},{"t":"FAQ","u":"/docs/start/faq.html","s":"Start here","d":"Short answers to the first questions people ask about LOPW: what it costs, what it covers, what it cannot do, and how it relates to x402.","h":[],"x":"Short answers first; each links to the page with the full detail. Does LOPW change how x402 payments work?No. LOPW is an x402 v2 extension named lopw. Verify and settle are untouched, any conformant facilitator works, and a buyer that ignores the extension still completes a normal purchase. Wire format Is this escrow?No. The payment settles normally. What is locked is the seller\u2019s stake, once, for all its calls. A claim pays from that stake. What does a buyer actually get when a promise is broken?min(max_claim, stake remaining) in the stake token, paid to the buyer named in the receipt in the same transaction as the claim. max_claim is in every offer, so the buyer knows the cover before paying. What can be claimed, exactly?Six conditions: two different receipts for one payment (P1), a receipt citing other terms than the offer (P2), data older than promised (P3), a status the seller promised not to charge for (P4), a response larger than promised (P5), and a served value further from a named oracle than the promised tolerance (P6). Predicates Can a seller be slashed for a bad answer, slow response or an outage?No. Quality, latency and availability seen only by the buyer cannot be proven from evidence alone and are out of scope. A missing receipt or an invalid signature is not slashable either: the SDK reports it as an untrusted response. Can a buyer be paid twice for one payment?No. A paid claim is recorded under (service_id, payment_reference); a second claim for the same payment fails with AlreadyClaimed, whichever receipt it presents. Can anyone file a claim, even someone who did not pay?Yes, anyone holding the signed offer and receipt may submit. The payout still goes to the buyer named in the receipt, so there is nothing to gain by racing. What if the seller withdraws its stake before I claim?It cannot do so in time. A full exit waits the unbonding delay: the largest claim window the service ever registered plus one hour. The stake stays slashable throughout. Unbonding Can a seller pay itself to empty its stake ahead of honest claims?It is rate-limited: within one epoch (one unbonding delay long) total slashes may not exceed 50% of the stake at the epoch\u2019s start, so stake can leave no faster than a normal withdrawal would allow. Threats How much stake does a seller need?At least 100 USDC, set as min_stake at registration. Agents may demand more through their policy, and the stake is public before payment. What does the seller pay?A one-off registration fee and an annual fee on stake held, both in the stake token and both possibly zero; and, when it breaks a promise, the payout. Economics What if the oracle is wrong or has no data?The oracle named in the terms is trusted: a wrong observation produces a wrong outcome. A missing observation makes the claim NotVerifiable: nothing is paid and nobody is penalised. Which chains?Built first on Stellar with Soroban, USDC and x402; designed for every chain. The offer, receipt, validator interface and claim semantics are portable; encoding, signatures, contract functions and the payment rail are per binding. Bindings Where can I see it working?The demo replays a real claim paid on a live network, and the proofs page lists every transaction and test. Proofs and scenarios I am an AI agent. Where do I start?Agents start here: Markdown twins of every page, /llms.txt, /llms-full.txt, JSON Schemas and test vectors. "},{"t":"Getting started","u":"/docs/getting-started.html","s":"Guides","d":"Pick your track: make an agent pay only bonded x402 services, add a stake-backed warranty to your x402 API, or read bonds and claims from any app over HTTP.","h":[["amounts","Before you start"],["how-tos","How-to guides"]],"x":"Three tracks. If you run an agent, make it pay only services that stand behind their promises. If you run a paid API, back your promise with stake and win the agents that check. If you build apps, read every bond and claim over plain HTTP. For agents (buyers)Install the buyer SDK, set a policy, fetch, claim.For API providers (sellers)Register, stake, add the middleware to your x402 server.For app buildersServices, claims, events and live streams from the public API. Amounts Amounts are integers in the stake token\u2019s smallest unit. USDC uses 7 decimals, so 10_000_000n is 1 USDC and 100n * 10_000_000n is 100 USDC. The SDKs use bigint for every amount; the HTTP API returns amounts as decimal strings. Packages: @lopw/buyer-sdk for agents, @lopw/seller-middleware for sellers, and @lopw/protocol (types, encoding, hashing, local predicate evaluation and the Stellar binding) used by both. Access is for approved integrators. Networks: the Stellar binding ships with STELLAR_TESTNET (contract addresses included) and STELLAR_MAINNET (addresses supplied by you once a mainnet deployment exists). getNetwork(name) resolves one by name and applies LOPW_NETWORK, LOPW_REGISTRY, LOPW_CLAIMS, STELLAR_RPC_URL and LOPW_ORACLE from the environment when set. Task-sized recipes once you are set up: Add a warranty to an x402 API Make an agent pay only bonded services File a claim Choose predicates and limits Rotate a signing key Withdraw stake safely Read a bond from any app Run the scenarios yourself "},{"t":"Getting started for agents","u":"/docs/guides/agents.html","s":"Guides","d":"Make an agent pay only bonded x402 services with @lopw/buyer-sdk: install, set a policy, fetch with warranty checks before and after payment, and claim when a promise is broken.","h":[["install","1. Install"],["policy","2. Create a buyer with a policy"],["fetch","3. Fetch a paid resource"],["claim","4. Claim when a promise is broken"]],"x":"The buyer SDK wraps x402\u2019s fetch. Before paying it verifies the seller\u2019s signed offer, reads the service\u2019s on-chain state, stake and terms, and applies your policy. After paying it verifies the receipt against what you actually sent and received, evaluates every predicate locally, and files a claim in one call. TerminalCopy npm install @lopw/buyer-sdk # access for approved integrators Your agent needs a Stellar key (S...) with USDC on the same network as the contracts it talks to. The same key pays via x402 and signs claim transactions. The policy is what your agent insists on before it pays. If any check fails, the x402 payment is aborted before money moves. Here: at least 100 USDC of stake, the service must offer P6 (oracle deviation), and a single receipt must be able to pay out at least 1 USDC. TypeScriptCopy import { LopwBuyer, FileReceiptStore } from \"@lopw/buyer-sdk\"; const agent = new LopwBuyer({ secret: process.env.AGENT_SECRET, // Stellar secret key; pays via x402 and files claims policy: { minStake: 100n * 10_000_000n, requiredPredicates: [6], minMaxClaim: 10_000_000n }, store: new FileReceiptStore(\"./receipts.json\"), // receipts survive restarts until their claim windows close }); BuyerPolicy field Type Meaning minStake bigint Refuse services whose stake is below this (asset base units) requiredPredicates number[] Refuse unless the warranty covers all of these predicate IDs requireWarranty boolean Refuse sellers that offer no LOPW warranty at all. Default true minMaxClaim bigint Refuse offers whose single-receipt cover (max_claim) is below this LopwBuyerConfig field Meaning secret Stellar secret key that pays and files claims (required) binding A StellarBinding; defaults to testnet. Use new StellarBinding(getNetwork()) to follow LOPW_NETWORK policy The BuyerPolicy above store Where receipts are kept: FileReceiptStore(path) or MemoryReceiptStore (default). Any ReceiptStore with put, list, hasPaidOffer works fetch A custom fetch implementation, if needed TypeScriptCopy const { response, body, warranty } = await agent.fetch(\"https://fx.example.com/v1/quote?pair=EUR-USD\"); // response: the HTTP Response body: Buffer warranty: WarrantyRecord | undefined Before paying, the SDK decodes the offer from extensions.lopw and refuses to pay, throwing WarrantyRefused through x402\u2019s abort hook, when: the offer names a registry other than the binding\u2019s (offer names an unknown registry) valid_until has passed (offer expired) the store already holds a receipt for this offer_nonce (offer already paid) the offer\u2019s price or asset differ from the x402 payment requirements (offer does not match the payment requested) the service is not Active (service is Suspended, and so on) offer.terms_hash is not the service\u2019s current terms stake or max_claim are below the policy minimums the offer signature does not verify against the key at offer.key_version the terms lack a required predicate (warranty lacks predicate P6) After paying, it reads the receipt from PAYMENT-RESPONSE and checks the signature, that the receipt cites this offer, names your address as buyer and the settlement transaction as payment, and that request_hash, response_hash, response_bytes and status match what was sent and received. Then it evaluates every predicate in the terms, reading the oracle for P6. WarrantyRecord field Meaning offer, receipt The signed offer and receipt, decoded duplicate A second receipt for the same payment, if the seller sent one (P1 evidence) terms, service The terms paid under and the service\u2019s state at payment time problems Receipt checks that failed. Non-empty means the response is untrusted and not claimable breaches Predicate IDs the receipt proves were breached claimableUntil Unix seconds after which no claim is possible: served_at + claim_window TypeScriptCopy if (warranty?.breaches.length) { const result = await agent.claim(warranty); // uses the first breach unless you pass a predicate ID // result: { outcome: { paid: 50000000n } | { rejected: \"not_proven\" | \"not_verifiable\" }, transaction: \"\u2026\" } } A proven breach pays your agent from the seller\u2019s stake in the same transaction: min(max_claim, stake left). The SDK waits one ledger if needed so the claim is never rejected for landing in a ledger closed before served_at. Contract errors read as names (WindowClosed, AlreadyClaimed, SlashRateLimited) rather than numbers. What your agent gets A paid call either matches what the seller promised, or produces evidence that pays out automatically. A service whose stake falls below its minimum is suspended, and agents with a policy stop paying it at once. Next: tune the policy, file a claim, or read the predicates your agent evaluates. "},{"t":"Getting started for sellers","u":"/docs/guides/sellers.html","s":"Guides","d":"Add a stake-backed warranty to an x402 API: generate a signing key, choose terms, register and stake in the Bond Registry, and add @lopw/seller-middleware to your resource server.","h":[["install","1. Install"],["signing-key","2. Generate a signing key"],["terms","3. Choose your terms"],["register","4. Register and stake"],["middleware","5. Add the middleware"],["headers","6. Report freshness and oracle evidence"]],"x":"Four steps turn an x402 endpoint into a bonded one: a dedicated signing key, terms you can keep, a registered and funded service, and the lopw extension on your resource server. TerminalCopy npm install @lopw/seller-middleware @lopw/protocol @x402/express @x402/core @x402/stellar # LOPW packages: access for approved integrators Each service signs with an Ed25519 key that is separate from the seller\u2019s account. The server holds its 32-byte seed; it can sign offers and receipts but cannot move stake. Keep the seed as a server secret and never reuse the test-vector seed. TypeScriptCopy import { randomBytes } from \"node:crypto\"; import { signingKeyFromSeed, publicKeyHex } from \"@lopw/protocol\"; const signingSeed = randomBytes(32).toString(\"hex\"); // store as a secret (SIGNING_SEED) const signingKey = publicKeyHex(signingKeyFromSeed(signingSeed)); // 64 hex chars, registered on-chain Terms are the predicates you promise, their limits, and a claim window. Promise only what you can keep: see Choose predicates and limits. This example is the warranty of the reference FX quote service. TypeScriptCopy import type { Terms } from \"@lopw/protocol\"; const terms: Terms = { predicates: [1, 2, 3, 4, 6], // equivocation, terms mismatch, stale data, bad status, oracle deviation claim_window: 3600n, // seconds; at most 86400, at most 3600 when P6 is listed max_data_age: 600n, // P3: data no older than 10 minutes when served allowed_status: [200], // P4: only 200 is chargeable max_response_bytes: 4096, // P5 limit (P5 not listed here, so not enforced) oracle: { oracle_contract: network.oracle, oracle_asset: { Other: \"EUR\" }, max_deviation_bps: 50, max_oracle_age: 600n }, }; Registration needs the seller\u2019s account key (it signs the transaction and pays the registration fee, if one is set) and returns the 32-byte service_id. The service starts Pending; the first deposit that reaches min_stake makes it Active. TypeScriptContract calls TypeScriptCopy import { Keypair } from \"@stellar/stellar-sdk\"; import { StellarBinding, getNetwork } from \"@lopw/protocol\"; const network = getNetwork(); // \"testnet\" unless LOPW_NETWORK says otherwise const binding = new StellarBinding(network); const seller = Keypair.fromSecret(process.env.SELLER_SECRET); const { serviceId } = await binding.registerService(seller, signingKey, terms, 100n * 10_000_000n); // min_stake: 100 USDC await binding.deposit(seller, serviceId, 150n * 10_000_000n); // stake 150 USDC -> Active Bond RegistryCopy register_service(seller, signing_key, terms, min_stake) -> service_id // seller signs; min_stake >= 100 USDC deposit(service_id, amount) // reaching min_stake: Pending -> Active Errors you can meet here: StakeBelowFloor (minimum under 100 USDC), InvalidTerms (see the terms rules), NotAllowlisted or Paused during a guarded launch, StakeCapReached when a launch limit is hit. The warranty is an x402 v2 extension. Register it on your resource server, run lopwRequestContext() before paymentMiddleware, and enable it per route with extensions: { lopw: {} }. TypeScriptCopy import { paymentMiddleware, x402ResourceServer } from \"@x402/express\"; import { HTTPFacilitatorClient } from \"@x402/core/server\"; import { ExactStellarScheme } from \"@x402/stellar/exact/server\"; import { StellarBinding, getNetwork } from \"@lopw/protocol\"; import { createLopwExtension, lopwRequestContext, DATA_AS_OF_HEADER } from \"@lopw/seller-middleware\"; const network = getNetwork(); const lopw = createLopwExtension({ binding: new StellarBinding(network), serviceId: process.env.SERVICE_ID, signingSeed: process.env.SIGNING_SEED, maxClaim: 5n * 10_000_000n, // most one receipt can pay out: 5 USDC }); const server = new x402ResourceServer(new HTTPFacilitatorClient({ url: process.env.X402_FACILITATOR })) .register(network.caip2, new ExactStellarScheme()) .registerExtension(lopw); app.use(lopwRequestContext()); app.use(paymentMiddleware({ \"GET /v1/quote\": { accepts: [{ scheme: \"exact\", price: \"$0.01\", network: network.caip2, payTo, maxTimeoutSeconds: 60 }], extensions: { lopw: {} } }, }, server)); app.get(\"/v1/quote\", (req, res) => { res.set(DATA_AS_OF_HEADER, String(asOf)); res.json(quote); }); LopwSellerConfig field Meaning binding The chain binding that reads your service\u2019s state and terms serviceId Your 32-byte service ID as hex signingSeed Hex Ed25519 seed of the service signing key (not the seller\u2019s account key) maxClaim Most a single receipt can pay out, in the asset\u2019s smallest unit; goes into every offer offerValiditySeconds Seconds an offer stays payable. Default 120 stateCacheSeconds Seconds to cache on-chain service state. Default 15 faults Test hook for fault injection in reference services. Never set in production From now on every 402 carries a signed offer under extensions.lopw, and every paid response carries a signed receipt in PAYMENT-RESPONSE under extensions.lopw. The middleware only issues offers while the service is Active; otherwise routes serve plain x402 terms until you deposit again. lopw.refresh() re-reads the state on demand. Response header Constant When Value LOPW-Data-As-Of DATA_AS_OF_HEADER Any route that promises P3 Unix seconds the data was true as of. Omitted or 0 means not applicable; P3 is then never proven, but the buyer SDK flags a P3 service reporting 0 as untrusted LOPW-Oracle-Evidence ORACLE_EVIDENCE_HEADER Any route that promises P6 JSON {\"oracle_timestamp\",\"oracle_price\",\"reported_value\"} as decimal strings; the middleware fills in the oracle contract and asset from the terms TypeScriptCopy const { price, timestamp } = await binding.lastPrice(terms.oracle.oracle_contract, terms.oracle.oracle_asset); // SEP-40 lastprice() res.set(ORACLE_EVIDENCE_HEADER, JSON.stringify({ oracle_timestamp: timestamp.toString(), oracle_price: price.toString(), reported_value: served.toString() })); Read the oracle\u2019s last completed observation before quoting and cite the timestamp the oracle returned; the validator will fetch that same observation on-chain. See P6 for the e"},{"t":"Getting started for app builders","u":"/docs/guides/apps.html","s":"Guides","d":"Read bonded services, claims, events and live streams from the public LOPW HTTP API: JSON, cursor pagination, ETags, problem+json errors and Server-Sent Events, with open CORS.","h":[["conventions","Conventions"],["first-calls","First calls"],["live","Live events"],["from-chain","Reading the chain directly"]],"x":"Everything LOPW does on-chain is public and indexed. The read-only API serves it as JSON for third-party apps, mobile clients and agents: no wallet, no node, no key. Versioned paths with the network in the path: /v1/{network}/..., where network is testnet or mainnet. JSON only, snake_case fields matching the contract types; amounts as decimal strings (i128 does not fit a JSON number); timestamps as Unix seconds; 32-byte values as lowercase hex. Errors are RFC 9457 application/problem+json with a stable machine-readable code. Cursor pagination (cursor, limit 1 to 200, default 50), never offset pages, so clients can resume. Open CORS on read endpoints; no cookies; no auth for public reads. Cache-friendly: ETag and 304 Not Modified; rate limits advertised with RateLimit-* headers and 429 plus Retry-After. Real time: GET /v1/{network}/stream is a Server-Sent Events feed of contract events. Self-describing: the OpenAPI 3.1 document is served at /v1/openapi.json; a copy lives at /docs/api/openapi.json. HTTPJavaScript HTTPCopy GET /v1/testnet/stats // { \"services\": 2, \"active_services\": 2, \"total_stake\": \"4000000000\", \"claims_paid\": 9, \"claims_rejected\": 1, \"total_paid\": \"320000000\", \"indexed_ledger\": 5021999 } GET /v1/testnet/services?state=Active&limit=50 // { \"data\": [ { \"service_id\": \"\u202664 hex\u2026\", \"seller\": \"G\u2026\", \"state\": \"Active\", \"stake\": \"2000000000\", \"min_stake\": \"1000000000\", // \"pending_withdrawal\": \"0\", \"terms_hash\": \"\u2026\", \"key_version\": 1, \"claims\": { \"paid\": 3, \"rejected\": 0, \"total_paid\": \"150000000\" } } ], // \"next_cursor\": null } GET /v1/testnet/services/{service_id}/claims?outcome=paid // { \"data\": [ { \"id\": \"\u2026\", \"service_id\": \"\u2026\", \"predicate_id\": 6, \"outcome\": \"paid\", \"reason\": null, \"buyer\": \"G\u2026\", \"amount\": \"50000000\", // \"payment_reference\": \"\u2026\", \"transaction\": \"\u2026\", \"ledger\": 5021500, \"occurred_at\": 1790991420 } ], \"next_cursor\": null } TypeScriptCopy const API = \"https://api.lopw.com\"; const res = await fetch(`${API}/v1/testnet/services?state=Active`); const { data, next_cursor } = await res.json(); for (const s of data) { const stakeUsdc = Number(BigInt(s.stake)) / 1e7; // decimal string -> number for display only console.log(s.service_id.slice(0, 8), s.state, stakeUsdc, \"USDC\", s.claims.paid, \"claims paid\"); } Field values shown here are illustrative shapes, not live numbers. TypeScriptCopy const es = new EventSource(\"https://api.lopw.com/v1/testnet/stream\"); es.addEventListener(\"claim_paid\", (e) => console.log(\"claim paid\", JSON.parse(e.data))); es.addEventListener(\"state_changed\", (e) => console.log(\"state changed\", JSON.parse(e.data))); // Each message has id (event ID), event (kind) and data (an Event). Resume with the Last-Event-ID header or ?after=<id>. Event kinds mirror the contract events: service_registered, deposited, slashed, state_changed, claim_paid, claim_rejected, and the admin events (claims_change_proposed, validator_change_proposed, claims_contract_set, validator_set). The full list is in the registry and claims references. The API is a convenience; the chain is the source of truth. Any app can read the same data with the protocol package\u2019s binding, which simulates read-only contract calls from a throwaway account and never signs anything: TypeScriptCopy import { StellarBinding, getNetwork } from \"@lopw/protocol\"; const binding = new StellarBinding(getNetwork(\"testnet\")); const service = await binding.getService(serviceId); // { seller, state, stake, min_stake, terms_hash, key_version, max_claim_window, pending_withdrawal } const terms = await binding.getTerms(serviceId, service.terms_hash); // { predicates, claim_window, max_data_age, allowed_status, max_response_bytes, oracle } const key = await binding.getKey(serviceId, service.key_version); // { public_key, registered_at, retired_at } Next: the complete HTTP API reference, or read one service\u2019s bond from its own discovery endpoint. "},{"t":"How-to guides","u":"/docs/guides.html","s":"Guides","d":"Task-focused recipes for sellers, agents and apps: add a warranty, pay only bonded services, file a claim, choose predicates, rotate keys, unbond, read a bond, run the scenarios.","h":[],"x":"Each guide assumes you have read the getting started track for your role. Code uses the real SDK and API names. Add a warranty to an x402 APIThe seller checklist from key to first bonded 402.Make an agent pay only bonded servicesPolicy fields, what the SDK refuses on its own, and how refusals surface.File a claimFrom breach to payout, with the window, the outcomes and the errors.Choose predicates and limitsWhat each predicate needs from you and how to set limits you can keep.Rotate a signing keyRetire a key without stranding a single receipt.Withdraw stake safelyPartial and full exits, the delay, cancellation, and what stays slashable.Read a bond from any appA service's own discovery endpoint, the public API and the chain.Run the scenarios yourselfThe demo agent, the fault modes and the scheduled catalogue. "},{"t":"Add a warranty to an x402 API","u":"/docs/guides/add-warranty.html","s":"Guides","d":"Checklist to turn an existing x402 endpoint into a bonded one: signing key, terms, registration, stake, the lopw extension and the freshness header.","h":[],"x":"Turn an existing x402 endpoint into a bonded one. Six steps; the detailed code is in Getting started for sellers. Create a dedicated Ed25519 signing key for the service (signingKeyFromSeed, publicKeyHex) and store its seed as a server secret. Pick the predicates you can honour and set their limits (guide). Every parameter is part of the hashed terms. Register the service: binding.registerService(seller, signingKey, terms, minStake), or register_service(seller, signing_key, terms, min_stake) on the registry. Keep the returned service_id. Deposit at least 100 USDC: binding.deposit(seller, serviceId, amount). When stake reaches min_stake the service turns Active. Register the lopw extension on your x402 resource server (createLopwExtension, registerExtension), add lopwRequestContext() before paymentMiddleware, and enable it on each route with extensions: { lopw: {} }. On routes that promise freshness (P3), set DATA_AS_OF_HEADER to the Unix time your data was true as of. On routes that promise P6, set ORACLE_EVIDENCE_HEADER. TypeScriptCopy const lopw = createLopwExtension({ binding: new StellarBinding(network), serviceId, signingSeed, maxClaim: 5n * 10_000_000n }); const server = new x402ResourceServer(new HTTPFacilitatorClient({ url: facilitatorUrl })) .register(network.caip2, new ExactStellarScheme()) .registerExtension(lopw); app.use(lopwRequestContext()); app.use(paymentMiddleware({ \"GET /v1/quote\": { accepts: [{ scheme: \"exact\", price: \"$0.01\", network: network.caip2, payTo, maxTimeoutSeconds: 60 }], extensions: { lopw: {} } } }, server)); The middleware only issues offers while the service is Active. If stake drops below the minimum, routes fall back to plain x402 terms until you deposit again. It only issues receipts for offers it signed itself, and only after the facilitator reports a successful settlement. Check your work GET your route without payment: the 402 body\u2019s extensions.lopw.info carries version, network, registry, service_id and a base64 offer. Pay it with the buyer SDK: warranty.problems is empty and warranty.breaches is empty. Read the service over the public API: state Active, stake as expected. "},{"t":"Make an agent pay only bonded services","u":"/docs/guides/bonded-only.html","s":"Guides","d":"Configure the buyer policy so an agent refuses unbonded, under-staked or under-covered sellers before any money moves, and handle refusals.","h":[],"x":"The buyer policy is checked against live on-chain data before every payment. If it fails, the SDK aborts the x402 payment: the seller never receives a request to serve and no transaction is made. Policy field Meaning Suggested starting point requireWarranty Refuse sellers that offer no LOPW warranty at all true (the default) minStake Least stake the service must hold, in 7-decimal units At least what a day of your calls could be worth in claims requiredPredicates Predicate IDs the terms must include [3] for data feeds, [6] for prices, [4, 5] for anything you pay per byte minMaxClaim Least a single receipt must be able to pay out At least the price of the call, usually more TypeScriptCopy const agent = new LopwBuyer({ secret: process.env.AGENT_SECRET, policy: { minStake: 100n * 10_000_000n, requiredPredicates: [6], minMaxClaim: 10_000_000n }, }); try { const { warranty } = await agent.fetch(url); } catch (e) { // A policy refusal surfaces as an aborted x402 payment whose reason is the WarrantyRefused message, // e.g. \"stake below policy minimum\", \"service is Suspended\", \"warranty lacks predicate P6\". } Independently of the policy, the SDK only pays a service that is Active, under its current terms, with a valid, unexpired offer whose price and asset match the x402 requirements and which it has not paid before. Watch the stake, not just the state A service stays Active while stake is at or above its own min_stake, which may be as low as 100 USDC. If many agents could claim at once, later claims find less stake. minStake and minMaxClaim are how your agent prices that risk; the economics page works through it. "},{"t":"File a claim","u":"/docs/guides/file-claim.html","s":"Guides","d":"From a breached receipt to a payout: what the SDK checks, when to claim, what the Claims contract returns, and every error you can meet.","h":[],"x":"A claim is one transaction with one outcome. The SDK builds it from the record it kept at payment time. Fetch through the SDK. After payment it evaluates every predicate and lists any breaches on warranty.breaches. If warranty.problems is non-empty the response is untrusted and cannot be claimed. Call agent.claim(warranty) before warranty.claimableUntil. It uses the first breach unless you pass a predicate ID; for P1 it attaches the duplicate receipt automatically. Read the result: { outcome: { paid: amount } | { rejected: \"not_proven\" | \"not_verifiable\" }, transaction }. TypeScriptContract call TypeScriptCopy const { warranty } = await agent.fetch(url); if (warranty && !warranty.problems.length && warranty.breaches.length) { const result = await agent.claim(warranty, warranty.breaches[0]); if (\"paid\" in result.outcome) console.log(\"paid\", result.outcome.paid, \"tx\", result.transaction); else console.log(\"rejected:\", result.outcome.rejected); } // Later: const open = await agent.store.list(); // records whose claim windows are still open Claims contractCopy claim(predicate_id: u32, offer: SignedOffer, receipt: SignedReceipt, extra: ClaimExtra) -> Result<ClaimOutcome, ClaimError> // extra = SecondReceipt(SignedReceipt) for P1, None otherwise // ClaimOutcome = Paid(i128) | Rejected(1 NotProven | 2 NotVerifiable) Fact Detail Who may submit Anyone holding the evidence. The payout always goes to receipt.buyer. Claim window Starts at served_at; lasts the terms\u2019 claim_window (at most 24 hours; at most one hour when P6 is offered). Payout min(offer.max_claim, stake remaining), also capped by the launch limit max_payout when one is set, and subject to the slash rate limit. Recorded outcomes Paid(amount) emits ClaimPaid. Rejected(NotProven | NotVerifiable) emits ClaimRejected and does not block a later valid claim for the same payment. Errors (nothing recorded) UnknownService, BadSignature, OfferMismatch, WindowClosed, AlreadyClaimed, PredicateNotOffered, NoStake, ValidatorNotSet, KeyRetired, UnsupportedVersion, SlashRateLimited. Full table in the claims reference. Rate limited? SlashRateLimited means this service already paid out 50% of its epoch-start stake in the current epoch. Retry in the next epoch if the window is still open. Fees The submitter pays the normal transaction fee, including the oracle read for P6. A rejected claim costs the fee and nothing else. Keep evidence: the FileReceiptStore keeps every record until its claim window closes and survives restarts. You can also call binding.submitClaim(signer, predicateId, offer, receipt, secondReceipt?) directly with evidence obtained elsewhere. "},{"t":"Choose predicates and limits","u":"/docs/guides/predicates-limits.html","s":"Guides","d":"How a seller picks which predicates to promise and sets limits it can keep: parameters per predicate, claim window, max_claim and min_stake, and the registry's validity rules.","h":[["validity","What the registry rejects"],["changing","Changing terms later"]],"x":"Promise only what you can keep, with limits that leave room for normal operation. Every parameter is part of the hashed terms, so changing one creates a new terms version. Predicate Parameter How to choose P1 Equivocation none Always safe to offer: an honest seller never signs two receipts for one payment. P2 Terms mismatch none Always safe to offer: the middleware signs receipts with the offer\u2019s terms. P3 Stale data max_data_age (seconds, > 0) Your worst normal data age plus headroom. Report data_as_of on every response; reporting 0 makes P3 unprovable but marks your response untrusted to policy-driven agents. P4 Bad status allowed_status (non-empty) The statuses you are willing to charge for, usually [200]. P5 Size limit max_response_bytes (> 0) Above your largest legitimate response. P6 Oracle deviation oracle_contract, oracle_asset, max_deviation_bps, max_oracle_age (> 0) A deviation wide enough to cover the gap between your source and the oracle; an age that covers at least one oracle tick (the reference feed ticks every 5 minutes, so the reference service uses 600 s). Service setting Meaning Rule claim_window Seconds a receipt stays claimable 1 to 86,400 (24 h); at most 3,600 (1 h) when P6 is listed, inside the oracle\u2019s 24-hour history retention max_claim Most one receipt pays out Set per offer by the middleware (maxClaim); agents may require a floor with minMaxClaim min_stake Stake below which the service is Suspended At least 100 USDC; set at registration, outside the terms hash, so changing it never invalidates a buyer\u2019s receipt register_service and update_terms fail with InvalidTerms when: predicates is empty, repeats an ID, or names an ID outside 1 to 6 claim_window is 0 or above the protocol maximum P3 is listed with max_data_age 0 P4 is listed with an empty allowed_status P5 is listed with max_response_bytes 0 P6 is listed without an oracle block, with max_oracle_age 0, or with a claim window above the oracle limit Call update_terms(service_id, terms). It returns the new terms_hash, makes it current, and keeps every earlier version readable so receipts issued under them remain claimable under their own terms. Terms cannot change while a service is Unbonding or Closed. The largest claim window you have ever registered sets your unbonding delay from then on (why). Use the predicate explorer to see how a sample receipt is judged under different limits. "},{"t":"Rotate a signing key","u":"/docs/guides/rotate-key.html","s":"Guides","d":"Replace a service's Ed25519 signing key without stranding any receipt: what rotate_key does, what happens to old receipts, and what a leaked key can no longer do.","h":[],"x":"Keys are stored by key_version. Rotating stamps the previous key with a retirement time: receipts it signed before then stay claimable, and it cannot produce new ones that any claim would accept. Generate a new Ed25519 seed and compute its public key (signingKeyFromSeed, publicKeyHex). Call rotate_key(service_id, signing_key) with the seller\u2019s account key. It returns the new key_version (previous + 1), stores the new key with registered_at = now, and sets the old key\u2019s retired_at = now. A KeyRotated event is emitted. Deploy the new seed to your server (signingSeed). New offers and receipts carry the new key_version; the middleware picks it up from the service state within stateCacheSeconds, or at once after lopw.refresh(). Bond RegistryCopy rotate_key(service_id, signing_key) -> key_version // seller signs; not allowed when Closed get_key(service_id, key_version) -> { public_key, registered_at, retired_at } // retired_at = 0 while current Receipt signed by served_at Claim result Old key before retired_at Verified normally; claimable until its window closes Old key at or after retired_at KeyRetired: rejected, nothing recorded New key any time after rotation Verified normally If a key leaks Rotate at once. The attacker can only produce receipts with served_at before the retirement time, inside their claim windows, each capped by max_claim and all of them by the slash rate limit. Offers are checked the same way: a claim verifies the offer signature against the key at offer.key_version. "},{"t":"Withdraw stake safely","u":"/docs/guides/unbonding.html","s":"Guides","d":"Partial and full withdrawals through request_unbond, the unbonding delay, cancel_unbond, withdraw, and what remains slashable, with the exact contract rules.","h":[],"x":"Stake leaves only through an unbonding delay, so every receipt you issued can still be claimed. The delay is the largest claim window the service has ever registered plus one hour. Call request_unbond(service_id, amount) from Active or Suspended. If stake \u2212 amount \u2265 min_stake the service stays Active and only amount is pending (partial withdrawal). Otherwise the whole stake becomes pending, full_exit is set and the service moves to Unbonding, which stops new offers. Wait until unlock_at = now + max_claim_window + 3600. The pending amount remains fully slashable throughout, and a slash reduces it if stake falls below it. Call withdraw(service_id). It pays min(pending, stake); a full exit pays the whole stake and closes the service. Before unlock_at it fails with StillLocked. Bond RegistryCopy request_unbond(service_id, amount) // Active | Suspended; one pending withdrawal at a time (WithdrawalPending otherwise) cancel_unbond(service_id) // clears the pending amount; Unbonding -> Active (stake >= min) or Suspended withdraw(service_id) -> amount // after unlock_at; a Pending service withdraws at once (no receipts can exist) Changed your mind? cancel_unbond(service_id) clears the pending amount and returns an Unbonding service to Active, or to Suspended if stake is below the minimum. Terms cannot be updated while a service is Unbonding. Two side effects to plan for Fees are collected whenever stake moves (deposit, request_unbond, withdraw, slash). A partial withdrawal that leaves exactly min_stake can therefore end below it after the fee, which suspends the service. Stake a little above the minimum. A Pending service (never funded to its minimum) can call withdraw immediately: no offer could have existed, so there is nothing to wait for. Try the numbers in the unbonding and fee calculator. "},{"t":"Read a bond from any app","u":"/docs/guides/read-bond.html","s":"Guides","d":"Three ways to read a service's bond without a wallet: the service's own GET /v1/warranty, the public API, and a direct chain read with the protocol package.","h":[["warranty-endpoint","The service's own discovery endpoint"],["public-api","The public API"],["chain","Straight from the chain"]],"x":"Any app, dashboard or agent can see what a seller stands behind before paying it a cent. Reference services expose a free, CORS-open GET /v1/warranty that returns the live bond. It is a convention for sellers, not a contract function; the fields come straight from get_service: HTTPCopy GET /v1/warranty // { // \"network\": \"testnet\", // \"registry\": \"C\u2026\", // Bond Registry contract // \"service_id\": \"\u202664 hex\u2026\", // \"state\": \"Active\", // \"stake\": \"2000000000\", // 200 USDC, 7-decimal units // \"terms_hash\": \"\u202664 hex\u2026\", // \"price\": { \"amount\": \"100000\", \"asset\": \"C\u2026\", \"display\": \"$0.01\" } // } TypeScriptCopy const bond = await (await fetch(`${serviceUrl}/v1/warranty`)).json(); if (bond.state !== \"Active\" || BigInt(bond.stake) < 100n * 10_000_000n) { // do not pay this service } HTTPCopy GET /v1/testnet/services/{service_id} // state, stake, min_stake, pending_withdrawal, terms_hash, key_version and claim totals (paid, rejected, total_paid) See Getting started for app builders for conventions and the API reference for every endpoint. TypeScriptCopy const service = await binding.getService(serviceId); // state, stake, min_stake, terms_hash, key_version, max_claim_window, pending_withdrawal const terms = await binding.getTerms(serviceId, service.terms_hash); // the predicates and limits behind terms_hash get_service(service_id) and get_terms(service_id, terms_hash) on the Bond Registry are the underlying calls; get_terms works for every terms version the service has ever registered, so a receipt\u2019s terms_hash can always be resolved. "},{"t":"Run the scenarios yourself","u":"/docs/guides/run-scenarios.html","s":"Guides","d":"Reproduce every demonstrated scenario with the demo agent against the reference services: honest quote, each predicate's breach, tampered signature, replay, honest claim rejected and policy refusal.","h":[["demo-agent","The demo agent"],["reference-services","The reference services"],["catalogue","The scheduled catalogue"]],"x":"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. TerminalCopy 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 close AGENT_SECRET is the agent\u2019s Stellar secret key; the account needs USDC on the same network as the contracts (LOPW_NETWORK, default testnet). The agent reads the service\u2019s 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 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 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\u2019s expected outcome and recorded with its payment and claim transactions, payout and stake before and after. Payouts are recycled into the seller\u2019s 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 HTTPCopy GET /v1/testnet/scenarios?runs=5 // [ { \"id\": \"bad-price\", \"title\": \"\u2026\", \"summary\": \"\u2026\", \"why\": \"\u2026\", \"steps\": [ \u2026 ], \"expected\": \"\u2026\", \"service\": \"fx\", // \"totals\": { \"runs\": 12, \"passed\": 12 }, // \"runs\": [ { \"id\": 1, \"started_at\": \u2026, \"finished_at\": \u2026, \"outcome\": \"pass\", \"observed\": \"\u2026\", \"payment_tx\": \"\u2026\", \"claim_tx\": \"\u2026\", \"payout\": \"50000000\", \"stake_before\": \"\u2026\", \"stake_after\": \"\u2026\", \"service_id\": \"\u2026\" } ] } ] The demo and proofs pages are built from this feed. What each scenario demonstrates and why it matters is in Proofs and scenarios. "},{"t":"Protocol overview","u":"/docs/protocol/","s":"Protocol reference","d":"The LOPW protocol for engineers: the six roles, the eight-step flow, the three kinds of contract, what is portable across chains and what is not, and what is out of scope.","h":[["roles","Roles"],["flow","Protocol flow"],["contracts","Contracts"],["portable","Portable and binding-specific"],["not","Out of scope in version 1"]],"x":"LOPW is a warranty and slashing primitive for machine payments. This reference describes version 1 exactly as the contracts and SDKs implement it. Where the specification and the decisions log differ, the decisions log wins; this reference follows the decisions. Offer, receipt and termsEvery field, type and rule.Hashing and signingDomain separation, exact byte layout, test vectors.Predicates P1 to P6Exact conditions, edge rules, interactive explorer.ClaimsSeven checks in order, outcomes, errors, events.Bond RegistryFunctions, events, states, floors, fees, time locks, unbonding.x402 extensionThe lopw extension on the wire.HTTP APIRead-only endpoints and the OpenAPI document.Chain bindingsPortable core and the Stellar binding. Six parties take part. The first three already exist in any x402 payment; LOPW adds the last three. Party Role Seller Runs a paid API, stakes the bond, signs offers and receipts with a dedicated signing key Buyer An agent or app that pays per request and may file a claim Facilitator The x402 service that verifies and settles the payment; unchanged Bond Registry Contract that holds stake per service and stores each service\u2019s terms versions and signing keys Validators Contracts that each decide one predicate, with one function: validate Claims contract Verifies evidence, applies the checks, calls the validator, pays the buyer or records a rejection Warranty offer Payment Service receipt Deterministic claim Validator Slash or reject The seller registers a service in the Bond Registry with its terms, a signing key and a minimum stake, then deposits stake. The service becomes Active when stake reaches the minimum. The buyer requests the resource. The seller answers 402 with the normal x402 payment requirements plus a signed warranty offer under extensions.lopw. The buyer verifies the offer signature against the key registered on-chain and reads the service\u2019s state, stake and terms. The buyer pays through standard x402, echoing the offer in its payment payload. The facilitator settles on-chain. The seller returns the response. The settlement response (PAYMENT-RESPONSE) carries a signed receipt under extensions.lopw that cites the offer and the settlement transaction. The buyer verifies the receipt and evaluates every predicate locally. If a breach is found, anyone may submit the offer and receipt to the Claims contract inside the claim window. The Claims contract runs the matching validator. A proven breach pays the buyer named in the receipt from the seller\u2019s stake. Anything else is rejected or fails. The x402 payment itself is unchanged: there is no escrow, no arbitration and no response period. The payment settles normally while LOPW runs as an independent accountability layer. An animated version is in How a warranty works. Contract Holds Public surface Bond Registry Stake per service (one SEP-41 token per deployment; USDC), terms versions, signing keys, service state, protocol floors, launch limits, fee configuration Functions; only the Claims contract may call slash Claims Paid-claim records keyed by (service_id, payment_reference); the validator allowlist by predicate ID; the registry address it serves Functions Validators P1 to P6 Nothing; pure functions of their inputs (P6 reads the oracle) validate(terms, offer, receipt, extra) Separating custody from claim logic keeps the code that holds money short, and lets predicates be added by allowlisting a new validator without redeploying the contract that holds stake. Contracts have no upgrade function; slashing rights can move only through time-locked admin changes (time locks). Portable across chains and payment rails Specific to a chain binding The warranty offer and its fields The encoding (ScVal XDR on Stellar) The service receipt and its fields The signature scheme (Ed25519 over a domain-separated SHA-256) The validator interface and the six predicates The contract functions and error codes The claim semantics: checks, outcomes, replay rule, payout rule The payment-rail binding (x402 exact on Stellar) and the oracle interface (SEP-40) Built first on Stellar with Soroban, USDC and x402; designed for every chain. See chain bindings. Judging whether an answer is correct, useful or well translated. Latency or availability witnessed only by the buyer. Slashing for missing receipts or invalid signatures. Reserved per-call coverage, insurance pools or a custom x402 settlement scheme. Arbitration, voting or seller appeals. Schema checks on the response body (a possible P7, after P1 to P6 have run in production). Version 1 pays only on breaches that can be proven deterministically from cryptographic evidence, contract state or a named on-chain oracle. "},{"t":"Offer, receipt and terms","u":"/docs/protocol/offer-and-receipt.html","s":"Protocol reference","d":"Field-by-field reference for the LOPW Terms, Offer, Receipt and oracle types, exactly as the contract types define them, with validity rules and JSON Schemas.","h":[["terms","Terms"],["oracle-terms","OracleTerms"],["terms-rules","Validity rules"],["offer","Offer"],["receipt","Receipt"],["oracle-evidence","OracleEvidence"],["registry-types","Registry records"],["service","Service"],["signing-key","SigningKey"],["config-types","ProtocolConfig, LaunchLimits, FeeConfig"],["validator-types","Validator types"]],"x":"Three structures carry the whole protocol: the terms a service registers, the offer it signs before payment and the receipt it signs after. All three are contract types; their canonical encoding is ScVal XDR, which is what gets hashed and signed. JSON Schemas derived from the contract types: terms.json, offer.json, receipt.json. In JSON, 32-byte values are lowercase hex, i128 amounts are decimal strings, u64 times are Unix seconds, and addresses are strkeys. A service\u2019s registered warranty terms. Hashed (terms_hash) and versioned; every offer pins one version. min_stake is deliberately not part of the terms, so changing the stake requirement never invalidates offers or receipts a buyer holds. Field Type Meaning predicates Vec<u32> Predicate IDs this warranty covers: 1 to 6, non-empty, no duplicates claim_window u64 Seconds after served_at during which a claim may be filed max_data_age u64 P3: most seconds allowed between data_as_of and served_at allowed_status Vec<u32> P4: HTTP statuses the seller may charge for max_response_bytes u32 P5: largest response body in bytes oracle OracleWarranty P6 parameters: None, or Some(OracleTerms). Required when P6 is listed Field Type Meaning oracle_contract Address The SEP-40 oracle the warranty is measured against oracle_asset OracleAsset Stellar(Address) or Other(Symbol), as that oracle names the asset max_deviation_bps u32 Allowed difference, in basis points of the oracle price max_oracle_age u64 Most seconds allowed between the oracle observation and served_at The registry rejects terms with InvalidTerms unless all of the following hold: predicates is non-empty, each ID is 1 to 6, and no ID repeats 1 \u2264 claim_window \u2264 max_claim_window (86,400 s on the first deployment) P3 listed \u21d2 max_data_age > 0; P4 listed \u21d2 allowed_status non-empty; P5 listed \u21d2 max_response_bytes > 0 P6 listed \u21d2 claim_window \u2264 max_oracle_claim_window (3,600 s) and oracle = Some(o) with o.max_oracle_age > 0 Terms from the shared test vectorsCopy { \"predicates\": [3, 4, 6], \"claim_window\": 3600, \"max_data_age\": 30, \"allowed_status\": [200], \"max_response_bytes\": 4096, \"oracle\": { \"oracle_contract\": \"C\u2026\", \"oracle_asset\": { \"Other\": \"EUR\" }, \"max_deviation_bps\": 50, \"max_oracle_age\": 600 } } The seller\u2019s signed promise for one call, shown with the 402 response. It does not repeat the warranty conditions: those live on-chain as the registered terms, and terms_hash pins the exact version the buyer is paying under. The offer is the signed body; the signature travels beside it in SignedOffer { offer, signature }. Field Type Meaning version u32 Protocol version, 1. Any other value fails a claim with UnsupportedVersion service_id BytesN<32> Assigned by the Bond Registry at registration registry Address Bond Registry contract that holds the stake. A claim requires it to be the registry the Claims contract serves terms_hash BytesN<32> Hash of the registered terms for this service. The buyer SDK requires it to be the service\u2019s current terms price i128 Price of this call, in the asset\u2019s smallest unit. The buyer SDK requires it to equal the x402 amount asset Address Token contract of the payment asset max_claim i128 Most a single receipt can pay out valid_until u64 Unix seconds after which the offer must not be paid offer_nonce BytesN<32> Random, unique per offer; makes every offer hash unique key_version u32 Which registered signing key signed this offer signature (in SignedOffer) BytesN<64> Ed25519 signature over offer_hash by the service signing key The middleware sets valid_until = now + offerValiditySeconds (default 120 s), max_claim from its configuration, price and asset from the matching x402 requirement, and key_version from the service\u2019s current key. The seller\u2019s signed statement of what it served and under which offer. Every field is a claim the seller can be held to. Signed body; the signature travels beside it in SignedReceipt { receipt, signature }. Field Type Binds the receipt to version u32 Protocol version, 1 service_id BytesN<32> The registered service offer_hash BytesN<32> The exact warranty offer terms_hash BytesN<32> The terms the seller says it served under (P2 compares it with the offer\u2019s) buyer Address The payer, who receives any claim payout payment_reference BytesN<32> The settlement transaction hash request_hash BytesN<32> Method, path with query, and body of the request response_hash BytesN<32> The response body as sent response_bytes u32 Size of the response body (P5) status u32 HTTP status served (P4) served_at u64 Unix seconds when the response was sent; starts the claim window data_as_of u64 Unix seconds the data was true as of; 0 if not applicable (P3) oracle OracleEvidenceOpt None, or Some(OracleEvidence) for P6 services key_version u32 Which signing key signed this receipt signature (in SignedReceipt) BytesN<64> Ed25519 signature over receipt_hash by the service signing key Field Type Meaning oracle_contract Address The SEP-40 oracle named in the terms oracle_asset OracleAsset The asset or pair, as the oracle names it oracle_timestamp u64 The timestamp the oracle returned for the observation used oracle_price i128 The oracle price the seller saw. Informational: the validator reads the oracle itself reported_value i128 The value the seller served, in the oracle\u2019s decimals The contract does not check that the payment happened. The seller\u2019s signature over payment_reference and buyer is its admission that it did, and any payout comes from the seller\u2019s own stake. The middleware takes buyer and payment_reference from the facilitator\u2019s settlement result and only issues receipts for offers it signed itself. Field Type Meaning seller Address The account that registered the service and may manage it state ServiceState Pending 0, Active 1, Suspended 2, Unbonding 3, Closed 4 stake i128 Stake currently held min_stake i128 Stake below which the service is Suspended; at least the protocol floor terms_hash BytesN<32> Current terms version key_version u32 Current signing key ve"},{"t":"Hashing and signing","u":"/docs/protocol/hashing-and-signing.html","s":"Protocol reference","d":"Exactly which bytes are hashed and signed in LOPW: ScVal XDR encoding, the domain-separated message, request and terms hashes, service IDs, key rotation, replay protection and the shared test vectors.","h":[["domain-separation","Domain separation"],["scval","ScVal XDR encoding rules"],["terms-hash","Terms hash and service ID"],["request-hash","Request and response hashes"],["signing-keys","Signing keys and rotation"],["invalid-signatures","Invalid signatures"],["replay","Replay protection"],["vectors","Shared test vectors"]],"x":"Offers and receipts are contract types, serialized as ScVal XDR, hashed with SHA-256 and signed with Ed25519. Off-chain code and the contracts hash identical bytes, and the shared test vectors fix every one of them. The signed message is the SHA-256 hash of four parts, in order: A type tag as ASCII: LOPW/OFFER/v1 (13 bytes) or LOPW/RECEIPT/v1 (15 bytes). The network ID: the 32-byte SHA-256 of the network passphrase, so a signature for one network is invalid on another. The ScVal XDR of the Bond Registry address (40 bytes for a contract address), so signatures cannot be replayed against another deployment. The ScVal XDR of the body: the Offer or Receipt struct, which never contains the signature. SigningCopy message = SHA-256( tag || network_id || XDR(registry) || XDR(body) ) signature = Ed25519.sign(service_signing_key, message) // over the 32-byte hash offer_hash = message (tag LOPW/OFFER/v1, registry = offer.registry) receipt_hash = message (tag LOPW/RECEIPT/v1, registry = the registry the Claims contract is configured with) Offers name their registry; for receipts the Claims contract uses the registry it serves, and also requires offer.registry to be that registry. The buyer SDK and the middleware pass the binding\u2019s registry address for receipts. What exactly gets hashed and signed OfferReceipt message = SHA-256( tag \u2016 network_id \u2016 XDR(registry) \u2016 XDR(body) ) tag 'LOPW/OFFER/v1' (ASCII)13 bytes network_id = SHA-256(network passphrase)32 bytes ScVal XDR of the registry address40 bytes ScVal XDR of the offer body (no signature)464 bytes \u2193 SHA-256message / offer_hash (32 bytes)324ef666a1b8cefea457e24eaf0fcc583ae711242d974e20b4eb74c7b44d4384 \u2193 Ed25519.sign(service signing key)signature (64 bytes)37a1c624c506953fecd0d76c6411c662ef3cac44bca1fb6e7b1817d787e2825b90915803a62fccbd6e0f28193e0d4c8a102092c9f84816cdd7d1cb10e3b9c108 message = SHA-256( tag \u2016 network_id \u2016 XDR(registry) \u2016 XDR(body) ) tag 'LOPW/RECEIPT/v1' (ASCII)15 bytes network_id = SHA-256(network passphrase)32 bytes ScVal XDR of the registry address40 bytes ScVal XDR of the receipt body (no signature)912 bytes \u2193 SHA-256message / receipt_hash (32 bytes)781d98c00ebc497c1fe9b3272ce02b1fae56e87c3a74083b454bad1883af429c \u2193 Ed25519.sign(service signing key)signature (64 bytes)05df38bb643802c3609ebc227d2591e9f42645a1e90fbfb92edde4439d06a4ac4f1094b33d1a7a8500e71d32404d4a9876c70f9f26b204ef0e6928ce54e0fd07 Select a segment to see its bytes. Byte-by-byte layout of the offer body (464 bytes, from the shared test vectors)Offset Len Bytes Meaning 0x0000 4 00000011 type ScvMap (17) 0x0004 4 00000001 optional ScMap present (1) 0x0008 4 0000000a map entries 10 (keys in sorted order) 0x000c 4 0000000f key type ScvSymbol (15) 0x0010 4 00000005 length 5 0x0014 8 6173736574000000 'asset' + 3 pad 0x001c 4 00000012 value type ScvAddress (18) 0x0020 4 00000001 address type contract (1) 0x0024 32 5045cd5ec0729a768fd5ad02505852df\u2026 32-byte key / contract id 0x0044 4 0000000f key type ScvSymbol (15) 0x0048 4 0000000b length 11 0x004c 12 6b65795f76657273696f6e00 'key_version' + 1 pad 0x0058 4 00000003 value type ScvU32 (3) 0x005c 4 00000001 u32 value 1 0x0060 4 0000000f key type ScvSymbol (15) 0x0064 4 00000009 length 9 0x0068 12 6d61785f636c61696d000000 'max_claim' + 3 pad 0x0074 4 0000000a value type ScvI128 (10) 0x0078 16 00000000000000000000000002faf080 i128 value 50000000 (hi 8 bytes, lo 8 bytes) 0x0088 4 0000000f key type ScvSymbol (15) 0x008c 4 0000000b length 11 0x0090 12 6f666665725f6e6f6e636500 'offer_nonce' + 1 pad 0x009c 4 0000000d value type ScvBytes (13) 0x00a0 4 00000020 length 32 0x00a4 32 22222222222222222222222222222222\u2026 2222222222222222222222222222222222222222222222222222222222222222 0x00c4 4 0000000f key type ScvSymbol (15) 0x00c8 4 00000005 length 5 0x00cc 8 7072696365000000 'price' + 3 pad 0x00d4 4 0000000a value type ScvI128 (10) 0x00d8 16 000000000000000000000000000186a0 i128 value 100000 (hi 8 bytes, lo 8 bytes) 0x00e8 4 0000000f key type ScvSymbol (15) 0x00ec 4 00000008 length 8 0x00f0 8 7265676973747279 'registry' 0x00f8 4 00000012 value type ScvAddress (18) 0x00fc 4 00000001 address type contract (1) 0x0100 32 346c4ca8305685f347e061e8fc049df3\u2026 32-byte key / contract id 0x0120 4 0000000f key type ScvSymbol (15) 0x0124 4 0000000a length 10 0x0128 12 736572766963655f69640000 'service_id' + 2 pad 0x0134 4 0000000d value type ScvBytes (13) 0x0138 4 00000020 length 32 0x013c 32 11111111111111111111111111111111\u2026 1111111111111111111111111111111111111111111111111111111111111111 0x015c 4 0000000f key type ScvSymbol (15) 0x0160 4 0000000a length 10 0x0164 12 7465726d735f686173680000 'terms_hash' + 2 pad 0x0170 4 0000000d value type ScvBytes (13) 0x0174 4 00000020 length 32 0x0178 32 7261e3aa021d521d86d07e46dfc96d43\u2026 7261e3aa021d521d86d07e46dfc96d43978de8bff84651d71083d77a49514d35 0x0198 4 0000000f key type ScvSymbol (15) 0x019c 4 0000000b length 11 0x01a0 12 76616c69645f756e74696c00 'valid_until' + 1 pad 0x01ac 4 00000005 value type ScvU64 (5) 0x01b0 8 000000006ac07dc0 u64 value 1791000000 0x01b8 4 0000000f key type ScvSymbol (15) 0x01bc 4 00000007 length 7 0x01c0 8 76657273696f6e00 'version' + 1 pad 0x01c8 4 00000003 value type ScvU32 (3) 0x01cc 4 00000001 u32 value 1 Byte-by-byte layout of the receipt body (912 bytes, from the shared test vectors)Offset Len Bytes Meaning 0x0000 4 00000011 type ScvMap (17) 0x0004 4 00000001 optional ScMap present (1) 0x0008 4 0000000e map entries 14 (keys in sorted order) 0x000c 4 0000000f key type ScvSymbol (15) 0x0010 4 00000005 length 5 0x0014 8 6275796572000000 'buyer' + 3 pad 0x001c 4 00000012 value type ScvAddress (18) 0x0020 4 00000000 address type account (0) 0x0024 4 00000000 public key type 0 0x0028 32 2d2c0646f9a183fff28b3ec7959663e8\u2026 32-byte key / contract id 0x0048 4 0000000f key type ScvSymbol (15) 0x004c 4 0000000a length 10 0x0050 12 646174615f61735f6f660000 'data_as_of' + 2 pad 0x005c 4 00000005 value type ScvU64 (5) 0x0060 8 000000006ac05c1e u64 value 1790991390 0x0068 4 0000000f key type ScvS"},{"t":"Predicates P1 to P6","u":"/docs/protocol/predicates.html","s":"Protocol reference","d":"The six LOPW breach conditions with their exact proven conditions and edge rules (data_as_of 0, oracle tick rounding, the exact deviation test), the validator interface, and an interactive explorer.","h":[["explorer","Interactive explorer"],["p1","P1 Equivocation"],["p2","P2 Terms mismatch"],["p3","P3 Stale data"],["p4","P4 Bad status"],["p5","P5 Size limit"],["p6","P6 Oracle deviation"],["p6-edges","Edge rules"],["validator-interface","Validator interface"],["not-slashable","Not slashable"]],"x":"A predicate is one breach condition that a contract can decide from the evidence alone. Each service\u2019s terms list which predicates apply and with what limits. Each predicate is a separate validator contract with one function; the Claims contract keeps an allowlist of validators by predicate ID. ID Breach Evidence Proven when P1 Equivocation Seller signed two different receipts for one payment Two receipts Same service_id and payment_reference, receipts differ in any field P2 Terms mismatch Receipt cites the offer but different terms Offer and receipt receipt.terms_hash \u2260 offer.terms_hash P3 Stale data Data older than promised Receipt served_at \u2212 data_as_of > max_data_age P4 Bad status Charged for a status the seller promised not to charge for Receipt status \u2209 allowed_status P5 Size limit Response larger than promised Receipt response_bytes > max_response_bytes P6 Oracle deviation Served value differs from a named oracle observation beyond tolerance Receipt and the oracle Four checks ending in an exact integer deviation test P1 to P5 rest on what the seller signed: they catch a seller that contradicts itself or admits a breach in its own receipt. P6 is checked against evidence the seller does not control. Before any validator runs, the Claims contract has already verified both signatures, the offer binding, the claim window, replay and that the predicate is part of the terms (claim checks). Pick a predicate, adjust the sample receipt and terms, and see how the validator would decide. The logic is the same as the contracts and as the buyer SDK\u2019s local evaluation. Predicate explorer P1 Equivocation P2 Terms mismatch P3 Stale data P4 Bad status P5 Size limit P6 Oracle deviation Terms Receipt (sample from the test vectors) Evidence: the claimed receipt plus a second signed receipt in extra = SecondReceipt(second). The Claims contract verifies the second receipt\u2019s signature with the same key rules (including KeyRetired) before calling the validator, so the validator stays a pure function of its inputs. P1Copy proven \u21d4 second.service_id == receipt.service_id \u2227 second.payment_reference == receipt.payment_reference \u2227 second != receipt // any field differs not proven \u21d4 extra is None, or a different payment, or an identical receipt The reference services\u2019 DUPLICATE_RECEIPT fault returns a second receipt with a different response_hash under PAYMENT-RESPONSE.extensions.lopw.duplicate; the buyer SDK stores it as warranty.duplicate and attaches it to a P1 claim automatically. P2Copy proven \u21d4 receipt.terms_hash != offer.terms_hash not proven \u21d4 equal The Claims contract has already required receipt.offer_hash to equal the hash of the supplied offer and loaded the terms by offer.terms_hash, so P2 compares the terms the buyer paid under with the terms the seller claims to have served under. P3Copy not proven \u21d4 data_as_of == 0 // \"not applicable\" never proves a breach \u2228 data_as_of > served_at // no negative ages proven \u21d4 served_at - data_as_of > max_data_age Edge rule: data_as_of = 0 means not applicable. Without this rule served_at \u2212 0 would always exceed any limit. A seller that promises P3 and reports 0 is not slashable through P3, but the buyer SDK treats a P3 service reporting 0 as an untrusted response, so policy-driven agents stop paying it. P4Copy proven \u21d4 status \u2209 terms.allowed_status not proven \u21d4 status \u2208 terms.allowed_status P5Copy proven \u21d4 response_bytes > terms.max_response_bytes not proven \u21d4 response_bytes <= terms.max_response_bytes P6 compares the value the seller served with one specific past observation of a SEP-40 price oracle, never with the price at claim time. The terms name the oracle contract, the asset as the oracle names it, a tolerance in basis points and a maximum observation age. Seller side. Before quoting, the middleware reads the oracle\u2019s last completed observation (lastprice) and records the timestamp the oracle returned. The receipt carries that timestamp, the oracle price seen and the value served, in the oracle\u2019s own decimals. Validator checks, in order: terms.oracle must be Some (else not proven) and receipt.oracle must be Some (else not verifiable). If the evidence names another oracle_contract or oracle_asset than the terms, the breach is proven as a terms mismatch. The validator calls price(oracle_asset, oracle_timestamp) on the oracle. No observation, or a price of zero or less, is not verifiable. The returned observation\u2019s own timestamp must not be after served_at, and served_at \u2212 observed.timestamp must not exceed max_oracle_age; otherwise the breach is proven as stale. The deviation test uses exact integer math with no division, so no rounding can hide a breach: P6 deviation testCopy proven \u21d4 |reported_value \u2212 oracle_price| \u00d7 10000 > max_deviation_bps \u00d7 oracle_price not proven \u21d4 otherwise // oracle_price is the value the validator read, not the receipt's field Tick rounding. price(asset, t) rounds t down to the previous tick and returns that tick\u2019s own timestamp; t = tick + 100 and t = tick return the same record. The validator applies the age check to the returned timestamp, not the cited one, so citing an off-tick time cannot dodge a claim. Retention. The reference fiat-rates oracle keeps 24 hours of history at 5-minute resolution with 14 decimals, quoting USD per unit of the asset. A P6 claim window must be shorter than the retention, which the registry enforces with max_oracle_claim_window (1 hour). Exactness. The division form |r \u2212 p| \u00d7 10000 / p > bps rounds down and would let a 50.99 bps deviation pass a 50 bps limit; the multiplication form is used instead. Arithmetic overflow, which needs absurd values far outside any tolerance, counts as proven. Trust. The receipt\u2019s oracle_price is informational. The oracle itself is trusted: a wrong observation produces a wrong outcome. A validator or oracle call that fails outright counts as not verifiable, which pays nothing and penalises no one. Cost. A contract-to-contract oracle read from the validator carried no c"},{"t":"Claims","u":"/docs/protocol/claims.html","s":"Protocol reference","d":"The LOPW Claims contract: the seven checks in order (animated), outcomes, every error code, the payout rule, records, events and functions.","h":[["pipeline","The pipeline"],["checks","Checks, in order"],["outcomes","Outcomes"],["errors","Error codes"],["records","Claim records"],["events","Events"],["functions","Functions"]],"x":"A claim is one transaction with one outcome. Anyone may submit it; the payout always goes to the buyer named in the receipt. Evidence or preconditions that fail return an error and record nothing. When the evidence is valid, the validator decides: a proven breach pays, an unproven one is recorded as rejected. Claims contractCopy claim(predicate_id: u32, offer: SignedOffer, receipt: SignedReceipt, extra: ClaimExtra) -> Result<ClaimOutcome, ClaimError> // ClaimOutcome: Paid(amount: i128) | Rejected(reason: u32) reason 1 = NotProven, 2 = NotVerifiable The claim pipeline Scenario Breach proven: claim paidEvidence valid, breach not provenOracle has no observationFiled after the claim windowSecond claim for a paid paymentReceipt signed by a retired keyEpoch cap reachedPlayStepReset VersionBoth offer.version and receipt.version equal 1 fails with: UnsupportedVersion 1 \u00b7 ServiceThe service exists in the registry and is not Closed fails with: UnknownService 2 \u00b7 Receipt signatureValid for the key at receipt.key_version, and that key was not retired before served_at fails with: BadSignature \u00b7 KeyRetired 3 \u00b7 Offeroffer.registry is this registry, service_id matches, receipt.offer_hash equals the offer hash, offer signature valid fails with: OfferMismatch \u00b7 BadSignature 4 \u00b7 Claim windowTerms found for offer.terms_hash; served_at \u2264 now \u2264 served_at + claim_window fails with: OfferMismatch \u00b7 WindowClosed 5 \u00b7 Not paid beforeNo paid claim recorded for (service_id, payment_reference) fails with: AlreadyClaimed 6 \u00b7 Predicate and stakepredicate_id is in the terms; stake is above zero; for P1 the second receipt\u2019s signature is verified too fails with: PredicateNotOffered \u00b7 NoStake 7 \u00b7 ValidatorThe validator for the predicate returns proven fails with: Rejected(NotProven) \u00b7 Rejected(NotVerifiable) \u00b7 ValidatorNotSet PayoutRecord the claim, then slash(service_id, buyer, max_claim) on the registry fails with: SlashRateLimited \u00b7 NoStake Version. offer.version and receipt.version are both 1; else UnsupportedVersion. Service. get_service(receipt.service_id) succeeds and the state is not Closed; else UnknownService. Receipt signature. The key at receipt.key_version exists (else BadSignature); it was not retired before served_at (else KeyRetired); the Ed25519 signature over receipt_hash verifies (else the transaction traps). Offer. offer.registry is this registry and offer.service_id == receipt.service_id and receipt.offer_hash == offer_hash(offer); else OfferMismatch. The key at offer.key_version exists (else BadSignature) and the offer signature verifies. Claim window. get_terms(service_id, offer.terms_hash) succeeds (else OfferMismatch); served_at \u2264 now and now \u2264 served_at + claim_window; else WindowClosed. Not paid before. No paid record exists for (service_id, payment_reference); else AlreadyClaimed. Predicate and stake. predicate_id is in terms.predicates (else PredicateNotOffered); stake > 0 (else NoStake). For P1, the second receipt in extra passes the same signature and key-retirement checks. Validator. A validator is set for the predicate (else ValidatorNotSet). Ok(()) is proven; Err(NotProven) is Rejected(1); any other failure is Rejected(2). Rejections emit ClaimRejected and return Ok(Rejected(reason)). Payout. The claim record is written first, then slash(service_id, receipt.buyer, offer.max_claim) is called on the registry, which caps at the remaining stake (and at max_payout if set) and applies the slash rate limit. EpochCapReached from the registry returns SlashRateLimited, which rolls the record back so the claim can be retried; any other slash failure returns NoStake. On success the record is updated with the amount, ClaimPaid is emitted and Ok(Paid(amount)) returned. Result Recorded? Event Meaning Ok(Paid(amount)) Yes, under (service_id, payment_reference) ClaimPaid Breach proven; amount transferred to receipt.buyer from the service\u2019s stake in this transaction Ok(Rejected(1)) Event only ClaimRejected reason 1 Evidence valid, validator did not prove a breach (NotProven). A later valid claim for the same payment is still possible Ok(Rejected(2)) Event only ClaimRejected reason 2 The breach could not be checked (NotVerifiable): no oracle evidence or observation, or the validator failed Err(ClaimError) No none Evidence or preconditions failed; the transaction fails and nothing changes Payout rule. payout = min(offer.max_claim, stake remaining), further capped by the launch limit max_payout when it is non-zero, and subject to the slash rate limit (at most 50% of epoch-start stake per epoch, first slash of an epoch always allowed). Recording the claim before slashing means a payout can never be made twice for one payment, even if the slash fails part-way. Code Error Cause 1 UnknownService No such service, or it is Closed 2 BadSignature No signing key at the cited key_version (a signature that does not verify traps the transaction instead). No penalty to the seller 3 OfferMismatch Offer names another registry or service, the receipt does not cite the supplied offer, or the offer\u2019s terms are unknown 4 WindowClosed Claim window has passed, or served_at is in the future 5 AlreadyClaimed A claim was already paid for this payment 6 PredicateNotOffered The terms do not include this predicate 7 NoStake Stake is zero, or the registry refused the slash for a reason other than the rate limit 8 ValidatorNotSet No validator is registered for the predicate 9 KeyRetired The signing key was retired at or before the receipt\u2019s served_at 10 UnsupportedVersion Offer or receipt version is not 1 11 SlashRateLimited The epoch cap is reached; retry in the next epoch while the window is open 12 NoPendingChange Admin: no validator change is pending (apply or cancel) 13 ChangeNotReady Admin: the pending validator change\u2019s time lock has not passed 14 InvalidPredicate Admin: predicate ID outside 1 to 6 The TypeScript binding maps these codes to names (CLAIM_ERRORS), so a failed submission reads as WindowClosed (#4). ClaimRecord field M"},{"t":"Bond Registry","u":"/docs/protocol/bond-registry.html","s":"Protocol reference","d":"The LOPW Bond Registry: every function, event and error, the five service states with an animated state machine, protocol floors, launch limits, fees, time locks, the slash rate limit and the unbonding maths with a calculator.","h":[["states","Service states"],["functions","Functions"],["rules","Rules per function"],["errors","Error codes"],["events","Events"],["floors","Protocol floors"],["safeguards","Safeguards"],["slash-rate-limit","Slash rate limit"],["unbonding","Unbonding maths"],["calculator","Calculator"],["fees","Protocol fees"],["limits","Guarded launch: limits, pause, allowlist"],["time-locks","Time locks"],["storage","Storage lifetime"]],"x":"Stake is slashable seller capital, held per service in a single stake token per registry deployment (USDC on the first deployment). It is not per-call insurance: a service\u2019s total payouts can never exceed its stake. The registry also stores every terms version and every signing key a service has ever had. Service states and transitions PlayStepRestart Service state machine of the Bond Registrydeposit reaches min_stakewithdraw (no delay)slash or fee takes stake below min_stakedeposit restores min_stakerequest_unbond that would leave stake below min_stakerequest_unbond (full exit)cancel_unbond, stake at or above min_stakecancel_unbond, stake below min_stakewithdraw after the unbonding delayrequest_unbond of stake above min_stake (partial, stays Active)PendingActiveSuspendedUnbondingClosed Press Play to follow one service from registration to closure, or choose a state to see where it can go. State Meaning Offers Claims paid Pending Registered; stake has not yet reached min_stake No No receipts can exist; slash refuses Active Stake is at or above min_stake Yes Yes Suspended A slash or a fee took stake below min_stake No Yes, while stake remains Unbonding A full exit is waiting out the delay No Yes Closed Stake was withdrawn; final No No The registry sets the state during the slash, so suspension is automatic. The seller middleware issues offers only while Active, and the buyer SDK pays only Active services. Every change emits StateChanged { service_id, from, to }. Bond Registry interfaceCopy // seller register_service(seller, signing_key, terms, min_stake) -> Result<service_id, Error> update_terms(service_id, terms) -> Result<terms_hash, Error> // not while Unbonding or Closed rotate_key(service_id, signing_key) -> Result<key_version, Error> // not while Closed deposit(service_id, amount) -> Result<(), Error> request_unbond(service_id, amount) -> Result<(), Error> cancel_unbond(service_id) -> Result<(), Error> withdraw(service_id) -> Result<amount, Error> // claims contract only slash(service_id, buyer, amount) -> Result<paid, Error> // anyone collect_fee(service_id) -> Result<fee, Error> extend_service_ttl(service_id) -> Result<(), Error> extend_instance_ttl() apply_claims_contract() -> Result<Address, Error> apply_limits() -> Result<LaunchLimits, Error> apply_fees() -> Result<FeeConfig, Error> // reads get_service(service_id) -> Result<Service, Error> get_terms(service_id, terms_hash) -> Result<Terms, Error> get_key(service_id, key_version) -> Result<SigningKey, Error> terms_hash(terms) -> BytesN<32> get_config() -> ProtocolConfig get_token() -> Address get_claims_contract() -> Option<Address> get_pending_claims_contract() -> Option<PendingChange> get_limits() -> LaunchLimits get_pending_limits() -> Option<PendingLimits> get_fees() -> FeeConfig get_pending_fees() -> Option<PendingFees> is_paused() -> bool is_seller_allowed(seller) -> bool get_total_stake() -> i128 // admin set_claims_contract(claims) -> Result<eta, Error> cancel_claims_change() set_validator lives on the Claims contract set_limits(limits) -> Result<eta, Error> cancel_limits_change() set_fees(fees) -> Result<eta, Error> cancel_fees_change() set_paused(paused) set_seller_allowed(seller, allowed) Every public function returns a typed result, so generated clients, apps and agents receive the named error code rather than an opaque host failure. Function Requires Effect register_service Seller auth; not paused; seller allowlisted if allowlist_only; min_stake \u2265 min_stake_floor; valid terms Pays the registration fee to the treasury if any; derives service_id; stores terms version and key version 1; state Pending; max_claim_window = terms.claim_window; emits ServiceRegistered update_terms Seller auth; state not Unbonding or Closed; valid terms Stores a new terms version and makes it current; raises max_claim_window if larger; old versions stay readable; emits TermsUpdated rotate_key Seller auth; not Closed Old key gets retired_at = now; new key at version + 1; emits KeyRotated deposit Seller auth; amount > 0; not Closed; not paused; within launch caps Collects accrued fee; transfers; Pending or Suspended become Active when stake reaches the minimum; emits Deposited request_unbond Seller auth; Active or Suspended; no withdrawal pending; 0 < amount \u2264 stake Collects fee; partial if stake \u2212 amount \u2265 min_stake (stays Active) else full exit (whole stake pending, state Unbonding); unlock_at = now + max_claim_window + unbond_margin; emits UnbondRequested cancel_unbond Seller auth; a withdrawal pending Clears it; Unbonding returns to Active (stake \u2265 min) or Suspended; emits UnbondCancelled withdraw Seller auth; Pending, or a pending withdrawal past unlock_at Collects fee; Pending pays the whole stake and closes; full exit pays the whole stake and closes; partial pays min(pending, stake) and suspends if that leaves stake below the minimum; emits Withdrawn slash Claims contract auth; state not Pending or Closed; amount > 0; stake > 0 Collects fee; payout = min(amount, stake, max_payout?); applies the rate limit; reduces stake and any pending withdrawal; suspends if below the minimum; transfers to the buyer; emits Slashed collect_fee Service exists Collects the stake fee accrued since the last collection; may suspend; emits FeeCollected Code Error Cause 1 AlreadyInitialized Constructor called twice 2 ClaimsNotSet slash before a Claims contract is wired 3 UnknownService No such service 4 UnknownTerms No terms version with that hash for the service 5 UnknownKey No key at that version 6 InvalidTerms Terms break a validity rule 7 StakeBelowFloor min_stake under the protocol floor 8 InvalidAmount Zero, negative or above stake 9 WrongState The function is not allowed in the service\u2019s state 10 WithdrawalPending A withdrawal is already pending 11 NoWithdrawalPending Nothing to cancel or withdraw 12 StillLocked unlock_at not reached 13 NoStake Stake is zero 14 EpochCapReached Slash would exceed the epoch cap (surfaces as SlashRateLimited on a claim) 1"},{"t":"x402 extension","u":"/docs/protocol/x402.html","s":"Protocol reference","d":"The LOPW x402 v2 extension on the wire: where the signed offer and receipt travel, the exact JSON shapes, the buyer echo, the handler headers, and why verify and settle are untouched.","h":[["offer-wire","The offer in the 402"],["echo","The buyer's echo"],["receipt-wire","The receipt in PAYMENT-RESPONSE"],["handler-headers","Handler inputs"],["why-own-format","Why LOPW keeps its own receipt format"],["sdk-surface","SDK surface on the wire"]],"x":"LOPW is an x402 v2 extension named lopw. It changes nothing in verify or settle: any conformant facilitator works, and a buyer that ignores the extension still gets a normal x402 purchase. Checked against x402 2.28.0. Item Carried in Warranty offer The 402 PaymentRequired.extensions.lopw: { info: { version, network, registry, service_id, offer }, schema }. The buyer echoes it in PaymentPayload.extensions.lopw Payment Standard x402 exact scheme, unchanged Service receipt The settlement response PAYMENT-RESPONSE.extensions.lopw: { receipt }, plus duplicate only in the DUPLICATE_RECEIPT fault mode of reference services HTTPCopy HTTP/1.1 402 Payment Required // PaymentRequired (x402 v2 envelope), with: \"extensions\": { \"lopw\": { \"info\": { \"version\": 1, \"network\": \"testnet\", // the binding's network name \"registry\": \"C\u2026\", // Bond Registry contract address \"service_id\": \"\u202664 hex\u2026\", \"offer\": \"AAAAEQAAAAEAAAAK\u2026\" // base64 ScVal XDR of SignedOffer { offer, signature } }, \"schema\": { \"type\": \"object\" } } } offer is declared a dynamic info field (dynamicInfoFields: [\"offer\"]) so per-request nonces do not break x402\u2019s echo validation. The middleware issues an offer only when the service is Active and only for the payment requirement whose network matches the binding\u2019s CAIP-2 id; otherwise the 402 carries plain x402 terms. HTTPCopy // PaymentPayload (sent by the buyer with the payment), with: \"extensions\": { \"lopw\": { \"info\": { \u2026the same info object\u2026 }, \"schema\": { \"type\": \"object\" } } } The buyer SDK copies extensions.lopw from the requirements into the payload (enrichPaymentPayload). The seller only issues receipts for offers it signed itself: it decodes the echoed offer and verifies the signature with its own key before building the receipt. HTTPCopy HTTP/1.1 200 OK PAYMENT-RESPONSE: <base64 settlement response> // decoded settlement response, with: \"success\": true, \"transaction\": \"\u202664 hex\u2026\", \"payer\": \"G\u2026\", \"extensions\": { \"lopw\": { \"receipt\": \"AAAAEQAAAAEAAAAO\u2026\", // base64 ScVal XDR of SignedReceipt { receipt, signature } \"duplicate\": \"\u2026\" // optional; reference services' fault injection only } } The receipt is attached through x402\u2019s enrichSettlementResponse hook, after the facilitator reports a successful settlement, using the settlement transaction as payment_reference and the payer as buyer. It travels in the standard x402 envelope every client already decodes (decodePaymentResponseHeader). Response header Read by the middleware as LOPW-Data-As-Of receipt.data_as_of (Unix seconds; absent means 0) LOPW-Oracle-Evidence receipt.oracle: JSON with oracle_timestamp, oracle_price, reported_value as decimal strings; the oracle contract and asset come from the terms The middleware reads the HTTP status, the raw request (method, original URL, raw body) and the response body through request-scoped context (lopwRequestContext()) and x402\u2019s transport context. The two headers are ordinary response headers that a client may read as well. x402 ships a generic offer-receipt extension that signs JWS or EIP-712 payloads with resource URL, payer and transaction, but no terms, request or response hashes, or oracle evidence, and a Soroban contract cannot cheaply verify JWS. LOPW receipts must be verifiable on-chain, so LOPW keeps XDR plus Ed25519. The two extensions can be served side by side. Side Package Exports Seller @lopw/seller-middleware createLopwExtension(config) (returns a ResourceServerExtension with refresh()), lopwRequestContext(), LOPW_EXTENSION_KEY, DATA_AS_OF_HEADER, ORACLE_EVIDENCE_HEADER, types LopwSellerConfig, LopwOfferInfo, LopwReceiptInfo, ReceiptFaults Buyer @lopw/buyer-sdk LopwBuyer, BuyerPolicy, WarrantyRecord, WarrantedResponse, ReceiptStore, FileReceiptStore, MemoryReceiptStore, WarrantyRefused, LOPW_EXTENSION_KEY Both @lopw/protocol Types, Predicate, XDR encoders, offerHash/receiptHash/termsHash/requestHash/responseHash, sign and verify, base64 codecs, evaluatePredicate/breaches, StellarBinding, getNetwork, CLAIM_ERRORS "},{"t":"HTTP API","u":"/docs/protocol/api.html","s":"Protocol reference","d":"The read-only LOPW HTTP API: every public endpoint, parameters, response shapes, pagination, caching, errors and the Server-Sent Events stream, with the OpenAPI 3.1 document.","h":[["endpoints","Endpoints"],["schemas","Schemas"],["pagination","Pagination, caching, limits, errors"],["stream","Live stream"]],"x":"Read-only records of bonded services, warranty claims and protocol events, built for third-party apps, mobile clients and autonomous agents. The chain is the source of truth; the API is an indexed, cacheable view of it. OpenAPI 3.1 document: /docs/api/openapi.json (also served by the API itself at /v1/openapi.json). Base URL: https://api.lopw.com. Method and path Returns Parameters GET /healthz { status, indexed_ledger, indexed_at }; 503 with a problem when unhealthy \u2014 GET /v1/openapi.json This document \u2014 GET /v1/{network}/stats Stats: network-wide totals network GET /v1/{network}/services { data: Service[], next_cursor } state, cursor, limit GET /v1/{network}/services/{id} Service id: 64 hex GET /v1/{network}/services/{id}/claims { data: Claim[], next_cursor }, newest first outcome, cursor, limit GET /v1/{network}/services/{id}/events { data: Event[], next_cursor }, newest first cursor, limit GET /v1/{network}/claims { data: Claim[], next_cursor }, newest first outcome, cursor, limit GET /v1/{network}/stream text/event-stream of contract events after (or the Last-Event-ID header) GET /v1/{network}/scenarios Scenario[] with plain-language notes and recent runs runs 0 to 100, default 5 GET /v1/rates Exchange-rate snapshot with exact cross rates base, symbols, date network is testnet or mainnet. limit is 1 to 200, default 50. Operator-only endpoints under /v1/admin/ are not public. Schema Fields Service service_id, seller, state, stake, min_stake, pending_withdrawal, terms_hash, key_version, claims { paid, rejected, total_paid }, registered_at, updated_at Claim id, service_id, predicate_id 1 to 6, outcome paid | rejected, reason not_proven | not_verifiable | null, buyer, amount, payment_reference, transaction, ledger, occurred_at Event id, kind (for example service_registered, deposited, slashed, state_changed, claim_paid, claim_rejected), service_id, contract_id, transaction, ledger, topics, data, occurred_at Stats services, active_services, total_stake, claims_paid, claims_rejected, total_paid, indexed_ledger Scenario id, title, summary, why, steps[], expected, service, totals { runs, passed }, runs[] with outcome pass | fail, observed, payment_tx, claim_tx, payout, stake_before, stake_after Problem type, title, status, code (stable, mirrors contract error names where applicable), detail Amounts are decimal strings in the stake asset\u2019s smallest unit (USDC: 7 decimals); 32-byte values are lowercase hex; times are Unix seconds; state is one of Pending, Active, Suspended, Unbonding, Closed. Pagination: pass the previous page\u2019s next_cursor as cursor; null means the end. Caching: reads carry ETag; send If-None-Match to receive 304 Not Modified. Rate limits: advertised with RateLimit-* headers; exceeding them returns 429 with Retry-After. Errors: application/problem+json with status 400, 404 or 429 and a machine-readable code. CORS: open on read endpoints; no cookies. HTTPCopy GET /v1/testnet/stream Accept: text/event-stream id: 00000000000000123 event: claim_paid data: { \"id\": \"\u2026\", \"kind\": \"claim_paid\", \"service_id\": \"\u2026\", \"contract_id\": \"C\u2026\", \"transaction\": \"\u2026\", \"ledger\": 5021500, \"topics\": [ \u2026 ], \"data\": { \u2026 }, \"occurred_at\": 1790991420 } Each message has id (event ID), event (the kind) and data (an Event). Resume after a disconnect with the Last-Event-ID header or ?after=<id>. Monitoring is built on this stream plus direct chain reads. "},{"t":"Chain bindings","u":"/docs/protocol/bindings.html","s":"Protocol reference","d":"What a LOPW chain binding supplies, and the Stellar binding in detail: Soroban contracts, USDC stake, x402 exact payments, SEP-40 oracles, ScVal XDR and Ed25519.","h":[["stellar","The Stellar binding"],["binding-contract","What a new binding must provide"]],"x":"The portable core of LOPW is the warranty offer, the service receipt, the validator interface and the claim semantics. A chain binding supplies the parts that depend on a chain: the encoding, the signature scheme, the contract functions and the payment-rail binding. Binding Contracts Stake Payments Oracle (P6) Encoding and signatures Stellar (reference binding) Soroban: Bond Registry, Claims, six validators USDC Stellar Asset Contract, 7 decimals (one SEP-41 token per registry deployment) x402 exact, SEP-41 transfer SEP-40 (Reflector) ScVal XDR; Ed25519 over a domain-separated SHA-256 EVM (Base first, then Ethereum, Arbitrum, Optimism, Polygon, Avalanche) Solidity: the same three kinds of contract Native USDC, 6 decimals x402 exact, EIP-3009 transferWithAuthorization Chainlink getRoundData(roundId) ABI encoding; secp256k1 over EIP-712 typed data (keccak-256) Solana (planned) Anchor programs USDC SPL, 6 decimals x402 exact, SPL transfer Pyth price update verified in-program Borsh; Ed25519 over a domain-separated SHA-256 One protocol, the same semantics on every chain. Each binding adds its own contracts and SDK adapter implementing the same ChainBinding shape without changing the offer, receipt, predicate or claim semantics. Per-network details (chain ids, CAIP-2 ids, token and oracle addresses, deployed contracts) are in the Networks section. Aspect Stellar binding Addresses Strkeys: G... accounts, C... contracts Network ID SHA-256 of the network passphrase (Test SDF Network ; September 2015 on testnet; Public Global Stellar Network ; September 2015 on mainnet) CAIP-2 id for x402 stellar:testnet, stellar:pubnet Read path Read-only calls are simulated through Stellar RPC from a throwaway account that never signs Write path invoke: simulate, assemble, sign, send, poll; contract errors are decoded to names Ledger time Trails wall-clock time by up to one ledger; the SDK waits before submitting a claim so it never lands in a ledger closed before served_at Storage Persistent entries with TTL extended on access; instance storage for contract configuration TypeScriptCopy import { StellarBinding, STELLAR_TESTNET, STELLAR_MAINNET, getNetwork, withContracts } from \"@lopw/protocol\"; const binding = new StellarBinding(STELLAR_TESTNET); // contract addresses included const main = withContracts(STELLAR_MAINNET, { rpcUrl, registry, claims }); // supply your own for a mainnet deployment const net = getNetwork(); // LOPW_NETWORK + env overrides StellarNetwork field Meaning name testnet or mainnet; used in API paths caip2 CAIP-2 id used by x402 passphrase Network passphrase hashed into every signature rpcUrl Stellar RPC endpoint registry, claims Contract addresses usdc The USDC Stellar Asset Contract oracle The SEP-40 fiat-rates oracle used by the reference services Contract addresses of the current deployment are listed on the proofs page and inside STELLAR_TESTNET. A canonical, deterministic encoding of Terms, Offer, Receipt and the oracle blocks that the contracts and SDKs produce byte for byte, plus a test-vector file in the style of v1.json. A signature scheme over the domain-separated message (type tag, network identifier, registry identifier, encoded body). Contracts with the registry, claims and validator functions, the same states, checks, outcomes and error semantics. A payment-rail binding that yields a settlement reference and payer for the receipt, and carries the offer and receipt alongside the payment. An oracle interface for P6 that returns a past observation with its own timestamp. "},{"t":"Networks","u":"/docs/networks/","s":"Networks","d":"LOPW is one protocol with the same semantics on every chain. What a chain binding is, what differs per chain (signatures, hashing, token, oracle, x402 scheme), a comparison table, and the networks: Stellar as the reference binding, the EVM networks, and the planned Solana binding.","h":[["what-is-a-binding","What a chain binding is"],["comparison","The bindings side by side"],["networks","The networks"],["names","Network names and CAIP-2 ids"],["agents","For agents"]],"x":"LOPW is a universal protocol: the warranty offer, the service receipt, predicates P1 to P6, the claim checks and outcomes, the service states, floors, fees and time locks mean exactly the same thing on every chain. A chain binding is everything one chain needs to carry those semantics, and nothing in a binding may change what a predicate means or when a claim pays. One protocol, bound to each chain PlayPreviousNextRestart One LOPW protocol at the centre, bound to Stellar, six EVM networks and a planned Solana bindingStellarreferenceBaseEthereumArbitrumOptimismPolygonAvalancheSolanaLOPWportable core Stellar: the reference bindingSoroban contracts in Rust. Ed25519 signatures over SHA-256(tag \u2016 network_id \u2016 XDR(registry) \u2016 XDR(body)). Stake in the USDC Stellar Asset Contract (7 decimals). P6 reads a Reflector SEP-40 oracle at a cited timestamp. x402 exact with a SEP-41 transfer. Live on Stellar testnet. Base: the first EVM networkThe Solidity binding is built against Base Sepolia first: EIP-712 typed data signed with secp256k1, native USDC (6 decimals), Chainlink getRoundData(roundId) for P6, x402 exact with EIP-3009. Ethereum: same bindingEthereum mainnet and Sepolia run the same Solidity contracts; only the chain id in the EIP-712 domain, the USDC address and the feed addresses change. Arbitrum: same bindingArbitrum One and Arbitrum Sepolia: unchanged contracts and SDK, Arbitrum\u2019s native USDC and Chainlink feeds. Optimism: same bindingOP Mainnet and OP Sepolia: the same OP Stack as Base, so the binding behaves identically. Polygon: same bindingPolygon PoS and Amoy: native USDC is the stake and payment token; everything else is the shared binding. Avalanche: same bindingThe Avalanche C-Chain and Fuji: EVM-compatible, native USDC, Chainlink feeds; the binding applies unchanged. Solana: a planned bindingAnchor programs, Ed25519 over a domain-separated SHA-256 of Borsh bytes, USDC SPL (6 decimals), a Pyth price update verified in-program, x402 exact with an SPL transfer. A design; nothing is deployed. The protocol is specified once. A binding supplies the carriers: how bytes are encoded and hashed, which signature scheme the service signing key uses, which token holds the stake, which oracle P6 reads and how it cites an observation, how x402 settles the payment, and how the signed offer and receipt are encoded inside the lopw extension. Everything a buyer or seller reasons about (terms, stake, states, payout, replay protection) is identical. Same everywhere Differs per chain Offer, receipt and terms fields and their meaning Encoding of those fields (ScVal XDR, ABI, Borsh) and the hash function Predicates P1 to P6 and their proven / not proven / not verifiable rules Signature scheme of the service signing key (Ed25519, secp256k1) The seven claim checks, outcomes, error names and events Stake token contract and its decimals (USDC with 7 or 6 decimals) Service states, floors, launch limits, fees, slash rate limit, time locks Oracle interface and how an observation is cited (timestamp, round id, price update) request_hash and response_hash (SHA-256, identical construction) x402 scheme details and the settlement reference used as payment_reference The ChainBinding interface the SDKs call Wire encoding of SignedOffer and SignedReceipt in extensions.lopw Binding Contracts Signatures Hashing and encoding Stake token P6 oracle x402 scheme Wire form of the lopw extension Stellar reference Soroban, Rust Ed25519 SHA-256 over ScVal XDR, domain-separated USDC Stellar Asset Contract (7 decimals) Reflector, SEP-40 price(asset, timestamp) exact, SEP-41 transfer with sponsored fees base64 ScVal XDR EVM: Base first, then Ethereum, Arbitrum, Optimism, Polygon, Avalanche Solidity, Foundry secp256k1 over EIP-712 typed data keccak-256, EIP-712 hashTypedDataV4 Native USDC, ERC-20 (6 decimals) Chainlink AggregatorV3Interface.getRoundData(roundId) exact, EIP-3009 transferWithAuthorization 0x-hex ABI encoding Solana planned Anchor, Rust Ed25519 SHA-256 over Borsh, domain-separated USDC SPL (6 decimals) Pyth price update verified in-program exact, SPL transfer Borsh (design) Amounts are always strings of the token\u2019s smallest unit on the wire; the API and SDKs never use floating point. Floors are the same value expressed in each token\u2019s decimals: the 100 USDC minimum-stake floor is 1_000_000_000 units on Stellar and 100_000_000 units on EVM networks. Stellar StellarReference bindingSoroban \u00b7 Ed25519 \u00b7 ScVal XDR \u00b7 USDC SAC (7 decimals) \u00b7 Reflector SEP-40 \u00b7 x402 exact, SEP-41. Live on Stellar testnet with the contract addresses on the page. EVM networks (one Solidity binding) BaseBase firstbase-sepolia \u00b7 base \u00b7 chain ids 84532 and 8453Ethereumsepolia \u00b7 ethereum \u00b7 chain ids 11155111 and 1Arbitrumarbitrum-sepolia \u00b7 arbitrum \u00b7 chain ids 421614 and 42161Optimismoptimism-sepolia \u00b7 optimism \u00b7 chain ids 11155420 and 10Polygonpolygon-amoy \u00b7 polygon \u00b7 chain ids 80002 and 137Avalancheavalanche-fuji \u00b7 avalanche \u00b7 chain ids 43113 and 43114 Planned SolanaPlanned bindingAnchor \u00b7 Ed25519 over Borsh \u00b7 USDC SPL (6 decimals) \u00b7 Pyth \u00b7 x402 exact, SPL transfer. A design; no program is deployed. API paths and documentation use these network names; the {network} segment of /v1/{network}/... is one of them. The CAIP-2 id is what x402 uses in PaymentRequirements.network. Name CAIP-2 Chain Binding LOPW deployment testnet stellar:testnet Stellar testnet Stellar Deployed mainnet stellar:pubnet Stellar mainnet Stellar None published base-sepolia eip155:84532 Base Sepolia EVM None published base eip155:8453 Base EVM None published sepolia eip155:11155111 Ethereum Sepolia EVM None published ethereum eip155:1 Ethereum EVM None published arbitrum-sepolia eip155:421614 Arbitrum Sepolia EVM None published arbitrum eip155:42161 Arbitrum One EVM None published optimism-sepolia eip155:11155420 OP Sepolia EVM None published optimism eip155:10 OP Mainnet EVM None published polygon-amoy eip155:80002 Polygon Amoy EVM None published polygon eip155:137 Polygon PoS "},{"t":"Stellar","u":"/docs/networks/stellar.html","s":"Networks","d":"The reference LOPW binding: Soroban contracts, Ed25519 signatures, SHA-256 over domain-separated ScVal XDR, the USDC Stellar Asset Contract with 7 decimals, Reflector SEP-40 oracles, x402 exact with SEP-41, network names testnet and mainnet, and the live contract addresses on Stellar testnet.","h":[["glance","At a glance"],["networks","Networks"],["contracts","LOPW contracts"],["contracts-testnet","Stellar testnet"],["contracts-mainnet","Stellar mainnet"],["hashing","Hashing and signing, exactly"],["oracle","P6 on Stellar"],["x402","x402 on Stellar"],["sdk","SDK"],["agents","For agents"]],"x":"Stellar is the reference binding: the first chain LOPW runs on and the one the other bindings are defined against. The contracts are Soroban, signatures are Ed25519, and the shared test vectors pin every byte. The protocol semantics on this page are the same on every other chain; what is specific to Stellar is listed here. Reference binding Live on Stellar testnet Aspect Stellar binding Contracts Soroban (Rust): Bond Registry, Claims, six validators Signatures Ed25519 by a dedicated service signing key registered on-chain (key_version); it cannot move stake Hashing message = SHA-256(tag \u2016 network_id \u2016 XDR(registry) \u2016 XDR(body)) with tags LOPW/OFFER/v1 and LOPW/RECEIPT/v1; network_id = SHA-256(network passphrase) Encoding ScVal XDR of the contract types (maps with keys in sorted order), so the contracts and the SDKs hash identical bytes Stake token USDC Stellar Asset Contract (SEP-41), 7 decimals; one token per registry deployment P6 oracle Reflector, SEP-40 price(asset, timestamp); the validator reads the tick the oracle returns for the cited timestamp x402 scheme exact on stellar:testnet / stellar:pubnet, a SEP-41 transfer with sponsored fees; payment_reference is the settlement transaction hash, buyer the payer Addresses Strkeys: G... accounts, C... contracts Network names testnet and mainnet Network Name CAIP-2 Passphrase USDC Reflector oracle Explorer Stellar testnet testnet stellar:testnet Test SDF Network ; September 2015 CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA CCSSOHTBL3LEWUCBBEB5NJFC2OKFRC74OWEIJIZLRJBGAAU4VMU5NV4W stellar.expert (testnet) Stellar mainnet mainnet stellar:pubnet Public Global Stellar Network ; September 2015 CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75 CBKGPWGKSKZF52CFHMTRR23TBWTPMRDIYZ4O2P5VS65BMHYH4DXMCJZC stellar.expert (public) USDC on Stellar testnet is issued by GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5; the Stellar Asset Contract above is its Soroban interface. Stellar RPC for testnet: https://soroban-testnet.stellar.org. Deployed Current LOPW contracts on Stellar testnet, each linked to the public explorer. Contract Address Bond Registry CBNGZB5GL3FEZKIQK73BTJYCQAC76N7SZCLN34JRMTQCUWER4GB3WHPL Claims CBUBZC2AHVKFKYTAO3DIF6CZFQVM4UYCNABYVTPQNUUJGLOIBSIWONSC Validator P1 Equivocation CBEBETGU7AFDBYSP5VGUIKJ7FHLLDOG5LM56MHYWYWT4M4S7FZEZ7TU3 Validator P2 Terms mismatch CCDRQUWK5JHLNJRAMT6P37K4RS4B3SNELLZPP6LMM64IZ357MSSGSDW4 Validator P3 Stale data CDUIB7ZPIRXMUZLIXRTALEFLXMAYLLIBJ6TDN5HWDR77CPLLPIG44DVJ Validator P4 Bad status CA5FBH3RUW65UXIHU4YP2GSTQNUXHXTB4NIEVICPHHV7ISXY2VB6A2LV Validator P5 Size limit CDJGYTFQOGMY3UX7CPYLJPNZF2SPFEHJRJGY3VOOHVVIPLNS4NWL5BEE Validator P6 Oracle deviation CDPLC6G732LTARGLAKNA5TRR4DAERXKMWGWI4TS4XQ6WLHRSPHQNEWGV Treasury account GBNQ7FKN25IGBY7V7XJUVMMACLDIACYDLUNYZA56RR2RQMHW3UV2CCQ5. Registration fee 1 USDC; stake fee 100 basis points a year; admin change delay 93600 seconds (26 hours). Not deployed No LOPW deployment is published on this network yet. The Hashing and signing page shows every byte with an interactive layout taken from the test vectors. In short: TextCopy message = SHA-256( tag || network_id || ScVal-XDR(registry) || ScVal-XDR(body) ) tag = \"LOPW/OFFER/v1\" or \"LOPW/RECEIPT/v1\" (ASCII) network_id = SHA-256(network passphrase) // testnet signatures are invalid on mainnet registry = ScvAddress XDR of the Bond Registry // signatures cannot be replayed against another deployment signature = Ed25519.sign(service signing key, message) // 64 bytes terms_hash = SHA-256(ScVal-XDR(terms)) // no domain separation service_id = SHA-256(XDR(registry address) || XDR(counter)) Soroban\u2019s signature check traps on an invalid signature, so a bad signature fails the claim transaction with the same outcome as a rejection: nothing recorded, nothing paid. Terms name a SEP-40 oracle contract and an asset (Stellar(address) or Other(symbol)). The validator calls price(asset, oracle_timestamp); the oracle rounds the timestamp down to its previous tick and returns that tick\u2019s price and timestamp. The validator applies max_oracle_age to the returned timestamp, rejects a tick after served_at, and proves a breach with the exact integer test |reported_value \u2212 oracle_price| \u00d7 10000 > max_deviation_bps \u00d7 oracle_price. The Reflector fiat-rates oracle quotes USD per unit with 14 decimals, ticks every 5 minutes and keeps 24 hours of history, which is why a P6 claim window is at most 1 hour. 402 PaymentRequired, StellarCopy \"extensions\": { \"lopw\": { \"info\": { \"version\": 1, \"network\": \"testnet\", \"registry\": \"CBNGZB5GL3FEZKIQK73BTJYCQAC76N7SZCLN34JRMTQCUWER4GB3WHPL\", \"service_id\": \"\u202664 hex\u2026\", \"offer\": \"AAAAEQAAAAEAAAAK\u2026\" // base64 ScVal XDR of SignedOffer { offer, signature } }, \"schema\": { \"type\": \"object\" } } } The receipt returns in PAYMENT-RESPONSE.extensions.lopw.receipt as base64 ScVal XDR of SignedReceipt. The full envelope is on the x402 extension page. TypeScriptCopy import { StellarBinding, STELLAR_TESTNET, STELLAR_MAINNET, getNetwork, withContracts } from \"@lopw/protocol\"; const binding = new StellarBinding(STELLAR_TESTNET); // contract addresses included const main = withContracts(STELLAR_MAINNET, { rpcUrl, registry, claims }); // supply addresses for a mainnet deployment const net = getNetwork(); // LOPW_NETWORK + env overrides Read-only calls are simulated through Stellar RPC from a throwaway account that never signs; writes are simulate, assemble, sign, send, poll, with contract errors decoded to names. Ledger time trails wall-clock time by up to one ledger, so the SDK waits before submitting a claim. For agents Item Value Network name in API paths testnet (/v1/testnet/..., served) and mainnet (no deployment published, not served) CAIP-2 id stellar:testnet, stellar:pubnet Wire encoding of extensions.lopw info.offer and receipt are base64 ScVal XDR of SignedOffer / SignedReceipt; JSON Schemas offer.json, receipt.json, terms.json Explorer https://stellar.expert/explorer/testnet/tx/{hash} and "},{"t":"EVM binding","u":"/docs/networks/evm.html","s":"Networks","d":"The LOPW binding shared by Base, Ethereum, Arbitrum, Optimism, Polygon and Avalanche: Solidity contracts, EIP-712 typed data with the exact type strings, keccak-256 termsHash, secp256k1 signatures, Chainlink getRoundData(roundId) with the round id as the citation, EIP-3009 for x402, ABI-hex wire encoding, and confirmations before indexing.","h":[["glance","At a glance"],["types","Types"],["eip712","Hashing and signing (EIP-712)"],["contracts","Contracts"],["oracle","P6 with Chainlink"],["x402","x402 on EVM"],["sdk","SDK"],["networks","Networks on this binding"],["agents","For agents"]],"x":"One Solidity binding serves every EVM network. It is built against Base first (Base Sepolia for the test network) and runs unchanged on Ethereum, Arbitrum, Optimism, Polygon and Avalanche with each network\u2019s own USDC and Chainlink addresses. Semantics are those of the protocol specification; only the carriers differ from Stellar. Shared by six networks Base first Aspect EVM binding Contracts Solidity (Foundry): Bond Registry, Claims, six validators; admin is Ownable2Step; reentrancy guard on every token-moving function; USDC moves with SafeERC20 Signatures secp256k1 over EIP-712 typed data; the service signing key is an ordinary key held by the seller\u2019s server and registered as an address, never the seller\u2019s wallet Hashing keccak-256; offerHash and receiptHash are EIP-712 digests (_hashTypedDataV4(structHash)); termsHash = keccak256(abi.encode(terms)) Stake token Native USDC (ERC-20, 6 decimals); the 100 USDC floor is 100_000_000 units P6 oracle Chainlink AggregatorV3Interface.getRoundData(roundId); the cited round id plays the role of Stellar\u2019s oracle timestamp x402 scheme exact on eip155:<chainId> with USDC transferWithAuthorization (EIP-3009) Wire form abi.encode(SignedOffer) and abi.encode(SignedReceipt) as 0x-prefixed hex inside extensions.lopw Indexing eth_getLogs on the registry and claims addresses from the deployment block, after EVM_CONFIRMATIONS confirmations (default 12 on Base Sepolia) Solidity structs; field order is normative because it defines the ABI and the EIP-712 hashes. Field names are the Stellar names in camelCase. SolidityCopy struct OracleTerms { address feed; bytes32 asset; uint32 maxDeviationBps; uint64 maxOracleAge; } struct Terms { uint8[] predicates; // 1..6, strictly increasing, no duplicates uint64 claimWindow; uint64 maxDataAge; uint16[] allowedStatus; uint32 maxResponseBytes; bool hasOracle; OracleTerms oracle; // zeroed when hasOracle is false } struct Offer { uint32 version; // 1 bytes32 serviceId; address registry; bytes32 termsHash; uint256 price; address asset; // USDC uint256 maxClaim; uint64 validUntil; bytes32 offerNonce; uint32 keyVersion; } struct OracleEvidence { address feed; bytes32 asset; uint80 roundId; int256 oraclePrice; int256 reportedValue; } struct Receipt { uint32 version; bytes32 serviceId; bytes32 offerHash; bytes32 termsHash; address buyer; bytes32 paymentReference; // settlement transaction hash bytes32 requestHash; bytes32 responseHash; uint32 responseBytes; uint16 status; uint64 servedAt; uint64 dataAsOf; bool hasOracle; OracleEvidence oracle; // zeroed when hasOracle is false uint32 keyVersion; } struct SignedOffer { Offer offer; bytes signature; } // 65-byte secp256k1 (r, s, v) struct SignedReceipt { Receipt receipt; bytes signature; } struct ClaimExtra { bool hasSecond; SignedReceipt second; } // P1 only termsHash = keccak256(abi.encode(terms)). Service ids are keccak256(abi.encode(address(registry), uint256(counter))) with the counter starting at 0 per registry. requestHash and responseHash are the same SHA-256 constructions as on Stellar, so a seller\u2019s HTTP layer is chain-agnostic. Domain: EIP712Domain(name=\"LOPW\", version=\"1\", chainId, verifyingContract=registry). The chain id and the registry address give the same two replay protections as Stellar\u2019s network id and registry address in the hash. EIP-712 type stringsCopy Offer(uint32 version,bytes32 serviceId,address registry,bytes32 termsHash,uint256 price,address asset,uint256 maxClaim,uint64 validUntil,bytes32 offerNonce,uint32 keyVersion) OracleEvidence(address feed,bytes32 asset,uint80 roundId,int256 oraclePrice,int256 reportedValue) Receipt(uint32 version,bytes32 serviceId,bytes32 offerHash,bytes32 termsHash,address buyer,bytes32 paymentReference,bytes32 requestHash,bytes32 responseHash,uint32 responseBytes,uint16 status,uint64 servedAt,uint64 dataAsOf,bool hasOracle,OracleEvidence oracle,uint32 keyVersion) offerHash and receiptHash are the EIP-712 digests _hashTypedDataV4(structHash); nested structs are hashed by their own type hash. Verification is ECDSA.recover(digest, signature) == registeredKey with malleable signatures rejected (OpenZeppelin ECDSA). A receipt is accepted only if servedAt < retiredAt of its key version, exactly as on Stellar (decision D-006). Functions keep the Stellar names where possible; errors are custom errors with the Stellar names; events carry the same names and fields in CamelCase, so one indexer schema serves every chain. Solidity interfacesCopy // Bond Registry constructor(address admin, IERC20 usdc, ProtocolConfig config, LaunchLimits limits, FeeConfig fees) setClaimsContract(address) -> uint64 eta // first call immediate; later ones time-locked applyClaimsContract(); cancelClaimsChange() setLimits(LaunchLimits) -> uint64 eta // tightening immediate, loosening time-locked setPaused(bool); setSellerAllowed(address, bool); setFees(FeeConfig) -> uint64 eta registerService(address signingKey, Terms terms, uint256 minStake) -> bytes32 serviceId updateTerms(bytes32, Terms) -> bytes32 termsHash; rotateKey(bytes32, address) -> uint32 keyVersion deposit(bytes32, uint256) // seller must have approved USDC requestUnbond(bytes32, uint256); cancelUnbond(bytes32); withdraw(bytes32) -> uint256; collectFee(bytes32) -> uint256 slash(bytes32, address buyer, uint256 amount) -> uint256 // onlyClaims getService / getTerms / getKey / termsHash / getConfig / getLimits / getFees / isPaused / isSellerAllowed / getTotalStake // Claims constructor(address admin, address registry, uint64 changeDelay) setValidator(uint8 predicateId, address) -> uint64 eta; applyValidator(uint8); cancelValidatorChange(uint8) claim(uint8 predicateId, SignedOffer, SignedReceipt, ClaimExtra) -> (bool paid, uint256 amount, uint8 reason) getClaim(bytes32 serviceId, bytes32 paymentReference) -> ClaimRecord // Validators validate(Terms, Offer, Receipt, ClaimExtra) external view returns (uint8) // 0 proven, 1 NotProven, 2 NotVerifiable The claim runs the seven checks in the specification\u2019"},{"t":"Base","u":"/docs/networks/base.html","s":"Networks","d":"LOPW on Base through the EVM binding: chain ids and CAIP-2 ids for Base Sepolia and Base, native USDC addresses, explorers, the Chainlink oracle note, and the LOPW contract addresses or the deployment status for each network.","h":[["glance","At a glance"],["contracts","LOPW contracts"],["contracts-test","Base Sepolia"],["contracts-main","Base"],["oracle","Oracle note"],["same-binding","What is identical to every other EVM network"],["agents","For agents"]],"x":"Base runs LOPW through the shared EVM binding: Solidity contracts, EIP-712 typed data signed with secp256k1, native USDC with 6 decimals, Chainlink feeds for P6 and x402 exact with EIP-3009. The first EVM network of the binding: the Solidity contracts, the TypeScript binding and the EVM reference service are built against Base Sepolia first. EVM binding Base first Item Base Sepolia Base Network name (API paths) base-sepolia base CAIP-2 eip155:84532 eip155:8453 Chain id 84532 8453 USDC (native, 6 decimals) 0x036CbD53842c5426634e7929541eC2318f3dCF7e 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 Explorer sepolia.basescan.org basescan.org P6 oracle 0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1Chainlink ETH / USD, 8 decimals, heartbeat 1200 s, 0.15 % threshold; Base Sepolia publishes no EUR/USD feed, so the EVM reference service quotes ETH/USD here Chainlink EUR / USD, verified on-chain at deployment LOPW deployment None published None published Not deployed No LOPW deployment is published on this network yet. Not deployed No LOPW deployment is published on this network yet. P6 terms on Base name a Chainlink AggregatorV3Interface feed and its pair label as bytes32. The seller cites the roundId it used; the validator reads getRoundData(roundId) on-chain, applies max_oracle_age to the round\u2019s updatedAt, and proves a breach with the exact integer deviation test. Every feed address is verified on-chain (description(), decimals()) before use and recorded in deployments/<network>.json. Chainlink ETH / USD, 8 decimals, heartbeat 1200 s, 0.15 % threshold; Base Sepolia publishes no EUR/USD feed, so the EVM reference service quotes ETH/USD here. The Solidity contracts, their functions, errors and events. The EIP-712 type strings and the domain LOPW / 1; only chainId and verifyingContract change. The wire form: 0x-hex ABI encoding of SignedOffer and SignedReceipt. USDC with 6 decimals as stake and payment token; x402 exact with EIP-3009. Indexing after EVM_CONFIRMATIONS confirmations, writing the same events, services and claims rows with network set to the names above. For agents Item Value Network name in API paths base-sepolia (Base Sepolia), base (Base); served once a LOPW deployment is published for the network CAIP-2 id eip155:84532, eip155:8453 Wire encoding of extensions.lopw 0x-hex ABI encoding of SignedOffer (info.offer) and SignedReceipt (receipt); schemas offer-evm.json and receipt-evm.json Explorer https://sepolia.basescan.org/tx/{hash}, https://sepolia.basescan.org/address/{address} USDC 0x036CbD53842c5426634e7929541eC2318f3dCF7e (Base Sepolia), 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (Base) EVM bindingTypes, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK.All networksStellar as the reference binding, the EVM networks, and the planned Solana binding. "},{"t":"Ethereum","u":"/docs/networks/ethereum.html","s":"Networks","d":"LOPW on Ethereum through the EVM binding: chain ids and CAIP-2 ids for Ethereum Sepolia and Ethereum, native USDC addresses, explorers, the Chainlink oracle note, and the LOPW contract addresses or the deployment status for each network.","h":[["glance","At a glance"],["contracts","LOPW contracts"],["contracts-test","Ethereum Sepolia"],["contracts-main","Ethereum"],["oracle","Oracle note"],["same-binding","What is identical to every other EVM network"],["agents","For agents"]],"x":"Ethereum runs LOPW through the shared EVM binding: Solidity contracts, EIP-712 typed data signed with secp256k1, native USDC with 6 decimals, Chainlink feeds for P6 and x402 exact with EIP-3009. Ethereum mainnet and its Sepolia test network run the same contracts as Base with Ethereum's own USDC and Chainlink feed addresses. EVM binding Item Ethereum Sepolia Ethereum Network name (API paths) sepolia ethereum CAIP-2 eip155:11155111 eip155:1 Chain id 11155111 1 USDC (native, 6 decimals) 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 Explorer sepolia.etherscan.io etherscan.io P6 oracle Chainlink feed, verified on-chain at deployment Chainlink feed, verified on-chain at deployment LOPW deployment None published None published Not deployed No LOPW deployment is published on this network yet. Not deployed No LOPW deployment is published on this network yet. P6 terms on Ethereum name a Chainlink AggregatorV3Interface feed and its pair label as bytes32. The seller cites the roundId it used; the validator reads getRoundData(roundId) on-chain, applies max_oracle_age to the round\u2019s updatedAt, and proves a breach with the exact integer deviation test. Every feed address is verified on-chain (description(), decimals()) before use and recorded in deployments/<network>.json. The Solidity contracts, their functions, errors and events. The EIP-712 type strings and the domain LOPW / 1; only chainId and verifyingContract change. The wire form: 0x-hex ABI encoding of SignedOffer and SignedReceipt. USDC with 6 decimals as stake and payment token; x402 exact with EIP-3009. Indexing after EVM_CONFIRMATIONS confirmations, writing the same events, services and claims rows with network set to the names above. For agents Item Value Network name in API paths sepolia (Ethereum Sepolia), ethereum (Ethereum); served once a LOPW deployment is published for the network CAIP-2 id eip155:11155111, eip155:1 Wire encoding of extensions.lopw 0x-hex ABI encoding of SignedOffer (info.offer) and SignedReceipt (receipt); schemas offer-evm.json and receipt-evm.json Explorer https://sepolia.etherscan.io/tx/{hash}, https://sepolia.etherscan.io/address/{address} USDC 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 (Ethereum Sepolia), 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 (Ethereum) EVM bindingTypes, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK.All networksStellar as the reference binding, the EVM networks, and the planned Solana binding. "},{"t":"Arbitrum","u":"/docs/networks/arbitrum.html","s":"Networks","d":"LOPW on Arbitrum through the EVM binding: chain ids and CAIP-2 ids for Arbitrum Sepolia and Arbitrum One, native USDC addresses, explorers, the Chainlink oracle note, and the LOPW contract addresses or the deployment status for each network.","h":[["glance","At a glance"],["contracts","LOPW contracts"],["contracts-test","Arbitrum Sepolia"],["contracts-main","Arbitrum One"],["oracle","Oracle note"],["same-binding","What is identical to every other EVM network"],["agents","For agents"]],"x":"Arbitrum runs LOPW through the shared EVM binding: Solidity contracts, EIP-712 typed data signed with secp256k1, native USDC with 6 decimals, Chainlink feeds for P6 and x402 exact with EIP-3009. Arbitrum One and Arbitrum Sepolia: an optimistic rollup with native USDC and Chainlink feeds; the same EVM binding applies unchanged. EVM binding Item Arbitrum Sepolia Arbitrum One Network name (API paths) arbitrum-sepolia arbitrum CAIP-2 eip155:421614 eip155:42161 Chain id 421614 42161 USDC (native, 6 decimals) 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 Explorer sepolia.arbiscan.io arbiscan.io P6 oracle Chainlink feed, verified on-chain at deployment Chainlink feed, verified on-chain at deployment LOPW deployment None published None published Not deployed No LOPW deployment is published on this network yet. Not deployed No LOPW deployment is published on this network yet. P6 terms on Arbitrum name a Chainlink AggregatorV3Interface feed and its pair label as bytes32. The seller cites the roundId it used; the validator reads getRoundData(roundId) on-chain, applies max_oracle_age to the round\u2019s updatedAt, and proves a breach with the exact integer deviation test. Every feed address is verified on-chain (description(), decimals()) before use and recorded in deployments/<network>.json. The Solidity contracts, their functions, errors and events. The EIP-712 type strings and the domain LOPW / 1; only chainId and verifyingContract change. The wire form: 0x-hex ABI encoding of SignedOffer and SignedReceipt. USDC with 6 decimals as stake and payment token; x402 exact with EIP-3009. Indexing after EVM_CONFIRMATIONS confirmations, writing the same events, services and claims rows with network set to the names above. For agents Item Value Network name in API paths arbitrum-sepolia (Arbitrum Sepolia), arbitrum (Arbitrum One); served once a LOPW deployment is published for the network CAIP-2 id eip155:421614, eip155:42161 Wire encoding of extensions.lopw 0x-hex ABI encoding of SignedOffer (info.offer) and SignedReceipt (receipt); schemas offer-evm.json and receipt-evm.json Explorer https://sepolia.arbiscan.io/tx/{hash}, https://sepolia.arbiscan.io/address/{address} USDC 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d (Arbitrum Sepolia), 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 (Arbitrum One) EVM bindingTypes, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK.All networksStellar as the reference binding, the EVM networks, and the planned Solana binding. "},{"t":"Optimism","u":"/docs/networks/optimism.html","s":"Networks","d":"LOPW on Optimism through the EVM binding: chain ids and CAIP-2 ids for OP Sepolia and OP Mainnet, native USDC addresses, explorers, the Chainlink oracle note, and the LOPW contract addresses or the deployment status for each network.","h":[["glance","At a glance"],["contracts","LOPW contracts"],["contracts-test","OP Sepolia"],["contracts-main","OP Mainnet"],["oracle","Oracle note"],["same-binding","What is identical to every other EVM network"],["agents","For agents"]],"x":"Optimism runs LOPW through the shared EVM binding: Solidity contracts, EIP-712 typed data signed with secp256k1, native USDC with 6 decimals, Chainlink feeds for P6 and x402 exact with EIP-3009. OP Mainnet and OP Sepolia share the OP Stack with Base, so the binding behaves identically; only addresses and chain ids differ. EVM binding Item OP Sepolia OP Mainnet Network name (API paths) optimism-sepolia optimism CAIP-2 eip155:11155420 eip155:10 Chain id 11155420 10 USDC (native, 6 decimals) 0x5fd84259d66Cd46123540766Be93DFE6D43130D7 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85 Explorer sepolia-optimism.etherscan.io optimistic.etherscan.io P6 oracle Chainlink feed, verified on-chain at deployment Chainlink feed, verified on-chain at deployment LOPW deployment None published None published Not deployed No LOPW deployment is published on this network yet. Not deployed No LOPW deployment is published on this network yet. P6 terms on Optimism name a Chainlink AggregatorV3Interface feed and its pair label as bytes32. The seller cites the roundId it used; the validator reads getRoundData(roundId) on-chain, applies max_oracle_age to the round\u2019s updatedAt, and proves a breach with the exact integer deviation test. Every feed address is verified on-chain (description(), decimals()) before use and recorded in deployments/<network>.json. The Solidity contracts, their functions, errors and events. The EIP-712 type strings and the domain LOPW / 1; only chainId and verifyingContract change. The wire form: 0x-hex ABI encoding of SignedOffer and SignedReceipt. USDC with 6 decimals as stake and payment token; x402 exact with EIP-3009. Indexing after EVM_CONFIRMATIONS confirmations, writing the same events, services and claims rows with network set to the names above. For agents Item Value Network name in API paths optimism-sepolia (OP Sepolia), optimism (OP Mainnet); served once a LOPW deployment is published for the network CAIP-2 id eip155:11155420, eip155:10 Wire encoding of extensions.lopw 0x-hex ABI encoding of SignedOffer (info.offer) and SignedReceipt (receipt); schemas offer-evm.json and receipt-evm.json Explorer https://sepolia-optimism.etherscan.io/tx/{hash}, https://sepolia-optimism.etherscan.io/address/{address} USDC 0x5fd84259d66Cd46123540766Be93DFE6D43130D7 (OP Sepolia), 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85 (OP Mainnet) EVM bindingTypes, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK.All networksStellar as the reference binding, the EVM networks, and the planned Solana binding. "},{"t":"Polygon","u":"/docs/networks/polygon.html","s":"Networks","d":"LOPW on Polygon through the EVM binding: chain ids and CAIP-2 ids for Polygon Amoy and Polygon PoS, native USDC addresses, explorers, the Chainlink oracle note, and the LOPW contract addresses or the deployment status for each network.","h":[["glance","At a glance"],["contracts","LOPW contracts"],["contracts-test","Polygon Amoy"],["contracts-main","Polygon PoS"],["oracle","Oracle note"],["same-binding","What is identical to every other EVM network"],["agents","For agents"]],"x":"Polygon runs LOPW through the shared EVM binding: Solidity contracts, EIP-712 typed data signed with secp256k1, native USDC with 6 decimals, Chainlink feeds for P6 and x402 exact with EIP-3009. Polygon PoS and the Amoy test network: native USDC (not the bridged USDC.e) is the stake and payment token. EVM binding Item Polygon Amoy Polygon PoS Network name (API paths) polygon-amoy polygon CAIP-2 eip155:80002 eip155:137 Chain id 80002 137 USDC (native, 6 decimals) 0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 Explorer amoy.polygonscan.com polygonscan.com P6 oracle Chainlink feed, verified on-chain at deployment Chainlink feed, verified on-chain at deployment LOPW deployment None published None published Not deployed No LOPW deployment is published on this network yet. Not deployed No LOPW deployment is published on this network yet. P6 terms on Polygon name a Chainlink AggregatorV3Interface feed and its pair label as bytes32. The seller cites the roundId it used; the validator reads getRoundData(roundId) on-chain, applies max_oracle_age to the round\u2019s updatedAt, and proves a breach with the exact integer deviation test. Every feed address is verified on-chain (description(), decimals()) before use and recorded in deployments/<network>.json. The Solidity contracts, their functions, errors and events. The EIP-712 type strings and the domain LOPW / 1; only chainId and verifyingContract change. The wire form: 0x-hex ABI encoding of SignedOffer and SignedReceipt. USDC with 6 decimals as stake and payment token; x402 exact with EIP-3009. Indexing after EVM_CONFIRMATIONS confirmations, writing the same events, services and claims rows with network set to the names above. For agents Item Value Network name in API paths polygon-amoy (Polygon Amoy), polygon (Polygon PoS); served once a LOPW deployment is published for the network CAIP-2 id eip155:80002, eip155:137 Wire encoding of extensions.lopw 0x-hex ABI encoding of SignedOffer (info.offer) and SignedReceipt (receipt); schemas offer-evm.json and receipt-evm.json Explorer https://amoy.polygonscan.com/tx/{hash}, https://amoy.polygonscan.com/address/{address} USDC 0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582 (Polygon Amoy), 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 (Polygon PoS) EVM bindingTypes, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK.All networksStellar as the reference binding, the EVM networks, and the planned Solana binding. "},{"t":"Avalanche","u":"/docs/networks/avalanche.html","s":"Networks","d":"LOPW on Avalanche through the EVM binding: chain ids and CAIP-2 ids for Avalanche Fuji and Avalanche C-Chain, native USDC addresses, explorers, the Chainlink oracle note, and the LOPW contract addresses or the deployment status for each network.","h":[["glance","At a glance"],["contracts","LOPW contracts"],["contracts-test","Avalanche Fuji"],["contracts-main","Avalanche C-Chain"],["oracle","Oracle note"],["same-binding","What is identical to every other EVM network"],["agents","For agents"]],"x":"Avalanche runs LOPW through the shared EVM binding: Solidity contracts, EIP-712 typed data signed with secp256k1, native USDC with 6 decimals, Chainlink feeds for P6 and x402 exact with EIP-3009. The Avalanche C-Chain and the Fuji test network: EVM-compatible, native USDC, Chainlink feeds; the binding applies unchanged. EVM binding Item Avalanche Fuji Avalanche C-Chain Network name (API paths) avalanche-fuji avalanche CAIP-2 eip155:43113 eip155:43114 Chain id 43113 43114 USDC (native, 6 decimals) 0x5425890298aed601595a70AB815c96711a31Bc65 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E Explorer testnet.snowtrace.io snowtrace.io P6 oracle Chainlink feed, verified on-chain at deployment Chainlink feed, verified on-chain at deployment LOPW deployment None published None published Not deployed No LOPW deployment is published on this network yet. Not deployed No LOPW deployment is published on this network yet. P6 terms on Avalanche name a Chainlink AggregatorV3Interface feed and its pair label as bytes32. The seller cites the roundId it used; the validator reads getRoundData(roundId) on-chain, applies max_oracle_age to the round\u2019s updatedAt, and proves a breach with the exact integer deviation test. Every feed address is verified on-chain (description(), decimals()) before use and recorded in deployments/<network>.json. The Solidity contracts, their functions, errors and events. The EIP-712 type strings and the domain LOPW / 1; only chainId and verifyingContract change. The wire form: 0x-hex ABI encoding of SignedOffer and SignedReceipt. USDC with 6 decimals as stake and payment token; x402 exact with EIP-3009. Indexing after EVM_CONFIRMATIONS confirmations, writing the same events, services and claims rows with network set to the names above. For agents Item Value Network name in API paths avalanche-fuji (Avalanche Fuji), avalanche (Avalanche C-Chain); served once a LOPW deployment is published for the network CAIP-2 id eip155:43113, eip155:43114 Wire encoding of extensions.lopw 0x-hex ABI encoding of SignedOffer (info.offer) and SignedReceipt (receipt); schemas offer-evm.json and receipt-evm.json Explorer https://testnet.snowtrace.io/tx/{hash}, https://testnet.snowtrace.io/address/{address} USDC 0x5425890298aed601595a70AB815c96711a31Bc65 (Avalanche Fuji), 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E (Avalanche C-Chain) EVM bindingTypes, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK.All networksStellar as the reference binding, the EVM networks, and the planned Solana binding. "},{"t":"Solana","u":"/docs/networks/solana.html","s":"Networks","d":"The planned LOPW binding for Solana, as a design: Anchor programs, Ed25519 over domain-separated SHA-256 of Borsh bytes, USDC SPL with 6 decimals, Pyth price updates verified in-program, x402 exact with an SPL transfer, and the network names solana-devnet and solana. Nothing is deployed.","h":[["glance","The design at a glance"],["networks","Networks"],["same","What stays identical"],["agents","For agents"]],"x":"Solana is a planned binding. This page records the design so that the protocol\u2019s universality is concrete: which carriers Solana supplies for the same offer, receipt, predicates and claim semantics. No program is deployed, no test vectors are published, and the API serves no Solana network. Planned binding Design, not a deployment Aspect Solana binding (design) Programs Anchor (Rust): a bond registry program, a claims program and one validator program per predicate, with the same states, checks, outcomes, error names and events Signatures Ed25519 by a dedicated service signing key registered in the service account Hashing SHA-256 over Borsh-serialised bodies, domain-separated with the same type tags, a network identifier (the genesis hash) and the registry program address Stake token USDC SPL token, 6 decimals; the 100 USDC floor is 100_000_000 units P6 oracle A Pyth price update verified in-program; the cited price update plays the role of Stellar\u2019s oracle timestamp and Chainlink\u2019s round id x402 scheme exact on solana:<genesis> with an SPL transfer; payment_reference is the settlement transaction signature, buyer the payer Wire form Borsh encoding of SignedOffer and SignedReceipt inside extensions.lopw Network Name CAIP-2 Status Solana devnet solana-devnet solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 Planned; nothing deployed Solana mainnet solana solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp Planned; nothing deployed Offer, receipt and terms fields and their meaning; request_hash and response_hash as SHA-256. Predicates P1 to P6 and their proven / not proven / not verifiable rules, including the exact integer deviation test. The seven claim checks, the paid record per (service_id, payment_reference), the error names and the events. Service states, floors, launch limits, fees, the slash rate limit and time-locked admin changes. The ChainBinding interface: offerHash, receiptHash, termsHash, sign and verify, getService, getTerms, getKey, oraclePrice, submitClaim, registerService, deposit, and the encode / decode pair for the wire form. For agents Item Value Network name in API paths solana-devnet, solana; not served (no deployment) CAIP-2 id solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1, solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp Wire encoding of extensions.lopw Borsh bytes of SignedOffer / SignedReceipt in the design; no schema or test vectors are published for this binding Status Design only. Treat any solana offer as unsupported until a deployment and test vectors are published All networksStellar as the reference binding and the EVM networks with their chain ids, addresses and deployment status.Chain bindings (reference)The contract every binding, including this one, must satisfy. "},{"t":"Security model","u":"/docs/deep-dive/security.html","s":"Deep dive","d":"LOPW's security model: what is protected, trust assumptions, the threats and their mitigations, residual risks, and the known limits accepted in version 1.","h":[["assets","What is protected"],["trust","Trust assumptions"],["threats","Threats and mitigations"],["limits","Known limits accepted in version 1"],["before-mainnet","Before any production deployment"]],"x":"Scope: the version 1 Stellar binding, which is the Bond Registry, Claims and validator contracts, the seller middleware, the buyer SDK, and the off-chain indexer and API. Nothing here is a promise that the system is unbreakable; it is a list of what was considered, what was done about it and what remains. Asset Where it lives Who must not be able to take it Seller stake (USDC) Bond Registry Anyone except the seller after unbonding, and buyers with a proven claim Buyer payouts Paid from stake on a proven claim Anyone other than the buyer named in the receipt Service signing keys Seller\u2019s server Attackers, who could sign receipts the seller is liable for Seller account keys Seller Attackers, who could withdraw stake after the delay Admin key Protocol operator Attackers, who could rewire which contracts may slash Integrity of claim records Claims contract Anyone trying to be paid twice for one payment Trusted For If wrong Consensus and the contract host Correct execution, finality Everything fails; out of scope The SEP-40 oracle named in the terms P6 truth Wrong P6 outcomes (known limit) The x402 facilitator Settling the buyer\u2019s payment Payment fails or is not settled; LOPW does not escrow, so no stake is at risk The admin key Initial wiring and time-locked changes Bounded by the 26-hour time lock The seller\u2019s own reports for P3 to P5 data_as_of, status, response_bytes A consistently lying seller is not caught by P3 to P5 Not trusted: buyers, claim submitters, sellers beyond what they sign, and the API and indexer, which are a convenience only; the chain is the source of truth, and the SDKs read it directly for every payment decision. # Threat Mitigation Residual risk T1 Replay a paid claim with the same or a different receipt One paid claim per (service_id, payment_reference), stored on-chain None known T2 Cross-network replay (a test-network receipt on the production network) Network ID in every signed hash None T3 Cross-deployment replay against another registry Registry address in every signed hash; Claims checks offer.registry None T4 Forged receipt Ed25519 signature by the service key registered on-chain Key theft (T6) T5 Receipt for an offer the buyer never saw Claims requires receipt.offer_hash == hash(offer) and a valid offer signature Not slashable, by design T6 Signing key stolen rotate_key retires the old key; receipts with served_at after retirement fail with KeyRetired Receipts forged before rotation, within the claim window, bounded by max_claim each and the slash rate limit T7 Seller self-drain: invented payments to its own addresses to pull stake out ahead of honest claims Slash rate limit: at most 50% of epoch-start stake per epoch (epoch = unbonding delay); every payout capped by max_claim A seller can still pay itself up to the cap; it gains nothing over a normal withdrawal, and honest claims compete in the same epoch T8 Seller exits before claims land Unbonding delay exceeds the longest claim window of any terms version ever used; stake stays slashable while unbonding None within the claim window T9 Admin rewires slashing (malicious Claims contract or validator) First setting immediate; any later change time-locked 26 hours, longer than any unbonding delay; changes are on-chain events Sellers must watch for ClaimsChangeProposed / ValidatorChangeProposed T10 Contract upgrade abuse Contracts have no upgrade function; changes require a new deployment None T11 Oracle manipulation or outage P6 compares against one past tick, read on-chain; missing data is NotVerifiable (no payout, no penalty); the seller chooses its tolerance The oracle itself is trusted T12 Off-tick oracle timestamp to dodge P6 The validator uses the tick the oracle returns and checks the age against it None T13 Rounding to dodge P6 Exact integer comparison with no division None T14 Front-running a claim Anyone may submit, but the payout always goes to receipt.buyer None T15 Claim spam or griefing The submitter pays fees; rejected claims change no state except an event Event noise only T16 Buyer pays a suspended or under-staked seller Middleware stops offering when not Active; the SDK reads live state, stake and terms before paying and aborts on policy failure A race of seconds between read and payment T17 Over-subscription: the seller serves more calls than its stake covers Stake and max_claim are public before payment; agents set minStake / minMaxClaim Later claims may find stake exhausted T18 State expiry of services, terms, keys or claim records Persistent storage with lifetime extended on every access; claim windows are far shorter Long-idle services may need a lifetime extension or restore T19 Indexer or API compromise or lag Read-only, rebuilt from chain events; SDKs read the chain directly for every payment decision Misleading dashboards, no fund risk T20 API abuse Rate limits, pagination caps, no write endpoints Denial of service of a convenience service P3 to P5 rest on seller-reported fields. They catch a seller that contradicts itself or admits a breach in its own signed receipt, not one that lies consistently. P6 is the only predicate checked against evidence the seller does not control. Payment is not verified on-chain. The seller\u2019s signature over payment_reference and buyer is its admission; T7 bounds the abuse. A facilitator-signed settlement attestation would close this in a later version. Missing receipts and bad signatures are not slashable. The buyer SDK reports them as untrusted responses. The oracle is trusted. A wrong observation produces a wrong claim outcome. A single admin key on the first deployment. Before a production launch: a multisig admin account, and consideration of making the wiring immutable once stable. Receipts are public once claimed. Claim records and events reveal the buyer address and payment reference. A seller that serves far more calls than its stake covers leaves later claims unpaid. Buyers see the stake before paying and decide. Independent security audit of a"},{"t":"Economics","u":"/docs/deep-dive/economics.html","s":"Deep dive","d":"What a seller risks and pays, what a buyer gains and pays, how the payout, caps, epochs and fees interact, with exact fee maths and worked examples.","h":[["seller","What a seller risks"],["seller-costs","What a seller pays"],["buyer","What a buyer gains"],["buyer-costs","What a buyer pays"],["payout-example","A worked claim"],["incentives","Incentives in one table"]],"x":"LOPW moves money in exactly three ways: a seller locks stake, a seller pays fees, and a proven claim moves part of the stake to a buyer. Everything else is information. Exposure Bound Where set Per receipt max_claim, and never more than the stake left or the launch limit max_payout Each offer (maxClaim in the middleware) Per epoch (one unbonding delay) 50% of the stake held at the epoch\u2019s start, with the first slash of an epoch always allowed Protocol floor slash_epoch_cap_bps In total The stake The seller\u2019s deposits Suspension Any slash or fee that leaves stake below min_stake stops new offers until a deposit restores it min_stake at registration A seller never owes more than it staked. The right stake is a business decision: it must be large enough that agents with a policy will pay (they read it before paying), and small enough that a run of honest claims would not hurt more than the breach deserved. max_claim sets what one bad response costs; the epoch cap sets how fast stake can bleed even under a flood of claims. Cost Amount Note Registration fee registration_fee once (1 USDC on the first deployment; 10 USDC proposed for a production launch) Transferred from the seller\u2019s account at registration Stake fee stake_fee_bps_per_year of stake, accrued per second (100 bps = 1% a year) Collected from stake whenever stake moves or anyone calls collect_fee Transaction fees Normal network fees for registering, depositing, rotating keys and withdrawing Paid by the seller\u2019s account Payouts min(max_claim, stake) per proven breach The actual cost of a broken promise TextCopy fee = stake * bps_per_year * elapsed_seconds / (10000 * 31_536_000) // integer division, capped at stake 200 USDC at 100 bps for 30 days: 2_000_000_000 * 100 * 2_592_000 / 315_360_000_000 = 1_643_835 units = 0.1643835 USDC 200 USDC at 100 bps for a year: 2_000_000_000 * 100 * 31_536_000 / 315_360_000_000 = 20_000_000 units = 2 USDC Because the fee is taken from stake, a service staked exactly at its minimum is suspended by the first collection. Stake above the minimum by at least a year of fees to be safe. Gain Detail Information before paying State, stake, terms and max_claim are readable for free. A policy turns them into an automatic decision. Evidence after paying A signed receipt bound to the payment, request, response and terms. Even without a claim, it is proof of what was served. Compensation min(max_claim, stake remaining) on a proven breach, in the stake token, to the buyer\u2019s own address, in the same transaction as the claim. Protection from decay A service whose stake drops below its minimum is suspended automatically; policy-driven agents stop paying it at once. The x402 price of the call, unchanged. The network fee of the claim transaction, including the oracle read for P6 (the first live P6 claim cost only the normal transaction fee, with no oracle charge). Nothing when the seller keeps its promise: the happy path has no extra transaction. Nothing when a claim is rejected except the transaction fee; no stake, no penalty. The reference FX service stakes 200 USDC with min_stake 100 USDC, a 1-hour claim window and max_claim 5 USDC; a quote costs $0.01. Unbonding delay and slash epoch: 1 h + 1 h = 2 h. Epoch cap: 50% of 200 = 100 USDC. A quote 10% off the oracle is claimed under P6. Payout: min(5, 200) = 5 USDC. Stake 195 USDC; still Active. Nineteen more proven claims in the same epoch pay 5 USDC each: total 100 USDC, exactly the cap. Stake 100 USDC, still at the minimum. A twenty-first claim in that epoch fails with SlashRateLimited and can be retried in the next epoch; its claim window (1 h) may close first. Meanwhile any slash or fee that takes the stake below 100 USDC suspends the service; the middleware stops issuing offers and agents stop paying. For a buyer, max_claim of 5 USDC against a $0.01 call is cover of 500 times the price. For the seller, the worst case in one epoch is 100 USDC, and only if it breaks its promise twenty times to twenty claimants. Party Wants LOPW makes it true that Honest seller To be chosen by agents Stake and terms are visible before payment; keeping the promise costs nothing beyond fees Dishonest seller To take payment and under-deliver Each admitted or oracle-provable breach costs max_claim; lying consistently about self-reported fields escapes P3 to P5 but not P6, and the stake is what agents judge Buyer Recourse without a dispute process A claim is one transaction decided by code; no one can redirect the payout Claim submitter who is not the buyer Nothing to gain The payout goes to the receipt\u2019s buyer, so there is nothing to front-run Protocol operator To never hold funds hostage Pause never blocks claims or withdrawals; admin changes are time-locked; contracts cannot be upgraded "},{"t":"The decisions log, explained","u":"/docs/deep-dive/decisions.html","s":"Deep dive","d":"Why each LOPW rule exists: the twenty-two recorded decisions that refine the specification, in plain language, with what they changed and why.","h":[["decisions","The decisions, one by one"]],"x":"The specification left open questions and a few gaps. Each was settled with a recorded decision. Where the specification and a decision differ, the decision wins. Here is each one, with the reason, in plain words. D-001 Two contracts: Bond Registry and Claims Decision. Stake custody lives in a small registry contract; claim logic lives in a separate contract, with one more contract per validator. Why. The code that holds money stays short and easy to audit, and claim logic can evolve (new predicates, new evidence rules) without redeploying the contract that holds stake. Only the Claims contract may call slash. D-002 A Pending state Decision. A newly registered service is Pending until deposits reach min_stake. Why. Registration and deposit are separate calls, and the draft had no state for the gap. A Pending service issues no offers, so no receipt can exist against it, so its stake can be withdrawn immediately without an unbonding delay. D-003 Partial withdrawal keeps the service Active Decision. request_unbond records a pending withdrawal; if the remainder stays at or above the minimum the service stays Active, otherwise the whole stake exits and the service is Unbonding. Why. The purpose of the delay is only to keep money slashable for open receipts, not to stop an honest seller from trimming surplus. The pending amount stays slashable until withdrawn; cancel_unbond restores Active or, if below the minimum, Suspended. D-004 The unbonding delay covers every terms version Decision. unbonding_delay = largest claim window ever registered + 1 h; terms cannot change while Unbonding. Why. Claim windows can change with terms. A receipt issued under an older, longer window must still be claimable after a seller shortens its window and exits. D-005 Seller self-drain is rate-limited Decision. Within one epoch (one unbonding delay) total slashes may not exceed 50% of the stake at the epoch start; the first slash of an epoch is always allowed. Why. The contract never checks that a payment happened, so a seller could sign receipts for invented payments to its own addresses and claim them: an instant withdrawal that skips the delay and races honest buyers. The draft\u2019s note that this \u201ccosts the seller its own money\u201d was wrong, since the money returns to the seller. With the cap, stake leaves no faster than a normal withdrawal, so honest claims compete for real stake. The trade-off is that a genuine mass breach pays out over more than one epoch. D-006 Rotated keys have a retirement time Decision. rotate_key stamps the old key with retired_at; a receipt is accepted only if served_at is before it. Why. A rotated key that stayed valid forever would let a leaked old key keep signing receipts with fresh timestamps. Old receipts stay claimable; the old key cannot produce new ones. D-006a Typed errors everywhere Decision. All public registry functions return Result<_, Error> instead of panicking. Why. Generated clients, mobile apps and agents receive the named error code (EpochCapReached, StillLocked) rather than an opaque host failure. D-007 min_stake is a service parameter, not part of the terms Decision. Terms (predicates, parameters, claim window) are hashed and versioned; min_stake is set at registration outside the hash. Why. Changing the stake requirement must never invalidate offers or receipts a buyer already holds. D-008 P3 does not apply when data_as_of is 0 Decision. The P3 validator returns NotProven for data_as_of == 0 and for data_as_of > served_at. Why. 0 means \u201cnot applicable\u201d, but served_at \u2212 0 would always exceed any limit and prove a breach. A P3 service reporting 0 is instead caught by the buyer SDK as an untrusted response. Negative ages are not breaches either. D-009 P6 uses the oracle's price, exactly Decision. The breach test is |reported_value \u2212 oracle_price| \u00d7 10000 > max_deviation_bps \u00d7 oracle_price, with oracle_price read from the oracle, not from the receipt. Why. The draft\u2019s division form rounds down, so a 50.99 bps deviation would pass a 50 bps limit; a unit test caught it. The receipt\u2019s own price field is informational. A zero or negative oracle price is NotVerifiable. D-010 Claims verifies P1's second receipt Decision. For P1 the Claims contract checks the signature of the receipt in extra with the registry key before calling the validator. Why. validate() is not given the seller\u2019s keys. Doing the check in Claims keeps validators pure functions of their inputs. D-011 One stake token per registry deployment Decision. The registry is initialised with a single SEP-41 token: USDC. Why. Any SEP-41 token works technically; one token per deployment keeps payouts and floors in one unit. D-012 Protocol floors Decision. Minimum min_stake 100 USDC; maximum claim window 24 h; unbond margin 1 h; slash epoch cap 50%. Why. Adopted as the draft proposed, configured at registry construction so a deployment can choose differently. D-013 Every API is built for third-party, mobile and agent clients Decision. Versioned paths with the network in them, an OpenAPI document, JSON with snake_case fields, decimal-string amounts, problem+json errors, cursor pagination, open CORS on reads, ETags, advertised rate limits, agent discovery files and a Server-Sent Events stream. Why. So other applications, mobile apps and autonomous agents can use the APIs without special handling, and so that paid reference services remain plain x402 endpoints any agent can call. D-014 Reflector oracle findings and demo pairs Decision. The fiat-rates oracle quotes USD per unit with 14 decimals, ticks every 5 minutes and keeps 24 hours of history. price(asset, t) rounds t down to the previous tick and returns that tick\u2019s timestamp; it returns nothing beyond retention. The validator applies the age check to the returned timestamp. The test-network demo uses EUR/USD; the FX service takes the asset from configuration. No operator-run mirror oracle. Why. Requiring the cited timestamp to equal a tick would let a seller dodge claim"},{"t":"Proofs and scenarios","u":"/docs/deep-dive/proofs-and-scenarios.html","s":"Deep dive","d":"What LOPW has demonstrated on a live network: every predicate's payout after a real x402 payment, the stake lifecycle, governance safety and the automated test suites, with links to the demo and proofs pages.","h":[["payouts","Warranty payouts after real payments"],["lifecycle","Stake lifecycle"],["governance","Governance and safety"],["tests","Automated test suites"],["scenarios","The scheduled scenarios"],["mainnet","Production network"]],"x":"Everything below has been run for real on the Stellar test network, with real x402 payments and real claims. The proofs page links each item to its transaction on a public explorer; the demo replays one claim step by step with its console output. Demonstrated Result P1 Equivocation: the seller signed two different receipts for one payment Claim paid 5 USDC from stake P2 Terms mismatch: the receipt cited different terms than the offer Claim paid 5 USDC P3 Stale data: data an hour old against a 10-minute promise Claim paid 5 USDC P4 Bad status: charged for HTTP 203 when only 200 was chargeable Claim paid 1 USDC P5 Size limit: response larger than the 4 KiB promised Claim paid 1 USDC P6 Oracle deviation: EUR/USD quoted 10% away from the oracle; the validator read the oracle on-chain Claim paid 5 USDC Honest response Receipt verified, warranty kept, no claim Tampered receipt signature Flagged untrusted by the buyer SDK; no claim possible, no penalty Payout took stake below the minimum Service suspended automatically; agents stopped paying it Demonstrated Result Deposit 150 USDC activates a Pending service State Active Partial unbonding keeps the service Active Pending withdrawal recorded with unlock time Withdraw before the unlock time Rejected: StillLocked Cancel unbonding Pending withdrawal cleared Registration below the 100 USDC floor Rejected: StakeBelowFloor Demonstrated Result Admin proposed a replacement Claims contract Time-locked 26 hours; the monitor raised a critical alert within seconds; proposal cancelled Oracle read charges A contract-to-contract oracle read carried no fee beyond the normal transaction fee Honest claim against an honest quote Recorded as rejected, NotProven, no payout Protocol fees: 1 USDC registration fee on each reference service Fees reached the treasury account Annual stake fee (1% a year) accrued per second and collected on demand collect_fee moved the accrued amount to the treasury; FeeCollected indexed Payment settled by a self-hosted x402 facilitator, then a P6 claim Claim paid 5 USDC Storage keeper extended the registry, claims and every service entry All transactions succeeded Property test: random deposits, slashes, fees, unbonding and withdrawals The contract\u2019s USDC balance equalled the sum of stakes after every step in 64 random sequences Suite Tests Covers Soroban contracts (Rust) 97 Every registry state transition, slash rate limit, key retirement, admin time locks, launch limits, pause, allowlist, fees, a stake-accounting property test, all six validators, end-to-end claims with real signatures and a test USDC token Protocol, buyer SDK, seller middleware (TypeScript) 22 Byte-for-byte test vectors shared with Rust, local predicate evaluation, policy refusals, receipt verification, durable receipt store, live test-network reads Indexer, API, monitor (Go) 11 Event decoding, API pagination, errors, ETags, rate limits, OpenAPI coverage, alert rules including launch-configuration alerts Every push builds and tests all three: Rust (format, lint, tests, wasm build), TypeScript (build, type check, tests) and Go (format, vet, tests). Counts are those recorded on the proofs page. Eleven named scenarios run on a schedule against the live reference services and are judged automatically: honest quote, each predicate\u2019s breach, tampered signature, replay blocked, honest claim rejected and policy refusal. Each is written in plain language with why it matters: Scenario Why it matters Honest FX quote: warranty kept The normal path must stay cheap and simple: one x402 payment, one receipt, no escrow, no extra transaction when the seller behaves. Quote 10% off the oracle: P6 claim paid The only predicate checked against evidence the seller does not control. A seller cannot lie about market data without paying for it. Data older than promised: P3 claim paid Freshness guarantees are enforceable without any oracle: the seller\u2019s signed receipt is the evidence. Receipt cites different terms: P2 claim paid A seller cannot quietly swap the promise after payment: the receipt must name the exact terms the buyer paid under. Two different receipts for one payment: P1 claim paid Equivocation is the clearest proof of dishonesty, and the second receipt is all the evidence needed. Broken receipt signature: untrusted, no claim, no penalty An invalid signature proves nothing about the seller (anyone could forge one), so it is never slashable; the protocol must say so rather than guess. Charged for an unpromised HTTP status: P4 claim paid Sellers cannot bill for responses they promised not to bill for; the status is in the signed receipt. Response larger than promised: P5 claim paid Size limits protect agents from paying for padded or runaway responses. Same claim filed twice: second one rejected One payment can be compensated once. Replay protection is on-chain, keyed by service and payment. Claim against an honest receipt: rejected, nothing paid Anyone may submit a claim, so the protocol must produce no false positives: an honest seller is never slashed. Agent policy refuses an under-staked seller: no payment made The bond is readable before payment, so an agent can decline a seller it does not trust at zero cost. Run them yourself: Run the scenarios. Read the recorded runs: GET /v1/{network}/scenarios in the API. No production-network deployment exists yet. When one does, its proofs are added to the proofs page under that network with the same explorer links, and the Stellar binding\u2019s STELLAR_MAINNET entry receives its contract addresses. "},{"t":"Agents start here","u":"/docs/agents/","s":"For AI agents","d":"Machine-readable entry point to LOPW: Markdown twins of every page, llms.txt, llms-full.txt, the protocol descriptor, JSON Schemas for Offer, Receipt and Terms, shared test vectors and the OpenAPI document, plus the facts an agent needs to decide and act.","h":[["files","Machine-readable files"],["facts","Facts you need first"],["decide","To decide whether to pay a service"],["verify","To verify a receipt you received"],["claim","To file a claim"],["read","To read protocol state without a key"],["map","Page map"]],"x":"Everything on this site is available in a form you can consume directly. This page tells you where, then gives you the facts you most often need, in the order you need them. File What it is Format /llms.txt Index of every documentation page with one-line descriptions and links to the Markdown twins Markdown (llms.txt convention) /llms-full.txt The entire documentation concatenated, generated from the same source as the HTML Markdown /docs/<path>.md A Markdown twin of every HTML page at the same path, for example /docs/protocol/predicates.md; each HTML page links to it with <link rel=\"alternate\" type=\"text/markdown\"> Markdown /.well-known/lopw.json Protocol descriptor: names, flow, wire placement, signing, predicates, states, error codes, floors, SDK exports, chain bindings and every documentation link JSON offer.json, receipt.json, terms.json JSON Schemas (draft 2020-12) derived from the contract types of the Stellar reference binding, with the XDR mapping in each description JSON Schema offer-evm.json, receipt-evm.json, terms-evm.json The same types for the EVM binding: Solidity struct fields, the exact EIP-712 type strings and the 0x-hex ABI wire form of SignedOffer and SignedReceipt JSON Schema /docs/vectors/v1.json Shared test vectors: exact XDR bytes, hashes and signatures every implementation must reproduce JSON /docs/api/openapi.json OpenAPI 3.1 description of the read-only HTTP API JSON /docs/search-index.json Title, section, headings and text of every page, used by the site search JSON Protocol version 1. Offers and receipts with any other version fail a claim with UnsupportedVersion. Wire: x402 v2 extension lopw. Offer: PaymentRequired.extensions.lopw.info.offer (SignedOffer), echoed in PaymentPayload.extensions.lopw. Receipt: PAYMENT-RESPONSE.extensions.lopw.receipt (SignedReceipt). The encoding of those two values is the only per-chain difference in the envelope: base64 ScVal XDR on Stellar, 0x-hex ABI encoding on EVM networks. Signing: on Stellar (reference binding) message = SHA-256(tag || network_id || XDR(registry) || XDR(body)), tags LOPW/OFFER/v1 and LOPW/RECEIPT/v1, network_id = SHA-256(passphrase), Ed25519 over the 32-byte message by a dedicated service key that cannot move stake. On EVM networks the same fields are EIP-712 typed data under the domain LOPW / 1 / chainId / registry, signed with secp256k1. Networks: one protocol, the same semantics on every chain. testnet and mainnet are Stellar (stellar:testnet, stellar:pubnet); base-sepolia, base, sepolia, ethereum, arbitrum-sepolia, arbitrum, optimism-sepolia, optimism, polygon-amoy, polygon, avalanche-fuji, avalanche are eip155:<chainId>; Solana is a planned binding. Per-network ids, addresses and deployment status: Networks and networks[] in lopw.json. Predicates: 1 Equivocation, 2 Terms mismatch, 3 Stale data (served_at \u2212 data_as_of > max_data_age; 0 means not applicable), 4 Bad status, 5 Size limit, 6 Oracle deviation (|reported \u2212 oracle| \u00d7 10000 > bps \u00d7 oracle against the observation the oracle returns for the cited timestamp). Payout: min(max_claim, stake remaining), capped by max_payout if set, to receipt.buyer; one paid claim per (service_id, payment_reference); at most 50% of epoch-start stake per epoch. Service states: Pending, Active, Suspended, Unbonding, Closed. Pay only Active services. Floors: min_stake at least 100 USDC (1,000,000,000 units of 7 decimals); claim window at most 24 h, at most 1 h with P6; unbonding delay = largest claim window ever + 1 h; admin time lock 26 h. Not slashable: invalid signature, missing receipt, receipt citing an offer you never saw, answer quality, latency only you observed. Read its bond: the service\u2019s own GET /v1/warranty if it exposes one, GET /v1/{network}/services/{id} on the public API, or get_service and get_terms on the registry. Require state == \"Active\", stake at or above your threshold, the predicates you care about in terms.predicates, and an offer max_claim at or above your floor. Verify the offer signature against get_key(service_id, offer.key_version), check valid_until, and check that offer.terms_hash equals the service\u2019s current terms_hash and that price and asset match the x402 requirement. The buyer SDK does all of this in agent.fetch(url) with a BuyerPolicy: Getting started for agents (Markdown). Decode PAYMENT-RESPONSE.extensions.lopw.receipt (base64 ScVal XDR of SignedReceipt). Compute receipt_hash with the registry the Claims contract serves and verify the Ed25519 signature with get_key(service_id, receipt.key_version).public_key; the key must not have retired_at at or before served_at. Check offer_hash equals the hash of the offer you paid, buyer is your address, payment_reference is the settlement transaction, request_hash and response_hash match what you sent and received, response_bytes is the body length and status is the HTTP status. Evaluate each predicate in the terms exactly as in Predicates (Markdown); for P6 read price(oracle_asset, oracle_timestamp) from the oracle named in the terms. Any failure in step 2 or 3 means the response is untrusted and cannot be claimed. A breach in step 4 can be claimed until served_at + claim_window. Claims contractCopy claim(predicate_id: u32, offer: SignedOffer, receipt: SignedReceipt, extra: ClaimExtra) -> Result<ClaimOutcome, ClaimError> // extra = SecondReceipt(second_signed_receipt) for predicate 1, None otherwise // Ok(Paid(amount)) | Ok(Rejected(1 NotProven | 2 NotVerifiable)) | Err(1 UnknownService .. 11 SlashRateLimited) With the SDK: agent.claim(warranty). Without it: binding.submitClaim(signer, predicateId, offer, receipt, secondReceipt?) in @lopw/protocol, which waits one ledger if needed, submits, and decodes the outcome. Error codes and their causes: Claims (Markdown). Need Where Network totals GET /v1/{network}/stats Services, filtered by state GET /v1/{network}/services?state=Active&cursor=&limit= One service with claim totals GET /v1/{network}/services/{id} Claims, newest first GET /v1/{ne"}]