MEV

How to Fix PumpFun Custom:6024 Overflow Error After the Cashback Upgrade

March 1, 2026AllenHark Team

AnchorError thrown in programs/pump/src/lib.rs:764. Error Code: Overflow. Error Number: 6024. Error Message: Overflow. reference.::

How to Fix PumpFun Custom:6024 Overflow Error After the Cashback Upgrade

A complete guide to the February 2026 PumpFun bonding curve program upgrade that broke buy/sell transactions. Covers the new bonding_curve_v2 PDA requirement, the cashback_enabled flag, and correct account layouts for both cashback and non-cashback tokens — with working code in Rust, TypeScript, and Python.


Table of Contents

  1. The Problem
  2. Root Cause
  3. Key Discovery: The cashback_enabled Flag
  4. Account Layouts After the Upgrade
  5. Detecting Cashback Status On-Chain
  6. The bonding_curve_v2 PDA
  7. Complete Working Examples
  8. PumpSwap AMM (pool_v2)
  9. Common Mistakes
  10. Summary

The Problem

Starting late February 2026, PumpFun bonding curve buy and sell transactions began failing with:

InstructionError(N, Custom(6024))
AnchorError thrown in programs/pump/src/buy.rs:181. Error Code: Overflow.

or on sell:

InstructionError(N, Custom(6024))
AnchorError thrown in programs/pump/src/lib.rs:764. Error Code: Overflow.

This affected all tokens on the bonding curve program (6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P), regardless of trade size. Even 0.001 SOL buys would fail. The error is misleading — it says "Overflow" but the actual cause is missing accounts introduced by a silent program upgrade.


Root Cause

PumpFun deployed a cashback program upgrade that introduced a new required account: the bonding_curve_v2 PDA. Without this account appended to the instruction's account list, the on-chain program reads data from the wrong account indices, causing u64 arithmetic to operate on garbage data — hence the "Overflow" error.

The upgrade also introduced a distinction between cashback-enabled and non-cashback tokens, which affects the sell instruction's account layout.

Key facts:

  • The bonding_curve_v2 PDA must be appended as the last remaining account for all buy and sell instructions — cashback and non-cashback alike.
  • The sell instruction additionally requires a user_volume_accumulator account only for cashback-enabled tokens.
  • Being a Token-2022 token does NOT mean cashback is enabled. The authoritative check is byte[82] in the bonding curve's on-chain account data.

Key Discovery: The cashback_enabled Flag

This is the critical insight that isn't documented anywhere in the official PumpFun docs:

The bonding curve account data contains a cashback_enabled boolean flag at byte offset 82.

The bonding curve account layout (151 bytes after the cashback upgrade):

OffsetSizeFieldType
08Discriminator[u8; 8]
88virtual_token_reservesu64
168virtual_sol_reservesu64
248real_token_reservesu64
328real_sol_reservesu64
408token_total_supplyu64
481completebool
4932creatorPubkey
811(reserved)u8
821cashback_enabledbool
83–15068(extended fields)

This flag determines the sell account layout:

  • byte[82] == 0 → Non-cashback → 15 accounts on sell
  • byte[82] == 1 → Cashback enabled → 16 accounts on sell (includes user_volume_accumulator)

Warning: Do NOT use the token's program ID (Token-2022 vs spl-token) as a proxy for cashback status. Many Token-2022 tokens have cashback_enabled = false. Using Token-2022 status will cause sells to fail with overflow on non-cashback Token-2022 tokens.


Account Layouts After the Upgrade

Buy (buy_exact_sol_in) — 17 accounts (same for all tokens)

