Skip to content

Sell all of a coin above a floor ​

pump.fun

Status: Tested locally in LiteSVM against pump.fun's bonding-curve program and live curves copied from mainnet; not yet run on devnet or mainnet.

Cost: Ballista's own work took 4,925 of the tested transaction's 71,505compute units; pump.fun took the rest. Ballista charges no fee; see what it costs.

What it does ​

Sells everything in a pump.fun coin's token account back to its bonding curve, and fails unless the seller receives at least minSolOut lamports.

pump.fun's sell takes the amount to sell, fixed when you sign. If a buy or a transfer lands before your sale, the balance has changed: the sale leaves the difference behind, or fails. The template reads the balance while the transaction runs, and sells exactly that.

The template:

  1. requires the seller to own the token account (sellsTheSellersOwnTokens). pump.fun sells from any account the seller can move tokens out of, including one it is only a delegate on;
  2. requires the account to hold mint (holdsTheCurvesCoin). pump.fun derives the curve from mint and refuses any other;
  3. requires the curve not to have graduated (curveNotGraduated), by its complete flag. A graduated coin trades on PumpSwap, and pump.fun would refuse the sale with its own BondingCurveComplete (6005);
  4. reads the balance and requires it to be above 0 (hasTokensToSell);
  5. sells exactly that balance;
  6. requires the seller's lamports to have grown by at least minSolOut (receivedAtLeastMinSolOut): what arrived, after pump.fun's and the creator's fees.

pump.fun's own min_sol_output is left at 0. The last check covers it, on what actually arrived, and a failure names its step.

It does not guard against a floor set too low. minSolOut is yours to set: quote the sale from the curve's reserves and pump.fun's fees, less a tolerance.

Template ​

ts
import {
  SYSTEM_PROGRAM_ADDRESS_BYTES,
  TOKEN_2022_PROGRAM_ADDRESS_BYTES,
  account,
  compileTemplate,
  data,
  defineTemplate,
  expression,
  step,
} from '@jac0xb/ballista';
import {
  PUMP_BONDING_CURVE,
  PUMP_FEES,
  PUMP_FUN,
  PUMP_FUN_SELL,
  TOKEN_ACCOUNT_AMOUNT_OFFSET,
  TOKEN_ACCOUNT_LENGTH,
  TOKEN_ACCOUNT_MINT_OFFSET,
  TOKEN_ACCOUNT_OWNER_OFFSET,
  addressBytes,
} from './shared.js';

const seller = account.fixed('seller');
const sellerTokenAccount = account.fixed('sellerTokenAccount');
const sellersLamports = expression.accountField(seller, 'lamports');

