Chio/Docs
LOGIN · JOIN

Cross-Currency Budgets

Hold a dollar budget against a tool that bills in USDC or ETH, converted at a checked oracle rate with a margin.

Concept chio@5f63e6e

On this page

A budget is a promise in one unit. When a grant is denominated in dollars and a tool bills in USDC or ETH, someone chooses the exchange rate, and a rate the seller chooses can turn a ten-dollar ceiling into any amount the seller likes. Chio takes the rate from Chainlink, corroborated by Pyth, checks the reading before it uses it, converts in exact integer arithmetic with a margin the operator sets, and signs the reading into the receipt so anyone can recompute the charge.

The hold never leaves the grant's currency. The kernel reserves the worst case in dollars before dispatch, and conversion happens once, at reconciliation, when the tool reports what the call cost in its own currency.

Oracle conversionChoose a reading.

A fresh, corroborated reading reconciles the hold

The Chainlink reading is 312 s old and Pyth agrees within 500 bps. The kernel converts 0.50 USDC at 0.9998, rounds up, adds the 200 bps margin, and charges USD 0.51 against the USD 0.80 hold. The reading rides on the receipt as oracle_evidence. Oracle evidence in receipts.

One cross-currency charge at reconciliation: the grant holds USD, the tool reports its cost in USDC, and the kernel reads the USDC/USD pair from Chainlink, corroborated by Pyth, before it converts. The rate, the reading's age, and the amounts are illustrative; operator pause, disabled chains, and TWAP smoothing are left out.Chainlink Oracles

Conversion rounds up, then adds the operator's margin

The tool reports uu minor units of its currency. The oracle supplies the rate as a fraction n/dn/d, each currency has a pinned scale of minor units per unit, mbm_b for the reported currency and mqm_q for the grant's, and μ\mu is the pair's margin in basis points:

c  =  ⌈⌈u⋅n⋅mqmb⋅d⌉⋅104+μ104⌉,charge=min⁡(c, h)c \;=\; \left\lceil \left\lceil \frac{u \cdot n \cdot m_q}{m_b \cdot d} \right\rceil \cdot \frac{10^4 + \mu}{10^4} \right\rceil, \qquad \text{charge} = \min(c,\, h)

Both divisions round up, so the recorded spend never understates the reported cost. A USDC cost of 0.50 is 500,000 minor units; at 0.9998 it converts to 49.99 cents, rounded to 50, and the default 200 basis-point margin makes it 51. The margin covers the price's movement between the reading and settlement. USD, EUR, and GBP have 100 minor units to the unit, JPY 1, USDC and USDT a million, BTC 100 million, and ETH and LINK 1018; any other currency is refused.

The charge is the converted amount up to the hold hh. A conversion that comes out above the hold is charged at the hold and recorded with settlement_status: failed, the same rule that caps any reported cost, so a weak exchange rate cannot raise a charge past what the grant authorized.

Two sources must agree before a price is used

Each read checks the reading's age and compares the two sources. A reading older than the pair's max_age_seconds (600 by default) fails with Stale. When the pair has both sources, their normalized prices p1p_1 and p2p_2 must agree:

δ  =  ⌈∣p1−p2∣⋅104min⁡(p1,p2)⌉  ≤  500 bps\delta \;=\; \left\lceil \frac{|p_1 - p_2| \cdot 10^4}{\min(p_1, p_2)} \right\rceil \;\le\; 500\ \text{bps}

A wider spread fails the read with CircuitBreakerTripped, carrying the pair, the observed divergence, and the threshold, instead of picking one source. LINK/USD has no Pyth source, so its breaker never trips on divergence. Volatile pairs such as ETH/USD price from a time-weighted average over a 600-second window; USDC/USD prices at spot, because a peg's deviation is the signal. The operator can read a pair the way the kernel will:

Terminalbash
chio oracle read USDC/USD
Output
pair        USDC/USD on Base, spot
chainlink   0.99980000, updated 312 s ago, limit 600 s
pyth        0.99985000, divergence 1 bps, limit 500 bps
sequencer   up, past its 300 s grace period
margin      200 bps
status      healthy

Degraded mode is off by default. An operator who enables it for a pair lets a cached reading up to 300 seconds past its limit price a charge, with an extra 800 basis points of margin, and the evidence names its source as chainlink:degraded.

An L2 price waits for its sequencer

Chainlink feeds on a rollup are only as current as the rollup's sequencer. Base is enabled by default and watched through Chainlink's sequencer-uptime feed; Arbitrum One ships as standby inventory, disabled until you configure it. While a sequencer is down, reads for its pairs fail with SequencerDown before any price is fetched. After it recovers, they fail with SequencerRecovering until sequencer_grace_period_seconds (300 by default) elapses, so a freshly restarted chain cannot price against stale state. Setting global_pause stops every read.

Read more in Chainlink Oracles, which configures the feeds, the chains, and their policies.

A failed read closes the hold at the authorized amount

The call has already run when its cost is reported, so a failed read cannot undo it. The kernel closes the hold at the amount it reserved, marks settlement failed so no rail captures a payment on an unread rate, and writes an oracle_conversion note with status: failed, both currencies, the reported and provisional amounts, and the reason. A pair with no oracle configured takes the same path. The payer is never charged more than the hold it authorized, and the operator resolves the true amount through reconciliation.

The receipt carries the reading, so anyone can recompute the charge

Read the receipt for a paid fetch the fixer made, billed in USDC against its USD 2.00 grant:

audit.tsaudit.pysrc/main.rs · in mainTerminal
const [receipt] = await project.history.receipts({ agent: fixer, since: "5m" });
console.log(receipt.describe());
Output
receipt    rcpt-01a8…7f20
request    fixer web.fetch, billed in USDC
verdict    allow
held       USD 0.80 before dispatch
reported   0.50 USDC
rate       USDC/USD 9998/10000 from chainlink, 312 s old of 600, pyth within 500 bps
charged    USD 0.51 with a 200 bps margin, USD 0.29 released
signed     your kernel 4f1c…a2e9, signature verifies

The reading itself sits in the receipt's financial metadata as an OracleConversionEvidence object, and the settlement execution receipt carries the same object:

rcpt-01a8…7f20 · metadata.financialjson
"oracle_evidence": {
  "schema": "chio.oracle-conversion-evidence.v1",
  "base": "USDC",
  "quote": "USD",
  "authority": "chio_link_runtime_v1",
  "rate_numerator": 9998,
  "rate_denominator": 10000,
  "source": "chainlink",
  "feed_address": "0x7e860098F58bBFC8648a4311b374B1D669a2bc6B",
  "max_age_seconds": 600,
  "cache_age_seconds": 312,
  "original_cost_units": 500000,
  "original_currency": "USDC",
  "converted_cost_units": 51,
  "grant_currency": "USD"
}

The rate is an exact fraction and both amounts are in minor units, so a reader recomputes the conversion in integer arithmetic without trusting the kernel's. The type is closed: a key it does not declare makes the whole receipt fail to parse.

Next: configure the feeds