IndexAccountMutableSigner
0global (PDA: ["global"])NoNo
1fee_recipient (from GlobalConfig)YesNo
2mintNoNo
3bonding_curve (PDA: ["bonding-curve", mint])YesNo
4associated_bonding_curve (ATA of bonding_curve for mint)YesNo
5associated_user (user's ATA for mint)YesNo
6user (payer)YesYes
7system_programNoNo
8token_program (spl-token or Token-2022)NoNo
9creator_vault (PDA: ["creator-vault", creator])YesNo
10event_authority (PDA: ["__event_authority"])NoNo
11program (6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P)NoNo
12global_volume_accumulator (PDA: ["global_volume_accumulator"])NoNo
13user_volume_accumulator (PDA: ["user_volume_accumulator", user])YesNo
14fee_config (PDA on fee program)NoNo
15fee_program (pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ)NoNo
16bonding_curve_v2 (PDA: ["bonding-curve-v2", mint])NoNo

Sell — Non-cashback tokens (15 accounts)

IndexAccountMutableSigner
0globalNoNo
1fee_recipientYesNo
2mintNoNo
3bonding_curveYesNo
4associated_bonding_curveYesNo
5associated_userYesNo
6userYesYes
7system_programNoNo
8creator_vaultYesNo
9token_programNoNo
10event_authorityNoNo
11programNoNo
12fee_configNoNo
13fee_programNoNo
14bonding_curve_v2NoNo

Sell — Cashback-enabled tokens (16 accounts)

IndexAccountMutableSigner
0–13(same as non-cashback)
14user_volume_accumulator (PDA: ["user_volume_accumulator", user])YesNo
15bonding_curve_v2NoNo

bonding_curve_v2 must always be the last account.


Detecting Cashback Status On-Chain

Reading byte[82] from the bonding curve account

The bonding curve PDA is derived as:

seeds = ["bonding-curve", mint_pubkey]
program = 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P

After fetching the account data, check byte 82:

cashback_enabled = data.length > 82 && data[82] != 0

Fee Config PDA

Both buy and sell use the same fee_config PDA. The seed key for the bonding curve program's fee config is:

key = [1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
       81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176]

seeds = ["fee_config", key]
program = pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ

Warning: The PumpSwap AMM program uses a different fee config key for sell. Do not mix them up. The bonding curve program uses the same fee config for both buy and sell.


The bonding_curve_v2 PDA

Derived from:

seeds = ["bonding-curve-v2", mint_pubkey]
program = 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P

This PDA does not need to exist on-chain (it can be an uninitialized account). The program just needs it in the accounts list to properly index the remaining accounts. Pass it as read-only.


Complete Working Examples

Rust

use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;

const PUMP_PROGRAM: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
const FEE_PROGRAM: &str = "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ";

/// Detect cashback status from bonding curve on-chain data.
/// byte[82] == 1 means cashback is enabled for this token.
fn is_cashback_enabled(bonding_curve_data: &[u8]) -> bool {
    bonding_curve_data.len() > 82 && bonding_curve_data[82] != 0
}

/// Derive the bonding_curve_v2 PDA (required for ALL tokens after Feb 2026 upgrade).
fn derive_bonding_curve_v2(mint: &Pubkey) -> Pubkey {
    let program = Pubkey::from_str(PUMP_PROGRAM).unwrap();
    let (pda, _) = Pubkey::find_program_address(
        &[b"bonding-curve-v2", mint.as_ref()],
        &program,
    );
    pda
}

/// Derive the fee_config PDA (same for buy AND sell on the bonding curve program).
fn derive_fee_config() -> Pubkey {
    let fee_program = Pubkey::from_str(FEE_PROGRAM).unwrap();
    let key: [u8; 32] = [
        1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
        81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176,
    ];
    let (pda, _) = Pubkey::find_program_address(&[b"fee_config", &key], &fee_program);
    pda
}

/// Build a buy_exact_sol_in instruction (17 accounts for all tokens).
fn build_buy_ix(
    mint: &Pubkey,
    payer: &Pubkey,
    sol_amount: u64,
    min_tokens_out: u64,
    fee_recipient: &Pubkey,
    creator: &Pubkey,
    token_program: &Pubkey,
) -> Instruction {
    let program_id = Pubkey::from_str(PUMP_PROGRAM).unwrap();

    let (global, _) = Pubkey::find_program_address(&[b"global"], &program_id);
    let (bonding_curve, _) = Pubkey::find_program_address(
        &[b"bonding-curve", mint.as_ref()], &program_id,
    );
    let associated_bonding_curve = derive_ata(bonding_curve, mint, token_program);
    let user_ata = derive_ata(*payer, mint, token_program);
    let (creator_vault, _) = Pubkey::find_program_address(
        &[b"creator-vault", creator.as_ref()], &program_id,
    );
    let (event_authority, _) = Pubkey::find_program_address(
        &[b"__event_authority"], &program_id,
    );
    let (global_vol, _) = Pubkey::find_program_address(
        &[b"global_volume_accumulator"], &program_id,
    );
    let (user_vol, _) = Pubkey::find_program_address(
        &[b"user_volume_accumulator", payer.as_ref()], &program_id,
    );
    let fee_config = derive_fee_config();
    let fee_program = Pubkey::from_str(FEE_PROGRAM).unwrap();
    let bonding_curve_v2 = derive_bonding_curve_v2(mint);

    // buy_exact_sol_in discriminator
    let disc: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95];
    let mut data = Vec::with_capacity(24);
    data.extend_from_slice(&disc);
    data.extend_from_slice(&sol_amount.to_le_bytes());
    data.extend_from_slice(&min_tokens_out.to_le_bytes());

    let accounts = vec![
        AccountMeta::new_readonly(global, false),                         // 0
        AccountMeta::new(*fee_recipient, false),                          // 1
        AccountMeta::new_readonly(*mint, false),                          // 2
        AccountMeta::new(bonding_curve, false),                           // 3
        AccountMeta::new(associated_bonding_curve, false),                // 4
        AccountMeta::new(user_ata, false),                                // 5
        AccountMeta::new(*payer, true),                                   // 6
        AccountMeta::new_readonly(solana_sdk::system_program::ID, false), // 7
        AccountMeta::new_readonly(*token_program, false),                 // 8
        AccountMeta::new(creator_vault, false),                           // 9
        AccountMeta::new_readonly(event_authority, false),                // 10
        AccountMeta::new_readonly(program_id, false),                     // 11
        AccountMeta::new_readonly(global_vol, false),                     // 12
        AccountMeta::new(user_vol, false),                                // 13
        AccountMeta::new_readonly(fee_config, false),                     // 14
        AccountMeta::new_readonly(fee_program, false),                    // 15
        AccountMeta::new_readonly(bonding_curve_v2, false),               // 16 ← NEW
    ];

    Instruction { program_id, accounts, data }
}

