architecture
dust is one Anchor program, one USDC vault and one $DUST mint. Around them sit roles that anyone can run: keepers that settle, liquidate and fill. A relayer pays gas, an indexer reads the events and a price feed draws the chart.
the program has 49 instructions, 6 account types, 34 events and 59 errors. it is built with Anchor 0.31.1.
the pieces
player wallet
| one signature, then a session key signs the bets
v
app ==> relayer (fee payer, builds the bundle) ==> Solana RPC
|
keepers K1 K2 K3 K4 K7 K7b (anyone can run) ========> |
v
+================================================================+
| dust program 49 instructions |
| Exchange Instrument x N UserAccount Position Request |
| QueueRing (the line) USDC vault $DUST mint (Token 2022) |
+================================================================+
^ |
| signed price tick, verified by CPI | events
| v
Pyth Pro verifier program indexer ==> feed, boards, cards
price feed (server side Pyth stream) ==> the chart in the app. it never settles a bet.
K6 watchdog reads the chain and the keepers, and signs nothing.| piece | what it is | what it can touch |
|---|---|---|
| dust program | one Anchor program, program id q8WWu9FefmPYtLrz4mKRBYVEX5apB4UmRW1F3TxmaW3 | all state below |
| USDC vault | a token account at the PDA ["vault"], token authority is the Exchange PDA | debited only by withdraw and claim_dev |
| $DUST mint | a Token-2022 mint at the PDA ["dust_mint"], 6 decimals, authority is the Exchange PDA, supply 0 | at launch $DUST lives in the ledger. the program has no instruction that mints it to a wallet |
| Pyth Pro verifier | the official verifier program pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt and its storage account | checks signatures on price ticks |
| keepers | PM2 processes that hold only SOL | call permissionless instructions |
| relayer | a service that signs as fee payer | pays fees and rent. never an authority over funds |
| indexer | reads finalized transactions, decodes events, serves an API | read only |
| price feed | a server that streams Pyth Pro prices for display | nothing on chain |
accounts
| account | seeds | size | holds |
|---|---|---|---|
| Exchange | ["exchange"] | 656 B | roles, the ledger buckets, fee parameters, genesis values, global limits and switches. sizes include the 8 byte discriminator |
| Instrument | ["instrument", id], id is one byte | 395 B | feed pin, timing, the confidence ring, impact and DUST MODE cells, open interest, caps, retire state |
| UserAccount | ["user", owner] | 313 B | USDC balance, $DUST, stake, referrer, session and agent keys, wallet limit counters |
| Position | ["pos", owner, id], id is a u64 in little endian | 237 B | a Core position or a resting limit order, with its take profit and stop loss slots |
| Request | ["req", owner, id] | 161 B | a pending open, close or DUST MODE round, with its target tick ts_star |
| QueueRing | a plain program owned account, created before initialize | 196,632 B | the FIFO payout line, 4096 entries |
instrument ids are PDA seeds, not Pyth feed ids. id 0 is BTC, 1 is ETH, 2 is SOL. the Pyth feed ids are 1, 2 and 6.
a DUST MODE round is one Request account and no Position. a limit order is a Position with status Resting.
where the money sits
every dollar in the vault is in exactly one bucket:
vault tokens = A + Mg + LP + SB + SF + SP + DF + RF| bucket | field | meaning |
|---|---|---|
| A | balances_total | sum of every UserAccount balance |
| Mg | margin_total | margin locked in pending and open bets, DUST MODE rounds and resting orders |
| LP | lp_cash | free cash of the house |
| SB | side_bucket | house cash earmarked for the line |
| SF | staker_pending | staker fees collected, not yet pushed |
| SP | staker_reserve | pushed staker rewards, not yet claimed |
| DF | dev_acc | dev fees, not yet claimed |
| RF | ref_pending | referral USDC owed, not yet claimed |
the line Q (queue_total) is a debt, not a bucket. it is never part of the vault. K6 checks the identity once a minute.
all user USDC is a ledger entry. the vault token account moves only in deposit, withdraw and claim_dev.
how a bet moves
every price event is two steps: a request, then a settle.
- request. the program locks margin, reserves open interest and writes a target tick time
ts_star. it reads no price. - settle. anyone presents the signed tick whose timestamp is exactly
ts_star. the program verifies it and fills at that price.
| action | request | settle |
|---|---|---|
| open | request_open | settle_open |
| close | request_close | settle |
| DUST MODE round | request_dust_round | settle_dust_round, with the entry tick and the exit tick in one transaction |
| bust | none | liquidate, with any tick past the bust price |
| limit order | place_limit | fill_order, at exactly the limit price |
| take profit, stop loss | set_trigger | execute_trigger |
the timing rule behind ts_star is in pricing oracle. the rules for who settles and what it pays are in settlement and keepers.
the keepers
every instruction a keeper sends has no role check. any wallet can send it. keepers hold only SOL.
| role | process | instructions | needs |
|---|---|---|---|
| K1 settle | dust-k1 | settle_open, settle, settle_dust_round | SOL, a Pyth Pro key, a tick archive |
| K2 liquidate | dust-k2 | liquidate | SOL and a UserAccount, because the bounty is credited there |
| K3 heartbeat | dust-k3 | touch_oracle | SOL. a touch with one Pyth update costs 10,000 lamports |
| K4 distribute | dust-k4 | harvest, push_fees, claim_dev | SOL |
| K6 watchdog | dust-k6 | none | no key. it only reads |
| K7 and K7b order executor | dust-k7, dust-k7b | fill_order, execute_trigger, expire_order | SOL and a UserAccount for the trigger bounty. two instances with two different keys |
K1 archives every tick it sees, so any ts_star can be served. before it settles a close it checks the archive for a bust or a trigger crossing in the bet's window. K3 spaces its touches at least 200 ms apart while anything is live, and 1 to 8 s apart when idle.
the numbering keeps K5 for the risk role. that role is a key that tunes caps, not a process.
the relayer
the relayer is a fee payer. it builds the transaction, signs as payer, sends it and reports the status.
- first trade in one bundle. a new player with no SOL signs once. the transaction is
[ed25519, set_referrer, deposit, register_session_key, touch_oracle, request_open]. it is 1,141 bytes, the limit is 1,232. - what it pays. 2,240,280 lamports of rent for each new UserAccount, which is never refunded. 15,001 lamports for each user signed bundled request.
- what it cannot do. the payer is never an authority, a seed or a destination, and no instruction credits it. the owner's signature authorises every movement of the owner's funds.
- it is not a gate. requests are permissionless. the relayer pays fees, nothing more. you can pay your own.
the indexer
the indexer reads finalized transactions, decodes the events with the SDK, stores them in SQLite and serves an HTTP and WebSocket API: feed, boards, house numbers and trade cards.
- finalized transactions are the only writer. a confirmed preview is pushed to WebSocket clients and never stored.
- one database per program id. a database stamped with another program is refused.
- it is a view. the chain is the record. everything it holds can be rebuilt from the raw log.
the price feed
the price feed streams Pyth Pro prices for the chart and serves history bars. the browser never talks to Pyth. the Pyth Pro key stays on the server.
a bet never fills on the feed. a fill uses only the signed tick that the program verifies on chain.
what is not in the program
no raw action passthrough, no relayer gated trading, no manual credit, no sweeper, and no admin signature that can move a user balance. the full list of powers is in security.
