feat: add Raydium CLMM protocol support, bump version to 0.2.1
This commit is contained in:
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
@@ -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` 参数用于自动处理 wSOL(Wrapped 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
@@ -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, ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), &[¶ms.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,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[¶ms.payer.pubkey()],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[¶ms.payer.pubkey()],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
¶ms.mint,
|
||||
if mint_token_in_pool_state_index == 1 {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT
|
||||
} else {
|
||||
¶ms.mint
|
||||
},
|
||||
if mint_token_in_pool_state_index == 1 {
|
||||
¶ms.mint
|
||||
} else {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
@@ -124,7 +135,9 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.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,
|
||||
¶ms.mint,
|
||||
if mint_token_in_pool_state_index == 1 {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT
|
||||
} else {
|
||||
¶ms.mint
|
||||
},
|
||||
if mint_token_in_pool_state_index == 1 {
|
||||
¶ms.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,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[¶ms.payer.pubkey()],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
if protocol_params.auto_handle_wsol {
|
||||
instructions.push(
|
||||
close_account(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
&wsol_token_account,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[¶ms.payer.pubkey()],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
+4
-2
@@ -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() {
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod events;
|
||||
pub mod parser;
|
||||
|
||||
pub use events::*;
|
||||
pub use parser::RaydiumClmmEventParser;
|
||||
+170
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user