Canonical Format
The v1 pipe-delimited canonical payload specification — the string that gets signed.
Envelope Format
Every attestation follows a two-layer structure: a fixed envelope wrapping type-specific payload fields. Signing and verification operate on the complete canonical string.
VERSION | TYPE | <payload fields...> | TIMESTAMP | NONCE
Envelope Fields
| Field | Position | Type | Example | Description |
|---|---|---|---|---|
| VERSION | 0 | string | v1 | Format version. |
| TYPE | 1 | string | PRICE, ECON, VOLATILITY, SENTIMENT, STRESS, GAS, GAS_INDEX, WEATHER | Attestation type — determines payload structure. COMMODITIES use TYPE=ECON with REGION=COMMODITIES. |
| TIMESTAMP | N-1 | integer | 1741514400 | Unix timestamp (seconds). Always second-to-last. |
| NONCE | N | integer | 482910 | 6-digit random nonce. Always last field. |
PRICE Type
v1|PRICE|PAIR|PRICE_VALUE|CURRENCY|DECIMALS|SOURCES|METHOD|TIMESTAMP|NONCE
v1|PRICE|BTCUSD|84231.50|USD|2|binance,binance_us,bitfinex,bitstamp,coinbase,gateio,gemini,kraken,okx|median|1741514400|482910
v1|PRICE|EURUSD|1.15000|USD|5|bankofcanada,bitstamp,cnb,ecb,ecbdirect,kraken,norgesbank|median|1741514400|719304
PRICE_VALUE is always zero-padded to exactly DECIMALS decimal places (e.g. 84231.50 not 84231.5). Default decimals: JPY pairs = 0, USD/EUR/GBP/CHF/CNY/CAD = 2. Some pairs override this (e.g. EUR/USD uses 5). SOURCES are always lexicographically sorted (ASCII order). Any deviation in precision or ordering will cause signature verification to fail.
ECON Type
v1|ECON|REGION|INDICATOR|VALUE|UNIT|PERIOD|VINTAGEDATE|SOURCEAGENCY|SERIESID|SOURCEMODEL|TIMESTAMP|NONCE
v1|ECON|US|CPI|326.785|index198284100|2026-02|2026-03-14|BLS|CUUR0000SA0|directapi|1741514400|830114
COMMODITIES (ECON subtype)
v1|ECON|COMMODITIES|SYMBOL|VALUE|UNIT|PERIOD|VINTAGEDATE|SOURCEAGENCY|SERIESID|directapi|TIMESTAMP|NONCE
v1|ECON|COMMODITIES|WTI|94.65|usdperbarrel|2026-03-09|2026-03-15|EIA|DCOILWTICO|directapi|1741514400|402341
COMMODITIES shares the ECON TYPE but uses COMMODITIES in the REGION field, followed by SYMBOL (e.g. WTI, BRENT, NATGAS) in the INDICATOR position. The SOURCEMODEL is always directapi. Regional ECON uses a country code (US, EU) in the REGION field and a variable SOURCEMODEL.
VOLATILITY Type (MSVI)
v1|VOLATILITY|PAIR|MSVI|VALUE|UNIT|30D|RV:{rv}:{w},IV:{iv}:{w},TS:{ts}:{w},FR:{fr}:{w},PCR:{pcr}:{w}|CONFIDENCE:{c}|METHOD:v{n}|{TIMESTAMP}|{NONCE}v1|VOLATILITY|BTCUSD|MSVI|15.65|index|30D|RV:38.99:0.3,IV:38.39:0.25,TS:0.909:0.15,FR:20.02:0.2,PCR:0.651:0.1|CONFIDENCE:0.6765|METHOD:v1|1744416000|291847
VOLATILITY canonicals include a 30D window field and omit REGIME. The UNIT field uses lowercase index. Component values use the raw value (e.g. RV = realized vol percentage), not the normalizedValue from the preview response.
The components field includes all five weights (RV, IV, TS, FR, PCR) allowing independent reconstruction and verification of the index value.
SENTIMENT Type (MSXI)
v1|SENTIMENT|PAIR|MSXI|VALUE|INDEX|FR:{fr}:{w},SKEW:{skew}:{w},PCR:{pcr}:{w},TS:{ts}:{w},BASIS:{basis}:{w}|REGIME:{regime}|CONFIDENCE:{c}|METHOD:v{n}|{TIMESTAMP}|{NONCE}v1|SENTIMENT|BTCUSD|MSXI|-9.88|INDEX|FR:-0.00:0.3,SKEW:-4.12:0.25,PCR:0.863:0.2,TS:0.923:0.15,BASIS:0.049:0.1|REGIME:NEUTRAL|CONFIDENCE:1.0000|METHOD:v1|1744416000|382910
Positive values indicate bullish positioning, negative values bearish. The components field includes all five weights (FR, SKEW, PCR, TS, BASIS) allowing independent reconstruction.
STRESS Type (MSSI)
v1|STRESS|MARKET|MSSI|VALUE|INDEX|VOL:{vol}:{w},STBL:{stbl}:{w},FR:{fr}:{w}|REGIME:{regime}|CONFIDENCE:{c}|METHOD:v{n}|{TIMESTAMP}|{NONCE}v1|STRESS|MARKET|MSSI|64.32|INDEX|VOL:61.81:0.35,STBL:0.00:0.3,FR:38.57:0.35|REGIME:HIGH|CONFIDENCE:1.0000|METHOD:v1|1744416000|571923
Market-wide single number — not per-pair. The components field includes all three weights (VOL, STBL, FR) allowing independent reconstruction and stress source identification.
Index Numeric Precision
Index values are always formatted to 2 decimal places. Component values use fixed precision per key: FR, SKEW, RV, IV, VOL, STBL use 2dp. PCR, TS use 3dp. BASIS uses 4dp. Weights are unformatted floats. Any deviation in decimal precision will cause signature verification to fail. When a component is unavailable, it is encoded as KEY:NA:weight.
Signing Process
canonical_bytes = canonical_string.encode("utf-8")
signature = Ed25519_sign(private_key, canonical_bytes) # RFC 8032, no pre-hash
output = hex_encode(signature) # 64 bytes, 128 hex charactersThe signature is standard Ed25519 over the canonical string's UTF-8 bytes, with no pre-hash: Ed25519 already hashes internally (SHA-512) as part of the algorithm, so any standard library verifies it directly. Pass the canonical bytes, the hex-decoded signature and the node's public key. Signatures and public keys are hex-encoded. Every signed data response uses this one scheme, on either payment rail. DLC attestations are separate: they use BIP-340 Schnorr, described in the DLC documentation.
from nacl.signing import VerifyKey
# PUBKEY: the published key of the node that signed (see Public Keys)
VerifyKey(bytes.fromhex(PUBKEY)).verify(
response["canonical"].encode("utf-8"),
bytes.fromhex(response["signature"]),
) # raises BadSignatureError if the signature is invalidTwo nodes sign, each with its own key, and the pubkey field names which one. Check that it matches a key on the Public Keys page before trusting the signature: a response will always verify against a key it supplies itself. See Signature Verification for JavaScript and Go.
Until 19 September 2026 several services signed a SHA-256 or SHA-512 digest of the canonical instead of the canonical itself, with the signature hex- or base64-encoded. Archived responses from before the change verify under the scheme they were signed with; every response since verifies under the standard above.
Parsing
fields = canonical.split("|")
version = fields[0] # "v1"
type_id = fields[1] # "PRICE" or "ECON"
payload = fields[2:-2] # type-specific fields
timestamp = fields[-2] # Unix integer
nonce = fields[-1] # 6-digit nonce
# For PRICE type:
if type_id == "PRICE":
pair = fields[2] # "BTCUSD"
price = fields[3] # "84231.50"
sources = fields[6].split(",") # ["binance", ...]
# For VOLATILITY (no REGIME, has 30D window):
elif type_id == "VOLATILITY":
pair = fields[2] # "BTCUSD"
index_name = fields[3] # "MSVI"
value = fields[4] # index value
unit = fields[5] # "index"
window = fields[6] # "30D"
components = fields[7] # "RV:38.99:0.3,IV:38.39:0.25,..."
confidence = fields[8].split(":")[1] # "0.6765"
# For SENTIMENT and STRESS (have REGIME, no window):
elif type_id in ("SENTIMENT", "STRESS"):
pair = fields[2] # "BTCUSD" or "MARKET"
index_name = fields[3] # "MSXI" or "MSSI"
value = fields[4] # index value
components = fields[6] # "FR:-0.00:0.3,SKEW:-4.12:0.25,..."
regime = fields[7].split(":")[1] # "NEUTRAL"
# For ECON (includes COMMODITIES where region="COMMODITIES"):
elif type_id == "ECON":
region = fields[2] # "US", "EU", or "COMMODITIES"
indicator = fields[3] # "CPI", "WTI", etc.
value = fields[4]
unit = fields[5]
period = fields[6]
source = fields[8] # "BLS", "EIA", etc.
# GAS and WEATHER types have distinct formats — see dedicated docs.Never reconstruct the canonical string from other response fields. The nonce is server-generated and cannot be reproduced. Always verify against the canonical field returned in the response.
SYNOPSIS Type
Used by /oracle/synopsis/market. 15-signal market state snapshot.
v1|SYNOPSIS|MARKET|mssi|{value}|{regime}|msti|{value}|{regime}|msxi_btc|{value}|{regime}|msvi_btc|{value}|{regime}|funding_btc|{value}|oi_delta_1h|{value}|liq_1h|{value}|btc|{price}|eth|{price}|{timestamp}|{nonce}PERP_REGIME Type
Used by /oracle/perp/btc, /oracle/perp/eth, /oracle/perp/sol.
v1|PERP_REGIME|{CURRENCY}|regime|{perp_regime}|bias|{bias}|confidence|{confidence}|funding|{funding_pct}|msxi|{msxi}|msvi|{msvi}|{timestamp}|{nonce}MACRO_RISK Type
Used by /oracle/intel/macro/risk.
v1|MACRO_RISK|MARKET|score|{risk_score}|regime|{risk_regime}|mssi|{value}|mssi_regime|{regime}|msti|{value}|msti_regime|{regime}|high_impact|{count}|{timestamp}|{nonce}