Skip to main content
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.
The published SDKs are pre-release (0.1.x) and currently lag the live API. The deposit itself is a direct on-chain contract call (shown below via ethers.js / web3.py), not a KeyStone REST call - use those on-chain snippets, but for KeyStone API calls (fetching the settlement, its legs, and the per-leg deposit calldata) prefer the REST examples in the API reference until the SDKs catch up.

How it works

1. Settlement reaches AWAITING_DEPOSITS

After compliance clears, the settlement advances to AWAITING_DEPOSITS. At this point, each leg with direction: "deliver" has its deposit transaction available from POST /v1/settlements/{id}/legs/{leg_index}/deposit-calldata. That endpoint is the only place the API serves the deposit secret, one leg per call, embedded in pre-encoded calldata - settlement reads never include it.

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 fetches the pre-encoded transaction from the deposit-calldata endpoint and uses its custody provider (such as MPC wallets or any signing solution) to sign and broadcast it. 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
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.

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:

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} for its legs and their statuses - settlement reads never include deposit material
  3. For each leg where your party has direction: "deliver", call POST /v1/settlements/{id}/legs/{leg_index}/deposit-calldata - the response carries the escrow address, pre-encoded depositLeg calldata with the secret embedded, and matching ERC-20 approve calldata for exactly the leg amount (approval_amount equals deposit_amount)
  4. Your custody provider signs and broadcasts both transactions (approve, then deposit) from the wallet the deposit key was computed for
  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
The returned approval_calldata approves exactly the leg amount, with no fee component. With no settlement fee configured (fee_bps of zero, the default), that is exactly what the deposit pulls, and both returned transactions are submittable as-is. The same holds under the deductive seller_pays mode, where the fee comes out of the recipient’s proceeds. With a nonzero fee_bps in an additive fee_mode (buyer_pays, the default mode, or split), the deposit for a payment leg pulls the leg amount plus the additive fee, so the returned approval is too small and the deposit reverts on the token allowance. On such environments, build the approval yourself for the leg amount plus the additive fee - see Fees for how the fee is computed and who bears it.
Cross-chain mode: a settlement whose legs span more than one chain is registered with a non-zero abortDeadline and does not auto-execute on the last deposit. It flips to the contract’s Prepared phase, and the KeystoneRouter releases only once every participating chain has confirmed its own legs are funded and its own compliance gate is clear. Your deposit flow is identical either way.

Integration example

Monitoring deposit status

Check deposit progress by querying the settlement:
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 for details on how fees work at the contract level.

Token standards

See KeystoneSettlement for full contract documentation.