Chio/Docs
LOGIN · JOIN

Settlement Rails

Settle a priced call's charge after dispatch and reconcile the payment with the signed receipt that recorded it.

Guide chio@5f63e6e

On this page

Settle what an agent spent: read the payment a priced call recorded on its receipt, plug a payment rail in behind the kernel, and release an on-chain escrow against the receipt itself.

The kernel holds the worst-case cost before dispatch; settlement happens after the call, against records the kernel signs. The steps start from the three 15-unit discount calls of Tool Pricing.

Start

1. Read the payment on a priced call's receipt

Enter the run directory the discount service printed. Verify the first call's receipt against the public key the host selected before it requested any call.

Before you begin

Fetch the project with chio new pricing-metered --example pricing-metered, or download it, and run it as Budgets describes, with the price set to 15 units as in Tool Pricing. The host creates its stores and keys. Amounts on this page are in minor currency units.

Terminalbash
cargo run --release --locked --bin verify-receipt -- \
  call-1-receipt.json kernel-public-key.txt
Output
{
  "authorized": true,
  "crypto_error": null,
  "crypto_floor": "allow_classical",
  "decision": {
    "verdict": "allow"
  },
  "ok": true,
  "parameter_hash_valid": true,
  "receipt_id": "607a245ba9f85470eb7277a30754527deeb47e8233880f583d0f8c53b50169ed",
  "receipt_id_valid": true,
  "signature_valid": true,
  "signer_trusted": true
}

You should see signer_trusted: true and signature_valid: true. The receipt's financial block carries the payment; your identifiers differ from these.

call-1-receipt.json · settlement evidencejson
{
  "budget_remaining": 35,
  "budget_total": 50,
  "cost_breakdown": {
    "payment": {
      "authorization_id": "hold:d6ffa273e4882e6a0454ef6f25bf01582dd488a368ca09015c9ea989e6b2ec1e",
      "preauthorized_units": 15,
      "rail": "finding-operator-ledger",
      "rail_mode": "reversible_hold",
      "recorded_units": 15,
      "transaction_id": "capture:d6a78ca355ecb41e407513f49efe01fcea77bdefd253cfd0b511c16364584340"
    }
  },
  "cost_charged": 15,
  "currency": "USD",
  "delegation_depth": 0,
  "grant_index": 0,
  "payment_reference": "capture:d6a78ca355ecb41e407513f49efe01fcea77bdefd253cfd0b511c16364584340",
  "root_budget_holder": "4806f7a942076d27d3652d6d74b861455fce7eaeb7e235957ea26d8e87cb075d",
  "settlement_status": "settled"
}
FieldValueWhat it records
cost_charged15A 15-unit charge in USD
preauthorized_units, recorded_units15, 15The same fixed amount, authorized and captured
authorization_idhold:dd73…5387The hold, inside cost_breakdown.payment
transaction_id, payment_referencecapture:7b42…1266The completed capture on this durable path
rail, rail_modefinding-operator-ledger, reversible_holdPersistent local-credit accounting
settlement_statussettledThe local adapter reported a completed capture

The host checked each capture against the run's persisted payment records as well as its receipts, and each payment was captured once. The application holds a monetary capability with no governed intent, so this durable local-credit flow needs no separate governed settlement intent. Here settled means the local ledger captured the credit; a bank or chain transfer is a rail outcome, recorded the same way in step 6.

2. Plug a payment rail in behind the kernel

Payment rails plug in as optional modules behind one PaymentAdapter port. The kernel records the hold against the grant's budget and caps before dispatch; the adapter then authorizes on the rail, and captures or releases after the call.

Rails outside the kernelChoose a moment.

Holds and caps prevent spend before dispatch

The kernel checks the grant and records the hold against its budget and caps, then asks the configured adapter to authorize up to the quoted amount. A payment on the rail never authorizes a call by itself: the grant and the hold do. PaymentAdapter lifecycle.

Payment rails as optional modules attached to the kernel through the PaymentAdapter lifecycle. Holds and caps prevent spend before dispatch; what the rail does is observed and recorded. The modules are off in default builds; custody and the facilitator service are left out.Settlement rails

