feat: add Raydium CLMM protocol support, bump version to 0.2.1

This commit is contained in:
ysq
2025-07-19 21:28:01 +08:00
parent 2ddec98805
commit 7942bea571
16 changed files with 413 additions and 41 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "sol-trade-sdk"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
repository = "https://github.com/0xfnzero/sol-trade-sdk"
+17 -3
View File
@@ -29,11 +29,23 @@ Add the dependency to your `Cargo.toml`:
```toml
# Add to your Cargo.toml
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.0" }
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.1" }
```
## Usage Examples
### Important Parameter Description
#### auto_handle_wsol Parameter
In PumpSwap, Bonk, and Raydium CPMM trading, the `auto_handle_wsol` parameter is used to automatically handle wSOL (Wrapped SOL):
- **Mechanism**:
- When `auto_handle_wsol: true`, the SDK automatically handles the conversion between SOL and wSOL
- When buying: automatically wraps SOL to wSOL for trading
- When selling: automatically unwraps the received wSOL to SOL
- Default value is `true`
### 1. Event Subscription - Monitor Token Trading
#### 1.1 Subscribe to Events Using Yellowstone gRPC
@@ -410,8 +422,9 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
recent_blockhash,
None,
Some(Box::new(RaydiumCpmmParams {
pool_state: Some(pool_state), // If not provided, will auto-calculate wsol-mint direction pool
pool_state: Some(pool_state), // If not provided, will auto-calculate
mint_token_program: Some(spl_token::ID), // Support spl_token or spl_token_2022::ID
mint_token_in_pool_state_index: Some(1), // Index of mint_token in pool_state, default is at index 1
minimum_amount_out: Some(buy_amount_out), // If not provided, defaults to 0
auto_handle_wsol: true, // Automatically handle wSOL wrapping/unwrapping
})),
@@ -430,8 +443,9 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
recent_blockhash,
None,
Some(Box::new(RaydiumCpmmParams {
pool_state: Some(pool_state), // If not provided, will auto-calculate wsol-mint direction pool
pool_state: Some(pool_state), // If not provided, will auto-calculate
mint_token_program: Some(spl_token::ID), // Support spl_token or spl_token_2022::ID
mint_token_in_pool_state_index: Some(1), // Index of mint_token in pool_state, default is at index 1
minimum_amount_out: Some(sell_sol_amount), // If not provided, defaults to 0
auto_handle_wsol: true, // Automatically handle wSOL wrapping/unwrapping
})),
+17 -3
View File
@@ -29,11 +29,23 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
```toml
# 添加到您的 Cargo.toml
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.0" }
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.1" }
```
## 使用示例
### 重要参数说明
#### auto_handle_wsol 参数
在 PumpSwap、Bonk、Raydium CPMM 交易中,`auto_handle_wsol` 参数用于自动处理 wSOLWrapped SOL):
- **作用机制**
-`auto_handle_wsol: true` 时,SDK 会自动处理 SOL 与 wSOL 之间的转换
- 买入时:自动将 SOL 包装为 wSOL 进行交易
- 卖出时:自动将获得的 wSOL 解包装为 SOL
- 默认值为 `true`
### 1. 事件订阅 - 监听代币交易
#### 1.1 使用 Yellowstone gRPC 订阅事件
@@ -408,8 +420,9 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
recent_blockhash,
None,
Some(Box::new(RaydiumCpmmParams {
pool_state: Some(pool_state), // 如果不传,会自动计算 wsol-mint 方向的池子
pool_state: Some(pool_state), // 如果不传,会自动计算
mint_token_program: Some(spl_token::ID), // 支持 spl_token 或 spl_token_2022::ID
mint_token_in_pool_state_index: Some(1), // mint_token 在 pool_state 中的索引,默认在索引1
minimum_amount_out: Some(buy_amount_out), // 如果不传,默认为0
auto_handle_wsol: true, // 自动处理 wSOL 包装/解包装
})),
@@ -428,8 +441,9 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
recent_blockhash,
None,
Some(Box::new(RaydiumCpmmParams {
pool_state: Some(pool_state), // 如果不传,会自动计算 wsol-mint 方向的池子
pool_state: Some(pool_state), // 如果不传,会自动计算
mint_token_program: Some(spl_token::ID), // 支持 spl_token 或 spl_token_2022::ID
mint_token_in_pool_state_index: Some(1), // mint_token 在 pool_state 中的索引,默认在索引1
minimum_amount_out: Some(sell_sol_amount), // 如果不传,默认为0
auto_handle_wsol: true, // 自动处理 wSOL 包装/解包装
})),
+23 -12
View File
@@ -4,9 +4,7 @@ use spl_associated_token_account::instruction::create_associated_token_account_i
use spl_token::instruction::close_account;
use crate::{
constants::bonk::{
accounts, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR,
},
constants::bonk::{accounts, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR},
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::bonk::{
common::{get_amount_out, get_pool_pda, get_vault_pda},
@@ -14,7 +12,7 @@ use crate::{
},
trading::common::utils::get_token_balance,
trading::core::{
params::{BuyParams, BonkParams, SellParams},
params::{BonkParams, BuyParams, SellParams},
traits::InstructionBuilder,
},
};
@@ -70,11 +68,7 @@ impl BonkInstructionBuilder {
let mut real_base = protocol_params.real_base.unwrap_or(0);
let mut real_quote = protocol_params.real_quote.unwrap_or(0);
if virtual_base == 0
|| virtual_quote == 0
|| real_base == 0
|| real_quote == 0
{
if virtual_base == 0 || virtual_quote == 0 || real_base == 0 || real_quote == 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;
@@ -147,7 +141,7 @@ impl BonkInstructionBuilder {
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(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)
@@ -253,7 +247,7 @@ impl BonkInstructionBuilder {
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(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)
@@ -274,7 +268,24 @@ impl BonkInstructionBuilder {
data,
});
instructions.push(close_account(&accounts::TOKEN_PROGRAM, &user_quote_token_account, &params.payer.pubkey(), &params.payer.pubkey(), &[&params.payer.pubkey()]).unwrap());
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?;
if protocol_params.auto_handle_wsol {
instructions.push(
close_account(
&accounts::TOKEN_PROGRAM,
&user_quote_token_account,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[&params.payer.pubkey()],
)
.unwrap(),
);
}
Ok(instructions)
}
+19
View File
@@ -1,6 +1,7 @@
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 spl_token::instruction::close_account;
use crate::{
constants::pumpswap::{accounts, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR},
@@ -361,6 +362,24 @@ impl PumpSwapInstructionBuilder {
data,
});
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
if protocol_params.auto_handle_wsol {
instructions.push(
close_account(
&accounts::TOKEN_PROGRAM,
&user_quote_token_account,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[&params.payer.pubkey()],
)
.unwrap(),
);
}
Ok(instructions)
}
}
+53 -17
View File
@@ -49,10 +49,21 @@ impl RaydiumCpmmInstructionBuilder {
let pool_state = if protocol_params.pool_state.is_some() {
protocol_params.pool_state.unwrap()
} else {
let mint_token_in_pool_state_index = protocol_params
.mint_token_in_pool_state_index
.unwrap_or(1);
get_pool_pda(
&accounts::AMM_CONFIG,
&accounts::WSOL_TOKEN_ACCOUNT,
&params.mint,
if mint_token_in_pool_state_index == 1 {
&accounts::WSOL_TOKEN_ACCOUNT
} else {
&params.mint
},
if mint_token_in_pool_state_index == 1 {
&params.mint
} else {
&accounts::WSOL_TOKEN_ACCOUNT
},
)
.unwrap()
};
@@ -124,7 +135,9 @@ impl RaydiumCpmmInstructionBuilder {
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM),
&protocol_params
.mint_token_program
.unwrap_or(accounts::TOKEN_PROGRAM),
));
// 创建买入指令
@@ -138,7 +151,12 @@ impl RaydiumCpmmInstructionBuilder {
solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Input Vault Account
solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Output Vault Account
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Input Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM), false), // Output Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
protocol_params
.mint_token_program
.unwrap_or(accounts::TOKEN_PROGRAM),
false,
), // Output Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Input token mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Output token mint (readonly)
solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account
@@ -221,10 +239,21 @@ impl RaydiumCpmmInstructionBuilder {
let pool_state = if protocol_params.pool_state.is_some() {
protocol_params.pool_state.unwrap()
} else {
let mint_token_in_pool_state_index = protocol_params
.mint_token_in_pool_state_index
.unwrap_or(1);
get_pool_pda(
&accounts::AMM_CONFIG,
&accounts::WSOL_TOKEN_ACCOUNT,
&params.mint,
if mint_token_in_pool_state_index == 1 {
&accounts::WSOL_TOKEN_ACCOUNT
} else {
&params.mint
},
if mint_token_in_pool_state_index == 1 {
&params.mint
} else {
&accounts::WSOL_TOKEN_ACCOUNT
},
)
.unwrap()
};
@@ -267,7 +296,12 @@ impl RaydiumCpmmInstructionBuilder {
solana_sdk::instruction::AccountMeta::new(wsol_token_account, false), // Output Token Account
solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Input Vault Account
solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Output Vault Account
solana_sdk::instruction::AccountMeta::new_readonly(protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM), false), // Input Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
protocol_params
.mint_token_program
.unwrap_or(accounts::TOKEN_PROGRAM),
false,
), // Input Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Output Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Input token mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Output token mint (readonly)
@@ -285,16 +319,18 @@ impl RaydiumCpmmInstructionBuilder {
data,
});
instructions.push(
close_account(
&accounts::TOKEN_PROGRAM,
&wsol_token_account,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[&params.payer.pubkey()],
)
.unwrap(),
);
if protocol_params.auto_handle_wsol {
instructions.push(
close_account(
&accounts::TOKEN_PROGRAM,
&wsol_token_account,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[&params.payer.pubkey()],
)
.unwrap(),
);
}
Ok(instructions)
}
+4 -2
View File
@@ -353,8 +353,9 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
recent_blockhash,
None,
Some(Box::new(RaydiumCpmmParams {
pool_state: Some(pool_state), // 如果不传,会自动计算 wsol-mint 方向的池子
pool_state: Some(pool_state), // 如果不传,会自动计算
mint_token_program: Some(spl_token::ID), // spl_token_2022::ID
mint_token_in_pool_state_index: Some(1), // mint_token 在 pool_state 中的索引,默认在索引1
minimum_amount_out: Some(buy_amount_out), // 如果不传、默认为0
auto_handle_wsol: true,
})),
@@ -373,8 +374,9 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
recent_blockhash,
None,
Some(Box::new(RaydiumCpmmParams {
pool_state: Some(pool_state), // 如果不传,会自动计算 wsol-mint 方向的池子
pool_state: Some(pool_state), // 如果不传,会自动计算
mint_token_program: Some(spl_token::ID), // spl_token_2022::ID
mint_token_in_pool_state_index: Some(1), // mint_token 在 pool_state 中的索引,默认在索引1
minimum_amount_out: Some(sell_sol_amount), // 如果不传、默认为0
auto_handle_wsol: true,
})),
@@ -13,6 +13,7 @@ pub enum ProtocolType {
PumpFun,
Bonk,
RaydiumCpmm,
RaydiumClmm,
}
/// 事件类型枚举
@@ -44,6 +45,10 @@ pub enum EventType {
RaydiumCpmmSwapBaseInput,
RaydiumCpmmSwapBaseOutput,
// Raydium CLMM 事件
RaydiumClmmSwap,
RaydiumClmmSwapV2,
// 通用事件
Unknown,
}
@@ -66,6 +71,8 @@ impl EventType {
EventType::BonkInitialize => "BonkInitialize".to_string(),
EventType::RaydiumCpmmSwapBaseInput => "RaydiumCpmmSwapBaseInput".to_string(),
EventType::RaydiumCpmmSwapBaseOutput => "RaydiumCpmmSwapBaseOutput".to_string(),
EventType::RaydiumClmmSwap => "RaydiumClmmSwap".to_string(),
EventType::RaydiumClmmSwapV2 => "RaydiumClmmSwapV2".to_string(),
EventType::Unknown => "Unknown".to_string(),
}
}
@@ -56,6 +56,22 @@ pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
Some(u64::from_le_bytes(bytes))
}
pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
if data.len() < offset + 16 {
return None;
}
let bytes: [u8; 16] = data[offset..offset + 16].try_into().ok()?;
Some(u128::from_le_bytes(bytes))
}
pub fn read_u8_le(data: &[u8], offset: usize) -> Option<u8> {
if data.len() < offset + 1 {
return None;
}
let bytes: [u8; 1] = data[offset..offset + 1].try_into().ok()?;
Some(u8::from_le_bytes(bytes))
}
/// 安全地从字节数组中读取u32
pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
if data.len() < offset + 4 {
@@ -436,6 +436,10 @@ impl EventParser for GenericEventParser {
signature: &str,
slot: u64,
) -> Vec<Box<dyn UnifiedEvent>> {
let program_id = accounts[instruction.program_id_index as usize];
if !self.should_handle(&program_id) {
return Vec::new();
}
let mut events = Vec::new();
for (disc, configs) in &self.instruction_configs {
if instruction.data.len() < disc.len() {
+7 -1
View File
@@ -5,7 +5,8 @@ use std::sync::Arc;
use crate::streaming::event_parser::protocols::{
bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID,
pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID,
BonkEventParser, RaydiumCpmmEventParser,
raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, BonkEventParser, RaydiumCpmmEventParser,
RaydiumClmmEventParser,
};
use super::{
@@ -20,6 +21,7 @@ pub enum Protocol {
PumpFun,
Bonk,
RaydiumCpmm,
RaydiumClmm,
}
impl Protocol {
@@ -29,6 +31,7 @@ impl Protocol {
Protocol::PumpFun => vec![PUMPFUN_PROGRAM_ID],
Protocol::Bonk => vec![BONK_PROGRAM_ID],
Protocol::RaydiumCpmm => vec![RAYDIUM_CPMM_PROGRAM_ID],
Protocol::RaydiumClmm => vec![RAYDIUM_CLMM_PROGRAM_ID],
}
}
}
@@ -40,6 +43,7 @@ impl std::fmt::Display for Protocol {
Protocol::PumpFun => write!(f, "PumpFun"),
Protocol::Bonk => write!(f, "Bonk"),
Protocol::RaydiumCpmm => write!(f, "RaydiumCpmm"),
Protocol::RaydiumClmm => write!(f, "RaydiumClmm"),
}
}
}
@@ -53,6 +57,7 @@ impl std::str::FromStr for Protocol {
"pumpfun" => Ok(Protocol::PumpFun),
"bonk" => Ok(Protocol::Bonk),
"raydiumcpmm" => Ok(Protocol::RaydiumCpmm),
"raydiumclmm" => Ok(Protocol::RaydiumClmm),
_ => Err(anyhow!("Unsupported protocol: {}", s)),
}
}
@@ -69,6 +74,7 @@ impl EventParserFactory {
Protocol::PumpFun => Arc::new(PumpFunEventParser::new()),
Protocol::Bonk => Arc::new(BonkEventParser::new()),
Protocol::RaydiumCpmm => Arc::new(RaydiumCpmmEventParser::new()),
Protocol::RaydiumClmm => Arc::new(RaydiumClmmEventParser::new()),
}
}
+3 -1
View File
@@ -2,8 +2,10 @@ pub mod pumpfun;
pub mod pumpswap;
pub mod bonk;
pub mod raydium_cpmm;
pub mod raydium_clmm;
pub use pumpfun::PumpFunEventParser;
pub use pumpswap::PumpSwapEventParser;
pub use bonk::BonkEventParser;
pub use raydium_cpmm::RaydiumCpmmEventParser;
pub use raydium_cpmm::RaydiumCpmmEventParser;
pub use raydium_clmm::RaydiumClmmEventParser;
@@ -0,0 +1,59 @@
use crate::impl_unified_event;
use crate::streaming::event_parser::common::EventMetadata;
// use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
/// 交易
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmSwapEvent {
pub metadata: EventMetadata,
pub amount: u64,
pub other_amount_threshold: u64,
pub sqrt_price_limit_x64: u128,
pub is_base_input: bool,
pub payer: Pubkey,
pub amm_config: Pubkey,
pub pool_state: Pubkey,
pub input_token_account: Pubkey,
pub output_token_account: Pubkey,
pub input_vault: Pubkey,
pub output_vault: Pubkey,
pub observation_state: Pubkey,
pub token_program: Pubkey,
pub tick_array: Pubkey,
pub remaining_accounts: Vec<Pubkey>,
}
impl_unified_event!(RaydiumClmmSwapEvent,);
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmSwapV2Event {
pub metadata: EventMetadata,
pub amount: u64,
pub other_amount_threshold: u64,
pub sqrt_price_limit_x64: u128,
pub is_base_input: bool,
pub payer: Pubkey,
pub amm_config: Pubkey,
pub pool_state: Pubkey,
pub input_token_account: Pubkey,
pub output_token_account: Pubkey,
pub input_vault: Pubkey,
pub output_vault: Pubkey,
pub observation_state: Pubkey,
pub token_program: Pubkey,
pub token_program2022: Pubkey,
pub memo_program: Pubkey,
pub input_vault_mint: Pubkey,
pub output_vault_mint: Pubkey,
pub remaining_accounts: Vec<Pubkey>,
}
impl_unified_event!(RaydiumClmmSwapV2Event,);
/// 事件鉴别器常量
pub mod discriminators {
// 指令鉴别器
pub const SWAP: &[u8] = &[248, 198, 158, 145, 225, 117, 135, 200];
pub const SWAP_V2: &[u8] = &[43, 4, 237, 11, 26, 201, 30, 98];
}
+5
View File
@@ -0,0 +1,5 @@
pub mod events;
pub mod parser;
pub use events::*;
pub use parser::RaydiumClmmEventParser;
+170
View File
@@ -0,0 +1,170 @@
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
use solana_transaction_status::UiCompiledInstruction;
use crate::streaming::event_parser::{
common::{read_u128_le, read_u64_le, read_u8_le, EventMetadata, EventType, ProtocolType},
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::raydium_clmm::{discriminators, RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event},
};
/// Raydium CLMM程序ID
pub const RAYDIUM_CLMM_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK");
/// Raydium CLMM事件解析器
pub struct RaydiumClmmEventParser {
inner: GenericEventParser,
}
impl RaydiumClmmEventParser {
pub fn new() -> Self {
// 配置所有事件类型
let configs = vec![
GenericEventParseConfig {
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::SWAP,
event_type: EventType::RaydiumClmmSwap,
inner_instruction_parser: Self::parse_trade_inner_instruction,
instruction_parser: Self::parse_swap_instruction,
},
GenericEventParseConfig {
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::SWAP_V2,
event_type: EventType::RaydiumClmmSwapV2,
inner_instruction_parser: Self::parse_trade_inner_instruction,
instruction_parser: Self::parse_swap_v2_instruction,
},
];
let inner =
GenericEventParser::new(RAYDIUM_CLMM_PROGRAM_ID, ProtocolType::RaydiumClmm, configs);
Self { inner }
}
/// 解析交易事件
fn parse_trade_inner_instruction(
_data: &[u8],
_metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
None
}
/// 解析交易指令事件
fn parse_swap_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 10 {
return None;
}
let amount = read_u64_le(data, 0)?;
let other_amount_threshold = read_u64_le(data, 8)?;
let sqrt_price_limit_x64 = read_u128_le(data, 16)?;
let is_base_input = read_u8_le(data, 32)?;
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature, accounts[2], accounts[3], accounts[4]
));
Some(Box::new(RaydiumClmmSwapEvent {
metadata,
amount,
other_amount_threshold,
sqrt_price_limit_x64,
is_base_input: is_base_input == 1,
payer: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
input_token_account: accounts[3],
output_token_account: accounts[4],
input_vault: accounts[5],
output_vault: accounts[6],
observation_state: accounts[7],
token_program: accounts[8],
tick_array: accounts[9],
remaining_accounts: accounts[10..].to_vec(),
..Default::default()
}))
}
fn parse_swap_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
return None;
}
let amount = read_u64_le(data, 0)?;
let other_amount_threshold = read_u64_le(data, 8)?;
let sqrt_price_limit_x64 = read_u128_le(data, 16)?;
let is_base_input = read_u8_le(data, 32)?;
let mut metadata = metadata;
metadata.set_id(format!(
"{}-{}-{}-{}",
metadata.signature, accounts[2], accounts[3], accounts[4]
));
Some(Box::new(RaydiumClmmSwapV2Event {
metadata,
amount,
other_amount_threshold,
sqrt_price_limit_x64,
is_base_input: is_base_input == 1,
payer: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
input_token_account: accounts[3],
output_token_account: accounts[4],
input_vault: accounts[5],
output_vault: accounts[6],
observation_state: accounts[7],
token_program: accounts[8],
token_program2022: accounts[9],
memo_program: accounts[10],
input_vault_mint: accounts[11],
output_vault_mint: accounts[12],
remaining_accounts: accounts[13..].to_vec(),
..Default::default()
}))
}
}
#[async_trait::async_trait]
impl EventParser for RaydiumClmmEventParser {
fn parse_events_from_inner_instruction(
&self,
inner_instruction: &UiCompiledInstruction,
signature: &str,
slot: u64,
) -> Vec<Box<dyn UnifiedEvent>> {
self.inner
.parse_events_from_inner_instruction(inner_instruction, signature, slot)
}
fn parse_events_from_instruction(
&self,
instruction: &CompiledInstruction,
accounts: &[Pubkey],
signature: &str,
slot: u64,
) -> Vec<Box<dyn UnifiedEvent>> {
self.inner
.parse_events_from_instruction(instruction, accounts, signature, slot)
}
fn should_handle(&self, program_id: &Pubkey) -> bool {
self.inner.should_handle(program_id)
}
fn supported_program_ids(&self) -> Vec<Pubkey> {
self.inner.supported_program_ids()
}
}
+8 -1
View File
@@ -218,8 +218,14 @@ impl ProtocolParams for BonkParams {
/// RaydiumCpmm协议特定参数
#[derive(Clone)]
pub struct RaydiumCpmmParams {
/// 池子状态账户地址
pub pool_state: Option<Pubkey>,
pub mint_token_program: Option<Pubkey>, // spl_token_2022::ID
/// 代币程序ID
/// 指定代币使用的程序,通常为 spl_token::ID 或 spl_token_2022::ID
pub mint_token_program: Option<Pubkey>,
/// 指定 mint_token 在 pool_state 账户数据中的索引位置
/// 默认值为1,表示在索引1的位置
pub mint_token_in_pool_state_index: Option<usize>,
pub minimum_amount_out: Option<u64>,
pub auto_handle_wsol: bool,
}
@@ -229,6 +235,7 @@ impl RaydiumCpmmParams {
Self {
pool_state: None,
mint_token_program: Some(spl_token::ID),
mint_token_in_pool_state_index: Some(1),
minimum_amount_out: None,
auto_handle_wsol: true,
}