Quick start

An empty project reaches a confirmed devnet swap in five short steps, with the SDK resolving every account.

Updated Nov 9, 2026

Install

npm install --save-exact @omnipair/dusk-sdk@2.21.0-next.20261109 @coral-xyz/anchor @solana/web3.js

The package is strict ESM and published to npm, with devnet builds under the next tag. Pin an exact version, as these commands do: the app pins 2.21.0-next.20261109, and the IDL inside the package is what your transactions are built against.

Create a client

client.ts
import { AnchorProvider } from "@coral-xyz/anchor"
import { Connection } from "@solana/web3.js"
import { Dusk } from "@omnipair/dusk-sdk"
 
const connection = new Connection(process.env.SOLANA_RPC_URL!, "confirmed")
const provider = new AnchorProvider(connection, wallet, {
  commitment: "confirmed",
})
 
export const dusk = new Dusk({
  provider,
  indexerBaseUrl: "https://api.indexer.omnipair.fi/api/v1",
})

A Dusk instance is split by source of truth: dusk.write builds instructions and transactions, dusk.get reads accounts and runs typed previews over RPC, and dusk.fetch calls the indexer for history. The raw Anchor program is on dusk.program.

Find a market

Markets are program-derived addresses of the base mint, the quote mint and the creator's parameter hash. During the beta there is one market, VETO / META; the app shows its address on the market page.

market.ts
import { PublicKey } from "@solana/web3.js"
 
const market = new PublicKey(
  "ErW95UhxqJ3zF1KodfVJQodnDAb6p1aPHNCEywMwMjW6",
)
const account = await dusk.get.market(market)
 
console.log(account.baseSide.assetMint.toBase58())
console.log(account.quoteSide.assetMint.toBase58())
console.log(account.config.swapFeeBps)

To derive an address for a market you know the mints of, use dusk.get.pda.market(baseMint, quoteMint, paramsHash); the hash comes from deriveMarketParamsHash with the creator's namespace and label. Never sort the mints yourself.

Preview a swap

Quotes are on-chain previews: the SDK simulates the preview_swap instruction and decodes its typed return data, so the number you show is the number the program would execute.

quote.ts
import { BN } from "@coral-xyz/anchor"
 
const quote = await dusk.get.previewSwap({
  market,
  assetInMint: account.quoteSide.assetMint,
  assetOutMint: account.baseSide.assetMint,
  exactAssetIn: new BN(5_000_000), // 5 USDC, six decimals
})
 
quote.amountOut // raw base atoms after fees
quote.netAmountOut // received, after the output token's transfer fee
quote.baseFee // fee components, in the fee asset
quote.divergenceFee
quote.volatilityFee

Amounts are raw integer atoms. Apply a slippage tolerance to netAmountOut to get the minAssetOut the recipient must actually receive.

Send the swap

swap.ts
// Accept up to 3% less than the quote.
const minAssetOut = quote.netAmountOut.muln(97).divn(100)
 
const transaction = await dusk.write.buildSwapTransaction({
  market,
  trader: wallet.publicKey,
  assetInMint: account.quoteSide.assetMint,
  assetOutMint: account.baseSide.assetMint,
  exactAssetIn: 5_000_000n,
  minAssetOut: minAssetOut.toString(), // bigint, number or string
})
 
const signature = await provider.sendAndConfirm(transaction)

buildSwapTransaction derives the reserve vaults, your associated token accounts and the token programs from the market and the two mints, and TypeScript checks every field. It also fetches the market: when either hLP vault has supply or residual exposure it prepends the five accounts the program needs to reconcile the vaults, exactly once, before any Token-2022 transfer-hook extras you pass as remainingAccounts. buildSwapInstruction returns the bare instruction.

Where to go next