Merge commit '1db60c2d659a326ec02168c60d3205d64a152feb'

This commit is contained in:
sgxiang
2025-06-12 00:08:19 +08:00
36 changed files with 3705 additions and 324 deletions
+2
View File
@@ -87,6 +87,8 @@ tokio-tungstenite = { version = "0.26.1", features = ["native-tls"] }
indicatif = "0.17.11"
toml = "0.8.20"
pumpfun_program = { version = "4.2.0", package = "pumpfun" }
+1 -1
View File
@@ -28,7 +28,7 @@
use serde::{Serialize, Deserialize};
use solana_sdk::pubkey::Pubkey;
use crate::{constants::global_constants::{INITIAL_REAL_TOKEN_RESERVES, INITIAL_VIRTUAL_SOL_RESERVES, INITIAL_VIRTUAL_TOKEN_RESERVES, TOKEN_TOTAL_SUPPLY}, pumpfun::common::{get_bonding_curve_pda, get_creator_vault_pda}};
use crate::{constants::pumpfun::global_constants::{INITIAL_REAL_TOKEN_RESERVES, INITIAL_VIRTUAL_SOL_RESERVES, INITIAL_VIRTUAL_TOKEN_RESERVES, TOKEN_TOTAL_SUPPLY}, pumpfun::common::{get_bonding_curve_pda, get_creator_vault_pda}};
/// Represents the global configuration account for token pricing and fees
#[derive(Debug, Clone, Serialize, Deserialize)]
+1 -1
View File
@@ -26,7 +26,7 @@
use solana_sdk::pubkey::Pubkey;
use serde::{Serialize, Deserialize};
use crate::constants::global_constants::*;
use crate::constants::pumpfun::global_constants::*;
/// Represents the global configuration account for token pricing and fees
#[derive(Debug, Clone, Serialize, Deserialize)]
+24 -24
View File
@@ -351,14 +351,14 @@ pub async fn extend_pumpfun_address_to_lookup_table(
pub fn get_pumpfun_addresses(payer: Pubkey, include_addresses: Vec<Pubkey>) -> Vec<Pubkey> {
let mut addresses = vec![
payer,
constants::accounts::PUMPFUN,
constants::accounts::SYSTEM_PROGRAM,
constants::accounts::TOKEN_PROGRAM,
constants::accounts::RENT,
constants::accounts::EVENT_AUTHORITY,
constants::accounts::ASSOCIATED_TOKEN_PROGRAM,
constants::global_constants::GLOBAL_ACCOUNT,
constants::global_constants::FEE_RECIPIENT,
constants::pumpfun::accounts::PUMPFUN,
constants::pumpfun::accounts::SYSTEM_PROGRAM,
constants::pumpfun::accounts::TOKEN_PROGRAM,
constants::pumpfun::accounts::RENT,
constants::pumpfun::accounts::EVENT_AUTHORITY,
constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM,
constants::pumpfun::global_constants::GLOBAL_ACCOUNT,
constants::pumpfun::global_constants::FEE_RECIPIENT,
];
addresses.extend(include_addresses);
@@ -369,22 +369,22 @@ pub fn get_pumpfun_addresses(payer: Pubkey, include_addresses: Vec<Pubkey>) -> V
pub fn get_pumpfun_filtered_addresses(payer: Pubkey, include_addresses: Vec<Pubkey>) -> Vec<Pubkey> {
let mut addresses = vec![
payer,
constants::accounts::PUMPFUN,
constants::accounts::SYSTEM_PROGRAM,
constants::accounts::TOKEN_PROGRAM,
constants::accounts::RENT,
constants::accounts::EVENT_AUTHORITY,
constants::accounts::ASSOCIATED_TOKEN_PROGRAM,
constants::global_constants::GLOBAL_ACCOUNT,
constants::global_constants::FEE_RECIPIENT,
constants::global_constants::PUMPFUN_AMM_FEE_1,
constants::global_constants::PUMPFUN_AMM_FEE_2,
constants::global_constants::PUMPFUN_AMM_FEE_3,
constants::global_constants::PUMPFUN_AMM_FEE_4,
constants::global_constants::PUMPFUN_AMM_FEE_5,
constants::global_constants::PUMPFUN_AMM_FEE_6,
constants::global_constants::PUMPFUN_AMM_FEE_7,
// constants::global_constants::PUMPFUN_AMM_FEE_8,
constants::pumpfun::accounts::PUMPFUN,
constants::pumpfun::accounts::SYSTEM_PROGRAM,
constants::pumpfun::accounts::TOKEN_PROGRAM,
constants::pumpfun::accounts::RENT,
constants::pumpfun::accounts::EVENT_AUTHORITY,
constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM,
constants::pumpfun::global_constants::GLOBAL_ACCOUNT,
constants::pumpfun::global_constants::FEE_RECIPIENT,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_1,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_2,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_3,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_4,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_5,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_6,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_7,
// constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_8,
];
addresses.extend(include_addresses);
+2 -5
View File
@@ -1,8 +1,5 @@
pub mod logs_data;
pub mod logs_parser;
pub mod logs_filters;
pub mod logs_subscribe;
pub mod logs_events;
pub mod pumpfun;
pub mod pumpswap;
pub mod address_lookup;
pub mod nonce_cache;
pub mod tip_cache;
@@ -1,7 +1,7 @@
use base64::engine::general_purpose;
use base64::Engine;
use regex::Regex;
use crate::common::logs_data::{CreateTokenInfo, TradeInfo, EventTrait, TransferInfo, TipInfo};
use crate::common::pumpfun::logs_data::{CreateTokenInfo, TradeInfo, EventTrait, TransferInfo, TipInfo};
pub const PROGRAM_DATA: &str = "Program data: ";
@@ -1,5 +1,5 @@
use crate::common::logs_data::DexInstruction;
use crate::common::logs_parser::{parse_create_token_data, parse_trade_data, parse_instruction_create_token_data, parse_instruction_trade_data};
use crate::common::pumpfun::logs_data::DexInstruction;
use crate::common::pumpfun::logs_parser::{parse_create_token_data, parse_trade_data, parse_instruction_create_token_data, parse_instruction_trade_data};
use crate::error::ClientResult;
pub struct LogFilter;
use solana_sdk::pubkey::Pubkey;
@@ -3,7 +3,7 @@ use std::str::FromStr;
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use crate::error::{ClientError, ClientResult};
use crate::common::{
use crate::common::pumpfun::{
logs_data::{DexInstruction, CreateTokenInfo, TradeInfo},
logs_filters::LogFilter
};
@@ -8,7 +8,7 @@ use std::sync::Arc;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use futures::StreamExt;
use crate::{constants, common::{
use crate::{constants, common::pumpfun::{
logs_data::DexInstruction, logs_events::DexEvent, logs_filters::LogFilter
}};
@@ -41,7 +41,7 @@ pub async fn tokens_subscription<F>(
where
F: Fn(PumpfunEvent) + Send + Sync + 'static,
{
let program_address = constants::accounts::PUMPFUN.to_string();
let program_address = constants::pumpfun::accounts::PUMPFUN.to_string();
let logs_filter = RpcTransactionLogsFilter::Mentions(vec![program_address]);
let logs_config = RpcTransactionLogsConfig {
+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::*;
+353
View File
@@ -0,0 +1,353 @@
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,
pub coin_creator: Pubkey,
pub coin_creator_fee_basis_points: u64,
pub coin_creator_fee: u64,
#[borsh(skip)]
pub signature: String,
#[borsh(skip)]
pub base_mint: Pubkey,
#[borsh(skip)]
pub quote_mint: Pubkey,
#[borsh(skip)]
pub pool_base_token_account: Pubkey,
#[borsh(skip)]
pub pool_quote_token_account: Pubkey,
#[borsh(skip)]
pub coin_creator_vault_ata: Pubkey,
#[borsh(skip)]
pub coin_creator_vault_authority: Pubkey,
}
/// 卖出事件
#[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,
pub coin_creator: Pubkey,
pub coin_creator_fee_basis_points: u64,
pub coin_creator_fee: u64,
#[borsh(skip)]
pub signature: String,
#[borsh(skip)]
pub base_mint: Pubkey,
#[borsh(skip)]
pub quote_mint: Pubkey,
#[borsh(skip)]
pub pool_base_token_account: Pubkey,
#[borsh(skip)]
pub pool_quote_token_account: Pubkey,
#[borsh(skip)]
pub coin_creator_vault_ata: Pubkey,
#[borsh(skip)]
pub coin_creator_vault_authority: Pubkey,
}
/// 创建池子事件
#[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)
}
}
+310
View File
@@ -0,0 +1,310 @@
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..];
let accounts: Vec<Pubkey> = instruction.accounts.iter()
.map(|&idx| _accounts[idx as usize])
.collect();
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(),
base_mint: accounts[3],
quote_mint: accounts[4],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
coin_creator_vault_ata: if accounts.len() > 17 { accounts[17] } else { Pubkey::default() },
coin_creator_vault_authority: if accounts.len() > 18 { accounts[18] } else { Pubkey::default() },
..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(),
base_mint: accounts[3],
quote_mint: accounts[4],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
coin_creator_vault_ata: if accounts.len() > 17 { accounts[17] } else { Pubkey::default() },
coin_creator_vault_authority: if accounts.len() > 18 { accounts[18] } else { Pubkey::default() },
..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::*;
+1 -1
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
use serde::Deserialize;
use crate::{constants::trade::{DEFAULT_BUY_TIP_FEE, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE, DEFAULT_SELL_TIP_FEE}, swqos::FeeClient};
use crate::{constants::pumpfun::trade::{DEFAULT_BUY_TIP_FEE, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE, DEFAULT_SELL_TIP_FEE}, swqos::FeeClient};
#[derive(Debug, Clone, PartialEq)]
pub enum FeeType {
Executable → Regular
+12 -171
View File
@@ -1,174 +1,15 @@
//! Constants used by the crate.
//!
//! This module contains various constants used throughout the crate, including:
//!
//! - Seeds for deriving Program Derived Addresses (PDAs)
//! - Program account addresses and public keys
//!
//! The constants are organized into submodules for better organization:
//!
//! - `seeds`: Contains seed values used for PDA derivation
//! - `accounts`: Contains important program account addresses
pub mod pumpfun;
pub mod pumpswap;
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
pub mod seeds {
/// Seed for the global state PDA
pub const GLOBAL_SEED: &[u8] = b"global";
/// Seed for the mint authority PDA
pub const MINT_AUTHORITY_SEED: &[u8] = b"mint-authority";
/// Seed for bonding curve PDAs
pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve";
/// Seed for creator vault PDAs
pub const CREATOR_VAULT_SEED: &[u8] = b"creator-vault";
/// Seed for metadata PDAs
pub const METADATA_SEED: &[u8] = b"metadata";
pub mod trade_type {
pub const COPY_BUY: &'static str = "copy_buy";
pub const COPY_SELL: &'static str = "copy_sell";
pub const SNIPER_BUY: &'static str = "sniper_buy";
pub const SNIPER_SELL: &'static str = "sniper_sell";
}
pub mod global_constants {
use solana_sdk::{pubkey, pubkey::Pubkey};
pub const INITIAL_VIRTUAL_TOKEN_RESERVES: u64 = 1_073_000_000_000_000;
pub const INITIAL_VIRTUAL_SOL_RESERVES: u64 = 30_000_000_000;
pub const INITIAL_REAL_TOKEN_RESERVES: u64 = 793_100_000_000_000;
pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000;
pub const FEE_BASIS_POINTS: u64 = 95;
pub const ENABLE_MIGRATE: bool = false;
pub const POOL_MIGRATION_FEE: u64 = 15_000_001;
pub const CREATOR_FEE: u64 = 5;
pub const SCALE: u64 = 1_000_000; // 10^6 for token decimals
pub const LAMPORTS_PER_SOL: u64 = 1_000_000_000; // 10^9 for solana lamports
pub const TOTAL_SUPPLY: u64 = 1_000_000_000 * SCALE; // 1 billion tokens
pub const BONDING_CURVE_SUPPLY: u64 = 793_100_000 * SCALE; // total supply of bonding curve tokens
pub const COMPLETION_LAMPORTS: u64 = 85 * LAMPORTS_PER_SOL; // ~ 85 SOL
/// Public key for the fee recipient
pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
/// Public key for the global PDA
pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf");
/// Public key for the authority
pub const AUTHORITY: Pubkey = pubkey!("FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF");
/// Public key for the withdraw authority
pub const WITHDRAW_AUTHORITY: Pubkey = pubkey!("39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg");
pub const PUMPFUN_AMM_FEE_1: Pubkey = pubkey!("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"); // Pump.fun AMM: Protocol Fee 1
pub const PUMPFUN_AMM_FEE_2: Pubkey = pubkey!("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX"); // Pump.fun AMM: Protocol Fee 2
pub const PUMPFUN_AMM_FEE_3: Pubkey = pubkey!("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz"); // Pump.fun AMM: Protocol Fee 3
pub const PUMPFUN_AMM_FEE_4: Pubkey = pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"); // Pump.fun AMM: Protocol Fee 4
pub const PUMPFUN_AMM_FEE_5: Pubkey = pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"); // Pump.fun AMM: Protocol Fee 5
pub const PUMPFUN_AMM_FEE_6: Pubkey = pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"); // Pump.fun AMM: Protocol Fee 6
pub const PUMPFUN_AMM_FEE_7: Pubkey = pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"); // Pump.fun AMM: Protocol Fee 7
}
/// Constants related to program accounts and authorities
pub mod accounts {
use solana_sdk::{pubkey, pubkey::Pubkey};
/// Public key for the Pump.fun program
pub const PUMPFUN: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
/// Public key for the MPL Token Metadata program
pub const MPL_TOKEN_METADATA: Pubkey = pubkey!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
/// Authority for program events
pub const EVENT_AUTHORITY: Pubkey = pubkey!("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1");
/// System Program ID
pub const SYSTEM_PROGRAM: Pubkey = pubkey!("11111111111111111111111111111111");
/// Token Program ID
pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
/// Associated Token Program ID
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
/// Rent Sysvar ID
pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111");
pub const JITO_TIP_ACCOUNTS: [&str; 8] = [
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
];
/// Tip accounts
pub const NEXTBLOCK_TIP_ACCOUNTS: &[&str] = &[
"NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE",
"NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2",
"NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X",
"NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb",
"neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At",
"nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG",
"NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid",
"nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc"
];
pub const ZEROSLOT_TIP_ACCOUNTS: &[&str] = &[
"Eb2KpSC8uMt9GmzyAEm5Eb1AAAgTjRaXWFjKyFXHZxF3",
"FCjUJZ1qozm1e8romw216qyfQMaaWKxWsuySnumVCCNe",
"ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13",
"6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK",
"Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr",
];
pub const NOZOMI_TIP_ACCOUNTS: &[&str] = &[
"TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq",
"noz3jAjPiHuBPqiSPkkugaJDkJscPuRhYnSpbi8UvC4",
"noz3str9KXfpKknefHji8L1mPgimezaiUyCHYMDv1GE",
"noz6uoYCDijhu1V7cutCpwxNiSovEwLdRHPwmgCGDNo",
"noz9EPNcT7WH6Sou3sr3GGjHQYVkN3DNirpbvDkv9YJ",
"nozc5yT15LazbLTFVZzoNZCwjh3yUtW86LoUyqsBu4L",
"nozFrhfnNGoyqwVuwPAW4aaGqempx4PU6g6D9CJMv7Z",
"nozievPk7HyK1Rqy1MPJwVQ7qQg2QoJGyP71oeDwbsu",
"noznbgwYnBLDHu8wcQVCEw6kDrXkPdKkydGJGNXGvL7",
"nozNVWs5N8mgzuD3qigrCG2UoKxZttxzZ85pvAQVrbP",
"nozpEGbwx4BcGp6pvEdAh1JoC2CQGZdU6HbNP1v2p6P",
"nozrhjhkCr3zXT3BiT4WCodYCUFeQvcdUkM7MqhKqge",
"nozrwQtWhEdrA6W8dkbt9gnUaMs52PdAv5byipnadq3",
"nozUacTVWub3cL4mJmGCYjKZTnE9RbdY5AP46iQgbPJ",
"nozWCyTPppJjRuw2fpzDhhWbW355fzosWSzrrMYB1Qk",
"nozWNju6dY353eMkMqURqwQEoM3SFgEKC6psLCSfUne",
"nozxNBgWohjR75vdspfxR5H9ceC7XXH99xpxhVGt3Bb"
];
pub const AMM_PROGRAM: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
}
pub mod trade {
pub const TRADER_TIP_AMOUNT: f64 = 0.0001;
pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006;
pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001;
}
pub struct Symbol;
impl Symbol {
pub const SOLANA: &'static str = "solana";
}
pub mod trade_platform {
pub const PUMPFUN: &'static str = "pumpfun";
pub const PUMPFUN_SWAP: &'static str = "pumpswap";
pub const RAYDIUM: &'static str = "raydium";
}
+175
View File
@@ -0,0 +1,175 @@
//! Constants used by the crate.
//!
//! This module contains various constants used throughout the crate, including:
//!
//! - Seeds for deriving Program Derived Addresses (PDAs)
//! - Program account addresses and public keys
//!
//! The constants are organized into submodules for better organization:
//!
//! - `seeds`: Contains seed values used for PDA derivation
//! - `accounts`: Contains important program account addresses
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
pub mod seeds {
/// Seed for the global state PDA
pub const GLOBAL_SEED: &[u8] = b"global";
/// Seed for the mint authority PDA
pub const MINT_AUTHORITY_SEED: &[u8] = b"mint-authority";
/// Seed for bonding curve PDAs
pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve";
/// Seed for creator vault PDAs
pub const CREATOR_VAULT_SEED: &[u8] = b"creator-vault";
/// Seed for metadata PDAs
pub const METADATA_SEED: &[u8] = b"metadata";
}
pub mod global_constants {
use solana_sdk::{pubkey, pubkey::Pubkey};
pub const INITIAL_VIRTUAL_TOKEN_RESERVES: u64 = 1_073_000_000_000_000;
pub const INITIAL_VIRTUAL_SOL_RESERVES: u64 = 30_000_000_000;
pub const INITIAL_REAL_TOKEN_RESERVES: u64 = 793_100_000_000_000;
pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000;
pub const FEE_BASIS_POINTS: u64 = 95;
pub const ENABLE_MIGRATE: bool = false;
pub const POOL_MIGRATION_FEE: u64 = 15_000_001;
pub const CREATOR_FEE: u64 = 5;
pub const SCALE: u64 = 1_000_000; // 10^6 for token decimals
pub const LAMPORTS_PER_SOL: u64 = 1_000_000_000; // 10^9 for solana lamports
pub const TOTAL_SUPPLY: u64 = 1_000_000_000 * SCALE; // 1 billion tokens
pub const BONDING_CURVE_SUPPLY: u64 = 793_100_000 * SCALE; // total supply of bonding curve tokens
pub const COMPLETION_LAMPORTS: u64 = 85 * LAMPORTS_PER_SOL; // ~ 85 SOL
/// Public key for the fee recipient
pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
/// Public key for the global PDA
pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf");
/// Public key for the authority
pub const AUTHORITY: Pubkey = pubkey!("FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF");
/// Public key for the withdraw authority
pub const WITHDRAW_AUTHORITY: Pubkey = pubkey!("39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg");
pub const PUMPFUN_AMM_FEE_1: Pubkey = pubkey!("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"); // Pump.fun AMM: Protocol Fee 1
pub const PUMPFUN_AMM_FEE_2: Pubkey = pubkey!("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX"); // Pump.fun AMM: Protocol Fee 2
pub const PUMPFUN_AMM_FEE_3: Pubkey = pubkey!("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz"); // Pump.fun AMM: Protocol Fee 3
pub const PUMPFUN_AMM_FEE_4: Pubkey = pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"); // Pump.fun AMM: Protocol Fee 4
pub const PUMPFUN_AMM_FEE_5: Pubkey = pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"); // Pump.fun AMM: Protocol Fee 5
pub const PUMPFUN_AMM_FEE_6: Pubkey = pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"); // Pump.fun AMM: Protocol Fee 6
pub const PUMPFUN_AMM_FEE_7: Pubkey = pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"); // Pump.fun AMM: Protocol Fee 7
}
/// Constants related to program accounts and authorities
pub mod accounts {
use solana_sdk::{pubkey, pubkey::Pubkey};
/// Public key for the Pump.fun program
pub const PUMPFUN: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
/// Public key for the MPL Token Metadata program
pub const MPL_TOKEN_METADATA: Pubkey = pubkey!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
/// Authority for program events
pub const EVENT_AUTHORITY: Pubkey = pubkey!("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1");
/// System Program ID
pub const SYSTEM_PROGRAM: Pubkey = pubkey!("11111111111111111111111111111111");
/// Token Program ID
pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
/// Associated Token Program ID
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
/// Rent Sysvar ID
pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111");
pub const JITO_TIP_ACCOUNTS: [&str; 8] = [
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
];
/// Tip accounts
pub const NEXTBLOCK_TIP_ACCOUNTS: &[&str] = &[
"NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE",
"NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2",
"NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X",
"NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb",
"neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At",
"nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG",
"NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid",
"nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc"
];
pub const ZEROSLOT_TIP_ACCOUNTS: &[&str] = &[
"Eb2KpSC8uMt9GmzyAEm5Eb1AAAgTjRaXWFjKyFXHZxF3",
"FCjUJZ1qozm1e8romw216qyfQMaaWKxWsuySnumVCCNe",
"ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13",
"6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK",
"Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr",
];
pub const NOZOMI_TIP_ACCOUNTS: &[&str] = &[
"TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq",
"noz3jAjPiHuBPqiSPkkugaJDkJscPuRhYnSpbi8UvC4",
"noz3str9KXfpKknefHji8L1mPgimezaiUyCHYMDv1GE",
"noz6uoYCDijhu1V7cutCpwxNiSovEwLdRHPwmgCGDNo",
"noz9EPNcT7WH6Sou3sr3GGjHQYVkN3DNirpbvDkv9YJ",
"nozc5yT15LazbLTFVZzoNZCwjh3yUtW86LoUyqsBu4L",
"nozFrhfnNGoyqwVuwPAW4aaGqempx4PU6g6D9CJMv7Z",
"nozievPk7HyK1Rqy1MPJwVQ7qQg2QoJGyP71oeDwbsu",
"noznbgwYnBLDHu8wcQVCEw6kDrXkPdKkydGJGNXGvL7",
"nozNVWs5N8mgzuD3qigrCG2UoKxZttxzZ85pvAQVrbP",
"nozpEGbwx4BcGp6pvEdAh1JoC2CQGZdU6HbNP1v2p6P",
"nozrhjhkCr3zXT3BiT4WCodYCUFeQvcdUkM7MqhKqge",
"nozrwQtWhEdrA6W8dkbt9gnUaMs52PdAv5byipnadq3",
"nozUacTVWub3cL4mJmGCYjKZTnE9RbdY5AP46iQgbPJ",
"nozWCyTPppJjRuw2fpzDhhWbW355fzosWSzrrMYB1Qk",
"nozWNju6dY353eMkMqURqwQEoM3SFgEKC6psLCSfUne",
"nozxNBgWohjR75vdspfxR5H9ceC7XXH99xpxhVGt3Bb"
];
pub const AMM_PROGRAM: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
}
pub mod trade {
pub const TRADER_TIP_AMOUNT: f64 = 0.0001;
pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006;
pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001;
}
pub struct Symbol;
impl Symbol {
pub const SOLANA: &'static str = "solana";
}
+126
View File
@@ -0,0 +1,126 @@
//! Constants used by the crate.
//!
//! This module contains various constants used throughout the crate, including:
//!
//! - Seeds for deriving Program Derived Addresses (PDAs)
//! - Program account addresses and public keys
//!
//! The constants are organized into submodules for better organization:
//!
//! - `seeds`: Contains seed values used for PDA derivation
//! - `accounts`: Contains important program account addresses
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
pub mod seeds {
/// Seed for the global state PDA
pub const GLOBAL_SEED: &[u8] = b"global";
/// Seed for the mint authority PDA
pub const MINT_AUTHORITY_SEED: &[u8] = b"mint-authority";
/// Seed for bonding curve PDAs
pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve";
/// Seed for metadata PDAs
pub const METADATA_SEED: &[u8] = b"metadata";
}
/// Constants related to program accounts and authorities
pub mod accounts {
use solana_sdk::{pubkey, pubkey::Pubkey};
/// Public key for the fee recipient
pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
pub const FEE_RECIPIENT_ATA:Pubkey = pubkey!("94qWNrtmfn42h3ZjUZwWvK1MEo9uVmmrBPd2hpNjYDjb");
/// Public key for the global PDA
pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw");
/// Authority for program events
pub const EVENT_AUTHORITY: Pubkey = pubkey!("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR");
pub const WSOL_TOKEN_ACCOUNT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
/// System Program ID
pub const SYSTEM_PROGRAM: Pubkey = pubkey!("11111111111111111111111111111111");
/// Token Program ID
pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
/// Associated Token Program ID
pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
// PumpSwap 协议费用接收者
pub const PROTOCOL_FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV");
/// Rent Sysvar ID
pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111");
pub const JITO_TIP_ACCOUNTS: &[&str] = &[
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
];
/// Tip accounts
pub const NEXTBLOCK_TIP_ACCOUNTS: &[&str] = &[
"NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE",
"NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2",
"NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X",
"NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb",
"neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At",
"nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG",
"NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid",
"nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc"
];
pub const ZEROSLOT_TIP_ACCOUNTS: &[&str] = &[
"Eb2KpSC8uMt9GmzyAEm5Eb1AAAgTjRaXWFjKyFXHZxF3",
"FCjUJZ1qozm1e8romw216qyfQMaaWKxWsuySnumVCCNe",
"ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13",
"6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK",
"Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr",
];
pub const NOZOMI_TIP_ACCOUNTS: &[&str] = &[
"TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq",
"noz3jAjPiHuBPqiSPkkugaJDkJscPuRhYnSpbi8UvC4",
"noz3str9KXfpKknefHji8L1mPgimezaiUyCHYMDv1GE",
"noz6uoYCDijhu1V7cutCpwxNiSovEwLdRHPwmgCGDNo",
"noz9EPNcT7WH6Sou3sr3GGjHQYVkN3DNirpbvDkv9YJ",
"nozc5yT15LazbLTFVZzoNZCwjh3yUtW86LoUyqsBu4L",
"nozFrhfnNGoyqwVuwPAW4aaGqempx4PU6g6D9CJMv7Z",
"nozievPk7HyK1Rqy1MPJwVQ7qQg2QoJGyP71oeDwbsu",
"noznbgwYnBLDHu8wcQVCEw6kDrXkPdKkydGJGNXGvL7",
"nozNVWs5N8mgzuD3qigrCG2UoKxZttxzZ85pvAQVrbP",
"nozpEGbwx4BcGp6pvEdAh1JoC2CQGZdU6HbNP1v2p6P",
"nozrhjhkCr3zXT3BiT4WCodYCUFeQvcdUkM7MqhKqge",
"nozrwQtWhEdrA6W8dkbt9gnUaMs52PdAv5byipnadq3",
"nozUacTVWub3cL4mJmGCYjKZTnE9RbdY5AP46iQgbPJ",
"nozWCyTPppJjRuw2fpzDhhWbW355fzosWSzrrMYB1Qk",
"nozWNju6dY353eMkMqURqwQEoM3SFgEKC6psLCSfUne",
"nozxNBgWohjR75vdspfxR5H9ceC7XXH99xpxhVGt3Bb"
];
pub const AMM_PROGRAM: Pubkey = pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA");
}
pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
pub mod trade {
pub const TRADER_TIP_AMOUNT: u64 = 100000; // 0.0001 SOL in lamports
pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000;
pub const DEFAULT_BUY_TIP_FEE: u64 = 600000; // 0.0006 SOL in lamports
pub const DEFAULT_SELL_TIP_FEE: u64 = 100000; // 0.0001 SOL in lamports
}
+103 -3
View File
@@ -7,12 +7,15 @@ use tonic::transport::Channel;
use log::error;
use solana_sdk::transaction::VersionedTransaction;
use crate::common::pumpswap::PumpSwapInstruction;
use crate::common::AnyResult;
use solana_sdk::pubkey::Pubkey;
use crate::common::logs_data::DexInstruction;
use crate::common::logs_events::PumpfunEvent;
use crate::common::logs_filters::LogFilter;
use crate::common::pumpfun::logs_data::DexInstruction;
use crate::common::pumpfun::logs_events::PumpfunEvent;
use crate::common::pumpswap::logs_events::PumpSwapEvent;
use crate::common::pumpfun::logs_filters::LogFilter;
use crate::common::pumpswap::logs_filters::LogFilter as PumpswapLogFilter;
use crate::swqos::jito_grpc::shredstream::shredstream_proxy_client::ShredstreamProxyClient;
use crate::swqos::jito_grpc::shredstream::SubscribeEntriesRequest;
@@ -77,6 +80,47 @@ impl ShredStreamGrpc {
Ok(())
}
pub async fn shredstream_subscribe_pumpswap<F>(&self, callback: F) -> AnyResult<()>
where
F: Fn(PumpSwapEvent) + Send + Sync + 'static,
{
let request = tonic::Request::new(SubscribeEntriesRequest {});
let mut client = (*self.shredstream_client).clone();
let mut stream = client.subscribe_entries(request).await?.into_inner();
let (mut tx, mut rx) = mpsc::channel::<TransactionWithSlot>(CHANNEL_SIZE);
let callback = Box::new(callback);
tokio::spawn(async move {
while let Some(message) = stream.next().await {
match message {
Ok(msg) => {
if let Ok(entries) = bincode::deserialize::<Vec<Entry>>(&msg.entries) {
for entry in entries {
for transaction in entry.transactions {
let _ = tx.try_send(TransactionWithSlot {
transaction: transaction.clone(),
slot: msg.slot,
});
}
}
}
}
Err(error) => {
error!("Stream error: {error:?}");
break;
}
}
}
});
while let Some(transaction_with_slot) = rx.next().await {
if let Err(e) = Self::process_pumpswap_transaction(transaction_with_slot, &*callback).await {
error!("Error processing transaction: {:?}", e);
}
}
Ok(())
}
async fn process_pumpfun_transaction<F>(transaction_with_slot: TransactionWithSlot, callback: &F, bot_wallet: Option<Pubkey>) -> AnyResult<()>
where
F: Fn(PumpfunEvent) + Send + Sync,
@@ -111,4 +155,60 @@ impl ShredStreamGrpc {
}
Ok(())
}
async fn process_pumpswap_transaction<F>(transaction_with_slot: TransactionWithSlot, callback: &F) -> AnyResult<()>
where
F: Fn(PumpSwapEvent) + Send + Sync,
{
let slot = transaction_with_slot.slot;
let versioned_tx = transaction_with_slot.transaction;
let signature = versioned_tx.signatures[0].to_string();
let instructions = PumpswapLogFilter::parse_pumpswap_compiled_instruction(versioned_tx).unwrap();
for instruction in instructions {
match instruction {
PumpSwapInstruction::CreatePool(mut create_event) => {
create_event.slot = slot;
create_event.signature = signature.clone();
callback(PumpSwapEvent::CreatePool(create_event));
}
PumpSwapInstruction::Deposit(mut deposit_event) => {
deposit_event.slot = slot;
deposit_event.signature = signature.clone();
callback(PumpSwapEvent::Deposit(deposit_event));
}
PumpSwapInstruction::Withdraw(mut withdraw_event) => {
withdraw_event.slot = slot;
withdraw_event.signature = signature.clone();
callback(PumpSwapEvent::Withdraw(withdraw_event));
}
PumpSwapInstruction::Buy(mut buy_event) => {
buy_event.slot = slot;
buy_event.signature = signature.clone();
callback(PumpSwapEvent::Buy(buy_event));
}
PumpSwapInstruction::Sell(mut sell_event) => {
sell_event.slot = slot;
sell_event.signature = signature.clone();
callback(PumpSwapEvent::Sell(sell_event));
}
PumpSwapInstruction::UpdateFeeConfig(mut update_fee_event) => {
update_fee_event.slot = slot;
update_fee_event.signature = signature.clone();
callback(PumpSwapEvent::UpdateFeeConfig(update_fee_event));
}
PumpSwapInstruction::UpdateAdmin(mut update_admin_event) => {
update_admin_event.slot = slot;
update_admin_event.signature = signature.clone();
callback(PumpSwapEvent::UpdateAdmin(update_admin_event));
}
PumpSwapInstruction::Disable(mut disable_event) => {
disable_event.slot = slot;
disable_event.signature = signature.clone();
callback(PumpSwapEvent::Disable(disable_event));
}
_ => {}
}
}
Ok(())
}
}
+189 -3
View File
@@ -15,9 +15,9 @@ use solana_transaction_status::{
option_serializer::OptionSerializer, EncodedTransactionWithStatusMeta, UiTransactionEncoding,
};
use crate::common::logs_data::{DexInstruction, TransferInfo};
use crate::common::logs_events::{PumpfunEvent, SystemEvent};
use crate::common::logs_filters::LogFilter;
use crate::common::pumpfun::logs_data::{DexInstruction, TransferInfo};
use crate::common::pumpfun::logs_events::{PumpfunEvent, SystemEvent};
use crate::common::pumpfun::logs_filters::LogFilter;
use crate::common::AnyResult;
type TransactionsFilterMap = HashMap<String, SubscribeRequestFilterTransactions>;
@@ -349,4 +349,190 @@ impl YellowstoneGrpc {
Ok(())
}
// ------------------------------------------------------------
// PumpSwap
// ------------------------------------------------------------
/// 订阅PumpSwap事件
pub async fn subscribe_pumpswap<F>(&self, callback: F) -> AnyResult<()>
where
F: Fn(crate::common::pumpswap::logs_events::PumpSwapEvent) + Send + Sync + 'static,
{
// 使用constants中定义的AMM_PROGRAM
let pump_program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
let addrs = vec![pump_program_id.to_string()];
// 创建过滤器
let transactions = self.get_subscribe_request_filter(addrs, vec![], vec![]);
// 订阅事件
let (mut subscribe_tx, mut stream) = self.subscribe_with_request(transactions).await?;
// 创建通道
let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(1000);
// 创建回调函数
let callback = Box::new(callback);
// 启动处理流的任务
tokio::spawn(async move {
while let Some(message) = stream.next().await {
match message {
Ok(msg) => {
if let Err(e) = Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await {
error!("Error handling message: {:?}", e);
break;
}
}
Err(error) => {
error!("Stream error: {error:?}");
break;
}
}
}
});
// 处理交易
while let Some(transaction_pretty) = rx.next().await {
if let Err(e) = Self::process_pumpswap_transaction(transaction_pretty, &*callback).await {
error!("Error processing transaction: {:?}", e);
}
}
Ok(())
}
/// 使用过滤器订阅PumpSwap事件
pub async fn subscribe_pumpswap_with_filter<F>(
&self,
callback: F,
account_include: Option<Vec<String>>,
account_exclude: Option<Vec<String>>
) -> AnyResult<()>
where
F: Fn(crate::common::pumpswap::logs_events::PumpSwapEvent) + Send + Sync + 'static,
{
// 使用constants中定义的AMM_PROGRAM
let pump_program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
let addrs = vec![pump_program_id.to_string()];
// 创建过滤器
let account_include = account_include.unwrap_or_default();
let account_exclude = account_exclude.unwrap_or_default();
let transactions = self.get_subscribe_request_filter(account_include, account_exclude, addrs);
// 订阅事件
let (mut subscribe_tx, mut stream) = self.subscribe_with_request(transactions).await?;
// 创建通道
let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(1000);
// 创建回调函数
let callback = Box::new(callback);
// 启动处理流的任务
tokio::spawn(async move {
while let Some(message) = stream.next().await {
match message {
Ok(msg) => {
if let Err(e) = Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await {
error!("Error handling message: {:?}", e);
break;
}
}
Err(error) => {
error!("Stream error: {error:?}");
break;
}
}
}
});
// 处理交易
while let Some(transaction_pretty) = rx.next().await {
if let Err(e) = Self::process_pumpswap_transaction(transaction_pretty, &*callback).await {
error!("Error processing transaction: {:?}", e);
}
}
Ok(())
}
/// 处理PumpSwap交易
async fn process_pumpswap_transaction<F>(
transaction_pretty: TransactionPretty,
callback: &F
) -> AnyResult<()>
where
F: Fn(crate::common::pumpswap::logs_events::PumpSwapEvent) + Send + Sync,
{
let slot = transaction_pretty.slot;
let trade_raw: solana_transaction_status::EncodedTransactionWithStatusMeta = transaction_pretty.tx;
// 检查交易元数据
let meta = trade_raw.meta.as_ref()
.ok_or_else(|| anyhow::anyhow!("Missing transaction metadata"))?;
// 检查交易是否成功
if meta.err.is_some() {
return Ok(());
}
// 获取日志
let logs = if let solana_transaction_status::option_serializer::OptionSerializer::Some(logs) = &meta.log_messages {
logs
} else {
&vec![]
};
// 解析PumpSwap事件
let events = crate::common::pumpswap::logs_filters::LogFilter::parse_pumpswap_logs(logs);
// 处理事件
for mut event in events {
// 设置签名和slot
match &mut event {
crate::common::pumpswap::logs_events::PumpSwapEvent::Buy(e) => {
e.signature = transaction_pretty.signature.to_string();
e.slot = slot;
},
crate::common::pumpswap::logs_events::PumpSwapEvent::Sell(e) => {
e.signature = transaction_pretty.signature.to_string();
e.slot = slot;
},
crate::common::pumpswap::logs_events::PumpSwapEvent::CreatePool(e) => {
e.signature = transaction_pretty.signature.to_string();
e.slot = slot;
},
crate::common::pumpswap::logs_events::PumpSwapEvent::Deposit(e) => {
e.signature = transaction_pretty.signature.to_string();
e.slot = slot;
},
crate::common::pumpswap::logs_events::PumpSwapEvent::Withdraw(e) => {
e.signature = transaction_pretty.signature.to_string();
e.slot = slot;
},
crate::common::pumpswap::logs_events::PumpSwapEvent::Disable(e) => {
e.signature = transaction_pretty.signature.to_string();
e.slot = slot;
},
crate::common::pumpswap::logs_events::PumpSwapEvent::UpdateAdmin(e) => {
e.signature = transaction_pretty.signature.to_string();
e.slot = slot;
},
crate::common::pumpswap::logs_events::PumpSwapEvent::UpdateFeeConfig(e) => {
e.signature = transaction_pretty.signature.to_string();
e.slot = slot;
},
_ => {}
}
// 调用回调函数
callback(event);
}
Ok(())
}
}
+20 -20
View File
@@ -103,7 +103,7 @@ impl Sell {
pub fn create(payer: &Keypair, mint: &Keypair, args: Create) -> Instruction {
let bonding_curve: Pubkey = get_bonding_curve_pda(&mint.pubkey()).unwrap();
Instruction::new_with_bytes(
constants::accounts::PUMPFUN,
constants::pumpfun::accounts::PUMPFUN,
&args.data(),
vec![
AccountMeta::new(mint.pubkey(), true),
@@ -114,15 +114,15 @@ pub fn create(payer: &Keypair, mint: &Keypair, args: Create) -> Instruction {
false,
),
AccountMeta::new_readonly(get_global_pda(), false),
AccountMeta::new_readonly(constants::accounts::MPL_TOKEN_METADATA, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::MPL_TOKEN_METADATA, false),
AccountMeta::new(get_metadata_pda(&mint.pubkey()), false),
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false),
AccountMeta::new_readonly(constants::accounts::ASSOCIATED_TOKEN_PROGRAM, false),
AccountMeta::new_readonly(constants::accounts::RENT, false),
AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(constants::accounts::PUMPFUN, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::TOKEN_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::RENT, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::PUMPFUN, false),
],
)
}
@@ -152,21 +152,21 @@ pub fn buy(
args: Buy,
) -> Instruction {
Instruction::new_with_bytes(
constants::accounts::PUMPFUN,
constants::pumpfun::accounts::PUMPFUN,
&args.data(),
vec![
AccountMeta::new_readonly(constants::global_constants::GLOBAL_ACCOUNT, false),
AccountMeta::new_readonly(constants::pumpfun::global_constants::GLOBAL_ACCOUNT, false),
AccountMeta::new(*fee_recipient, false),
AccountMeta::new_readonly(*mint, false),
AccountMeta::new(*bonding_curve_pda, false),
AccountMeta::new(get_associated_token_address(bonding_curve_pda, mint), false),
AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false),
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::TOKEN_PROGRAM, false),
AccountMeta::new(*creator_vault_pda, false),
AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(constants::accounts::PUMPFUN, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::PUMPFUN, false),
],
)
}
@@ -196,21 +196,21 @@ pub fn sell(
) -> Instruction {
let bonding_curve: Pubkey = get_bonding_curve_pda(mint).unwrap();
Instruction::new_with_bytes(
constants::accounts::PUMPFUN,
constants::pumpfun::accounts::PUMPFUN,
&args.data(),
vec![
AccountMeta::new_readonly(constants::global_constants::GLOBAL_ACCOUNT, false),
AccountMeta::new_readonly(constants::pumpfun::global_constants::GLOBAL_ACCOUNT, false),
AccountMeta::new(*fee_recipient, false),
AccountMeta::new_readonly(*mint, false),
AccountMeta::new(bonding_curve, false),
AccountMeta::new(get_associated_token_address(&bonding_curve, mint), false),
AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false),
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::SYSTEM_PROGRAM, false),
AccountMeta::new(*creator_vault_pda, false),
AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false),
AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(constants::accounts::PUMPFUN, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::TOKEN_PROGRAM, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(constants::pumpfun::accounts::PUMPFUN, false),
],
)
}
+301 -28
View File
@@ -7,8 +7,10 @@ pub mod common;
pub mod ipfs;
pub mod swqos;
pub mod pumpfun;
pub mod pumpswap;
use std::sync::Arc;
use std::sync::Mutex;
use swqos::{FeeClient, JitoClient, NextBlockClient, NozomiClient, SolRpcClient, ZeroSlotClient};
use rustls::crypto::{ring::default_provider, CryptoProvider};
@@ -19,10 +21,13 @@ use solana_sdk::{
signature::{Keypair, Signer},
};
use common::{logs_data::TradeInfo, logs_events::PumpfunEvent, logs_subscribe, Cluster, PriorityFee, SolanaRpcClient};
use common::logs_subscribe::SubscriptionHandle;
use common::{pumpfun::logs_data::TradeInfo, pumpfun::logs_events::PumpfunEvent, pumpfun::logs_subscribe, Cluster, PriorityFee, SolanaRpcClient};
use common::pumpfun::logs_subscribe::SubscriptionHandle;
use ipfs::TokenMetadataIPFS;
use constants::trade_type::{COPY_BUY, SNIPER_BUY};
use constants::trade_platform::{PUMPFUN, PUMPFUN_SWAP, RAYDIUM};
pub struct PumpFun {
pub payer: Arc<Keypair>,
pub rpc: Arc<SolanaRpcClient>,
@@ -31,6 +36,8 @@ pub struct PumpFun {
pub cluster: Cluster,
}
static INSTANCE: Mutex<Option<Arc<PumpFun>>> = Mutex::new(None);
impl Clone for PumpFun {
fn clone(&self) -> Self {
Self {
@@ -105,13 +112,29 @@ impl PumpFun {
fee_clients.push(Arc::new(rpc_client));
}
Self {
let instance = Self {
payer,
rpc,
fee_clients,
priority_fee: cluster.clone().priority_fee,
cluster: cluster.clone(),
}
};
let mut current = INSTANCE.lock().unwrap();
*current = Some(Arc::new(instance.clone()));
instance
}
/// Get the RPC client instance
pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
&self.rpc
}
/// Get the current instance
pub fn get_instance() -> Arc<Self> {
let instance = INSTANCE.lock().unwrap();
instance.as_ref().expect("PumpFun instance not initialized. Please call new() first.").clone()
}
/// Create a new token
@@ -172,7 +195,7 @@ impl PumpFun {
}
/// Buy tokens
pub async fn buy(
pub async fn sniper_buy(
&self,
mint: Pubkey,
creator: Pubkey,
@@ -194,11 +217,59 @@ impl PumpFun {
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
SNIPER_BUY.to_string(),
).await
}
pub async fn copy_buy(
&self,
mint: Pubkey,
creator: Pubkey,
dev_buy_token: u64,
dev_sol_cost: u64,
buy_sol_cost: u64,
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
trade_platform: String,
) -> Result<(), anyhow::Error> {
if trade_platform == PUMPFUN {
pumpfun::buy::buy(
self.rpc.clone(),
self.payer.clone(),
mint,
creator,
dev_buy_token,
dev_sol_cost,
buy_sol_cost,
slippage_basis_points,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
COPY_BUY.to_string(),
).await
} else if trade_platform == PUMPFUN_SWAP {
pumpswap::buy::buy(
self.rpc.clone(),
self.payer.clone(),
mint,
creator,
buy_sol_cost,
slippage_basis_points,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
}
}
/// Buy tokens using Jito
pub async fn buy_with_tip(
pub async fn sniper_buy_with_tip(
&self,
mint: Pubkey,
creator: Pubkey,
@@ -220,9 +291,58 @@ impl PumpFun {
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
SNIPER_BUY.to_string(),
).await
}
pub async fn copy_buy_with_tip(
&self,
mint: Pubkey,
creator: Pubkey,
dev_buy_token: u64,
dev_sol_cost: u64,
buy_sol_cost: u64,
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
trade_platform: String,
) -> Result<(), anyhow::Error> {
if trade_platform == PUMPFUN {
pumpfun::buy::buy_with_tip(
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
dev_buy_token,
dev_sol_cost,
buy_sol_cost,
slippage_basis_points,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
COPY_BUY.to_string(),
).await
} else if trade_platform == PUMPFUN_SWAP {
pumpswap::buy::buy_with_tip(
self.rpc.clone(),
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
buy_sol_cost,
slippage_basis_points,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
}
}
/// Sell tokens
pub async fn sell(
&self,
@@ -251,18 +371,80 @@ impl PumpFun {
percent: u64,
amount_token: u64,
recent_blockhash: Hash,
trade_platform: String,
) -> Result<(), anyhow::Error> {
pumpfun::sell::sell_by_percent(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
percent,
amount_token,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
).await
if trade_platform == PUMPFUN {
pumpfun::sell::sell_by_percent(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
percent,
amount_token,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
).await
} else if trade_platform == PUMPFUN_SWAP {
pumpswap::sell::sell_by_percent(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
percent,
None,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
}
}
/// Sell tokens by amount
pub async fn sell_by_amount(
&self,
mint: Pubkey,
creator: Pubkey,
amount: u64,
recent_blockhash: Hash,
trade_platform: String,
) -> Result<(), anyhow::Error> {
if trade_platform == PUMPFUN {
pumpfun::sell::sell_by_amount(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
amount,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
).await
} else if trade_platform == PUMPFUN_SWAP {
pumpswap::sell::sell_by_amount(
self.rpc.clone(),
self.payer.clone(),
mint.clone(),
creator,
amount,
None,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
}
}
pub async fn sell_by_percent_with_tip(
@@ -272,18 +454,81 @@ impl PumpFun {
percent: u64,
amount_token: u64,
recent_blockhash: Hash,
trade_platform: String,
) -> Result<(), anyhow::Error> {
pumpfun::sell::sell_by_percent_with_tip(
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
percent,
amount_token,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
).await
if trade_platform == PUMPFUN {
pumpfun::sell::sell_by_percent_with_tip(
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
percent,
amount_token,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
).await
} else if trade_platform == PUMPFUN_SWAP {
pumpswap::sell::sell_by_percent_with_tip(
self.rpc.clone(),
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
percent,
None,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
}
}
pub async fn sell_by_amount_with_tip(
&self,
mint: Pubkey,
creator: Pubkey,
amount: u64,
recent_blockhash: Hash,
trade_platform: String,
) -> Result<(), anyhow::Error> {
if trade_platform == PUMPFUN {
pumpfun::sell::sell_by_amount_with_tip(
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
amount,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
recent_blockhash,
).await
} else if trade_platform == PUMPFUN_SWAP {
pumpswap::sell::sell_by_amount_with_tip(
self.rpc.clone(),
self.fee_clients.clone(),
self.payer.clone(),
mint,
creator,
amount,
None,
self.priority_fee.clone(),
self.cluster.clone().lookup_table_key,
None,
None,
None,
None,
None,
).await
} else {
Err(anyhow::anyhow!("Unsupported trade platform: {}", trade_platform))
}
}
/// Sell tokens using Jito
@@ -375,4 +620,32 @@ impl PumpFun {
pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> {
pumpfun::common::close_token_account(&self.rpc, self.payer.as_ref(), mint).await
}
#[inline]
pub async fn get_current_price(&self, mint: &Pubkey) -> Result<f64, anyhow::Error> {
let (bonding_curve, _) = pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves;
let virtual_token_reserves = bonding_curve.virtual_token_reserves;
Ok(pumpfun::common::get_token_price(virtual_sol_reserves, virtual_token_reserves))
}
#[inline]
pub async fn get_real_sol_reserves(&self, mint: &Pubkey) -> Result<u64, anyhow::Error> {
let (bonding_curve, _) = pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
let actual_sol_reserves = bonding_curve.real_sol_reserves;
Ok(actual_sol_reserves)
}
#[inline]
pub async fn get_real_sol_reserves_with_pumpswap(&self, pool_address: &Pubkey) -> Result<u64, anyhow::Error> {
let pool = pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let (_, quote_amount) = pool.get_token_balances(&self.rpc).await?;
Ok(quote_amount)
}
}
+243 -40
View File
@@ -1,17 +1,38 @@
use pumpfun_sdk::{common::{
logs_events::PumpfunEvent,
logs_subscribe::{stop_subscription, tokens_subscription}, AnyResult
}, grpc::ShredStreamGrpc};
use solana_sdk::{commitment_config::CommitmentConfig, transaction::VersionedTransaction};
use std::{str::FromStr, sync::Arc};
use pumpfun_sdk::{
common::{
pumpfun::{
self,
logs_events::PumpfunEvent,
logs_subscribe::{stop_subscription, tokens_subscription},
},
pumpswap::{self, PumpSwapEvent},
AnyResult, Cluster, PriorityFee,
},
grpc::{ShredStreamGrpc, YellowstoneGrpc},
PumpFun,
};
use solana_hash::Hash;
use solana_sdk::{
commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair,
transaction::VersionedTransaction,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let grpc = ShredStreamGrpc::new(
"http://127.0.0.1:10800".to_string(),
).await?;
// test_pumpfun_with_shreds().await?;
// test_pumpfun_with_grpc().await?;
// test_pumpswap_with_shreds().await?;
// test_pumpswap_with_grpc().await?;
test_sell().await?;
Ok(())
}
async fn test_pumpfun_with_shreds() -> Result<(), Box<dyn std::error::Error>> {
let grpc = ShredStreamGrpc::new("http://127.0.0.1:10000".to_string()).await?;
let callback = |event: PumpfunEvent| {
// TradeInfo 的 sol_amount 不是真实线上消费/获取的数量
// 当 is_buy 为 true 时,sol_amount = max_sol_cost,代表用户愿意支付的最大金额
// 当 is_buy 为 false 时,sol_amount = min_sol_output,代表用户愿意接受的最小金额
@@ -26,16 +47,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
},
}
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
},
}
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
},
}
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
},
}
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
@@ -44,45 +65,227 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
grpc.shredstream_subscribe(callback, None).await?;
Ok(())
Ok(())
}
async fn test_pumpfun_with_grpc() -> Result<(), Box<dyn std::error::Error>> {
let grpc = YellowstoneGrpc::new(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None,
)?;
let callback = |event: PumpfunEvent| match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
}
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
}
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
}
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
}
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
};
grpc.subscribe_pumpfun(callback, None).await?;
Ok(())
}
async fn test_pumpswap_with_shreds() -> Result<(), Box<dyn std::error::Error>> {
// 使用 ShredStream 客户端订阅 PumpSwap 事件
println!("正在订阅 PumpSwap ShredStream 事件...");
let grpc_client = ShredStreamGrpc::new("http://140.82.2.197:10800".to_string()).await?;
// 定义回调函数处理 PumpSwap 事件
let callback = |event: PumpSwapEvent| {
match event {
PumpSwapEvent::Buy(buy_event) => {
// println!("buy_event: {:?}", buy_event);
}
PumpSwapEvent::Sell(sell_event) => {
println!("sell_event: {:?}", sell_event);
}
PumpSwapEvent::CreatePool(create_event) => {
// println!("create_event: {:?}", create_event);
}
PumpSwapEvent::Deposit(deposit_event) => {
// println!("deposit_event: {:?}", deposit_event);
}
PumpSwapEvent::Withdraw(withdraw_event) => {
// println!("withdraw_event: {:?}", withdraw_event);
}
PumpSwapEvent::Disable(disable_event) => {
// println!("disable_event: {:?}", disable_event);
}
PumpSwapEvent::UpdateAdmin(update_admin_event) => {
// println!("update_admin_event: {:?}", update_admin_event);
}
PumpSwapEvent::UpdateFeeConfig(update_fee_event) => {
// println!("update_fee_event: {:?}", update_fee_event);
}
PumpSwapEvent::Error(err) => {
println!("error: {}", err);
}
}
};
// 订阅 PumpSwap 事件
println!("开始监听 PumpSwap 事件,按 Ctrl+C 停止...");
grpc_client.shredstream_subscribe_pumpswap(callback).await?;
Ok(())
}
async fn test_pumpswap_with_grpc() -> Result<(), Box<dyn std::error::Error>> {
// 使用 GRPC 客户端订阅 PumpSwap 事件
println!("正在订阅 PumpSwap GRPC 事件...");
let grpc = YellowstoneGrpc::new(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None,
)?;
// 定义回调函数处理 PumpSwap 事件
let callback = |event: PumpSwapEvent| match event {
PumpSwapEvent::Buy(buy_event) => {
println!("buy_event: {:?}", buy_event);
}
PumpSwapEvent::Sell(sell_event) => {
println!("sell_event: {:?}", sell_event);
}
PumpSwapEvent::CreatePool(create_event) => {
println!("create_event: {:?}", create_event);
}
PumpSwapEvent::Deposit(deposit_event) => {
println!("deposit_event: {:?}", deposit_event);
}
PumpSwapEvent::Withdraw(withdraw_event) => {
println!("withdraw_event: {:?}", withdraw_event);
}
PumpSwapEvent::Disable(disable_event) => {
println!("disable_event: {:?}", disable_event);
}
PumpSwapEvent::UpdateAdmin(update_admin_event) => {
println!("update_admin_event: {:?}", update_admin_event);
}
PumpSwapEvent::UpdateFeeConfig(update_fee_event) => {
println!("update_fee_event: {:?}", update_fee_event);
}
PumpSwapEvent::Error(err) => {
println!("error: {}", err);
}
};
// 订阅 PumpSwap 事件
println!("开始监听 PumpSwap 事件,按 Ctrl+C 停止...");
grpc.subscribe_pumpswap(callback).await?;
Ok(())
}
async fn test_sell() -> AnyResult<()> {
let payer = Keypair::new();
// Define cluster configuration
let cluster = Cluster {
rpc_url: "https://mainnet.helius-rpc.com/?api-key=f2f194bb-6bd6-4f20-9a94-7fe0799ade0b"
.to_string(),
commitment: CommitmentConfig::confirmed(),
priority_fee: PriorityFee::default(),
use_jito: false,
use_zeroslot: false,
use_nozomi: false,
use_nextblock: false,
block_engine_url: "".to_string(),
zeroslot_url: "".to_string(),
zeroslot_auth_token: "".to_string(),
nozomi_url: "".to_string(),
nozomi_auth_token: "".to_string(),
nextblock_url: "".to_string(),
nextblock_auth_token: "".to_string(),
lookup_table_key: None,
use_rpc: true,
};
let pumpswap = PumpFun::new(Arc::new(payer), &cluster).await;
let creator = Pubkey::from_str("8BtoThi2ZoXnF7QQK1Wjmh2JuBw9FjVvhnGMVZ2vpump")?;
let dev_buy_token = 0;
let dev_sol_cost = 0;
let buy_sol_cost = 100_000_000;
let slippage_basis_points = Some(100);
let recent_blockhash = Hash::default();
let trade_platform = "pumpswap".to_string();
let mint_pubkey = Pubkey::from_str("8BtoThi2ZoXnF7QQK1Wjmh2JuBw9FjVvhnGMVZ2vpump")?;
println!("Buying tokens from PumpSwap...");
pumpswap
.copy_buy(
mint_pubkey,
creator,
dev_buy_token,
dev_sol_cost,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
trade_platform,
)
.await?;
// 需要先转sol到wsol才能buy
// pumpswap
// .buy(
// mint_pubkey,
// 10_000_000, // 0.01 SOL
// Some(100), // 1% slippage
// )
// .await?;
// println!("Selling tokens to PumpSwap...");
// pumpswap
// .sell_by_percent(
// mint_pubkey,
// 100, // Sell 100% of tokens
// Some(500), // 5% slippage
// )
// .await?;
Ok(())
}
async fn test_wss() -> AnyResult<()> {
println!("Starting token subscription\n");
let ws_url = "wss://api.mainnet-beta.solana.com";
// Set commitment
let commitment = CommitmentConfig::confirmed();
// Define callback function
let callback = |event: PumpfunEvent| {
match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
},
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
},
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
},
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
},
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
let callback = |event: PumpfunEvent| match event {
PumpfunEvent::NewDevTrade(trade_info) => {
println!("Received new dev trade event: {:?}", trade_info);
}
PumpfunEvent::NewToken(token_info) => {
println!("Received new token event: {:?}", token_info);
}
PumpfunEvent::NewUserTrade(trade_info) => {
println!("Received new trade event: {:?}", trade_info);
}
PumpfunEvent::NewBotTrade(trade_info) => {
println!("Received new bot trade event: {:?}", trade_info);
}
PumpfunEvent::Error(err) => {
println!("Received error: {}", err);
}
};
// Start subscription
let subscription = tokens_subscription(
ws_url,
commitment,
callback,
None
).await.unwrap();
let subscription = tokens_subscription(ws_url, commitment, callback, None)
.await
.unwrap();
// Wait for a while to receive events
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
@@ -91,4 +294,4 @@ async fn test_wss() -> AnyResult<()> {
stop_subscription(subscription).await;
Ok(())
}
}
+27 -7
View File
@@ -15,14 +15,16 @@ use crate::{
PriorityFee,
SolanaRpcClient
},
constants::{self, global_constants::FEE_RECIPIENT},
constants::{self, pumpfun::global_constants::FEE_RECIPIENT},
instruction,
swqos::{ClientType, FeeClient, TradeType}
};
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 250000;
use super::common::{calculate_with_slippage_buy, get_buy_token_amount_from_sol_amount, init_bonding_curve_account};
use super::common::{calculate_with_slippage_buy, get_buy_token_amount_from_sol_amount, init_bonding_curve_account, get_bonding_curve_account_v2, get_bonding_curve_pda};
use crate::constants::trade_type::{SNIPER_BUY};
use crate::PumpFun;
/// 添加nonce消费指令到指令集合中
///
@@ -76,10 +78,11 @@ pub async fn buy(
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
trade_type: String,
) -> Result<(), anyhow::Error> {
let start_time = Instant::now();
let mint = Arc::new(mint.clone());
let instructions = build_buy_instructions(payer.clone(), mint.clone(), creator, dev_buy_token, dev_sol_cost, buy_sol_cost, slippage_basis_points).await?;
let instructions = build_buy_instructions(payer.clone(), mint.clone(), creator, dev_buy_token, dev_sol_cost, buy_sol_cost, slippage_basis_points, trade_type).await?;
println!(" 买入交易指令: {:?}", start_time.elapsed());
let start_time = Instant::now();
@@ -191,10 +194,11 @@ pub async fn buy_with_tip(
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
trade_type: String,
) -> Result<(), anyhow::Error> {
let start_time = Instant::now();
let mint = Arc::new(mint.clone());
let instructions = build_buy_instructions(payer.clone(), mint.clone(), creator, dev_buy_token, dev_sol_cost, buy_sol_cost, slippage_basis_points).await?;
let instructions = build_buy_instructions(payer.clone(), mint.clone(), creator, dev_buy_token, dev_sol_cost, buy_sol_cost, slippage_basis_points, trade_type).await?;
println!(" 买入交易指令: {:?}", start_time.elapsed());
let start_time = Instant::now();
@@ -356,7 +360,6 @@ pub async fn build_buy_transaction_with_tip(
}
pub async fn build_buy_instructions(
// rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Arc<Pubkey>,
creator: Pubkey,
@@ -364,12 +367,29 @@ pub async fn build_buy_instructions(
dev_sol_cost: u64,
buy_sol_cost: u64,
slippage_basis_points: Option<u64>,
trade_type: String,
) -> Result<Vec<Instruction>, anyhow::Error> {
if buy_sol_cost == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
let bonding_curve = init_bonding_curve_account(&mint, dev_buy_token, dev_sol_cost, creator).await?;
let bonding_curve = if trade_type == SNIPER_BUY {
init_bonding_curve_account(&mint, dev_buy_token, dev_sol_cost, creator).await?
} else {
let (bonding_curve, _) = get_bonding_curve_account_v2(&PumpFun::get_instance().get_rpc(), &mint).await?;
Arc::new(crate::accounts::BondingCurveAccount {
discriminator: bonding_curve.discriminator,
account: get_bonding_curve_pda(&mint).unwrap(),
virtual_token_reserves: bonding_curve.virtual_token_reserves,
virtual_sol_reserves: bonding_curve.virtual_sol_reserves,
real_token_reserves: bonding_curve.real_token_reserves,
real_sol_reserves: bonding_curve.real_sol_reserves,
token_total_supply: bonding_curve.token_total_supply,
complete: bonding_curve.complete,
creator: creator,
})
};
let max_sol_cost = calculate_with_slippage_buy(buy_sol_cost, slippage_basis_points.unwrap_or(100));
let creator_vault_pda = bonding_curve.get_creator_vault_pda();
@@ -387,7 +407,7 @@ pub async fn build_buy_instructions(
&payer.pubkey(),
&payer.pubkey(),
&mint,
&constants::accounts::TOKEN_PROGRAM,
&constants::pumpfun::accounts::TOKEN_PROGRAM,
));
instructions.push(instruction::buy(
+30 -10
View File
@@ -7,7 +7,8 @@ use solana_sdk::{
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::Transaction
};
use spl_associated_token_account::get_associated_token_address;
use crate::{accounts::{self, BondingCurveAccount}, common::{logs_data::TradeInfo, PriorityFee, SolanaRpcClient}, constants::{self, global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::DEFAULT_SLIPPAGE}};
use pumpfun_program::accounts::BondingCurveAccount as PumpfunBondingCurveAccount;
use crate::{accounts::{self, BondingCurveAccount}, common::{pumpfun::logs_data::TradeInfo, PriorityFee, SolanaRpcClient}, constants::{self, pumpfun::{self, global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::DEFAULT_SLIPPAGE}}};
lazy_static::lazy_static! {
static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<accounts::GlobalAccount>>> = RwLock::new(HashMap::new());
@@ -142,7 +143,7 @@ pub async fn get_sol_balance(rpc: &SolanaRpcClient, account: &Pubkey) -> Result<
#[inline]
pub fn get_global_pda() -> Pubkey {
static GLOBAL_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
Pubkey::find_program_address(&[constants::seeds::GLOBAL_SEED], &constants::accounts::PUMPFUN).0
Pubkey::find_program_address(&[constants::pumpfun::seeds::GLOBAL_SEED], &constants::pumpfun::accounts::PUMPFUN).0
});
*GLOBAL_PDA
}
@@ -150,23 +151,23 @@ pub fn get_global_pda() -> Pubkey {
#[inline]
pub fn get_mint_authority_pda() -> Pubkey {
static MINT_AUTHORITY_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
Pubkey::find_program_address(&[constants::seeds::MINT_AUTHORITY_SEED], &constants::accounts::PUMPFUN).0
Pubkey::find_program_address(&[constants::pumpfun::seeds::MINT_AUTHORITY_SEED], &constants::pumpfun::accounts::PUMPFUN).0
});
*MINT_AUTHORITY_PDA
}
#[inline]
pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 2] = &[constants::seeds::BONDING_CURVE_SEED, mint.as_ref()];
let program_id: &Pubkey = &constants::accounts::PUMPFUN;
let seeds: &[&[u8]; 2] = &[constants::pumpfun::seeds::BONDING_CURVE_SEED, mint.as_ref()];
let program_id: &Pubkey = &constants::pumpfun::accounts::PUMPFUN;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
#[inline]
pub fn get_creator_vault_pda(creator: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 2] = &[constants::seeds::CREATOR_VAULT_SEED, creator.as_ref()];
let program_id: &Pubkey = &constants::accounts::PUMPFUN;
let seeds: &[&[u8]; 2] = &[constants::pumpfun::seeds::CREATOR_VAULT_SEED, creator.as_ref()];
let program_id: &Pubkey = &constants::pumpfun::accounts::PUMPFUN;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
@@ -175,11 +176,11 @@ pub fn get_creator_vault_pda(creator: &Pubkey) -> Option<Pubkey> {
pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
Pubkey::find_program_address(
&[
constants::seeds::METADATA_SEED,
constants::accounts::MPL_TOKEN_METADATA.as_ref(),
constants::pumpfun::seeds::METADATA_SEED,
constants::pumpfun::accounts::MPL_TOKEN_METADATA.as_ref(),
mint.as_ref(),
],
&constants::accounts::MPL_TOKEN_METADATA
&constants::pumpfun::accounts::MPL_TOKEN_METADATA
).0
}
@@ -224,6 +225,25 @@ pub async fn get_bonding_curve_account(
Ok((bonding_curve, bonding_curve_pda))
}
#[inline]
pub async fn get_bonding_curve_account_v2(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Arc<PumpfunBondingCurveAccount>, Pubkey), anyhow::Error> {
let bonding_curve_pda = get_bonding_curve_pda(mint)
.ok_or(anyhow!("Bonding curve not found"))?;
let account = rpc.get_account(&bonding_curve_pda).await?;
if account.data.is_empty() {
return Err(anyhow!("Bonding curve not found"));
}
let bonding_curve = solana_sdk::borsh1::try_from_slice_unchecked::<PumpfunBondingCurveAccount>(&account.data)
.map_err(|e| anyhow::anyhow!("Failed to deserialize bonding curve account: {}", e))?;
Ok((Arc::new(bonding_curve), bonding_curve_pda))
}
// #[inline]
// pub fn get_buy_token_amount(
// mint: &Pubkey,
+2 -2
View File
@@ -219,7 +219,7 @@ pub async fn build_create_and_buy_instructions(
&payer.pubkey(),
&payer.pubkey(),
&mint.pubkey(),
&constants::accounts::TOKEN_PROGRAM,
&constants::pumpfun::accounts::TOKEN_PROGRAM,
));
instructions.push(instruction::buy(
@@ -227,7 +227,7 @@ pub async fn build_create_and_buy_instructions(
&mint.pubkey(),
&bonding_curve_pda,
&creator_vault_pda,
&constants::global_constants::FEE_RECIPIENT,
&constants::pumpfun::global_constants::FEE_RECIPIENT,
instruction::Buy {
_amount: buy_token_amount,
_max_sol_cost: max_sol_cost,
+36 -1
View File
@@ -8,7 +8,7 @@ use spl_token::instruction::close_account;
use tokio::task::JoinHandle;
use std::{str::FromStr, sync::Arc, time::Instant};
use crate::{common::{address_lookup_cache::get_address_lookup_table_account, PriorityFee, SolanaRpcClient}, constants::{global_constants::FEE_RECIPIENT}, instruction, swqos::{FeeClient, TradeType, ClientType}};
use crate::{common::{address_lookup_cache::get_address_lookup_table_account, PriorityFee, SolanaRpcClient}, constants::pumpfun::{global_constants::FEE_RECIPIENT}, instruction, swqos::{FeeClient, TradeType, ClientType}};
use super::common::get_creator_vault_pda;
@@ -56,6 +56,24 @@ pub async fn sell_by_percent(
sell(rpc, payer, mint, creator, amount, priority_fee, lookup_table_key, recent_blockhash).await
}
/// Sell tokens by amount
pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("Amount must be greater than 0"));
}
sell(rpc, payer, mint, creator, amount, priority_fee, lookup_table_key, recent_blockhash).await
}
pub async fn sell_by_percent_with_tip(
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
@@ -75,6 +93,23 @@ pub async fn sell_by_percent_with_tip(
sell_with_tip(fee_clients, payer, mint, creator, amount, priority_fee, lookup_table_key, recent_blockhash).await
}
pub async fn sell_by_amount_with_tip(
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("Amount must be greater than 0"));
}
sell_with_tip(fee_clients, payer, mint, creator, amount, priority_fee, lookup_table_key, recent_blockhash).await
}
/// Sell tokens using Jito
pub async fn sell_with_tip(
fee_clients: Vec<Arc<FeeClient>>,
+556
View File
@@ -0,0 +1,556 @@
use anyhow::anyhow;
use chrono;
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
instruction::{AccountMeta, Instruction},
message::{v0, AddressLookupTableAccount, VersionedMessage},
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction,
transaction::VersionedTransaction,
};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Instant;
use crate::constants::pumpswap::{accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR};
use crate::pumpswap::common::{calculate_with_slippage_buy, find_pool, get_buy_token_amount};
use crate::swqos::FeeClient;
use crate::{
common::{
address_lookup_cache::get_address_lookup_table_account,
nonce_cache::{self, NonceCache},
PriorityFee, SolanaRpcClient,
},
pumpswap::common::{coin_creator_vault_ata, coin_creator_vault_authority},
};
// Constants for compute budget
// Increased from 64KB to 256KB to handle larger transactions
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
/// 添加nonce消费指令到指令集合中
///
/// 只有当同时提供了nonce_pubkey和nonce_program_id时才使用nonce功能
/// 如果nonce被锁定、已使用或未准备好,将返回错误
/// 成功时会锁定并标记nonce为已使用
fn add_nonce_instruction(
instructions: &mut Vec<Instruction>,
payer: &Keypair,
) -> Result<(), anyhow::Error> {
let nonce_cache = NonceCache::get_instance();
let nonce_info = nonce_cache.get_nonce_info();
if let Some(nonce_pubkey) = nonce_info.nonce_account {
let nonce_value = nonce_info.current_nonce;
// 暂不加锁
// if nonce_info.lock {
// return Err(anyhow!("Nonce is locked"));
// }
if nonce_info.used {
return Err(anyhow!("Nonce is used"));
}
if nonce_info.next_buy_time == 0
|| chrono::Utc::now().timestamp() < nonce_info.next_buy_time
{
return Err(anyhow!("Nonce is not ready"));
}
// 加锁 - 暂不加锁
// nonce_cache.lock();
// 创建自定义nonce消费指令
let nonce_consume_ix = Instruction {
program_id: crate::constants::pumpswap::accounts::AMM_PROGRAM,
accounts: vec![
AccountMeta::new(nonce_pubkey, false),
AccountMeta::new_readonly(payer.pubkey(), true),
],
// INSTR_CONSUME = 1, 使用传入的nonce值
data: {
let mut data = vec![1]; // INSTR_CONSUME = 1
data.extend_from_slice(&nonce_value.to_bytes()); // 添加nonce值
data
},
};
instructions.push(nonce_consume_ix);
}
Ok(())
}
/// 验证地址表是否被成功用于编译后的消息中
fn verify_lookup_table_usage(
v0_message: &v0::Message,
address_lookup_table_accounts: &[AddressLookupTableAccount],
) {
if !address_lookup_table_accounts.is_empty() {
println!("消息已编译,使用了地址表引用");
// 如果地址表有地址,但没有被使用,给出警告
if v0_message.address_table_lookups.is_empty() {
println!("警告:编译后的消息没有使用地址表引用!");
} else {
for (i, lookup) in v0_message.address_table_lookups.iter().enumerate() {
println!(
"使用地址表 {}: 可写索引 {} 个, 只读索引 {}",
i,
lookup.writable_indexes.len(),
lookup.readonly_indexes.len()
);
}
}
}
}
// Buy tokens from a Pumpswap pool
pub async fn buy(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let start_time = Instant::now();
let mint = Arc::new(mint.clone());
let creator = Arc::new(creator.clone());
let instructions = match (
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
build_buy_instructions_with_accounts(
rpc.clone(),
payer.clone(),
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
mint.clone(),
creator.clone(),
amount_sol,
slippage_basis_points,
)
.await?
}
_ => {
build_buy_instructions(
rpc.clone(),
payer.clone(),
mint.clone(),
creator.clone(),
amount_sol,
slippage_basis_points,
)
.await?
}
};
println!(" Buy transaction instructions: {:?}", start_time.elapsed());
let start_time = Instant::now();
let transaction = build_buy_transaction(
rpc.clone(),
payer.clone(),
priority_fee.clone(),
instructions,
lookup_table_key,
)
.await?;
println!(" Buy transaction signature: {:?}", start_time.elapsed());
let start_time = Instant::now();
rpc.send_and_confirm_transaction(&transaction).await?;
println!(" Buy transaction confirmation: {:?}", start_time.elapsed());
Ok(())
}
// Buy tokens using a MEV service
pub async fn buy_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let start_time = Instant::now();
let mint = Arc::new(mint.clone());
let creator = Arc::new(creator.clone());
let instructions = match (
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
build_buy_instructions_with_accounts(
rpc.clone(),
payer.clone(),
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
mint.clone(),
creator.clone(),
amount_sol,
slippage_basis_points,
)
.await?
}
_ => {
build_buy_instructions(
rpc.clone(),
payer.clone(),
mint.clone(),
creator.clone(),
amount_sol,
slippage_basis_points,
)
.await?
}
};
println!(" Buy transaction instructions: {:?}", start_time.elapsed());
let start_time = Instant::now();
let mut transactions = vec![];
for fee_client in fee_clients.clone() {
let tip_account = fee_client.get_tip_account()?;
let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?);
let transaction = build_buy_transaction_with_tip(
rpc.clone(),
tip_account,
payer.clone(),
priority_fee.clone(),
instructions.clone(),
lookup_table_key,
)
.await?;
transactions.push(transaction);
}
println!(" Buy transaction signature: {:?}", start_time.elapsed());
let mut handles = vec![];
for (i, fee_client) in fee_clients.iter().enumerate() {
let transaction = transactions[i].clone();
let fee_client = fee_client.clone();
let handle = tokio::spawn(async move {
fee_client
.send_transaction(crate::swqos::TradeType::Buy, &transaction)
.await
});
handles.push(handle);
}
for handle in handles {
let _ = handle.await?;
}
println!(" Buy transaction confirmation: {:?}", start_time.elapsed());
Ok(())
}
// Build a transaction for buying tokens
pub async fn build_buy_transaction(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
priority_fee: PriorityFee,
build_instructions: Vec<Instruction>,
lookup_table_key: Option<Pubkey>,
) -> Result<VersionedTransaction, anyhow::Error> {
let mut instructions = vec![
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
),
ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price),
ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit),
];
// 添加nonce消费指令
if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) {
return Err(e);
}
instructions.extend(build_instructions);
let blockhash = rpc.get_latest_blockhash().await?;
// 确保所有需要签名的账户都被正确标记
for instruction in &instructions {
for account_meta in &instruction.accounts {
if account_meta.is_signer && account_meta.pubkey != payer.pubkey() {
return Err(anyhow!(
"Transaction requires a signature from an account other than the payer: {}",
account_meta.pubkey
));
}
}
}
let mut address_lookup_table_accounts = vec![];
if let Some(lookup_table_key) = lookup_table_key {
let account = get_address_lookup_table_account(&lookup_table_key).await;
address_lookup_table_accounts.push(account);
}
let v0_message = v0::Message::try_compile(
&payer.pubkey(),
&instructions,
&address_lookup_table_accounts,
blockhash,
)
.map_err(|e| anyhow!(e))?;
let versioned_message = VersionedMessage::V0(v0_message.clone());
let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?;
// 验证地址表使用情况
verify_lookup_table_usage(&v0_message, &address_lookup_table_accounts);
Ok(transaction)
}
// Build a transaction with tip for buying tokens
pub async fn build_buy_transaction_with_tip(
rpc: Arc<SolanaRpcClient>,
tip_account: Arc<Pubkey>,
payer: Arc<Keypair>,
priority_fee: PriorityFee,
build_instructions: Vec<Instruction>,
lookup_table_key: Option<Pubkey>,
) -> Result<VersionedTransaction, anyhow::Error> {
let mut instructions = vec![
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
),
ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price),
ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit),
system_instruction::transfer(
&payer.pubkey(),
&tip_account,
sol_to_lamports(priority_fee.buy_tip_fee),
),
];
// 添加nonce消费指令
if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) {
return Err(e);
}
instructions.extend(build_instructions);
let blockhash = rpc.get_latest_blockhash().await?;
// 确保所有需要签名的账户都被正确标记
for instruction in &instructions {
for account_meta in &instruction.accounts {
if account_meta.is_signer && account_meta.pubkey != payer.pubkey() {
return Err(anyhow!(
"Transaction requires a signature from an account other than the payer: {}",
account_meta.pubkey
));
}
}
}
let mut address_lookup_table_accounts = vec![];
if let Some(lookup_table_key) = lookup_table_key {
let account = get_address_lookup_table_account(&lookup_table_key).await;
address_lookup_table_accounts.push(account);
}
let v0_message = v0::Message::try_compile(
&payer.pubkey(),
&instructions,
&address_lookup_table_accounts,
blockhash,
)
.map_err(|e| anyhow!(e))?;
let versioned_message = VersionedMessage::V0(v0_message.clone());
let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?;
// 验证地址表使用情况
verify_lookup_table_usage(&v0_message, &address_lookup_table_accounts);
Ok(transaction)
}
// Build instructions for buying tokens
pub async fn build_buy_instructions(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Arc<Pubkey>,
creator: Arc<Pubkey>,
amount_sol: u64,
slippage_basis_points: Option<u64>,
) -> Result<Vec<Instruction>, anyhow::Error> {
if amount_sol == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// Find the pool for this mint
let pool = find_pool(rpc.as_ref(), mint.as_ref()).await?;
// Create the user's token account if it doesn't exist
let user_base_token_account =
spl_associated_token_account::get_associated_token_address(&payer.pubkey(), mint.as_ref());
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// Get pool token accounts
let pool_base_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
mint.as_ref(),
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
let instructions = build_buy_instructions_with_accounts(
rpc,
payer,
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
mint,
creator,
amount_sol,
slippage_basis_points,
)
.await?;
Ok(instructions)
}
pub async fn build_buy_instructions_with_accounts(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
pool: Arc<Pubkey>,
pool_base_token_account: Arc<Pubkey>,
pool_quote_token_account: Arc<Pubkey>,
user_base_token_account: Arc<Pubkey>,
user_quote_token_account: Arc<Pubkey>,
mint: Arc<Pubkey>,
creator: Arc<Pubkey>,
amount_sol: u64,
slippage_basis_points: Option<u64>,
) -> Result<Vec<Instruction>, anyhow::Error> {
if amount_sol == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// Calculate the expected token amount
let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, amount_sol).await?;
// Calculate the maximum SOL amount with slippage
let max_sol_amount = calculate_with_slippage_buy(
amount_sol,
slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let mut instructions = vec![];
// Create the user's base token account if it doesn't exist
instructions.push(create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
mint.as_ref(),
&accounts::TOKEN_PROGRAM,
));
let coin_creator_vault_ata = coin_creator_vault_ata(*creator.as_ref());
let coin_creator_vault_authority = coin_creator_vault_authority(*creator.as_ref());
// Create the buy instruction
// 注意:账户顺序必须与JavaScript SDK匹配
let accounts = vec![
solana_sdk::instruction::AccountMeta::new_readonly(*pool, false), // pool_id (readonly)
solana_sdk::instruction::AccountMeta::new(payer.pubkey(), true), // user (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(*mint, false), // mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly)
solana_sdk::instruction::AccountMeta::new(*user_base_token_account, false), // user_base_token_account
solana_sdk::instruction::AccountMeta::new(*user_quote_token_account, false), // user_quote_token_account
solana_sdk::instruction::AccountMeta::new(*pool_base_token_account, false), // pool_base_token_account
solana_sdk::instruction::AccountMeta::new(*pool_quote_token_account, false), // pool_quote_token_account
solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly)
solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
accounts::ASSOCIATED_TOKEN_PROGRAM,
false,
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
];
// Create the instruction data
let mut data = vec![];
data.extend_from_slice(&BUY_DISCRIMINATOR);
data.extend_from_slice(&token_amount.to_le_bytes());
data.extend_from_slice(&max_sol_amount.to_le_bytes());
instructions.push(Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
data,
});
Ok(instructions)
}
+85
View File
@@ -0,0 +1,85 @@
use anyhow::anyhow;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use crate::common::SolanaRpcClient;
// Calculate slippage for buy operations
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount + (amount * basis_points / 10000)
}
// Calculate slippage for sell operations
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
if amount <= basis_points / 10000 {
1
} else {
amount - (amount * basis_points / 10000)
}
}
// Get token balance for a specific mint and owner
pub async fn get_token_balance(
rpc: &SolanaRpcClient,
owner: &Keypair,
mint: &Pubkey,
) -> Result<(u64, Pubkey), anyhow::Error> {
let ata = spl_associated_token_account::get_associated_token_address(&owner.pubkey(), mint);
match rpc.get_token_account_balance(&ata).await {
Ok(balance) => {
let amount = balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
Ok((amount, ata))
}
Err(_) => Ok((0, ata)),
}
}
// Find a pool for a specific mint
pub async fn find_pool(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<Pubkey, anyhow::Error> {
let (pool_address, _) = crate::pumpswap::pool::Pool::find_by_mint(rpc, mint).await?;
Ok(pool_address)
}
// Calculate the amount of tokens to receive for a given SOL amount
pub async fn get_buy_token_amount(
rpc: &SolanaRpcClient,
pool: &Pubkey,
sol_amount: u64,
) -> Result<u64, anyhow::Error> {
let pool_data = crate::pumpswap::pool::Pool::fetch(rpc, pool).await?;
pool_data.calculate_buy_amount(rpc, sol_amount).await
}
// Calculate the amount of SOL to receive for a given token amount
pub async fn get_sell_sol_amount(
rpc: &SolanaRpcClient,
pool: &Pubkey,
token_amount: u64,
) -> Result<u64, anyhow::Error> {
let pool_data = crate::pumpswap::pool::Pool::fetch(rpc, pool).await?;
pool_data.calculate_sell_amount(rpc, token_amount).await
}
pub(crate) fn coin_creator_vault_authority(coin_creator: Pubkey) -> Pubkey {
let (pump_pool_authority, _) = Pubkey::find_program_address(
&[b"creator_vault", &coin_creator.to_bytes()],
&crate::constants::pumpswap::accounts::AMM_PROGRAM,
);
pump_pool_authority
}
pub(crate) fn coin_creator_vault_ata(coin_creator: Pubkey) -> Pubkey {
let creator_vault_authority = coin_creator_vault_authority(coin_creator);
let associated_token_creator_vault_authority =
spl_associated_token_account::get_associated_token_address_with_program_id(
&creator_vault_authority,
&crate::constants::pumpswap::accounts::WSOL_TOKEN_ACCOUNT,
&crate::constants::pumpswap::accounts::TOKEN_PROGRAM,
);
associated_token_creator_vault_authority
}
+4
View File
@@ -0,0 +1,4 @@
pub mod buy;
pub mod sell;
pub mod common;
pub mod pool;
+163
View File
@@ -0,0 +1,163 @@
use solana_sdk::pubkey::Pubkey;
use anyhow::anyhow;
use solana_account_decoder::UiAccountEncoding;
use crate::{common::SolanaRpcClient, constants::pumpswap::accounts};
use std::str::FromStr;
#[derive(Debug, Clone)]
pub struct Pool {
pub pool_bump: u8,
pub index: u16,
pub creator: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub lp_mint: Pubkey,
pub pool_base_token_account: Pubkey,
pub pool_quote_token_account: Pubkey,
pub lp_supply: u64,
}
impl Pool {
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
if data.len() < 211 {
return Err(anyhow!("Data too short for Pool account"));
}
// 跳过discriminator (8字节)
let data = &data[8..];
let pool_bump = data[0];
let index = u16::from_le_bytes([data[1], data[2]]);
let creator = Pubkey::new_from_array(data[3..35].try_into().map_err(|e| anyhow!("Failed to convert creator: {:?}", e))?);
let base_mint = Pubkey::new_from_array(data[35..67].try_into().map_err(|e| anyhow!("Failed to convert base_mint: {:?}", e))?);
let quote_mint = Pubkey::new_from_array(data[67..99].try_into().map_err(|e| anyhow!("Failed to convert quote_mint: {:?}", e))?);
let lp_mint = Pubkey::new_from_array(data[99..131].try_into().map_err(|e| anyhow!("Failed to convert lp_mint: {:?}", e))?);
let pool_base_token_account = Pubkey::new_from_array(data[131..163].try_into().map_err(|e| anyhow!("Failed to convert pool_base_token_account: {:?}", e))?);
let pool_quote_token_account = Pubkey::new_from_array(data[163..195].try_into().map_err(|e| anyhow!("Failed to convert pool_quote_token_account: {:?}", e))?);
let lp_supply = u64::from_le_bytes([
data[195], data[196], data[197], data[198],
data[199], data[200], data[201], data[202],
]);
Ok(Self {
pool_bump,
index,
creator,
base_mint,
quote_mint,
lp_mint,
pool_base_token_account,
pool_quote_token_account,
lp_supply,
})
}
pub async fn fetch(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::AMM_PROGRAM {
return Err(anyhow!("Account is not owned by PumpSwap program"));
}
Self::from_bytes(&account.data)
}
pub async fn find_by_mint(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Pubkey, Self), anyhow::Error> {
// 使用getProgramAccounts查找给定mint的池子
let filters = vec![
// solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &mint.to_bytes()),
),
];
let config = solana_rpc_client_api::config::RpcProgramAccountsConfig {
filters: Some(filters),
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: None,
commitment: None,
min_context_slot: None,
},
with_context: None,
sort_results: None,
};
let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
println!("program_id: {:?}", program_id);
let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?;
if accounts.is_empty() {
return Err(anyhow!("No pool found for mint {}", mint));
}
let mut pools: Vec<_> = accounts.into_iter()
.filter_map(|(addr, acc)| {
Self::from_bytes(&acc.data)
.map(|pool| (addr, pool))
.ok()
})
.collect();
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
let (address, pool) = pools[0].clone();
println!("pool: {:?}", pool);
println!("address: {:?}", address);
Ok((address, pool))
}
pub async fn get_token_balances(
&self,
rpc: &SolanaRpcClient,
) -> Result<(u64, u64), anyhow::Error> {
let base_balance = rpc.get_token_account_balance(&self.pool_base_token_account).await?;
let quote_balance = rpc.get_token_account_balance(&self.pool_quote_token_account).await?;
let base_amount = base_balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
let quote_amount = quote_balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
Ok((base_amount, quote_amount))
}
pub async fn calculate_buy_amount(
&self,
rpc: &SolanaRpcClient,
sol_amount: u64,
) -> Result<u64, anyhow::Error> {
let (base_amount, quote_amount) = self.get_token_balances(rpc).await?;
// 使用常数乘积公式 (x * y = k) 计算
let product = base_amount as u128 * quote_amount as u128;
let new_quote_amount = quote_amount as u128 + sol_amount as u128;
let new_base_amount = product / new_quote_amount;
let token_amount = base_amount as u128 - new_base_amount;
Ok(token_amount as u64)
}
pub async fn calculate_sell_amount(
&self,
rpc: &SolanaRpcClient,
token_amount: u64,
) -> Result<u64, anyhow::Error> {
let (base_amount, quote_amount) = self.get_token_balances(rpc).await?;
// 使用常数乘积公式 (x * y = k) 计算
let product = base_amount as u128 * quote_amount as u128;
let new_base_amount = base_amount as u128 + token_amount as u128;
let new_quote_amount = product / new_base_amount;
let sol_amount = quote_amount as u128 - new_quote_amount;
Ok(sol_amount as u64)
}
}
+627
View File
@@ -0,0 +1,627 @@
use anyhow::anyhow;
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
instruction::Instruction,
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction,
transaction::VersionedTransaction,
};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Instant;
use crate::constants::pumpswap::{accounts, trade::DEFAULT_SLIPPAGE, SELL_DISCRIMINATOR};
use crate::pumpswap::common::{
calculate_with_slippage_sell, find_pool, get_sell_sol_amount, get_token_balance,
};
use crate::swqos::FeeClient;
use crate::{
common::{
address_lookup_cache::get_address_lookup_table_account, PriorityFee, SolanaRpcClient,
},
pumpswap::common::{coin_creator_vault_ata, coin_creator_vault_authority},
};
// Constants for compute budget
// Increased from 64KB to 256KB to handle larger transactions
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
// Sell tokens to a Pumpswap pool
pub async fn sell(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let start_time = Instant::now();
let instructions = match (
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
build_sell_instructions_with_accounts(
rpc.clone(),
payer.clone(),
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
Arc::new(mint),
Arc::new(creator),
amount_token,
slippage_basis_points,
)
.await?
}
_ => {
build_sell_instructions(
rpc.clone(),
payer.clone(),
mint.clone(),
creator.clone(),
amount_token,
slippage_basis_points,
)
.await?
}
};
println!(" Sell transaction instructions: {:?}", start_time.elapsed());
let start_time = Instant::now();
let recent_blockhash = rpc.get_latest_blockhash().await?;
let transaction = build_sell_transaction(
rpc.clone(),
payer.clone(),
priority_fee,
instructions,
lookup_table_key,
recent_blockhash,
)
.await?;
println!(" Sell transaction signature: {:?}", start_time.elapsed());
let start_time = Instant::now();
rpc.send_and_confirm_transaction(&transaction).await?;
println!(" Sell transaction confirmation: {:?}", start_time.elapsed());
Ok(())
}
// Sell tokens by percentage
pub async fn sell_by_percent(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell(
rpc,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
/// Sell tokens by amount
pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("Amount must be greater than 0"));
}
sell(
rpc,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Sell tokens using a MEV service
pub async fn sell_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let mut transactions = vec![];
let instructions = match (
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
build_sell_instructions_with_accounts(
rpc.clone(),
payer.clone(),
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
Arc::new(mint),
Arc::new(creator),
amount_token,
slippage_basis_points,
)
.await?
}
_ => {
build_sell_instructions(
rpc.clone(),
payer.clone(),
mint.clone(),
creator.clone(),
amount_token,
slippage_basis_points,
)
.await?
}
};
let recent_blockhash = rpc.get_latest_blockhash().await?;
for fee_client in fee_clients.clone() {
let tip_account = fee_client.get_tip_account()?;
let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?);
let transaction = build_sell_transaction_with_tip(
rpc.clone(),
tip_account,
payer.clone(),
priority_fee.clone(),
instructions.clone(),
lookup_table_key,
recent_blockhash,
)
.await?;
transactions.push(transaction);
}
let mut handles = vec![];
for (i, fee_client) in fee_clients.iter().enumerate() {
let transaction = transactions[i].clone();
let fee_client = fee_client.clone();
let handle = tokio::spawn(async move {
fee_client
.send_transaction(crate::swqos::TradeType::Sell, &transaction)
.await
});
handles.push(handle);
}
for handle in handles {
let _ = handle.await?;
}
Ok(())
}
// Sell tokens by percentage using a MEV service
pub async fn sell_by_percent_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell_with_tip(
rpc,
fee_clients,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Sell tokens by amount using a MEV service
pub async fn sell_by_amount_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<FeeClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("Amount must be greater than 0"));
}
sell_with_tip(
rpc,
fee_clients,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Build a transaction for selling tokens
pub async fn build_sell_transaction(
_rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
priority_fee: PriorityFee,
build_instructions: Vec<Instruction>,
lookup_table_key: Option<Pubkey>,
recent_blockhash: solana_sdk::hash::Hash,
) -> Result<VersionedTransaction, anyhow::Error> {
let mut instructions = vec![
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
),
ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price),
ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit),
];
instructions.extend(build_instructions);
// 确保所有需要签名的账户都被正确标记
for instruction in &instructions {
for account_meta in &instruction.accounts {
if account_meta.is_signer && account_meta.pubkey != payer.pubkey() {
return Err(anyhow!(
"Transaction requires a signature from an account other than the payer: {}",
account_meta.pubkey
));
}
}
}
let mut address_lookup_table_accounts = vec![];
if let Some(lookup_table_key) = lookup_table_key {
let account = get_address_lookup_table_account(&lookup_table_key).await;
address_lookup_table_accounts.push(account);
}
let v0_message = solana_sdk::message::v0::Message::try_compile(
&payer.pubkey(),
&instructions,
&address_lookup_table_accounts,
recent_blockhash,
)
.map_err(|e| anyhow!(e))?;
let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message);
let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?;
Ok(transaction)
}
// Build a transaction with tip for selling tokens
pub async fn build_sell_transaction_with_tip(
_rpc: Arc<SolanaRpcClient>,
tip_account: Arc<Pubkey>,
payer: Arc<Keypair>,
priority_fee: PriorityFee,
build_instructions: Vec<Instruction>,
lookup_table_key: Option<Pubkey>,
recent_blockhash: solana_sdk::hash::Hash,
) -> Result<VersionedTransaction, anyhow::Error> {
let mut instructions = vec![
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
),
ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price),
ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit),
system_instruction::transfer(
&payer.pubkey(),
&tip_account,
sol_to_lamports(priority_fee.sell_tip_fee),
),
];
instructions.extend(build_instructions);
// 确保所有需要签名的账户都被正确标记
for instruction in &instructions {
for account_meta in &instruction.accounts {
if account_meta.is_signer && account_meta.pubkey != payer.pubkey() {
return Err(anyhow!(
"Transaction requires a signature from an account other than the payer: {}",
account_meta.pubkey
));
}
}
}
let mut address_lookup_table_accounts = vec![];
if let Some(lookup_table_key) = lookup_table_key {
let account = get_address_lookup_table_account(&lookup_table_key).await;
address_lookup_table_accounts.push(account);
}
let v0_message = solana_sdk::message::v0::Message::try_compile(
&payer.pubkey(),
&instructions,
&address_lookup_table_accounts,
recent_blockhash,
)
.map_err(|e| anyhow!(e))?;
let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message);
let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?;
Ok(transaction)
}
// Build instructions for selling tokens
pub async fn build_sell_instructions(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
) -> Result<Vec<Instruction>, anyhow::Error> {
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = amount_token.unwrap_or(balance_u64);
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// Find the pool for this mint
let pool = find_pool(rpc.as_ref(), &mint).await?;
// Get token accounts
let user_base_token_account =
spl_associated_token_account::get_associated_token_address(&payer.pubkey(), &mint);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// Get pool token accounts
let pool_base_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&mint,
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
let instructions = build_sell_instructions_with_accounts(
rpc,
payer,
Arc::new(pool),
Arc::new(pool_base_token_account),
Arc::new(pool_quote_token_account),
Arc::new(user_base_token_account),
Arc::new(user_quote_token_account),
Arc::new(mint),
Arc::new(creator),
amount_token,
slippage_basis_points,
)
.await?;
Ok(instructions)
}
pub async fn build_sell_instructions_with_accounts(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
pool: Arc<Pubkey>,
pool_base_token_account: Arc<Pubkey>,
pool_quote_token_account: Arc<Pubkey>,
user_base_token_account: Arc<Pubkey>,
user_quote_token_account: Arc<Pubkey>,
mint: Arc<Pubkey>,
creator: Arc<Pubkey>,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
) -> Result<Vec<Instruction>, anyhow::Error> {
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = amount_token.unwrap_or(balance_u64);
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// Calculate the expected SOL amount
let sol_amount = get_sell_sol_amount(rpc.as_ref(), &pool, amount).await?;
// Calculate the minimum SOL amount with slippage
let min_sol_amount = calculate_with_slippage_sell(
sol_amount,
slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let coin_creator_vault_ata = coin_creator_vault_ata(*creator.as_ref());
let coin_creator_vault_authority = coin_creator_vault_authority(*creator.as_ref());
let mut instructions = vec![];
// Create the user's token account if it doesn't exist
instructions.push(create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
&mint,
&accounts::TOKEN_PROGRAM,
));
// Create the sell instruction
// 注意:账户顺序必须与JavaScript SDK匹配
let accounts = vec![
solana_sdk::instruction::AccountMeta::new_readonly(*pool, false), // pool_id (readonly)
solana_sdk::instruction::AccountMeta::new(payer.pubkey(), true), // user (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(*mint, false), // mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly)
solana_sdk::instruction::AccountMeta::new(*user_base_token_account, false), // user_base_token_account
solana_sdk::instruction::AccountMeta::new(*user_quote_token_account, false), // user_quote_token_account
solana_sdk::instruction::AccountMeta::new(*pool_base_token_account, false), // pool_base_token_account
solana_sdk::instruction::AccountMeta::new(*pool_quote_token_account, false), // pool_quote_token_account
solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly)
solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
accounts::ASSOCIATED_TOKEN_PROGRAM,
false,
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
];
// Create the instruction data
let mut data = vec![];
data.extend_from_slice(&SELL_DISCRIMINATOR);
data.extend_from_slice(&amount.to_le_bytes());
data.extend_from_slice(&min_sol_amount.to_le_bytes());
instructions.push(Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
data,
});
Ok(instructions)
}
+1 -1
View File
@@ -24,7 +24,7 @@ use anyhow::{anyhow, Result};
use rand::{rng, seq::{IndexedRandom, IteratorRandom}};
use solana_sdk::transaction::VersionedTransaction;
use crate::{common::SolanaRpcClient, constants::accounts::{JITO_TIP_ACCOUNTS, NEXTBLOCK_TIP_ACCOUNTS, ZEROSLOT_TIP_ACCOUNTS, NOZOMI_TIP_ACCOUNTS}};
use crate::{common::SolanaRpcClient, constants::pumpfun::accounts::{JITO_TIP_ACCOUNTS, NEXTBLOCK_TIP_ACCOUNTS, ZEROSLOT_TIP_ACCOUNTS, NOZOMI_TIP_ACCOUNTS}};
pub mod api;
pub mod common;