pricing oracle
dust has one price source: Pyth Pro (formerly Lazer), a signed price tick every 50 ms. every bet fills on a tick the program verifies itself. other venues only draw the chart.
the feed
each instrument pins its feed and its channel when it is listed. both are fixed after listing.
| instrument | id | Pyth Pro feed id | channel |
|---|---|---|---|
| BTC | 0 | 1 | fixed rate, 50 ms (channel id 2) |
| ETH | 1 | 2 | fixed rate, 50 ms (channel id 2) |
| SOL | 2 | 6 | fixed rate, 50 ms (channel id 2) |
the ids and channel are from the devnet listing. channel ids 2, 3 and 4 mean 50, 200 and 1000 ms. the real time channel has no grid and is never accepted.
how a tick is verified on chain
- the transaction carries an ed25519 precompile instruction at index 0. it holds one signature entry for each signed update in the dust instruction.
- the dust instruction takes the signed message as bytes. the size limit is 512 bytes.
- the program calls the official verifier program
pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPtby CPI. the verifier checks the signature against its trusted signer set and charges the update fee, 1 lamport, to the instruction signer. - the program parses the signed payload strictly: magic number, timestamp in microseconds, channel, exactly one feed, then tagged properties. a repeated property, an unknown tag or trailing bytes are all rejected.
- the program checks the tick against the instrument:
- the feed id and the channel id match the pin
- the timestamp is on the 50 ms grid
- the price is positive and at most 10^15 in units of 1e-9 USD
- the timestamp is at most
max_future_ms(5000) ahead of the clock
the price is stored as mantissa × 10^(exponent + 9), so $83,000 is 83000000000000.
a settler must ask Pyth for four properties: price, exponent, confidence and publisher count.
the trust you take on is Pyth's signers. if they went bad, so would the price.
the future tick lock
a request reads no price. it only writes a target time. the fill is the tick at that time, and that tick does not exist yet when the request lands.
T_req = max(hwm, clock_ms + G)
ts* = ceil((T_req + D) / P) × Phwm is the newest tick time the program has recorded. clock_ms is the Solana clock in whole seconds times 1000.
| symbol | meaning | shipped value | bound |
|---|---|---|---|
| P | tick grid | 50 ms | one of 50, 100, 200, 400, 1000, and equal to the channel rate |
| D | settle delay | 1000 ms | 1000 to 5000 ms |
| G | clock guard | 1000 ms | 0 to 3000 ms |
| G + D | the floor | 2000 ms | no listing and no setter can go below it |
the same rule stamps four other things: the activation tick of a limit order, the cancel tick of a limit order, the switch tick of a take profit or stop loss change, and the DUST MODE entry tick.
worked example. the clock reads 1,791,669,410 s and the hwm is 1,791,669,410,200 ms:
T_req = max(1791669410200, 1791669410000 + 1000) = 1791669411000
ts* = ceil((1791669411000 + 1000) / 50) × 50 = 1791669412000if the hwm is ahead of the clock, at 1,791,669,412,040, the hwm wins: T_req = 1791669412040 and ts* = 1791669413050.
what you feel: the price locks about two seconds after you submit. the median is 1.75 s, the 10th percentile 1.3 s and the 90th percentile 2.2 s, measured on devnet.
why G + D is at least 2000
the price at ts* is unknowable to anyone only if ts* is at least one grid step past what the fastest outside observer can know when the request lands. write L for the total clock lag, λ for the lead an outside trader has over the oracle, and Δmin for one grid step. the condition is:
G + D ≥ L + λ + Δminit covers both cases of T_req: the clock term and the hwm term. an adversary who presents an old tick cannot do better than the clock floor.
| quantity | value | where it comes from |
|---|---|---|
| L, in transaction clock lag | p50 628 ms, p99 1184 ms, max 1293 ms, n = 891 | measured on devnet |
| λ, outside lead | 125 ms | measured against the fastest outside venue |
| Δmin | 50 ms | one grid step |
| needed | 1468 ms | 1293 + 125 + 50 |
| compiled floor | 2000 ms | needed plus about 500 ms of stress allowance |
| margin at the worst sample | 532 ms | 2000 − 1468 |
| margin at the median | 1197 ms | 2000 − (628 + 125 + 50) |
| unsound above | L = 1825 ms | 2000 − 125 − 50 |
the Solana clock is whole seconds, a floor of wall time. it can lag by up to 999 ms before any real delay. that is why "about a second" is not reachable at any sound setting. the keepers warn when the measured lag passes 1500 ms and page above 1825 ms.
mainnet gets its own measurement before trading starts: at least 24 hours, at least 5000 landed touches, a dedicated RPC. the mainnet G + D is set from it. mainnet values: set at launch.
why nobody can pick the price or front run
- a request reads no price. the open, close and DUST MODE requests only lock margin and write
ts_star. - the settler has no choice of tick. a settle is accepted only when the tick's timestamp equals
ts_starexactly. - the tick did not exist at request time. the floor
G + D ≥ 2000putsts_starpast anything knowable at inclusion, for every clock lag up to 1825 ms. - an old tick cannot pull
ts_starearlier. the hwm never goes down. a stale tick can only fall back to the clock term. - a compromised signer cannot freeze requests. presented ticks must be within
max_future_msof the clock, so the hwm cannot be pushed far ahead. - no house discretion. the one fallback that lets a keeper pick a tick in a 2P window is off. its key is the zero key at
initialize. even if it were on, it is closed once the chain has recorded any tick at or afterts_star. - no free option on changes. a cancel, a take profit change and a limit activation all use the same future tick rule, so nobody changes an order against a tick they have already seen.
- a tick that is not there cannot be forced. if Pyth never produces the tick, the request waits. the owner can cancel after 30 s if the program never saw a tick at
ts_star, and after 10 minutes in any case.
the high water mark and the freshness gate
the program records the newest tick time it has seen. every settle, liquidation and touch_oracle can advance it. it never goes backward.
a second number, hwm_slot, records the slot where the hwm last advanced. only a strictly newer tick sets it. presenting the same tick again does not make the chain look fresh.
the gate: a request is refused with OracleStale unless slot − hwm_slot ≤ max_hwm_slots. the shipped value is 3 slots, bounds 1 to 10. it applies to request_open, request_dust_round, place_limit, request_close, set_trigger and cancel_order. a retired instrument is exempt for request_close.
the gate stops a stalled heartbeat from degrading T_req to the clock term. it is a liveness guard. the timing floor above is the defense.
to pass it, a client sends [ed25519, touch_oracle, request_*] in one transaction. only touch_oracle reads the price, so the request sees an hwm stamped in its own slot. K3 keeps the gate open for unbundled requests.
opens are also refused when the clock is more than 10 seconds past the hwm.
there is no recency gate on requests. the program only sees lower bounds of true time, so it cannot prove a tick is recent. the floor makes the age of a presented tick irrelevant.
the quality gate: confidence and publishers
opens and DUST MODE entries check the quality of the tick they settle on. closes, DUST MODE exits and liquidations never do.
conf_cbp = confidence / price × 1,000,000 (centi basis points)
limit = clamp(k × median(last 16 confidences), 3 bp, ceiling)kis 3.0 on devnet. bounds 1.5 to 10.0.- the ceiling is 30 bp on devnet. bounds 5 bp to 50 bp.
- the floor of the limit is 3 bp.
- the median needs 5 samples. before that only the ceiling applies.
- the ring holds the last 16 verified ticks that carried a confidence.
an open is voided when publishers < min_publishers or confidence > limit. the margin and the open interest are released and a Cancelled event is emitted.
a tick that omits confidence or publisher count makes the whole transaction fail with OracleInvalid. it never voids. the reason: Pyth signs whichever property subset the requester asks for, so the settler would otherwise choose which variant to present and could void any pending open.
a limit order never voids. on a dirty tick it waits.
the publisher floor
min_publishers is a parameter of each instrument, set by the owner within 1 to 64. the launch rule is ceil(0.75 × the measured median publisher count). measured medians were 27 for BTC and 25 for ETH, which gives 21 and 19.
| instrument | devnet value | mainnet value |
|---|---|---|
| BTC | 21 | 21 |
| ETH | 19 | 19 |
| SOL | 10 | set at launch, after at least 24 hours of measurement |
K6 watches the publisher count against a rolling one hour maximum and the rate of voided opens, and pages when they drift.
exact ticks only
the grid mode accepts a tick only if its time equals ts_star. a DUST MODE round settles two exact ticks, the entry at ts_open and the exit at ts_open + horizon, in one transaction with two signature entries.
retired instruments
if an instrument is retired, ticks stop mattering for it. positions close at the frozen price. see settlement and keepers for what a retire does to each kind of request.
