feat: PumpFun & PumpSwap Cashback support
- Sync IDL from pump-public-docs into idl/ - Add is_cashback_coin to BondingCurve and Pool - Pump sell: append UserVolumeAccumulator as remaining account when cashback coin - PumpSwap buy/sell: append cashback remaining accounts after fee_config/fee_program - Add claim_cashback instructions and TradingClient.claim_cashback_pumpfun/pumpswap() - Add docs/PUMP_CASHBACK_README.md Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -263,7 +263,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
global_constants::FEE_RECIPIENT_META
|
||||
};
|
||||
|
||||
let accounts: [AccountMeta; 14] = [
|
||||
let mut accounts: Vec<AccountMeta> = vec![
|
||||
global_constants::GLOBAL_ACCOUNT_META,
|
||||
fee_recipient_meta,
|
||||
AccountMeta::new_readonly(params.input_mint, false),
|
||||
@@ -280,10 +280,17 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
accounts::FEE_PROGRAM_META,
|
||||
];
|
||||
|
||||
// Cashback: Bonding Curve Sell expects UserVolumeAccumulator PDA at 0th remaining account (writable)
|
||||
if bonding_curve.is_cashback_coin {
|
||||
let user_volume_accumulator =
|
||||
get_user_volume_accumulator_pda(¶ms.payer.pubkey()).unwrap();
|
||||
accounts.push(AccountMeta::new(user_volume_accumulator, false));
|
||||
}
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&sell_data,
|
||||
accounts.to_vec(),
|
||||
accounts,
|
||||
));
|
||||
|
||||
// Optional: Close token account
|
||||
@@ -302,3 +309,21 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
/// Claim cashback for Bonding Curve (Pump program). Transfers native lamports from UserVolumeAccumulator to user.
|
||||
pub fn claim_cashback_pumpfun_instruction(payer: &Pubkey) -> Option<Instruction> {
|
||||
const CLAIM_CASHBACK_DISCRIMINATOR: [u8; 8] = [37, 58, 35, 126, 190, 53, 228, 197];
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(payer)?;
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*payer, true), // user (signer, writable)
|
||||
AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable, not signer)
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
accounts::EVENT_AUTHORITY_META,
|
||||
accounts::PUMPFUN_META,
|
||||
];
|
||||
Some(Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&CLAIM_CASHBACK_DISCRIMINATOR,
|
||||
accounts,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
constants::trade::trade::DEFAULT_SLIPPAGE,
|
||||
instruction::utils::pumpswap::{
|
||||
accounts, fee_recipient_ata, get_user_volume_accumulator_pda, BUY_DISCRIMINATOR,
|
||||
accounts, fee_recipient_ata, get_user_volume_accumulator_pda,
|
||||
get_user_volume_accumulator_wsol_ata, BUY_DISCRIMINATOR,
|
||||
BUY_EXACT_QUOTE_IN_DISCRIMINATOR, SELL_DISCRIMINATOR,
|
||||
},
|
||||
trading::{
|
||||
@@ -183,6 +184,12 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
}
|
||||
accounts.push(accounts::FEE_CONFIG_META);
|
||||
accounts.push(accounts::FEE_PROGRAM_META);
|
||||
// Cashback: remaining_accounts[0] = WSOL ATA of UserVolumeAccumulator (after named accounts per IDL)
|
||||
if protocol_params.is_cashback_coin {
|
||||
if let Some(wsol_ata) = get_user_volume_accumulator_wsol_ata(¶ms.payer.pubkey()) {
|
||||
accounts.push(AccountMeta::new(wsol_ata, false));
|
||||
}
|
||||
}
|
||||
|
||||
// Create instruction data
|
||||
let mut data = [0u8; 24];
|
||||
@@ -373,9 +380,18 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
accounts.push(accounts::FEE_CONFIG_META);
|
||||
accounts.push(accounts::FEE_PROGRAM_META);
|
||||
// Cashback: remaining_accounts[0] = WSOL ATA of UserVolumeAccumulator, remaining_accounts[1] = UserVolumeAccumulator PDA
|
||||
if protocol_params.is_cashback_coin {
|
||||
if let (Some(wsol_ata), Some(accumulator)) = (
|
||||
get_user_volume_accumulator_wsol_ata(¶ms.payer.pubkey()),
|
||||
get_user_volume_accumulator_pda(¶ms.payer.pubkey()),
|
||||
) {
|
||||
accounts.push(AccountMeta::new(wsol_ata, false));
|
||||
accounts.push(AccountMeta::new(accumulator, false));
|
||||
}
|
||||
}
|
||||
|
||||
// Create instruction data
|
||||
let mut data = [0u8; 24];
|
||||
@@ -420,3 +436,38 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
/// Claim cashback for PumpSwap (AMM). Transfers WSOL from UserVolumeAccumulator's WSOL ATA to user's WSOL ATA.
|
||||
/// Caller should ensure user's WSOL ATA exists (e.g. create idempotent ATA instruction) before this instruction.
|
||||
pub fn claim_cashback_pumpswap_instruction(
|
||||
payer: &Pubkey,
|
||||
quote_mint: Pubkey,
|
||||
quote_token_program: Pubkey,
|
||||
) -> Option<solana_sdk::instruction::Instruction> {
|
||||
const CLAIM_CASHBACK_DISCRIMINATOR: [u8; 8] = [37, 58, 35, 126, 190, 53, 228, 197];
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(payer)?;
|
||||
let user_volume_accumulator_wsol_ata = get_user_volume_accumulator_wsol_ata(payer)?;
|
||||
let user_wsol_ata = crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
payer,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
// IDL order: user, user_volume_accumulator, quote_mint, quote_token_program,
|
||||
// user_volume_accumulator_wsol_token_account, user_wsol_token_account, system_program, event_authority, program
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*payer, true), // user (signer, writable)
|
||||
AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable)
|
||||
AccountMeta::new_readonly(quote_mint, false),
|
||||
AccountMeta::new_readonly(quote_token_program, false),
|
||||
AccountMeta::new(user_volume_accumulator_wsol_ata, false), // writable
|
||||
AccountMeta::new(user_wsol_ata, false), // writable
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
accounts::EVENT_AUTHORITY_META,
|
||||
accounts::AMM_PROGRAM_META,
|
||||
];
|
||||
Some(solana_sdk::instruction::Instruction::new_with_bytes(
|
||||
accounts::AMM_PROGRAM,
|
||||
&CLAIM_CASHBACK_DISCRIMINATOR,
|
||||
accounts,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -185,6 +185,16 @@ pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Option<Pubkey> {
|
||||
)
|
||||
}
|
||||
|
||||
/// WSOL ATA of UserVolumeAccumulator for Pump AMM (used for cashback remaining accounts).
|
||||
pub fn get_user_volume_accumulator_wsol_ata(user: &Pubkey) -> Option<Pubkey> {
|
||||
let accumulator = get_user_volume_accumulator_pda(user)?;
|
||||
Some(crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&accumulator,
|
||||
&crate::constants::WSOL_TOKEN_ACCOUNT,
|
||||
&crate::constants::TOKEN_PROGRAM,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_global_volume_accumulator_pda() -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 1] = &[&seeds::GLOBAL_VOLUME_ACCUMULATOR_SEED];
|
||||
let program_id: &Pubkey = &&accounts::AMM_PROGRAM;
|
||||
|
||||
@@ -15,9 +15,11 @@ pub struct Pool {
|
||||
pub lp_supply: u64,
|
||||
pub coin_creator: Pubkey,
|
||||
pub is_mayhem_mode: bool,
|
||||
/// Whether this pool's coin has cashback enabled
|
||||
pub is_cashback_coin: bool,
|
||||
}
|
||||
|
||||
pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1;
|
||||
pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1 + 1;
|
||||
|
||||
pub fn pool_decode(data: &[u8]) -> Option<Pool> {
|
||||
if data.len() < POOL_SIZE {
|
||||
|
||||
Reference in New Issue
Block a user