feat: Add Raydium AMM V4 support and enhance protocol parsers

- Add complete Raydium AMM V4 protocol integration
- Enhance PumpFun, CLMM, CPMM event parsing
- Refactor parser architecture for better extensibility
- Update documentation and examples
This commit is contained in:
ysq
2025-08-11 22:02:09 +08:00
parent 97faba9406
commit 8e3d64a09c
19 changed files with 1778 additions and 128 deletions
@@ -27,6 +27,7 @@ pub struct RaydiumClmmSwapEvent {
impl_unified_event!(RaydiumClmmSwapEvent,);
/// 交易v2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmSwapV2Event {
pub metadata: EventMetadata,
@@ -51,9 +52,180 @@ pub struct RaydiumClmmSwapV2Event {
}
impl_unified_event!(RaydiumClmmSwapV2Event,);
/// 关闭仓位
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmClosePositionEvent {
pub metadata: EventMetadata,
pub nft_owner: Pubkey,
pub position_nft_mint: Pubkey,
pub position_nft_account: Pubkey,
pub personal_position: Pubkey,
pub system_program: Pubkey,
pub token_program: Pubkey,
}
impl_unified_event!(RaydiumClmmClosePositionEvent,);
/// 减少流动性v2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmDecreaseLiquidityV2Event {
pub metadata: EventMetadata,
pub liquidity: u128,
pub amount0_min: u64,
pub amount1_min: u64,
pub nft_owner: Pubkey,
pub nft_account: Pubkey,
pub personal_position: Pubkey,
pub pool_state: Pubkey,
pub protocol_position: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub tick_array_lower: Pubkey,
pub tick_array_upper: Pubkey,
pub recipient_token_account0: Pubkey,
pub recipient_token_account1: Pubkey,
pub token_program: Pubkey,
pub token_program2022: Pubkey,
pub memo_program: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
pub remaining_accounts: Vec<Pubkey>,
}
impl_unified_event!(RaydiumClmmDecreaseLiquidityV2Event,);
/// 创建池
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmCreatePoolEvent {
pub metadata: EventMetadata,
pub sqrt_price_x64: u128,
pub open_time: u64,
pub pool_creator: Pubkey,
pub amm_config: Pubkey,
pub pool_state: Pubkey,
pub token_mint0: Pubkey,
pub token_mint1: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub observation_state: Pubkey,
pub tick_array_bitmap: Pubkey,
pub token_program0: Pubkey,
pub token_program1: Pubkey,
pub system_program: Pubkey,
pub rent: Pubkey,
}
impl_unified_event!(RaydiumClmmCreatePoolEvent,);
/// 增加流动性v2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmIncreaseLiquidityV2Event {
pub metadata: EventMetadata,
pub liquidity: u128,
pub amount0_max: u64,
pub amount1_max: u64,
pub base_flag: Option<bool>,
pub nft_owner: Pubkey,
pub nft_account: Pubkey,
pub pool_state: Pubkey,
pub protocol_position: Pubkey,
pub personal_position: Pubkey,
pub tick_array_lower: Pubkey,
pub tick_array_upper: Pubkey,
pub token_account0: Pubkey,
pub token_account1: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub token_program: Pubkey,
pub token_program2022: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
}
impl_unified_event!(RaydiumClmmIncreaseLiquidityV2Event,);
/// 打开仓位v2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmOpenPositionWithToken22NftEvent {
pub metadata: EventMetadata,
pub tick_lower_index: i32,
pub tick_upper_index: i32,
pub tick_array_lower_start_index: i32,
pub tick_array_upper_start_index: i32,
pub liquidity: u128,
pub amount0_max: u64,
pub amount1_max: u64,
pub with_metadata: bool,
pub base_flag: Option<bool>,
pub payer: Pubkey,
pub position_nft_owner: Pubkey,
pub position_nft_mint: Pubkey,
pub position_nft_account: Pubkey,
pub pool_state: Pubkey,
pub protocol_position: Pubkey,
pub tick_array_lower: Pubkey,
pub tick_array_upper: Pubkey,
pub personal_position: Pubkey,
pub token_account0: Pubkey,
pub token_account1: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub rent: Pubkey,
pub system_program: Pubkey,
pub token_program: Pubkey,
pub associated_token_program: Pubkey,
pub token_program2022: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
}
impl_unified_event!(RaydiumClmmOpenPositionWithToken22NftEvent,);
/// 打开仓位V2
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RaydiumClmmOpenPositionV2Event {
pub metadata: EventMetadata,
pub tick_lower_index: i32,
pub tick_upper_index: i32,
pub tick_array_lower_start_index: i32,
pub tick_array_upper_start_index: i32,
pub liquidity: u128,
pub amount0_max: u64,
pub amount1_max: u64,
pub with_metadata: bool,
pub base_flag: Option<bool>,
pub payer: Pubkey,
pub position_nft_owner: Pubkey,
pub position_nft_mint: Pubkey,
pub position_nft_account: Pubkey,
pub metadata_account: Pubkey,
pub pool_state: Pubkey,
pub protocol_position: Pubkey,
pub tick_array_lower: Pubkey,
pub tick_array_upper: Pubkey,
pub personal_position: Pubkey,
pub token_account0: Pubkey,
pub token_account1: Pubkey,
pub token_vault0: Pubkey,
pub token_vault1: Pubkey,
pub rent: Pubkey,
pub system_program: Pubkey,
pub token_program: Pubkey,
pub associated_token_program: Pubkey,
pub metadata_program: Pubkey,
pub token_program2022: Pubkey,
pub vault0_mint: Pubkey,
pub vault1_mint: Pubkey,
pub remaining_accounts: Vec<Pubkey>,
}
impl_unified_event!(RaydiumClmmOpenPositionV2Event,);
/// 事件鉴别器常量
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];
pub const CLOSE_POSITION: &[u8] = &[123, 134, 81, 0, 49, 68, 98, 98];
pub const INCREASE_LIQUIDITY_V2: &[u8] = &[133, 29, 89, 223, 69, 238, 176, 10];
pub const DECREASE_LIQUIDITY_V2: &[u8] = &[58, 127, 188, 62, 79, 82, 196, 96];
pub const CREATE_POOL: &[u8] = &[233, 146, 209, 142, 207, 104, 64, 188];
pub const OPEN_POSITION_WITH_TOKEN_22_NFT: &[u8] = &[77, 255, 174, 82, 125, 29, 201, 46];
pub const OPEN_POSITION_V2: &[u8] = &[77, 184, 74, 214, 112, 86, 241, 199];
}
@@ -5,9 +5,17 @@ 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},
common::{
read_i32_le, read_option_bool, read_u128_le, read_u64_le, read_u8_le, EventMetadata,
EventType, ProtocolType,
},
core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent},
protocols::raydium_clmm::{discriminators, RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event},
protocols::raydium_clmm::{
discriminators, RaydiumClmmClosePositionEvent, RaydiumClmmCreatePoolEvent,
RaydiumClmmDecreaseLiquidityV2Event, RaydiumClmmIncreaseLiquidityV2Event,
RaydiumClmmOpenPositionV2Event, RaydiumClmmOpenPositionWithToken22NftEvent,
RaydiumClmmSwapEvent, RaydiumClmmSwapV2Event,
},
};
/// Raydium CLMM程序ID
@@ -35,7 +43,7 @@ impl RaydiumClmmEventParser {
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::SWAP,
event_type: EventType::RaydiumClmmSwap,
inner_instruction_parser: Self::parse_trade_inner_instruction,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_swap_instruction,
},
GenericEventParseConfig {
@@ -44,9 +52,63 @@ impl RaydiumClmmEventParser {
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::SWAP_V2,
event_type: EventType::RaydiumClmmSwapV2,
inner_instruction_parser: Self::parse_trade_inner_instruction,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_swap_v2_instruction,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::CLOSE_POSITION,
event_type: EventType::RaydiumClmmClosePosition,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_close_position_instruction,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::DECREASE_LIQUIDITY_V2,
event_type: EventType::RaydiumClmmDecreaseLiquidityV2,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_decrease_liquidity_v2_instruction,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::CREATE_POOL,
event_type: EventType::RaydiumClmmCreatePool,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_create_pool_instruction,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::INCREASE_LIQUIDITY_V2,
event_type: EventType::RaydiumClmmIncreaseLiquidityV2,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_increase_liquidity_v2_instruction,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::OPEN_POSITION_WITH_TOKEN_22_NFT,
event_type: EventType::RaydiumClmmOpenPositionWithToken22Nft,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_open_position_with_token_22_nft_instruction,
},
GenericEventParseConfig {
program_id: RAYDIUM_CLMM_PROGRAM_ID,
protocol_type: ProtocolType::RaydiumClmm,
inner_instruction_discriminator: "",
instruction_discriminator: discriminators::OPEN_POSITION_V2,
event_type: EventType::RaydiumClmmOpenPositionV2,
inner_instruction_parser: Self::empty_parse,
instruction_parser: Self::parse_open_position_v2_instruction,
},
];
let inner = GenericEventParser::new(vec![RAYDIUM_CLMM_PROGRAM_ID], configs);
@@ -54,21 +116,234 @@ impl RaydiumClmmEventParser {
Self { inner }
}
/// 解析交易事件
fn parse_trade_inner_instruction(
_data: &[u8],
_metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
fn empty_parse(_data: &[u8], _metadata: EventMetadata) -> Option<Box<dyn UnifiedEvent>> {
None
}
/// 解析打开仓位V2指令事件
fn parse_open_position_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 51 || accounts.len() < 22 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
Some(Box::new(RaydiumClmmOpenPositionV2Event {
metadata,
tick_lower_index: read_i32_le(data, 0)?,
tick_upper_index: read_i32_le(data, 4)?,
tick_array_lower_start_index: read_i32_le(data, 8)?,
tick_array_upper_start_index: read_i32_le(data, 12)?,
liquidity: read_u128_le(data, 16)?,
amount0_max: read_u64_le(data, 32)?,
amount1_max: read_u64_le(data, 40)?,
with_metadata: read_u8_le(data, 48)? == 1,
base_flag: read_option_bool(data, &mut 49)?,
payer: accounts[0],
position_nft_owner: accounts[1],
position_nft_mint: accounts[2],
position_nft_account: accounts[3],
metadata_account: accounts[4],
pool_state: accounts[5],
protocol_position: accounts[6],
tick_array_lower: accounts[7],
tick_array_upper: accounts[8],
personal_position: accounts[9],
token_account0: accounts[10],
token_account1: accounts[11],
token_vault0: accounts[12],
token_vault1: accounts[13],
rent: accounts[14],
system_program: accounts[15],
token_program: accounts[16],
associated_token_program: accounts[17],
metadata_program: accounts[18],
token_program2022: accounts[19],
vault0_mint: accounts[20],
vault1_mint: accounts[21],
remaining_accounts: accounts[22..].to_vec(),
}))
}
/// 解析打开仓位v2指令事件
fn parse_open_position_with_token_22_nft_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 51 || accounts.len() < 20 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
Some(Box::new(RaydiumClmmOpenPositionWithToken22NftEvent {
metadata,
tick_lower_index: read_i32_le(data, 0)?,
tick_upper_index: read_i32_le(data, 4)?,
tick_array_lower_start_index: read_i32_le(data, 8)?,
tick_array_upper_start_index: read_i32_le(data, 12)?,
liquidity: read_u128_le(data, 16)?,
amount0_max: read_u64_le(data, 32)?,
amount1_max: read_u64_le(data, 40)?,
with_metadata: read_u8_le(data, 48)? == 1,
base_flag: read_option_bool(data, &mut 49)?,
payer: accounts[0],
position_nft_owner: accounts[1],
position_nft_mint: accounts[2],
position_nft_account: accounts[3],
pool_state: accounts[4],
protocol_position: accounts[5],
tick_array_lower: accounts[6],
tick_array_upper: accounts[7],
personal_position: accounts[8],
token_account0: accounts[9],
token_account1: accounts[10],
token_vault0: accounts[11],
token_vault1: accounts[12],
rent: accounts[13],
system_program: accounts[14],
token_program: accounts[15],
associated_token_program: accounts[16],
token_program2022: accounts[17],
vault0_mint: accounts[18],
vault1_mint: accounts[19],
}))
}
/// 解析增加流动性v2指令事件
fn parse_increase_liquidity_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 34 || accounts.len() < 15 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
Some(Box::new(RaydiumClmmIncreaseLiquidityV2Event {
metadata,
liquidity: read_u128_le(data, 0)?,
amount0_max: read_u64_le(data, 16)?,
amount1_max: read_u64_le(data, 24)?,
base_flag: read_option_bool(data, &mut 32)?,
nft_owner: accounts[0],
nft_account: accounts[1],
pool_state: accounts[2],
protocol_position: accounts[3],
personal_position: accounts[4],
tick_array_lower: accounts[5],
tick_array_upper: accounts[6],
token_account0: accounts[7],
token_account1: accounts[8],
token_vault0: accounts[9],
token_vault1: accounts[10],
token_program: accounts[11],
token_program2022: accounts[12],
vault0_mint: accounts[13],
vault1_mint: accounts[14],
}))
}
/// 解析创建池指令事件
fn parse_create_pool_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 24 || accounts.len() < 13 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
Some(Box::new(RaydiumClmmCreatePoolEvent {
metadata,
sqrt_price_x64: read_u128_le(data, 0)?,
open_time: read_u64_le(data, 16)?,
pool_creator: accounts[0],
amm_config: accounts[1],
pool_state: accounts[2],
token_mint0: accounts[3],
token_mint1: accounts[4],
token_vault0: accounts[5],
token_vault1: accounts[6],
observation_state: accounts[7],
tick_array_bitmap: accounts[8],
token_program0: accounts[9],
token_program1: accounts[10],
system_program: accounts[11],
rent: accounts[12],
}))
}
/// 解析减少流动性v2指令事件
fn parse_decrease_liquidity_v2_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 32 || accounts.len() < 16 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
Some(Box::new(RaydiumClmmDecreaseLiquidityV2Event {
metadata,
liquidity: read_u128_le(data, 0)?,
amount0_min: read_u64_le(data, 16)?,
amount1_min: read_u64_le(data, 24)?,
nft_owner: accounts[0],
nft_account: accounts[1],
personal_position: accounts[2],
pool_state: accounts[3],
protocol_position: accounts[4],
token_vault0: accounts[5],
token_vault1: accounts[6],
tick_array_lower: accounts[7],
tick_array_upper: accounts[8],
recipient_token_account0: accounts[9],
recipient_token_account1: accounts[10],
token_program: accounts[11],
token_program2022: accounts[12],
memo_program: accounts[13],
vault0_mint: accounts[14],
vault1_mint: accounts[15],
remaining_accounts: accounts[16..].to_vec(),
}))
}
/// 解析关闭仓位指令事件
fn parse_close_position_instruction(
_data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if accounts.len() < 6 {
return None;
}
let mut metadata = metadata;
metadata.set_id(format!("{}-{}-{}", metadata.signature, accounts[0], accounts[1]));
Some(Box::new(RaydiumClmmClosePositionEvent {
metadata,
nft_owner: accounts[0],
position_nft_mint: accounts[1],
position_nft_account: accounts[2],
personal_position: accounts[3],
system_program: accounts[4],
token_program: accounts[5],
}))
}
/// 解析交易指令事件
fn parse_swap_instruction(
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 10 {
if data.len() < 33 || accounts.len() < 10 {
return None;
}
@@ -108,7 +383,7 @@ impl RaydiumClmmEventParser {
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<Box<dyn UnifiedEvent>> {
if data.len() < 16 || accounts.len() < 13 {
if data.len() < 33 || accounts.len() < 13 {
return None;
}