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_v2PDA requirement, thecashback_enabledflag, and correct account layouts for both cashback and non-cashback tokens — with working code in Rust, TypeScript, and Python.
Table of Contents
- The Problem
- Root Cause
- Key Discovery: The
cashback_enabledFlag - Account Layouts After the Upgrade
- Detecting Cashback Status On-Chain
- The
bonding_curve_v2PDA - Complete Working Examples
- PumpSwap AMM (
pool_v2) - Common Mistakes
- 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_v2PDA 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_accumulatoraccount 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):
| Offset | Size | Field | Type |
|---|---|---|---|
| 0 | 8 | Discriminator | [u8; 8] |
| 8 | 8 | virtual_token_reserves | u64 |
| 16 | 8 | virtual_sol_reserves | u64 |
| 24 | 8 | real_token_reserves | u64 |
| 32 | 8 | real_sol_reserves | u64 |
| 40 | 8 | token_total_supply | u64 |
| 48 | 1 | complete | bool |
| 49 | 32 | creator | Pubkey |
| 81 | 1 | (reserved) | u8 |
| 82 | 1 | cashback_enabled | bool |
| 83–150 | 68 | (extended fields) | — |
This flag determines the sell account layout:
byte[82] == 0→ Non-cashback → 15 accounts on sellbyte[82] == 1→ Cashback enabled → 16 accounts on sell (includesuser_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)
| Index | Account | Mutable | Signer |
|---|---|---|---|
| 0 | global (PDA: ["global"]) | No | No |
| 1 | fee_recipient (from GlobalConfig) | Yes | No |
| 2 | mint | No | No |
| 3 | bonding_curve (PDA: ["bonding-curve", mint]) | Yes | No |
| 4 | associated_bonding_curve (ATA of bonding_curve for mint) | Yes | No |
| 5 | associated_user (user's ATA for mint) | Yes | No |
| 6 | user (payer) | Yes | Yes |
| 7 | system_program | No | No |
| 8 | token_program (spl-token or Token-2022) | No | No |
| 9 | creator_vault (PDA: ["creator-vault", creator]) | Yes | No |
| 10 | event_authority (PDA: ["__event_authority"]) | No | No |
| 11 | program (6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P) | No | No |
| 12 | global_volume_accumulator (PDA: ["global_volume_accumulator"]) | No | No |
| 13 | user_volume_accumulator (PDA: ["user_volume_accumulator", user]) | Yes | No |
| 14 | fee_config (PDA on fee program) | No | No |
| 15 | fee_program (pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ) | No | No |
| 16 | bonding_curve_v2 (PDA: ["bonding-curve-v2", mint]) | No | No |
Sell — Non-cashback tokens (15 accounts)
| Index | Account | Mutable | Signer |
|---|---|---|---|
| 0 | global | No | No |
| 1 | fee_recipient | Yes | No |
| 2 | mint | No | No |
| 3 | bonding_curve | Yes | No |
| 4 | associated_bonding_curve | Yes | No |
| 5 | associated_user | Yes | No |
| 6 | user | Yes | Yes |
| 7 | system_program | No | No |
| 8 | creator_vault | Yes | No |
| 9 | token_program | No | No |
| 10 | event_authority | No | No |
| 11 | program | No | No |
| 12 | fee_config | No | No |
| 13 | fee_program | No | No |
| 14 | bonding_curve_v2 | No | No |
Sell — Cashback-enabled tokens (16 accounts)
| Index | Account | Mutable | Signer |
|---|---|---|---|
| 0–13 | (same as non-cashback) | — | — |
| 14 | user_volume_accumulator (PDA: ["user_volume_accumulator", user]) | Yes | No |
| 15 | bonding_curve_v2 | No | No |
bonding_curve_v2must 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
| Instruction | Cashback Status | Total Accounts | Extra Remaining Accounts |
|---|---|---|---|
buy_exact_sol_in | Any | 17 | bonding_curve_v2 |
sell | Non-cashback (byte[82]=0) | 15 | bonding_curve_v2 |
sell | Cashback (byte[82]=1) | 16 | user_volume_accumulator + bonding_curve_v2 |
Three rules to remember:
- Always append
bonding_curve_v2(PDA:["bonding-curve-v2", mint]) as the last account on every buy and sell instruction. - Read
byte[82]from the bonding curve account data to determine cashback status — not the token program. - Cashback sell needs
user_volume_accumulatorinserted beforebonding_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.