# LOPW documentation (complete) > The entire LOPW documentation as one Markdown file, generated from the same source as the HTML pages. Index: https://lopw.com/llms.txt. Each section below is one page; its canonical URL and Markdown twin are given under its title. --- # What is LOPW > 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. *LOPW documentation · Start here.* Canonical page: https://lopw.com/docs/ · This Markdown twin: https://lopw.com/docs/index.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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 works](https://lopw.com/docs/start/how-it-works.html): An animated walk through offer, payment, receipt, check, claim, validator and payout. - [Getting started](https://lopw.com/docs/getting-started.html): Three tracks: agents that buy, APIs that sell, apps that read bonds. - [Protocol reference](https://lopw.com/docs/protocol/): Every type, hash, predicate, function, error code and state, exactly as the contracts implement them. - [For AI agents](https://lopw.com/docs/agents/): Markdown twins, llms.txt, JSON Schemas and test vectors you can consume directly. ## In plain words 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. 1. **The seller puts down a deposit.** An API provider locks USDC stake in a smart contract (the Bond Registry) and publishes the exact conditions it promises to meet: its terms. 2. **Every sale comes with a signed receipt.** The buyer pays as usual over x402 and gets a receipt the seller signed, tied to that payment, that request and that response. 3. **Broken promise, automatic payout.** If 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’s promise, not a third party’s | A warranty card needs a claims department. LOPW’s 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’s own promise and, for prices, against an independent oracle | ## Why it exists 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’s 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. ## What LOPW is not - **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](https://lopw.com/docs/protocol/predicates.html), exist in version 1. ## Five words you will meet everywhere | 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’s signed promise for one call, shown before payment in the 402 response. | | **Receipt** | The seller’s 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](https://lopw.com/docs/start/glossary.html) has every term. If you prefer to see it move, go to [How a warranty works](https://lopw.com/docs/start/how-it-works.html). ## Where next - [Who it is for](https://lopw.com/docs/start/who-it-is-for.html): Agents, API sellers and platforms: what each gets and what each must do. - [Networks](https://lopw.com/docs/networks/): One protocol on every chain: Stellar as the reference binding, the EVM networks, and the planned Solana binding. - [FAQ](https://lopw.com/docs/start/faq.html): Short answers to the questions people ask first. - [Security model](https://lopw.com/docs/deep-dive/security.html): Trust assumptions, threats, mitigations and known limits. - [Proofs and scenarios](https://lopw.com/docs/deep-dive/proofs-and-scenarios.html): What has been demonstrated on a live network, and how to rerun it. --- # How a warranty works > An animated, step-by-step walk through a LOPW warranty: offer, payment, receipt, local check, claim, validator, and payout or rejection. *LOPW documentation · Start here.* Canonical page: https://lopw.com/docs/start/how-it-works.html · This Markdown twin: https://lopw.com/docs/start/how-it-works.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. 1. **Warranty offer** The 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. 2. **Payment** The agent verifies the offer signature against the key registered on-chain, reads the service’s 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. 3. **Service receipt** The 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’s freshness. 4. **Local check** The 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. 5. **Claim** If 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. 6. **Validator** The 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. 7. **Paid or rejected** A proven breach pays the buyer named in the receipt `min(max_claim, stake remaining)` from the seller’s 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. ## Who does what | 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’s 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 | ## The happy path costs nothing extra 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. ## When a promise is broken 1. The agent’s local check finds a breached predicate, for example `served_at − data_as_of` above the promised `max_data_age`. 2. 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. 3. 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. 4. The validator decides from the evidence alone. For P6 it reads the named oracle’s observation on-chain. 5. A proven breach pays `min(max_claim, stake remaining)` to the buyer named in the receipt, from the seller’s 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. ## What cannot be claimed 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](https://lopw.com/docs/start/who-it-is-for.html), or jump to the exact rules in the [claim lifecycle](https://lopw.com/docs/protocol/claims.html). --- # Who it is for > What LOPW gives agents, API sellers and platforms, what each has to do, and where each should start reading. *LOPW documentation · Start here.* Canonical page: https://lopw.com/docs/start/who-it-is-for.html · This Markdown twin: https://lopw.com/docs/start/who-it-is-for.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. ## Agents and the people who run them Your agent pays small amounts, often, to services nobody vetted by hand. With the buyer SDK it reads a seller’s 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’s stake when a breach is proven, in one transaction | Call `agent.claim(warranty)` inside the window | Start with [Getting started for agents](https://lopw.com/docs/guides/agents.html). ## API sellers 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](https://lopw.com/docs/guides/sellers.html). ## Platforms, dashboards and marketplaces Everything LOPW does on-chain is public: every service’s 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](https://lopw.com/docs/guides/apps.html) and the [HTTP API reference](https://lopw.com/docs/protocol/api.html). ## Protocol engineers and researchers The [protocol reference](https://lopw.com/docs/protocol/) describes every type, hash, predicate, function and error exactly as the contracts implement them, with shared test vectors. The [deep dive](https://lopw.com/docs/deep-dive/security.html) covers the threat model, the economics and the reasoning behind each rule. --- # Glossary > Every LOPW term in one place, from stake and terms to predicates, epochs and key versions, with links to where each is specified. *LOPW documentation · Start here.* Canonical page: https://lopw.com/docs/start/glossary.html · This Markdown twin: https://lopw.com/docs/start/glossary.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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](https://lopw.com/docs/protocol/bond-registry.html#states) - **allowed_status**: P4 parameter: the HTTP statuses the seller may charge for. A receipt with any other status proves a breach. [P4](https://lopw.com/docs/protocol/predicates.html#p4) - **Bond Registry**: The contract that holds stake per service and stores each service’s terms versions and signing keys. Only the Claims contract may slash. [Reference](https://lopw.com/docs/protocol/bond-registry.html) - **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](https://lopw.com/docs/protocol/bindings.html) - **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](https://lopw.com/docs/protocol/claims.html) - **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](https://lopw.com/docs/protocol/claims.html) - **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](https://lopw.com/docs/protocol/hashing-and-signing.html) - **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](https://lopw.com/docs/protocol/bond-registry.html#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](https://lopw.com/docs/protocol/bond-registry.html#fees) - **Key version**: Index of a service’s Ed25519 signing key. Rotation retires the old version with a timestamp; receipts served before it stay verifiable. [Rotate a key](https://lopw.com/docs/guides/rotate-key.html) - **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’s signed promise for one call, carried in the 402 response under `extensions.lopw`. [Fields](https://lopw.com/docs/protocol/offer-and-receipt.html#offer) - **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](https://lopw.com/docs/protocol/predicates.html#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](https://lopw.com/docs/protocol/predicates.html) - **Receipt (service receipt)**: The seller’s signed statement of what it served and under which offer, carried in `PAYMENT-RESPONSE` under `extensions.lopw`. [Fields](https://lopw.com/docs/protocol/offer-and-receipt.html#receipt) - **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’s account. It signs offers and receipts and cannot move stake. - **Slash**: The registry transferring part of a service’s 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’s 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’s 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](https://lopw.com/docs/protocol/offer-and-receipt.html#terms) - **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](https://lopw.com/docs/protocol/bond-registry.html#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’s 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](https://lopw.com/docs/protocol/x402.html) --- # FAQ > 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. *LOPW documentation · Start here.* Canonical page: https://lopw.com/docs/start/faq.html · This Markdown twin: https://lopw.com/docs/start/faq.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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](https://lopw.com/docs/protocol/x402.html) **Is this escrow?** No. The payment settles normally. What is locked is the *seller’s* 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](https://lopw.com/docs/protocol/predicates.html) **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](https://lopw.com/docs/protocol/bond-registry.html#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’s start, so stake can leave no faster than a normal withdrawal would allow. [Threats](https://lopw.com/docs/deep-dive/security.html) **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](https://lopw.com/docs/deep-dive/economics.html) **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](https://lopw.com/docs/protocol/bindings.html) **Where can I see it working?** The [demo](https://lopw.com/demo/) replays a real claim paid on a live network, and the [proofs](https://lopw.com/proofs/) page lists every transaction and test. [Proofs and scenarios](https://lopw.com/docs/deep-dive/proofs-and-scenarios.html) **I am an AI agent. Where do I start?** [Agents start here](https://lopw.com/docs/agents/): Markdown twins of every page, `/llms.txt`, `/llms-full.txt`, JSON Schemas and test vectors. --- # Getting started > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/getting-started.html · This Markdown twin: https://lopw.com/docs/getting-started.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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)](https://lopw.com/docs/guides/agents.html): Install the buyer SDK, set a policy, fetch, claim. - [For API providers (sellers)](https://lopw.com/docs/guides/sellers.html): Register, stake, add the middleware to your x402 server. - [For app builders](https://lopw.com/docs/guides/apps.html): Services, claims, events and live streams from the public API. ## Before you start > **Amounts** > > Amounts are integers in the stake token’s 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. ## How-to guides Task-sized recipes once you are set up: - [Add a warranty to an x402 API](https://lopw.com/docs/guides/add-warranty.html) - [Make an agent pay only bonded services](https://lopw.com/docs/guides/bonded-only.html) - [File a claim](https://lopw.com/docs/guides/file-claim.html) - [Choose predicates and limits](https://lopw.com/docs/guides/predicates-limits.html) - [Rotate a signing key](https://lopw.com/docs/guides/rotate-key.html) - [Withdraw stake safely](https://lopw.com/docs/guides/unbonding.html) - [Read a bond from any app](https://lopw.com/docs/guides/read-bond.html) - [Run the scenarios yourself](https://lopw.com/docs/guides/run-scenarios.html) --- # Getting started for agents > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/agents.html · This Markdown twin: https://lopw.com/docs/guides/agents.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt The buyer SDK wraps x402’s fetch. Before paying it verifies the seller’s signed offer, reads the service’s 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. ## 1. Install ```sh 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. ## 2. Create a buyer with a policy 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. ```ts 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 | ## 3. Fetch a paid resource ```ts 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’s abort hook, when: - the offer names a registry other than the binding’s (*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’s `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’s 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’s 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` | ## 4. Claim when a promise is broken ```ts 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: "…" } } ``` A proven breach pays your agent from the seller’s 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](https://lopw.com/docs/guides/bonded-only.html), [file a claim](https://lopw.com/docs/guides/file-claim.html), or read the [predicates](https://lopw.com/docs/protocol/predicates.html) your agent evaluates. --- # Getting started for sellers > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/sellers.html · This Markdown twin: https://lopw.com/docs/guides/sellers.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. ## 1. Install ```sh npm install @lopw/seller-middleware @lopw/protocol @x402/express @x402/core @x402/stellar # LOPW packages: access for approved integrators ``` ## 2. Generate a signing key Each service signs with an Ed25519 key that is separate from the seller’s 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. ```ts 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 ``` ## 3. Choose your terms Terms are the predicates you promise, their limits, and a claim window. Promise only what you can keep: see [Choose predicates and limits](https://lopw.com/docs/guides/predicates-limits.html). This example is the warranty of the reference FX quote service. ```ts 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 }, }; ``` ## 4. Register and stake Registration needs the seller’s 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`. **TypeScript** ```ts 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 ``` **Contract calls** *Bond Registry* ```text 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](https://lopw.com/docs/protocol/offer-and-receipt.html#terms-rules)), `NotAllowlisted` or `Paused` during a guarded launch, `StakeCapReached` when a launch limit is hit. ## 5. Add the middleware 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: {} }`. ```ts 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’s state and terms | | `serviceId` | Your 32-byte service ID as hex | | `signingSeed` | Hex Ed25519 seed of the service signing key (not the seller’s account key) | | `maxClaim` | Most a single receipt can pay out, in the asset’s 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. ## 6. Report freshness and oracle evidence | 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 | ```ts 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’s last completed observation *before* quoting and cite the timestamp the oracle returned; the validator will fetch that same observation on-chain. See [P6](https://lopw.com/docs/protocol/predicates.html#p6) for the exact test. > **Why it pays to bond** > > Agents with a policy read your stake and terms before they pay. A bonded service is one they can trust without a human in the loop; the stake is the signal. Next: [the checklist version](https://lopw.com/docs/guides/add-warranty.html), [rotate a key](https://lopw.com/docs/guides/rotate-key.html), [withdraw stake safely](https://lopw.com/docs/guides/unbonding.html). --- # Getting started for app builders > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/apps.html · This Markdown twin: https://lopw.com/docs/guides/apps.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. ## Conventions - **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](https://lopw.com/docs/api/openapi.json). ## First calls **HTTP** ```http 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": "…64 hex…", "seller": "G…", "state": "Active", "stake": "2000000000", "min_stake": "1000000000", // "pending_withdrawal": "0", "terms_hash": "…", "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": "…", "service_id": "…", "predicate_id": 6, "outcome": "paid", "reason": null, "buyer": "G…", "amount": "50000000", // "payment_reference": "…", "transaction": "…", "ledger": 5021500, "occurred_at": 1790991420 } ], "next_cursor": null } ``` **JavaScript** ```ts 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. ## Live events ```ts 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=. ``` 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](https://lopw.com/docs/protocol/bond-registry.html#events) and [claims](https://lopw.com/docs/protocol/claims.html#events) references. ## Reading the chain directly The API is a convenience; the chain is the source of truth. Any app can read the same data with the protocol package’s binding, which simulates read-only contract calls from a throwaway account and never signs anything: ```ts 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](https://lopw.com/docs/protocol/api.html), or [read one service’s bond](https://lopw.com/docs/guides/read-bond.html) from its own discovery endpoint. --- # How-to guides > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides.html · This Markdown twin: https://lopw.com/docs/guides.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Each guide assumes you have read the [getting started](https://lopw.com/docs/getting-started.html) track for your role. Code uses the real SDK and API names. - [Add a warranty to an x402 API](https://lopw.com/docs/guides/add-warranty.html): The seller checklist from key to first bonded 402. - [Make an agent pay only bonded services](https://lopw.com/docs/guides/bonded-only.html): Policy fields, what the SDK refuses on its own, and how refusals surface. - [File a claim](https://lopw.com/docs/guides/file-claim.html): From breach to payout, with the window, the outcomes and the errors. - [Choose predicates and limits](https://lopw.com/docs/guides/predicates-limits.html): What each predicate needs from you and how to set limits you can keep. - [Rotate a signing key](https://lopw.com/docs/guides/rotate-key.html): Retire a key without stranding a single receipt. - [Withdraw stake safely](https://lopw.com/docs/guides/unbonding.html): Partial and full exits, the delay, cancellation, and what stays slashable. - [Read a bond from any app](https://lopw.com/docs/guides/read-bond.html): A service's own discovery endpoint, the public API and the chain. - [Run the scenarios yourself](https://lopw.com/docs/guides/run-scenarios.html): The demo agent, the fault modes and the scheduled catalogue. --- # Add a warranty to an x402 API > Checklist to turn an existing x402 endpoint into a bonded one: signing key, terms, registration, stake, the lopw extension and the freshness header. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/add-warranty.html · This Markdown twin: https://lopw.com/docs/guides/add-warranty.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Turn an existing x402 endpoint into a bonded one. Six steps; the detailed code is in [Getting started for sellers](https://lopw.com/docs/guides/sellers.html). 1. Create a dedicated Ed25519 signing key for the service (`signingKeyFromSeed`, `publicKeyHex`) and store its seed as a server secret. 2. Pick the predicates you can honour and set their limits ([guide](https://lopw.com/docs/guides/predicates-limits.html)). Every parameter is part of the hashed terms. 3. 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`. 4. Deposit at least 100 USDC: `binding.deposit(seller, serviceId, amount)`. When stake reaches `min_stake` the service turns `Active`. 5. Register the `lopw` extension on your x402 resource server (`createLopwExtension`, `registerExtension`), add `lopwRequestContext()` before `paymentMiddleware`, and enable it on each route with `extensions: { lopw: {} }`. 6. 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`. ```ts 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’s `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](https://lopw.com/docs/protocol/api.html): state `Active`, stake as expected. --- # Make an agent pay only bonded services > Configure the buyer policy so an agent refuses unbonded, under-staked or under-covered sellers before any money moves, and handle refusals. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/bonded-only.html · This Markdown twin: https://lopw.com/docs/guides/bonded-only.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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 | ```ts 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](https://lopw.com/docs/deep-dive/economics.html) page works through it. --- # File a claim > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/file-claim.html · This Markdown twin: https://lopw.com/docs/guides/file-claim.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt A claim is one transaction with one outcome. The SDK builds it from the record it kept at payment time. 1. 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. 2. 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. 3. Read the result: `{ outcome: { paid: amount } | { rejected: "not_proven" | "not_verifiable" }, transaction }`. **TypeScript** ```ts 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 ``` **Contract call** *Claims contract* ```text claim(predicate_id: u32, offer: SignedOffer, receipt: SignedReceipt, extra: ClaimExtra) -> Result // 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’ `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](https://lopw.com/docs/protocol/claims.html#errors). | | 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. --- # Choose predicates and limits > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/predicates-limits.html · This Markdown twin: https://lopw.com/docs/guides/predicates-limits.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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’s 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’s 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’s receipt | ## What the registry rejects `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 ## Changing terms later 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](https://lopw.com/docs/guides/unbonding.html)). Use the [predicate explorer](https://lopw.com/docs/protocol/predicates.html#explorer) to see how a sample receipt is judged under different limits. --- # Rotate a signing key > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/rotate-key.html · This Markdown twin: https://lopw.com/docs/guides/rotate-key.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. 1. Generate a new Ed25519 seed and compute its public key (`signingKeyFromSeed`, `publicKeyHex`). 2. Call `rotate_key(service_id, signing_key)` with the seller’s account key. It returns the new `key_version` (previous + 1), stores the new key with `registered_at = now`, and sets the old key’s `retired_at = now`. A `KeyRotated` event is emitted. 3. 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 Registry* ```text 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`. --- # Withdraw stake safely > Partial and full withdrawals through request_unbond, the unbonding delay, cancel_unbond, withdraw, and what remains slashable, with the exact contract rules. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/unbonding.html · This Markdown twin: https://lopw.com/docs/guides/unbonding.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. 1. Call `request_unbond(service_id, amount)` from `Active` or `Suspended`. If `stake − amount ≥ 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. 2. 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. 3. 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 Registry* ```text 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](https://lopw.com/docs/protocol/bond-registry.html#calculator). --- # Read a bond from any app > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/read-bond.html · This Markdown twin: https://lopw.com/docs/guides/read-bond.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Any app, dashboard or agent can see what a seller stands behind before paying it a cent. ## The service's own discovery endpoint 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`: ```http GET /v1/warranty // { // "network": "testnet", // "registry": "C…", // Bond Registry contract // "service_id": "…64 hex…", // "state": "Active", // "stake": "2000000000", // 200 USDC, 7-decimal units // "terms_hash": "…64 hex…", // "price": { "amount": "100000", "asset": "C…", "display": "$0.01" } // } ``` ```ts 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 } ``` ## The public API ```http 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](https://lopw.com/docs/guides/apps.html) for conventions and the [API reference](https://lopw.com/docs/protocol/api.html) for every endpoint. ## Straight from the chain ```ts 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’s `terms_hash` can always be resolved. --- # Run the scenarios yourself > 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. *LOPW documentation · Guides.* Canonical page: https://lopw.com/docs/guides/run-scenarios.html · This Markdown twin: https://lopw.com/docs/guides/run-scenarios.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt The demo agent is a short script on top of the buyer SDK. Point it at a reference service, choose a fault, and it prints each step with a transaction hash of its own. A scheduled catalogue runs the same scenarios on a timer and records every run for the demo and proofs pages. ## The demo agent ```sh 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’s Stellar secret key; the account needs USDC on the same network as the contracts (`LOPW_NETWORK`, default testnet). The agent reads the service’s bond, pays $0.01 through x402 with the policy `minStake: 100 USDC, requiredPredicates: REQUIRE, minMaxClaim: 1 USDC`, verifies the receipt and claims if a predicate is breached. `FAULT` is sent as the `x-lopw-fault` request header. A reference service honours it only when its operator has enabled per-request fault injection; otherwise its own configured mode applies. | Fault mode | What the seller does | Expected result | | --- | --- | --- | | `NORMAL` | Serves correctly | Receipt verified; warranty kept; no claim | | `BAD_PRICE` | Quotes 10% below the oracle, citing the real observation | P6 claim paid (FX service) | | `STALE_DATA` | Serves data an hour old with an honest `data_as_of` | P3 claim paid | | `WRONG_TERMS` | Signs the receipt with a different `terms_hash` | P2 claim paid | | `DUPLICATE_RECEIPT` | Signs a second receipt with a different `response_hash` for the same payment | P1 claim paid | | `BAD_STATUS` | Charges for HTTP 203 when only 200 is allowed | P4 claim paid (ledger service) | | `OVERSIZE` | Returns more than the 4 KiB promised | P5 claim paid (ledger service) | | `BAD_SIGNATURE` | Sends a receipt with a broken signature | SDK flags the response untrusted; no claim; no penalty | ## The reference services | Service | What it sells | Warranty | | --- | --- | --- | | FX quote API | EUR/USD quoted against USD, $0.01 per call | P1, P2, P3 (600 s), P4 (`[200]`), P6 (EUR on the fiat-rates oracle, 50 bps, 600 s); claim window 1 h | | Ledger data API | Latest-ledger facts and account balances | P1, P2, P3 (30 s), P4 (`[200]`), P5 (4096 bytes); claim window 1 h | ## The scheduled catalogue Eleven named scenarios run against the live reference services with real x402 payments, on a schedule (every six hours by default), and every run is judged against the scenario’s expected outcome and recorded with its payment and claim transactions, payout and stake before and after. Payouts are recycled into the seller’s stake so the services stay funded. | Scenario | Expected | | --- | --- | | Honest FX quote | Receipt verified; warranty kept; no claim; stake unchanged | | Quote 10% off the oracle | P6 claim paid 5 USDC | | Data older than promised | P3 claim paid 5 USDC | | Receipt cites different terms | P2 claim paid 5 USDC | | Two different receipts for one payment | P1 claim paid 5 USDC | | Broken receipt signature | Untrusted; no claim; no penalty | | Charged for an unpromised status | P4 claim paid 1 USDC | | Response larger than promised | P5 claim paid 1 USDC | | Same claim filed twice | Second submission fails with `AlreadyClaimed` | | Claim against an honest receipt | Rejected as `NotProven`; no payout | | Agent policy refuses an under-staked seller | Payment refused by policy; no transaction | ```http GET /v1/testnet/scenarios?runs=5 // [ { "id": "bad-price", "title": "…", "summary": "…", "why": "…", "steps": [ … ], "expected": "…", "service": "fx", // "totals": { "runs": 12, "passed": 12 }, // "runs": [ { "id": 1, "started_at": …, "finished_at": …, "outcome": "pass", "observed": "…", "payment_tx": "…", "claim_tx": "…", "payout": "50000000", "stake_before": "…", "stake_after": "…", "service_id": "…" } ] } ] ``` The [demo](https://lopw.com/demo/) and [proofs](https://lopw.com/proofs/) pages are built from this feed. What each scenario demonstrates and why it matters is in [Proofs and scenarios](https://lopw.com/docs/deep-dive/proofs-and-scenarios.html). --- # Protocol overview > 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. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/ · This Markdown twin: https://lopw.com/docs/protocol/index.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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 terms](https://lopw.com/docs/protocol/offer-and-receipt.html): Every field, type and rule. - [Hashing and signing](https://lopw.com/docs/protocol/hashing-and-signing.html): Domain separation, exact byte layout, test vectors. - [Predicates P1 to P6](https://lopw.com/docs/protocol/predicates.html): Exact conditions, edge rules, interactive explorer. - [Claims](https://lopw.com/docs/protocol/claims.html): Seven checks in order, outcomes, errors, events. - [Bond Registry](https://lopw.com/docs/protocol/bond-registry.html): Functions, events, states, floors, fees, time locks, unbonding. - [x402 extension](https://lopw.com/docs/protocol/x402.html): The lopw extension on the wire. - [HTTP API](https://lopw.com/docs/protocol/api.html): Read-only endpoints and the OpenAPI document. - [Chain bindings](https://lopw.com/docs/protocol/bindings.html): Portable core and the Stellar binding. ## Roles 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’s 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 | ## Protocol flow Warranty offer → Payment → Service receipt → Deterministic claim → Validator → Slash or reject 1. 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. 2. The buyer requests the resource. The seller answers `402` with the normal x402 payment requirements plus a signed warranty offer under `extensions.lopw`. 3. The buyer verifies the offer signature against the key registered on-chain and reads the service’s state, stake and terms. 4. The buyer pays through standard x402, echoing the offer in its payment payload. The facilitator settles on-chain. 5. 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. 6. The buyer verifies the receipt and evaluates every predicate locally. 7. If a breach is found, anyone may submit the offer and receipt to the Claims contract inside the claim window. 8. The Claims contract runs the matching validator. A proven breach pays the buyer named in the receipt from the seller’s 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](https://lopw.com/docs/start/how-it-works.html). ## Contracts | 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](https://lopw.com/docs/protocol/bond-registry.html#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](https://lopw.com/docs/protocol/claims.html#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](https://lopw.com/docs/protocol/bond-registry.html#time-locks)). ## Portable and binding-specific | 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](https://lopw.com/docs/protocol/bindings.html). ## Out of scope in version 1 - 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. --- # Offer, receipt and terms > 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. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/offer-and-receipt.html · This Markdown twin: https://lopw.com/docs/protocol/offer-and-receipt.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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](https://lopw.com/docs/schemas/terms.json), [offer.json](https://lopw.com/docs/schemas/offer.json), [receipt.json](https://lopw.com/docs/schemas/receipt.json). In JSON, 32-byte values are lowercase hex, i128 amounts are decimal strings, u64 times are Unix seconds, and addresses are strkeys. ## Terms A service’s registered warranty terms. Hashed ([terms_hash](https://lopw.com/docs/protocol/hashing-and-signing.html#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 | 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 | 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 | ### OracleTerms | 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` | ### Validity rules 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 ≤ claim_window ≤ max_claim_window` (86,400 s on the first deployment) - P3 listed ⇒ `max_data_age > 0`; P4 listed ⇒ `allowed_status` non-empty; P5 listed ⇒ `max_response_bytes > 0` - P6 listed ⇒ `claim_window ≤ max_oracle_claim_window` (3,600 s) and `oracle = Some(o)` with `o.max_oracle_age > 0` *Terms from the shared test vectors* ```json { "predicates": [3, 4, 6], "claim_window": 3600, "max_data_age": 30, "allowed_status": [200], "max_response_bytes": 4096, "oracle": { "oracle_contract": "C…", "oracle_asset": { "Other": "EUR" }, "max_deviation_bps": 50, "max_oracle_age": 600 } } ``` ## Offer The seller’s 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’s *current* terms | | `price` | i128 | Price of this call, in the asset’s 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’s current key. ## Receipt The seller’s 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’s) | | `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 | ### OracleEvidence | 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’s decimals | The contract does not check that the payment happened. The seller’s signature over `payment_reference` and `buyer` is its admission that it did, and any payout comes from the seller’s own stake. The middleware takes `buyer` and `payment_reference` from the facilitator’s settlement result and only issues receipts for offers it signed itself. ## Registry records ### Service | 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 version | | `max_claim_window` | u64 | Largest claim window of any terms version ever registered; sets the unbonding delay | | `pending_withdrawal` | i128 | Stake requested for withdrawal and still slashable | | `unlock_at` | u64 | When the pending withdrawal may be paid out | | `full_exit` | bool | True when the pending withdrawal closes the service | | `epoch_start`, `epoch_base_stake`, `epoch_slashed` | u64, i128, i128 | Slash rate limit bookkeeping | | `fee_accrued_at` | u64 | Time up to which the annual stake fee has been collected | ### SigningKey | Field | Type | Meaning | | --- | --- | --- | | `public_key` | BytesN<32> | Ed25519 public key | | `registered_at` | u64 | When this version was registered | | `retired_at` | u64 | 0 while current; otherwise the time it was rotated out | ### ProtocolConfig, LaunchLimits, FeeConfig | Type | Fields | Set | | --- | --- | --- | | `ProtocolConfig` | `min_stake_floor` i128, `max_claim_window` u64, `max_oracle_claim_window` u64, `unbond_margin` u64, `slash_epoch_cap_bps` u32 | Once, at registry construction | | `LaunchLimits` | `max_stake_per_service` i128, `max_total_stake` i128, `max_payout` i128, `allowlist_only` bool (0 means no limit) | Tightening immediate; loosening time-locked | | `FeeConfig` | `treasury` Address, `registration_fee` i128, `stake_fee_bps_per_year` u32 | Changes time-locked | ## Validator types | Type | Definition | | --- | --- | | `ClaimExtra` | `None` \| `SecondReceipt(SignedReceipt)`: predicate-specific evidence; the second receipt for P1, nothing for P2 to P6 | | `ValidationError` | `NotProven = 1` \| `NotVerifiable = 2`: why a validator did not prove a breach | | `PriceData` | `{ price: i128, timestamp: u64 }`: a SEP-40 price record as the oracle returns it | | `Sep40Oracle` | `price(asset, timestamp) -> Option`, `decimals() -> u32`: what the P6 validator reads | --- # Hashing and signing > 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. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/hashing-and-signing.html · This Markdown twin: https://lopw.com/docs/protocol/hashing-and-signing.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. ## Domain separation The signed message is the SHA-256 hash of four parts, in order: 1. A type tag as ASCII: `LOPW/OFFER/v1` (13 bytes) or `LOPW/RECEIPT/v1` (15 bytes). 2. The network ID: the 32-byte SHA-256 of the network passphrase, so a signature for one network is invalid on another. 3. The ScVal XDR of the Bond Registry address (40 bytes for a contract address), so signatures cannot be replayed against another deployment. 4. The ScVal XDR of the body: the `Offer` or `Receipt` struct, which never contains the signature. *Signing* ```text 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’s registry address for receipts. ```text message = SHA-256( tag || network_id || ScVal-XDR(registry) || ScVal-XDR(body) ) tag = "LOPW/OFFER/v1" (13 bytes) or "LOPW/RECEIPT/v1" (15 bytes), ASCII network_id = SHA-256(network passphrase), 32 bytes registry = ScvAddress XDR of the Bond Registry contract, 40 bytes body = ScvMap XDR of Offer (464 bytes in the test vectors) or Receipt (912 bytes) signature = Ed25519.sign(service_signing_key, message), 64 bytes ``` Test-vector values (network Test SDF Network ; September 2015, registry `CA2GYTFIGBLIL42H4BQ6R7AETXZ5D5Z357CLWXBDBUXP6ESCGGRDFHTC`): | Value | Hex | | --- | --- | | network_id | `cee0302d59844d32bdca915c8203dd44b33fbb7edc19051ea37abedf28ecd472` | | offer_hash | `324ef666a1b8cefea457e24eaf0fcc583ae711242d974e20b4eb74c7b44d4384` | | offer_signature | `37a1c624c506953fecd0d76c6411c662ef3cac44bca1fb6e7b1817d787e2825b90915803a62fccbd6e0f28193e0d4c8a102092c9f84816cdd7d1cb10e3b9c108` | | receipt_hash | `781d98c00ebc497c1fe9b3272ce02b1fae56e87c3a74083b454bad1883af429c` | | receipt_signature | `05df38bb643802c3609ebc227d2591e9f42645a1e90fbfb92edde4439d06a4ac4f1094b33d1a7a8500e71d32404d4a9876c70f9f26b204ef0e6928ce54e0fd07` | ## ScVal XDR encoding rules The canonical bytes are the ScVal XDR of each Soroban `#[contracttype]`, produced identically by the contracts and by `@lopw/protocol`: - A struct is a `ScvMap` whose keys are `ScvSymbol` field names **in sorted order**, so `asset` comes before `key_version` and `version` is last. - A unit enum variant is a `ScvVec` of one symbol: `[Symbol("None")]`. A tuple variant is `[Symbol("Some"), value]`. Optional blocks (`Terms.oracle`, `Receipt.oracle`) use these variants because contract types cannot hold `Option`. - `u32` is `ScvU32` (4 bytes), `u64` is `ScvU64` (8 bytes), `i128` is `ScvI128` (16 bytes, high then low), 32-byte values are `ScvBytes` (length prefix then bytes, padded to 4), addresses are `ScvAddress`. - The signed body and the signature are separate types: `SignedOffer { offer, signature }` and `SignedReceipt { receipt, signature }`. “The struct with its signature left out” is therefore simply the body’s XDR, with no special-case encoding. On the wire, `SignedOffer` and `SignedReceipt` travel as base64 ScVal XDR (`encodeSignedOffer`, `decodeSignedReceipt` and friends in `@lopw/protocol`). ## Terms hash and service ID ```text terms_hash = SHA-256( XDR(terms) ) // no domain separation; keyed by (service_id, terms_hash) in the registry service_id = SHA-256( XDR(registry contract address) || XDR(counter as u64) ) // counter starts at 0 and increments per registration ``` `terms_hash` can be computed off-chain with `termsHash(terms)` or on-chain with the registry’s read-only `terms_hash(terms)`. ## Request and response hashes ```text request_hash = SHA-256( METHOD + "\n" + path?query + "\n" + lowercase-hex(SHA-256(body)) ) // METHOD upper-case; empty body hashes too response_hash = SHA-256( response body as sent ) ``` The middleware hashes the raw request body and `originalUrl`; the buyer SDK recomputes both hashes from what it actually sent and received and marks the response untrusted on any mismatch. Test-vector values: `GET /v1/quote?pair=EUR-USD` with an empty body gives `request_hash 3ad9d6da5164b75376fd879984ceece7f1b32b76e987d3fc663a04c549664c0e`; the 38-byte body `{"pair":"EUR-USD","rate":"1.12509966"}` gives `response_hash 97b8da906999282c9ddc6e4f842df5c37f0db9a607436f582815a2fbe85eed82`. ## Signing keys and rotation Each service registers an Ed25519 public key separate from the seller’s account. The server holds the 32-byte seed; it can sign offers and receipts but cannot move stake. Keys are stored by `key_version`, starting at 1. - `rotate_key` stamps the previous key with `retired_at = now` and stores the new key as version + 1. - A receipt signed with version `k` is accepted only if `retired_at(k) == 0` or `receipt.served_at < retired_at(k)`; otherwise the claim fails with `KeyRetired`. Old receipts stay claimable; a leaked old key cannot produce new ones. - Offers are verified against the key at `offer.key_version`; an unknown version fails with `BadSignature`. ## Invalid signatures Soroban’s `ed25519_verify` aborts the transaction rather than returning an error. A claim carrying a signature that does not verify therefore fails as a host error, not with the `BadSignature` code, which is reserved for a missing key version. The outcome is the same: the claim is rejected, nothing is recorded and the seller is not penalised. Off-chain, `verifyOffer` and `verifyReceipt` return `false` and the buyer SDK records *receipt signature invalid* as a problem. ## Replay protection - A paid claim is recorded under `(service_id, payment_reference)`. A second claim for the same payment fails with `AlreadyClaimed`, whichever receipt it presents. A *rejected* claim records nothing and does not block a later valid claim. - `offer_nonce` makes every offer hash unique. The buyer SDK refuses an offer whose nonce it has already paid. - `valid_until` bounds how long an offer can be paid. The SDK refuses expired offers. - The network ID and registry address in every hash stop cross-network and cross-deployment replay. - Claim records live in persistent storage with a lifetime extended on every access (threshold 30 days, extended to 60 days, in ledgers), far longer than any claim window. ## Shared test vectors [v1.json](https://lopw.com/docs/vectors/v1.json) fixes the exact bytes of version 1 hashing and signing: the terms, offer and receipt from this page, their XDR, hashes and signatures, computed by the contract types and reproduced byte for byte by the TypeScript package. Any change to an encoding shows up as a diff. The signing seed in the vectors is a fixed test value and must never be used for a real service. | Vector | Value | | --- | --- | | Network passphrase | `Test SDF Network ; September 2015` | | `network_id` | `cee0302d59844d32bdca915c8203dd44b33fbb7edc19051ea37abedf28ecd472` | | `terms_hash` | `7261e3aa021d521d86d07e46dfc96d43978de8bff84651d71083d77a49514d35` | | `offer_hash` | `324ef666a1b8cefea457e24eaf0fcc583ae711242d974e20b4eb74c7b44d4384` | | `receipt_hash` | `781d98c00ebc497c1fe9b3272ce02b1fae56e87c3a74083b454bad1883af429c` | | `receipt_without_oracle_hash` | `7a186bf09547ac7f8485972d0da38d944dcddfe35c4ec26448e4dfc255df7194` | ```ts import { offerHash, receiptHash, termsHash, requestHash, signOffer, verifyReceipt, signingKeyFromSeed } from "@lopw/protocol"; const kp = signingKeyFromSeed(seedHex); const signed = signOffer(offer, kp, network.passphrase); // { offer, signature } const ok = verifyReceipt(signedReceipt, key.public_key, network.passphrase, network.registry); ``` --- # Predicates P1 to P6 > 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. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/predicates.html · This Markdown twin: https://lopw.com/docs/protocol/predicates.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt A predicate is one breach condition that a contract can decide from the evidence alone. Each service’s 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 ≠ offer.terms_hash` | | P3 Stale data | Data older than promised | Receipt | `served_at − data_as_of > max_data_age` | | P4 Bad status | Charged for a status the seller promised not to charge for | Receipt | `status ∉ 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](#p6) 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](https://lopw.com/docs/protocol/claims.html#checks)). ## Interactive explorer 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’s local evaluation. | Predicate | Proven when | Not proven / not verifiable when | | --- | --- | --- | | P1 | `extra` holds a second receipt with the same `service_id` and `payment_reference` whose fields differ in any way (both signatures already verified by Claims) | No second receipt, different payment, or an identical receipt | | P2 | `receipt.terms_hash != offer.terms_hash` | Hashes equal | | P3 | `served_at - data_as_of > max_data_age` | `data_as_of == 0` (not applicable) or `data_as_of > served_at`: not proven | | P4 | `status` not in `allowed_status` | Status is listed | | P5 | `response_bytes > max_response_bytes` | At or below the limit | | P6 | Evidence names another oracle or asset; or the returned tick is after `served_at` or older than `max_oracle_age`; or `\|reported_value - oracle_price\| * 10000 > max_deviation_bps * oracle_price` | No evidence block, no observation, or non-positive price: not verifiable; within tolerance: not proven | ## P1 Equivocation **Evidence:** the claimed receipt plus a second signed receipt in `extra = SecondReceipt(second)`. The Claims contract verifies the second receipt’s signature with the same key rules (including `KeyRetired`) before calling the validator, so the validator stays a pure function of its inputs. *P1* ```text proven ⇔ second.service_id == receipt.service_id ∧ second.payment_reference == receipt.payment_reference ∧ second != receipt // any field differs not proven ⇔ extra is None, or a different payment, or an identical receipt ``` The reference services’ 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. ## P2 Terms mismatch *P2* ```text proven ⇔ receipt.terms_hash != offer.terms_hash not proven ⇔ 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. ## P3 Stale data *P3* ```text not proven ⇔ data_as_of == 0 // "not applicable" never proves a breach ∨ data_as_of > served_at // no negative ages proven ⇔ served_at - data_as_of > max_data_age ``` Edge rule: `data_as_of = 0` means not applicable. Without this rule `served_at − 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. ## P4 Bad status *P4* ```text proven ⇔ status ∉ terms.allowed_status not proven ⇔ status ∈ terms.allowed_status ``` ## P5 Size limit *P5* ```text proven ⇔ response_bytes > terms.max_response_bytes not proven ⇔ response_bytes <= terms.max_response_bytes ``` ## P6 Oracle deviation 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’s 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’s own decimals. **Validator checks, in order:** 1. `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. 2. The validator calls `price(oracle_asset, oracle_timestamp)` on the oracle. No observation, or a price of zero or less, is **not verifiable**. 3. The returned observation’s own timestamp must not be after `served_at`, and `served_at − observed.timestamp` must not exceed `max_oracle_age`; otherwise the breach is **proven** as stale. 4. The deviation test uses exact integer math with no division, so no rounding can hide a breach: *P6 deviation test* ```text proven ⇔ |reported_value − oracle_price| × 10000 > max_deviation_bps × oracle_price not proven ⇔ otherwise // oracle_price is the value the validator read, not the receipt's field ``` ### Edge rules - **Tick rounding.** `price(asset, t)` rounds `t` down to the previous tick and returns that tick’s 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 − p| × 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’s `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 charge beyond the normal transaction fee on the first live P6 claim. ## Validator interface *contracts/types: Validator* ```rust pub trait Validator { fn validate(env: Env, terms: Terms, offer: Offer, receipt: Receipt, extra: ClaimExtra) -> Result<(), ValidationError>; } // Ok(()) breach proven // Err(NotProven = 1) evidence valid, no breach // Err(NotVerifiable = 2) the breach cannot be checked (P6: no oracle evidence or observation) ``` The Claims contract calls the validator through `try_validate`. `Ok(())` is proven; `Err(NotProven)` is recorded as `Rejected(1)`; any other failure, including a trapped oracle call, is recorded as `Rejected(2)`. `evaluatePredicate(id, evidence)` in `@lopw/protocol` mirrors every rule so a buyer knows before paying fees whether a claim will be paid. ## Not slashable > **Not provable against the seller** > > An invalid signature, a missing receipt, and a receipt citing an offer the buyer never saw cannot be proven against the seller: anyone could produce them. The buyer SDK flags each as an untrusted response and reports it; no claim is possible. A claim carrying a bad signature fails with no penalty to the seller. --- # Claims > The LOPW Claims contract: the seven checks in order (animated), outcomes, every error code, the payout rule, records, events and functions. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/claims.html · This Markdown twin: https://lopw.com/docs/protocol/claims.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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 contract* ```text claim(predicate_id: u32, offer: SignedOffer, receipt: SignedReceipt, extra: ClaimExtra) -> Result // ClaimOutcome: Paid(amount: i128) | Rejected(reason: u32) reason 1 = NotProven, 2 = NotVerifiable ``` ## The pipeline 1. **Version.** Both `offer.version` and `receipt.version` equal 1 (fails with: UnsupportedVersion) 2. **1 · Service.** The service exists in the registry and is not Closed (fails with: UnknownService) 3. **2 · Receipt signature.** Valid for the key at `receipt.key_version`, and that key was not retired before `served_at` (fails with: BadSignature · KeyRetired) 4. **3 · Offer.** `offer.registry` is this registry, `service_id` matches, `receipt.offer_hash` equals the offer hash, offer signature valid (fails with: OfferMismatch · BadSignature) 5. **4 · Claim window.** Terms found for `offer.terms_hash`; `served_at ≤ now ≤ served_at + claim_window` (fails with: OfferMismatch · WindowClosed) 6. **5 · Not paid before.** No paid claim recorded for `(service_id, payment_reference)` (fails with: AlreadyClaimed) 7. **6 · Predicate and stake.** `predicate_id` is in the terms; stake is above zero; for P1 the second receipt’s signature is verified too (fails with: PredicateNotOffered · NoStake) 8. **7 · Validator.** The validator for the predicate returns proven (fails with: Rejected(NotProven) · Rejected(NotVerifiable) · ValidatorNotSet) 9. **Payout.** Record the claim, then `slash(service_id, buyer, max_claim)` on the registry (fails with: SlashRateLimited · NoStake) ## Checks, in order 1. **Version.** `offer.version` and `receipt.version` are both 1; else `UnsupportedVersion`. 2. **Service.** `get_service(receipt.service_id)` succeeds and the state is not `Closed`; else `UnknownService`. 3. **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). 4. **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. 5. **Claim window.** `get_terms(service_id, offer.terms_hash)` succeeds (else `OfferMismatch`); `served_at ≤ now` and `now ≤ served_at + claim_window`; else `WindowClosed`. 6. **Not paid before.** No paid record exists for `(service_id, payment_reference)`; else `AlreadyClaimed`. 7. **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. 8. **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))`. 9. **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. ## Outcomes | Result | Recorded? | Event | Meaning | | --- | --- | --- | --- | | `Ok(Paid(amount))` | Yes, under `(service_id, payment_reference)` | `ClaimPaid` | Breach proven; `amount` transferred to `receipt.buyer` from the service’s 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. ## Error codes | 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’s 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’s `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’s 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)`. ## Claim records | `ClaimRecord` field | Meaning | | --- | --- | | `predicate_id` | The predicate that was proven | | `buyer` | Who was paid | | `amount` | What was paid | | `paid_at` | Ledger time of the claim | | `receipt_hash` | Which receipt proved it | Records are keyed by `(service_id, payment_reference)` in persistent storage; `get_claim(service_id, payment_reference)` returns one. Their lifetime is extended on every claim (threshold 30 days, extended to 60 days, counted in ledgers), far beyond the longest claim window. ## Events | Event | Topics | Data | | --- | --- | --- | | `ClaimPaid` | `service_id`, `predicate_id` | `buyer`, `amount`, `payment_reference` | | `ClaimRejected` | `service_id`, `predicate_id` | `reason` (1 NotProven, 2 NotVerifiable), `payment_reference` | | `ValidatorSet` | `predicate_id` | `validator` | | `ValidatorChangeProposed` | `predicate_id` | `validator`, `eta` | | `ValidatorChangeCancelled` | `predicate_id` | — | The registry additionally emits `Slashed` and, when the payout takes stake below the minimum, `StateChanged` to `Suspended`. ## Functions *Claims contract interface* ```text __constructor(admin, registry, change_delay) // change_delay > any unbonding delay (93,600 s on the first deployment) claim(predicate_id, offer, receipt, extra) -> Result get_claim(service_id, payment_reference) -> Option get_validator(predicate_id) -> Option
get_registry() -> Address get_pending_validator(predicate_id) -> Option set_validator(predicate_id, validator) -> Result // admin; first setting immediate (returns 0), replacement time-locked (returns eta) apply_validator(predicate_id) -> Result // anyone, after eta cancel_validator_change(predicate_id) -> Result<(), ClaimError> // admin extend_instance_ttl() // anyone; keeps instance storage alive ``` --- # Bond Registry > 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. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/bond-registry.html · This Markdown twin: https://lopw.com/docs/protocol/bond-registry.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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’s 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 | From | Trigger | To | | --- | --- | --- | | `Pending` | deposit reaches min_stake | `Active` | | `Pending` | withdraw (no delay) | `Closed` | | `Active` | slash or fee takes stake below min_stake | `Suspended` | | `Suspended` | deposit restores min_stake | `Active` | | `Active` | request_unbond that would leave stake below min_stake | `Unbonding` | | `Suspended` | request_unbond (full exit) | `Unbonding` | | `Unbonding` | cancel_unbond, stake at or above min_stake | `Active` | | `Unbonding` | cancel_unbond, stake below min_stake | `Suspended` | | `Unbonding` | withdraw after the unbonding delay | `Closed` | | `Active` | request_unbond of stake above min_stake (partial, stays Active) | `Active` | | 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 }`. ## Functions *Bond Registry interface* ```text // seller register_service(seller, signing_key, terms, min_stake) -> Result update_terms(service_id, terms) -> Result // not while Unbonding or Closed rotate_key(service_id, signing_key) -> Result // 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 // claims contract only slash(service_id, buyer, amount) -> Result // anyone collect_fee(service_id) -> Result extend_service_ttl(service_id) -> Result<(), Error> extend_instance_ttl() apply_claims_contract() -> Result apply_limits() -> Result apply_fees() -> Result // reads get_service(service_id) -> Result get_terms(service_id, terms_hash) -> Result get_key(service_id, key_version) -> Result terms_hash(terms) -> BytesN<32> get_config() -> ProtocolConfig get_token() -> Address get_claims_contract() -> Option
get_pending_claims_contract() -> Option get_limits() -> LaunchLimits get_pending_limits() -> Option get_fees() -> FeeConfig get_pending_fees() -> Option is_paused() -> bool is_seller_allowed(seller) -> bool get_total_stake() -> i128 // admin set_claims_contract(claims) -> Result cancel_claims_change() set_validator lives on the Claims contract set_limits(limits) -> Result cancel_limits_change() set_fees(fees) -> Result 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. ### Rules per function | Function | Requires | Effect | | --- | --- | --- | | `register_service` | Seller auth; not paused; seller allowlisted if `allowlist_only`; `min_stake ≥ 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 ≤ stake` | Collects fee; partial if `stake − amount ≥ 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 ≥ 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` | ## Error codes | 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’s 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) | | 15 | `InvalidConfig` | Constructor floors inconsistent | | 16 | `NoPendingChange` | Nothing to apply or cancel | | 17 | `ChangeNotReady` | Time lock not passed | | 18 | `Paused` | Registrations and deposits are paused | | 19 | `NotAllowlisted` | Seller not approved during an allowlist-only launch | | 20 | `StakeCapReached` | Per-service or total stake limit | | 21 | `InvalidFees` | Negative registration fee or more than 10,000 bps | | 22 | `InvalidLimits` | Negative limit | ## Events | Event | Data | | --- | --- | | `ServiceRegistered` | `service_id` (topic), `seller`, `terms_hash`, `min_stake` | | `TermsUpdated` | `service_id` (topic), `terms_hash` | | `KeyRotated` | `service_id` (topic), `key_version` | | `Deposited` | `service_id` (topic), `amount`, `stake` | | `UnbondRequested` | `service_id` (topic), `amount`, `unlock_at`, `full_exit` | | `UnbondCancelled` | `service_id` (topic) | | `Withdrawn` | `service_id` (topic), `amount`, `stake` | | `Slashed` | `service_id` (topic), `buyer`, `amount`, `stake` | | `FeeCollected` | `service_id` (topic), `amount`, `stake` | | `StateChanged` | `service_id` (topic), `from`, `to` | | `ClaimsContractSet`, `ClaimsChangeProposed`, `ClaimsChangeCancelled` | `claims`, and `eta` on the proposal | | `LimitsSet`, `LimitsChangeProposed` | `limits`, and `eta` on the proposal | | `FeesSet`, `FeesChangeProposed` | `fees`, and `eta` on the proposal | | `PausedSet` | `paused` | | `SellerAllowed` | `seller` (topic), `allowed` | ## Protocol floors Fixed at registry construction in `ProtocolConfig`. Values of the first deployment: | Parameter | Value | Effect | | --- | --- | --- | | `min_stake_floor` | 100 USDC (1,000,000,000 in 7-decimal units) | Lowest allowed `min_stake` | | `max_claim_window` | 86,400 s (24 h) | Longest allowed claim window | | `max_oracle_claim_window` | 3,600 s (1 h) | Longest claim window for services that offer P6, inside the oracle’s history retention | | `unbond_margin` | 3,600 s (1 h) | Added to the largest claim window to form the unbonding delay and the slash epoch | | `slash_epoch_cap_bps` | 5,000 (50%) | Share of epoch-start stake that may be slashed per epoch | ## Safeguards - **Claim cap.** One receipt pays at most `max_claim`, never more than the stake left, and never more than `max_payout` when a launch limit is set. - **Unbonding delay.** `max_claim_window_ever + unbond_margin`, so every receipt issued before an exit can still be claimed, whatever terms version it was issued under. Terms cannot change while a service is Unbonding. - **Partial withdrawal.** Stake above the minimum leaves through the same delay, and the service stays Active. The pending amount stays slashable until it is withdrawn. - **Automatic suspension.** The registry sets `Suspended` during the slash or fee collection that takes stake below the minimum. - **Slash rate limit.** Below. - **Time-locked administration.** Below. ## Slash rate limit The contract never checks that a payment happened, so a seller could sign receipts for invented payments to addresses it controls and claim them, pulling stake out instantly and ahead of honest buyers. The rate limit removes the advantage: stake can leave through claims no faster than through a normal withdrawal. *Rate limit, as the registry applies it* ```text epoch_len = max_claim_window + unbond_margin // same length as the unbonding delay if epoch_base_stake == 0 or now >= epoch_start + epoch_len: // start a new epoch epoch_start, epoch_base_stake, epoch_slashed = now, stake, 0 cap = epoch_base_stake * slash_epoch_cap_bps / 10000 // 50% of stake at epoch start if epoch_slashed > 0 and epoch_slashed + payout > cap: fail EpochCapReached // first slash of an epoch is always allowed epoch_slashed += payout ``` The first slash of an epoch is always allowed (still bounded by `max_claim` and remaining stake), so a single honest claim is never blocked even when `max_claim` exceeds the cap. A claim refused by the cap fails with `SlashRateLimited`, records nothing, and can be retried in the next epoch if its window is still open. The trade-off: a genuine mass breach is paid out over more than one epoch. ## Unbonding maths ```text unbonding_delay = max_claim_window_ever + unbond_margin full_exit = stake - amount < min_stake pending = full_exit ? stake : amount unlock_at = now + unbonding_delay withdraw pays = full_exit ? stake : min(pending, stake) // slashes and fees reduce pending with the stake ``` Examples: a service whose largest claim window ever was 1 hour unbonds in 2 hours; one that once registered a 24-hour window unbonds in 25 hours, for the rest of its life. The admin time lock (26 hours) is longer than any possible unbonding delay by design. ### Calculator ```text unbonding_delay = max_claim_window_ever + unbond_margin (unbond_margin = 3600 s) full_exit = stake - amount < min_stake (then the whole stake exits) unlock_at = now + unbonding_delay slash_epoch = unbonding_delay; cap = epoch_base_stake * 5000 / 10000 payout = min(max_claim, stake, max_payout if set); first slash of an epoch always allowed stake_fee = stake * bps_per_year * elapsed_seconds / (10000 * 31536000), capped at stake admin_time_lock = max_claim_window + 2 * unbond_margin = 93600 s (26 h) ``` ## Protocol fees Two fees in the stake token, paid to a treasury address, both of which may be zero: | Fee | When | How | | --- | --- | --- | | `registration_fee` | Once, on `register_service` | Transferred from the seller’s account to the treasury before the service is created | | `stake_fee_bps_per_year` | Continuously on stake held | Accrued per second; collected from stake whenever the service is touched (`deposit`, `request_unbond`, `withdraw`, `slash`) or when anyone calls `collect_fee`; emits `FeeCollected` | ```text fee = stake * stake_fee_bps_per_year * elapsed_seconds / (10000 * 31_536_000) // integer division, capped at stake // example: 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 ``` The fee can take a service below its minimum, which suspends it like a slash would, so sellers should stake above the minimum. The first deployment runs a 1 USDC registration fee and 100 bps (1%) per year; proposed launch values on a production network are 10 USDC and 100 bps. Fee changes are time-locked. ## Guarded launch: limits, pause, allowlist | Control | Meaning | Change rule | | --- | --- | --- | | `max_stake_per_service` | Most stake one service may hold (0 = no limit) | Tightening immediate; loosening time-locked | | `max_total_stake` | Most stake the registry may hold across all services | Same; backed by a running `TotalStake` counter | | `max_payout` | Ceiling on any single claim, whatever the offer’s `max_claim` | Same | | `allowlist_only` | Only sellers approved with `set_seller_allowed` may register | Turning on immediate; turning off time-locked | | `set_paused` | Stops new registrations and deposits; never claims, unbonding or withdrawals, so funds are never trapped | Immediate in both directions | The first deployment runs with no limits and no allowlist. A guarded production launch is expected to start with low caps and `allowlist_only` on, then loosen them through the time lock. ## Time locks The one way slashing rights can move is through the admin, and every such change is announced on-chain and delayed long enough for every seller to withdraw first. The first setting of each value applies immediately (initial wiring); every later change is a two-step proposal. | Change | Propose | Apply (anyone, after `eta`) | Cancel (admin) | | --- | --- | --- | --- | | Which contract may slash | `set_claims_contract` → `ClaimsChangeProposed` | `apply_claims_contract` → `ClaimsContractSet` | `cancel_claims_change` | | Which validator decides a predicate | `set_validator` (Claims contract) → `ValidatorChangeProposed` | `apply_validator` → `ValidatorSet` | `cancel_validator_change` | | Fees | `set_fees` → `FeesChangeProposed` | `apply_fees` → `FeesSet` | `cancel_fees_change` | | Looser limits | `set_limits` → `LimitsChangeProposed` | `apply_limits` → `LimitsSet` | `cancel_limits_change` | ```text admin_delay = max_claim_window + 2 * unbond_margin = 86400 + 7200 = 93,600 s (26 h) // registry change_delay = 93,600 s // Claims contract, set at construction ``` 26 hours is longer than any service’s unbonding delay (at most 25 hours), so every seller can fully withdraw before a change takes effect. Contracts have no upgrade function. Sellers and monitors should watch for the `*ChangeProposed` events. ## Storage lifetime Services, terms versions, keys and claim records live in persistent storage. Every access extends their lifetime (threshold 30 days, extended to 60 days, counted in ledgers of about five seconds). `extend_service_ttl(service_id)` refreshes a service’s entries and `extend_instance_ttl()` the contract’s own; anyone may call them, and a keeper does so on a schedule. Long-idle services may need such a refresh. --- # x402 extension > 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. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/x402.html · This Markdown twin: https://lopw.com/docs/protocol/x402.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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 | ## The offer in the 402 ```http 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…", // Bond Registry contract address "service_id": "…64 hex…", "offer": "AAAAEQAAAAEAAAAK…" // 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’s echo validation. The middleware issues an offer only when the service is `Active` and only for the payment requirement whose network matches the binding’s CAIP-2 id; otherwise the 402 carries plain x402 terms. ## The buyer's echo ```http // PaymentPayload (sent by the buyer with the payment), with: "extensions": { "lopw": { "info": { …the same info object… }, "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. ## The receipt in PAYMENT-RESPONSE ```http HTTP/1.1 200 OK PAYMENT-RESPONSE: // decoded settlement response, with: "success": true, "transaction": "…64 hex…", "payer": "G…", "extensions": { "lopw": { "receipt": "AAAAEQAAAAEAAAAO…", // base64 ScVal XDR of SignedReceipt { receipt, signature } "duplicate": "…" // optional; reference services' fault injection only } } ``` The receipt is attached through x402’s `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`). ## Handler inputs | 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’s transport context. The two headers are ordinary response headers that a client may read as well. ## Why LOPW keeps its own receipt format 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. ## SDK surface on the wire | 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` | --- # HTTP API > 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. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/api.html · This Markdown twin: https://lopw.com/docs/protocol/api.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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](https://lopw.com/docs/api/openapi.json) (also served by the API itself at `/v1/openapi.json`). Base URL: `https://api.lopw.com`. ## Endpoints | Method and path | Returns | Parameters | | --- | --- | --- | | `GET /healthz` | `{ status, indexed_ledger, indexed_at }`; 503 with a problem when unhealthy | — | | `GET /v1/openapi.json` | This document | — | | `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. ## Schemas | 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’s 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, caching, limits, errors - **Pagination:** pass the previous page’s `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. ## Live stream ```http GET /v1/testnet/stream Accept: text/event-stream id: 00000000000000123 event: claim_paid data: { "id": "…", "kind": "claim_paid", "service_id": "…", "contract_id": "C…", "transaction": "…", "ledger": 5021500, "topics": [ … ], "data": { … }, "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=`. Monitoring is built on this stream plus direct chain reads. --- # Chain bindings > 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. *LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/bindings.html · This Markdown twin: https://lopw.com/docs/protocol/bindings.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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](https://lopw.com/docs/networks/stellar.html) (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](https://lopw.com/docs/networks/evm.html) (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](https://lopw.com/docs/networks/solana.html) (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](https://lopw.com/docs/networks/) section. ## The Stellar binding | 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 | ```ts 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](https://lopw.com/proofs/) page and inside `STELLAR_TESTNET`. ## What a new binding must provide - 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](https://lopw.com/docs/vectors/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. --- # Networks > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/ · This Markdown twin: https://lopw.com/docs/networks/index.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. 1. **Stellar: the reference binding** Soroban contracts in Rust. Ed25519 signatures over `SHA-256(tag ‖ network_id ‖ XDR(registry) ‖ 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. 2. **Base: the first EVM network** The 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. 3. **Ethereum: same binding** Ethereum 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. 4. **Arbitrum: same binding** Arbitrum One and Arbitrum Sepolia: unchanged contracts and SDK, Arbitrum’s native USDC and Chainlink feeds. 5. **Optimism: same binding** OP Mainnet and OP Sepolia: the same OP Stack as Base, so the binding behaves identically. 6. **Polygon: same binding** Polygon PoS and Amoy: native USDC is the stake and payment token; everything else is the shared binding. 7. **Avalanche: same binding** The Avalanche C-Chain and Fuji: EVM-compatible, native USDC, Chainlink feeds; the binding applies unchanged. 8. **Solana: a planned binding** Anchor 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. ## What a chain binding is 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` | ## The bindings side by side | Binding | Contracts | Signatures | Hashing and encoding | Stake token | P6 oracle | x402 scheme | Wire form of the lopw extension | | --- | --- | --- | --- | --- | --- | --- | --- | | [Stellar](https://lopw.com/docs/networks/stellar.html) 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](https://lopw.com/docs/networks/evm.html): 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](https://lopw.com/docs/networks/solana.html) 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’s smallest unit on the wire; the API and SDKs never use floating point. Floors are the same value expressed in each token’s decimals: the 100 USDC minimum-stake floor is `1_000_000_000` units on Stellar and `100_000_000` units on EVM networks. ## The networks **Stellar (reference binding)** - [Stellar](https://lopw.com/docs/networks/stellar.md): Soroban, Ed25519, ScVal XDR, USDC SAC (7 decimals), Reflector SEP-40, x402 exact with SEP-41; live on Stellar testnet **EVM networks (one Solidity binding)** - [Base](https://lopw.com/docs/networks/base.md): `base-sepolia` and `base`, chain ids 84532 and 8453 - [Ethereum](https://lopw.com/docs/networks/ethereum.md): `sepolia` and `ethereum`, chain ids 11155111 and 1 - [Arbitrum](https://lopw.com/docs/networks/arbitrum.md): `arbitrum-sepolia` and `arbitrum`, chain ids 421614 and 42161 - [Optimism](https://lopw.com/docs/networks/optimism.md): `optimism-sepolia` and `optimism`, chain ids 11155420 and 10 - [Polygon](https://lopw.com/docs/networks/polygon.md): `polygon-amoy` and `polygon`, chain ids 80002 and 137 - [Avalanche](https://lopw.com/docs/networks/avalanche.md): `avalanche-fuji` and `avalanche`, chain ids 43113 and 43114 **Planned** - [Solana](https://lopw.com/docs/networks/solana.md): Anchor, Ed25519 over Borsh, USDC SPL (6 decimals), Pyth, x402 exact with SPL transfer; a design, nothing deployed ## Network names and CAIP-2 ids 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](https://lopw.com/docs/networks/stellar.html) | Stellar | Deployed | | `mainnet` | `stellar:pubnet` | [Stellar mainnet](https://lopw.com/docs/networks/stellar.html) | Stellar | None published | | `base-sepolia` | `eip155:84532` | [Base Sepolia](https://lopw.com/docs/networks/base.html) | EVM | None published | | `base` | `eip155:8453` | [Base](https://lopw.com/docs/networks/base.html) | EVM | None published | | `sepolia` | `eip155:11155111` | [Ethereum Sepolia](https://lopw.com/docs/networks/ethereum.html) | EVM | None published | | `ethereum` | `eip155:1` | [Ethereum](https://lopw.com/docs/networks/ethereum.html) | EVM | None published | | `arbitrum-sepolia` | `eip155:421614` | [Arbitrum Sepolia](https://lopw.com/docs/networks/arbitrum.html) | EVM | None published | | `arbitrum` | `eip155:42161` | [Arbitrum One](https://lopw.com/docs/networks/arbitrum.html) | EVM | None published | | `optimism-sepolia` | `eip155:11155420` | [OP Sepolia](https://lopw.com/docs/networks/optimism.html) | EVM | None published | | `optimism` | `eip155:10` | [OP Mainnet](https://lopw.com/docs/networks/optimism.html) | EVM | None published | | `polygon-amoy` | `eip155:80002` | [Polygon Amoy](https://lopw.com/docs/networks/polygon.html) | EVM | None published | | `polygon` | `eip155:137` | [Polygon PoS](https://lopw.com/docs/networks/polygon.html) | EVM | None published | | `avalanche-fuji` | `eip155:43113` | [Avalanche Fuji](https://lopw.com/docs/networks/avalanche.html) | EVM | None published | | `avalanche` | `eip155:43114` | [Avalanche C-Chain](https://lopw.com/docs/networks/avalanche.html) | EVM | None published | | `solana-devnet` | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | [Solana devnet](https://lopw.com/docs/networks/solana.html) | Solana | Planned binding | | `solana` | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | [Solana mainnet](https://lopw.com/docs/networks/solana.html) | Solana | Planned binding | `testnet` and `mainnet` without a prefix are the Stellar networks, kept for continuity with the first deployment. Every other name carries its chain. ## For agents > **For agents** > > | Item | Value | > | --- | --- | > | Network name in API paths | The `name` column above, for example `/v1/testnet/services`; the API serves a network once a LOPW deployment is published for it | > | CAIP-2 id | `stellar:testnet`, `stellar:pubnet`, `eip155:`, `solana:` | > | Wire encoding of `extensions.lopw` | Stellar: base64 ScVal XDR. EVM: 0x-hex ABI encoding. Solana (design): Borsh. The JSON shape of the extension is identical on every chain; `info.network` names the binding’s network | > | Machine-readable list | [/.well-known/lopw.json](https://lopw.com/.well-known/lopw.json) carries `bindings[]` and `networks[]` with the same fields as this page | - [Chain bindings (protocol reference)](https://lopw.com/docs/protocol/bindings.html): What every binding must provide, on-chain and off-chain, in the terms of the ChainBinding interface. - [Hashing and signing](https://lopw.com/docs/protocol/hashing-and-signing.html): The reference (Stellar) construction byte by byte, with the shared test vectors. - [x402 extension](https://lopw.com/docs/protocol/x402.html): Where the signed offer and receipt travel; identical envelope on every chain. - [Agents start here](https://lopw.com/docs/agents/): Every machine-readable file, including the EVM JSON Schemas. --- # Stellar > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/stellar.html · This Markdown twin: https://lopw.com/docs/networks/stellar.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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](https://lopw.com/docs/vectors/v1.json) 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** ## At a glance | 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 ‖ network_id ‖ XDR(registry) ‖ 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` | ## Networks | Network | Name | CAIP-2 | Passphrase | USDC | Reflector oracle | Explorer | | --- | --- | --- | --- | --- | --- | --- | | Stellar testnet | `testnet` | `stellar:testnet` | `Test SDF Network ; September 2015` | [CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA](https://stellar.expert/explorer/testnet/contract/CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA) | [CCSSOHTBL3LEWUCBBEB5NJFC2OKFRC74OWEIJIZLRJBGAAU4VMU5NV4W](https://stellar.expert/explorer/testnet/contract/CCSSOHTBL3LEWUCBBEB5NJFC2OKFRC74OWEIJIZLRJBGAAU4VMU5NV4W) | [stellar.expert (testnet)](https://stellar.expert/explorer/testnet) | | Stellar mainnet | `mainnet` | `stellar:pubnet` | `Public Global Stellar Network ; September 2015` | [CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75](https://stellar.expert/explorer/public/contract/CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75) | [CBKGPWGKSKZF52CFHMTRR23TBWTPMRDIYZ4O2P5VS65BMHYH4DXMCJZC](https://stellar.expert/explorer/public/contract/CBKGPWGKSKZF52CFHMTRR23TBWTPMRDIYZ4O2P5VS65BMHYH4DXMCJZC) | [stellar.expert (public)](https://stellar.expert/explorer/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`. ## LOPW contracts ### Stellar testnet Deployed Current LOPW contracts on Stellar testnet, each linked to the public explorer. | Contract | Address | | --- | --- | | Bond Registry | [CBNGZB5GL3FEZKIQK73BTJYCQAC76N7SZCLN34JRMTQCUWER4GB3WHPL](https://stellar.expert/explorer/testnet/contract/CBNGZB5GL3FEZKIQK73BTJYCQAC76N7SZCLN34JRMTQCUWER4GB3WHPL) | | Claims | [CBUBZC2AHVKFKYTAO3DIF6CZFQVM4UYCNABYVTPQNUUJGLOIBSIWONSC](https://stellar.expert/explorer/testnet/contract/CBUBZC2AHVKFKYTAO3DIF6CZFQVM4UYCNABYVTPQNUUJGLOIBSIWONSC) | | Validator P1 Equivocation | [CBEBETGU7AFDBYSP5VGUIKJ7FHLLDOG5LM56MHYWYWT4M4S7FZEZ7TU3](https://stellar.expert/explorer/testnet/contract/CBEBETGU7AFDBYSP5VGUIKJ7FHLLDOG5LM56MHYWYWT4M4S7FZEZ7TU3) | | Validator P2 Terms mismatch | [CCDRQUWK5JHLNJRAMT6P37K4RS4B3SNELLZPP6LMM64IZ357MSSGSDW4](https://stellar.expert/explorer/testnet/contract/CCDRQUWK5JHLNJRAMT6P37K4RS4B3SNELLZPP6LMM64IZ357MSSGSDW4) | | Validator P3 Stale data | [CDUIB7ZPIRXMUZLIXRTALEFLXMAYLLIBJ6TDN5HWDR77CPLLPIG44DVJ](https://stellar.expert/explorer/testnet/contract/CDUIB7ZPIRXMUZLIXRTALEFLXMAYLLIBJ6TDN5HWDR77CPLLPIG44DVJ) | | Validator P4 Bad status | [CA5FBH3RUW65UXIHU4YP2GSTQNUXHXTB4NIEVICPHHV7ISXY2VB6A2LV](https://stellar.expert/explorer/testnet/contract/CA5FBH3RUW65UXIHU4YP2GSTQNUXHXTB4NIEVICPHHV7ISXY2VB6A2LV) | | Validator P5 Size limit | [CDJGYTFQOGMY3UX7CPYLJPNZF2SPFEHJRJGY3VOOHVVIPLNS4NWL5BEE](https://stellar.expert/explorer/testnet/contract/CDJGYTFQOGMY3UX7CPYLJPNZF2SPFEHJRJGY3VOOHVVIPLNS4NWL5BEE) | | Validator P6 Oracle deviation | [CDPLC6G732LTARGLAKNA5TRR4DAERXKMWGWI4TS4XQ6WLHRSPHQNEWGV](https://stellar.expert/explorer/testnet/contract/CDPLC6G732LTARGLAKNA5TRR4DAERXKMWGWI4TS4XQ6WLHRSPHQNEWGV) | Treasury account `GBNQ7FKN25IGBY7V7XJUVMMACLDIACYDLUNYZA56RR2RQMHW3UV2CCQ5`. Registration fee 1 USDC; stake fee 100 basis points a year; admin change delay 93600 seconds (26 hours). ### Stellar mainnet Not deployed No LOPW deployment is published on this network yet. ## Hashing and signing, exactly The [Hashing and signing](https://lopw.com/docs/protocol/hashing-and-signing.html) page shows every byte with an interactive layout taken from the test vectors. In short: ```text 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’s 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. ## P6 on Stellar 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’s 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 − oracle_price| × 10000 > max_deviation_bps × 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. ## x402 on Stellar *402 PaymentRequired, Stellar* ```http "extensions": { "lopw": { "info": { "version": 1, "network": "testnet", "registry": "CBNGZB5GL3FEZKIQK73BTJYCQAC76N7SZCLN34JRMTQCUWER4GB3WHPL", "service_id": "…64 hex…", "offer": "AAAAEQAAAAEAAAAK…" // 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](https://lopw.com/docs/protocol/x402.html) page. ## SDK ```ts 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 > **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](https://lopw.com/docs/schemas/offer.json), [receipt.json](https://lopw.com/docs/schemas/receipt.json), [terms.json](https://lopw.com/docs/schemas/terms.json) | > | Explorer | `https://stellar.expert/explorer/testnet/tx/{hash}` and `.../contract/{address}`; `public` for mainnet | > | Test vectors | [/docs/vectors/v1.json](https://lopw.com/docs/vectors/v1.json) | - [Proofs](https://lopw.com/proofs/): Every contract and transaction on Stellar testnet, linked to the explorer. - [Demo](https://lopw.com/demo/): One real claim on Stellar testnet, step by step. - [Chain bindings (reference)](https://lopw.com/docs/protocol/bindings.html): What any binding must provide, with the Stellar binding fields. - [EVM binding](https://lopw.com/docs/networks/evm.html): How the same protocol is carried on Base, Ethereum, Arbitrum, Optimism, Polygon and Avalanche. --- # EVM binding > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/evm.html · This Markdown twin: https://lopw.com/docs/networks/evm.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt One Solidity binding serves every EVM network. It is built against [Base](https://lopw.com/docs/networks/base.html) first (Base Sepolia for the test network) and runs unchanged on Ethereum, Arbitrum, Optimism, Polygon and Avalanche with each network’s own USDC and Chainlink addresses. Semantics are those of the protocol specification; only the carriers differ from [Stellar](https://lopw.com/docs/networks/stellar.html). **Shared by six networks · Base first** ## At a glance | 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’s server and registered as an `address`, never the seller’s 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’s oracle timestamp | | x402 scheme | `exact` on `eip155:` 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) | ## Types Solidity structs; field order is normative because it defines the ABI and the EIP-712 hashes. Field names are the Stellar names in camelCase. *Solidity* ```text 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’s HTTP layer is chain-agnostic. ## Hashing and signing (EIP-712) Domain: `EIP712Domain(name="LOPW", version="1", chainId, verifyingContract=registry)`. The chain id and the registry address give the same two replay protections as Stellar’s network id and registry address in the hash. *EIP-712 type strings* ```text 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). ## Contracts 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 interfaces* ```text // 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’s order and reverts with the Stellar error names (`UnknownService`, `BadSignature`, `OfferMismatch`, `WindowClosed`, `AlreadyClaimed`, `PredicateNotOffered`, `NoStake`, `ValidatorNotSet`, `KeyRetired`, `UnsupportedVersion`, `SlashRateLimited`). The window check uses `block.timestamp`. Outcomes are `ClaimPaid(serviceId, predicateId, buyer, amount, paymentReference)` and `ClaimRejected(serviceId, predicateId, reason, paymentReference)` with reason 1 = NotProven and 2 = NotVerifiable. The P1 second receipt is signature-checked by Claims before the validator runs. Fees accrue per second with `SECONDS_PER_YEAR = 31_536_000` and basis points over 10,000. ## P6 with Chainlink 1. `receipt.oracle.feed == terms.oracle.feed && receipt.oracle.asset == terms.oracle.asset`, otherwise proven (terms mismatch). 2. `(, int256 answer,, uint256 updatedAt,) = feed.getRoundData(receipt.oracle.roundId)`; a revert, `answer <= 0` or `updatedAt == 0` is NotVerifiable. 3. `updatedAt > servedAt` or `servedAt − updatedAt > maxOracleAge`: proven (stale). 4. `|reportedValue − answer| × 10000 > maxDeviationBps × answer`: proven, exact integer arithmetic (decision D-009). Chainlink rounds are immutable once published, so a cited `roundId` is the citation, exactly as the oracle timestamp is on Stellar. Terms name the feed by its pair label (`asset = bytes32("ETH/USD")`) and the reference service reports values in the feed’s decimals (8 on Chainlink USD feeds, against 14 on Reflector); the deviation check is decimal-agnostic because both sides come from the same feed. ## x402 on EVM Scheme `exact` on `eip155:` with USDC `transferWithAuthorization` (EIP-3009). The seller’s `PaymentRequirements.extra` carries the asset’s EIP-712 name and version as x402 requires (`{ "name": "USDC", "version": "2" }` on Base Sepolia, verified on-chain). `paymentReference` is the settlement transaction hash from `PAYMENT-RESPONSE.transaction`; `buyer` is the `payer` address. The `lopw` extension is carried exactly as on Stellar; only the signed-offer and signed-receipt encoding changes. *402 PaymentRequired, EVM* ```http "extensions": { "lopw": { "info": { "version": 1, "network": "base-sepolia", "registry": "0x…", // Bond Registry contract address "service_id": "…64 hex…", "offer": "0x0000…" // abi.encode(SignedOffer { offer, signature }) as hex }, "schema": { "type": "object" } } } // PAYMENT-RESPONSE.extensions.lopw.receipt: abi.encode(SignedReceipt { receipt, signature }) as 0x-hex ``` ## SDK ```ts import { EvmBinding, EVM_NETWORKS } from "@lopw/binding-evm"; const binding = new EvmBinding(EVM_NETWORKS["base-sepolia"]); // chain id 84532, USDC and feed addresses included // EVM_RPC_URL overrides the public JSON-RPC; LOPW_ORACLE overrides the feed; registry and claims come from deployments/.json ``` | `EvmNetwork` field | Meaning | | --- | --- | | `name` | Network name used in API paths, for example `base-sepolia` | | `caip2` | `eip155:`, used by x402 | | `chainId` | The EIP-712 domain chain id | | `rpcUrl` | Public JSON-RPC used by default | | `usdc`, `usdcDecimals` | Circle’s native USDC contract, 6 decimals; checked on-chain (`symbol()`, `decimals()`) before use | | `registry`, `claims` | LOPW contract addresses, filled from `deployments/.json` once a deployment is published | | `oracle` | The Chainlink feed the reference service measures against, verified on-chain (`description()`, `decimals()`) | | `explorerTx`, `explorerContract` | Explorer URL builders for the network | ## Networks on this binding | Chain | Test network | Chain id | Production network | Chain id | LOPW deployment | | --- | --- | --- | --- | --- | --- | | [Base](https://lopw.com/docs/networks/base.html) | `base-sepolia` | 84532 | `base` | 8453 | None published | | [Ethereum](https://lopw.com/docs/networks/ethereum.html) | `sepolia` | 11155111 | `ethereum` | 1 | None published | | [Arbitrum](https://lopw.com/docs/networks/arbitrum.html) | `arbitrum-sepolia` | 421614 | `arbitrum` | 42161 | None published | | [Optimism](https://lopw.com/docs/networks/optimism.html) | `optimism-sepolia` | 11155420 | `optimism` | 10 | None published | | [Polygon](https://lopw.com/docs/networks/polygon.html) | `polygon-amoy` | 80002 | `polygon` | 137 | None published | | [Avalanche](https://lopw.com/docs/networks/avalanche.html) | `avalanche-fuji` | 43113 | `avalanche` | 43114 | None published | ## For agents > **For agents** > > | Item | Value | > | --- | --- | > | Network name in API paths | `base-sepolia`, `base`, `sepolia`, `ethereum`, `arbitrum-sepolia`, `arbitrum`, `optimism-sepolia`, `optimism`, `polygon-amoy`, `polygon`, `avalanche-fuji`, `avalanche`; a network is served once a deployment is published for it | > | CAIP-2 id | `eip155:` (the chain id is also the EIP-712 domain chain id) | > | Wire encoding of `extensions.lopw` | `info.offer` = `abi.encode(SignedOffer)` and `receipt` = `abi.encode(SignedReceipt)`, 0x-prefixed lowercase hex; JSON Schemas [offer-evm.json](https://lopw.com/docs/schemas/offer-evm.json), [receipt-evm.json](https://lopw.com/docs/schemas/receipt-evm.json), [terms-evm.json](https://lopw.com/docs/schemas/terms-evm.json) | > | Signature | 65 bytes (r, s, v) over the EIP-712 digest; recover and compare with `getKey(serviceId, keyVersion)` | > | Amounts | Decimal strings of USDC base units, 6 decimals | --- # Base > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/base.html · This Markdown twin: https://lopw.com/docs/networks/base.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Base runs LOPW through the shared [EVM binding](https://lopw.com/docs/networks/evm.html): 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** ## At a glance | 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](https://sepolia.basescan.org/address/0x036CbD53842c5426634e7929541eC2318f3dCF7e) | [0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913](https://basescan.org/address/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) | | Explorer | [sepolia.basescan.org](https://sepolia.basescan.org) | [basescan.org](https://basescan.org) | | P6 oracle | [0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1](https://sepolia.basescan.org/address/0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1) 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 | Chainlink EUR / USD, verified on-chain at deployment | | LOPW deployment | None published | None published | ## LOPW contracts ### Base Sepolia Not deployed No LOPW deployment is published on this network yet. ### Base Not deployed No LOPW deployment is published on this network yet. ## Oracle note 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’s `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/.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. ## What is identical to every other EVM network - 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 > **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](https://lopw.com/docs/schemas/offer-evm.json) and [receipt-evm.json](https://lopw.com/docs/schemas/receipt-evm.json) | > | Explorer | `https://sepolia.basescan.org/tx/{hash}`, `https://sepolia.basescan.org/address/{address}` | > | USDC | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` (Base Sepolia), `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` (Base) | - [EVM binding](https://lopw.com/docs/networks/evm.html): Types, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK. - [All networks](https://lopw.com/docs/networks/): Stellar as the reference binding, the EVM networks, and the planned Solana binding. --- # Ethereum > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/ethereum.html · This Markdown twin: https://lopw.com/docs/networks/ethereum.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Ethereum runs LOPW through the shared [EVM binding](https://lopw.com/docs/networks/evm.html): 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** ## At a glance | 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](https://sepolia.etherscan.io/address/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238) | [0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) | | Explorer | [sepolia.etherscan.io](https://sepolia.etherscan.io) | [etherscan.io](https://etherscan.io) | | P6 oracle | Chainlink feed, verified on-chain at deployment | Chainlink feed, verified on-chain at deployment | | LOPW deployment | None published | None published | ## LOPW contracts ### Ethereum Sepolia Not deployed No LOPW deployment is published on this network yet. ### Ethereum Not deployed No LOPW deployment is published on this network yet. ## Oracle note 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’s `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/.json`. ## What is identical to every other EVM network - 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 > **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](https://lopw.com/docs/schemas/offer-evm.json) and [receipt-evm.json](https://lopw.com/docs/schemas/receipt-evm.json) | > | Explorer | `https://sepolia.etherscan.io/tx/{hash}`, `https://sepolia.etherscan.io/address/{address}` | > | USDC | `0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238` (Ethereum Sepolia), `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` (Ethereum) | - [EVM binding](https://lopw.com/docs/networks/evm.html): Types, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK. - [All networks](https://lopw.com/docs/networks/): Stellar as the reference binding, the EVM networks, and the planned Solana binding. --- # Arbitrum > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/arbitrum.html · This Markdown twin: https://lopw.com/docs/networks/arbitrum.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Arbitrum runs LOPW through the shared [EVM binding](https://lopw.com/docs/networks/evm.html): 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** ## At a glance | 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](https://sepolia.arbiscan.io/address/0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d) | [0xaf88d065e77c8cC2239327C5EDb3A432268e5831](https://arbiscan.io/address/0xaf88d065e77c8cC2239327C5EDb3A432268e5831) | | Explorer | [sepolia.arbiscan.io](https://sepolia.arbiscan.io) | [arbiscan.io](https://arbiscan.io) | | P6 oracle | Chainlink feed, verified on-chain at deployment | Chainlink feed, verified on-chain at deployment | | LOPW deployment | None published | None published | ## LOPW contracts ### Arbitrum Sepolia Not deployed No LOPW deployment is published on this network yet. ### Arbitrum One Not deployed No LOPW deployment is published on this network yet. ## Oracle note 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’s `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/.json`. ## What is identical to every other EVM network - 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 > **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](https://lopw.com/docs/schemas/offer-evm.json) and [receipt-evm.json](https://lopw.com/docs/schemas/receipt-evm.json) | > | Explorer | `https://sepolia.arbiscan.io/tx/{hash}`, `https://sepolia.arbiscan.io/address/{address}` | > | USDC | `0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d` (Arbitrum Sepolia), `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` (Arbitrum One) | - [EVM binding](https://lopw.com/docs/networks/evm.html): Types, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK. - [All networks](https://lopw.com/docs/networks/): Stellar as the reference binding, the EVM networks, and the planned Solana binding. --- # Optimism > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/optimism.html · This Markdown twin: https://lopw.com/docs/networks/optimism.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Optimism runs LOPW through the shared [EVM binding](https://lopw.com/docs/networks/evm.html): 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** ## At a glance | 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](https://sepolia-optimism.etherscan.io/address/0x5fd84259d66Cd46123540766Be93DFE6D43130D7) | [0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85](https://optimistic.etherscan.io/address/0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) | | Explorer | [sepolia-optimism.etherscan.io](https://sepolia-optimism.etherscan.io) | [optimistic.etherscan.io](https://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 | ## LOPW contracts ### OP Sepolia Not deployed No LOPW deployment is published on this network yet. ### OP Mainnet Not deployed No LOPW deployment is published on this network yet. ## Oracle note 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’s `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/.json`. ## What is identical to every other EVM network - 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 > **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](https://lopw.com/docs/schemas/offer-evm.json) and [receipt-evm.json](https://lopw.com/docs/schemas/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 binding](https://lopw.com/docs/networks/evm.html): Types, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK. - [All networks](https://lopw.com/docs/networks/): Stellar as the reference binding, the EVM networks, and the planned Solana binding. --- # Polygon > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/polygon.html · This Markdown twin: https://lopw.com/docs/networks/polygon.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Polygon runs LOPW through the shared [EVM binding](https://lopw.com/docs/networks/evm.html): 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** ## At a glance | 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](https://amoy.polygonscan.com/address/0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582) | [0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359](https://polygonscan.com/address/0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) | | Explorer | [amoy.polygonscan.com](https://amoy.polygonscan.com) | [polygonscan.com](https://polygonscan.com) | | P6 oracle | Chainlink feed, verified on-chain at deployment | Chainlink feed, verified on-chain at deployment | | LOPW deployment | None published | None published | ## LOPW contracts ### Polygon Amoy Not deployed No LOPW deployment is published on this network yet. ### Polygon PoS Not deployed No LOPW deployment is published on this network yet. ## Oracle note 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’s `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/.json`. ## What is identical to every other EVM network - 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 > **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](https://lopw.com/docs/schemas/offer-evm.json) and [receipt-evm.json](https://lopw.com/docs/schemas/receipt-evm.json) | > | Explorer | `https://amoy.polygonscan.com/tx/{hash}`, `https://amoy.polygonscan.com/address/{address}` | > | USDC | `0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582` (Polygon Amoy), `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` (Polygon PoS) | - [EVM binding](https://lopw.com/docs/networks/evm.html): Types, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK. - [All networks](https://lopw.com/docs/networks/): Stellar as the reference binding, the EVM networks, and the planned Solana binding. --- # Avalanche > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/avalanche.html · This Markdown twin: https://lopw.com/docs/networks/avalanche.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Avalanche runs LOPW through the shared [EVM binding](https://lopw.com/docs/networks/evm.html): 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** ## At a glance | 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](https://testnet.snowtrace.io/address/0x5425890298aed601595a70AB815c96711a31Bc65) | [0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E](https://snowtrace.io/address/0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E) | | Explorer | [testnet.snowtrace.io](https://testnet.snowtrace.io) | [snowtrace.io](https://snowtrace.io) | | P6 oracle | Chainlink feed, verified on-chain at deployment | Chainlink feed, verified on-chain at deployment | | LOPW deployment | None published | None published | ## LOPW contracts ### Avalanche Fuji Not deployed No LOPW deployment is published on this network yet. ### Avalanche C-Chain Not deployed No LOPW deployment is published on this network yet. ## Oracle note 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’s `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/.json`. ## What is identical to every other EVM network - 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 > **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](https://lopw.com/docs/schemas/offer-evm.json) and [receipt-evm.json](https://lopw.com/docs/schemas/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 binding](https://lopw.com/docs/networks/evm.html): Types, EIP-712 hashing, contracts, P6 with Chainlink, x402 with EIP-3009, SDK. - [All networks](https://lopw.com/docs/networks/): Stellar as the reference binding, the EVM networks, and the planned Solana binding. --- # Solana > 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. *LOPW documentation · Networks.* Canonical page: https://lopw.com/docs/networks/solana.html · This Markdown twin: https://lopw.com/docs/networks/solana.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Solana is a **planned binding**. This page records the design so that the protocol’s 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** ## The design at a glance | 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’s oracle timestamp and Chainlink’s round id | | x402 scheme | `exact` on `solana:` 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` | ## Networks | Network | Name | CAIP-2 | Status | | --- | --- | --- | --- | | Solana devnet | `solana-devnet` | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | Planned; nothing deployed | | Solana mainnet | `solana` | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Planned; nothing deployed | ## What stays identical - 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 > **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 networks](https://lopw.com/docs/networks/): Stellar as the reference binding and the EVM networks with their chain ids, addresses and deployment status. - [Chain bindings (reference)](https://lopw.com/docs/protocol/bindings.html): The contract every binding, including this one, must satisfy. --- # Security model > LOPW's security model: what is protected, trust assumptions, the threats and their mitigations, residual risks, and the known limits accepted in version 1. *LOPW documentation · Deep dive.* Canonical page: https://lopw.com/docs/deep-dive/security.html · This Markdown twin: https://lopw.com/docs/deep-dive/security.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. ## What is protected | 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’s 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 | ## Trust assumptions | 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’s 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’s 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. ## Threats and mitigations | # | 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 | ## Known limits accepted in version 1 - **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’s 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. ## Before any production deployment 1. Independent security audit of all contracts. 2. Admin moved to a multisig account; time lock reviewed. 3. Fuzz and property tests on stake accounting (deposit, slash, withdraw invariants); a property test already checks that the contract’s token balance equals the sum of stakes after random sequences. 4. Monitoring alerts live: admin-change proposals, self-drain patterns, oracle freshness, suspensions, indexer lag, storage lifetimes. 5. Review storage lifetime defaults against service lifetimes. 6. A guarded launch with low caps and an allowlist, loosened through the time lock after the audit. --- # Economics > 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. *LOPW documentation · Deep dive.* Canonical page: https://lopw.com/docs/deep-dive/economics.html · This Markdown twin: https://lopw.com/docs/deep-dive/economics.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. ## What a seller risks | 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’s start, with the first slash of an epoch always allowed | Protocol floor `slash_epoch_cap_bps` | | In total | The stake | The seller’s 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. ## What a seller pays | 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’s 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’s account | | Payouts | `min(max_claim, stake)` per proven breach | The actual cost of a broken promise | ```text 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. ## What a buyer gains | 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’s 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. | ## What a buyer pays - 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. ## A worked claim 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. 1. Unbonding delay and slash epoch: 1 h + 1 h = 2 h. Epoch cap: 50% of 200 = 100 USDC. 2. A quote 10% off the oracle is claimed under P6. Payout: `min(5, 200) = 5 USDC`. Stake 195 USDC; still Active. 3. 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. 4. 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. 5. 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. ## Incentives in one table | 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’s 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 | --- # The decisions log, explained > Why each LOPW rule exists: the twenty-two recorded decisions that refine the specification, in plain language, with what they changed and why. *LOPW documentation · Deep dive.* Canonical page: https://lopw.com/docs/deep-dive/decisions.html · This Markdown twin: https://lopw.com/docs/deep-dive/decisions.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. ## The decisions, one by one ### 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’s note that this “costs the seller its own money” 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` 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 “not applicable”, but `served_at − 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 − oracle_price| × 10000 > max_deviation_bps × oracle_price`, with `oracle_price` read from the oracle, not from the receipt. **Why.** The draft’s division form rounds down, so a 50.99 bps deviation would pass a 50 bps limit; a unit test caught it. The receipt’s 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’s 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’s 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 claims by citing an off-tick time. An oracle the operator controls would undermine the point of P6: evidence the seller does not control. A contract-to-contract read carried no charge beyond the normal transaction fee on the first live claim. ### D-015 Exact byte format for hashing and signing **Decision.** Signed body and signature are separate types; the message is `SHA-256(tag || network_id || ScVal-XDR(registry) || ScVal-XDR(body))`; optional blocks are None/Some enum variants; `request_hash` is fixed; invalid signatures trap; a shared TypeScript package and test vectors pin the bytes. **Why.** So the off-chain code and the contracts hash identical bytes, and so the seller middleware and buyer SDK cannot drift apart. Soroban’s signature check cannot return an error, so a bad signature fails as a host error with the same outcome: rejected, no penalty. ### D-016 Rejected claims are recorded; precondition failures are errors **Decision.** `claim` returns `Err` when evidence or preconditions fail (nothing recorded) and `Ok(Rejected(reason))` when the evidence is valid but the breach is not proven (event recorded). **Why.** A transaction that returns an error is rolled back, events included, so if every non-payout were an error the `ClaimRejected` event could never exist and monitoring could not see rejected claims. New error codes beyond the draft: `KeyRetired`, `UnsupportedVersion`, `ValidatorNotSet`, `SlashRateLimited`. ### D-017 x402 placement **Decision.** The offer travels in `PaymentRequired.extensions.lopw` and is echoed by the buyer; the receipt rides in `PAYMENT-RESPONSE.extensions.lopw` rather than a custom header; handlers report freshness and oracle evidence through two response headers. **Why.** Checked against the current x402 v2 implementation. The receipt then travels in the standard envelope every client already decodes. x402’s own offer-receipt extension signs generic JWS/EIP-712 payloads without terms, hashes or oracle evidence, and a contract cannot cheaply verify JWS, so LOPW keeps a format that is verifiable on-chain; both can be served side by side. ### D-018 Admin changes are time-locked **Decision.** The first wiring applies immediately; every later change to which contract may slash or which validator decides a predicate is a proposal applied only after 26 hours, cancellable until then, announced by events. Contracts have no upgrade function. **Why.** A compromised admin key could otherwise point the registry at a contract that slashes every service to itself in one block. 26 hours exceeds every possible unbonding delay (at most 25 hours), so every seller can withdraw first. ### D-019 Protocol fees **Decision.** A one-off registration fee and an annual stake fee in basis points, accrued per second, collected whenever stake moves or on demand, paid to a treasury; both may be zero; changes time-locked. **Why.** To fund the protocol without touching payouts. The fee can suspend a service at its minimum, so sellers should stake above it. The first deployment runs 1 USDC and 100 bps to show the mechanism; 10 USDC and 100 bps are proposed for a production launch. ### D-020 Guarded launch: limits, pause, allowlist **Decision.** Launch limits on stake per service, total stake and any single payout, plus an allowlist switch; tightening is immediate, loosening is time-locked; a pause stops registrations and deposits but never claims, unbonding or withdrawals. Storage-lifetime extension functions were added for a keeper. **Why.** A contract bug can then lose at most `max_payout` per claim and `max_total_stake` overall. The pause is an emergency brake that can never trap funds. ### D-021 Operational records and exchange rates **Decision.** The API offers `GET /v1/rates`, an hourly exchange-rate snapshot with exact cross rates for later currency calculations. Visitor records of the public hosts are private and operator-only. **Why.** Currency conversions for displays and reports; operational insight that is never exposed publicly. ### D-022 Scheduled scenario runs with plain-language notes **Decision.** A catalogue of named scenarios runs against the live reference services on a schedule with real x402 payments; each run is judged against the expected outcome and recorded with its transactions, payout and stake before and after; payouts are recycled into the seller’s stake. **Why.** So the demo and proofs pages show what the protocol does, continuously and verifiably, in words a reader without protocol knowledge can follow. --- # Proofs and scenarios > 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. *LOPW documentation · Deep dive.* Canonical page: https://lopw.com/docs/deep-dive/proofs-and-scenarios.html · This Markdown twin: https://lopw.com/docs/deep-dive/proofs-and-scenarios.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt Everything below has been run for real on the Stellar test network, with real x402 payments and real claims. The [proofs page](https://lopw.com/proofs/) links each item to its transaction on a public explorer; the [demo](https://lopw.com/demo/) replays one claim step by step with its console output. ## Warranty payouts after real payments | 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 | ## Stake lifecycle | 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` | ## Governance and safety | 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’s USDC balance equalled the sum of stakes after every step in 64 random sequences | ## Automated test suites | 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](https://lopw.com/proofs/). ## The scheduled scenarios Eleven named scenarios run on a schedule against the live reference services and are judged automatically: honest quote, each predicate’s 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’s 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](https://lopw.com/docs/guides/run-scenarios.html). Read the recorded runs: `GET /v1/{network}/scenarios` in the [API](https://lopw.com/docs/protocol/api.html). ## Production network 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’s `STELLAR_MAINNET` entry receives its contract addresses. --- # Agents start here > 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. *LOPW documentation · For AI agents.* Canonical page: https://lopw.com/docs/agents/ · This Markdown twin: https://lopw.com/docs/agents/index.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt 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. ## Machine-readable files | File | What it is | Format | | --- | --- | --- | | [/llms.txt](https://lopw.com/llms.txt) | Index of every documentation page with one-line descriptions and links to the Markdown twins | Markdown (llms.txt convention) | | [/llms-full.txt](https://lopw.com/llms-full.txt) | The entire documentation concatenated, generated from the same source as the HTML | Markdown | | `/docs/.md` | A Markdown twin of every HTML page at the same path, for example [/docs/protocol/predicates.md](https://lopw.com/docs/protocol/predicates.md); each HTML page links to it with `` | Markdown | | [/.well-known/lopw.json](https://lopw.com/.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](https://lopw.com/docs/schemas/offer.json), [receipt.json](https://lopw.com/docs/schemas/receipt.json), [terms.json](https://lopw.com/docs/schemas/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](https://lopw.com/docs/schemas/offer-evm.json), [receipt-evm.json](https://lopw.com/docs/schemas/receipt-evm.json), [terms-evm.json](https://lopw.com/docs/schemas/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](https://lopw.com/docs/vectors/v1.json) | Shared test vectors: exact XDR bytes, hashes and signatures every implementation must reproduce | JSON | | [/docs/api/openapi.json](https://lopw.com/docs/api/openapi.json) | OpenAPI 3.1 description of the read-only HTTP API | JSON | | [/docs/search-index.json](https://lopw.com/docs/search-index.json) | Title, section, headings and text of every page, used by the site search | JSON | ## Facts you need first - **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:`; Solana is a planned binding. Per-network ids, addresses and deployment status: [Networks](https://lopw.com/docs/networks/) and `networks[]` in [lopw.json](https://lopw.com/.well-known/lopw.json). - **Predicates:** 1 Equivocation, 2 Terms mismatch, 3 Stale data (`served_at − data_as_of > max_data_age`; 0 means not applicable), 4 Bad status, 5 Size limit, 6 Oracle deviation (`|reported − oracle| × 10000 > bps × 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. ## To decide whether to pay a service 1. Read its bond: the service’s 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. 2. 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. 3. Verify the offer signature against `get_key(service_id, offer.key_version)`, check `valid_until`, and check that `offer.terms_hash` equals the service’s current `terms_hash` and that `price` and `asset` match the x402 requirement. 4. The buyer SDK does all of this in `agent.fetch(url)` with a `BuyerPolicy`: [Getting started for agents (Markdown)](https://lopw.com/docs/guides/agents.md). ## To verify a receipt you received 1. Decode `PAYMENT-RESPONSE.extensions.lopw.receipt` (base64 ScVal XDR of `SignedReceipt`). 2. 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`. 3. 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. 4. Evaluate each predicate in the terms exactly as in [Predicates (Markdown)](https://lopw.com/docs/protocol/predicates.md); for P6 read `price(oracle_asset, oracle_timestamp)` from the oracle named in the terms. 5. 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`. ## To file a claim *Claims contract* ```text claim(predicate_id: u32, offer: SignedOffer, receipt: SignedReceipt, extra: ClaimExtra) -> Result // 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)](https://lopw.com/docs/protocol/claims.md#error-codes). ## To read protocol state without a key | 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/{network}/claims?outcome=paid\|rejected` or per service `/services/{id}/claims` | | Live events | `GET /v1/{network}/stream` (Server-Sent Events; resume with `Last-Event-ID`) | | Recorded scenario runs | `GET /v1/{network}/scenarios?runs=5` | | The chain itself | `get_service`, `get_terms`, `get_key`, `get_claim`, `get_validator` through a read-only simulation | Conventions: JSON, `snake_case`, amounts as decimal strings, times as Unix seconds, 32-byte values as lowercase hex, cursor pagination, ETags, problem+json errors, open CORS. Full reference: [HTTP API (Markdown)](https://lopw.com/docs/protocol/api.md). ## Page map **Start here** - [What is LOPW](https://lopw.com/docs/index.md): 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. - [How a warranty works](https://lopw.com/docs/start/how-it-works.md): An animated, step-by-step walk through a LOPW warranty: offer, payment, receipt, local check, claim, validator, and payout or rejection. - [Who it is for](https://lopw.com/docs/start/who-it-is-for.md): What LOPW gives agents, API sellers and platforms, what each has to do, and where each should start reading. - [Glossary](https://lopw.com/docs/start/glossary.md): Every LOPW term in one place, from stake and terms to predicates, epochs and key versions, with links to where each is specified. - [FAQ](https://lopw.com/docs/start/faq.md): 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. **Guides** - [Getting started](https://lopw.com/docs/getting-started.md): 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. - [Getting started for agents](https://lopw.com/docs/guides/agents.md): 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. - [Getting started for sellers](https://lopw.com/docs/guides/sellers.md): 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. - [Getting started for app builders](https://lopw.com/docs/guides/apps.md): 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. - [How-to guides](https://lopw.com/docs/guides.md): 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. - [Add a warranty to an x402 API](https://lopw.com/docs/guides/add-warranty.md): Checklist to turn an existing x402 endpoint into a bonded one: signing key, terms, registration, stake, the lopw extension and the freshness header. - [Make an agent pay only bonded services](https://lopw.com/docs/guides/bonded-only.md): Configure the buyer policy so an agent refuses unbonded, under-staked or under-covered sellers before any money moves, and handle refusals. - [File a claim](https://lopw.com/docs/guides/file-claim.md): 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. - [Choose predicates and limits](https://lopw.com/docs/guides/predicates-limits.md): 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. - [Rotate a signing key](https://lopw.com/docs/guides/rotate-key.md): 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. - [Withdraw stake safely](https://lopw.com/docs/guides/unbonding.md): Partial and full withdrawals through request_unbond, the unbonding delay, cancel_unbond, withdraw, and what remains slashable, with the exact contract rules. - [Read a bond from any app](https://lopw.com/docs/guides/read-bond.md): 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. - [Run the scenarios yourself](https://lopw.com/docs/guides/run-scenarios.md): 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. **Protocol reference** - [Protocol overview](https://lopw.com/docs/protocol/index.md): 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. - [Offer, receipt and terms](https://lopw.com/docs/protocol/offer-and-receipt.md): 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. - [Hashing and signing](https://lopw.com/docs/protocol/hashing-and-signing.md): 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. - [Predicates P1 to P6](https://lopw.com/docs/protocol/predicates.md): 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. - [Claims](https://lopw.com/docs/protocol/claims.md): The LOPW Claims contract: the seven checks in order (animated), outcomes, every error code, the payout rule, records, events and functions. - [Bond Registry](https://lopw.com/docs/protocol/bond-registry.md): 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. - [x402 extension](https://lopw.com/docs/protocol/x402.md): 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. - [HTTP API](https://lopw.com/docs/protocol/api.md): 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. - [Chain bindings](https://lopw.com/docs/protocol/bindings.md): 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. **Networks** - [Networks](https://lopw.com/docs/networks/index.md): 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. - [Stellar](https://lopw.com/docs/networks/stellar.md): 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. - [EVM binding](https://lopw.com/docs/networks/evm.md): 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. - [Base](https://lopw.com/docs/networks/base.md): 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. - [Ethereum](https://lopw.com/docs/networks/ethereum.md): 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. - [Arbitrum](https://lopw.com/docs/networks/arbitrum.md): 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. - [Optimism](https://lopw.com/docs/networks/optimism.md): 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. - [Polygon](https://lopw.com/docs/networks/polygon.md): 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. - [Avalanche](https://lopw.com/docs/networks/avalanche.md): 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. - [Solana](https://lopw.com/docs/networks/solana.md): 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. **Deep dive** - [Security model](https://lopw.com/docs/deep-dive/security.md): LOPW's security model: what is protected, trust assumptions, the threats and their mitigations, residual risks, and the known limits accepted in version 1. - [Economics](https://lopw.com/docs/deep-dive/economics.md): 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. - [The decisions log, explained](https://lopw.com/docs/deep-dive/decisions.md): Why each LOPW rule exists: the twenty-two recorded decisions that refine the specification, in plain language, with what they changed and why. - [Proofs and scenarios](https://lopw.com/docs/deep-dive/proofs-and-scenarios.md): 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. **For AI agents** - [Agents start here](https://lopw.com/docs/agents/index.md): 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.