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.
- 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
- forge install chronicleprotocol/scribe
- pnpm add viem
| Variable | Scope | Description |
|---|---|---|
| CHRONICLE_ORACLE_ADDRESS | Client | Chronicle 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_ADDRESS | Client | SelfKisser contract address (testnet only). Calling `selfKiss(oracle)` adds your address to the oracle's read whitelist. |
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`.
- ⚑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.