use super::program_ids;
use super::utils::*;
use crate::core::events::*;
use solana_sdk::{pubkey::Pubkey, signature::Signature};
pub mod discriminators {
pub const BUY: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
pub const SELL: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
pub const CREATE_POOL: [u8; 8] = [233, 146, 209, 142, 207, 104, 64, 188];
pub const BUY_EXACT_QUOTE_IN: [u8; 8] = [198, 46, 21, 82, 180, 217, 232, 112];
pub const DEPOSIT: [u8; 8] = [242, 35, 198, 137, 82, 225, 242, 182];
pub const WITHDRAW: [u8; 8] = [183, 18, 70, 156, 148, 109, 161, 34];
}
pub const PROGRAM_ID_PUBKEY: Pubkey = program_ids::PUMPSWAP_PROGRAM_ID;
fn fill_buy_upgrade_accounts(ev: &mut PumpSwapBuyEvent, accounts: &[Pubkey]) {
if accounts.len() >= 27 {
ev.pool_v2 = get_account(accounts, 24).unwrap_or_default();
ev.fee_recipient = get_account(accounts, 25).unwrap_or_default();
ev.fee_recipient_quote_token_account = get_account(accounts, 26).unwrap_or_default();
} else if accounts.len() >= 26 {
ev.pool_v2 = get_account(accounts, 23).unwrap_or_default();
ev.fee_recipient = get_account(accounts, 24).unwrap_or_default();
ev.fee_recipient_quote_token_account = get_account(accounts, 25).unwrap_or_default();
} else if accounts.len() >= 24 {
ev.pool_v2 = get_account(accounts, 23).unwrap_or_default();
}
}
fn fill_sell_upgrade_accounts(ev: &mut PumpSwapSellEvent, accounts: &[Pubkey]) {
if accounts.len() >= 26 {
ev.pool_v2 = get_account(accounts, 23).unwrap_or_default();
ev.fee_recipient = get_account(accounts, 24).unwrap_or_default();
ev.fee_recipient_quote_token_account = get_account(accounts, 25).unwrap_or_default();
} else if accounts.len() >= 24 {
ev.pool_v2 = get_account(accounts, 21).unwrap_or_default();
ev.fee_recipient = get_account(accounts, 22).unwrap_or_default();
ev.fee_recipient_quote_token_account = get_account(accounts, 23).unwrap_or_default();
} else if accounts.len() >= 22 {
ev.pool_v2 = get_account(accounts, 21).unwrap_or_default();
}
}
pub fn parse_instruction(
instruction_data: &[u8],
accounts: &[Pubkey],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
) -> Option<DexEvent> {
if instruction_data.len() < 8 {
return None;
}
let discriminator: [u8; 8] = instruction_data[0..8].try_into().ok()?;
let data = &instruction_data[8..];
match discriminator {
discriminators::BUY => {
parse_buy_instruction(data, accounts, signature, slot, tx_index, block_time_us)
}
discriminators::BUY_EXACT_QUOTE_IN => parse_buy_exact_quote_in_instruction(
data,
accounts,
signature,
slot,
tx_index,
block_time_us,
),
discriminators::SELL => {
parse_sell_instruction(data, accounts, signature, slot, tx_index, block_time_us)
}
discriminators::CREATE_POOL => {
parse_create_pool_instruction(data, accounts, signature, slot, tx_index, block_time_us)
}
discriminators::DEPOSIT => {
parse_deposit_instruction(data, accounts, signature, slot, tx_index, block_time_us)
}
discriminators::WITHDRAW => {
parse_withdraw_instruction(data, accounts, signature, slot, tx_index, block_time_us)
}
_ => None,
}
}
#[allow(dead_code)]
fn parse_buy_instruction(
data: &[u8],
accounts: &[Pubkey],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
) -> Option<DexEvent> {
if accounts.len() < 13 {
return None;
}
let (base_amount, quote_amount) = if data.len() >= 16 {
(read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
} else {
(0, 0)
};
let metadata = create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), 0);
let mut ev = PumpSwapBuyEvent {
metadata,
pool: get_account(accounts, 0).unwrap_or_default(),
user: get_account(accounts, 1).unwrap_or_default(),
base_mint: get_account(accounts, 3).unwrap_or_default(),
quote_mint: get_account(accounts, 4).unwrap_or_default(),
user_base_token_account: get_account(accounts, 5).unwrap_or_default(),
user_quote_token_account: get_account(accounts, 6).unwrap_or_default(),
pool_base_token_account: get_account(accounts, 7).unwrap_or_default(),
pool_quote_token_account: get_account(accounts, 8).unwrap_or_default(),
protocol_fee_recipient: get_account(accounts, 9).unwrap_or_default(),
protocol_fee_recipient_token_account: get_account(accounts, 10).unwrap_or_default(),
base_token_program: get_account(accounts, 11).unwrap_or_default(),
quote_token_program: get_account(accounts, 12).unwrap_or_default(),
base_amount_out: base_amount,
max_quote_amount_in: quote_amount,
..Default::default()
};
if accounts.len() >= 19 {
ev.coin_creator_vault_ata = get_account(accounts, 17).unwrap_or_default();
ev.coin_creator_vault_authority = get_account(accounts, 18).unwrap_or_default();
}
fill_buy_upgrade_accounts(&mut ev, accounts);
Some(DexEvent::PumpSwapBuy(ev))
}
#[allow(dead_code)]
fn parse_buy_exact_quote_in_instruction(
data: &[u8],
accounts: &[Pubkey],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
) -> Option<DexEvent> {
if accounts.len() < 13 {
return None;
}
let (quote_amount, base_amount) = if data.len() >= 16 {
(read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
} else {
(0, 0)
};
let metadata = create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), 0);
let mut ev = PumpSwapBuyEvent {
metadata,
pool: get_account(accounts, 0).unwrap_or_default(),
user: get_account(accounts, 1).unwrap_or_default(),
base_mint: get_account(accounts, 3).unwrap_or_default(),
quote_mint: get_account(accounts, 4).unwrap_or_default(),
user_base_token_account: get_account(accounts, 5).unwrap_or_default(),
user_quote_token_account: get_account(accounts, 6).unwrap_or_default(),
pool_base_token_account: get_account(accounts, 7).unwrap_or_default(),
pool_quote_token_account: get_account(accounts, 8).unwrap_or_default(),
protocol_fee_recipient: get_account(accounts, 9).unwrap_or_default(),
protocol_fee_recipient_token_account: get_account(accounts, 10).unwrap_or_default(),
base_token_program: get_account(accounts, 11).unwrap_or_default(),
quote_token_program: get_account(accounts, 12).unwrap_or_default(),
base_amount_out: base_amount,
max_quote_amount_in: quote_amount,
..Default::default()
};
if accounts.len() >= 19 {
ev.coin_creator_vault_ata = get_account(accounts, 17).unwrap_or_default();
ev.coin_creator_vault_authority = get_account(accounts, 18).unwrap_or_default();
}
fill_buy_upgrade_accounts(&mut ev, accounts);
Some(DexEvent::PumpSwapBuy(ev))
}
#[allow(dead_code)]
fn parse_sell_instruction(
data: &[u8],
accounts: &[Pubkey],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
) -> Option<DexEvent> {
if accounts.len() < 13 {
return None;
}
let (base_amount, quote_amount) = if data.len() >= 16 {
(read_u64_le(data, 0).unwrap_or(0), read_u64_le(data, 8).unwrap_or(0))
} else {
(0, 0)
};
let metadata = create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), 0);
let mut ev = PumpSwapSellEvent {
metadata,
pool: get_account(accounts, 0).unwrap_or_default(),
user: get_account(accounts, 1).unwrap_or_default(),
base_mint: get_account(accounts, 3).unwrap_or_default(),
quote_mint: get_account(accounts, 4).unwrap_or_default(),
user_base_token_account: get_account(accounts, 5).unwrap_or_default(),
user_quote_token_account: get_account(accounts, 6).unwrap_or_default(),
pool_base_token_account: get_account(accounts, 7).unwrap_or_default(),
pool_quote_token_account: get_account(accounts, 8).unwrap_or_default(),
protocol_fee_recipient: get_account(accounts, 9).unwrap_or_default(),
protocol_fee_recipient_token_account: get_account(accounts, 10).unwrap_or_default(),
base_token_program: get_account(accounts, 11).unwrap_or_default(),
quote_token_program: get_account(accounts, 12).unwrap_or_default(),
base_amount_in: base_amount,
min_quote_amount_out: quote_amount,
..Default::default()
};
if accounts.len() >= 19 {
ev.coin_creator_vault_ata = get_account(accounts, 17).unwrap_or_default();
ev.coin_creator_vault_authority = get_account(accounts, 18).unwrap_or_default();
}
fill_sell_upgrade_accounts(&mut ev, accounts);
Some(DexEvent::PumpSwapSell(ev))
}
#[allow(dead_code)]
fn parse_create_pool_instruction(
_data: &[u8],
accounts: &[Pubkey],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
) -> Option<DexEvent> {
if accounts.len() < 5 {
return None;
}
let metadata = create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), 0);
Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
metadata,
creator: get_account(accounts, 0).unwrap_or_default(),
base_mint: get_account(accounts, 2).unwrap_or_default(),
quote_mint: get_account(accounts, 3).unwrap_or_default(),
..Default::default()
}))
}
#[allow(dead_code)]
fn parse_deposit_instruction(
_data: &[u8],
accounts: &[Pubkey],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
) -> Option<DexEvent> {
if accounts.len() < 8 {
return None;
}
let metadata = create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), 0);
Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
metadata,
pool: get_account(accounts, 0).unwrap_or_default(),
user: get_account(accounts, 1).unwrap_or_default(),
user_base_token_account: get_account(accounts, 4).unwrap_or_default(),
user_quote_token_account: get_account(accounts, 5).unwrap_or_default(),
user_pool_token_account: get_account(accounts, 6).unwrap_or_default(),
..Default::default()
}))
}
#[allow(dead_code)]
fn parse_withdraw_instruction(
_data: &[u8],
accounts: &[Pubkey],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
) -> Option<DexEvent> {
if accounts.len() < 8 {
return None;
}
let metadata = create_metadata(signature, slot, tx_index, block_time_us.unwrap_or_default(), 0);
Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
metadata,
pool: get_account(accounts, 0).unwrap_or_default(),
user: get_account(accounts, 1).unwrap_or_default(),
user_base_token_account: get_account(accounts, 4).unwrap_or_default(),
user_quote_token_account: get_account(accounts, 5).unwrap_or_default(),
user_pool_token_account: get_account(accounts, 6).unwrap_or_default(),
..Default::default()
}))
}
#[cfg(test)]
mod tests {
use super::*;
fn data(first: u64, second: u64) -> Vec<u8> {
let mut out = Vec::with_capacity(16);
out.extend_from_slice(&first.to_le_bytes());
out.extend_from_slice(&second.to_le_bytes());
out
}
fn accounts(n: usize) -> Vec<Pubkey> {
(0..n).map(|_| Pubkey::new_unique()).collect()
}
#[test]
fn pumpswap_buy_maps_non_cashback_upgrade_tail() {
let acc = accounts(26);
let ev = parse_buy_instruction(&data(100, 200), &acc, Signature::default(), 1, 0, None)
.expect("buy");
match ev {
DexEvent::PumpSwapBuy(t) => {
assert_eq!(t.pool_v2, acc[23]);
assert_eq!(t.fee_recipient, acc[24]);
assert_eq!(t.fee_recipient_quote_token_account, acc[25]);
}
other => panic!("expected PumpSwapBuy, got {other:?}"),
}
}
#[test]
fn pumpswap_buy_maps_cashback_upgrade_tail() {
let acc = accounts(27);
let ev = parse_buy_instruction(&data(100, 200), &acc, Signature::default(), 1, 0, None)
.expect("buy");
match ev {
DexEvent::PumpSwapBuy(t) => {
assert_eq!(t.pool_v2, acc[24]);
assert_eq!(t.fee_recipient, acc[25]);
assert_eq!(t.fee_recipient_quote_token_account, acc[26]);
}
other => panic!("expected PumpSwapBuy, got {other:?}"),
}
}
#[test]
fn pumpswap_sell_maps_non_cashback_upgrade_tail() {
let acc = accounts(24);
let ev = parse_sell_instruction(&data(100, 200), &acc, Signature::default(), 1, 0, None)
.expect("sell");
match ev {
DexEvent::PumpSwapSell(t) => {
assert_eq!(t.pool_v2, acc[21]);
assert_eq!(t.fee_recipient, acc[22]);
assert_eq!(t.fee_recipient_quote_token_account, acc[23]);
}
other => panic!("expected PumpSwapSell, got {other:?}"),
}
}
#[test]
fn pumpswap_sell_maps_cashback_upgrade_tail() {
let acc = accounts(26);
let ev = parse_sell_instruction(&data(100, 200), &acc, Signature::default(), 1, 0, None)
.expect("sell");
match ev {
DexEvent::PumpSwapSell(t) => {
assert_eq!(t.pool_v2, acc[23]);
assert_eq!(t.fee_recipient, acc[24]);
assert_eq!(t.fee_recipient_quote_token_account, acc[25]);
}
other => panic!("expected PumpSwapSell, got {other:?}"),
}
}
}