Docs / Deep dive
Security model
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_referenceandbuyeris 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
- Independent security audit of all contracts.
- Admin moved to a multisig account; time lock reviewed.
- 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.
- Monitoring alerts live: admin-change proposals, self-drain patterns, oracle freshness, suspensions, indexer lag, storage lifetimes.
- Review storage lifetime defaults against service lifetimes.
- A guarded launch with low caps and an allowlist, loosened through the time lock after the audit.