export const pumpFunSellAll = defineTemplate({
  inputs: {
    /** The least the sale must pay the seller, in lamports, after every fee pump.fun takes. */
    minSolOut: { type: 'u64' },
  },
  accounts: {
    pumpProgram: { executable: true, address: addressBytes(PUMP_FUN) },
    global: {},
    /** An ordinary fee recipient, or a reserved one for a mayhem-mode coin. */
    feeRecipient: { writable: true },
    /** pump.fun derives the curve from this mint, and refuses a curve of any other. */
    mint: {},
    /** Owner-pinned, so `complete` is read from an account pump.fun wrote. */
    bondingCurve: {
      writable: true,
      owner: addressBytes(PUMP_FUN),
      minDataLength: PUMP_BONDING_CURVE.minLength,
    },
    curveTokenAccount: { writable: true },
    /** Sold whole. Must belong to the seller and hold `mint`. */
    sellerTokenAccount: {
      writable: true,
      owner: TOKEN_2022_PROGRAM_ADDRESS_BYTES,
      minDataLength: TOKEN_ACCOUNT_LENGTH,
    },
    seller: { signer: true, writable: true },
    systemProgram: { executable: true, address: SYSTEM_PROGRAM_ADDRESS_BYTES },
    creatorVault: { writable: true },
    /** pump.fun mints every coin `create_v2` makes with Token-2022. */
    tokenProgram: { executable: true, address: TOKEN_2022_PROGRAM_ADDRESS_BYTES },
    eventAuthority: {},
    feeConfig: {},
    feeProgram: { executable: true, address: addressBytes(PUMP_FEES) },
    bondingCurveV2: {},
    buybackFeeRecipient: { writable: true },
  },
  steps: [
    // A delegate approved on someone else's account could otherwise sell their balance.
    step.require(
      expression.equal(
        expression.accountData(sellerTokenAccount, TOKEN_ACCOUNT_OWNER_OFFSET, 'pubkey'),
        expression.accountField(seller, 'key'),
      ),
      'sellsTheSellersOwnTokens',
    ),
    step.require(
      expression.equal(
        expression.accountData(sellerTokenAccount, TOKEN_ACCOUNT_MINT_OFFSET, 'pubkey'),
        expression.accountField(account.fixed('mint'), 'key'),
      ),
      'holdsTheCurvesCoin',
    ),
    // A graduated coin trades on PumpSwap. Say so here, before pump.fun refuses the sale with its
    // own `BondingCurveComplete` (6005).
    step.require(
      expression.not(expression.accountData(account.fixed('bondingCurve'), PUMP_BONDING_CURVE.complete, 'bool')),
      'curveNotGraduated',
    ),
    // Whatever the account holds when the transaction runs, not what it held at signing.
    step.let('balance', expression.accountData(sellerTokenAccount, TOKEN_ACCOUNT_AMOUNT_OFFSET, 'u64'), 'readBalance'),
    step.require(expression.greaterThan(expression.variable('balance'), expression.u64(0)), 'hasTokensToSell'),
    step.let('lamportsBefore', sellersLamports, 'readLamportsBefore'),
    step.invoke({
      program: account.fixed('pumpProgram'),
      accounts: [
        { account: account.fixed('global'), signer: false, writable: false },
        { account: account.fixed('feeRecipient'), signer: false, writable: true },
        { account: account.fixed('mint'), signer: false, writable: false },
        { account: account.fixed('bondingCurve'), signer: false, writable: true },
        { account: account.fixed('curveTokenAccount'), signer: false, writable: true },
        { account: sellerTokenAccount, signer: false, writable: true },
        { account: seller, signer: true, writable: true },
        { account: account.fixed('systemProgram'), signer: false, writable: false },
        { account: account.fixed('creatorVault'), signer: false, writable: true },
        { account: account.fixed('tokenProgram'), signer: false, writable: false },
        { account: account.fixed('eventAuthority'), signer: false, writable: false },
        { account: account.fixed('pumpProgram'), signer: false, writable: false },
        { account: account.fixed('feeConfig'), signer: false, writable: false },
        { account: account.fixed('feeProgram'), signer: false, writable: false },
        { account: account.fixed('bondingCurveV2'), signer: false, writable: false },
        { account: account.fixed('buybackFeeRecipient'), signer: false, writable: true },
      ],
      data: [
        data.literal(PUMP_FUN_SELL),
        data.encode('u64', expression.variable('balance')),
        // pump.fun's own floor, left at 0: the check below measures what actually arrived.
        data.encode('u64', expression.u64(0)),
      ],
      label: 'sellEverything',
    }),
    // The seller's lamports after the sale, less before: the price, net of pump.fun's and the
    // creator's fees.
    step.require(
      expression.greaterThanOrEqual(
        expression.subtract(sellersLamports, expression.variable('lamportsBefore')),
        expression.input('minSolOut'),
      ),
      'receivedAtLeastMinSolOut',
    ),
  ],
});

