> ## 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.

# Escrow Deposits

> How parties deposit with wallet-bound commitment secrets and the contract executes autonomously.

KeyStone uses a commitment-based deposit scheme on the KeystoneSettlement contract to ensure atomic settlement while keeping deposit intent private. Parties deposit their assets directly to the contract using a wallet-bound deposit secret. The LAST deposit executes the settlement inline once the compliance gate passes - every leg pays out to its recipient bound at registration, with no further transaction from anyone.

## How it works

### 1. Settlement reaches AWAITING\_DEPOSITS

After compliance clears, the settlement advances to `AWAITING_DEPOSITS`. At this point, the API response includes a `deposit_secret` and `deposit_key` for each leg with `direction: "deliver"`.

### 2. Parties deposit using their secret

Each party deposits on-chain by calling `depositLeg(settlementId, legIndex, depositSecret)` on the KeystoneSettlement contract. The platform's backend uses its custody provider (such as MPC wallets or any signing solution) to construct and sign the deposit transaction.

KeyStone is NOT involved in deposits. The contract handles everything:

* Verifies `keccak256(abi.encode(msg.sender, depositSecret)) == leg.depositKey` - the key binds BOTH the depositor wallet and the secret
* Validates the leg has not already been deposited (`LegAlreadyDeposited`)
* Pulls `amount + additive fees` via `transferFrom`
* Emits a `LegDeposited` event

<Note>
  The deposit key commits to the depositor wallet AND the secret. The transaction MUST be sent from the wallet the key was computed for - a leaked secret is useless from any other address. Depositor addresses are only revealed on-chain at deposit time, not at settlement creation.
</Note>

### 3. Last deposit executes inline

If the leg just deposited was the last outstanding one AND every party hash is attested Pass in the ComplianceRegistry, the settlement executes **inside the same deposit transaction**:

* Tokens from the seller's leg go to the buyer's wallet (recipient bound at registration)
* Tokens from the buyer's leg go to the seller's wallet
* Fees (if configured) transfer to the fee recipient

If attestations are still pending at the last deposit, the settlement stays registered; once the gate clears, `execute(settlementId)` can be called by the operator or any depositor - the caller only triggers the pre-committed plan, no one can change it.

### 4. Recovery: abort, timeout, pull refunds

If the settlement is aborted (operator-only, pre-execution) or the deadline passes (`claimTimeout`, callable by the operator or any depositor):

* Deposits stay locked until each one is claimed back via `claimRefund(settlementId, legIndex)` - per leg, by its depositor (or the operator)
* The refund always pays the depositor address RECORDED at deposit time, including any additive fees
* One blocked depositor can never strand another leg's refund
* No partial execution is possible at any point

## Commitment scheme overview

The commitment scheme replaces address-based deposit authorization with cryptographic secrets:

| Concept          | Description                                                                                                                                                                                     |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deposit_secret` | Random 32-byte hex string generated by the API per leg, stored encrypted                                                                                                                        |
| `deposit_key`    | `keccak256(abi.encode(partyWallet, deposit_secret))` - bound on-chain at registration                                                                                                           |
| Authorization    | `keccak256(abi.encode(msg.sender, callerSecret)) == storedDepositKey`                                                                                                                           |
| Privacy benefit  | Depositor intent is hidden until the deposit transaction. Recipients are bound (and visible) from registration - that is what makes execution autonomous and safe for any depositor to trigger. |

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#1a3a35', 'actorBorder': '#2DD4A8', 'actorTextColor': '#e0f2ef', 'actorLineColor': '#2DD4A8', 'signalColor': '#2DD4A8', 'signalTextColor': '#e0f2ef', 'noteBkgColor': '#162e2a', 'noteTextColor': '#e0f2ef', 'noteBorderColor': '#1AAF8B', 'activationBkgColor': '#1a3a35', 'activationBorderColor': '#2DD4A8', 'labelBoxBkgColor': '#1a3a35', 'labelBoxBorderColor': '#2DD4A8', 'labelTextColor': '#e0f2ef', 'loopTextColor': '#2DD4A8'}}}%%
sequenceDiagram
    participant API as KeyStone API
    participant K as KeystoneSettlement
    participant Seller as Seller Wallet
    participant Buyer as Buyer Wallet

    Note over API: Generate secrets per leg at creation
    API->>API: secret_seller, secret_buyer = random(32 bytes)
    API->>API: key_seller = keccak256(sellerWallet, secret_seller)
    API->>API: key_buyer = keccak256(buyerWallet, secret_buyer)

    API->>K: registerSettlement(id, legs: [{recipient: buyer, depositKey: key_seller}, {recipient: seller, depositKey: key_buyer}], partyHashes, timeoutAt, abortDeadline=0)
    Note over K: recipients + keys + timeout bound permanently<br/>abortDeadline=0 = single-chain

    API-->>Seller: deposit_secret for their leg
    API-->>Buyer: deposit_secret for their leg

    Seller->>K: depositLeg(settlementId, 0, secret_seller)
    Note over K: keccak256(sellerWallet, secret) == key? Yes
    Buyer->>K: depositLeg(settlementId, 1, secret_buyer)
    Note over K: last deposit + compliance gate passes

    K->>K: execute inline: pay every leg to its bound recipient
```

## Deposit flow for platforms

From your platform's perspective:

