# 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).