export const compiled = compileTemplate(pumpFunSellAll);
rs
/// Sell a whole balance of a pump.fun coin on its bonding curve, with a floor on the SOL received.
pub fn pump_fun_sell_all() -> Template {
    Template::new()
        // The least the sale must pay the seller, in lamports, after every fee pump.fun takes.
        .input("minSolOut", Type::U64)
        .account("pumpProgram", account::program(PUMP_FUN))
        .account("global", account::readonly())
        .account("feeRecipient", account::writable())
        // pump.fun derives the curve from this mint, and refuses a curve of any other.
        .account("mint", account::readonly())
        .account(
            "bondingCurve",
            account::writable()
                .owner(PUMP_FUN)
                .min_data_length(PUMP_CURVE_MIN_LENGTH),
        )
        .account("curveTokenAccount", account::writable())
        // Sold whole. Must belong to the seller and hold `mint`.
        .account("sellerTokenAccount", token_2022_account())
        .account("seller", account::signer().writable())
        .account("systemProgram", account::program(SYSTEM_PROGRAM_ID))
        .account("creatorVault", account::writable())
        .account("tokenProgram", account::program(TOKEN_2022_PROGRAM_ID))
        .account("eventAuthority", account::readonly())
        .account("feeConfig", account::readonly())
        .account("feeProgram", account::program(PUMP_FEES))
        .account("bondingCurveV2", account::readonly())
        .account("buybackFeeRecipient", account::writable())
        // A delegate approved on someone else's account could otherwise sell their balance.
        .step(
            step::require(
                account_data(
                    "sellerTokenAccount",
                    TOKEN_ACCOUNT_OWNER_OFFSET,
                    ReadType::Pubkey,
                )
                .eq(key("seller")),
            )
            .label("sellsTheSellersOwnTokens"),
        )
        .step(
            step::require(
                account_data(
                    "sellerTokenAccount",
                    TOKEN_ACCOUNT_MINT_OFFSET,
                    ReadType::Pubkey,
                )
                .eq(key("mint")),
            )
            .label("holdsTheCurvesCoin"),
        )
        // A graduated coin trades on PumpSwap; say so before pump.fun refuses the sale.
        .step(
            step::require(account_data("bondingCurve", PUMP_CURVE_COMPLETE, ReadType::Bool).not())
                .label("curveNotGraduated"),
        )
        // Whatever the account holds when the transaction runs.
        .step(step::let_("balance", balance_of("sellerTokenAccount")).label("readBalance"))
        .step(step::require(var("balance").gt(u64(0))).label("hasTokensToSell"))
        .step(step::let_("lamportsBefore", lamports("seller")).label("readLamportsBefore"))
        .step(
            step::invoke("pumpProgram")
                .readonly("global")
                .writable("feeRecipient")
                .readonly("mint")
                .writable("bondingCurve")
                .writable("curveTokenAccount")
                .writable("sellerTokenAccount")
                .writable_signer("seller")
                .readonly("systemProgram")
                .writable("creatorVault")
                .readonly("tokenProgram")
                .readonly("eventAuthority")
                .readonly("pumpProgram")
                .readonly("feeConfig")
                .readonly("feeProgram")
                .readonly("bondingCurveV2")
                .writable("buybackFeeRecipient")
                .data(data::literal(pump_fun_sell()))
                .data(data::u64(var("balance")))
                // pump.fun's own floor, left at 0: the check below measures what arrived.
                .data(data::u64(u64(0)))
                .label("sellEverything"),
        )
        // The price, net of pump.fun's and the creator's fees.
        .step(
            step::require((lamports("seller") - var("lamportsBefore")).gte(input("minSolOut")))
                .label("receivedAtLeastMinSolOut"),
        )
}
ts
import type { Address, Instruction } from '@solana/kit';

import { buildKitRunInstruction } from '@jac0xb/ballista/kit';
import { compiled } from '../pump-fun-sell-all.js';
import { PUMP_FEES, PUMP_FUN } from '../shared.js';
import { SYSTEM_PROGRAM, at, pinned } from './programs.js';
import {
  PUMP_BUYBACK_FEE_RECIPIENT,
  PUMP_EVENT_AUTHORITY,
  PUMP_FEE_CONFIG,
  PUMP_GLOBAL,
  TOKEN_2022_PROGRAM,
  pumpCoinAccounts,
  token2022Ata,
  type PumpCoin,
} from './pump-fun.js';

/**
 * Sells everything in the seller's associated token account for `coin`, and fails unless the
 * seller receives at least `minSolOut` lamports after fees.
 */