1. You receive a webhook: `settlement.state.awaiting_deposits`
2. Query the settlement via `GET /v1/settlements/{id}` - the response includes `deposit_secret` and `deposit_key` on each leg
3. For each leg where your party has `direction: "deliver"`, construct a deposit transaction:
   * Approve the KeystoneSettlement contract to spend the token (amount + any additive fees)
   * Call `depositLeg(settlementId, legIndex, depositSecret)` from the wallet the deposit key was computed for
4. Your custody provider signs and broadcasts the transaction
5. The contract verifies the wallet-bound secret and accepts the deposit
6. The last deposit auto-executes the settlement when the compliance gate passes
7. You receive a webhook: `settlement.state.finalized`

<Note>
  **Cross-chain mode:** a settlement registered with a non-zero `abortDeadline` does not auto-execute on the last deposit. It flips to the contract's `Prepared` phase and the [KeystoneRouter](/smart-contracts/keystone-router) coordinates release across the involved chains. Your deposit flow is identical either way; the hosted platform currently registers every settlement in single-chain mode.
</Note>

## Integration example

<CodeGroup>
  ```typescript TypeScript SDK + ethers.js theme={null}
  import { KeystoneClient } from "@keystoneos/sdk";
  import { ethers } from "ethers";

  const client = new KeystoneClient({
    clientId: process.env.KEYSTONE_CLIENT_ID!,
    clientSecret: process.env.KEYSTONE_CLIENT_SECRET!,
  });

  // Get the deposit secret from the settlement response
  const settlement = await client.settlements.get(settlementId);

  // Find the leg for your party
  const leg = settlement.legs.find(
    (l) => l.partyRole === "seller" && l.direction === "deliver"
  );
  const depositSecret = leg.depositSecret;

  // First, approve the escrow contract to spend tokens
  const tokenContract = new ethers.Contract(leg.instrumentId, ERC20_ABI, partySigner);
  await (await tokenContract.approve(SETTLEMENT_ADDRESS, leg.quantity)).wait();

  // Then deposit with the secret
  const settlementContract = new ethers.Contract(SETTLEMENT_ADDRESS, KEYSTONE_SETTLEMENT_ABI, partySigner);
  const tx = await settlementContract.depositLeg(
    settlementId,   // bytes32
    leg.legIndex,   // uint256
    depositSecret   // bytes32
  );
  await tx.wait();
  ```

  ```python Python SDK + web3.py theme={null}
  from keystoneos import KeystoneClient
  from web3 import Web3

  async with KeystoneClient(
      client_id="YOUR_CLIENT_ID",
      client_secret="YOUR_CLIENT_SECRET",
  ) as client:
      # Get the deposit secret from the settlement response
      settlement = await client.settlements.get(settlement_id)

      # Find the leg for your party
      leg = next(
          l for l in settlement.legs
          if l.party_role == "seller" and l.direction == "deliver"
      )
      deposit_secret = leg.deposit_secret

      # First, approve the escrow contract to spend tokens
      token_contract = w3.eth.contract(address=leg.instrument_id, abi=ERC20_ABI)
      approve_tx = token_contract.functions.approve(
          SETTLEMENT_ADDRESS, int(leg.quantity)
      ).transact()
      w3.eth.wait_for_transaction_receipt(approve_tx)

      # Then deposit with the secret
      settlement_contract = w3.eth.contract(address=SETTLEMENT_ADDRESS, abi=KEYSTONE_SETTLEMENT_ABI)
      deposit_tx = settlement_contract.functions.depositLeg(
          settlement_id,   # bytes32
          leg.leg_index,   # uint256
          deposit_secret   # bytes32
      ).transact()
      w3.eth.wait_for_transaction_receipt(deposit_tx)
  ```
</CodeGroup>

## Monitoring deposit status

Check deposit progress by querying the settlement:

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.keystoneos.xyz/v1/settlements/$ID \
    -H "Authorization: Bearer $TOKEN"
  ```

  ```typescript TypeScript SDK theme={null}
  const settlement = await client.settlements.get("550e8400-...");
  console.log(settlement.state);

  for (const leg of settlement.legs) {
    console.log(`Leg ${leg.legIndex}: ${leg.depositStatus}`);
  }
  ```

  ```python Python SDK theme={null}
  settlement = await client.settlements.get("550e8400-...")
  print(settlement.state)

  for leg in settlement.legs:
      print(f"Leg {leg.leg_index}: {leg.deposit_status}")
  ```
</CodeGroup>

The response includes the current state and leg statuses showing which deposits have been received.

## Timeout behavior

Every settlement has a `timeout_at` deadline, enforced by the contract itself. If all deposits are not received strictly before it:

1. The operator or any depositor can call `claimTimeout(settlementId)` - not pausable
2. The settlement transitions to `TIMED_OUT`
3. Each received deposit is reclaimed via `claimRefund(settlementId, legIndex)` - by its depositor or the operator, always paying the recorded depositor
4. All platforms are notified via webhook once the events are observed

Deposits/execution and timeout claims are mutually exclusive by timestamp - there is no instant where both are live.

## Fees

If the environment has fees configured, they are included in the on-chain leg registration as `FeeSpec` entries. The contract collects fees at execution and transfers them to the configured recipient. See [KeystoneSettlement](/smart-contracts/keystone-settlement) for details on how fees work at the contract level.

## Token standards

| Standard | Support | Release mechanism                          |
| -------- | ------- | ------------------------------------------ |
| ERC-20   | Full    | Standard `transfer()`                      |
| ERC-3643 | Full    | `forcedTransfer()` via transfer agent role |

See [KeystoneSettlement](/smart-contracts/keystone-settlement) for full contract documentation.
