Chio/Docs
LOGIN · JOIN

BuildWeb3

Chainlink Oracles

Convert agent budgets across currencies with Chainlink and Pyth prices, then record the validated oracle reading in each settlement receipt.


Why Oracles in the Kernel

A seller-controlled exchange rate can defeat a capability budget. A kernel that authorizes a "$10 of API time" capability must use an independent price source. Oracles provide a public price and time, allowing third parties to verify the oracle reading in a settlement receipt.

The integration is policy-level, not runtime-level: Chio never executes a swap. It reads prices, applies a configurable margin, and records which oracle reading it used in the receipt. If an oracle is stale or a sequencer is down, the kernel rejects the evaluation.


Supported Oracle Stacks

OracleChainsTypical cadence
Chainlink Data FeedsBase Mainnet (default) · Arbitrum One (standby)Per-feed heartbeat plus deviation trigger
Pyth Network (Hermes)Base Mainnet (default) · Arbitrum One (standby)Pull-based; sub-second publish cadence

Base Mainnet is enabled by default and monitored through the official Chainlink sequencer-uptime feed. Arbitrum One ships as standby operator inventory, disabled by default until later web3-runtime milestones consume it. There is no Ethereum Mainnet, Optimism, or Solana entry in the oracle chain inventory today.

Chainlink is the primary source; where a pair also carries a Pyth id, Pyth acts as a corroborating fallback. When the two sources diverge beyond the configured threshold (500 bps by default), Chio trips the circuit breaker for that pair. The failure surfaces as PriceOracleError::CircuitBreakerTripped, carrying the pair, the observed divergence, and the threshold.


How a Price Gets Consumed

rendering
A price read fetches, validates, converts, and records a reading. A rejected step rejects the enclosing capability evaluation.

Pair Inventory and Freshness

The shipped inventory is four Base pairs under one uniform policy: a max_age_seconds of 600 for staleness and a divergence_threshold_bps of 500 (5%) for cross-source agreement. A reading is rejected if it is stale, and the pair trips its breaker if the sources diverge past the threshold. TWAP smoothing is applied per pair.

PairSourcesTWAP
ETH/USDChainlink + PythEnabled (600 s window)
BTC/USDChainlink + PythEnabled (600 s window)
USDC/USDChainlink + PythDisabled (spot only)
LINK/USDChainlink onlyEnabled (600 s window)

Two nuances matter. USDC/USD is spot-only: TWAP is deliberately off because peg deviation is itself the signal, and smoothing it away would hide the thing you want to catch. LINK/USD runs Chainlink-only, with no Pyth fallback, so its breaker never trips on cross-source divergence. Per-pair overrides (enable/disable, forced backend, custom divergence threshold, degraded mode) are loaded from the operator config and enforced for every subsequent call.


FX Conversion with Margin

Once a price passes its checks, Chio applies an operator-configured FX margin before quoting the converted amount. The margin protects both sides against the short window between price read and on-chain execution. The shipped default is a uniform exchange_rate_margin_bps of 200 (2%) across every pair; operators tune it per pair as needed.

json
{
  "primary": "chainlink",
  "fallback": "pyth",
  "pairs": [
    {
      "base": "ETH", "quote": "USD", "chain_id": 8453,
      "chainlink": { "address": "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", "decimals": 8, "heartbeat_seconds": 300 },
      "pyth": { "id": "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace" },
      "policy": {
        "max_age_seconds": 600,
        "divergence_threshold_bps": 500,
        "exchange_rate_margin_bps": 200,
        "twap_enabled": true
      }
    },
    {
      "base": "USDC", "quote": "USD", "chain_id": 8453,
      "chainlink": { "address": "0x7e860098F58bBFC8648a4311b374B1D669a2bc6B", "decimals": 8, "heartbeat_seconds": 68400 },
      "pyth": { "id": "0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a" },
      "policy": {
        "max_age_seconds": 600,
        "divergence_threshold_bps": 500,
        "exchange_rate_margin_bps": 200,
        "twap_enabled": false,
        "stable_pair": true
      }
    }
  ],
  "operator": {
    "global_pause": false,
    "chains": [
      {
        "chain_id": 8453,
        "label": "base-mainnet",
        "caip2": "eip155:8453",
        "enabled": true,
        "sequencer_uptime_feed": "0xBCF85224fc0756B9Fa45aA7892530B47e10b6433",
        "sequencer_grace_period_seconds": 300
      }
    ]
  }
}

