feat: add Meteora DAMM v2 protocol support

This commit is contained in:
ysq
2025-11-10 00:38:23 +08:00
parent 6e3d2c23d1
commit ad3a54625c
12 changed files with 1074 additions and 15 deletions
@@ -0,0 +1,417 @@
use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
use crate::streaming::event_parser::common::EventMetadata;
/// Base fee parameters
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct BaseFeeParameters {
pub cliff_fee_numerator: u64,
pub first_factor: u16,
pub second_factor: [u8; 8],
pub third_factor: u64,
pub base_fee_mode: u8,
}
/// Dynamic fee parameters
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct DynamicFeeParameters {
pub bin_step: u16,
pub bin_step_u128: u128,
pub filter_period: u16,
pub decay_period: u16,
pub reduction_factor: u16,
pub max_volatility_accumulator: u32,
pub variable_fee_control: u32,
}
/// Pool fee parameters
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct PoolFeeParameters {
pub base_fee: BaseFeeParameters,
pub padding: [u8; 3],
pub dynamic_fee: Option<DynamicFeeParameters>,
}
/// Meteora DAMM v2 Swap Event (对应 swap 指令)
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct MeteoraDammV2SwapEvent {
#[borsh(skip)]
pub metadata: EventMetadata,
// 来自 CPI Log Event 的数据
pub pool: Pubkey,
pub trade_direction: u8, // 0 or 1
pub collect_fee_mode: u8,
pub has_referral: bool,
// Swap parameters
pub amount_0: u64, // amount0 from params
pub amount_1: u64, // amount1 from params
pub swap_mode: u8, // swapMode from params
// Swap result
pub included_fee_input_amount: u64,
pub excluded_fee_input_amount: u64,
pub amount_left: u64,
pub output_amount: u64,
pub next_sqrt_price: u128,
pub trading_fee: u64,
pub protocol_fee: u64,
pub partner_fee: u64,
pub referral_fee: u64,
// Transfer fee amounts
pub included_transfer_fee_amount_in: u64,
pub included_transfer_fee_amount_out: u64,
pub excluded_transfer_fee_amount_out: u64,
// Additional info
pub current_timestamp: u64,
pub reserve_a_amount: u64,
pub reserve_b_amount: u64,
// 来自 Input Accounts 的数据
#[borsh(skip)]
pub pool_authority: Pubkey,
#[borsh(skip)]
pub input_token_account: Pubkey,
#[borsh(skip)]
pub output_token_account: Pubkey,
#[borsh(skip)]
pub token_a_vault: Pubkey,
#[borsh(skip)]
pub token_b_vault: Pubkey,
#[borsh(skip)]
pub token_a_mint: Pubkey,
#[borsh(skip)]
pub token_b_mint: Pubkey,
#[borsh(skip)]
pub payer: Pubkey,
#[borsh(skip)]
pub token_a_program: Pubkey,
#[borsh(skip)]
pub token_b_program: Pubkey,
#[borsh(skip)]
pub referral_token_account: Option<Pubkey>,
#[borsh(skip)]
pub event_authority: Pubkey,
#[borsh(skip)]
pub program: Pubkey,
}
/// Meteora DAMM v2 Swap2 Event (对应 swap2 指令)
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct MeteoraDammV2Swap2Event {
#[borsh(skip)]
pub metadata: EventMetadata,
// 来自 CPI Log Event 的数据
pub pool: Pubkey,
pub trade_direction: u8, // 0 or 1
pub collect_fee_mode: u8,
pub has_referral: bool,
// Swap parameters
pub amount_0: u64, // amount0 from params
pub amount_1: u64, // amount1 from params
pub swap_mode: u8, // swapMode from params
// Swap result
pub included_fee_input_amount: u64,
pub excluded_fee_input_amount: u64,
pub amount_left: u64,
pub output_amount: u64,
pub next_sqrt_price: u128,
pub trading_fee: u64,
pub protocol_fee: u64,
pub partner_fee: u64,
pub referral_fee: u64,
// Transfer fee amounts
pub included_transfer_fee_amount_in: u64,
pub included_transfer_fee_amount_out: u64,
pub excluded_transfer_fee_amount_out: u64,
// Additional info
pub current_timestamp: u64,
pub reserve_a_amount: u64,
pub reserve_b_amount: u64,
// 来自 Input Accounts 的数据
#[borsh(skip)]
pub pool_authority: Pubkey,
#[borsh(skip)]
pub input_token_account: Pubkey,
#[borsh(skip)]
pub output_token_account: Pubkey,
#[borsh(skip)]
pub token_a_vault: Pubkey,
#[borsh(skip)]
pub token_b_vault: Pubkey,
#[borsh(skip)]
pub token_a_mint: Pubkey,
#[borsh(skip)]
pub token_b_mint: Pubkey,
#[borsh(skip)]
pub payer: Pubkey,
#[borsh(skip)]
pub token_a_program: Pubkey,
#[borsh(skip)]
pub token_b_program: Pubkey,
#[borsh(skip)]
pub referral_token_account: Option<Pubkey>,
#[borsh(skip)]
pub event_authority: Pubkey,
#[borsh(skip)]
pub program: Pubkey,
#[borsh(skip)]
pub sysvar: Pubkey,
}
/// Meteora DAMM v2 Initialize Pool Event (对应 initialize_pool 指令)
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct MeteoraDammV2InitializePoolEvent {
#[borsh(skip)]
pub metadata: EventMetadata,
// 来自 CPI Log Event 的数据
pub pool: Pubkey,
pub token_a_mint: Pubkey,
pub token_b_mint: Pubkey,
pub creator: Pubkey,
pub payer: Pubkey,
pub alpha_vault: Pubkey,
// Pool fees
pub pool_fees: PoolFeeParameters,
// Price and liquidity
pub sqrt_min_price: u128,
pub sqrt_max_price: u128,
pub activation_type: u8,
pub collect_fee_mode: u8,
pub liquidity: u128,
pub sqrt_price: u128,
pub activation_point: u64,
// Token amounts
pub token_a_flag: u8,
pub token_b_flag: u8,
pub token_a_amount: u64,
pub token_b_amount: u64,
pub total_amount_a: u64,
pub total_amount_b: u64,
pub pool_type: u8,
// 来自 Input Accounts 的数据
#[borsh(skip)]
pub position_nft_mint: Pubkey,
#[borsh(skip)]
pub position_nft_account: Pubkey,
#[borsh(skip)]
pub pool_authority: Pubkey,
#[borsh(skip)]
pub position: Pubkey,
#[borsh(skip)]
pub token_a_vault: Pubkey,
#[borsh(skip)]
pub token_b_vault: Pubkey,
#[borsh(skip)]
pub payer_token_a: Pubkey,
#[borsh(skip)]
pub payer_token_b: Pubkey,
#[borsh(skip)]
pub token_a_program: Pubkey,
#[borsh(skip)]
pub token_b_program: Pubkey,
#[borsh(skip)]
pub event_authority: Pubkey,
#[borsh(skip)]
pub program: Pubkey,
#[borsh(skip)]
pub config: Pubkey,
#[borsh(skip)]
pub remaining_accounts: Vec<Pubkey>,
}
/// Meteora DAMM v2 Initialize Customizable Pool Event (对应 initialize_customizable_pool 指令)
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct MeteoraDammV2InitializeCustomizablePoolEvent {
#[borsh(skip)]
pub metadata: EventMetadata,
// 来自 CPI Log Event 的数据
pub pool: Pubkey,
pub token_a_mint: Pubkey,
pub token_b_mint: Pubkey,
pub creator: Pubkey,
pub payer: Pubkey,
pub alpha_vault: Pubkey,
// Pool fees
pub pool_fees: PoolFeeParameters,
// Price and liquidity
pub sqrt_min_price: u128,
pub sqrt_max_price: u128,
pub activation_type: u8,
pub collect_fee_mode: u8,
pub liquidity: u128,
pub sqrt_price: u128,
pub activation_point: u64,
// Token amounts
pub token_a_flag: u8,
pub token_b_flag: u8,
pub token_a_amount: u64,
pub token_b_amount: u64,
pub total_amount_a: u64,
pub total_amount_b: u64,
pub pool_type: u8,
// 来自 Input Accounts 的数据
#[borsh(skip)]
pub position_nft_mint: Pubkey,
#[borsh(skip)]
pub position_nft_account: Pubkey,
#[borsh(skip)]
pub pool_authority: Pubkey,
#[borsh(skip)]
pub position: Pubkey,
#[borsh(skip)]
pub token_a_vault: Pubkey,
#[borsh(skip)]
pub token_b_vault: Pubkey,
#[borsh(skip)]
pub payer_token_a: Pubkey,
#[borsh(skip)]
pub payer_token_b: Pubkey,
#[borsh(skip)]
pub token_a_program: Pubkey,
#[borsh(skip)]
pub token_b_program: Pubkey,
#[borsh(skip)]
pub token_2022_program: Pubkey,
#[borsh(skip)]
pub system_program: Pubkey,
#[borsh(skip)]
pub event_authority: Pubkey,
#[borsh(skip)]
pub program: Pubkey,
#[borsh(skip)]
pub remaining_accounts: Vec<Pubkey>,
}
/// Meteora DAMM v2 Initialize Pool With Dynamic Config Event (对应 initialize_pool_with_dynamic_config 指令)
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct MeteoraDammV2InitializePoolWithDynamicConfigEvent {
#[borsh(skip)]
pub metadata: EventMetadata,
// 来自 CPI Log Event 的数据
pub pool: Pubkey,
pub token_a_mint: Pubkey,
pub token_b_mint: Pubkey,
pub creator: Pubkey,
pub payer: Pubkey,
pub alpha_vault: Pubkey,
// Pool fees
pub pool_fees: PoolFeeParameters,
// Price and liquidity
pub sqrt_min_price: u128,
pub sqrt_max_price: u128,
pub activation_type: u8,
pub collect_fee_mode: u8,
pub liquidity: u128,
pub sqrt_price: u128,
pub activation_point: u64,
// Token amounts
pub token_a_flag: u8,
pub token_b_flag: u8,
pub token_a_amount: u64,
pub token_b_amount: u64,
pub total_amount_a: u64,
pub total_amount_b: u64,
pub pool_type: u8,
// 来自 Input Accounts 的数据
#[borsh(skip)]
pub position_nft_mint: Pubkey,
#[borsh(skip)]
pub position_nft_account: Pubkey,
#[borsh(skip)]
pub pool_authority: Pubkey,
#[borsh(skip)]
pub pool_creator_authority: Pubkey,
#[borsh(skip)]
pub position: Pubkey,
#[borsh(skip)]
pub token_a_vault: Pubkey,
#[borsh(skip)]
pub token_b_vault: Pubkey,
#[borsh(skip)]
pub payer_token_a: Pubkey,
#[borsh(skip)]
pub payer_token_b: Pubkey,
#[borsh(skip)]
pub token_a_program: Pubkey,
#[borsh(skip)]
pub token_b_program: Pubkey,
#[borsh(skip)]
pub token_2022_program: Pubkey,
#[borsh(skip)]
pub system_program: Pubkey,
#[borsh(skip)]
pub event_authority: Pubkey,
#[borsh(skip)]
pub program: Pubkey,
#[borsh(skip)]
pub config: Pubkey,
}
/// Event discriminators
pub mod discriminators {
// Instruction discriminators
// 从文档中提取的 instruction data 第一个 8 bytes
pub const SWAP_IX: &[u8] = &[0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8]; // swap
pub const SWAP2_IX: &[u8] = &[0x41, 0x4b, 0x3f, 0x4c, 0xeb, 0x5b, 0x5b, 0x88]; // swap2
pub const INITIALIZE_CUSTOMIZABLE_POOL_IX: &[u8] =
&[0x14, 0xa1, 0xf1, 0x18, 0xbd, 0xdd, 0xb4, 0x02]; // initialize_customizable_pool
pub const INITIALIZE_POOL_IX: &[u8] = &[0x5f, 0xb4, 0x0a, 0xac, 0x54, 0xae, 0xe8, 0x28]; // initialize_pool
pub const INITIALIZE_POOL_WITH_DYNAMIC_CONFIG_IX: &[u8] =
&[0x95, 0x52, 0x48, 0xc5, 0xfd, 0xfc, 0x44, 0x0f]; // initialize_pool_with_dynamic_config
// Event discriminators (CPI Log Event)
// e445a52e51cb9a1d 是 Meteora 的事件前缀
// 后面的 8 字节是具体事件类型
pub const SWAP_EVENT: &[u8] = &[
0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xbd, 0x42, 0x33, 0xa8, 0x26, 0x50, 0x75,
0x99,
]; // swap event
pub const INITIALIZE_POOL_EVENT: &[u8] = &[
0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d, 0xe4, 0x32, 0xf6, 0x55, 0xcb, 0x42, 0x86,
0x25,
]; // initialize pool event
}
/// Decode swap event from CPI log
pub const METEORA_DAMM_V2_SWAP_EVENT_LOG_SIZE: usize = 180;
pub fn meteora_damm_v2_swap_event_decode(data: &[u8]) -> Option<MeteoraDammV2SwapEvent> {
if data.len() < METEORA_DAMM_V2_SWAP_EVENT_LOG_SIZE {
return None;
}
borsh::from_slice::<MeteoraDammV2SwapEvent>(&data[..METEORA_DAMM_V2_SWAP_EVENT_LOG_SIZE]).ok()
}
/// Decode initialize pool event from CPI log
/// Note: discriminator (16 bytes) is already removed by the caller
pub fn meteora_damm_v2_initialize_pool_event_decode(
data: &[u8],
) -> Option<MeteoraDammV2InitializePoolEvent> {
borsh::from_slice::<MeteoraDammV2InitializePoolEvent>(&data).ok()
}
@@ -0,0 +1,5 @@
pub mod events;
pub mod parser;
pub mod types;
pub use events::*;
@@ -0,0 +1,444 @@
use crate::streaming::event_parser::{
common::{EventMetadata, EventType},
protocols::meteora_damm_v2::{
discriminators, meteora_damm_v2_initialize_pool_event_decode,
meteora_damm_v2_swap_event_decode, MeteoraDammV2InitializeCustomizablePoolEvent,
MeteoraDammV2InitializePoolEvent, MeteoraDammV2InitializePoolWithDynamicConfigEvent,
MeteoraDammV2Swap2Event, MeteoraDammV2SwapEvent,
},
DexEvent,
};
use solana_sdk::pubkey::Pubkey;
/// Meteora DAMM v2 程序ID
pub const METEORA_DAMM_V2_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG");
/// 解析 Meteora DAMM v2 instruction data
///
/// 根据判别器路由到具体的 instruction 解析函数
pub fn parse_meteora_damm_v2_instruction_data(
discriminator: &[u8],
data: &[u8],
accounts: &[Pubkey],
metadata: EventMetadata,
) -> Option<DexEvent> {
match discriminator {
discriminators::SWAP_IX => parse_swap_instruction(data, accounts, metadata),
discriminators::SWAP2_IX => parse_swap2_instruction(data, accounts, metadata),
discriminators::INITIALIZE_POOL_IX => {
parse_initialize_pool_instruction(data, accounts, metadata)
}
discriminators::INITIALIZE_CUSTOMIZABLE_POOL_IX => {
parse_initialize_customizable_pool_instruction(data, accounts, metadata)
}
discriminators::INITIALIZE_POOL_WITH_DYNAMIC_CONFIG_IX => {
parse_initialize_pool_with_dynamic_config_instruction(data, accounts, metadata)
}
_ => None,
}
}
/// 解析 Meteora DAMM v2 inner instruction data (CPI events)
///
/// 根据判别器路由到具体的 inner instruction 解析函数
pub fn parse_meteora_damm_v2_inner_instruction_data(
discriminator: &[u8],
data: &[u8],
metadata: EventMetadata,
) -> Option<DexEvent> {
match discriminator {
discriminators::SWAP_EVENT => parse_swap_inner_instruction(data, metadata),
discriminators::INITIALIZE_POOL_EVENT => {
parse_initialize_pool_inner_instruction(data, metadata)
}
_ => None,
}
}
/// 解析 swap 指令
fn parse_swap_instruction(
data: &[u8],
accounts: &[Pubkey],
mut metadata: EventMetadata,
) -> Option<DexEvent> {
metadata.event_type = EventType::MeteoraDammV2Swap;
if data.len() < 16 || accounts.len() < 14 {
return None;
}
// 跳过 discriminator (8 bytes)
let amount_in = u64::from_le_bytes(data[0..8].try_into().unwrap());
let minimum_amount_out = u64::from_le_bytes(data[8..16].try_into().unwrap());
Some(DexEvent::MeteoraDammV2SwapEvent(MeteoraDammV2SwapEvent {
metadata,
pool_authority: accounts[0],
pool: accounts[1],
input_token_account: accounts[2],
output_token_account: accounts[3],
token_a_vault: accounts[4],
token_b_vault: accounts[5],
token_a_mint: accounts[6],
token_b_mint: accounts[7],
payer: accounts[8],
token_a_program: accounts[9],
token_b_program: accounts[10],
referral_token_account: Some(accounts[11]),
event_authority: accounts[12],
program: accounts[13],
amount_0: amount_in,
amount_1: minimum_amount_out,
..Default::default()
}))
}
/// 解析 swap2 指令
fn parse_swap2_instruction(
data: &[u8],
accounts: &[Pubkey],
mut metadata: EventMetadata,
) -> Option<DexEvent> {
metadata.event_type = EventType::MeteoraDammV2Swap2;
if data.len() < 16 || accounts.len() < 13 {
return None;
}
// 跳过 discriminator (8 bytes)
let amount_0 = u64::from_le_bytes(data[0..8].try_into().unwrap());
let amount_1 = u64::from_le_bytes(data[8..16].try_into().unwrap());
let swap_mode = data[16];
// swap2 可能有 15 个账户(带 referral)或 14 个账户
let has_referral = accounts.len() >= 15;
Some(DexEvent::MeteoraDammV2Swap2Event(MeteoraDammV2Swap2Event {
metadata,
pool_authority: accounts[0],
pool: accounts[1],
input_token_account: accounts[2],
output_token_account: accounts[3],
token_a_vault: accounts[4],
token_b_vault: accounts[5],
token_a_mint: accounts[6],
token_b_mint: accounts[7],
payer: accounts[8],
token_a_program: accounts[9],
token_b_program: accounts[10],
referral_token_account: if has_referral && accounts.len() > 11 {
Some(accounts[11])
} else {
None
},
event_authority: accounts[if has_referral { 12 } else { 11 }],
program: accounts[if has_referral { 13 } else { 12 }],
sysvar: accounts[if has_referral { 14 } else { 13 }],
amount_0,
amount_1,
swap_mode,
has_referral,
..Default::default()
}))
}
/// 解析 initialize_pool 指令
fn parse_initialize_pool_instruction(
data: &[u8],
accounts: &[Pubkey],
mut metadata: EventMetadata,
) -> Option<DexEvent> {
metadata.event_type = EventType::MeteoraDammV2InitializePool;
if accounts.len() < 20 {
return None;
}
// 解析 instruction data (不包含 discriminator,已被调用者移除)
// 结构: liquidity (u128 = 16 bytes) + sqrt_price (u128 = 16 bytes) + activation_point (Option<u64> = 1 + 8 bytes)
if data.len() < 33 {
return None;
}
let mut offset = 0;
// 读取 liquidity (u128)
let liquidity = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 sqrt_price (u128)
let sqrt_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 activation_point (Option<u64>)
let option_tag = data[offset];
offset += 1;
let _activation_point = if option_tag == 1 && data.len() >= offset + 8 {
Some(u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?))
} else {
None
};
Some(DexEvent::MeteoraDammV2InitializePoolEvent(MeteoraDammV2InitializePoolEvent {
metadata,
creator: accounts[0],
position_nft_mint: accounts[1],
position_nft_account: accounts[2],
payer: accounts[3],
config: accounts[4],
pool_authority: accounts[5],
pool: accounts[6],
position: accounts[7],
token_a_mint: accounts[8],
token_b_mint: accounts[9],
token_a_vault: accounts[10],
token_b_vault: accounts[11],
payer_token_a: accounts[12],
payer_token_b: accounts[13],
token_a_program: accounts[14],
token_b_program: accounts[15],
event_authority: accounts[18],
program: accounts[19],
remaining_accounts: accounts[20..].to_vec(),
liquidity,
sqrt_price,
..Default::default()
}))
}
/// 解析 initialize_customizable_pool 指令
fn parse_initialize_customizable_pool_instruction(
data: &[u8],
accounts: &[Pubkey],
mut metadata: EventMetadata,
) -> Option<DexEvent> {
metadata.event_type = EventType::MeteoraDammV2InitializeCustomizablePool;
if accounts.len() < 19 {
return None;
}
// 解析 instruction data (不包含 discriminator)
// 结构: PoolFeeParameters + sqrt_min_price + sqrt_max_price + has_alpha_vault + liquidity + sqrt_price + activation_type + collect_fee_mode + activation_point
if data.len() < 99 {
return None;
}
let mut offset = 0;
// 解析 PoolFeeParameters
use crate::streaming::event_parser::protocols::meteora_damm_v2::PoolFeeParameters;
use borsh::BorshDeserialize;
// PoolFeeParameters size: 8 + 2 + 8 + 8 + 1 + 3 + 1 + (optional DynamicFee)
// 先读取前 31 bytes (不包含 dynamic_fee option tag)
let pool_fees = PoolFeeParameters::deserialize(&mut &data[offset..]).ok()?;
// 计算 pool_fees 消耗的字节数
// BaseFee: 8 + 2 + 8 + 8 + 1 = 27 bytes
// padding: 3 bytes
// option tag: 1 byte
// 如果 dynamic_fee 存在: 2 + 16 + 2 + 2 + 2 + 4 + 4 = 32 bytes
let pool_fees_size = 31 + if pool_fees.dynamic_fee.is_some() { 32 } else { 0 };
offset += pool_fees_size;
// 读取 sqrt_min_price (u128)
let sqrt_min_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 sqrt_max_price (u128)
let sqrt_max_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 has_alpha_vault (bool)
let _has_alpha_vault = data[offset];
offset += 1;
// 读取 liquidity (u128)
let liquidity = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 sqrt_price (u128)
let sqrt_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 activation_type (u8)
let activation_type = data[offset];
offset += 1;
// 读取 collect_fee_mode (u8)
let collect_fee_mode = data[offset];
offset += 1;
// 读取 activation_point (Option<u64>)
let option_tag = data[offset];
let _activation_point = if option_tag == 1 && data.len() >= offset + 9 {
Some(u64::from_le_bytes(data[offset + 1..offset + 9].try_into().ok()?))
} else {
None
};
Some(DexEvent::MeteoraDammV2InitializeCustomizablePoolEvent(
MeteoraDammV2InitializeCustomizablePoolEvent {
metadata,
creator: accounts[0],
position_nft_mint: accounts[1],
position_nft_account: accounts[2],
payer: accounts[3],
pool_authority: accounts[4],
pool: accounts[5],
position: accounts[6],
token_a_mint: accounts[7],
token_b_mint: accounts[8],
token_a_vault: accounts[9],
token_b_vault: accounts[10],
payer_token_a: accounts[11],
payer_token_b: accounts[12],
token_a_program: accounts[13],
token_b_program: accounts[14],
token_2022_program: accounts[15],
system_program: accounts[16],
event_authority: accounts[17],
program: accounts[18],
remaining_accounts: accounts[19..].to_vec(),
pool_fees,
sqrt_min_price,
sqrt_max_price,
activation_type,
collect_fee_mode,
liquidity,
sqrt_price,
..Default::default()
},
))
}
/// 解析 initialize_pool_with_dynamic_config 指令
fn parse_initialize_pool_with_dynamic_config_instruction(
data: &[u8],
accounts: &[Pubkey],
mut metadata: EventMetadata,
) -> Option<DexEvent> {
metadata.event_type = EventType::MeteoraDammV2InitializePoolWithDynamicConfig;
if accounts.len() < 21 {
return None;
}
if data.len() < 99 {
return None;
}
let mut offset = 0;
// 解析 PoolFeeParameters
use crate::streaming::event_parser::protocols::meteora_damm_v2::PoolFeeParameters;
use borsh::BorshDeserialize;
let pool_fees = PoolFeeParameters::deserialize(&mut &data[offset..]).ok()?;
// 计算 pool_fees 消耗的字节数
// BaseFee: 8 + 2 + 8 + 8 + 1 = 27 bytes
// padding: 3 bytes
// option tag: 1 byte
// 如果 dynamic_fee 存在: 2 + 16 + 2 + 2 + 2 + 4 + 4 = 32 bytes
let pool_fees_size = 31 + if pool_fees.dynamic_fee.is_some() { 32 } else { 0 };
offset += pool_fees_size;
// 读取 sqrt_min_price (u128)
let sqrt_min_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 sqrt_max_price (u128)
let sqrt_max_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 has_alpha_vault (bool)
let _has_alpha_vault = data[offset];
offset += 1;
// 读取 liquidity (u128)
let liquidity = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 sqrt_price (u128)
let sqrt_price = u128::from_le_bytes(data[offset..offset + 16].try_into().ok()?);
offset += 16;
// 读取 activation_type (u8)
let activation_type = data[offset];
offset += 1;
// 读取 collect_fee_mode (u8)
let collect_fee_mode = data[offset];
offset += 1;
// 读取 activation_point (Option<u64>)
let option_tag = data[offset];
let _activation_point = if option_tag == 1 && data.len() >= offset + 9 {
Some(u64::from_le_bytes(data[offset + 1..offset + 9].try_into().ok()?))
} else {
None
};
Some(DexEvent::MeteoraDammV2InitializePoolWithDynamicConfigEvent(
MeteoraDammV2InitializePoolWithDynamicConfigEvent {
metadata,
creator: accounts[0],
position_nft_mint: accounts[1],
position_nft_account: accounts[2],
payer: accounts[3],
pool_creator_authority: accounts[4],
pool_authority: accounts[6],
pool: accounts[7],
position: accounts[8],
token_a_mint: accounts[9],
token_b_mint: accounts[10],
token_a_vault: accounts[11],
token_b_vault: accounts[12],
payer_token_a: accounts[13],
payer_token_b: accounts[14],
token_a_program: accounts[15],
token_b_program: accounts[16],
token_2022_program: accounts[17],
system_program: accounts[18],
event_authority: accounts[19],
program: accounts[20],
config: accounts[5],
pool_fees,
sqrt_min_price,
sqrt_max_price,
activation_type,
collect_fee_mode,
liquidity,
sqrt_price,
..Default::default()
},
))
}
/// 解析 swap inner instruction (CPI event)
fn parse_swap_inner_instruction(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
if let Some(event) = meteora_damm_v2_swap_event_decode(data) {
Some(DexEvent::MeteoraDammV2SwapEvent(MeteoraDammV2SwapEvent { metadata, ..event }))
} else {
None
}
}
/// 解析 initialize pool inner instruction (CPI event)
fn parse_initialize_pool_inner_instruction(
data: &[u8],
mut metadata: EventMetadata,
) -> Option<DexEvent> {
metadata.event_type = EventType::MeteoraDammV2InitializePool;
if let Some(event) = meteora_damm_v2_initialize_pool_event_decode(data) {
Some(DexEvent::MeteoraDammV2InitializePoolEvent(MeteoraDammV2InitializePoolEvent {
metadata,
..event
}))
} else {
None
}
}
@@ -0,0 +1,2 @@
// 此文件用于定义 Meteora DAMM v2 的账户数据结构
// 暂时留空,后续如需要解析 Pool 账户状态时可以在这里添加
@@ -1,5 +1,6 @@
pub mod block;
pub mod bonk;
pub mod meteora_damm_v2;
pub mod pumpfun;
pub mod pumpswap;
pub mod raydium_amm_v4;
@@ -1,7 +1,8 @@
use crate::streaming::event_parser::protocols::{
bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID,
pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID,
raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID,
bonk::parser::BONK_PROGRAM_ID, meteora_damm_v2::parser::METEORA_DAMM_V2_PROGRAM_ID,
pumpfun::parser::PUMPFUN_PROGRAM_ID, pumpswap::parser::PUMPSWAP_PROGRAM_ID,
raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID,
raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID,
};
use anyhow::{anyhow, Result};
use solana_sdk::pubkey::Pubkey;
@@ -15,6 +16,7 @@ pub enum Protocol {
RaydiumCpmm,
RaydiumClmm,
RaydiumAmmV4,
MeteoraDammV2,
}
impl Protocol {
@@ -26,6 +28,7 @@ impl Protocol {
Protocol::RaydiumCpmm => vec![RAYDIUM_CPMM_PROGRAM_ID],
Protocol::RaydiumClmm => vec![RAYDIUM_CLMM_PROGRAM_ID],
Protocol::RaydiumAmmV4 => vec![RAYDIUM_AMM_V4_PROGRAM_ID],
Protocol::MeteoraDammV2 => vec![METEORA_DAMM_V2_PROGRAM_ID],
}
}
}
@@ -39,6 +42,7 @@ impl std::fmt::Display for Protocol {
Protocol::RaydiumCpmm => write!(f, "RaydiumCpmm"),
Protocol::RaydiumClmm => write!(f, "RaydiumClmm"),
Protocol::RaydiumAmmV4 => write!(f, "RaydiumAmmV4"),
Protocol::MeteoraDammV2 => write!(f, "MeteoraDammV2"),
}
}
}
@@ -54,6 +58,7 @@ impl std::str::FromStr for Protocol {
"raydiumcpmm" => Ok(Protocol::RaydiumCpmm),
"raydiumclmm" => Ok(Protocol::RaydiumClmm),
"raydiumammv4" => Ok(Protocol::RaydiumAmmV4),
"meteoradamm_v2" => Ok(Protocol::MeteoraDammV2),
_ => Err(anyhow!("Unsupported protocol: {}", s)),
}
}