export async function buildPumpSellAllRun(input: {
  templateAddress: Address;
  seller: Address;
  coin: PumpCoin;
  minSolOut: bigint;
}): Promise<Instruction> {
  const coin = await pumpCoinAccounts(input.coin);
  return buildKitRunInstruction({
    compiled,
    templateAddress: input.templateAddress,
    inputs: { minSolOut: input.minSolOut },
    accounts: {
      pumpProgram: pinned(PUMP_FUN),
      global: at(PUMP_GLOBAL),
      feeRecipient: at(coin.feeRecipient),
      mint: at(coin.mint),
      bondingCurve: at(coin.bondingCurve),
      curveTokenAccount: at(coin.curveTokenAccount),
      sellerTokenAccount: at(await token2022Ata(input.seller, coin.mint)),
      seller: at(input.seller),
      systemProgram: pinned(SYSTEM_PROGRAM),
      creatorVault: at(coin.creatorVault),
      tokenProgram: pinned(TOKEN_2022_PROGRAM),
      eventAuthority: at(PUMP_EVENT_AUTHORITY),
      feeConfig: at(PUMP_FEE_CONFIG),
      feeProgram: pinned(PUMP_FEES),
      bondingCurveV2: at(coin.bondingCurveV2),
      buybackFeeRecipient: at(PUMP_BUYBACK_FEE_RECIPIENT),
    },
  });
}
rs
/// Sells everything in the seller's associated token account for `coin`, and fails unless the
/// seller receives at least `min_sol_out` lamports after fees.
pub fn run_pump_sell_all(
    template: Pubkey,
    seller: Pubkey,
    coin: &PumpCoin,
    min_sol_out: u64,
) -> Result<Instruction, Box<dyn Error>> {
    let instruction = templates::pump_fun_sell_all()
        .compile()?
        .run(template)
        .input("minSolOut", min_sol_out)
        .account("pumpProgram", PUMP_FUN)
        .account("global", PUMP_GLOBAL)
        .account("feeRecipient", coin.fee_recipient())
        .account("mint", coin.mint)
        .account("bondingCurve", coin.bonding_curve())
        .account("curveTokenAccount", coin.curve_token_account())
        .account("sellerTokenAccount", token_2022_ata(&seller, &coin.mint))
        .account("seller", seller)
        .account("systemProgram", SYSTEM_PROGRAM_ID)
        .account("creatorVault", coin.creator_vault())
        .account("tokenProgram", TOKEN_2022_PROGRAM_ID)
        .account("eventAuthority", PUMP_EVENT_AUTHORITY)
        .account("feeConfig", PUMP_FEE_CONFIG)
        .account("feeProgram", PUMP_FEES)
        .account("bondingCurveV2", coin.bonding_curve_v2())
        .account("buybackFeeRecipient", PUMP_BUYBACK_FEE_RECIPIENT)
        .instruction()?;
    Ok(instruction)
}

The token account's mint, owner and balance sit at offsets 0, 32 and 64, as in SPL Token. The Rust template takes its program addresses, offsets, token_2022_account() and balance_of() from the shared helpers.

Run it ​

The Run tabs pass the 16 declared accounts in the order pump.fun's sell takes them: global, feeRecipient, mint, bondingCurve, curveTokenAccount, sellerTokenAccount, seller, systemProgram, creatorVault, tokenProgram, eventAuthority, pumpProgram, feeConfig, feeProgram, bondingCurveV2 and buybackFeeRecipient, with pumpProgram first. The input is minSolOut.

  • sellerTokenAccount is the seller's associated token account for the coin.
  • feeRecipient is an ordinary fee recipient, or a reserved one for a mayhem-mode coin.
  • bondingCurveV2 is a PDA pump.fun has taken since its April 2026 upgrade, whether or not it exists.
Deriving pump.fun's accounts
ts
import { address, getAddressDecoder, getAddressEncoder, getProgramDerivedAddress, type Address } from '@solana/kit';

import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS_BYTES, TOKEN_2022_PROGRAM_ADDRESS_BYTES } from '@jac0xb/ballista';
import { PUMP_FUN } from '../shared.js';

const decoder = getAddressDecoder();
const encoder = getAddressEncoder();
export const TOKEN_2022_PROGRAM = decoder.decode(TOKEN_2022_PROGRAM_ADDRESS_BYTES);
const ASSOCIATED_TOKEN_PROGRAM = decoder.decode(ASSOCIATED_TOKEN_PROGRAM_ADDRESS_BYTES);