Circuit Breakers and Sequencer Health

Two operational conditions trigger an automatic circuit break on oracle-backed conversions:

  • Source divergence · When two sources are configured for a pair and their prices differ by more than divergence_threshold_bps, the read fails with CircuitBreakerTripped rather than picking one. This is the only breaker that compares readings against each other.
  • L2 sequencer downtime · On optimistic L2s a frozen sequencer can keep a feed's on-chain timestamp advancing while the underlying price has not actually updated. Chio reads the chain's official sequencer-uptime feed and fails with SequencerDown while the sequencer is offline. After it recovers, conversions stay blocked with SequencerRecovering until the sequencer_grace_period_seconds window (300 s by default) elapses, so a just-restarted sequencer does not immediately price against stale state.

Staleness is a per-read failure, not a breaker

A reading older than max_age_seconds fails that read with Stale, carrying the observed age and the limit. There is no consecutive-failure counter and no persisted unhealthy flag: each conversion is judged on the readings it gets. The PairHealthStatus an operator report shows (healthy, fallback_active, degraded_grace, paused, tripped, unavailable) is derived per observation for reporting; it does not gate the next read.

Either way the conversion fails closed, and the denial is a signed receipt.


Oracle Evidence in Receipts

When a conversion succeeds, chio attaches an OracleConversionEvidence object to the settlement receipt under oracle_evidence. It is flat and it is deny_unknown_fields: a verifier recomputes the conversion from the rate fraction and the two cost figures recorded on it, and any key the type does not declare makes the whole receipt fail to parse.

crates/core/chio-core-types/src/oracle.rs11-31rust
pub struct OracleConversionEvidence {
    pub schema: String,
    pub base: String,
    pub quote: String,
    pub authority: String,
    pub rate_numerator: u64,
    pub rate_denominator: u64,
    pub source: String,
    pub feed_address: String,
    pub updated_at: u64,
    pub max_age_seconds: u64,
    pub cache_age_seconds: u64,
    pub converted_cost_units: u64,
    pub original_cost_units: u64,
    pub original_currency: String,
    pub grant_currency: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub oracle_public_key: Option<PublicKey>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub signature: Option<Signature>,
}

The rate is a rate_numerator over rate_denominator fraction rather than a float, so the recomputation is integer arithmetic and exact. Both cost figures are in the minor unit of their own currency, so the check the settlement verifier reruns rescales between them: converted_cost_units must equal original_cost_units times rate_numerator times the quote currency's minor units per unit, divided by the base currency's minor units per unit times rate_denominator, rounded up. USD, EUR and GBP are 100 to the unit, JPY 1, USDC and USDT a million, BTC 100 million, ETH and LINK 10 to the eighteenth; anything else is refused as an unsupported currency. Two more checks ride alongside: base and quote must equal original_currency and grant_currency, and cache_age_seconds must not exceed max_age_seconds. Here is one out of a committed settlement bundle:

oracle-evidence · settlement-oracle-evidencetranscript
$ jq '.settlement_receipt.oracle_evidence' \
  fixtures/proof-room/public-settlement/finality-below-threshold/settlement-proof-bundle.json
{
  "authority": "chio_link_runtime_v1",
  "base": "ETH",
  "cache_age_seconds": 45,
  "converted_cost_units": 300,
  "feed_address": "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612",
  "grant_currency": "USD",
  "max_age_seconds": 3600,
  "oracle_public_key": "d9bf2148748a85c89da5aad8ee0b0fc2d105fd39d41a4c796536354f0ae2900c",
  "original_cost_units": 1000000000000000,
  "original_currency": "ETH",
  "quote": "USD",
  "rate_denominator": 100,
  "rate_numerator": 300000,
  "schema": "chio.oracle-conversion-evidence.v1",
  "signature": "d1b5f86427cd80be8fced5663435ba313b1daf8ab8b469e2864ec21a7bb07613463f9863dbff2711ac0392fc1b8737f2520f0e9c001122414fbc1305dc820c09",
  "source": "chainlink",
  "updated_at": 1743292740
}
exit 0

Next Steps

  • Settlement · on-chain execution that uses oracle-backed quotes
  • Budgets & Metering · how cross-currency budgets are authored and enforced
  • x402 Payments · agent-level payments where oracle quotes feed per-request pricing