Holds and caps stop the spend before dispatch, and a payment on a rail never authorizes a call by itself. The adapter authorizes up to the quoted amount, captures the observed cost, releases a zero-cost hold, and reverses later through refund. The host picks the adapter (sim, http-x402, http-acp) at configuration. The rail's RailSettlementStatus folds into the receipt's SettlementStatus (NotApplicable, Pending, Settled, Failed) beside the payment_reference, observed and recorded.

crates/kernel/chio-kernel/src/payment.rs21-84rust
pub trait PaymentAdapter: Send + Sync {
    fn rail_id(&self) -> &'static str {
        "unspecified"
    }

    fn rail_mode(&self) -> Option<PaymentRailMode> {
        None
    }

    /// Authorize or prepay up to `amount_units` before the tool executes.
    ///
    /// Implementations must be idempotent by `request.reference`: repeating the
    /// same request returns the same authorization and creates at most one
    /// rail-side hold or prepayment.
    fn authorize(
        &self,
        request: &PaymentAuthorizeRequest,
    ) -> Result<PaymentAuthorization, PaymentError>;

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

    /// Release an unused authorization hold.
    ///
    /// Implementations must be idempotent by `(authorization_id, reference)`.
    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>;

    /// Return the side-effect-free rail state for a durable reference.
    ///
    /// This query must remain answerable when `authorization_id` is absent so
    /// recovery can close the crash window after authorization but before the
    /// rail-assigned identifier reaches the local journal.
    fn settlement_state(
        &self,
        reference: &str,
        authorization_id: Option<&str>,
    ) -> Result<RailSettlementState, PaymentError> {
        let _ = (reference, authorization_id);
        Err(PaymentError::Unavailable(
            "settlement_state query is unsupported by this payment adapter".to_owned(),
        ))
    }
}

Keep a stable operation reference across retries. authorize is idempotent by request.reference; capture and release are idempotent by the pair (authorization_id, reference). settlement_state answers without an authorization_id, so recovery can ask the rail what happened before the rail identifier reached the local journal.

src/host.rsrust
// 1. Authorize the worst case before the tool runs.
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. Run the tool invocation.
let result = invoke_tool(&request)?;

// 3. Capture the observed cost, or release the whole hold when 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)?;
}

The downloadable host wraps these three calls in its durable payment journal. A governed intent that carries MustPrepay with no adapter configured is refused before the tool runs, with a signed deny receipt. Configure a compatible adapter to clear it, and change the settlement mode only when the application's payment contract changes.

Reproduce a prepaid call and its refusal with the simulator

A prepaid call, then the same call with no adapter

chio mcp governed-sim drives one governed MustPrepay call through an ephemeral kernel and writes the signed receipt bundle to --out. The simulator records 100 authorized and 100 charged; in this capture payment_reference carries the simulator's authorization ID.

budget-receipts · allowtranscript
$ chio --session-db ./s.sqlite mcp governed-sim \
    --governed-mustprepay --payment-adapter sim --out ./bundle.json
$ jq '.metadata.financial' ./bundle.json
{
  "budget_remaining": 900,
  "budget_total": 1000,
  "cost_breakdown": {
    "payment": {
      "adapter_metadata": {
        "adapter": "sim",
        "commerce": null,
        "governed": {
          "approvalTokenId": "governed-sim-approval-1",
          "intentHash": "f57e0f00b78fe5267f81f92696a0fe9a32339cb02d57b81c9e15f59996f3062d",
          "intentId": "governed-sim-intent-1",
          "purpose": "no-key CI smoke",
          "serverId": "governed-sim-srv",
          "toolName": "compute"
        },
        "mode": "prepaid_no_broadcast"
      },
      "authorization_id": "sim-268afdc9129342029708eb795c87da08",
      "preauthorized_units": 100,
      "recorded_units": 100
    }
  },
  "cost_charged": 100,
  "currency": "USD",
  "delegation_depth": 0,
  "grant_index": 0,
  "payment_reference": "sim-268afdc9129342029708eb795c87da08",
  "root_budget_holder": "0473a15a01ca05a11b0558cd9fd1edd8bddd26f3f598f1c168aaec97dd7f031b",
  "settlement_status": "settled"
}
exit 0

Re-run with --payment-adapter none. The intent is unpayable, so the kernel refuses before the tool runs and the simulator exits 1 with the CLI's error code, context, and suggested fix.