/// Build a sell instruction.
/// Non-cashback: 15 accounts. Cashback-enabled: 16 accounts.
fn build_sell_ix(
    mint: &Pubkey,
    payer: &Pubkey,
    token_amount: u64,
    min_sol_out: u64,
    fee_recipient: &Pubkey,
    creator: &Pubkey,
    token_program: &Pubkey,
    cashback_enabled: bool, // ← Read from bonding curve byte[82]
) -> Instruction {
    let program_id = Pubkey::from_str(PUMP_PROGRAM).unwrap();

    let (global, _) = Pubkey::find_program_address(&[b"global"], &program_id);
    let (bonding_curve, _) = Pubkey::find_program_address(
        &[b"bonding-curve", mint.as_ref()], &program_id,
    );
    let associated_bonding_curve = derive_ata(bonding_curve, mint, token_program);
    let user_ata = derive_ata(*payer, mint, token_program);
    let (creator_vault, _) = Pubkey::find_program_address(
        &[b"creator-vault", creator.as_ref()], &program_id,
    );
    let (event_authority, _) = Pubkey::find_program_address(
        &[b"__event_authority"], &program_id,
    );
    let fee_config = derive_fee_config();
    let fee_program = Pubkey::from_str(FEE_PROGRAM).unwrap();
    let bonding_curve_v2 = derive_bonding_curve_v2(mint);

    // sell discriminator
    let disc: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
    let mut data = Vec::with_capacity(24);
    data.extend_from_slice(&disc);
    data.extend_from_slice(&token_amount.to_le_bytes());
    data.extend_from_slice(&min_sol_out.to_le_bytes());

    let mut accounts = vec![
        AccountMeta::new_readonly(global, false),                         // 0
        AccountMeta::new(*fee_recipient, false),                          // 1
        AccountMeta::new_readonly(*mint, false),                          // 2
        AccountMeta::new(bonding_curve, false),                           // 3
        AccountMeta::new(associated_bonding_curve, false),                // 4
        AccountMeta::new(user_ata, false),                                // 5
        AccountMeta::new(*payer, true),                                   // 6
        AccountMeta::new_readonly(solana_sdk::system_program::ID, false), // 7
        AccountMeta::new(creator_vault, false),                           // 8
        AccountMeta::new_readonly(*token_program, false),                 // 9
        AccountMeta::new_readonly(event_authority, false),                // 10
        AccountMeta::new_readonly(program_id, false),                     // 11
        AccountMeta::new_readonly(fee_config, false),                     // 12
        AccountMeta::new_readonly(fee_program, false),                    // 13
    ];

    // Cashback-enabled tokens need user_volume_accumulator BEFORE bonding_curve_v2
    if cashback_enabled {
        let (user_vol, _) = Pubkey::find_program_address(
            &[b"user_volume_accumulator", payer.as_ref()], &program_id,
        );
        accounts.push(AccountMeta::new(user_vol, false));                 // 14
    }

    // bonding_curve_v2 is ALWAYS the last account
    accounts.push(AccountMeta::new_readonly(bonding_curve_v2, false));    // 14 or 15

    Instruction { program_id, accounts, data }
}

