LOPWDocumentation

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

AssetWhere it livesWho must not be able to take it
Seller stake (USDC)Bond RegistryAnyone except the seller after unbonding, and buyers with a proven claim
Buyer payoutsPaid from stake on a proven claimAnyone other than the buyer named in the receipt
Service signing keysSeller’s serverAttackers, who could sign receipts the seller is liable for
Seller account keysSellerAttackers, who could withdraw stake after the delay
Admin keyProtocol operatorAttackers, who could rewire which contracts may slash
Integrity of claim recordsClaims contractAnyone trying to be paid twice for one payment

Trust assumptions

TrustedForIf wrong
Consensus and the contract hostCorrect execution, finalityEverything fails; out of scope
The SEP-40 oracle named in the termsP6 truthWrong P6 outcomes (known limit)
The x402 facilitatorSettling the buyer’s paymentPayment fails or is not settled; LOPW does not escrow, so no stake is at risk
The admin keyInitial wiring and time-locked changesBounded by the 26-hour time lock
The seller’s own reports for P3 to P5data_as_of, status, response_bytesA 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

#ThreatMitigationResidual risk
T1Replay a paid claim with the same or a different receiptOne paid claim per (service_id, payment_reference), stored on-chainNone known
T2Cross-network replay (a test-network receipt on the production network)Network ID in every signed hashNone
T3Cross-deployment replay against another registryRegistry address in every signed hash; Claims checks offer.registryNone
T4Forged receiptEd25519 signature by the service key registered on-chainKey theft (T6)
T5Receipt for an offer the buyer never sawClaims requires receipt.offer_hash == hash(offer) and a valid offer signatureNot slashable, by design
T6Signing key stolenrotate_key retires the old key; receipts with served_at after retirement fail with KeyRetiredReceipts forged before rotation, within the claim window, bounded by max_claim each and the slash rate limit
T7Seller self-drain: invented payments to its own addresses to pull stake out ahead of honest claimsSlash rate limit: at most 50% of epoch-start stake per epoch (epoch = unbonding delay); every payout capped by max_claimA seller can still pay itself up to the cap; it gains nothing over a normal withdrawal, and honest claims compete in the same epoch
T8Seller exits before claims landUnbonding delay exceeds the longest claim window of any terms version ever used; stake stays slashable while unbondingNone within the claim window
T9Admin 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 eventsSellers must watch for ClaimsChangeProposed / ValidatorChangeProposed
T10Contract upgrade abuseContracts have no upgrade function; changes require a new deploymentNone
T11Oracle manipulation or outageP6 compares against one past tick, read on-chain; missing data is NotVerifiable (no payout, no penalty); the seller chooses its toleranceThe oracle itself is trusted
T12Off-tick oracle timestamp to dodge P6The validator uses the tick the oracle returns and checks the age against itNone
T13Rounding to dodge P6Exact integer comparison with no divisionNone
T14Front-running a claimAnyone may submit, but the payout always goes to receipt.buyerNone
T15Claim spam or griefingThe submitter pays fees; rejected claims change no state except an eventEvent noise only
T16Buyer pays a suspended or under-staked sellerMiddleware stops offering when not Active; the SDK reads live state, stake and terms before paying and aborts on policy failureA race of seconds between read and payment
T17Over-subscription: the seller serves more calls than its stake coversStake and max_claim are public before payment; agents set minStake / minMaxClaimLater claims may find stake exhausted
T18State expiry of services, terms, keys or claim recordsPersistent storage with lifetime extended on every access; claim windows are far shorterLong-idle services may need a lifetime extension or restore
T19Indexer or API compromise or lagRead-only, rebuilt from chain events; SDKs read the chain directly for every payment decisionMisleading dashboards, no fund risk
T20API abuseRate limits, pagination caps, no write endpointsDenial of service of a convenience service

Known limits accepted in version 1

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.