Chio/Docs

BuildEconomics

Settlement

Settlement transfers payment from the caller to the provider for a tool invocation, using payment adapters or on-chain escrow.


Settlement rails

A settlement rail is the mechanism used to move capital between counterparties. Each rail is represented by a CapitalExecutionRailKind variant in the chio kernel:

RailDescription
ManualHuman-initiated transfers outside the system (invoices, checks). Settlement is recorded manually.
ApiPayment processor integrations (Stripe, Adyen). Pre-authorization and capture via the PaymentAdapter trait.
AchACH bank transfers. Lower cost, higher latency. Suitable for batch settlement.
WireWire transfers for high-value settlements with same-day finality.
LedgerInternal ledger entries between accounts within the same operator. No external capital movement.
SandboxDevelopment rail that simulates settlement without moving funds.
Web3On-chain settlement via the chio-settle crate. Supports EVM (Base / Arbitrum) escrow, the bounded Solana Ed25519 memo path, and Chainlink CCIP cross-chain coordination.

The rail is selected per-invocation based on the operator's configuration, the counterparty's supported rails, and the settlement amount. Most operators start with Sandbox during development and graduate to Api or Web3 in production.


Payment Adapter Integration

For off-chain rails (Api, Ach, Wire), settlement flows through the PaymentAdapter trait. The trait is synchronous and provides a four-method lifecycle for payment processing:

rust
pub trait PaymentAdapter: Send + Sync {
    /// Authorize or prepay up to `amount_units` before the tool executes.
    fn authorize(
        &self,
        request: &PaymentAuthorizeRequest,
    ) -> Result<PaymentAuthorization, PaymentError>;

    /// Finalize payment for the actual cost after tool execution.
    fn capture(
        &self,
        authorization_id: &str,
        amount_units: u64,
        currency: &str,
        reference: &str,
    ) -> Result<PaymentResult, PaymentError>;

    /// Release an unused authorization hold.
    fn release(
        &self,
        authorization_id: &str,
        reference: &str,
    ) -> Result<PaymentResult, PaymentError>;

    /// Refund a previously executed payment.
    fn refund(
        &self,
        transaction_id: &str,
        amount_units: u64,
        currency: &str,
        reference: &str,
    ) -> Result<PaymentResult, PaymentError>;
}

The API uses request arguments instead of a Money type. The authorization object tracks the state of each payment flow, and capture, release, and refund all return one shared PaymentResult:

rust
pub struct PaymentAuthorization {
    pub authorization_id: String,
    pub settled: bool,
    pub metadata: serde_json::Value,
}

pub struct PaymentResult {
    pub transaction_id: String,
    pub settlement_status: RailSettlementStatus,
    pub metadata: serde_json::Value,
}

Pre-authorization flow

The standard pattern is: pre-authorize for max_cost_per_invocation before the tool call, then capture the actual cost after the call completes. If the actual cost is zero (for example, a cached result), release the authorization instead of capturing it.

Here is the authorize-capture-release cycle:

rust
// 1. Pre-authorize the maximum possible cost.
let auth = adapter.authorize(&PaymentAuthorizeRequest {
    amount_units: max_cost_units,
    currency: "USD".to_string(),
    payer: agent_id.clone(),
    payee: tool_server_id.clone(),
    reference: invocation_id.clone(),
    governed: None,
    commerce: None,
})?;

// 2. Execute the tool invocation.
let result = invoke_tool(&request)?;

// 3. Capture the actual cost, or release the whole hold if nothing was spent.
let actual_units = result.metered_cost_units();
if actual_units == 0 {
    adapter.release(&auth.authorization_id, &invocation_id)?;
} else {
    adapter.capture(&auth.authorization_id, actual_units, "USD", &invocation_id)?;
}

On-Chain Settlement (Web3 Rail)

The Web3 rail settles tool invocation costs on-chain through smart contracts. A Merkle proof lets a provider release escrow without the operator or an intermediary.

rendering…
Escrow lock, then settle via DualSignature (fast) or MerkleProof (trustless fallback).

Smart Contracts

The on-chain settlement system uses these contracts:

ContractRole
ChioEscrowHolds funds in escrow during tool invocations. Supports ERC-20 tokens with permit.
ChioBondVaultManages collateral bonds for credit facilities and autonomous agent execution.
ChioRootRegistryStores merkle roots published by the kernel for checkpoint anchoring.
ChioIdentityRegistryMaps did:chio identifiers to on-chain addresses for settlement routing.
ChioPriceResolverIntegrates oracle price feeds for cross-currency settlement and FX verification.