/// Derive ATA with a specific token program.
fn derive_ata(owner: Pubkey, mint: &Pubkey, token_program: &Pubkey) -> Pubkey {
    let (ata, _) = Pubkey::find_program_address(
        &[owner.as_ref(), token_program.as_ref(), mint.as_ref()],
        &spl_associated_token_account::ID,
    );
    ata
}

TypeScript

import {
  Connection,
  PublicKey,
  TransactionInstruction,
  AccountMeta,
  SystemProgram,
} from "@solana/web3.js";

const PUMP_PROGRAM = new PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
const FEE_PROGRAM = new PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ");
const ATA_PROGRAM = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");

// Same key for buy AND sell fee_config on the bonding curve program
const FEE_CONFIG_KEY = Buffer.from([
  1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
  81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176,
]);

function deriveAta(owner: PublicKey, mint: PublicKey, tokenProgram: PublicKey): PublicKey {
  const [ata] = PublicKey.findProgramAddressSync(
    [owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()],
    ATA_PROGRAM
  );
  return ata;
}

function deriveBondingCurve(mint: PublicKey): PublicKey {
  const [pda] = PublicKey.findProgramAddressSync(
    [Buffer.from("bonding-curve"), mint.toBuffer()],
    PUMP_PROGRAM
  );
  return pda;
}

function deriveBondingCurveV2(mint: PublicKey): PublicKey {
  const [pda] = PublicKey.findProgramAddressSync(
    [Buffer.from("bonding-curve-v2"), mint.toBuffer()],
    PUMP_PROGRAM
  );
  return pda;
}

function deriveFeeConfig(): PublicKey {
  const [pda] = PublicKey.findProgramAddressSync(
    [Buffer.from("fee_config"), FEE_CONFIG_KEY],
    FEE_PROGRAM
  );
  return pda;
}

/**
 * Detect cashback status from bonding curve account data.
 * byte[82] == 1 means cashback is enabled.
 *
 * WARNING: Do NOT use the token program (Token-2022 vs spl-token) as a proxy.
 * Many Token-2022 tokens have cashback_enabled = false.
 */
function isCashbackEnabled(bondingCurveData: Buffer): boolean {
  return bondingCurveData.length > 82 && bondingCurveData[82] !== 0;
}

/**
 * Read the creator pubkey from bonding curve data (offset 49, 32 bytes).
 */
function readCreator(bondingCurveData: Buffer): PublicKey {
  return new PublicKey(bondingCurveData.subarray(49, 81));
}

/**
 * Build a buy_exact_sol_in instruction (17 accounts for ALL tokens).
 */
