← Protocols
Chronicle
Oracle·EVM · Multi-chain

Chronicle

01Description

Decentralized oracle spawned from MakerDAO (formerly MakerDAO's Oracles), securing $5B+ in DeFi. Uses the Scribe primitive — Schnorr signature aggregation that cuts oracle update gas by ~60%. Provides cost-efficient, verifiable price feeds across Ethereum, L2s, and other EVM chains.

02Best for
  • 01low-gas push price feeds (Schnorr-aggregated)
  • 02MakerDAO / Sky ecosystem and forks
  • 03Ethereum L1 + L2s (Optimism, Arbitrum, Base, Gnosis, Polygon)
  • 04verifiable, cost-efficient data feeds
  • 05long-running protocols sensitive to oracle update fees
03Install
  • forge install chronicleprotocol/scribe
  • pnpm add viem
04Environment variables
VariableScopeDescription
CHRONICLE_ORACLE_ADDRESSClientChronicle Oracle / Scribe contract address for the desired feed (e.g. `Chronicle_ETH_USD_3`) on the target chain. See chroniclelabs.org/dashboard.
CHRONICLE_SELF_KISSER_ADDRESSClientSelfKisser contract address (testnet only). Calling `selfKiss(oracle)` adds your address to the oracle's read whitelist.
05Prompt snippet
Chronicle oracles are READ-PROTECTED by a whitelist. On testnet, call `ISelfKisser(selfKisser).selfKiss(oracle)` once from your contract address to add yourself to the toll list (`tolled`). On mainnet, request access via the Chronicle dashboard. Then import `IChronicle` and read with `(uint256 value, uint256 age) = chronicle.readWithAge()` — `value` is 18-decimal scaled, `age` is the unix timestamp. Prefer the `tryRead()` / `tryReadWithAge()` variants which return a `(bool ok, uint256 value, uint256 age)` tuple and NEVER revert on stale or missing data — much safer in liquidation paths. Always staleness-check: `require(block.timestamp - age <= maxAge)` and reject `value == 0`.
06Gotchas
  • Read-protected by default: calling `read()` from a non-tolled address REVERTS with `NotTolled`. You MUST `selfKiss` (testnet) or be added by Chronicle ops (mainnet) BEFORE first read — silent integration test failures otherwise.
  • Use `tryRead()` / `tryReadWithAge()` in critical paths: the bare `read()` reverts on staleness or zero value, which can brick a liquidation flow; the `try*` variants return `(bool ok, ...)` and let you fall back gracefully.
  • Push model with Schnorr-aggregated `poke`: feeds update on heartbeat + deviation thresholds (per-feed, often 0.5-1% deviation / 24h heartbeat). Always enforce `block.timestamp - age <= maxAge` matching the feed's heartbeat — the gas savings come at the cost of slightly less frequent updates than competing feeds.
  • Decimal scaling is FIXED at 18 decimals (not Chainlink's per-feed `decimals()` of typically 8) — mixing Chronicle with Chainlink in the same pricing math is a 1e10 silent bug.
  • Per-chain, per-pair addresses: the same pair (e.g. ETH/USD) has different addresses on Ethereum vs Optimism vs Arbitrum AND uses versioned suffixes (`_3`, `_4`) — copy from chroniclelabs.org/dashboard, never hardcode.
  • Network fees: reads are free (just gas). Updates (`poke`) are paid by Chronicle validators — your protocol does NOT pay per-update, but you also can't force an out-of-cadence update.
  • Chronicle is Maker-aligned: feed coverage skews to ETH/USD, BTC/USD, RWA collateral types, and Sky/Maker priorities — long-tail tokens are NOT the focus; verify the feed exists on-chain before integrating.
07Alternatives