SDK
@dust/sdk is the TypeScript client for the dust program. version 0.3.0. ESM, Node 22 or newer.
install: published at launch. the package is not on npm yet. the source builds with npm install and npm run build.
the SDK depends on two packages, pinned: @coral-xyz/anchor 0.31.1 and @solana/web3.js 1.98.4. use one copy of web3.js in your tree. two copies break instanceof PublicKey.
it is offline first and key free. the one exception is PythProClient. it is server side only and takes its API key from the caller. nothing in the package logs, embeds in a URL or stores a secret.
what is in the box
| module | what it gives you |
|---|---|
| constants | program ids, seeds, enums, scales, the compiled limits a client needs, Pyth constants |
| pda | exchangePda, vaultPda, dustMintPda, instrumentPda, userPda, positionPda, requestPda, programDataPda, pythStoragePda, associatedTokenAddress |
| accounts | decoders for the six account types and the queue ring, plus views for staking, referrals and wallet limits, and vaultIdentityHolds |
| events | parseDustEvents and eventsOf. all 34 events typed |
| errors | DustErrors with all 59 codes, parseDustErrorFromLogs, isDustError |
| timing | tReq, tsStar, planTs, planTsBundled, cancelAllowed, the soundness helpers |
| math | bit exact ports of the program's bustPrice, coreWin, coreLoss, winFee, splitFee, refSplit |
| orders, limits | ports of the order rules and the per wallet limit rules |
| validate | error prediction in the program's own check order: checkRequestOpen, checkPlaceLimit, checkRequestDustRound, checkFillOrder, checkSetTrigger and more |
| pyth | message parsing, the ed25519 precompile builder, and PythProClient for the exact tick at a time |
| ix | DustIx: a synchronous builder for every one of the 49 instructions |
| tx | DustTx: bundled transactions with the ed25519 instruction at index 0, size checks, signing |
| agent | agent key and session key registration, checkMarginRequest, checkCloseRequest |
| client | DustClient: typed reads, bundled request helpers and settle planners |
the IDL is also exported at @dust/sdk/idl. the SDK build checks that its copy matches the program's IDL byte for byte.
read the exchange
DustClient.connect takes any object with getAccountInfo. a web3.js Connection works as is. it reads the Pyth treasury from the storage account, so it is correct on devnet and on mainnet.
ts
import { Connection } from '@solana/web3.js';
import { DustClient } from '@dust/sdk';
const conn = new Connection(process.env.SOLANA_RPC_URL!); // a devnet RPC
const dust = await DustClient.connect(conn);
const ex = await dust.getExchange();
console.log('house cash', ex.ledger.lpCash); // bigint, micro USDC
console.log('the line', ex.ledger.queueTotal);
console.log('tracked LP', ex.ledger.trackedLp);
const btc = await dust.getInstrument(0); // 0 BTC, 1 ETH, 2 SOL
console.log('hwm', btc.oracleTsHwm, 'delay', btc.delayMs, 'guard', btc.guardMs);
const check = await dust.checkVaultIdentity(); // vault tokens equal the ledger buckets
console.log(check.ok);the decoded accounts use camelCase names and bigint for numbers. other reads: getUser(owner), getPosition(owner, id), getRequest(owner, id), getQueue(), getClock(), nextId(owner).
open a bet
a request is one transaction: [ed25519, compute budget, touch_oracle(latest tick), request_open]. the touch makes the request self refreshing. requestOpenBundled builds it and tells you the ts* the program will assign.
ts
import { Keypair } from '@solana/web3.js';
import { DustClient, PythProClient, Side, USDC, sendBuiltTransaction } from '@dust/sdk';
const pyth = new PythProClient({ apiKey: process.env.PYTH_PRO_API_KEY! }); // server side only
const { built, plan } = await dust.requestOpenBundled(pyth, {
authority: user.publicKey, // the owner, a session key or an agent key
payer: relayer.publicKey, // pays fees and rent. can be the user
owner: user.publicKey,
instrumentId: 0,
side: Side.Long, // 0 long (MOON), 1 short (DUST)
leverage: 100,
margin: 50n * USDC,
tpPrice: 0n, // optional take profit and stop loss, 0 for none
slPrice: 0n,
});
const sig = await sendBuiltTransaction(conn, built, [relayer, user]);
console.log('price locks at', plan.tsStarMs);- a take profit or stop loss in the request is armed at the fill. an invalid one is dropped and the bet still opens.
- if the latest tick is older than the chain's hwm, the helper sends the request plain when the hwm is still fresh. otherwise it throws
StaleHwmError, which says how long to wait. - limit orders, closes, triggers and cancels have the same bundled helpers:
placeLimitBundled,requestCloseBundled,setTriggerBundled,cancelOrderBundled,requestDustRoundBundled.
settle it
anyone can settle. once the exact tick at ts* exists, settleRequest picks settle_open, settle or settle_dust_round from the request kind, fetches the tick or ticks, and fills in the referrer account for you.
ts
const exchange = await dust.getExchange();
const request = (await dust.getRequest(user.publicKey, 0n))!;
const position = (await dust.getPosition(user.publicKey, 0n))!;
const settle = await dust.settleRequest(pyth, {
settler: keeper.publicKey, // pays the Pyth fee and the transaction fee
request,
position,
queue: exchange.queue,
});
await sendBuiltTransaction(conn, settle, [keeper]);the position and request of a Core bet share one id. a DUST MODE round has a request and no position, so pass the request only. the settler needs a UserAccount only if the close lands on a busted price.
keeper helpers: findLiquidatingTick, findTriggeringTick, findFillingTick, fillOrderTx, executeTriggerTx. settlement and keepers says what each pays.
read what happened
ts
import { eventsOf, parseDustErrorFromLogs } from '@dust/sdk';
const tx = await conn.getTransaction(sig, { maxSupportedTransactionVersion: 0 });
const logs = tx?.meta?.logMessages ?? [];
for (const e of eventsOf(logs, 'Closed')) {
console.log(e.data.pnl, e.data.via, e.data.exitPx); // via: 0 settle, 1 take profit, 2 stop loss, 3 frozen
}
console.log(parseDustErrorFromLogs(logs)); // { name, code, ... } for a failed transactionclassify a stop loss by Closed.via, never by comparing the exit price with the trigger. a stop closes at the tick, not at its trigger.
predict an error before you send
validate mirrors the program's checks in the program's order. it gives the exact error name and code, so a refusal costs nothing.
ts
import { checkFillOrder } from '@dust/sdk';
const verdict = checkFillOrder({ ex, inst, user, pos, ctx: { nowS, slot } }, { tsMs: tick.tsMs, price });
if (!verdict.ok) console.log(verdict.error.name, verdict.error.code); // for example OrderWindow 6051no wallet users
deposit and set_referrer take a separate payer. a wallet with no SOL can sign once while the relayer pays the rent. payer defaults to owner.
ts
dx.ix.setReferrer({ owner, payer: relayer, referrer });
dx.ix.deposit({ owner, payer: relayer, ownerAta, usdcMint, amount: 100n * USDC, referrer });AI agent keys
registerAgentKeyIx builds the owner's registration of an agent key with its caps. see AI agents.
tests
the SDK has unit tests and end to end tests that run the production program in LiteSVM, with vectors computed by the Rust code. npm run test:live runs one test against the real Pyth Pro API and needs a key in the environment.