function buildBuyInstruction(
  mint: PublicKey,
  payer: PublicKey,
  solAmount: bigint,
  minTokensOut: bigint,
  feeRecipient: PublicKey,
  creator: PublicKey,
  tokenProgram: PublicKey
): TransactionInstruction {
  const bondingCurve = deriveBondingCurve(mint);
  const [global] = PublicKey.findProgramAddressSync([Buffer.from("global")], PUMP_PROGRAM);
  const [creatorVault] = PublicKey.findProgramAddressSync(
    [Buffer.from("creator-vault"), creator.toBuffer()],
    PUMP_PROGRAM
  );
  const [eventAuth] = PublicKey.findProgramAddressSync(
    [Buffer.from("__event_authority")],
    PUMP_PROGRAM
  );
  const [globalVol] = PublicKey.findProgramAddressSync(
    [Buffer.from("global_volume_accumulator")],
    PUMP_PROGRAM
  );
  const [userVol] = PublicKey.findProgramAddressSync(
    [Buffer.from("user_volume_accumulator"), payer.toBuffer()],
    PUMP_PROGRAM
  );

  // buy_exact_sol_in discriminator: sha256("global:buy_exact_sol_in")[..8]
  const disc = Buffer.from([56, 252, 116, 8, 158, 223, 205, 95]);
  const data = Buffer.alloc(24);
  disc.copy(data, 0);
  data.writeBigUInt64LE(solAmount, 8);
  data.writeBigUInt64LE(minTokensOut, 16);

  const keys: AccountMeta[] = [
    { pubkey: global, isSigner: false, isWritable: false },                       // 0
    { pubkey: feeRecipient, isSigner: false, isWritable: true },                  // 1
    { pubkey: mint, isSigner: false, isWritable: false },                         // 2
    { pubkey: bondingCurve, isSigner: false, isWritable: true },                  // 3
    { pubkey: deriveAta(bondingCurve, mint, tokenProgram), isSigner: false, isWritable: true },  // 4
    { pubkey: deriveAta(payer, mint, tokenProgram), isSigner: false, isWritable: true },         // 5
    { pubkey: payer, isSigner: true, isWritable: true },                          // 6
    { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },      // 7
    { pubkey: tokenProgram, isSigner: false, isWritable: false },                 // 8
    { pubkey: creatorVault, isSigner: false, isWritable: true },                  // 9
    { pubkey: eventAuth, isSigner: false, isWritable: false },                    // 10
    { pubkey: PUMP_PROGRAM, isSigner: false, isWritable: false },                 // 11
    { pubkey: globalVol, isSigner: false, isWritable: false },                    // 12
    { pubkey: userVol, isSigner: false, isWritable: true },                       // 13
    { pubkey: deriveFeeConfig(), isSigner: false, isWritable: false },            // 14
    { pubkey: FEE_PROGRAM, isSigner: false, isWritable: false },                  // 15
    { pubkey: deriveBondingCurveV2(mint), isSigner: false, isWritable: false },   // 16 ← NEW
  ];

  return new TransactionInstruction({ programId: PUMP_PROGRAM, keys, data });
}

/**
 * Build a sell instruction.
 * Non-cashback: 15 accounts. Cashback: 16 accounts.
 */
function buildSellInstruction(
  mint: PublicKey,
  payer: PublicKey,
  tokenAmount: bigint,
  minSolOut: bigint,
  feeRecipient: PublicKey,
  creator: PublicKey,
  tokenProgram: PublicKey,
  cashbackEnabled: boolean // ← from bonding curve byte[82]
): TransactionInstruction {
  const bondingCurve = deriveBondingCurve(mint);
  const [global] = PublicKey.findProgramAddressSync([Buffer.from("global")], PUMP_PROGRAM);
  const [creatorVault] = PublicKey.findProgramAddressSync(
    [Buffer.from("creator-vault"), creator.toBuffer()],
    PUMP_PROGRAM
  );
  const [eventAuth] = PublicKey.findProgramAddressSync(
    [Buffer.from("__event_authority")],
    PUMP_PROGRAM
  );

  // sell discriminator
  const disc = Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]);
  const data = Buffer.alloc(24);
  disc.copy(data, 0);
  data.writeBigUInt64LE(tokenAmount, 8);
  data.writeBigUInt64LE(minSolOut, 16);

  const keys: AccountMeta[] = [
    { pubkey: global, isSigner: false, isWritable: false },                       // 0
    { pubkey: feeRecipient, isSigner: false, isWritable: true },                  // 1
    { pubkey: mint, isSigner: false, isWritable: false },                         // 2
    { pubkey: bondingCurve, isSigner: false, isWritable: true },                  // 3
    { pubkey: deriveAta(bondingCurve, mint, tokenProgram), isSigner: false, isWritable: true },  // 4
    { pubkey: deriveAta(payer, mint, tokenProgram), isSigner: false, isWritable: true },         // 5
    { pubkey: payer, isSigner: true, isWritable: true },                          // 6
    { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },      // 7
    { pubkey: creatorVault, isSigner: false, isWritable: true },                  // 8
    { pubkey: tokenProgram, isSigner: false, isWritable: false },                 // 9
    { pubkey: eventAuth, isSigner: false, isWritable: false },                    // 10
    { pubkey: PUMP_PROGRAM, isSigner: false, isWritable: false },                 // 11
    { pubkey: deriveFeeConfig(), isSigner: false, isWritable: false },            // 12
    { pubkey: FEE_PROGRAM, isSigner: false, isWritable: false },                  // 13
  ];

  // Cashback tokens need user_volume_accumulator BEFORE bonding_curve_v2
  if (cashbackEnabled) {
    const [userVol] = PublicKey.findProgramAddressSync(
      [Buffer.from("user_volume_accumulator"), payer.toBuffer()],
      PUMP_PROGRAM
    );
    keys.push({ pubkey: userVol, isSigner: false, isWritable: true });            // 14
  }

  // bonding_curve_v2 is ALWAYS the last account
  keys.push({
    pubkey: deriveBondingCurveV2(mint),
    isSigner: false,
    isWritable: false,
  }); // 14 or 15

  return new TransactionInstruction({ programId: PUMP_PROGRAM, keys, data });
}

