BuildWeb3
Settlement
Review supported settlement networks, release paths, and receipts that link authorized capabilities to on-chain payments.
Looking for a walkthrough?
Supported Chains
Settlement is handled by the chio-settle crate and its contract bindings. An operator pins one settlement chain config: contract addresses, operator address, settlement token, and a tiered confirmation and dispute policy. The ChioEscrow and ChioBondVault contracts are EVM-generic, so any EVM chain with them deployed can be the pinned target; Base is the shipped reference. Separate Solana support verifies Ed25519 receipts locally and prepares a canonical release payload; it does not broadcast on-chain.
| Chain | Assets | Finality | Status |
|---|---|---|---|
| Base (EVM, L2) | USDC · ETH | OptimisticL2 · L1Finalized | Shipped reference (pinned chain) |
| Other EVM chains | Per pinned config | OptimisticL2 · L1Finalized | Pinnable (one active config) |
| Solana | Ed25519 receipts | SolanaConfirmed | Local verification and instruction prep only |
Solana support verifies locally; it does not settle on mainnet
chio.settle.solana-release.v1 payload for commitment-parity checks. It does not provide the EVM escrow and bond path.Architecture
Every settlement starts as a chio.web3-settlement-dispatch.v2 artifact. The settle crate composes it from a signed capital execution instruction, the pinned chain config and a signed identity binding, derives the escrow id from the contract itself, and validates the result before anything is submitted (crates/economy/chio-settle/src/evm/prepare.rs:150-240). It then submits and watches for finality. Once finality is reached a chio.web3-settlement-execution-receipt.v2 is produced (chio-settle/src/evm/finalize.rs, observe.rs:213). Both artifacts travel inside a SignedExportEnvelope, whose signature is over the canonical JSON of the body (crates/core/chio-core-types/src/receipt/lineage.rs:410-431), so any client that canonicalizes the same way verifies the same bytes.
Authority boundary: receipts over chain state
Smart Contracts
On EVM, two contracts implement settlement. Both are deployed per-chain with stable addresses published in the chio-web3 trust profile. Alloy-generated Rust bindings live in the chio-web3-bindings crate.
IChioEscrow· opens a fixed-amount, time-bounded escrow that either a dual-signature release or a merkle-proof release can unlock. An escrow id is derived from its terms, and opening one that already exists reverts withEscrowAlreadyExists(contracts/src/ChioEscrow.sol:309), so a settled escrow is never reopened. Releases against a live escrow can be partial and accumulate: each one adds toreleasedand emitsEscrowPartialReleaseuntil the deposit is drawn down (ChioEscrow.sol:452-467).IChioBondVault· holds operator bonds that back dispute outcomes. On a slash verdict the bond is transferred to the disputing party minus a protocol fee; on a timeout refund the bond returns to the operator.
Solana support is not a live settlement program. It verifies Chio Ed25519 receipts and key bindings locally, prepares the canonical instruction set, and emits a chio.settle.solana-release.v1 payload for commitment-parity checks. There is no broadcast to Solana mainnet and no on-chain program verification in the shipped API.
Release Paths
An escrow unlocks in one of two ways. Both paths produce settlement receipts; the deployment selects one through policy decision, not a protocol one.
| Dual Signature | Merkle Proof | |
|---|---|---|
| Who signs | Counterparty + Chio operator | Anyone, once the receipt batch is anchored |
| Trust model | Mutual signature at release time | Trust the Merkle root; verify inclusion |
| Best for | Low-volume, high-value transfers | High-volume batched settlement |
| Cost | Two signatures per escrow | One anchor tx amortised across receipts |
Settlement Receipt
A settlement execution receipt is its own artifact, not a kernel receipt with different fields. Its Rust type is Web3SettlementExecutionReceiptArtifact in chio-web3, and it carries #[serde(deny_unknown_fields)], so a key the type does not declare is a parse error rather than an ignored extra. It embeds the whole dispatch it settles, which is what lets an auditor reconstruct the transaction from the receipt alone.
pub struct Web3SettlementExecutionReceiptArtifact {
pub schema: String,
pub execution_receipt_id: String,
pub issued_at: u64,
pub dispatch: Web3SettlementDispatchArtifact,
pub observed_execution: crate::credit::CapitalExecutionObservation,
pub lifecycle_state: Web3SettlementLifecycleState,
pub settlement_reference: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reconciled_anchor_proof: Option<AnchorInclusionProof>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub identity_registry_evidence: Option<Web3SettlementIdentityRegistryEvidence>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub identity_registry_evidence_binding: Option<Web3SettlementIdentityRegistryEvidenceBinding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub oracle_evidence: Option<OracleConversionEvidence>,
pub settled_amount: MonetaryAmount,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reversal_of: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub failure_reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
}Eight of those fields are required by spec/schemas/chio-web3/v2/settlement-execution-receipt.schema.json: schema, execution_receipt_id, issued_at, dispatch, observed_execution, lifecycle_state, settlement_reference, and settled_amount. The rest are optional and omitted when absent. lifecycle_state is one of nine values: pending_dispatch, escrow_locked, partially_settled, settled, reversed, charged_back, timed_out, failed, and reorged.
A settled one ships in the public-settlement proof room fixture. Its own fields first, with the embedded dispatch held back:
$ jq '.settlement_receipt | {schema, execution_receipt_id, issued_at, lifecycle_state,
$ settlement_reference, settled_amount, observed_execution}' \
fixtures/proof-room/public-settlement/valid-offline-finality/settlement-proof-bundle.json{
"schema": "chio.web3-settlement-execution-receipt.v2",
"execution_receipt_id": "receipt-web3-1",
"issued_at": 1743292860,
"lifecycle_state": "settled",
"settlement_reference": "settlement-web3-1",
"settled_amount": {
"currency": "USD",
"units": 150
},
"observed_execution": {
"amount": {
"currency": "USD",
"units": 150
},
"externalReferenceId": "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"observedAt": 1743292860
}
}The chain, the escrow, the settlement token, and the release path live on the embedded dispatch rather than on the receipt. This is the same object with capital_instruction (the signed authority chain) removed for length:
$ jq '.settlement_receipt.dispatch | del(.capital_instruction)' \
fixtures/proof-room/public-settlement/valid-offline-finality/settlement-proof-bundle.json{
"beneficiary_address": "0x2222222222222222222222222222222222222222",
"bond_vault_contract": "0x1000000000000000000000000000000000000003",
"chain_id": "eip155:8453",
"contract_package_id": "chio.official-web3-contracts",
"dispatch_id": "dispatch-web3-1",
"escrow_contract": "0x1000000000000000000000000000000000000002",
"escrow_id": "escrow-web3-1",
"issued_at": 1743292800,
"note": "Dispatches one governed escrow release over the official Base-first contract stack.",
"operator_key_hash": "0x0791868d8f29ea735f26a17a9aea038cd4255baac26eac5a74e58a07ed2f1975",
"schema": "chio.web3-settlement-dispatch.v2",
"settlement_amount": {
"currency": "USD",
"units": 150
},
"settlement_path": "merkle_proof",
"settlement_token_address": "0x735F1Ba389D9D350501dB8FBbB5b52477DcaddA8",
"support_boundary": {
"anchor_proof_required": true,
"custody_boundary_explicit": true,
"oracle_evidence_required_for_fx": true,
"real_dispatch_supported": true,
"reversal_supported": true
},
"trust_profile_id": "chio.official-web3-stack"
}The full set of keys on the fixture receipt, so nothing above reads as the whole object:
$ jq '.settlement_receipt | keys' \
fixtures/proof-room/public-settlement/valid-offline-finality/settlement-proof-bundle.json[
"dispatch",
"execution_receipt_id",
"issued_at",
"lifecycle_state",
"note",
"observed_execution",
"oracle_evidence",
"reconciled_anchor_proof",
"schema",
"settled_amount",
"settlement_reference"
]Not a receipt id
execution_receipt_id is a settlement identifier chosen by the settle runtime, not a Chio receipt id. Chio receipt ids are the 64-hex content address of the receipt body (crates/core/chio-core-types/src/receipt/body.rs:240-243); the two never interchange.The Solana lane produces no artifact of this shape. prepare_solana_settlement returns a PreparedSolanaSettlement (crates/economy/chio-settle/src/solana.rs:45-63), which carries the program and mint addresses, the Ed25519 program id, signature and Chio public key, and the hex-encoded chio.settle.solana-release.v1 instruction payload. The binding runs the other way from the EVM lane: the payload commits to the Chio receipt through receiptHash, the sha256 of the canonical receipt body (solana.rs:177-191), and commitment() derives a SettlementCommitment from it for parity checks. Nothing is broadcast, so there is no transaction hash, no block number, and no inclusion proof to record.
Reading Receipts for Reconciliation
The route below returns kernel receipts, the ChioReceipt objects carrying the cost metadata a reconciliation joins on. It does not return the web3 execution-receipt artifact above. Receipts are read over GET /v1/receipts/query with a bearer token, so reconciliation runs in whatever language the finance side already uses. ReceiptQueryClient ships in both SDKs with the same filter names, all optional: capabilityId, toolServer, toolName, outcome, since, until, minCost, maxCost, costCurrency, agentSubject, cursor, and limit. The response carries totalCount, receipts, and a nextCursor when more remain.
import { ReceiptQueryClient } from "@chio-protocol/sdk";
const receipts = new ReceiptQueryClient(
process.env.CHIO_CONTROL_URL!,
process.env.CHIO_AUTH_TOKEN!,
);
const page = await receipts.query({
capabilityId: "cap-expense-line-item-019",
costCurrency: "USDC",
limit: 10,
});
console.log(page.totalCount, page.receipts.length);import os
from chio import ReceiptQueryClient
receipts = ReceiptQueryClient(
os.environ["CHIO_CONTROL_URL"],
os.environ["CHIO_AUTH_TOKEN"],
)
page = receipts.query(
{
"capabilityId": "cap-expense-line-item-019",
"costCurrency": "USDC",
"limit": 10,
}
)
print(page["totalCount"], len(page["receipts"]))For a full reconciliation run, use paginate instead of query. It yields one page of receipts at a time and refuses to loop: a nextCursor that fails to advance raises rather than replaying the same page forever. The TypeScript form is an async generator; the Python form is an iterator.
Finality Modes
Settlement finality is a policy input, not a constant. An agent paying for a second of API time does not need L1-finalised settlement and should not wait 15 minutes for it. An agent releasing a bond after a dispute must wait. The chain-level finality mode comes from chio-web3's Web3FinalityMode, which has three variants:
| Mode | Meaning | Typical latency |
|---|---|---|
OptimisticL2 | Inclusion on the L2 sequencer | ~2 s (Base) |
L1Finalized | Included in an L1-finalised block | ~13 min (Ethereum) |
SolanaConfirmed | Confirmed by a supermajority of stake | ~1 s |
The mode above describes what a chain considers final. What settlement observation actually reports is separate and chain-agnostic: inspect_finality returns a SettlementFinalityStatus of AwaitingConfirmations, AwaitingDisputeWindow, Finalized, or Reorged. A Reorged observation drives the recovery path: ResubmitAfterReorg and the rest of the SettlementRecoveryAction set.
Dispute Policies
The dispatch envelope includes dispute policy. Each settlement declares the policy that will apply if either party raises a claim before the release window closes.
- Off-chain arbitration · A designated arbiter address can sign a resolution that either party can execute on-chain. Lowest cost, highest trust assumption.
- Timeout refund · If no counterparty signature arrives before the window closes, the escrow refunds to the depositor. No arbiter required.
- Bond slash · If the operator breaches the agreement, the disputing party can trigger a bond slash against
IChioBondVault. The protocol fee is deducted and the remainder transfers to the disputing party.
Bitcoin Anchoring
For the longest-lived audit guarantees, Chio publishes a super-root of its receipt Merkle tree to Bitcoin via OpenTimestamps and mirrors the same root to an EVM ChioRootRegistry. The chio-anchor crate is responsible for this process; it fails closed for each configured anchor, so a checkpoint is only considered anchored if EVM publication, Bitcoin attestation, and (where configured) a Solana memo record all succeed.
Why two anchors
Next Steps
- Settlement guide · walkthrough of payment adapters, escrow lifecycle, and oracle verification
- Chainlink Oracles · price feeds and FX conversion that back cross-currency settlement
- x402 Payments · per-request stablecoin payments at the HTTP layer
- Receipt format · canonical JSON-LD shape shared by every receipt