Settlement Paths

Once funds are locked in ChioEscrow, they can be released through two settlement paths:

  • DualSignature (two-party signed): the operator signs a release message authorizing the provider to withdraw. Settlement completes in one transaction after both parties sign the amount.
  • MerkleProof(inclusion-proof from the receipt log): the provider submits a merkle proof showing that the invocation receipt was included in a published checkpoint. The provider submits a valid proof against a root in ChioRootRegistry; the operator does not need to cooperate.

Choosing a settlement path

Use DualSignature when both parties can sign promptly. Use MerkleProof for a dispute or an unresponsive operator. Each escrow supports both paths, so the provider can submit a Merkle proof when necessary.

Escrow Lifecycle

The lifecycle flows PendingDispatch -> EscrowLocked -> PartiallySettled / Settled / Reversed / ChargedBack / TimedOut:

StateMeaning
PendingDispatchEscrow created but funds not yet locked on-chain
EscrowLockedFunds locked in the ChioEscrow contract, invocation can proceed
PartiallySettledSome funds released to the provider, remainder still held
SettledAll escrowed funds distributed
ReversedEscrow reversed, funds returned to the caller
ChargedBackDispute resolved in caller's favor after settlement
TimedOutEscrow expired without settlement, funds returned to caller

ERC-20 token support uses the EIP-2612 permit pattern, allowing callers to approve and deposit in a single transaction without a separate approval step.

Payment-Rail Compatibility

Beyond escrow and bond settlement, the chio-settle crate exposes support for established on-chain payment methods, so a settlement can use an existing method instead of the native escrow contracts. Each integration provides preparation and verification functions in the crate's payments module:

  • x402 payment requirements (build_x402_payment_requirements, X402SettlementMode)
  • EIP-3009 transferWithAuthorization digests (prepare_transfer_with_authorization, Eip3009Domain)
  • Circle nanopayments (evaluate_circle_nanopayment, CircleNanopaymentPolicy)
  • ERC-4337 paymaster-sponsorship checks (prepare_paymaster_compatibility, Erc4337PaymasterPolicy)

Setting Up an Escrow

Settlement uses Rust and SDK APIs

On-chain settlement is driven through the chio-settle crate's prepare / submit / confirm sequence. The economic layer exposes these as Rust and SDK APIs; there is no chio settle <chain> subcommand beyond chio settle status.

Create an escrow and settle it through DualSignature as follows. The SDK prepares each call, validates the capital instruction and identity binding, then submits and confirms it:

rust
use chio_settle::evm::{
    prepare_web3_escrow_dispatch, submit_call, confirm_transaction,
    finalize_escrow_dispatch, EscrowDispatchRequest,
};
use chio_core::web3::trust_profile::Web3SettlementPath;

// `config` (SettlementChainConfig), the signed capital instruction, and the
// operator's SignedWeb3IdentityBinding are assembled upstream.
let request = EscrowDispatchRequest {
    dispatch_id: dispatch_id.clone(),
    issued_at,
    trust_profile_id: profile_id.clone(),
    contract_package_id: contract_package_id.clone(),
    capability_id: capability_id.clone(),
    depositor_address: caller_address.clone(),
    beneficiary_address: provider_address.clone(),
    capital_instruction: instruction.clone(),
    settlement_path: Web3SettlementPath::DualSignature,
    oracle_evidence_required_for_fx: false,
    note: None,
};

// 1. Prepare the escrow-create call. This validates the instruction and
//    identity binding and derives the escrow id via a static contract read.
let prepared = prepare_web3_escrow_dispatch(&config, &request, &operator_binding).await?;
// State: PendingDispatch -> (submit) -> EscrowLocked

// 2. Submit and confirm the create transaction.
let tx_hash = submit_call(&config, &prepared.call).await?;
let tx_receipt = confirm_transaction(&config, &tx_hash).await?;

// 3. Finalize the on-chain escrow id from the emitted event.
let dispatch = finalize_escrow_dispatch(&prepared, &tx_receipt)?.dispatch;

After the tool invocation produces a signed ChioReceipt, the operator prepares the dual-signature release and submits it. The release is configured to release the entire escrowed amount:

