Skip to main content
Oracle v1. This is the new Pragma oracle on Miden, which replaces the current one. Its address is published here when it is deployed; the current oracle stays live during a migration window. The price keys change: see Pairs and keys.

Ready-to-run example

An account that calls get_median on the oracle through a foreign call, with the checks below. cargo run --release -p consume-price -- --oracle ORACLE_ID --pair BTC/USD
get_median never tells you whether the price is good enough for your use. It returns what the oracle knows; the checks below are the consumer’s job. Skip one and a late, unknown or thin price can look like a valid one.

The read

  • KEY is pair_key("BTC/USD"): the Poseidon2 hash (Miden Hasher::hash) of the pair name in upper case, BTC/USD, as UTF-8 bytes. Push it as a hex literal. The keys of the supported pairs are listed in Pairs and keys and never change.
  • Only publisher entries no older than max_age seconds (against the reference block timestamp) enter the median. get_median uses 120 s.
  • num_publishers: publishers whose entry is in the median.
  • median_price, decimals: the median (average of the two middle prices for an even count) and the decimals of the median entry. 0, 0 when num_publishers is 0.
  • last_updated: timestamp of the newest entry ever published for the pair, fresh or not. 0 if nobody ever published it.
Pairs, decimals and keys: see Pairs and keys.

What to check before using a price

  1. num_publishers is at least what you need. 0 means there is no price: median_price and decimals are 0 and must never be used. The call does not fail in that case. Pick a minimum that matches the value at risk: with a single publisher the “median” is that publisher alone.
  2. Distinguish late from unknown. num_publishers = 0 with last_updated > 0 is a late pair; both at 0 is a key nobody ever published, which is also what a typo in the pair name produces and what a paused oracle answers. Derive the key with pair_key, never by hand, and test it against a pair you know is live.
  3. Freshness against your own tolerance. The default 120 s is a general-purpose window, not a safety margin for your protocol. If you liquidate, settle or mint on this price, call get_median_with_max_age with the bound your protocol can live with, and compare last_updated with the reference block timestamp yourself.
  4. decimals is what you expect. It follows the pair (8 for most, 6 for USDT/USD, USDC/USD and XAUT/USD) and is read from the data, so assert it against the value your arithmetic assumes instead of hard-coding a scale.
  5. The price is not zero and is plausible for your application. Add the bounds or the deviation cap your protocol needs (a circuit breaker against the last accepted price, for instance).
  6. Arithmetic stays in range. A price is a felt below 2^64 - 2^32 + 1. Multiplying it by an amount overflows quickly; scale in u64/u128 helpers, not raw felt arithmetic.

Things that can change under you

  • Pause. We can pause the oracle in an incident (one signer). While it is paused, get_median and get_median_with_max_age do not fail: they answer [0, 0, 0, 0], “no price”, which is the answer for a pair nobody published. A foreign call that fails would abort your whole transaction and cannot be caught on Miden, so a paused oracle is handled on the same path as a missing price: the check on num_publishers below is what protects you, and you decide whether to block, fall back or wait. pm-oracle-cli median reads the pause flag and says so; pm-multisig status shows it.
  • Procedure root. A foreign procedure invocation is bound to the MAST root of get_median. The oracle is code-upgradable (multisig, full quorum), and an upgrade that touches get_median or what it calls changes the root, after which a call using the old root fails (UnknownAccountProcedure, seen on the testnet after an upgrade of the dummy oracle). Pin the oracle account id and the root, and follow the upgrade announcements. examples/consume-price is a complete consumer using this call.
  • Publisher set. Publishers are added and removed by the multisig (default quorum). The median moves accordingly, which is why num_publishers is part of the answer.
num_sources (how many exchanges fed a publisher’s own price) is stored in each publisher’s entry and is not part of the median read. To inspect it, read the publishers’ entries off-chain.