// ----- Usage Example -----

async function example() {
  const connection = new Connection("https://api.mainnet-beta.solana.com");
  const mint = new PublicKey("HhEdFsuJ88cFsUFT5LyqDCEdsbYzWiHs2MBMucJtpump");

  // 1. Fetch bonding curve data
  const bondingCurve = deriveBondingCurve(mint);
  const accountInfo = await connection.getAccountInfo(bondingCurve);
  if (!accountInfo) throw new Error("Bonding curve not found");

  // 2. Read cashback flag from byte[82]
  const cashbackEnabled = isCashbackEnabled(accountInfo.data as Buffer);
  console.log(`Cashback enabled: ${cashbackEnabled}`);

  // 3. Read creator from bonding curve data
  const creator = readCreator(accountInfo.data as Buffer);

  // 4. Detect token program
  const mintInfo = await connection.getAccountInfo(mint);
  const tokenProgram = mintInfo!.owner;

  // 5. Build instructions using cashbackEnabled flag
  // ... buildBuyInstruction(...) or buildSellInstruction(..., cashbackEnabled)
}

Python

from solders.pubkey import Pubkey
from solders.instruction import Instruction, AccountMeta
from solders.system_program import ID as SYSTEM_PROGRAM
from solana.rpc.api import Client
import struct

PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
ATA_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
SPL_TOKEN = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
TOKEN_2022 = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")

# Same key for buy AND sell fee_config
FEE_CONFIG_KEY = bytes([
    1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
    81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176,
])


def derive_pda(seeds: list[bytes], program: Pubkey) -> Pubkey:
    """Derive a PDA from seeds and program ID."""
    pda, _ = Pubkey.find_program_address(seeds, program)
    return pda


def derive_ata(owner: Pubkey, mint: Pubkey, token_program: Pubkey) -> Pubkey:
    return derive_pda(
        [bytes(owner), bytes(token_program), bytes(mint)],
        ATA_PROGRAM,
    )


def derive_bonding_curve(mint: Pubkey) -> Pubkey:
    return derive_pda([b"bonding-curve", bytes(mint)], PUMP_PROGRAM)


def derive_bonding_curve_v2(mint: Pubkey) -> Pubkey:
    return derive_pda([b"bonding-curve-v2", bytes(mint)], PUMP_PROGRAM)


def derive_fee_config() -> Pubkey:
    return derive_pda([b"fee_config", FEE_CONFIG_KEY], FEE_PROGRAM)


def is_cashback_enabled(bonding_curve_data: bytes) -> bool:
    """
    Check byte[82] of the bonding curve account data.
    Returns True if cashback is enabled for this token.

    WARNING: Do NOT use the token program ID as a proxy for cashback.
    Many Token-2022 tokens have cashback_enabled = False.
    """
    return len(bonding_curve_data) > 82 and bonding_curve_data[82] != 0


def read_creator(bonding_curve_data: bytes) -> Pubkey:
    """Read the creator pubkey from bonding curve data (offset 49)."""
    return Pubkey.from_bytes(bonding_curve_data[49:81])


