Skip to main content

chio_settle/
lib.rs

1//! Chio settlement runtime over the official web3 contract family.
2//!
3//! `chio-settle` turns approved Chio capital instructions into real contract
4//! calls, projects on-chain state back into the frozen web3 receipt family, and
5//! exposes the bounded Solana-native settlement model used for Ed25519-first
6//! parity checks.
7
8#![forbid(unsafe_code)]
9#![cfg(feature = "web3")]
10
11mod automation;
12mod ccip;
13pub mod channel;
14mod config;
15mod evm;
16mod hook;
17mod observe;
18mod ops;
19mod outcome_store;
20mod payments;
21mod retry;
22mod solana;
23
24use chio_core::capability::scope::MonetaryAmount;
25use serde::{Deserialize, Serialize};
26
27pub use automation::{
28    assess_watchdog_execution, build_bond_watchdog_job, build_settlement_watchdog_job,
29    SettlementAutomationExecution, SettlementAutomationOutcome, SettlementAutomationTriggerKind,
30    SettlementWatchdogJob, SettlementWatchdogKind, CHIO_SETTLEMENT_AUTOMATION_JOB_SCHEMA,
31};
32pub use ccip::{
33    prepare_ccip_settlement_message, reconcile_ccip_delivery, CcipDeliveryObservation,
34    CcipLaneConfig, CcipMessageStatus, CcipReconciliationOutcome, CcipSettlementMessage,
35    CcipSettlementPayload, CHIO_CCIP_SETTLEMENT_MESSAGE_SCHEMA,
36};
37pub use config::{
38    settlement_devnet_rpc_egress_contract, DevnetContracts, DevnetMocks, EvidenceSubstrateMode,
39    LocalDevnetDeployment, SettlementAmountTier, SettlementChainConfig, SettlementEvidenceConfig,
40    SettlementOracleAuthority, SettlementOracleConfig, SettlementPolicyConfig,
41};
42pub use evm::{
43    build_failure_receipt, build_reversal_receipt, confirm_transaction, estimate_call_gas,
44    finalize_bond_lock, finalize_escrow_dispatch, prepare_authorized_channel_merkle_release,
45    prepare_bond_expiry, prepare_bond_impair, prepare_bond_lock,
46    prepare_bond_proof_root_publication, prepare_bond_release, prepare_dual_sign_release,
47    prepare_erc20_approval, prepare_escrow_refund, prepare_merkle_release,
48    prepare_merkle_release_root_publication, prepare_web3_escrow_dispatch, read_bond_snapshot,
49    read_escrow_snapshot, scale_chio_amount_to_token_minor_units,
50    scale_token_minor_units_to_chio_amount, static_validate_call, submit_call, BondLockRequest,
51    DualSignReleaseInput, EscrowDispatchRequest, EscrowExecutionAmount, EscrowSnapshot,
52    EvmBondSnapshot, EvmLogEntry, EvmSignature, EvmTransactionReceipt,
53    PreparedAuthorizedChannelMerkleReleaseV1, PreparedBondExpiry, PreparedBondImpair,
54    PreparedBondLock, PreparedBondProofRoot, PreparedBondRelease, PreparedDualSignRelease,
55    PreparedErc20Approval, PreparedEscrowCreate, PreparedEscrowRefund, PreparedEvmCall,
56    PreparedEvmSubmission, PreparedMerkleRelease, PreparedRootPublication,
57    SettlementAnchorContentBinding,
58};
59pub use hook::{
60    SettlementFailureClass, SettlementFailureCode, SettlementFailureCodeParseError,
61    SettlementFailureReason, SettlementHook, SettlementHookError, SettlementIdempotencyKey,
62    SettlementObservation, SettlementOutcome, SettlementSkipReason, SETTLEMENT_OBSERVATION_SCHEMA,
63    SETTLEMENT_OUTCOME_SCHEMA,
64};
65pub use observe::{
66    inspect_finality, inspect_finality_for_receipt, observe_bond, project_escrow_execution_receipt,
67    BondLifecycleObservation, BondLifecycleStatus, EscrowExecutionProjection,
68    EscrowLifecycleStatus, ExecutionProjectionInput, SettlementFinalityAssessment,
69    SettlementFinalityStatus, SettlementRecoveryAction,
70};
71pub use ops::{
72    classify_settlement_lane, ensure_settlement_operation_allowed, SettlementAlertSeverity,
73    SettlementControlChangeRecord, SettlementControlState, SettlementEmergencyControls,
74    SettlementEmergencyMode, SettlementIncidentAlert, SettlementIndexerCursor,
75    SettlementIndexerCursorInput, SettlementIndexerStatus, SettlementLaneRuntimeStatus,
76    SettlementLaneRuntimeStatusInput, SettlementOperationKind, SettlementRecoveryRecord,
77    SettlementRuntimeReport, SettlementRuntimeStatus, CHIO_SETTLE_RUNTIME_REPORT_SCHEMA,
78};
79pub use outcome_store::{
80    validate_settlement_claim, SettlementAttemptClaim, SettlementClaimValidationError,
81    SettlementOutcomeStore, SettlementRoute, SettlementRouteError, SettlementRouteErrorClass,
82    SettlementRoutingInput, SettlementStoreBinding, MAX_SETTLEMENT_CLAIM_BATCH,
83    MAX_SETTLEMENT_LEASE_MS, MAX_SETTLEMENT_WORKER_ID_BYTES,
84};
85pub use payments::{
86    approval_binding_from_governed, build_x402_payment_requirements, evaluate_circle_nanopayment,
87    prepare_paymaster_compatibility, prepare_transfer_with_authorization,
88    validate_offchain_settlement_receipt, ApprovalBinding, CircleNanopaymentPolicy, Eip3009Domain,
89    Eip3009NonceStore, Erc4337PaymasterPolicy, InMemoryEip3009NonceStore, NonceOutcome,
90    OffchainSettlementReceiptArtifact, PreparedCircleNanopayment, PreparedPaymasterCompatibility,
91    PreparedTransferWithAuthorization, RailBinding, TransferWithAuthorizationInput,
92    X402PaymentRequirements, X402SettlementMode, CHIO_OFFCHAIN_SETTLEMENT_RECEIPT_SCHEMA,
93    DEFAULT_MAX_EIP3009_NONCE_ENTRIES,
94};
95pub use retry::{
96    classify_attempt, DeadLetterRecord, RetryDecision, RetryPolicy, RetryPolicyError,
97    DEFAULT_BACKOFF_CAP_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_INITIAL_BACKOFF_MS,
98    DEFAULT_MAX_RETRIES, SETTLE_DEAD_LETTER_SCHEMA,
99};
100pub use solana::{
101    compare_commitments, prepare_solana_settlement, verify_solana_binding_and_receipt,
102    CommitmentConsistencyReport, PreparedSolanaSettlement, SolanaSettlementConfig,
103    SolanaSettlementRequest, SOLANA_ED25519_PROGRAM_ID,
104};
105
106pub const SETTLEMENT_COMPLETION_FLOW_ROW_ID_PREFIX: &str = "economic-completion-flow:";
107
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
109#[serde(deny_unknown_fields)]
110pub struct SettlementCommitment {
111    pub chain_id: String,
112    pub lane_kind: String,
113    pub capability_commitment: String,
114    pub receipt_reference: String,
115    pub operator_identity: String,
116    pub settlement_amount: MonetaryAmount,
117}
118
119pub fn settlement_completion_flow_row_id(receipt_id: &str) -> Result<String, SettlementError> {
120    if receipt_id.trim().is_empty() {
121        return Err(SettlementError::invalid_input(
122            "completion-flow binding requires a non-empty receipt id",
123        ));
124    }
125    if receipt_id.trim() != receipt_id {
126        return Err(SettlementError::invalid_input(
127            "completion-flow receipt id must not contain surrounding whitespace",
128        ));
129    }
130    Ok(format!(
131        "{SETTLEMENT_COMPLETION_FLOW_ROW_ID_PREFIX}{receipt_id}"
132    ))
133}
134
135pub fn settlement_completion_flow_receipt_id(row_id: &str) -> Result<&str, SettlementError> {
136    let receipt_id = row_id
137        .strip_prefix(SETTLEMENT_COMPLETION_FLOW_ROW_ID_PREFIX)
138        .ok_or_else(|| {
139            SettlementError::invalid_input(format!(
140                "completion-flow row id `{row_id}` does not carry the expected prefix"
141            ))
142        })?;
143    if receipt_id.trim().is_empty() {
144        return Err(SettlementError::invalid_input(format!(
145            "completion-flow row id `{row_id}` does not carry the expected prefix"
146        )));
147    }
148    if receipt_id.trim() != receipt_id {
149        return Err(SettlementError::invalid_input(
150            "completion-flow row id receipt id must not contain surrounding whitespace",
151        ));
152    }
153    Ok(receipt_id)
154}
155
156#[derive(Debug, thiserror::Error)]
157pub enum SettlementError {
158    #[error("invalid input: {0}")]
159    InvalidInput(String),
160
161    #[error("invalid dispatch: {0}")]
162    InvalidDispatch(String),
163
164    #[error("invalid binding: {0}")]
165    InvalidBinding(String),
166
167    #[error("unsupported operation: {0}")]
168    Unsupported(String),
169
170    #[error("rpc error: {0}")]
171    Rpc(String),
172
173    #[error("serialization error: {0}")]
174    Serialization(String),
175
176    #[error("signature error: {0}")]
177    Signature(String),
178
179    #[error("verification error: {0}")]
180    Verification(String),
181}
182
183impl SettlementError {
184    pub(crate) fn invalid_input(message: impl Into<String>) -> Self {
185        Self::InvalidInput(message.into())
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::{
192        settlement_completion_flow_receipt_id, settlement_completion_flow_row_id, SettlementError,
193        SETTLEMENT_COMPLETION_FLOW_ROW_ID_PREFIX,
194    };
195
196    use chio_test_support::prelude::*;
197
198    #[test]
199    fn invalid_input_constructor_preserves_message() {
200        let error = SettlementError::invalid_input("rpc_url must not be empty");
201        assert!(matches!(
202            error,
203            SettlementError::InvalidInput(message) if message == "rpc_url must not be empty"
204        ));
205    }
206
207    #[test]
208    fn completion_flow_receipt_ids_reject_surrounding_whitespace() {
209        let error = settlement_completion_flow_row_id(" receipt-1")
210            .test_expect_err("padded receipt id rejected when building row id");
211        assert!(error.to_string().contains("surrounding whitespace"));
212
213        let row_id = format!("{SETTLEMENT_COMPLETION_FLOW_ROW_ID_PREFIX}receipt-1 ");
214        let error = settlement_completion_flow_receipt_id(&row_id)
215            .test_expect_err("padded receipt id rejected when parsing row id");
216        assert!(error.to_string().contains("surrounding whitespace"));
217    }
218}