mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-17 02:48:05 +00:00
feat: add Meteora DAMM v2 protocol support
This commit is contained in:
@@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user