feat: refactor event parsing architecture and add Raydium Launchpad support

Major changes:
- Refactor event parsing system: migrate scattered logs_* modules to unified event_parser architecture
- Add unified UnifiedEvent trait and EventParser trait for standardized event parsing interface
- Introduce GenericEventParser and EventParserFactory for plugin-style protocol extension
- Add match_event! macro to simplify event type matching and handling
- Add Raydium Launchpad (Bonk.fun) protocol support:
  - Complete buy/sell trading functionality
  - Pool state management and querying
  - Event parsing and subscription
- Update trading system to support new protocol architecture
- Refactor constants organization, rename raydium to raydium_launchpad
- Update documentation and example code

Technical improvements:
- Unified event interface design for better code maintainability
- Factory pattern implementation for dynamic protocol loading
- Generic event parser to reduce code duplication
- Improved error handling and type safety

Breaking Changes:
- Remove old logs_* modules, use new event_parser system
- Protocol constants path change: raydium -> raydium_launchpad
- Event subscription API updated to unified interface
This commit is contained in:
ysq
2025-07-09 22:29:29 +08:00
parent def16e6b92
commit 44ae51747b
64 changed files with 5122 additions and 4575 deletions
-1
View File
@@ -1 +0,0 @@
pub const DEFAULT_SLIPPAGE_BASIS_POINTS: u64 = 100;
-1
View File
@@ -1,4 +1,3 @@
pub mod constants;
pub mod params;
pub mod traits;
pub mod executor;
+20
View File
@@ -122,6 +122,26 @@ impl ProtocolParams for PumpSwapParams {
}
}
/// RaydiumLaunchpad协议特定参数
#[derive(Clone)]
pub struct RaydiumLaunchpadParams {
pub virtual_base: Option<u128>,
pub virtual_quote: Option<u128>,
pub real_base_before: Option<u128>,
pub real_quote_before: Option<u128>,
pub auto_handle_wsol: bool,
}
impl ProtocolParams for RaydiumLaunchpadParams {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn clone_box(&self) -> Box<dyn ProtocolParams> {
Box::new(self.clone())
}
}
impl BuyParams {
/// 转换为BuyWithTipParams
pub fn with_tip(self, swqos_clients: Vec<Arc<SwqosClient>>) -> BuyWithTipParams {
+17 -30
View File
@@ -1,6 +1,8 @@
use anyhow::{anyhow, Result};
use std::sync::Arc;
use crate::trading::protocols::raydium_launchpad::RaydiumLaunchpadInstructionBuilder;
use super::{
core::{executor::GenericTradeExecutor, traits::TradeExecutor},
protocols::{pumpfun::PumpFunInstructionBuilder, pumpswap::PumpSwapInstructionBuilder},
@@ -11,6 +13,7 @@ use super::{
pub enum Protocol {
PumpFun,
PumpSwap,
RaydiumLaunchpad,
}
impl std::fmt::Display for Protocol {
@@ -18,6 +21,7 @@ impl std::fmt::Display for Protocol {
match self {
Protocol::PumpFun => write!(f, "PumpFun"),
Protocol::PumpSwap => write!(f, "PumpSwap"),
Protocol::RaydiumLaunchpad => write!(f, "RaydiumLaunchpad"),
}
}
}
@@ -29,6 +33,7 @@ impl std::str::FromStr for Protocol {
match s.to_lowercase().as_str() {
"pumpfun" => Ok(Protocol::PumpFun),
"pumpswap" => Ok(Protocol::PumpSwap),
"raydiumlaunchpad" => Ok(Protocol::RaydiumLaunchpad),
_ => Err(anyhow!("Unsupported protocol: {}", s)),
}
}
@@ -49,12 +54,23 @@ impl TradeFactory {
let instruction_builder = Arc::new(PumpSwapInstructionBuilder);
Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpSwap"))
}
Protocol::RaydiumLaunchpad => {
let instruction_builder = Arc::new(RaydiumLaunchpadInstructionBuilder);
Arc::new(GenericTradeExecutor::new(
instruction_builder,
"RaydiumLaunchpad",
))
}
}
}
/// 获取所有支持的协议
pub fn supported_protocols() -> Vec<Protocol> {
vec![Protocol::PumpFun, Protocol::PumpSwap]
vec![
Protocol::PumpFun,
Protocol::PumpSwap,
Protocol::RaydiumLaunchpad,
]
}
/// 检查协议是否支持
@@ -62,32 +78,3 @@ impl TradeFactory {
Self::supported_protocols().contains(protocol)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_protocol_from_str() {
assert_eq!("pumpfun".parse::<Protocol>().unwrap(), Protocol::PumpFun);
assert_eq!("pumpswap".parse::<Protocol>().unwrap(), Protocol::PumpSwap);
assert_eq!("PUMPFUN".parse::<Protocol>().unwrap(), Protocol::PumpFun);
assert!("unknown".parse::<Protocol>().is_err());
}
#[test]
fn test_create_executor() {
let pumpfun_executor = TradeFactory::create_executor(Protocol::PumpFun);
assert_eq!(pumpfun_executor.protocol_name(), "PumpFun");
let pumpswap_executor = TradeFactory::create_executor(Protocol::PumpSwap);
assert_eq!(pumpswap_executor.protocol_name(), "PumpSwap");
}
#[test]
fn test_supported_protocols() {
let protocols = TradeFactory::supported_protocols();
assert!(protocols.contains(&Protocol::PumpFun));
assert!(protocols.contains(&Protocol::PumpSwap));
}
}
+2 -1
View File
@@ -1,2 +1,3 @@
pub mod pumpfun;
pub mod pumpswap;
pub mod pumpswap;
pub mod raydium_launchpad;
+2 -3
View File
@@ -10,14 +10,13 @@ use std::sync::Arc;
use crate::{
accounts::BondingCurveAccount,
constants::{self, pumpfun::global_constants::FEE_RECIPIENT, trade_type::SNIPER_BUY},
constants::{self, pumpfun::{global_constants::FEE_RECIPIENT, trade::DEFAULT_SLIPPAGE}, trade_type::SNIPER_BUY},
instruction,
pumpfun::common::{
calculate_with_slippage_buy, get_bonding_curve_account_v2, get_bonding_curve_pda,
get_buy_token_amount_from_sol_amount, get_creator_vault_pda, init_bonding_curve_account,
},
trading::core::{
constants::DEFAULT_SLIPPAGE_BASIS_POINTS,
params::{BuyParams, PumpFunParams, SellParams},
traits::InstructionBuilder,
},
@@ -50,7 +49,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
params.amount_sol,
params
.slippage_basis_points
.unwrap_or(DEFAULT_SLIPPAGE_BASIS_POINTS),
.unwrap_or(DEFAULT_SLIPPAGE),
);
let creator_vault_pda = bonding_curve.get_creator_vault_pda();
+11 -1
View File
@@ -13,7 +13,6 @@ use crate::{
get_token_balance,
},
trading::core::{
constants::DEFAULT_SLIPPAGE_BASIS_POINTS,
params::{BuyParams, PumpSwapParams, SellParams},
traits::InstructionBuilder,
},
@@ -367,6 +366,17 @@ impl PumpSwapInstructionBuilder {
let mut instructions = vec![];
// 插入wsol
instructions.push(
// 创建wSOL ATA账户,如果不存在
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
// 创建用户的代币账户
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
+284
View File
@@ -0,0 +1,284 @@
use anyhow::{anyhow, Result};
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use crate::{
constants::raydium_launchpad::{
accounts, trade::DEFAULT_SLIPPAGE, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR,
},
raydium_launchpad::{
common::{get_amount_out, get_pool_pda, get_token_balance, get_vault_pda},
pool::Pool,
},
trading::core::{
params::{BuyParams, RaydiumLaunchpadParams, SellParams},
traits::InstructionBuilder,
},
};
/// RaydiumLaunchpad协议的指令构建器
pub struct RaydiumLaunchpadInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for RaydiumLaunchpadInstructionBuilder {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
if params.amount_sol == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
self.build_buy_instructions_with_accounts(params).await
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
self.build_sell_instructions_with_accounts(params).await
}
}
impl RaydiumLaunchpadInstructionBuilder {
/// 使用提供的账户信息构建买入指令
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
) -> Result<Vec<Instruction>> {
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<RaydiumLaunchpadParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumLaunchpad"))?;
let pool_state = get_pool_pda(&params.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
// 创建用户代币账户
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
let base_vault_account = get_vault_pda(&pool_state, &params.mint).unwrap();
let quote_vault_account =
get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
let mut virtual_base = protocol_params.virtual_base.unwrap_or(0);
let mut virtual_quote = protocol_params.virtual_quote.unwrap_or(0);
let mut real_base_before = protocol_params.real_base_before.unwrap_or(0);
let mut real_quote_before = protocol_params.real_quote_before.unwrap_or(0);
if virtual_base == 0
|| virtual_quote == 0
|| real_base_before == 0
|| real_quote_before == 0
{
let pool = Pool::fetch(params.rpc.as_ref().unwrap(), &pool_state).await?;
virtual_base = pool.virtual_base as u128;
virtual_quote = pool.virtual_quote as u128;
real_base_before = pool.real_base as u128;
real_quote_before = pool.real_quote as u128;
}
let amount_in: u64 = params.amount_sol;
let share_fee_rate: u64 = 0;
let minimum_amount_out: u64 = get_amount_out(
amount_in,
accounts::PROTOCOL_FEE_RATE,
accounts::PLATFORM_FEE_RATE,
accounts::SHARE_FEE_RATE,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
);
let mut instructions = vec![];
if protocol_params.auto_handle_wsol {
// 插入wsol
instructions.push(
// 创建wSOL ATA账户,如果不存在
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
instructions.push(
// 将SOL转入wSOL ATA账户
solana_sdk::system_instruction::transfer(
&params.payer.pubkey(),
&user_quote_token_account,
amount_in,
),
);
// 同步wSOL余额
instructions.push(
spl_token::instruction::sync_native(
&accounts::TOKEN_PROGRAM,
&user_quote_token_account,
)
.unwrap(),
);
}
// 创建用户的基础代币账户
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&accounts::TOKEN_PROGRAM,
));
// 创建买入指令
let accounts = vec![
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_CONFIG, false), // Global Config (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::PLATFORM_CONFIG, false), // Platform Config (readonly)
solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // User Base Token
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // User Quote Token
solana_sdk::instruction::AccountMeta::new(base_vault_account, false), // Base Vault
solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault
solana_sdk::instruction::AccountMeta::new(params.mint, false), // Base Token Mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Quote Token Mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Base Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Quote Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // Event Authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::LAUNCHPAD_PROGRAM, false), // Program (readonly)
];
// 创建指令数据
let mut data = vec![];
data.extend_from_slice(&BUY_EXECT_IN_DISCRIMINATOR);
data.extend_from_slice(&amount_in.to_le_bytes());
data.extend_from_slice(&minimum_amount_out.to_le_bytes());
data.extend_from_slice(&share_fee_rate.to_le_bytes());
instructions.push(Instruction {
program_id: accounts::LAUNCHPAD_PROGRAM,
accounts,
data,
});
if protocol_params.auto_handle_wsol {
// 关闭wSOL ATA账户,回收租金
instructions.push(
spl_token::instruction::close_account(
&accounts::TOKEN_PROGRAM,
&user_quote_token_account,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[],
)
.unwrap(),
);
}
Ok(instructions)
}
/// 使用提供的账户信息构建卖出指令
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 获取代币余额
let mut amount = params.amount_token;
if params.amount_token.is_none() || params.amount_token.unwrap_or(0) == 0 {
let balance_u64 =
get_token_balance(rpc.as_ref(), &params.payer.pubkey(), &params.mint).await?;
amount = Some(balance_u64);
}
let amount = amount.unwrap_or(0);
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// 计算预期的SOL数量
let minimum_amount_out: u64 = 1;
let pool_state = get_pool_pda(&params.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
// 创建用户代币账户
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
let base_vault_account = get_vault_pda(&pool_state, &params.mint).unwrap();
let quote_vault_account =
get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
let share_fee_rate: u64 = 0;
let mut instructions = vec![];
// 插入wsol
instructions.push(
// 创建wSOL ATA账户,如果不存在
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
// 创建用户的代币账户
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&accounts::TOKEN_PROGRAM,
));
// 创建卖出指令
let accounts = vec![
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_CONFIG, false), // Global Config (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::PLATFORM_CONFIG, false), // Platform Config (readonly)
solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // User Base Token
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // User Quote Token
solana_sdk::instruction::AccountMeta::new(base_vault_account, false), // Base Vault
solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault
solana_sdk::instruction::AccountMeta::new(params.mint, false), // Base Token Mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Quote Token Mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Base Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Quote Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // Event Authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::LAUNCHPAD_PROGRAM, false), // Program (readonly)
];
// 创建指令数据
let mut data = vec![];
data.extend_from_slice(&SELL_EXECT_IN_DISCRIMINATOR);
data.extend_from_slice(&amount.to_le_bytes());
data.extend_from_slice(&minimum_amount_out.to_le_bytes());
data.extend_from_slice(&share_fee_rate.to_le_bytes());
instructions.push(Instruction {
program_id: accounts::LAUNCHPAD_PROGRAM,
accounts,
data,
});
Ok(instructions)
}
}