sell
Sell launchpad tokens back to the pool for SOL or WSOL. Supports exact-in (sell X tokens, receive ≥ Y quote) and exact-out (receive exactly Y quote, sell ≤ X tokens), applies protocol/creator fees, updates pool state, and emits a structured trade log (log_trade_internal
) for indexers.
Program: Tradersdex Curve (Launchpad) Instruction:
sell
Emits:TradeEvent
via internallog_trade_internal
Pool status required:LIVE
Modes & Pool Types
Trade modes
Exact-in (is_exact_in = true
)
Sell exact token_amount
; must receive ≥ min_sol_out
(after fees)
Exact-out (is_exact_in = false
)
Receive exact min_sol_out
(after fees); must sell ≤ token_amount
Pool types (on-chain pool_type
)
pool_type
)0 (Basic)
SOL
❌
1 (WithCreator)
SOL
✅
2 (WithCustomQuote)
WSOL
❌
3 (Full)
WSOL
✅
QuoteVault rule:
WSOL pools (2/3):
quote_vault = ATA(NATIVE_MINT, pool)
SOL pools (0/1):
quote_vault = pool
(lamports held on pool PDA)
Arguments
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct SellArgs {
/// Tokens to sell (exact-in) or max tokens you're willing to sell (exact-out)
pub token_amount: u64,
/// Minimum SOL/WSOL you must receive (exact-in) or exact SOL/WSOL you want (exact-out)
pub min_sol_out: u64,
/// true => exact-in, false => exact-out
pub is_exact_in: bool,
}
Accounts
0
pool
✅
["pool", mint]
, owner = program
Pool PDA; custom-deserialized
1
mint
Sale token mint
2
quote_mint
For WSOL must be NATIVE_MINT
For SOL may be Pubkey::default()
3
token_vault
✅
ATA(mint
, pool
)
Pool base token vault
4
quote_vault
WSOL: ATA(NATIVE_MINT, pool
) • SOL: pool
Pool quote holding
5
user_token_account
✅
ATA(mint
, payer
)
Seller’s token ATA (source)
6
user_quote_account
WSOL: ATA(NATIVE_MINT, payer
)
Seller’s WSOL ATA (dest)
7
fee_recipient
✅
Must equal one of PDA(["protocol_fee_vault",[i]])
Protocol fee receiver
8
payer
✅
Seller
9
token_program
= anchor_spl::token::ID
SPL Token
10
associated_token_program
For ATA ops
11
system_program
For SOL transfers
12
tradersdex_curve
= crate::ID
Self-check
13
event_authority
["event_authority"]
Signs inner log ix
14
token_creator
Must equal pool creator for fee pools
Creator pubkey
15
token_creator_ata
ATA(NATIVE_MINT, token_creator
)
Needed to activate creator fee (WSOL path)
Behavior
Math
Uses virtual reserves
vr_quote
,vr_token
.Exact-in (sell X tokens):
Gross quote out (before fee):
gross = floor(tokens_in * vr_quote / (vr_token + tokens_in))
Fees are output-side:
total_fee = ceil(gross * fee_bps / 10000)
Net to user:
net = gross - total_fee
(must be ≥min_sol_out
)
Exact-out (receive exact Y quote after fees):
gross = ceil( net * 10000 / (10000 - fee_bps) )
Tokens required:
tokens_in = floor(gross * vr_token / (vr_quote - gross))
Must satisfy
tokens_in ≤ token_amount
Indexer
Emits inner
log_trade_internal
withis_buy = false
and updated fields.
TypeScript: build the sell
instruction (no send)
sell
instruction (no send)This helper derives all accounts and returns the TransactionInstruction
for sell
.
It applies your quoteVault rule automatically.
import { Program, BN } from "@coral-xyz/anchor";
import {
PublicKey,
SystemProgram,
TransactionInstruction,
} from "@solana/web3.js";
import {
NATIVE_MINT,
getAssociatedTokenAddressSync,
} from "@solana/spl-token";
import {
derivePoolPda,
deriveEventAuthority,
deriveProtocolFeeVault,
ZERO_PUBKEY,
} from "./utils";
/**
* Build 'sell' instruction (does NOT send).
*/
export async function buildSellIx(
program: Program,
args: {
payer: PublicKey; // seller
mint: PublicKey; // sale token mint
poolType: 0 | 1 | 2 | 3; // 0/1=SOL, 2/3=WSOL
tokenAmount: bigint; // exact tokens in (exact-in) or max tokens in (exact-out)
minSolOut: bigint; // min net SOL/WSOL (exact-in) or exact net out (exact-out)
isExactIn: boolean;
feeVaultIndex: number;
tokenCreator: PublicKey;
}
): Promise<{ ix: TransactionInstruction }> {
const isWSOL = args.poolType === 2 || args.poolType === 3;
const pool = derivePoolPda(program.programId, args.mint);
// Pool base token vault
const tokenVault = getAssociatedTokenAddressSync(args.mint, pool, true);
// Quote vault rule:
// - WSOL: ATA(NATIVE_MINT, pool)
// - SOL: pool (lamports on PDA)
const quoteVault = isWSOL
? getAssociatedTokenAddressSync(NATIVE_MINT, pool, true)
: pool;
// Seller accounts
const userTokenAccount = getAssociatedTokenAddressSync(args.mint, args.payer);
const userQuoteAccount = getAssociatedTokenAddressSync(NATIVE_MINT, args.payer);
// Fee & misc
const feeRecipient = deriveProtocolFeeVault(args.feeVaultIndex, program.programId);
const quoteMint = isWSOL ? NATIVE_MINT : ZERO_PUBKEY;
const tokenCreatorAta = getAssociatedTokenAddressSync(NATIVE_MINT, args.tokenCreator);
const ix = await program.methods
.sell({
tokenAmount: new BN(args.tokenAmount.toString()),
minSolOut: new BN(args.minSolOut.toString()),
isExactIn: args.isExactIn,
})
.accounts({
pool,
mint: args.mint,
quoteMint,
tokenVault,
quoteVault,
userTokenAccount,
userQuoteAccount,
feeRecipient,
payer: args.payer,
tokenProgram: new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
associatedTokenProgram: new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"),
systemProgram: SystemProgram.programId,
tradersdex_curve: program.programId,
eventAuthority: deriveEventAuthority(program.programId),
tokenCreator: args.tokenCreator,
tokenCreatorAta,
})
.instruction();
return { ix };
}
Usage
Exact-in (sell X tokens, receive ≥ Y quote):
const { ix } = await buildSellIx(program, {
payer,
mint,
poolType: 3, // 0/1=SOL, 2/3=WSOL
tokenAmount: 50_000n, // exact tokens to sell
minSolOut: 1_000_000n, // min net lamports/WSOL (slippage guard)
isExactIn: true,
feeVaultIndex: 0,
tokenCreator,
});
Exact-out (receive exactly Y quote, sell ≤ X tokens):
const { ix } = await buildSellIx(program, {
payer,
mint,
poolType: 0,
tokenAmount: 60_000n, // max tokens you’re willing to sell
minSolOut: 1_000_000n, // exact net quote you want
isExactIn: false,
feeVaultIndex: 0,
tokenCreator,
});
Indexer: log_trade_internal
log_trade_internal
Every successful sell triggers an internal instruction with:
pool
Pubkey
Pool address
trader
Pubkey
Seller
mint
Pubkey
Token mint
sol_amount
u64
Net SOL/WSOL to seller (after fees)
token_amount
u64
Tokens sold (debited from seller)
fee_amount
u64
Total fee (protocol + creator) on output
creator_fee
u64
Creator fee portion
protocol_fee
u64
Protocol fee portion
is_buy
bool
false
for sell
vr_quote
/ vr_token
u64
Updated virtual reserves
raised_quote
/ sold_supply
u64
Updated totals
timestamp
i64
Block time (seconds)
Troubleshooting
InvalidWsolAccount (WSOL pools): ensure
quote_mint == NATIVE_MINT
quote_vault == ATA(NATIVE_MINT, pool)
user_quote_account == ATA(NATIVE_MINT, payer)
For creator-fee pools,
token_creator_ata == ATA(NATIVE_MINT, token_creator)
(and initialized to activate creator fee)
InvalidFeeRecipient: derive one of
protocol_fee_vault[i]
.SlippageExceeded: raise
min_sol_out
tolerance (exact-in) or increasetoken_amount
cap (exact-out).InsufficientLiquidity: pool doesn’t have enough quote to pay out (SOL path checks pool lamports; WSOL path checks quote_vault balance).
Last updated