def build_buy_instruction(
    mint: Pubkey,
    payer: Pubkey,
    sol_amount: int,
    min_tokens_out: int,
    fee_recipient: Pubkey,
    creator: Pubkey,
    token_program: Pubkey,
) -> Instruction:
    """
    Build a buy_exact_sol_in instruction.
    Always 17 accounts (same for cashback and non-cashback tokens).
    """
    bonding_curve = derive_bonding_curve(mint)
    global_pda = derive_pda([b"global"], PUMP_PROGRAM)
    creator_vault = derive_pda([b"creator-vault", bytes(creator)], PUMP_PROGRAM)
    event_auth = derive_pda([b"__event_authority"], PUMP_PROGRAM)
    global_vol = derive_pda([b"global_volume_accumulator"], PUMP_PROGRAM)
    user_vol = derive_pda([b"user_volume_accumulator", bytes(payer)], PUMP_PROGRAM)
    fee_config = derive_fee_config()
    bonding_curve_v2 = derive_bonding_curve_v2(mint)

    # buy_exact_sol_in discriminator
    disc = bytes([56, 252, 116, 8, 158, 223, 205, 95])
    data = disc + struct.pack("<QQ", sol_amount, min_tokens_out)

    accounts = [
        AccountMeta(global_pda, is_signer=False, is_writable=False),                          # 0
        AccountMeta(fee_recipient, is_signer=False, is_writable=True),                         # 1
        AccountMeta(mint, is_signer=False, is_writable=False),                                 # 2
        AccountMeta(bonding_curve, is_signer=False, is_writable=True),                         # 3
        AccountMeta(derive_ata(bonding_curve, mint, token_program), is_signer=False, is_writable=True),  # 4
        AccountMeta(derive_ata(payer, mint, token_program), is_signer=False, is_writable=True),          # 5
        AccountMeta(payer, is_signer=True, is_writable=True),                                  # 6
        AccountMeta(SYSTEM_PROGRAM, is_signer=False, is_writable=False),                       # 7
        AccountMeta(token_program, is_signer=False, is_writable=False),                        # 8
        AccountMeta(creator_vault, is_signer=False, is_writable=True),                         # 9
        AccountMeta(event_auth, is_signer=False, is_writable=False),                           # 10
        AccountMeta(PUMP_PROGRAM, is_signer=False, is_writable=False),                         # 11
        AccountMeta(global_vol, is_signer=False, is_writable=False),                           # 12
        AccountMeta(user_vol, is_signer=False, is_writable=True),                              # 13
        AccountMeta(fee_config, is_signer=False, is_writable=False),                           # 14
        AccountMeta(FEE_PROGRAM, is_signer=False, is_writable=False),                          # 15
        AccountMeta(bonding_curve_v2, is_signer=False, is_writable=False),                     # 16 ← NEW
    ]

    return Instruction(PUMP_PROGRAM, data, accounts)


def build_sell_instruction(
    mint: Pubkey,
    payer: Pubkey,
    token_amount: int,
    min_sol_out: int,
    fee_recipient: Pubkey,
    creator: Pubkey,
    token_program: Pubkey,
    cashback_enabled: bool,  # ← from bonding curve byte[82]
) -> Instruction:
    """
    Build a sell instruction.
    Non-cashback: 15 accounts. Cashback: 16 accounts.
    """
    bonding_curve = derive_bonding_curve(mint)
    global_pda = derive_pda([b"global"], PUMP_PROGRAM)
    creator_vault = derive_pda([b"creator-vault", bytes(creator)], PUMP_PROGRAM)
    event_auth = derive_pda([b"__event_authority"], PUMP_PROGRAM)
    fee_config = derive_fee_config()
    bonding_curve_v2 = derive_bonding_curve_v2(mint)

    # sell discriminator
    disc = bytes([51, 230, 133, 164, 1, 127, 131, 173])
    data = disc + struct.pack("<QQ", token_amount, min_sol_out)

    accounts = [
        AccountMeta(global_pda, is_signer=False, is_writable=False),                          # 0
        AccountMeta(fee_recipient, is_signer=False, is_writable=True),                         # 1
        AccountMeta(mint, is_signer=False, is_writable=False),                                 # 2
        AccountMeta(bonding_curve, is_signer=False, is_writable=True),                         # 3
        AccountMeta(derive_ata(bonding_curve, mint, token_program), is_signer=False, is_writable=True),  # 4
        AccountMeta(derive_ata(payer, mint, token_program), is_signer=False, is_writable=True),          # 5
        AccountMeta(payer, is_signer=True, is_writable=True),                                  # 6
        AccountMeta(SYSTEM_PROGRAM, is_signer=False, is_writable=False),                       # 7
        AccountMeta(creator_vault, is_signer=False, is_writable=True),                         # 8
        AccountMeta(token_program, is_signer=False, is_writable=False),                        # 9
        AccountMeta(event_auth, is_signer=False, is_writable=False),                           # 10
        AccountMeta(PUMP_PROGRAM, is_signer=False, is_writable=False),                         # 11
        AccountMeta(fee_config, is_signer=False, is_writable=False),                           # 12
        AccountMeta(FEE_PROGRAM, is_signer=False, is_writable=False),                          # 13
    ]

    # Cashback tokens need user_volume_accumulator BEFORE bonding_curve_v2
    if cashback_enabled:
        user_vol = derive_pda(
            [b"user_volume_accumulator", bytes(payer)],
            PUMP_PROGRAM,
        )
        accounts.append(AccountMeta(user_vol, is_signer=False, is_writable=True))              # 14

    # bonding_curve_v2 is ALWAYS the last account
    accounts.append(AccountMeta(bonding_curve_v2, is_signer=False, is_writable=False))         # 14 or 15

    return Instruction(PUMP_PROGRAM, data, accounts)