budget-receipts · denytranscript
$ chio --session-db ./s2.sqlite mcp governed-sim \
    --governed-mustprepay --payment-adapter none --out ./bundle-deny.json
exit 1

The bundle holds the signed deny: attempted_cost 100, cost_charged 0, the budget at its 1000 total, and the kernel as guard (the allow left 900). Both captures start from fresh allowances; Budgets covers a deny after prior spending.

budget-receipts · deny-receipttranscript
$ jq '{decision, financial: .metadata.financial}' ./bundle-deny.json
{
  "decision": {
    "verdict": "deny",
    "reason": "governed transaction denied: governed intent mandates prepayment (settlement_mode=MustPrepay) but no payment adapter is configured",
    "guard": "kernel"
  },
  "financial": {
    "attempted_cost": 100,
    "budget_remaining": 1000,
    "budget_total": 1000,
    "cost_charged": 0,
    "currency": "USD",
    "delegation_depth": 0,
    "grant_index": 0,
    "root_budget_holder": "95dd6964b2c519849a0f53ded5c810fc87791bb622ace9b887537de4c51a2c59",
    "settlement_status": "not_applicable"
  }
}
exit 0deny

3. Lock the payment in an EVM escrow

When two parties settle on chain, the depositor funds a ChioEscrow before the call. Opening it fixes the beneficiary, the token, the maximum amount, and the deadline; the receipt later authorizes the release.

Escrow releaseChoose a path.

Opening fixes both branches

Opening the escrow fixes the beneficiary, the token, the maximum amount, and the deadline. The depositor funds its own escrow, and those terms decide where a release and a refund can go. Predeclared settlement.

The EVM escrow release path: the terms fixed at opening, the two evidence paths a release accepts, and the refund after the deadline. Chain execution and finality are observed; signed receipts and the pinned verifier policy stay the authority.

The operator's signature or an inclusion proof against ChioRootRegistry (step 4) authorizes the release. Either way the escrow checks the signer or proof, the beneficiary, the receipt, and an amount capped at the deposit before it transfers tokens; after the deadline the remainder refunds to the depositor.

Add the settlement crates, then prepare the dispatch with its release path. EscrowTerms on chain carries no path field and the contract exposes both entry points, so chio-settle records the path on the dispatch and refuses a release that differs from it.

Terminalbash
cargo add --git https://github.com/backbay-labs/chio \
  chio-settle chio-web3 chio-link
src/settle.rs · prepare and lockrust
use chio_settle::evm::{
    prepare_web3_escrow_dispatch, submit_call, confirm_transaction,
    finalize_escrow_dispatch, EscrowDispatchRequest,
};
use chio_web3::trust_profile::Web3SettlementPath;

// config (SettlementChainConfig), the signed capital instruction, and the
// operator's SignedWeb3IdentityBinding come from your settlement host.
let dispatch_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 the
//    identity binding and derives the escrow id with a static contract read.
let prepared =
    prepare_web3_escrow_dispatch(&config, &dispatch_request, &operator_binding).await?;
// State: PendingDispatch, then EscrowLocked once the create transaction confirms.

// 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. Read the on-chain escrow id from the emitted event.
let dispatch = finalize_escrow_dispatch(&prepared, &tx_receipt)?.dispatch;

Your host supplies the SettlementChainConfig, the signed capital instruction, the operator identity binding, the contract package, and a funded depositor. Once the kernel signs the call's ChioReceipt, submit the signed release from the beneficiary account.

src/settle.rs · release with the operator signaturerust
use chio_settle::evm::{
    prepare_dual_sign_release, submit_call, confirm_transaction, DualSignReleaseInput,
};

// The governed tool call returns its signed ChioReceipt.
let receipt = invoke_tool(&tool_request).await?;

