Skip to main content
KeystoneSettlement replaces the earlier SettlementCoordinator + KeystoneEscrow pair with one contract that owns the full on-chain lifecycle. The lifecycle is fixed in code - every settlement runs the same audited machine:
There are no on-chain state graphs, no gates configuration, and no transition transactions. KeyStone’s backend writes exactly two things here: registerSettlement and (operator-only) abort. setRouter is a deploy-time admin step, not part of settlement flow.

Lifecycle

Registration

abortDeadline selects the settlement mode. 0 is single-chain: the settlement executes autonomously on the last deposit, exactly as before. A non-zero value opts into the cross-chain Prepared phase (see Remote mode below): the last deposit flips the settlement to Prepared and the KeystoneRouter drives release or abort. Registering in cross-chain mode requires a configured router; the router address is snapshotted into the settlement at registration, so re-pointing the live router later can never redirect an in-flight settlement. KeyStone currently registers every settlement with abortDeadline = 0. Each LegInput binds everything permanently:
Validation at registration: 1-50 legs, 1-50 non-zero party hashes, future timeout, non-zero tokens/recipients/keys/amounts, deductive fees never exceeding the leg amount, and an isAgent check for ERC-3643 legs (so agent-gated payouts cannot strand funds later). Re-registering an existing id reverts SettlementAlreadyExists.

Deposits: wallet-bound commitment keys

The contract verifies keccak256(abi.encode(msg.sender, depositSecret)) == leg.depositKey - the key commits to BOTH the depositor address and the secret, so a leaked secret is useless from any other wallet. The deposit pulls amount + additive fees via transferFrom and records the depositor for refunds. Deposits are accepted strictly before timeoutAt. Duplicate deposits revert LegAlreadyDeposited; a wrong secret or wrong wallet reverts InvalidDepositKey.

Autonomous execution

If the last outstanding deposit lands while the compliance gate already passes, the settlement executes inline in the same transaction - every leg pays out to its bound recipient, fees transfer, all-or-nothing.
If attestations were still pending at the last deposit, the settlement stays Registered (a closed gate never reverts a deposit) and once the gate clears execute() can be called by the operator or any depositor: every recipient and fee was bound at registration, so the caller only triggers the pre-committed plan, never steers it. (Unrelated third parties are excluded - this is permissioned institutional settlement - but every party with funds at stake can always drive execution itself.) The gate: ComplianceRegistry.areAllPartiesCleared(settlementId, partyHashes) - every party hash bound at registration must be attested Pass.

Remote mode: the Prepared phase

A settlement registered with abortDeadline > 0 never auto-executes. Instead, when every leg is funded and the compliance gate passes, it flips to Prepared - the chain’s promise to hold funds while the KeystoneRouter collects confirmations from every participating chain and lands a release or abort decision.
  • prepare - operator-or-participant (the same gate as execute). Requires all legs deposited, the compliance gate passing, and block.timestamp < timeoutAt. The last depositLeg also flips to Prepared inline when the gate already passes, so an explicit prepare call is only needed when attestations land after the final deposit. Emits SettlementPrepared.
  • executeFromRouter - the only path that pays out a remote-mode settlement. Callable only by the settlement’s snapshotted router, strictly before abortDeadline, and the compliance gate is re-checked at release time - a settlement whose compliance has since failed reverts instead of paying out.
  • abortFromRouter - router-only abort, accepted from Registered (another chain never funded) or Prepared (the coordinator decided abort). Not pausable, and a re-delivered abort can never touch an Executed settlement.
The release window (< abortDeadline) and the Prepared-phase timeout window (>= abortDeadline, claimTimeout by the operator or any depositor) are disjoint by timestamp: at no instant are both release and timeout live, so a cross-chain settlement can never both pay out and refund.

Recovery: abort, timeout, pull refunds

  • abort - operator-only, allowed only while Registered. The race-free runbook against a pending auto-execution is pause first (pause blocks deposits and execution; abort stays pause-exempt), then abort.
  • claimTimeout - operator or any depositor. For a Registered settlement it opens at timeoutAt; for a Prepared (cross-chain) settlement it opens at abortDeadline. Each window is mutually exclusive by timestamp with the corresponding execution window.
  • claimRefund - any depositor (or the operator), per leg, after Aborted or TimedOut. Pays amount + additive fees to the recorded depositor, never an alternate address. Per-leg pull payment means one un-receivable depositor (e.g. a USDC blacklist) can never block other legs’ refunds.
  • refundViaTransfer - operator escape hatch for ERC-3643 legs whose agent role was revoked after deposit; identical guards, standard transfer path.
None of the recovery functions are pausable - a pause must never trap funds past their deadline.

Events

KeyStone’s backend maps SettlementExecuted / SettlementAborted / SettlementTimedOut to the settlement states SETTLED / ROLLED_BACK / TIMED_OUT by event name - see State Machine.

Reads

Roles and pause

Pause blocks registration, deposits, and execution - never abort, timeout, or refunds.

Token support

ERC-20 and ERC-3643 (T-REX security tokens). ERC-3643 payouts and refunds route through agent-gated forcedTransfer; the contract must be an agent on the token, verified at registration. Fee-on-transfer and rebasing tokens are not supported.