EconomyCost & Settlementnew
Clearinghouse
The clearinghouse nets a complete, reserved set of single-currency obligations into a smaller set of signed settlement instructions.
Use this page when
chio.clearing.* record schemas, and you want to see how a cycle of debts collapses into a smaller set of settlement intents. Don't use this if you want how one intent actually moves capital (see Settlement Rails) or how a dispatched settlement is closed out against observed cost (see Reconciliation & Watchdog).Multilateral Netting
Given a complete single-currency set of debts among many participants, the clearinghouse cancels offsetting flows, computes each participant's net balance, and emits the smaller set of settlement intents. A cycle in which A owes B owes C owes A collapses to its residual transfers; an acyclic chain A → B → C reduces to a single A → C transfer whenever the balances permit. The netting round emits a signed netting statement and the settlement intents it reduces to; the rail's reconciliation evidence follows later and separately.
The netting function lives in the pure chio_credit::clearing module; it adds no crate, because chio-credit already owns the canonical obligation contract it reads. The entry point is compute_netting_round, which accepts a round_id, an epoch, an algorithm_version, exactly one currency, a trusted participant-snapshot body and digest, a complete canonical atom manifest, and reservation proofs for every atom.
It produces settlement intents that a rail still has to execute. The clearinghouse does not hold funds or establish settlement finality.
The Input: Completeness-Proven Obligations
The clearinghouse never ingests exposure-ledger rows or IOU envelopes directly, because the two can describe the same receipt and reading both double-counts one debt. Its only input is the deduplicated canonical chio_credit::obligation::ObligationAtom: one immutable atom per receipt-backed debt, binding the authoritative debtor, the payee-bound creditor, the amount, and the currency. Identity is never inferred from IouEnvelope.issuer_key (a kernel signer, not a creditor) or from tool_server. The obligation and credit-note model those atoms come from is covered in Chio Paper.
Reserving an atom for a round flips its chio_credit::obligation::ObligationDisposition from per_call to clearing_reserved { round_id } transactionally. That flip is exclusive: per-call, assignment, and channel settlement paths all skip any clearing_reserved atom, so a single debt can never be settled through two paths at once. An atom already in assigned or channelized is ineligible, and duplicate obligation_id values, even with identical bytes, reject the whole manifest.
A round runs against exactly one currency. Every debtor and creditor identity is resolved through a signed, versioned participant snapshot that maps each identity or key alias to one canonical participant id and one settlement destination, and that must cover every atom and every output destination. There is no fallback mapping: unknown aliases, expired snapshots, conflicting mappings, and a proposer self-signing an otherwise untrusted mapping all reject the round.
Completeness is proven, not assumed. A bounded exposure report at its item limit is not proof that the epoch input is complete, so the source must supply a closed epoch range, start and end checkpoints, and a completeness proof. The input manifest commits to the sorted atom list and its count, and the round core binds that root and count so no atom can be added or dropped after signing. Any of the following rejects the round outright:
- a
has_morecondition or an unconsumed cursor; - a count mismatch against the committed manifest;
- a missing position in the epoch range;
- a capped or paginated report standing in for the full set.
The v1 Netting Algorithm
The algorithm is deterministic: all sorting compares canonical participant-id bytes, and shuffled inputs produce byte-identical outputs. All addition, subtraction, and signed-balance conversion is checked; overflow rejects the round, every emitted amount must convert exactly to MonetaryAmount.units: u64, and there is no saturating result or arithmetic_saturated success state.
- Verify. Check the participant snapshot, atom digests, source receipt signatures, completeness proof, reservation proofs, exact currency, unique ids, and canonical order.
- Aggregate. Sum directed amounts by ordered
(debtor, creditor)pair with checkedu128sums. - Bilateral cancellation. For each unordered pair, subtract the smaller direction from the larger and retain at most one residual direction.
- Balance. Compute each participant balance from the residual graph as credits received minus debts owed. Zero balances drop; negatives partition into debtors, positives into creditors.
- Conserve. Assert that the absolute total debtor balance equals the total creditor balance. A conservation failure denies.
- Match. Sort debtors and creditors canonically, match the first debtor to the first creditor for the minimum remaining amount, advance each exhausted side, and repeat until both lists are empty.
- Emit. Produce one immutable settlement intent per non-zero match, one participant statement per participant, and a deterministic transformation witness that lets a verifier replay how every input atom contributed to cancellation, balances, and intents.
This greedy match conserves balances and emits at most debtors + creditors - 1 intents. It is not claimed to minimize rail fees under every fee model; it is claimed to be deterministic and balance-conserving.
Gross Debts to Netted Intents
Round records
Every record uses RFC 8785 canonical JSON with deny_unknown_fields, a versioned schema id, and a signature over an immutable body. The round core is hashed before any output exists, and its round_core_digest is the only round digest that statements and intents bind, which keeps the digest graph acyclic:
| Schema | Binds | Role |
|---|---|---|
chio.clearing.netting-round-core.v1 | Round, epoch, governance scope id, currency, algorithm, participant-snapshot digest, input-manifest root and count, reservation-proof root, dispute window | The root every leaf points back to |
chio.clearing.participant-statement.v1 | Gross debit, gross credit, bilateral adjustment, final net balance, contributing atom digests, round_core_digest | One net position per participant |
chio.clearing.settlement-intent.v1 | intent_id, round_core_digest, debtor and creditor participant ids, authoritative destination, amount, currency, contributing reservation root, dispatch idempotency key | One residual transfer; carries no mutable reconciliation field |
chio.clearing.output-manifest.v1 | round_core_digest, statement / intent / transformation roots and counts | The complete, verifiable round output |
chio.clearing.participant-acceptance.v1 | Output-manifest digest, participant statement digest, participant id, acceptance signature | Each participant accepts the setoff |
chio.clearing.round-finalization.v1 | round_core_digest, output-manifest digest, acceptance root and count, external lifecycle head digest, version, and fence | Quorum-gated seal; excludes the FROST proof from its own digest |
A statement or intent alone is not a complete round proof: verification requires its inclusion in the signed chio.clearing.output-manifest.v1 and a valid finalization that includes every participant acceptance. Four more variants ride the same rules and are always emitted as separate records. The two reconciliation variants each stand apart from the intent they describe: chio.clearing.settlement-reconciliation.v1 (the rail outcome for one intent) and chio.clearing.zero-intent-reconciliation.v1 (outcome netted_without_rail for a finalized round that produced no intents). The remaining two, chio.clearing.round-abort.v1 and chio.clearing.round-dispute.v1, describe the round itself, not an intent.
The shapes below are an illustrative round output for the netting example above, not a signed wire schema; the field names track the record bodies:
{
"participant_statement": {
"schema": "chio.clearing.participant-statement.v1",
"round_core_digest": "b3:9f2c...",
"participant_id": "cp:acme",
"currency": "USD",
"gross_debit_units": 50,
"gross_credit_units": 30,
"bilateral_adjustment_units": 20,
"net_balance_units": -20,
"contributing_atom_digests": ["b3:1a4c...", "b3:77e1..."]
},
"settlement_intent": {
"schema": "chio.clearing.settlement-intent.v1",
"intent_id": "int:round-4471:0",
"round_core_digest": "b3:9f2c...",
"debtor_participant_id": "cp:acme",
"creditor_participant_id": "cp:globex",
"destination": "did:chio:c87a...",
"amount": { "units": 20, "currency": "USD" },
"contributing_reservation_root": "b3:5d90...",
"dispatch_idempotency_key": "round-4471:int-0"
}
}One Fenced Round
A round is a single fenced state machine. Its lifecycle contract is backend-neutral in chio_credit::clearing and implemented by chio-store-sqlite. Every transition is an authority-authenticated local stage followed by an external EconomicStateAnchor compare-and-swap over the round and its obligations; readiness requires the local head digest to equal the external head, and age is never reservation-release authority.
pub enum RoundLifecycleState {
Reserved,
Proposed,
Finalizing,
Finalized,
Dispatching,
Reconciling,
Satisfied,
Aborting,
Aborted,
Incident,
}
pub struct RoundLifecycleRecord {
pub round_id: String,
pub round_core_digest: String,
pub input_manifest_digest: String,
pub state: RoundLifecycleState,
pub row_version: u64,
pub fence: u64,
pub output_manifest_digest: Option<String>,
pub finalization_digest: Option<String>,
pub abort_digest: Option<String>,
pub first_dispatch_operation_id: Option<String>,
pub continuity_head_digest: String,
}The happy path advances Reserved → Proposed → Finalizing → Finalized → Dispatching → Reconciling → Satisfied, each hop gated on the external head at the expected row_version and fence:
- Reserve. Stage the signed round core plus every atom reservation and advance one external batch to
Reserved. Only the anchored batch makes reservations authoritative, so recovery can always name the round that owns an atom. - Propose and accept. Compute statements, intents, and transformation rows against
round_core_digest, persist the signed output manifest, and open the dispute window; every affected participant then signs its statement and the manifest digest. A missing acceptance, an in-window dispute, or an incomplete quorum blocks finalization. - Finalize. Advance
Proposed → Finalizing, obtain the quorum authorization over the proof-free body, then consume it once to reachFinalizedand bind the proof and finalization digests. - Dispatch, reconcile, satisfy. Each immutable intent is dispatched through its own fenced effect slot and closed by a separate reconciliation record; effect-possible ambiguity moves the round to
Incidentwhile preserving every reservation. Only after every intent has an exact settled slot does one batch advance the round and every input obligation toSatisfied. The atoms never change.
Abort competes through the same external head: Aborting → Aborted may run only after rechecking zero dispatch, and a Finalizing round must durably cancel and burn its quorum session first. Before the first dispatch a failed round keeps its atoms unavailable to every other settlement path; after the first dispatch they stay reserved until all intents settle or a compensating reconciliation is authorized. No atom silently returns to per_call.
Quorum-Gated Finalization
Participant acceptances alone do not finalize a round. Finalization is gated on a FROST threshold signature: a detached chio.frost.authorization.v1 using domain chio.frost.clearing-round-finalize.v1, registered ladder action class clearing.round_finalize, a scope_id equal to the round core's trusted governance scope, and a resource_id equal to the round id. It binds the finalization action digest, the active roster digest, the key epoch, the authorization-slot id, and the lifecycle version and fence.
The Finalizing → Finalized transition is the single point at which that authorization is consumed, against a permanent completed authorization slot, so a round can never be finalized twice on the same proof. The group signature proves threshold group authorization, not the exact signer subset; the individual participant acceptances remain attributable evidence and are never a fallback for the quorum.
What a netting statement is not
compute_netting_round emits chio.clearing.settlement-intent.v1 records; a rail still has to move the money, and settlement truth lives only in the separate chio.clearing.settlement-reconciliation.v1 evidence that rail returns. Finalization is quorum-gated: with no production FROST substrate wired in, the Finalizing → Finalized transition returns UnsupportedQuorum and the round stays non-dispatchable, so on devnet no round reaches Dispatching. The clearinghouse takes no custody, claims no on-chain or bank finality, and makes no availability claim; a reserved ObligationAtom stays exclusively clearing_reserved until reconciliation evidence is applied.See Also
- Chio Paper for the obligation and credit-note model that produces the canonical
ObligationAtoma round reserves. - Settlement Rails for the rail that actually executes a
chio.clearing.settlement-intent.v1. - Reconciliation & Watchdog for how a dispatched settlement is closed and reconciled against observed cost.
- Congregates for the multi-party operator structures that a clearing round nets across.