rust
use chio_settle::evm::{
    prepare_dual_sign_release, submit_call, confirm_transaction, DualSignReleaseInput,
};

let receipt = invoke_tool(&request).await?;

// 4. Prepare the dual-signature release for the observed amount.
let release = prepare_dual_sign_release(
    &config,
    &dispatch,
    &receipt,
    &DualSignReleaseInput {
        operator_private_key_hex: operator_settlement_key_hex.clone(),
        observed_amount: settlement_amount,
    },
).await?;

// 5. Submit and confirm the release transaction.
let tx_hash = submit_call(&config, &release.call).await?;
confirm_transaction(&config, &tx_hash).await?;
// State: EscrowLocked -> Settled

Settling via Merkle Proof

If the operator is unresponsive, the provider settles with an anchor inclusion proof once the receipt has been included in a published checkpoint. The dispatch must have been created with Web3SettlementPath::MerkleProof:

rust
use chio_settle::evm::{
    prepare_merkle_release, submit_call, confirm_transaction, EscrowExecutionAmount,
};
use chio_core::web3::anchors::AnchorInclusionProof;

// 1. Build the anchor inclusion proof once the receipt's checkpoint root is
//    published to ChioRootRegistry (see chio-anchor).
let anchor_proof: AnchorInclusionProof = build_anchor_inclusion_proof(/* ... */)?;

// 2. Prepare the Merkle release, binding the anchor proof to the dispatch.
let release = prepare_merkle_release(
    &config,
    &dispatch,
    &anchor_proof,
    &anchor_content,               // SettlementAnchorContentBinding
    EscrowExecutionAmount::Full,
)?;

// 3. Submit and confirm the release transaction.
let tx_hash = submit_call(&config, &release.call).await?;
confirm_transaction(&config, &tx_hash).await?;
// State: EscrowLocked -> Settled

Checkpoint Anchoring

Checkpoints commit receipt history to on-chain registries. The chio-anchor crate publishes Merkle roots to ChioRootRegistry and verifies returned proofs against those roots.

Checkpoint anchoring can use three independent methods, modeled as AnchorLaneKind::{EvmPrimary, BitcoinOts, SolanaMemo} and verified together as an AnchorProofBundle:

  • EVM primary: merkle root published to ChioRootRegistry on an EVM chain (Ethereum L1 or L2)
  • Bitcoin OpenTimestamps: OpenTimestamps proof anchored to the Bitcoin blockchain for calendar-independent timestamping
  • Solana memo: checkpoint hash written as a Solana memo transaction for fast, low-cost redundancy

chio-anchor also owns a second, independent mechanism: chio.anchor_batch.v1, which Merkle-batches checkpoint IDs and binds the batch to a public witness (Rekor or OpenTimestamps) through a WitnessPolicy. A bounded Chainlink Functions request (ChainlinkFunctionsTarget) provides a fallback-verification path over a receipt batch.

A proof bundle links an individual receipt to a chain anchor:

bash
Receipt (content hash)
  -> Merkle inclusion proof (siblings + index)
    -> Checkpoint statement (root + timestamp + lane)
      -> Chain anchor (tx hash on EVM / BTC / Solana)

Verification is offline-capable

Once you have the proof bundle, verification requires only the chain anchor's transaction data and the merkle math. No kernel access, no API calls: just cryptographic verification against the published root.

Settlement Finality

Settlement finality depends on the chain where the escrow or checkpoint is anchored. Each chain has its own finality semantics:

Finality LevelChainMeaning
L1FinalizedEthereum mainnetTransaction included in a finalized epoch (~12 minutes)
OptimisticL2Optimism, Arbitrum, BaseSoft-confirmed on L2, subject to challenge window for full finality
SolanaConfirmedSolanaConfirmed by supermajority of validators (~400ms)

A dispute window is not an on-chain release gate. Both release paths in ChioEscrow execute immediately: releaseWithSignature settles the moment a valid dual signature lands, and releaseWithProof settles the moment a valid inclusion proof lands. Neither path carries a timelock. Dispute windows live one layer up, in two places, and neither is keyed on the settlement path.

The chio-settle observer selects a finality dispute window from the settlement amount. The default SettlementPolicyConfig tiers a dispute_window_secs across four bands of minor units:

Settlement amount (minor units)dispute_window_secs
up to 1,0000 (immediate)
up to 100,0003,600 (1 hour)
up to 1,000,00014,400 (4 hours)
above 1,000,00086,400 (24 hours)

