Exchange state machine
Every commitment to an offer becomes an exchange with a state. The state machine is small, but the transitions are the protocol's contract surface.
State table
| State | Who can transition out | To | Action | Funds movement |
|---|---|---|---|---|
COMMITTED | Buyer | REDEEMED | redeemVoucher(exchangeId) | none (yet) |
COMMITTED | Buyer | CANCELLED | cancelVoucher(exchangeId) | buyer loses buyerCancelPenalty, gets rest back |
COMMITTED | Seller | REVOKED | revokeVoucher(exchangeId) | seller loses sellerDeposit to buyer |
COMMITTED | Anyone | EXPIRED | expireVoucher(exchangeId) | buyer loses buyerCancelPenalty, gets rest back |
REDEEMED | Anyone | COMPLETED | completeExchange(exchangeId) after dispute window | seller treasury receives price |
REDEEMED | Buyer | DISPUTED | raiseDispute(exchangeId) within dispute window | escrow held pending resolution |
What "anyone" means
completeExchange and the auto-EXPIRED transitions can be triggered by any wallet. Sellers typically batch-call them via complete_exchange to release earnings. See Build → Sellers → Manage funds.
Windows
Three time windows govern the state machine. All set on the offer at creation:
- Validity window (
validFromDate–validUntilDate) — when buyers can commit. - Voucher window (
voucherRedeemable{From,Until,ValidDuration}) — when the buyer can redeem. - Dispute window — fixed
disputeDurationafter redemption.
A buyer who doesn't redeem before the voucher window closes loses the price; the seller still loses sellerDeposit. This protects the buyer against silent seller absence.
Where to look in code
| Surface | Where |
|---|---|
| Contracts | ExchangeCommitFacet, ExchangeHandlerFacet, DisputeHandlerFacet |
| Core SDK | Exchanges mixin, Disputes mixin |
| MCP | commit_to_offer, redeem_voucher, cancel_voucher, revoke_voucher, complete_exchange, raise_dispute — see Reference → MCP tools |
Common footguns
- Don't read the new state until the subgraph indexes it. See Eventing & indexing.
- The
EXPIREDtransition is implicit. No event fires when a voucher silently expires; you must checkvoucherRedeemableUntilDateagainstblock.timestamp. The subgraph synthesizes the state for you. cancelVoucheris buyer-initiated;revokeVoucheris seller-initiated. Different functions; different penalties.
Next
- Dispute state machine — what happens after a dispute is raised.
- Offer lifecycle — the parent entity's state.
- Build → Sellers → Handle redemption / deliver for the fulfillment side.