SDK reference
@otcdex/sdk is thin. Reads return decoded accounts, builders return instructions you sign yourself, and quotes are pure arithmetic with no network call.
Reading
| Function | Returns |
|---|---|
client.getConfig() | Protocol config: fee, pause state, authorities |
client.getQuoteMintConfig(mint) | Value floor and ceiling for a payment asset |
client.getListing(address) | One ask |
client.getListings(filter) | Asks, filtered by seller, token or payment asset |
client.getFillableListings(filter) | Asks a given buyer can fill right now |
client.getBid(address) | One bid |
client.getBids(filter) | Bids |
import { OtcClient } from '@otcdex/sdk';
const client = new OtcClient(connection, programId?);
await client.getConfig(); // fee bps, pause state, authorities
await client.getQuoteMintConfig(quoteMint); // limits for a payment asset
await client.getListing(address); // one ask, or null
await client.getListings(filter); // asks by seller / token / quote
await client.getFillableListings(filter); // asks this buyer can fill now
await client.getBid(address); // one bid, or null
await client.getBids(filter); // bidsPricing
| Function | Purpose |
|---|---|
quoteFill(listing, amount, feeBps, slippageBps) | Cost of buying from an ask, plus the bound to sign |
quoteBidFill(bid, amount, feeBps, slippageBps) | Proceeds from selling into a bid, plus the floor to sign |
planSweep(listings, target, feeBps, slippageBps) | Cheapest-first route across several asks, with price impact |
maxSellableIntoBid(bid, feeBps) | Largest sale a bid can absorb |
import { quoteFill, quoteBidFill } from '@otcdex/sdk';
// Buying from a resting ask
const q = quoteFill(listing, tokenAmount, feeBps, slippageBps);
// -> { buyerPays, buyerReceives, quoteFee, tokenFee, effectivePricePerToken, maxPricePerToken }
// Selling into a resting bid
const s = quoteBidFill(bid, tokenAmount, feeBps, slippageBps);
// -> { buyerPays, sellerReceives, protocolFee, minPricePerToken }| Name | Type | Required | Description |
|---|---|---|---|
| tokenAmount | bigint | required | Token base units to trade |
| feeBps | number | required | Read from getConfig(), do not hard-code |
| slippageBps | number | required | Headroom added to the signed bound only, not to the price paid |
Always pass the returned bound to the instruction builder. The bound is computed per token received after fees, which the listed price is not. A bound equal to the headline price always reverts.
Sweeping several orders
A single order rarely covers real size. planSweep sorts fixed-price resting orders by effective price, walks them until the target is met, and returns one leg per order. On a book of discrete OTC orders, price impact is simply the spread between the best leg and the volume-weighted average: there is no curve to integrate.
import { planSweep, maxSellableIntoBid } from '@otcdex/sdk';
const plan = planSweep(listings, targetAmount, feeBps, slippageBps);
plan.legs; // one fill per listing, cheapest first
plan.filledAmount; // tokens the route actually covers
plan.totalCost; // what the buyer pays, in payment base units
plan.bestPricePerToken; // cheapest leg
plan.averagePricePerToken; // volume-weighted average listed price
plan.impactBps; // bps the average exceeds the best leg
// Largest sale one bid can absorb
const size = maxSellableIntoBid(bid, feeBps);Legs respect each order's minimum fill size and the venue's per-order value limits. A leg that would fall below an order's minimum is skipped rather than shrunk, so plan.filledAmount can be less than the target. Check it before signing.
Batching
buildBasket settles several legs in one go. It quotes every leg, simulates each one independently, drops the ones that would fail, packs the survivors into transactions and returns both the transactions and the dropped legs.
import { buildBasket } from '@otcdex/sdk';
const { transactions, included, dropped, totalCost } = await buildBasket(
connection,
ctx, // wallet, feeBps, treasury accounts
plan.legs,
{ slippageBps: 50, maxFillsPerTx: 8 },
);
for (const { leg, reason } of dropped) {
// Surface these. They did not execute.
}Eight fills per transaction is the default, with nine the measured maximum for a mixed basket. The binding constraint is Solana's 64-account-lock limit, not transaction size or compute. Address lookup tables compress how an account is referenced but do not reduce how many the runtime must lock.
Passing simulate: false skips the per-leg round trip. Only do that when every leg is already validated, because one stale order then takes down its whole chunk.
Events
parseProgramEvents decodes a transaction's logs into typed events. It tracks program invocation frames, so a log line emitted by a different program is discarded even when it is byte-identical to a real one.
import { parseProgramEvents } from '@otcdex/sdk';
const events = parseProgramEvents(tx.meta.logMessages, programId);