/** pump.fun's `Global`, `["global"]`. */
export const PUMP_GLOBAL = address('4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf');
/** Anchor's event authority, `["__event_authority"]`: pump.fun logs each trade by calling itself. */
export const PUMP_EVENT_AUTHORITY = address('Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1');
/** `["global_volume_accumulator"]`. */
export const PUMP_GLOBAL_VOLUME_ACCUMULATOR = address('Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y');
/** Pump Fees' `["fee_config", pump.fun]`. */
export const PUMP_FEE_CONFIG = address('8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt');
/** `Global.fee_recipient`, which an ordinary coin's trades may pay. `Global.fee_recipients` lists seven more. */
export const PUMP_FEE_RECIPIENT = address('62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV');
/** `Global.reserved_fee_recipient`, which a mayhem-mode coin's trades may pay instead. */
export const PUMP_RESERVED_FEE_RECIPIENT = address('GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS');
/** One of `Global.buyback_fee_recipients`, which every trade pays. */
export const PUMP_BUYBACK_FEE_RECIPIENT = address('9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7');

/** A pump.fun coin, as its bonding curve describes it. */
export interface PumpCoin {
  mint: Address;
  /** `BondingCurve.creator`: the 32 bytes at offset 49 of the curve's data. */
  creator: Address;
  /** `BondingCurve.is_mayhem_mode`: the byte at offset 81. */
  mayhem: boolean;
}

/** Reads a coin's creator and mode from its curve's account data. */
export function pumpCoin(mint: Address, curveData: Uint8Array): PumpCoin {
  return { mint, creator: decoder.decode(curveData.subarray(49, 81)), mayhem: curveData[81] === 1 };
}

const pumpPda = async (seeds: Parameters<typeof getProgramDerivedAddress>[0]['seeds']) =>
  (await getProgramDerivedAddress({ programAddress: address(PUMP_FUN), seeds }))[0];

/** `owner`'s associated token account for the Token-2022 mint `mint`. */
export async function token2022Ata(owner: Address, mint: Address): Promise<Address> {
  const [ata] = await getProgramDerivedAddress({
    programAddress: ASSOCIATED_TOKEN_PROGRAM,
    seeds: [encoder.encode(owner), TOKEN_2022_PROGRAM_ADDRESS_BYTES, encoder.encode(mint)],
  });
  return ata;
}

/** `["user_volume_accumulator", user]`. pump.fun creates it on the user's first buy. */
export function pumpUserVolumeAccumulator(user: Address): Promise<Address> {
  return pumpPda(['user_volume_accumulator', encoder.encode(user)]);
}

/** The accounts pump.fun's `buy` and `sell` take for one coin. */
export async function pumpCoinAccounts(coin: PumpCoin) {
  const bondingCurve = await pumpPda(['bonding-curve', encoder.encode(coin.mint)]);
  return {
    mint: coin.mint,
    bondingCurve,
    /** The curve's own token account, which coins are bought from and sold into. */
    curveTokenAccount: await token2022Ata(bondingCurve, coin.mint),
    /** `["creator-vault", creator]`: the creator's fees accrue here. */
    creatorVault: await pumpPda(['creator-vault', encoder.encode(coin.creator)]),
    /** `["bonding-curve-v2", mint]`, which `buy` and `sell` take whether or not it exists. */
    bondingCurveV2: await pumpPda(['bonding-curve-v2', encoder.encode(coin.mint)]),
    /** A fee recipient of the kind the coin's mode needs. */
    feeRecipient: coin.mayhem ? PUMP_RESERVED_FEE_RECIPIENT : PUMP_FEE_RECIPIENT,
  };
}
rs
/// pump.fun's `Global`, `["global"]`.
pub const PUMP_GLOBAL: Pubkey = pubkey!("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf");
/// Anchor's event authority, `["__event_authority"]`: pump.fun logs each trade by calling itself.
pub const PUMP_EVENT_AUTHORITY: Pubkey = pubkey!("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1");
/// `["global_volume_accumulator"]`.
pub const PUMP_GLOBAL_VOLUME_ACCUMULATOR: Pubkey =
    pubkey!("Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y");
/// Pump Fees' `["fee_config", pump.fun]`.
pub const PUMP_FEE_CONFIG: Pubkey = pubkey!("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt");
/// `Global.fee_recipient`, which an ordinary coin's trades may pay. `Global.fee_recipients` lists
/// seven more.
pub const PUMP_FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
/// `Global.reserved_fee_recipient`, which a mayhem-mode coin's trades may pay instead.
pub const PUMP_RESERVED_FEE_RECIPIENT: Pubkey =
    pubkey!("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS");