// 4. Prepare the dual-signature release for the observed amount.
// DualSignReleaseInput keeps its key field private and zeroizing,
// so it is built through its constructor.
let release = prepare_dual_sign_release(
    &config,
    &dispatch,
    &receipt,
    &DualSignReleaseInput::new(operator_settlement_key_hex.clone(), 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, then Settled.

You should see the lifecycle record move from PendingDispatch to EscrowLocked, then to Settled after the release, which must carry the permitted full-release amount from the beneficiary account. A cognition-market purchase between two organizations settles this way: its CrossOrgEscrow selector pins the settlement profile, and a funded escrow with a proof-gated release replaces the local hold and capture.

4. Anchor the checkpoint and release against its proof

chio-anchor publishes the receipt checkpoint's Merkle root to ChioRootRegistry. Once the receipt's checkpoint is published, the beneficiary releases with an inclusion proof and needs no new operator signature.

src/settle.rs · release against a Merkle proofrust
use chio_settle::evm::{
    prepare_merkle_release, submit_call, confirm_transaction, EscrowExecutionAmount,
};
use chio_web3::anchors::AnchorInclusionProof;

// 1. Build the anchor inclusion proof once the receipt's checkpoint root is
//    published to ChioRootRegistry by 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, then Settled.

This path needs a dispatch created with Web3SettlementPath::MerkleProof, a published root, and the operator's current identity binding. A Merkle release against a dispatch declared as DualSignature, or the reverse, is refused by chio-settle before anything reaches the chain; inspect the dispatch's declared path before you prepare a release. One proof bundle links the receipt to its chain anchor:

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

Signatures and inclusion check offline with their trusted inputs; whether an anchor is canonical and confirmed under your policy takes trusted chain evidence beyond a transaction hash.

5. Wait for finality before you book the transfer

An accepted release and a final settlement are separate observations. The release function checks beneficiary, active identity, signature or proof, amount, receipt reuse, pause state, and escrow liveness, and can transfer tokens before the observer classifies finality.

Terminalbash
chio settle status

chio settle status reads the local lifecycle records; prepare, submit, and confirm run through the chio-settle crate. The observer's inspect_finality_for_receipt classifies each release as AwaitingConfirmations, AwaitingDisputeWindow, Finalized, or Reorged, from the configured confirmation count and a dispute window tiered by amount. Its Finalized is that policy assessment, separate from an independent consensus-finality proof.

A reorganization surfaces as SettlementFinalityStatus::Reorged with the recommendation SettlementRecoveryAction::ResubmitAfterReorg. The settlement watchdog runs a SettlementWatchdogJob on its cron or log trigger; a job that carries operator_override_required stops at ManualOverrideRequired until an operator acts.

6. Reconcile the receipt with the rail's record

A settlement is verified when the signed receipt, the rail's authoritative record, and your finality policy agree. Read three fields first:

  • metadata.financial.settlement_status: settled on a completed prepaid call, not_applicable on the refusal in step 2.
  • metadata.financial.payment_reference: in the durable application, the capture transaction ID, while cost_breakdown.payment.authorization_id separately names the hold.
  • cost_breakdown.payment.preauthorized_units against recorded_units: the authorized and recorded amounts. A gap alone does not prove a release; inspect the terminal action and the rail's evidence.

The included collector verifies the same receipts and exports one accounting row per allowed charge.

Terminal · in the run directorybash
cargo run --release --locked --bin export-billing -- . kernel-public-key.txt
Output
verified records: 3; non-allow records excluded from billing: 0
billing records: 3
total: 45 USD minor units

You should see three verified records totaling 45 USD minor units, each traceable to its signed receipt. For an external rail, match it against the rail's transaction record first. Export Billing Records carries the rows into an accounting system.

An interrupted payment keeps its hold until both sides resolve it

Delivery and money are separate facts, and each move changes only one of them. A crash between authorize and capture leaves the call's delivery known or unknown, and the money held.

Delivery and moneyChoose an outcome.

An unknown outcome holds its money and is never re-dispatched

The reply is lost, so delivery is unknown and the money stays held until the outcome is resolved. The kernel refuses to capture against an unknown outcome and never dispatches the call again. Unknown outcomes.

Settlement of one call as two independent axes, delivery across and money down; every move changes one axis. The two records named on the drawing are the only moves out of their states. Partial captures and refunds after settlement are left out.Authoritative spend

A replayed authorization returns the same hold, because authorize is keyed on request.reference. A lost reply keeps its hold and is never dispatched again. After an unknown outcome, only a co-signed MutuallyAgreedUnknown releases the money; for a known return with a pending capture, only a ContractualCaptureWaiver stops the capture.

A hold blocks finalization, receipts, and delivery, but never a co-signed monetary successor; every finalizer reads the effective payment journal first, and a rotated signer signs with its current key bound to the original identity. Resolve the open operation before you submit new work against it.

Settlement types, contracts, and policies

Settlement rail categories

A rail names the mechanism that settles a capital instruction. CapitalExecutionRailKind has seven categories; the host connects the executor for the category it uses and reconciles that executor's result.

RailSettles throughMoves external funds
Manual
A transfer the operator performs and evidences
Yes
Api
An API-backed executor with its own credentials
Yes
Ach
An ACH bank transfer
Yes
Wire
A wire transfer
Yes
Ledger
Entries between accounts of one operator
No
Sandbox
A development simulation
No
Web3
On-chain settlement through chio-settle
Yes, for EVM escrow

Timing and finality for Ach and Wire come from the configured rail and its evidence. Under Web3, EVM escrow, Solana memo evidence, and cross-chain preparation have different scopes: a memo or a prepared artifact is evidence, and only the escrow release moves tokens.

Adapters and payment results

The host selects an adapter at configuration: sim by default, http-x402 for an x402-style prepaid HTTP bridge, or http-acp for an ACP-style facilitator bridge, each talking to a compatible external service. The persistent local-credit ledger is a fourth, and SimPaymentAdapter is a deterministic verifier aid. Payment-rail and web3 modules are off in default builds of the kernel and CLI.

Authorization returns an identifier and a state. PaymentAuthorizationState separates a reversible Held authorization from PrepaidFinal; authorization.state.is_final() picks the terminal action. For a reversible-hold adapter, authorize max_cost_per_invocation before the call and capture the observed cost after it; release the hold when the cost is zero, as for a cached result.

crates/kernel/chio-kernel/src/payment/types.rs8-15rust
pub struct PaymentAuthorization {
    /// Payment rail's authorization or hold identifier.
    pub authorization_id: String,
    /// Whether authorization created a reversible hold or completed final prepayment.
    pub state: PaymentAuthorizationState,
    /// Rail-specific metadata such as idempotency keys, quote IDs, or expiry.
    pub metadata: serde_json::Value,
}

The terminal operations capture, release, and refund all return one PaymentResult:

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

Settlement contracts

ContractHoldsRead by
ChioEscrow
Funds escrowed for a call, ERC-20 with permit
The beneficiary's release
ChioBondVault
Collateral bonds
Credit facilities, autonomous execution
ChioRootRegistry
Merkle roots the kernel publishes
Proof releases, anchor verification
ChioIdentityRegistry
Operator identity and settlement keys
Escrow and root verification
ChioPriceResolver
Oracle price reads on L2
Cross-currency settlement, FX checks

ERC-20 deposits use the EIP-2612 permit pattern, so a compatible token and a valid permit combine approval with the deposit. Token allowlisting, signatures, balances, and the contract's checks still apply. The five-argument proof entry point reverts; proof releases use the detailed entry point that carries the required metadata.

Release paths

  • DualSignature. The operator signs the release digest and the beneficiary submits the transaction. The contract checks the active operator key and epoch, the bound receipt hash, the amount, and the beneficiary.
  • MerkleProof. The provider submits a Merkle proof that the invocation receipt is in a published checkpoint, against a root in ChioRootRegistry. No new operator signature is needed; the published root and the current identity binding are.

Escrow lifecycle states

The lifecycle type reports observed settlement states; each rail implements and checks its own transitions. Web3SettlementLifecycleState lives in chio-web3.

StateFlowMeaning
PendingDispatch
Success
Dispatch prepared or submitted, before funds lock
EscrowLocked
Success
Funds locked in ChioEscrow; the call can proceed
PartiallySettled
Success
Some funds released to the provider, the rest held
Settled
Success
All escrowed funds distributed
Reversed
Exceptional
Funds returned to the caller before settlement
ChargedBack
Exceptional
A dispute resolved for the caller after settlement
TimedOut
Exceptional
A timeout or refund outcome the integration reports
Failed
Exceptional
The settlement transaction failed
Reorged
Exceptional
A chain reorganization invalidated the settlement

Expiry alone executes no refund: TimedOut is the outcome the integration reports after it acts.

crates/economy/chio-web3/src/settlement.rs39-49rust
pub enum Web3SettlementLifecycleState {
    PendingDispatch,
    EscrowLocked,
    PartiallySettled,
    Settled,
    Reversed,
    ChargedBack,
    TimedOut,
    Failed,
    Reorged,
}

On-chain payment methods

Beside escrow and bond settlement, the payments module of chio-settle prepares and checks established on-chain payment methods, so a settlement can use one in place of the escrow contracts:

  • x402 payment requirements: build_x402_payment_requirements, X402SettlementMode.
  • EIP-3009 transferWithAuthorization digests: prepare_transfer_with_authorization, Eip3009Domain, and an Eip3009NonceStore the prepare function takes as an argument.
  • Circle nanopayments: evaluate_circle_nanopayment, CircleNanopaymentPolicy.
  • ERC-4337 paymaster-sponsorship checks: prepare_paymaster_compatibility, Erc4337PaymasterPolicy.

Anchoring lanes

Checkpoint anchoring uses three independent methods, AnchorLaneKind::{EvmPrimary, BitcoinOts, SolanaMemo}, verified together as an AnchorProofBundle:

  • EVM primary. The Merkle root published to ChioRootRegistry on an EVM chain, Ethereum L1 or an L2.
  • Bitcoin OpenTimestamps. An OpenTimestamps proof anchored to Bitcoin, for calendar-independent timestamping.
  • Solana memo. The checkpoint hash written as a Solana memo transaction, for fast, low-cost redundancy.

chio-anchor also owns 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, gives a fallback verification path over a receipt batch.

Finality modes

Web3ChainFinalityRule associates a chain ID, a mode, and minimum confirmations in the trust profile. Configure the confirmations and waiting period per deployment; the mode name implies no elapsed time.

ModeChainConfigure
L1Finalized
Ethereum mainnet
The chain evidence L1 finality requires
OptimisticL2
Optimism, Arbitrum, Base
Confirmation and dispute policy
SolanaConfirmed
Solana
The commitment policy for confirmation

Dispute windows

The observer picks a dispute window from the settlement amount. The default SettlementPolicyConfig tiers dispute_window_secs across four bands of minor units, the same for DualSignature and MerkleProof releases:

Settlement amount (minor units)dispute_window_secsDuration
up to 1,000
0
Immediate
up to 100,000
3,600
1 hour
up to 1,000,000
14,400
4 hours
above 1,000,000
86,400
24 hours

The EVM observer queries the latest block and the receipt block's hash, then applies the confirmation count and the elapsed window; the classification never blocks 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 on every path, DualSignature included.

Currency conversion evidence

When a tool prices in one currency and settles in another (USD costs, USDC or ETH settlement), chio-link resolves the exchange rate. ChioLinkOracle implements PriceOracle over a pluggable OracleBackend that reads Chainlink (AggregatorV3Interface.latestRoundData) and Pyth (Hermes). The on-chain ChioPriceResolver is a separate contract-level price read on L2; receipt-side oracle evidence comes from chio-link.

  • Staleness. A price past the configured age threshold is rejected, and the resolver refuses the conversion.
  • L2 sequencer. On Optimism, Arbitrum, and Base the oracle reads 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 read is checked against a fallback with a basis-point circuit breaker that fails closed on divergence. Per-pair twap_enabled policy averages a rolling window into a TWAP.
  • Evidence. Each conversion produces an OracleConversionEvidence record stored on the receipt.

This helper takes a configured oracle and the converted amount the host calculated, resolves the pair, and validates the evidence's arithmetic. Feed configuration and the evidence signature follow the kernel's signing and trust policy.

src/conversion.rsrust
use chio_web3::anchors::{
    validate_oracle_conversion_evidence, OracleConversionEvidence,
};
use chio_link::PriceOracle;

pub async fn conversion_evidence(
    oracle: &dyn PriceOracle,
    original_units: u64,
    original_currency: &str,
    grant_currency: &str,
    converted_units: u64,
    now: u64,
) -> Result<OracleConversionEvidence, Box<dyn std::error::Error>> {
    let rate = oracle.get_rate(original_currency, grant_currency).await?;
    let evidence = rate.to_conversion_evidence(
        original_units, original_currency, grant_currency, converted_units, now,
    )?;
    validate_oracle_conversion_evidence(&evidence)?;
    Ok(evidence)
}

The settlement CLI command is chio settle status; there is no per-chain chio settle subcommand.

Next: export the settled charges