# ----- Usage Example -----

def example():
    client = Client("https://api.mainnet-beta.solana.com")
    mint = Pubkey.from_string("HhEdFsuJ88cFsUFT5LyqDCEdsbYzWiHs2MBMucJtpump")

    # 1. Fetch bonding curve data
    bonding_curve = derive_bonding_curve(mint)
    resp = client.get_account_info(bonding_curve)
    bc_data = bytes(resp.value.data)

    # 2. Read cashback flag from byte[82]
    cashback = is_cashback_enabled(bc_data)
    print(f"Cashback enabled: ${cashback}")

    # 3. Read creator
    creator = read_creator(bc_data)

    # 4. Detect token program
    mint_resp = client.get_account_info(mint)
    token_program = mint_resp.value.owner  # spl-token or Token-2022

    # 5. Build sell instruction with correct cashback flag
    # sell_ix = build_sell_instruction(mint, payer, amount, 0, fee_recipient, creator, token_program, cashback)

PumpSwap AMM (pool_v2)

The PumpSwap AMM program (pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA) requires a similar fix. Instead of bonding_curve_v2, it uses a pool_v2 PDA:

seeds = ["pool-v2", base_mint]
program = pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA

The PumpSwap pool account has its own is_cashback field within the pool data (at a different offset than the bonding curve). Check the pool data directly rather than inferring from token type.


Common Mistakes

1. Using Token-2022 as a proxy for cashback

// WRONG — many Token-2022 tokens are NOT cashback-enabled
const isCashback = mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID);

// CORRECT — read the flag from the bonding curve data
const isCashback = bondingCurveData[82] !== 0;

2. Using a different fee_config for sell vs buy

// WRONG — the bonding curve program uses the SAME fee_config for buy and sell
let sell_fee_key: [u8; 32] = [12, 20, 222, ...]; // This is the PumpSwap AMM key!

// CORRECT — same key for both buy and sell on the bonding curve program
let fee_key: [u8; 32] = [1, 86, 224, 246, 147, 102, 90, 207, ...];

3. Forgetting bonding_curve_v2 entirely

The overflow error message is misleading. The program doesn't say "missing account" — it says "Overflow" because it reads the wrong data from shifted account indices.

4. Putting bonding_curve_v2 before user_volume_accumulator

The order matters on sell for cashback tokens:

// WRONG order
accounts.push(bonding_curve_v2);      // ← this must be LAST
accounts.push(user_volume_accumulator);

// CORRECT order
accounts.push(user_volume_accumulator); // cashback tokens only
accounts.push(bonding_curve_v2);        // ALWAYS last

5. Always including user_volume_accumulator on sell

// WRONG — causes overflow on non-cashback tokens
accounts.push(user_volume_accumulator); // only for cashback!
accounts.push(bonding_curve_v2);

// CORRECT — conditional on byte[82]
if cashback_enabled {
    accounts.push(user_volume_accumulator);
}
accounts.push(bonding_curve_v2); // always

Summary

InstructionCashback StatusTotal AccountsExtra Remaining Accounts
buy_exact_sol_inAny17bonding_curve_v2
sellNon-cashback (byte[82]=0)15bonding_curve_v2
sellCashback (byte[82]=1)16user_volume_accumulator + bonding_curve_v2

Three rules to remember:

  1. Always append bonding_curve_v2 (PDA: ["bonding-curve-v2", mint]) as the last account on every buy and sell instruction.
  2. Read byte[82] from the bonding curve account data to determine cashback status — not the token program.
  3. Cashback sell needs user_volume_accumulator inserted before bonding_curve_v2.

*Last updated: March 2026. Based on the PumpFun bonding curve program at 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P. See pump-fun/pump-public-docs#30 for the original discussion.