/// One of `Global.buyback_fee_recipients`, which every trade pays.
pub const PUMP_BUYBACK_FEE_RECIPIENT: Pubkey =
    pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7");

/// A pump.fun coin, as its bonding curve describes it.
pub struct PumpCoin {
    pub mint: Pubkey,
    /// `BondingCurve.creator`: the 32 bytes at offset 49 of the curve's data.
    pub creator: Pubkey,
    /// `BondingCurve.is_mayhem_mode`: the byte at offset 81.
    pub mayhem: bool,
}

fn pump_pda(seeds: &[&[u8]]) -> Pubkey {
    Pubkey::find_program_address(seeds, &PUMP_FUN).0
}

/// `owner`'s associated token account for the Token-2022 mint `mint`.
pub fn token_2022_ata(owner: &Pubkey, mint: &Pubkey) -> Pubkey {
    Pubkey::find_program_address(
        &[
            owner.as_ref(),
            TOKEN_2022_PROGRAM_ID.as_ref(),
            mint.as_ref(),
        ],
        &ASSOCIATED_TOKEN_PROGRAM_ID,
    )
    .0
}

/// `["user_volume_accumulator", user]`. pump.fun creates it on the user's first buy.
pub fn pump_user_volume_accumulator(user: &Pubkey) -> Pubkey {
    pump_pda(&[b"user_volume_accumulator", user.as_ref()])
}

impl PumpCoin {
    /// `["bonding-curve", mint]`.
    pub fn bonding_curve(&self) -> Pubkey {
        pump_pda(&[b"bonding-curve", self.mint.as_ref()])
    }

    /// The curve's own token account, which coins are bought from and sold into.
    pub fn curve_token_account(&self) -> Pubkey {
        token_2022_ata(&self.bonding_curve(), &self.mint)
    }

    /// `["creator-vault", creator]`: the creator's fees accrue here.
    pub fn creator_vault(&self) -> Pubkey {
        pump_pda(&[b"creator-vault", self.creator.as_ref()])
    }

    /// `["bonding-curve-v2", mint]`, which `buy` and `sell` take since pump.fun's April 2026
    /// upgrade, whether or not it exists.
    pub fn bonding_curve_v2(&self) -> Pubkey {
        pump_pda(&[b"bonding-curve-v2", self.mint.as_ref()])
    }

    /// A fee recipient of the kind the coin's mode needs.
    pub fn fee_recipient(&self) -> Pubkey {
        if self.mayhem {
            PUMP_RESERVED_FEE_RECIPIENT
        } else {
            PUMP_FEE_RECIPIENT
        }
    }
}

The sale fits a transaction without a lookup table (707 bytes) and the default compute limit. Like the basket, it pins Token-2022 and calls pump.fun's sell, so it trades SOL-paired coins created with create_v2.

What has been tested ​

  • In LiteSVM. In tests/protocols/tests/pump_fun_sell_all.rs, the seller first buys 2,000,000 of each live coin through pump.fun's own buy. The template sold an ordinary coin's whole balance for 56,955,651 lamports, exactly what pump.fun's own sell paid for the same amount, in 71,505 compute units and 707 bytes. A mayhem-mode coin sold the same way.
  • A balance that grew. A run built for one balance, landing after a second buy had doubled it, sold all of it.
  • The floor. A floor equal to the proceeds lands. One lamport more fails at receivedAtLeastMinSolOut, and the sale reverts.
  • Failures. A graduated coin fails at curveNotGraduated, an empty account at hasTokensToSell, and the seller's account of another coin at holdsTheCurvesCoin. Another coin's curve fails in pump.fun with Anchor's ConstraintSeeds (2006). A stranger's account that the seller is a delegate on fails at sellsTheSellersOwnTokens, though pump.fun alone sells the stranger's coins and pays the seller.
  • Accounts. The semantics test checks sell's accounts and writable flags against a mainnet transaction.

Check before you upload

pump.fun changes often: its April 2026 upgrade added two accounts to every buy and sell. Check the accounts against pump.fun's current IDL and a recent transaction before you upload.

All protocol templates · What has been tested

BALLISTA / A SMALL MACHINE FOR COMPLEX TRANSACTIONS