Concepts
Feeds and symbols
How the oracle names a feed, how a feed ID derives from its symbol, how numbers are encoded, and which feeds and venues exist today.
A feed is one quantity that the oracle publishes, such as the spot price of BTC in USD. A canonical symbol names each feed. The feed ID is the keccak256 hash of that symbol.
Canonical symbols
A symbol has this form:
<CLASS>.<BASE>/<QUOTE>[.<KIND>]The symbol string is the only source of identity. The rules are:
| Part | Rule |
|---|---|
CLASS |
CRYPTO is the only class. |
BASE, QUOTE |
1 to 16 characters of A–Z and 0–9, with at least one letter. 1000PEPE is valid. 123 is not. |
KIND |
Optional. One of the suffixes in the table below. No suffix means a spot price. |
| Whole string | ASCII, uppercase, no whitespace. |
| Suffix | Feed kind | Exponent |
|---|---|---|
| none | Composite spot price | −8 |
PERP_MARK |
Composite perpetual mark price | −8 |
PERP_INDEX |
Composite perpetual index price | −8 |
PERP_MID |
Composite perpetual mid, from the books of the perpetuals | −8 |
FUNDING_8H |
Funding rate normalised to 8 hours, as a fraction | −9 |
OPEN_INTEREST |
Aggregate open interest, in base units | −8 |
Valid symbols:
CRYPTO.BTC/USD spot price
CRYPTO.USDT/USD the stablecoin peg
CRYPTO.BTC/USD.PERP_MARK perpetual mark price
CRYPTO.BTC/USD.PERP_INDEX perpetual index price
CRYPTO.BTC/USDT.PERP_MID perpetual mid, from the books of the perpetuals
CRYPTO.BTC/USD.FUNDING_8H funding rate over 8 hours
CRYPTO.1000PEPE/USDT.PERP_MARK numeric prefix in the asset codeThe parser refuses each of these:
crypto.btc/usd lowercase
CRYPTO.BTC-USD wrong separator
BTC/USD no class
CRYPTO.BTC/USD.FOO unknown kind
EQUITY.AAPL/USD unknown class
CRYPTO.123/USD digits onlyIn Rust, a Symbol is parsed, not validated. You cannot hold one in a non-canonical form. Two independent implementations that agree on this grammar therefore agree on every feed ID.
Feed IDs
A feed ID is 32 bytes:
feed_id = keccak256(canonical_symbol as ASCII bytes)No registry coordinates the feed IDs. Anyone derives the same ID from the same string. The kind suffix is part of the string, so CRYPTO.BTC/USD and CRYPTO.BTC/USD.PERP_MARK have different IDs.
| Symbol | Feed ID |
|---|---|
CRYPTO.BTC/USD |
0x0523e79c7ed211911a19afaa4def68c896bc72d674bf33c5c64d4d7089e320e2 |
CRYPTO.ETH/USD |
0x2473e24bc41464f61b79547860ce69c012a19808f3057492fb5f95e22a5e40e4 |
CRYPTO.USDT/USD |
0x09c19a99408ed93ad59886c676dda3da0d1e3215c8d349a913b290ef81b676d4 |
CRYPTO.BTC/USD.PERP_MARK |
0xc159655a2f44a71fc9815e636c2b0bd999206a00ba73666124dd11e6eb6a132e |
CRYPTO.BTC/USD.FUNDING_8H |
0xc04929eacfbc432a23afbc552de5122b922f2bb7c29bf83d0418aa01498e8d76 |
Tip
Pin the symbol-to-ID mapping for the feeds that you use. Do not trust a mapping that a server gives you. Compute the ID yourself, or check it against this table.
Numbers
The oracle never uses floating point for a price.
Inside a node. The aggregator works with Fixed: an i128 mantissa at a fixed scale of 10^-12. Division and multiplication round halves away from zero. For this reason a recorded set of observations replays to the same price, bit for bit, months later, on another machine.
On the wire. Each value is a mantissa and an exponent: value = mantissa × 10^expo. The node converts once, at publish time, and rounds halves away from zero.
| Field | Type | Note |
|---|---|---|
price |
i64 |
Mantissa. |
conf |
u64 |
Mantissa at the same exponent as price. Never negative. |
expo |
i32 |
−8 for prices, −9 for funding rates. |
For example, a price mantissa of 6431250000000 at exponent −8 is 64312.5. A funding rate of 0.0001 (1 bp) at exponent −9 is the mantissa 100000. Funding gets three more digits because it is a small fraction.
In JSON. Each decimal is a string, and each 64-bit time is a string. A JavaScript number loses precision past 2^53, and a nanosecond timestamp is far larger than that. A resolved price from the SDK looks like this:
{
"feed_id": "0x0523…20e2",
"symbol": "CRYPTO.BTC/USD",
"status": "ok",
"price": "86599.95",
"conf": "19.71359135",
"nodes": 3,
"halted_nodes": 0,
"concerns": [],
"publish_time_ns": "1791299498663252000",
"spread_bps": "0"
}The confidence interval
conf is a symmetric ± band around the price. It is roughly a 1σ estimate of how wrong the price can be. It is not decoration. It is the main signal that a consumer uses to decide whether to trust an update.
A wide conf is how the oracle says "the markets are dislocated" without a false price and without a halt. Each node computes conf as the widest of three terms:
- how much the venues disagree;
- the actual spread of the books;
- a floor of 1 bp of the price.
The SDK then makes conf at least half the spread between the nodes. See Aggregation and Trust model.
USDT is not USD
Binance, Bybit and OKX quote in USDT. Coinbase and Kraken are the only venues in scope that quote natively in USD. If both of them fail, that is a distinct failure class, not "two of five sources down".
CRYPTO.USDT/USD is therefore a feed like any other. The node prices it from the native-USD venues. A /USD feed converts each USDT-quoted source through it:
usd_price = usdt_price × price of CRYPTO.USDT/USDThe uncertainty of the peg adds to the uncertainty of the price, in quadrature. A peg that wobbles makes the whole catalogue wider. It does not misprice it silently. On a live run, with the peg at 1.0 bp and the aggregate floor at 1.0 bp, CRYPTO.BTC/USD published 1.414 bp: exactly √2 × 1 bp.
If the peg feed halts, every feed that depends on it halts too. A USDT number under a /USD symbol would be wrong by the drift of the peg, and nobody would notice.
The same books are also available without conversion, under a /USDT symbol such as CRYPTO.BTC/USDT. Those feeds carry no peg risk.
Warning
CRYPTO.USDT/USD must never come from a venue that quotes in USDT. It would then report 1.0 by construction, on exactly the day that it stops being true. The node config does not check this rule. The operator must follow it.
Feeds and venues today
Each node serves the feeds that its config file lists. There is no catalogue for the whole network. GET /v2/feeds on a node lists its feeds, with symbol, feed ID and exponent. GET /v1/catalog on the web proxy lists what the proxy serves.
Venues
| Venue | Wire index | Spot | Mark | Index | Funding |
|---|---|---|---|---|---|
| Binance | 0 | yes | yes | yes | yes, over its own interval |
| Bybit | 1 | yes | yes | yes | yes, over its own interval |
| OKX | 2 | yes | yes | no | yes, over its own interval |
| Coinbase | 3 | yes | no | no | no |
| Kraken | 4 | yes | yes | yes | yes, hourly |
| Deribit | 5 | no | yes | yes | yes, normalised to 8 hours |
The wire index is the bit of the venue in the source_bitmap of a price update. The indexes never change.
Two markets are deliberately absent:
- Coinbase perpetuals. They trade on Coinbase International. Its market-data socket needs an API key, a passphrase and an HMAC signature for each subscription. The node has no credential path for ingest, so such a config is a startup error.
- Binance coin-margined futures. They use a different host with its own contract list. The node refuses them. It does not fall back to the USD-margined host, which would stream a different contract under a name that looks correct.
Feeds that this build can serve
The repository has a config with every feed kind that the current build can serve. This list comes from it:
| Feed | Sources | Note |
|---|---|---|
CRYPTO.USDT/USD |
Coinbase, Kraken spot | The peg. Native-USD venues only. |
CRYPTO.BTC/USD |
Coinbase, Kraken; Binance, Bybit, OKX converted | Impact price for 20000. |
CRYPTO.ETH/USD |
Coinbase, Kraken; Binance, Bybit, OKX converted | Impact price for 20000. |
CRYPTO.SOL/USD |
Coinbase, Kraken; Binance, Bybit, OKX converted | Impact price for 6000. |
CRYPTO.BTC/USDT |
Binance, Bybit, OKX spot | No conversion. |
CRYPTO.BTC/USD.PERP_MARK |
Deribit, Kraken Futures | Both quote in USD. |
CRYPTO.ETH/USD.PERP_MARK |
Deribit, Kraken Futures | Both quote in USD. |
CRYPTO.BTC/USD.PERP_INDEX |
Deribit, Kraken Futures | The index that the mark follows. |
CRYPTO.BTC/USDT.PERP_MARK |
Binance, Bybit, OKX perpetuals | USDT-margined. |
CRYPTO.ETH/USDT.PERP_MARK |
Binance, Bybit, OKX perpetuals | USDT-margined. |
CRYPTO.BTC/USDT.PERP_MID |
Binance, Bybit, OKX perpetual books | Where the perpetuals trade. |
CRYPTO.ETH/USDT.PERP_MID |
Binance, Bybit, OKX perpetual books | Where the perpetuals trade. |
CRYPTO.BTC/USD.FUNDING_8H |
Deribit only | Single source on purpose. |
CRYPTO.ETH/USD.FUNDING_8H |
Deribit only | Single source on purpose. |
The config that oracle-node init writes for a new node has one feed: CRYPTO.BTC/USDT, from Binance, Bybit and OKX spot.
What is not served
OPEN_INTEREST. The symbol is valid, but no decoder in this build emits open interest. A feed for it would halt permanently.- Funding from several venues. Only Deribit publishes a rate that is truly normalised to 8 hours. The other venues publish a rate over their own interval, and the message does not say which interval. A median across them mixes numbers that do not compare. See Funding is not normalised.