> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keystoneos.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# KeystoneSettlement

> Escrow and the fixed settlement lifecycle in one contract.

`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:

```
Registered -> Executed              (single-chain: last deposit auto-executes)
Registered -> Prepared              (cross-chain mode: all legs funded, awaiting the router)
Prepared   -> Executed              (router release, strictly before abortDeadline)
Registered | Prepared -> Aborted    (operator while Registered; the router from either, cross-chain)
Registered | Prepared -> TimedOut   (operator or any depositor, past the deadline)
```

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

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#1a3a35', 'primaryTextColor': '#e0f2ef', 'primaryBorderColor': '#2DD4A8', 'lineColor': '#2DD4A8', 'labelColor': '#e0f2ef', 'stateBkg': '#1a3a35', 'stateBorder': '#2DD4A8', 'transitionColor': '#2DD4A8', 'compositeBackground': '#0f2420', 'compositeBorder': '#1AAF8B', 'compositeTitleBackground': '#162e2a', 'innerEndBackground': '#2DD4A8', 'specialStateColor': '#2DD4A8'}}}%%
stateDiagram-v2
    [*] --> Registered : registerSettlement (operator)
    Registered --> Executed : last depositLeg auto-executes,<br/>or execute() by operator/depositor<br/>(single-chain, abortDeadline = 0)
    Registered --> Prepared : last depositLeg or prepare()<br/>(cross-chain, abortDeadline > 0)
    Prepared --> Executed : executeFromRouter<br/>(router only, before abortDeadline)
    Prepared --> Aborted : abortFromRouter (router only)
    Prepared --> TimedOut : claimTimeout<br/>(operator/depositor, >= abortDeadline)
    Registered --> Aborted : abort (operator only)<br/>or abortFromRouter
    Registered --> TimedOut : claimTimeout<br/>(operator/depositor, >= timeoutAt)
    Aborted --> Aborted : claimRefund per leg<br/>(each depositor, or operator)
    TimedOut --> TimedOut : claimRefund per leg<br/>(each depositor, or operator)
    Executed --> [*]
```

## Registration

```solidity theme={null}
function registerSettlement(
    bytes32 settlementId,
    LegInput[] calldata legs,
    bytes32[] calldata partyHashes,
    uint256 timeoutAt,
    uint256 abortDeadline
) external onlyRole(OPERATOR_ROLE) whenNotPaused
```

`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](#remote-mode-the-prepared-phase) below): the last deposit flips the settlement to `Prepared` and the [KeystoneRouter](/smart-contracts/keystone-router) 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:

```solidity theme={null}
struct LegInput {
    address token;          // ERC-20 / ERC-3643 token
    uint256 amount;         // the settlement obligation (what the recipient gets)
    address recipient;      // bound here - nothing can change it later
    bytes32 depositKey;     // keccak256(abi.encode(depositorWallet, secret))
    TokenStandard tokenStandard;  // ERC20 = 0, ERC3643 = 1
    FeeSpec[] fees;         // (recipient, amount, additive) - max 10 per leg
}
```

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

```solidity theme={null}
function depositLeg(bytes32 settlementId, uint256 legIndex, bytes32 depositSecret)
    external whenNotPaused nonReentrant
```

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.

```solidity theme={null}
function execute(bytes32 settlementId) external whenNotPaused nonReentrant
```

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](/smart-contracts/keystone-router) collects confirmations from every participating chain and lands a release or abort decision.

```solidity theme={null}
function prepare(bytes32 settlementId) external whenNotPaused nonReentrant
function executeFromRouter(bytes32 settlementId) external onlyRouter(settlementId) whenNotPaused nonReentrant
function abortFromRouter(bytes32 settlementId) external onlyRouter(settlementId) nonReentrant
```

* `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

```solidity theme={null}
function abort(bytes32 settlementId) external onlyRole(OPERATOR_ROLE) nonReentrant
function claimTimeout(bytes32 settlementId) external nonReentrant
function claimRefund(bytes32 settlementId, uint256 legIndex) external nonReentrant
```

* `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

| Event                                                      | When                                                                      |
| ---------------------------------------------------------- | ------------------------------------------------------------------------- |
| `SettlementRegistered(settlementId, legCount, timeoutAt)`  | Registration                                                              |
| `LegDeposited(settlementId, legIndex, depositor, amount)`  | Each deposit (amount includes additive fees)                              |
| `SettlementPrepared(settlementId)`                         | Cross-chain settlement fully funded and gate-cleared, awaiting the router |
| `SettlementExecuted(settlementId)`                         | Atomic payout completed                                                   |
| `LegPaidOut(settlementId, legIndex, recipient, amount)`    | Per-leg payout at execution                                               |
| `FeesCollected(settlementId, recipient, token, amount)`    | Per-fee transfer at execution                                             |
| `SettlementAborted(settlementId)`                          | Operator or router abort                                                  |
| `SettlementTimedOut(settlementId)`                         | Timeout claim (operator or any depositor)                                 |
| `RefundClaimed(settlementId, legIndex, depositor, amount)` | Per-leg refund                                                            |
| `RouterSet(router)`                                        | Admin re-points the router used for future registrations                  |

KeyStone's backend maps `SettlementExecuted` / `SettlementAborted` / `SettlementTimedOut` to the settlement states `SETTLED` / `ROLLED_BACK` / `TIMED_OUT` by event name - see [State Machine](/concepts/state-machine).

## Reads

```solidity theme={null}
function getSettlement(bytes32 id) external view returns (Settlement memory);  // status, timeoutAt, abortDeadline, router, legCount, depositedCount
function getLeg(bytes32 id, uint256 legIndex) external view returns (LegState memory);
function getLegFees(bytes32 id, uint256 legIndex) external view returns (FeeSpec[] memory);
function getPartyHashes(bytes32 id) external view returns (bytes32[] memory);
function allLegsDeposited(bytes32 id) external view returns (bool);
function isExecutable(bytes32 id) external view returns (bool);
```

## Roles and pause

| Role                 | Powers                                             |
| -------------------- | -------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE` | pause/unpause, role management, `setRouter`        |
| `OPERATOR_ROLE`      | `registerSettlement`, `abort`, `refundViaTransfer` |

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.
