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 internal log_trade_internal Pool status required: LIVE


Modes & Pool Types

Trade modes

Mode
Semantics

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)

Value
Quote
Creator fee?

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

#
Name
Writable
Seeds / Constraints
Description

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 with is_buy = false and updated fields.


TypeScript: build the 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

Every successful sell triggers an internal instruction with:

Field
Type
Description

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 increase token_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