feat: Add PumpSwap trading functionality module

This commit is contained in:
sgxiang
2025-06-06 22:29:30 +08:00
parent 63109713f4
commit 94c2a22ab7
19 changed files with 2334 additions and 21 deletions
+1
View File
@@ -1,4 +1,5 @@
pub mod pumpfun;
pub mod pumpswap;
pub mod address_lookup;
pub mod nonce_cache;
pub mod tip_cache;
+321
View File
@@ -0,0 +1,321 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_sdk::pubkey::Pubkey;
use serde::{Deserialize, Serialize};
use crate::error::{ClientError, ClientResult};
/// PumpSwap指令类型
#[derive(Debug)]
pub enum PumpSwapInstruction {
Buy(BuyEvent),
Sell(SellEvent),
CreatePool(CreatePoolEvent),
Deposit(DepositEvent),
Withdraw(WithdrawEvent),
Disable(DisableEvent),
UpdateAdmin(UpdateAdminEvent),
UpdateFeeConfig(UpdateFeeConfigEvent),
Other,
}
/// 买入事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct BuyEvent {
#[borsh(skip)]
pub slot: u64,
pub timestamp: i64,
pub base_amount_out: u64,
pub max_quote_amount_in: u64,
pub user_base_token_reserves: u64,
pub user_quote_token_reserves: u64,
pub pool_base_token_reserves: u64,
pub pool_quote_token_reserves: u64,
pub quote_amount_in: u64,
pub lp_fee_basis_points: u64,
pub lp_fee: u64,
pub protocol_fee_basis_points: u64,
pub protocol_fee: u64,
pub quote_amount_in_with_lp_fee: u64,
pub user_quote_amount_in: u64,
pub pool: Pubkey,
pub user: Pubkey,
pub user_base_token_account: Pubkey,
pub user_quote_token_account: Pubkey,
pub protocol_fee_recipient: Pubkey,
pub protocol_fee_recipient_token_account: Pubkey,
#[borsh(skip)]
pub signature: String,
}
/// 卖出事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct SellEvent {
#[borsh(skip)]
pub slot: u64,
pub timestamp: i64,
pub base_amount_in: u64,
pub min_quote_amount_out: u64,
pub user_base_token_reserves: u64,
pub user_quote_token_reserves: u64,
pub pool_base_token_reserves: u64,
pub pool_quote_token_reserves: u64,
pub quote_amount_out: u64,
pub lp_fee_basis_points: u64,
pub lp_fee: u64,
pub protocol_fee_basis_points: u64,
pub protocol_fee: u64,
pub quote_amount_out_without_lp_fee: u64,
pub user_quote_amount_out: u64,
pub pool: Pubkey,
pub user: Pubkey,
pub user_base_token_account: Pubkey,
pub user_quote_token_account: Pubkey,
pub protocol_fee_recipient: Pubkey,
pub protocol_fee_recipient_token_account: Pubkey,
#[borsh(skip)]
pub signature: String,
}
/// 创建池子事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct CreatePoolEvent {
#[borsh(skip)]
pub slot: u64,
pub timestamp: i64,
pub index: u16,
pub creator: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub base_mint_decimals: u8,
pub quote_mint_decimals: u8,
pub base_amount_in: u64,
pub quote_amount_in: u64,
pub pool_base_amount: u64,
pub pool_quote_amount: u64,
pub minimum_liquidity: u64,
pub initial_liquidity: u64,
pub lp_token_amount_out: u64,
pub pool_bump: u8,
pub pool: Pubkey,
pub lp_mint: Pubkey,
pub user_base_token_account: Pubkey,
pub user_quote_token_account: Pubkey,
#[borsh(skip)]
pub signature: String,
}
/// 存款事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct DepositEvent {
#[borsh(skip)]
pub slot: u64,
pub timestamp: i64,
pub lp_token_amount_out: u64,
pub max_base_amount_in: u64,
pub max_quote_amount_in: u64,
pub user_base_token_reserves: u64,
pub user_quote_token_reserves: u64,
pub pool_base_token_reserves: u64,
pub pool_quote_token_reserves: u64,
pub base_amount_in: u64,
pub quote_amount_in: u64,
pub lp_mint_supply: u64,
pub pool: Pubkey,
pub user: Pubkey,
pub user_base_token_account: Pubkey,
pub user_quote_token_account: Pubkey,
pub user_pool_token_account: Pubkey,
#[borsh(skip)]
pub signature: String,
}
/// 提款事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct WithdrawEvent {
#[borsh(skip)]
pub slot: u64,
pub timestamp: i64,
pub lp_token_amount_in: u64,
pub min_base_amount_out: u64,
pub min_quote_amount_out: u64,
pub user_base_token_reserves: u64,
pub user_quote_token_reserves: u64,
pub pool_base_token_reserves: u64,
pub pool_quote_token_reserves: u64,
pub base_amount_out: u64,
pub quote_amount_out: u64,
pub lp_mint_supply: u64,
pub pool: Pubkey,
pub user: Pubkey,
pub user_base_token_account: Pubkey,
pub user_quote_token_account: Pubkey,
pub user_pool_token_account: Pubkey,
#[borsh(skip)]
pub signature: String,
}
/// 禁用事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct DisableEvent {
#[borsh(skip)]
pub slot: u64,
pub timestamp: i64,
pub admin: Pubkey,
pub disable_create_pool: bool,
pub disable_deposit: bool,
pub disable_withdraw: bool,
pub disable_buy: bool,
pub disable_sell: bool,
#[borsh(skip)]
pub signature: String,
}
/// 更新管理员事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct UpdateAdminEvent {
#[borsh(skip)]
pub slot: u64,
pub timestamp: i64,
pub old_admin: Pubkey,
pub new_admin: Pubkey,
#[borsh(skip)]
pub signature: String,
}
/// 更新费用配置事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct UpdateFeeConfigEvent {
#[borsh(skip)]
pub slot: u64,
pub timestamp: i64,
pub admin: Pubkey,
pub old_lp_fee_basis_points: u64,
pub new_lp_fee_basis_points: u64,
pub old_protocol_fee_basis_points: u64,
pub new_protocol_fee_basis_points: u64,
pub old_protocol_fee_recipients: [Pubkey; 8],
pub new_protocol_fee_recipients: [Pubkey; 8],
#[borsh(skip)]
pub signature: String,
}
/// 全局配置
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct GlobalConfig {
pub admin: Pubkey,
pub lp_fee_basis_points: u64,
pub protocol_fee_basis_points: u64,
pub disable_flags: u8,
pub protocol_fee_recipients: [Pubkey; 8],
}
/// 池子信息
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct Pool {
pub index: u16,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub lp_mint: Pubkey,
pub base_mint_decimals: u8,
pub quote_mint_decimals: u8,
pub lp_mint_decimals: u8,
pub base_token_account: Pubkey,
pub quote_token_account: Pubkey,
pub bump: u8,
pub is_disabled: bool,
}
/// 事件特性
pub trait EventTrait: Sized + std::fmt::Debug {
fn from_bytes(bytes: &[u8]) -> ClientResult<Self>;
fn discriminator() -> &'static [u8];
}
/// 从字节中提取鉴别器
pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8])> {
if data.len() < length {
return None;
}
Some((&data[..length], &data[length..]))
}
/// 事件鉴别器常量
pub mod discriminators {
// 事件鉴别器
pub const BUY_EVENT: &[u8] = &[0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x67, 0xf4, 0x52, 0x1f, 0x2c, 0xf5, 0x77, 0x77];
pub const SELL_EVENT: &[u8] = &[0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x3e, 0x2f, 0x37, 0x0a, 0xa5, 0x03, 0xdc, 0x2a];
pub const CREATE_POOL_EVENT: &[u8] = &[0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xb1, 0x31, 0x0c, 0xd2, 0xa0, 0x76, 0xa7, 0x74];
pub const DEPOSIT_EVENT: &[u8] = &[0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x78, 0xf8, 0x3d, 0x53, 0x1f, 0x8e, 0x6b, 0x90];
pub const WITHDRAW_EVENT: &[u8] = &[0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x16, 0x09, 0x85, 0x1a, 0xa0, 0x2c, 0x47, 0xc0];
pub const DISABLE_EVENT: &[u8] = &[0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x6b, 0xfd, 0xc1, 0x4c, 0xe4, 0xca, 0x1b, 0x68];
pub const UPDATE_ADMIN_EVENT: &[u8] = &[0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xe1, 0x98, 0xab, 0x57, 0xf6, 0x3f, 0x42, 0xea];
pub const UPDATE_FEE_CONFIG_EVENT: &[u8] = &[0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0x5a, 0x17, 0x41, 0x23, 0x3e, 0xf4, 0xbc, 0xd0];
// 指令鉴别器
pub const BUY_IX: &[u8] = &[102,
6,
61,
18,
1,
218,
235,
234];
pub const SELL_IX: &[u8] = &[51,
230,
133,
164,
1,
127,
131,
173];
pub const CREATE_POOL_IX: &[u8] = &[233,
146,
209,
142,
207,
104,
64,
188];
pub const DEPOSIT_IX: &[u8] = &[242,
35,
198,
137,
82,
225,
242,
182];
pub const WITHDRAW_IX: &[u8] = &[183,
18,
70,
156,
148,
109,
161,
34];
pub const DISABLE_IX: &[u8] = &[107,
253,
193,
76,
228,
202,
27,
104];
pub const UPDATE_ADMIN_IX: &[u8] = &[225,
152,
171,
87,
246,
63,
66,
234];
pub const UPDATE_FEE_CONFIG_IX: &[u8] = &[90,
23,
65,
35,
62,
244,
188,
208];
}
+106
View File
@@ -0,0 +1,106 @@
use base64::engine::general_purpose;
use base64::Engine;
use crate::common::pumpswap::logs_data::{
BuyEvent, SellEvent, CreatePoolEvent, DepositEvent, WithdrawEvent,
DisableEvent, UpdateAdminEvent, UpdateFeeConfigEvent, discriminators
};
use borsh::BorshDeserialize;
pub const PROGRAM_DATA: &str = "Program data: ";
pub const PROGRAM_LOG_PREFIX: &str = "Program log: PumpSwap: ";
/// PumpSwap事件枚举
#[derive(Debug)]
pub enum PumpSwapEvent {
Buy(BuyEvent),
Sell(SellEvent),
CreatePool(CreatePoolEvent),
Deposit(DepositEvent),
Withdraw(WithdrawEvent),
Disable(DisableEvent),
UpdateAdmin(UpdateAdminEvent),
UpdateFeeConfig(UpdateFeeConfigEvent),
Error(String),
}
impl PumpSwapEvent {
/// 解析日志并提取PumpSwap事件
pub fn parse_logs(logs: &[String]) -> Vec<PumpSwapEvent> {
let mut events = Vec::new();
if logs.is_empty() {
return events;
}
for log in logs {
// 检查是否是事件日志
if let Some(event_data) = log.strip_prefix(PROGRAM_DATA) {
let borsh_bytes = match general_purpose::STANDARD.decode(event_data) {
Ok(bytes) => bytes,
Err(_) => continue,
};
// 检查鉴别器
if borsh_bytes.len() < 16 {
continue;
}
let prefix = [0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d];
let discriminator = &[&prefix[..], &borsh_bytes[..8]].concat();
let data = &borsh_bytes[8..];
// 根据鉴别器解析不同类型的事件
if discriminator == discriminators::BUY_EVENT {
if let Ok(mut event) = BuyEvent::deserialize(&mut &data[..]) {
event.signature = String::new(); // 在外部设置
events.push(PumpSwapEvent::Buy(event));
}
} else if discriminator == discriminators::SELL_EVENT {
if let Ok(mut event) = SellEvent::deserialize(&mut &data[..]) {
event.signature = String::new(); // 在外部设置
events.push(PumpSwapEvent::Sell(event));
}
} else if discriminator == discriminators::CREATE_POOL_EVENT {
if let Ok(mut event) = CreatePoolEvent::deserialize(&mut &data[..]) {
event.signature = String::new(); // 在外部设置
events.push(PumpSwapEvent::CreatePool(event));
}
} else if discriminator == discriminators::DEPOSIT_EVENT {
if let Ok(mut event) = DepositEvent::deserialize(&mut &data[..]) {
event.signature = String::new(); // 在外部设置
events.push(PumpSwapEvent::Deposit(event));
}
} else if discriminator == discriminators::WITHDRAW_EVENT {
if let Ok(mut event) = WithdrawEvent::deserialize(&mut &data[..]) {
event.signature = String::new(); // 在外部设置
events.push(PumpSwapEvent::Withdraw(event));
}
} else if discriminator == discriminators::DISABLE_EVENT {
if let Ok(mut event) = DisableEvent::deserialize(&mut &data[..]) {
event.signature = String::new(); // 在外部设置
events.push(PumpSwapEvent::Disable(event));
}
} else if discriminator == discriminators::UPDATE_ADMIN_EVENT {
if let Ok(mut event) = UpdateAdminEvent::deserialize(&mut &data[..]) {
event.signature = String::new(); // 在外部设置
events.push(PumpSwapEvent::UpdateAdmin(event));
}
} else if discriminator == discriminators::UPDATE_FEE_CONFIG_EVENT {
if let Ok(mut event) = UpdateFeeConfigEvent::deserialize(&mut &data[..]) {
event.signature = String::new(); // 在外部设置
events.push(PumpSwapEvent::UpdateFeeConfig(event));
}
}
} else if let Some(event_log) = log.strip_prefix(PROGRAM_LOG_PREFIX) {
// 处理程序日志中的事件信息
if event_log.contains("BuyEvent") {
// 这里可以添加从日志文本中解析事件的逻辑
// 例如使用正则表达式提取关键信息
} else if event_log.contains("SellEvent") {
// 同上
}
// 其他事件类型...
}
}
events
}
}
+43
View File
@@ -0,0 +1,43 @@
use crate::common::pumpswap::logs_data::PumpSwapInstruction;
use crate::common::pumpswap::logs_parser::parse_pumpswap_instruction;
use crate::common::pumpswap::logs_events::PumpSwapEvent;
use crate::constants::pumpswap::accounts;
use crate::error::ClientResult;
use solana_sdk::transaction::VersionedTransaction;
pub struct LogFilter;
impl LogFilter {
/// 解析PumpSwap编译后的指令并返回指令类型和数据
pub fn parse_pumpswap_compiled_instruction(
versioned_tx: VersionedTransaction) -> ClientResult<Vec<PumpSwapInstruction>> {
let compiled_instructions = versioned_tx.message.instructions();
let accounts = versioned_tx.message.static_account_keys();
let program_id = accounts::AMM_PROGRAM;
let pump_index = accounts.iter().position(|key| key == &program_id);
let mut instructions: Vec<PumpSwapInstruction> = Vec::new();
if let Some(index) = pump_index {
for instruction in compiled_instructions {
if instruction.program_id_index as usize == index {
let all_accounts_valid = instruction.accounts.iter()
.all(|&acc_idx| (acc_idx as usize) < accounts.len());
if !all_accounts_valid {
continue;
}
if let Some(parsed_instruction) = parse_pumpswap_instruction(instruction, accounts) {
instructions.push(parsed_instruction);
}
}
}
}
Ok(instructions)
}
/// 解析PumpSwap交易日志并返回事件
pub fn parse_pumpswap_logs(logs: &[String]) -> Vec<PumpSwapEvent> {
PumpSwapEvent::parse_logs(logs)
}
}
+292
View File
@@ -0,0 +1,292 @@
use crate::error::ClientResult;
use crate::common::pumpswap::{
logs_data::{
PumpSwapInstruction,
BuyEvent, SellEvent, CreatePoolEvent, DepositEvent, WithdrawEvent,
DisableEvent, UpdateAdminEvent, UpdateFeeConfigEvent, discriminators
},
logs_events::PumpSwapEvent
};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::instruction::CompiledInstruction;
use std::time::{SystemTime, UNIX_EPOCH};
/// 处理PumpSwap日志并调用回调函数
pub async fn process_pumpswap_logs<F>(
signature: &str,
logs: Vec<String>,
slot: Option<u64>,
callback: F,
) -> ClientResult<()>
where
F: Fn(&str, PumpSwapEvent) + Send + Sync,
{
let events = PumpSwapEvent::parse_logs(&logs);
for mut event in events {
// 设置签名和slot
match &mut event {
PumpSwapEvent::Buy(e) => {
e.signature = signature.to_string();
if let Some(s) = slot {
e.slot = s;
}
},
PumpSwapEvent::Sell(e) => {
e.signature = signature.to_string();
if let Some(s) = slot {
e.slot = s;
}
},
PumpSwapEvent::CreatePool(e) => {
e.signature = signature.to_string();
if let Some(s) = slot {
e.slot = s;
}
},
PumpSwapEvent::Deposit(e) => {
e.signature = signature.to_string();
if let Some(s) = slot {
e.slot = s;
}
},
PumpSwapEvent::Withdraw(e) => {
e.signature = signature.to_string();
if let Some(s) = slot {
e.slot = s;
}
},
PumpSwapEvent::Disable(e) => {
e.signature = signature.to_string();
if let Some(s) = slot {
e.slot = s;
}
},
PumpSwapEvent::UpdateAdmin(e) => {
e.signature = signature.to_string();
if let Some(s) = slot {
e.slot = s;
}
},
PumpSwapEvent::UpdateFeeConfig(e) => {
e.signature = signature.to_string();
if let Some(s) = slot {
e.slot = s;
}
},
_ => {}
}
callback(signature, event);
}
Ok(())
}
/// 获取当前时间戳
fn current_timestamp() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs() as i64
}
/// 从指令中解析PumpSwap指令
pub fn parse_pumpswap_instruction(instruction: &CompiledInstruction, accounts: &[Pubkey]) -> Option<PumpSwapInstruction> {
if instruction.data.len() < 8 {
return None;
}
let discriminator = &instruction.data[..8];
let data = &instruction.data[8..];
match discriminator {
d if d == discriminators::BUY_IX => {
// buy指令参数: base_amount_out: u64, max_quote_amount_in: u64
// 账户顺序:pool, user, global_config, base_mint, quote_mint, user_base_token_account,
// user_quote_token_account, pool_base_token_account, pool_quote_token_account,
// protocol_fee_recipient, protocol_fee_recipient_token_account, ...
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let base_amount_out = u64::from_le_bytes(data[0..8].try_into().ok()?);
let max_quote_amount_in = u64::from_le_bytes(data[8..16].try_into().ok()?);
Some(PumpSwapInstruction::Buy(BuyEvent {
base_amount_out,
max_quote_amount_in,
pool: accounts[0],
user: accounts[1],
user_base_token_account: accounts[5],
user_quote_token_account: accounts[6],
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
timestamp: current_timestamp(),
..Default::default()
}))
},
d if d == discriminators::SELL_IX => {
// sell指令参数: base_amount_in: u64, min_quote_amount_out: u64
// 账户顺序:pool, user, global_config, base_mint, quote_mint, user_base_token_account,
// user_quote_token_account, pool_base_token_account, pool_quote_token_account,
// protocol_fee_recipient, protocol_fee_recipient_token_account, ...
if data.len() < 16 || accounts.len() < 11 {
return None;
}
let base_amount_in = u64::from_le_bytes(data[0..8].try_into().ok()?);
let min_quote_amount_out = u64::from_le_bytes(data[8..16].try_into().ok()?);
Some(PumpSwapInstruction::Sell(SellEvent {
base_amount_in,
min_quote_amount_out,
pool: accounts[0],
user: accounts[1],
user_base_token_account: accounts[5],
user_quote_token_account: accounts[6],
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
timestamp: current_timestamp(),
..Default::default()
}))
},
d if d == discriminators::CREATE_POOL_IX => {
// create_pool指令参数: index: u16, base_amount_in: u64, quote_amount_in: u64
// 账户顺序:pool, global_config, creator, base_mint, quote_mint, lp_mint,
// user_base_token_account, user_quote_token_account, user_pool_token_account,
// pool_base_token_account, pool_quote_token_account, ...
if data.len() < 18 || accounts.len() < 11 {
return None;
}
let index = u16::from_le_bytes(data[0..2].try_into().ok()?);
let base_amount_in = u64::from_le_bytes(data[2..10].try_into().ok()?);
let quote_amount_in = u64::from_le_bytes(data[10..18].try_into().ok()?);
Some(PumpSwapInstruction::CreatePool(CreatePoolEvent {
index,
base_amount_in,
quote_amount_in,
pool: accounts[0],
creator: accounts[2],
base_mint: accounts[3],
quote_mint: accounts[4],
lp_mint: accounts[5],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
timestamp: current_timestamp(),
..Default::default()
}))
},
d if d == discriminators::DEPOSIT_IX => {
// deposit指令参数: lp_token_amount_out: u64, max_base_amount_in: u64, max_quote_amount_in: u64
// 账户顺序:pool, global_config, user, base_mint, quote_mint, lp_mint,
// user_base_token_account, user_quote_token_account, user_pool_token_account,
// pool_base_token_account, pool_quote_token_account, ...
if data.len() < 24 || accounts.len() < 11 {
return None;
}
let lp_token_amount_out = u64::from_le_bytes(data[0..8].try_into().ok()?);
let max_base_amount_in = u64::from_le_bytes(data[8..16].try_into().ok()?);
let max_quote_amount_in = u64::from_le_bytes(data[16..24].try_into().ok()?);
Some(PumpSwapInstruction::Deposit(DepositEvent {
lp_token_amount_out,
max_base_amount_in,
max_quote_amount_in,
pool: accounts[0],
user: accounts[2],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
user_pool_token_account: accounts[8],
timestamp: current_timestamp(),
..Default::default()
}))
},
d if d == discriminators::WITHDRAW_IX => {
// withdraw指令参数: lp_token_amount_in: u64, min_base_amount_out: u64, min_quote_amount_out: u64
// 账户顺序:pool, global_config, user, base_mint, quote_mint, lp_mint,
// user_base_token_account, user_quote_token_account, user_pool_token_account,
// pool_base_token_account, pool_quote_token_account, ...
if data.len() < 24 || accounts.len() < 11 {
return None;
}
let lp_token_amount_in = u64::from_le_bytes(data[0..8].try_into().ok()?);
let min_base_amount_out = u64::from_le_bytes(data[8..16].try_into().ok()?);
let min_quote_amount_out = u64::from_le_bytes(data[16..24].try_into().ok()?);
Some(PumpSwapInstruction::Withdraw(WithdrawEvent {
lp_token_amount_in,
min_base_amount_out,
min_quote_amount_out,
pool: accounts[0],
user: accounts[2],
user_base_token_account: accounts[6],
user_quote_token_account: accounts[7],
user_pool_token_account: accounts[8],
timestamp: current_timestamp(),
..Default::default()
}))
},
d if d == discriminators::DISABLE_IX => {
// disable指令参数: disable_create_pool: bool, disable_deposit: bool, disable_withdraw: bool, disable_buy: bool, disable_sell: bool
// 账户顺序:admin, global_config, event_authority, program
if data.len() < 5 || accounts.len() < 2 {
return None;
}
let disable_create_pool = data[0] != 0;
let disable_deposit = data[1] != 0;
let disable_withdraw = data[2] != 0;
let disable_buy = data[3] != 0;
let disable_sell = data[4] != 0;
Some(PumpSwapInstruction::Disable(DisableEvent {
disable_create_pool,
disable_deposit,
disable_withdraw,
disable_buy,
disable_sell,
admin: accounts[0],
timestamp: current_timestamp(),
..Default::default()
}))
},
d if d == discriminators::UPDATE_ADMIN_IX => {
// update_admin指令参数: 无
// 账户顺序:admin, global_config, new_admin, event_authority, program
if accounts.len() < 3 {
return None;
}
Some(PumpSwapInstruction::UpdateAdmin(UpdateAdminEvent {
old_admin: accounts[0],
new_admin: accounts[2],
timestamp: current_timestamp(),
..Default::default()
}))
},
d if d == discriminators::UPDATE_FEE_CONFIG_IX => {
// update_fee_config指令参数: lp_fee_basis_points: u64, protocol_fee_basis_points: u64, protocol_fee_recipients: [pubkey; 8]
// 账户顺序:admin, global_config, event_authority, program
if data.len() < 272 || accounts.len() < 2 { // 8 + 8 + 32*8 = 272 bytes
return None;
}
let lp_fee_basis_points = u64::from_le_bytes(data[0..8].try_into().ok()?);
let protocol_fee_basis_points = u64::from_le_bytes(data[8..16].try_into().ok()?);
let mut protocol_fee_recipients = [Pubkey::default(); 8];
for i in 0..8 {
let start = 16 + i * 32;
let end = start + 32;
if let Ok(pubkey_bytes) = data[start..end].try_into() {
protocol_fee_recipients[i] = Pubkey::new_from_array(pubkey_bytes);
}
}
Some(PumpSwapInstruction::UpdateFeeConfig(UpdateFeeConfigEvent {
admin: accounts[0],
new_lp_fee_basis_points: lp_fee_basis_points,
new_protocol_fee_basis_points: protocol_fee_basis_points,
new_protocol_fee_recipients: protocol_fee_recipients,
timestamp: current_timestamp(),
..Default::default()
}))
},
_ => Some(PumpSwapInstruction::Other),
}
}
+134
View File
@@ -0,0 +1,134 @@
use solana_client::{
nonblocking::pubsub_client::PubsubClient,
rpc_config::{RpcTransactionLogsConfig, RpcTransactionLogsFilter}
};
use solana_sdk::commitment_config::CommitmentConfig;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use futures::StreamExt;
use crate::common::pumpswap::{
logs_events::PumpSwapEvent,
logs_filters::LogFilter
};
use crate::constants::pumpswap::accounts;
/// 订阅句柄,包含任务和取消订阅逻辑
pub struct SubscriptionHandle {
pub task: JoinHandle<()>,
pub unsub_fn: Box<dyn Fn() + Send>,
}
impl SubscriptionHandle {
pub async fn shutdown(self) {
(self.unsub_fn)();
self.task.abort();
}
}
/// 创建PubSub客户端
pub async fn create_pubsub_client(ws_url: &str) -> PubsubClient {
PubsubClient::new(ws_url).await.unwrap()
}
/// 启动PumpSwap代币订阅
pub async fn tokens_subscription<F>(
ws_url: &str,
commitment: CommitmentConfig,
callback: F,
) -> Result<SubscriptionHandle, Box<dyn std::error::Error>>
where
F: Fn(PumpSwapEvent) + Send + Sync + 'static,
{
// 使用constants中定义的AMM_PROGRAM
let program_address = accounts::AMM_PROGRAM.to_string();
let logs_filter = RpcTransactionLogsFilter::Mentions(vec![program_address]);
let logs_config = RpcTransactionLogsConfig {
commitment: Some(commitment),
};
// 创建PubsubClient
let sub_client = Arc::new(PubsubClient::new(ws_url).await.unwrap());
let sub_client_clone = Arc::clone(&sub_client);
// 创建用于取消订阅的通道
let (unsub_tx, _) = mpsc::channel(1);
// 启动订阅任务
let task = tokio::spawn(async move {
let (mut stream, _) = sub_client_clone.logs_subscribe(logs_filter, logs_config).await.unwrap();
loop {
let msg = stream.next().await;
match msg {
Some(msg) => {
if let Some(_err) = msg.value.err {
continue;
}
let events = LogFilter::parse_pumpswap_logs(&msg.value.logs);
for mut event in events {
// 设置签名和slot
match &mut event {
PumpSwapEvent::Buy(e) => {
e.signature = msg.value.signature.clone();
e.slot = msg.context.slot;
},
PumpSwapEvent::Sell(e) => {
e.signature = msg.value.signature.clone();
e.slot = msg.context.slot;
},
PumpSwapEvent::CreatePool(e) => {
e.signature = msg.value.signature.clone();
e.slot = msg.context.slot;
},
PumpSwapEvent::Deposit(e) => {
e.signature = msg.value.signature.clone();
e.slot = msg.context.slot;
},
PumpSwapEvent::Withdraw(e) => {
e.signature = msg.value.signature.clone();
e.slot = msg.context.slot;
},
PumpSwapEvent::Disable(e) => {
e.signature = msg.value.signature.clone();
e.slot = msg.context.slot;
},
PumpSwapEvent::UpdateAdmin(e) => {
e.signature = msg.value.signature.clone();
e.slot = msg.context.slot;
},
PumpSwapEvent::UpdateFeeConfig(e) => {
e.signature = msg.value.signature.clone();
e.slot = msg.context.slot;
},
_ => {}
}
callback(event);
}
}
None => {
println!("PumpSwap subscription stream ended");
}
}
}
});
// 返回订阅句柄和取消订阅逻辑
Ok(SubscriptionHandle {
task,
unsub_fn: Box::new(move || {
let _ = unsub_tx.try_send(());
}),
})
}
/// 停止订阅
pub async fn stop_subscription(handle: SubscriptionHandle) {
handle.shutdown().await;
}
+11
View File
@@ -0,0 +1,11 @@
pub mod logs_data;
pub mod logs_parser;
pub mod logs_filters;
pub mod logs_subscribe;
pub mod logs_events;
pub use logs_data::*;
pub use logs_parser::*;
pub use logs_filters::*;
pub use logs_subscribe::*;
pub use logs_events::*;