mainnet is not live yetdevnet beta is live at play.dust.bid
skip to content

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.

instrumentidPyth Pro feed idchannel
BTC01fixed rate, 50 ms (channel id 2)
ETH12fixed rate, 50 ms (channel id 2)
SOL26fixed 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 ​

  1. the transaction carries an ed25519 precompile instruction at index 0. it holds one signature entry for each signed update in the dust instruction.
  2. the dust instruction takes the signed message as bytes. the size limit is 512 bytes.
  3. the program calls the official verifier program pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt by CPI. the verifier checks the signature against its trusted signer set and charges the update fee, 1 lamport, to the instruction signer.
  4. 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.
  5. 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) × P

hwm is the newest tick time the program has recorded. clock_ms is the Solana clock in whole seconds times 1000.

symbolmeaningshipped valuebound
Ptick grid50 msone of 50, 100, 200, 400, 1000, and equal to the channel rate
Dsettle delay1000 ms1000 to 5000 ms
Gclock guard1000 ms0 to 3000 ms
G + Dthe floor2000 msno 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    = 1791669412000

if 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 + λ + Δmin

it 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.

quantityvaluewhere it comes from
L, in transaction clock lagp50 628 ms, p99 1184 ms, max 1293 ms, n = 891measured on devnet
λ, outside lead125 msmeasured against the fastest outside venue
Δmin50 msone grid step
needed1468 ms1293 + 125 + 50
compiled floor2000 msneeded plus about 500 ms of stress allowance
margin at the worst sample532 ms2000 − 1468
margin at the median1197 ms2000 − (628 + 125 + 50)
unsound aboveL = 1825 ms2000 − 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_star exactly.
  • the tick did not exist at request time. the floor G + D ≥ 2000 puts ts_star past anything knowable at inclusion, for every clock lag up to 1825 ms.
  • an old tick cannot pull ts_star earlier. 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_ms of 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 after ts_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)
  • k is 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.

instrumentdevnet valuemainnet value
BTC2121
ETH1919
SOL10set 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.