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.
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.
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.
Conversion rounds up, then adds the operator's margin
The tool reports minor units of its currency. The oracle supplies the rate as a fraction , each currency has a pinned scale of minor units per unit, for the reported currency and for the grant's, and is the pair's margin in basis points:
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 . 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 and must agree:
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:
chio oracle read USDC/USDpair 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:
const [receipt] = await project.history.receipts({ agent: fixer, since: "5m" });
console.log(receipt.describe());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
receipt = project.history.receipts(agent=fixer, since="5m")[0]
print(receipt.describe())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
let receipt = project.history().receipts().agent("fixer").since("5m").first().await?;
println!("{}", receipt.describe());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
chio receipt explain rcpt-01a8…7f20receipt 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:
"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.