inspect_finality_for_receipt applies this tiering identically whether the release used DualSignature or MerkleProof. It classifies a SettlementFinalityStatus (AwaitingConfirmations, AwaitingDisputeWindow, Finalized, Reorged) after the release; it does not block the release call.

Separately, a Web3TrustProfile declares a per-path dispute_windows vector. Each entry pairs a settlement_path with challenge_window_secs, recovery_window_secs, and a dispute_policy (OffChainArbitration, TimeoutRefund, or BondSlash). validate_web3_trust_profile rejects a zero challenge or recovery window for any path, including DualSignature, so a compliant profile cannot declare a zero-duration window for the fast path.

Web3SettlementLifecycleState defines states for successful and exceptional flows. The type is in chio-web3, re-exported via chio_core::web3:

rust
pub enum Web3SettlementLifecycleState {
    PendingDispatch,     // Created, awaiting on-chain funding
    EscrowLocked,        // Funded, invocation in progress
    PartiallySettled,    // Partial release completed
    Settled,             // Fully settled
    Reversed,            // Funds returned before settlement
    ChargedBack,         // Dispute resolved post-settlement
    TimedOut,            // Escrow expired without action
    Failed,              // Settlement transaction failed
    Reorged,             // Chain reorganization invalidated settlement
}

Reorg handling

The Reorged state is rare but important. Reorg handling is bounded, reviewable automation, not unconditional silent resubmission. The chio-settle observer classifies a reorg as SettlementFinalityStatus::Reorged and recommends SettlementRecoveryAction::ResubmitAfterReorg. Execution runs through a cron/log-triggered SettlementWatchdogJob that carries an operator_override_required flag; automation outcomes explicitly include ManualOverrideRequired.

Oracle Price Verification

When tool costs are denominated in one currency but settlement occurs in another (for example, costs in USD, settlement in USDC or ETH), the chio-link crate resolves verified exchange rates. Its ChioLinkOracle implements the PriceOracle trait and reads prices from a pluggable OracleBackend that supports both Chainlink (AggregatorV3Interface.latestRoundData) and Pyth (Hermes). The on-chain ChioPriceResolver contract is a separate, contract-level price read used on L2; the receipt-side oracle evidence is produced by chio-link.

The oracle applies several safety mechanisms:

  • Staleness checks: prices older than a configured threshold are rejected; the resolver denies conversion when the feed is stale.
  • L2 sequencer protection: on L2 chains (Optimism, Arbitrum, Base), it checks the Chainlink sequencer uptime feed and fails closed while the sequencer is down or inside its post-recovery grace period.
  • Cross-source divergence: the primary backend read is cross-checked against a fallback with a basis-point circuit breaker, failing closed on divergence. Per-pair twap_enabled policy averages a rolling observation window into a TWAP.
  • Oracle evidence: cross-currency conversions produce an OracleConversionEvidence record that is stored on the receipt for auditability.
rust
// ChioLinkOracle implements PriceOracle: it resolves the rate from Chainlink
// and/or Pyth behind a divergence circuit breaker and an L2 sequencer check.
let rate = oracle.get_rate(&pair)?;

// The resolved rate converts into OracleConversionEvidence for the receipt.
let evidence = rate.to_conversion_evidence(/* conversion context */);
// evidence fields: schema, base, quote, authority, rate_numerator,
// rate_denominator, source, feed_address, updated_at, max_age_seconds,
// cache_age_seconds, converted_cost_units, original_cost_units,
// original_currency, grant_currency, oracle_public_key, signature.

Summary

ConceptDescription
Settlement RailsSeven rails (Manual, Api, Ach, Wire, Ledger, Sandbox, Web3) as CapitalExecutionRailKind variants
PaymentAdapterauthorize, capture, release, refund lifecycle for off-chain rails
On-Chain EscrowChioEscrow contract with DualSignature (fast) and MerkleProof (trustless) settlement paths
Checkpoint Anchoringchio-anchor multi-lane proofs (EVM primary + Bitcoin OTS + Solana memo) plus the chio.anchor_batch.v1 witness mechanism
FinalityL1Finalized, OptimisticL2, SolanaConfirmed with chain-specific rules
Price Oraclechio-link (ChioLinkOracle) with Chainlink + Pyth backends, divergence circuit breaker, staleness and L2 sequencer checks

Next Steps