machine_version at creation) but cannot change its states or transitions. The on-chain KeystoneSettlement contract implements the chain-owned part of the machine natively.
One owner per state
Every state has exactly one owner - the only writer that can advance out of it:
KeyStone never sends state-transition transactions. Its only chain writes are
registerSettlement, the compliance attest, and the operator abort. Everything in the chain-owned region - deposits, execution, timeout, refunds - happens autonomously on the contract, and the observed events drive the settlement record.
The walk
- INSTRUCTED - Settlement created (from confirmed paired instructions or direct initiation), machine version pinned. On-chain registration happens later, when the walk leaves COMPLIANCE_CLEARED: recipients bound per leg, deposit keys, party hashes, timeout.
- COMPLIANCE_CHECKING - The engine screens all parties via the template’s providers (LSEG entity screening, CipherOwl wallet screening). A failure routes to
REJECTED- the terminal taxonomy for pre-deposit compliance failures. - COMPLIANCE_CLEARED - Checks passed. The engine continues the walk.
- AWAITING_DEPOSITS - The handoff point. The engine yields; only contract events advance past here.
- SETTLED - The observed
SettlementExecutedevent landed: the contract paid out every leg atomically. The engine resumes for close-out bookkeeping. - FINALIZED - Done.
Terminal states
MANUAL_REVIEW is not terminal: it parks a settlement for an operator after an operational dead end (a failed post-clearance action, a stale settlement past its deadline, or an operator stopping a settlement that must not proceed). The machine defines this escalation edge out of every pre-settlement state and deliberately not out of SETTLED - once funds are delivered, the only exit is FINALIZED.
Autonomous execution
On the lastdepositLeg, the contract checks the compliance gate (ComplianceRegistry.areAllPartiesCleared) and executes inline in the same transaction - every leg pays out to its recipient bound at registration, all-or-nothing. If attestations are still pending at the last deposit, the settlement stays registered and once the gate clears execute() can be called by the operator or any depositor: the caller only triggers the pre-committed plan, no one can steer it.
Depositor-driven recovery
Deposits and execution are live strictly beforetimeoutAt; recovery is live at and after it - the two windows never overlap.
claimTimeout and claimRefund can be called by any depositor (or the operator) and cannot be paused - funds are never locked indefinitely, even if KeyStone is down. Refunds are per-leg pull payments: one blocked depositor can never strand another leg’s funds.
KeyStone never calls claimTimeout itself - the claim belongs to the parties. What KeyStone does own is surfacing the condition: once the deadline passes in AWAITING_DEPOSITS with no timeout claimed, settlement reads report timeout_claimable: true, GET /v1/settlements?timeout_claimable=true lists every settlement in that condition, and a settlement.timeout_claimable activity entry (severity warning) is recorded once in the activity log.
Events
The contract emits a fixed event set -SettlementRegistered, LegDeposited, SettlementExecuted, SettlementAborted, SettlementTimedOut, RefundClaimed, LegPaidOut, FeesCollected. These form the complete on-chain audit trail; KeyStone’s database mirrors them through a deduplicated indexer/webhook intake, mapping events to states by name through the settlement’s pinned machine.
Error handling
- A failed engine action routes to the machine’s failure target for that state (
REJECTEDfrom compliance,MANUAL_REVIEWafter clearance). If no legal failure transition exists, the settlement parks in place with a durable audit record instead of corrupting the walk. - A settlement past
timeout_atin an engine-owned state escalates toMANUAL_REVIEW, exceptSETTLED, which is flagged for operators instead - delivered funds only finalize.TIMED_OUTitself only ever comes from the contract. - A settlement past
timeout_atinAWAITING_DEPOSITSkeeps its state - only a chain event or an admin forced transition moves it - but stops being invisible: reads report ittimeout_claimable, and onesettlement.timeout_claimableactivity entry records where the remedy lies. With an escrow registered (escrow_registered: true) that is the parties’claimTimeout; with none recorded, the entry asks an operator to establish on-chain whether a claim exists before resolving it. - A chain event that contradicts the database state is surfaced to operators as a divergence - never silently absorbed, never retried forever.