Selling: asks
An ask is a resting sell. Tokens sit in escrow at a price you set, and buyers take from it until it is empty or you close it.
Create
import { createListing, PricingKind } from '@otcdex/sdk';
const ix = createListing({
seller: wallet.publicKey,
sellerTokenAccount,
tokenMint,
tokenProgram,
quoteMint, // USDC or wSOL
seed: BigInt(Date.now()), // any u64 you have not used before
amount: 1_000_000_000n, // base units to escrow
minFillAmount: 1_000_000n, // smallest permitted purchase
expiryTs: BigInt(Math.floor(Date.now() / 1000) + 86_400),
allowedBuyer: null, // or a PublicKey for a private deal
pricing: { kind: PricingKind.Fixed, pricePerToken: 1_000_000n },
});| Name | Type | Required | Description |
|---|---|---|---|
| seed | bigint | required | Nonce forming part of the order address. Reuse fails. |
| amount | bigint | required | Token base units to escrow |
| minFillAmount | bigint | required | Smallest purchase, waived when a fill clears the order |
| expiryTs | bigint | required | Unix seconds. Between now+60s and now+90 days |
| allowedBuyer | PublicKey | null | required | Restrict to one wallet |
| pricePerToken | bigint | required | Quote base units per whole token |
Pricing example. To sell at 1 USDC per token, with USDC at 6 decimals, pricePerToken is 1_000_000n. The price is always per whole token, never per base unit.
Re-price
import { updatePrice } from '@otcdex/sdk';
const ix = updatePrice({
signer: wallet.publicKey, // the seller, OR the order's price authority
listing: listingAddress,
quoteMint,
pricing: { kind: PricingKind.Fixed, pricePerToken: 1_200_000n },
});Costs one transaction fee. Escrow is untouched, so this is the cheap way to track a moving market: poll a price feed and re-peg on drift rather than cancelling and re-creating.
Track the market
A market-priced ask stores no price at all. It names a pool and an offset, and the price is read from that pool's reserves inside the transaction that settles the fill. Nothing is stored, nothing goes stale, and no keeper has to maintain it.
import { createListing, tracked } from '@otcdex/sdk';
const ix = createListing({
seller: wallet.publicKey,
sellerTokenAccount,
tokenMint,
tokenProgram,
quoteMint,
seed,
amount: 1_000_000_000n,
minFillAmount: 0n,
expiryTs,
allowedBuyer: null,
pricing: tracked({
pool, // the constant-product pool to price from
discountBps: -500, // 5% below market. Negative is below.
maxVelocityBps: 0, // ignored by the program, pass 0
anchorPrice: spot, // your market price now, the floor's reference
}),
minPrice: (spot * 9_000n) / 10_000n, // REQUIRED: never fills below this
// maxPrice: optional ceiling, omit for none
});minPrice is required on a market-priced ask and is the seller's only protection: the order never fills below it, whatever the pool does. The program refuses a floor more than 25% below anchorPrice, and refuses a listing with no floor at all.
anchorPrice prices nothing. Pass the current market price: its one job is being the reference the floor is measured against. maxVelocityBps is ignored, so pass 0.
Cancel
import { cancelListing } from '@otcdex/sdk';
const ix = cancelListing({
closer: wallet.publicKey,
seller: listing.seller,
listing: listing.address,
tokenMint: listing.tokenMint,
tokenProgram: listing.tokenProgram,
sellerTokenAccount,
});Returns the unsold remainder plus the account rent. Works even while the protocol is paused.