feat: add Meteora DAMM V2 support and bump to v3.0.1

Add Meteora DAMM V2 trading protocol with instruction builder, type definitions, and fixed_output_token_amount parameter for precise output control. Update all examples and documentation.
This commit is contained in:
ysq
2025-10-05 22:27:04 +08:00
parent 30a7d570fa
commit 2f5a63ed55
33 changed files with 719 additions and 59 deletions
+22 -16
View File
@@ -69,14 +69,17 @@ impl InstructionBuilder for BonkInstructionBuilder {
// ========================================
let amount_in: u64 = params.input_amount.unwrap_or(0);
let share_fee_rate: u64 = 0;
let minimum_amount_out: u64 = get_buy_token_amount_from_sol_amount(
amount_in,
protocol_params.virtual_base,
protocol_params.virtual_quote,
protocol_params.real_base,
protocol_params.real_quote,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
);
let minimum_amount_out: u64 = match params.fixed_output_amount {
Some(fixed_amount) => fixed_amount,
None => get_buy_token_amount_from_sol_amount(
amount_in,
protocol_params.virtual_base,
protocol_params.virtual_quote,
protocol_params.real_base,
protocol_params.real_quote,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
),
};
let user_base_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
@@ -226,14 +229,17 @@ impl InstructionBuilder for BonkInstructionBuilder {
// Trade calculation and account address preparation
// ========================================
let share_fee_rate: u64 = 0;
let minimum_amount_out: u64 = get_sell_sol_amount_from_token_amount(
amount,
protocol_params.virtual_base,
protocol_params.virtual_quote,
protocol_params.real_base,
protocol_params.real_quote,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
);
let minimum_amount_out: u64 = match params.fixed_output_amount {
Some(fixed_amount) => fixed_amount,
None => get_sell_sol_amount_from_token_amount(
amount,
protocol_params.virtual_base,
protocol_params.virtual_quote,
protocol_params.real_base,
protocol_params.real_quote,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
),
};
let user_base_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
+214
View File
@@ -0,0 +1,214 @@
use crate::{
instruction::utils::meteora_damm_v2::{accounts, get_event_authority_pda, SWAP_DISCRIMINATOR},
trading::core::{
params::{MeteoraDammV2Params, SwapParams},
traits::InstructionBuilder,
},
};
use anyhow::{anyhow, Result};
use solana_sdk::{
instruction::{AccountMeta, Instruction},
signer::Signer,
};
/// Instruction builder for RaydiumCpmm protocol
pub struct MeteoraDammV2InstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for MeteoraDammV2InstructionBuilder {
async fn build_buy_instructions(&self, params: &SwapParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
if params.input_amount.unwrap_or(0) == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<MeteoraDammV2Params>()
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?;
// ========================================
// Trade calculation and account address preparation
// ========================================
let is_a_in = protocol_params.token_a_mint == crate::constants::WSOL_TOKEN_ACCOUNT;
let amount_in: u64 = params.input_amount.unwrap_or(0);
let minimum_amount_out: u64 = match params.fixed_output_amount {
Some(fixed) => fixed,
None => return Err(anyhow!("fixed_output_amount must be set for MeteoraDammV2 swap")),
};
let input_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
&params.input_mint,
if is_a_in {
&protocol_params.token_a_program
} else {
&protocol_params.token_b_program
},
params.open_seed_optimize,
);
let output_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
&params.output_mint,
if is_a_in {
&protocol_params.token_b_program
} else {
&protocol_params.token_a_program
},
params.open_seed_optimize,
);
// ========================================
// Build instructions
// ========================================
let mut instructions = Vec::with_capacity(6);
if params.create_input_mint_ata {
instructions
.extend(crate::trading::common::handle_wsol(&params.payer.pubkey(), amount_in));
}
if params.create_output_mint_ata {
instructions.extend(
crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.output_mint,
&crate::constants::TOKEN_PROGRAM,
params.open_seed_optimize,
),
);
}
// Create buy instruction
let accounts: [AccountMeta; 14] = [
accounts::AUTHORITY_META, // Pool Authority (readonly)
AccountMeta::new(protocol_params.pool, false), // Pool
AccountMeta::new(input_token_account, false), // Input Token Account
AccountMeta::new(output_token_account, false), // Output Token Account
AccountMeta::new(protocol_params.token_a_vault, false), // Token A Vault
AccountMeta::new(protocol_params.token_b_vault, false), // Token B Vault
AccountMeta::new_readonly(protocol_params.token_a_mint, false), // Token A Mint (readonly)
AccountMeta::new_readonly(protocol_params.token_b_mint, false), // Token B Mint (readonly)
AccountMeta::new(params.payer.pubkey(), true), // User Transfer Authority
AccountMeta::new_readonly(protocol_params.token_a_program, false), // Token Program (readonly)
AccountMeta::new_readonly(protocol_params.token_b_program, false), // Token Program (readonly)
accounts::METEORA_DAMM_V2_META, // Referral Token Account (readonly)
AccountMeta::new_readonly(get_event_authority_pda(), false), // Event Authority (readonly)
accounts::METEORA_DAMM_V2_META, // Program (readonly)
];
// Create instruction data
let mut data = [0u8; 24];
data[..8].copy_from_slice(&SWAP_DISCRIMINATOR);
data[8..16].copy_from_slice(&amount_in.to_le_bytes());
data[16..24].copy_from_slice(&minimum_amount_out.to_le_bytes());
instructions.push(Instruction::new_with_bytes(
accounts::METEORA_DAMM_V2,
&data,
accounts.to_vec(),
));
if params.close_input_mint_ata {
// Close wSOL ATA account, reclaim rent
instructions.extend(crate::trading::common::close_wsol(&params.payer.pubkey()));
}
Ok(instructions)
}
async fn build_sell_instructions(&self, params: &SwapParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<MeteoraDammV2Params>()
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?;
if params.input_amount.is_none() || params.input_amount.unwrap_or(0) == 0 {
return Err(anyhow!("Token amount is not set"));
}
// ========================================
// Trade calculation and account address preparation
// ========================================
let is_a_in = protocol_params.token_b_mint == crate::constants::WSOL_TOKEN_ACCOUNT;
let minimum_amount_out: u64 = match params.fixed_output_amount {
Some(fixed) => fixed,
None => return Err(anyhow!("fixed_output_amount must be set for MeteoraDammV2 swap")),
};
let input_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
&params.input_mint,
if is_a_in {
&protocol_params.token_a_program
} else {
&protocol_params.token_b_program
},
params.open_seed_optimize,
);
let output_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
&params.output_mint,
if is_a_in {
&protocol_params.token_b_program
} else {
&protocol_params.token_a_program
},
params.open_seed_optimize,
);
// ========================================
// Build instructions
// ========================================
let mut instructions = Vec::with_capacity(3);
if params.create_output_mint_ata {
instructions.extend(crate::trading::common::create_wsol_ata(&params.payer.pubkey()));
}
// Create buy instruction
let accounts: [AccountMeta; 14] = [
accounts::AUTHORITY_META, // Pool Authority (readonly)
AccountMeta::new(protocol_params.pool, false), // Pool
AccountMeta::new(input_token_account, false), // Input Token Account
AccountMeta::new(output_token_account, false), // Output Token Account
AccountMeta::new(protocol_params.token_a_vault, false), // Token A Vault
AccountMeta::new(protocol_params.token_b_vault, false), // Token B Vault
AccountMeta::new_readonly(protocol_params.token_a_mint, false), // Token A Mint (readonly)
AccountMeta::new_readonly(protocol_params.token_b_mint, false), // Token B Mint (readonly)
AccountMeta::new(params.payer.pubkey(), true), // User Transfer Authority
AccountMeta::new_readonly(protocol_params.token_a_program, false), // Token Program (readonly)
AccountMeta::new_readonly(protocol_params.token_b_program, false), // Token Program (readonly)
accounts::METEORA_DAMM_V2_META, // Referral Token Account (readonly)
AccountMeta::new_readonly(get_event_authority_pda(), false), // Event Authority (readonly)
accounts::METEORA_DAMM_V2_META, // Program (readonly)
];
// Create instruction data
let mut data = [0u8; 24];
data[..8].copy_from_slice(&SWAP_DISCRIMINATOR);
data[8..16].copy_from_slice(&params.input_amount.unwrap_or_default().to_le_bytes());
data[16..24].copy_from_slice(&minimum_amount_out.to_le_bytes());
instructions.push(Instruction::new_with_bytes(
accounts::METEORA_DAMM_V2,
&data,
accounts.to_vec(),
));
if params.close_output_mint_ata {
instructions.extend(crate::trading::common::close_wsol(&params.payer.pubkey()));
}
Ok(instructions)
}
}
+1
View File
@@ -3,4 +3,5 @@ pub mod pumpswap;
pub mod bonk;
pub mod raydium_cpmm;
pub mod raydium_amm_v4;
pub mod meteora_damm_v2;
pub mod utils;
+21 -13
View File
@@ -1,8 +1,10 @@
use crate::{
common::spl_token::close_account, constants::trade::trade::DEFAULT_SLIPPAGE, trading::core::{
common::spl_token::close_account,
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::core::{
params::{PumpFunParams, SwapParams},
traits::InstructionBuilder,
}
},
};
use crate::{
instruction::utils::pumpfun::{
@@ -44,13 +46,16 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
// ========================================
// Trade calculation and account address preparation
// ========================================
let buy_token_amount = get_buy_token_amount_from_sol_amount(
bonding_curve.virtual_token_reserves as u128,
bonding_curve.virtual_sol_reserves as u128,
bonding_curve.real_token_reserves as u128,
creator,
params.input_amount.unwrap_or(0),
);
let buy_token_amount = match params.fixed_output_amount {
Some(amount) => amount,
None => get_buy_token_amount_from_sol_amount(
bonding_curve.virtual_token_reserves as u128,
bonding_curve.virtual_sol_reserves as u128,
bonding_curve.real_token_reserves as u128,
creator,
params.input_amount.unwrap_or(0),
),
};
let max_sol_cost = calculate_with_slippage_buy(
params.input_amount.unwrap_or(0),
@@ -169,10 +174,13 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
token_amount,
);
let min_sol_output = calculate_with_slippage_sell(
sol_amount,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let min_sol_output = match params.fixed_output_amount {
Some(fixed) => fixed,
None => calculate_with_slippage_sell(
sol_amount,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
),
};
let bonding_curve_addr = if bonding_curve.account == Pubkey::default() {
get_bonding_curve_pda(&params.input_mint).unwrap()
+10 -2
View File
@@ -68,7 +68,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
creator = params_coin_creator_vault_authority;
}
let (token_amount, sol_amount) = if quote_mint_is_wsol {
let (mut token_amount, sol_amount) = if quote_mint_is_wsol {
let result = buy_quote_input_internal(
params.input_amount.unwrap_or(0),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
@@ -92,6 +92,10 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
(result.min_quote, params.input_amount.unwrap_or(0))
};
if params.fixed_output_amount.is_some() {
token_amount = params.fixed_output_amount.unwrap();
}
let user_base_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
@@ -233,7 +237,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
creator = params_coin_creator_vault_authority;
}
let (token_amount, sol_amount) = if quote_mint_is_wsol {
let (token_amount, mut sol_amount) = if quote_mint_is_wsol {
let result = sell_base_input_internal(
params.input_amount.unwrap(),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
@@ -257,6 +261,10 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
(result.max_quote, result.base)
};
if params.fixed_output_amount.is_some() {
sol_amount = params.fixed_output_amount.unwrap();
}
let fee_recipient_ata = fee_recipient_ata(accounts::FEE_RECIPIENT, quote_mint);
let user_base_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
+8 -2
View File
@@ -43,7 +43,10 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder {
amount_in,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let minimum_amount_out = swap_result.min_amount_out;
let minimum_amount_out = match params.fixed_output_amount {
Some(fixed) => fixed,
None => swap_result.min_amount_out,
};
let user_source_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
@@ -147,7 +150,10 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder {
params.input_amount.unwrap_or(0),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let minimum_amount_out = swap_result.min_amount_out;
let minimum_amount_out = match params.fixed_output_amount {
Some(fixed) => fixed,
None => swap_result.min_amount_out,
};
let user_source_token_account =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
+17 -9
View File
@@ -65,7 +65,10 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder {
amount_in,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let minimum_amount_out = result.min_amount_out;
let minimum_amount_out = match params.fixed_output_amount {
Some(fixed) => fixed,
None => result.min_amount_out,
};
let wsol_token_account = get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
@@ -188,14 +191,19 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder {
protocol_params.quote_token_program
};
let minimum_amount_out: u64 = compute_swap_amount(
protocol_params.base_reserve,
protocol_params.quote_reserve,
is_base_in,
params.input_amount.unwrap_or(0),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
)
.min_amount_out;
let minimum_amount_out: u64 = match params.fixed_output_amount {
Some(fixed) => fixed,
None => {
compute_swap_amount(
protocol_params.base_reserve,
protocol_params.quote_reserve,
is_base_in,
params.input_amount.unwrap_or(0),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
)
.min_amount_out
}
};
let wsol_token_account = get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
+54
View File
@@ -0,0 +1,54 @@
use crate::{
common::SolanaRpcClient,
instruction::utils::meteora_damm_v2_types::{pool_decode, Pool},
};
use anyhow::anyhow;
use solana_sdk::pubkey::Pubkey;
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
pub mod seeds {
pub const EVENT_AUTHORITY_SEED: &[u8] = b"__event_authority";
}
/// Constants related to program accounts and authorities
pub mod accounts {
use solana_sdk::{pubkey, pubkey::Pubkey};
pub const AUTHORITY: Pubkey = pubkey!("HLnpSz9h2S4hiLQ43rnSD9XkcUThA7B8hQMKmDaiTLcC");
pub const METEORA_DAMM_V2: Pubkey = pubkey!("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG");
// META
pub const METEORA_DAMM_V2_META: solana_sdk::instruction::AccountMeta =
solana_sdk::instruction::AccountMeta {
pubkey: METEORA_DAMM_V2,
is_signer: false,
is_writable: false,
};
pub const AUTHORITY_META: solana_sdk::instruction::AccountMeta =
solana_sdk::instruction::AccountMeta {
pubkey: AUTHORITY,
is_signer: false,
is_writable: false,
};
}
pub const SWAP_DISCRIMINATOR: &[u8] = &[248, 198, 158, 145, 225, 117, 135, 200];
pub async fn fetch_pool(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Pool, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::METEORA_DAMM_V2 {
return Err(anyhow!("Account is not owned by Meteora Damm V2 program"));
}
let pool = pool_decode(&account.data[8..]).ok_or_else(|| anyhow!("Failed to decode pool"))?;
Ok(pool)
}
#[inline]
pub fn get_event_authority_pda() -> Pubkey {
Pubkey::find_program_address(&[seeds::EVENT_AUTHORITY_SEED], &accounts::METEORA_DAMM_V2).0
}
@@ -0,0 +1,114 @@
use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct BaseFeeStruct {
pub cliff_fee_numerator: u64,
pub fee_scheduler_mode: u8,
pub padding_0: [u8; 5],
pub number_of_period: u16,
pub period_frequency: u64,
pub reduction_factor: u64,
pub padding_1: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct DynamicFeeStruct {
pub initialized: u8,
pub padding: [u8; 7],
pub max_volatility_accumulator: u32,
pub variable_fee_control: u32,
pub bin_step: u16,
pub filter_period: u16,
pub decay_period: u16,
pub reduction_factor: u16,
pub last_update_timestamp: u64,
pub bin_step_u128: u128,
pub sqrt_price_reference: u128,
pub volatility_accumulator: u128,
pub volatility_reference: u128,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct PoolFeesStruct {
pub base_fee: BaseFeeStruct,
pub protocol_fee_percent: u8,
pub partner_fee_percent: u8,
pub referral_fee_percent: u8,
pub padding_0: [u8; 5],
pub dynamic_fee: DynamicFeeStruct,
pub padding_1: [u64; 2],
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct PoolMetrics {
pub total_lp_a_fee: u128,
pub total_lp_b_fee: u128,
pub total_protocol_a_fee: u64,
pub total_protocol_b_fee: u64,
pub total_partner_a_fee: u64,
pub total_partner_b_fee: u64,
pub total_position: u64,
pub padding: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct RewardInfo {
pub initialized: u8,
pub reward_token_flag: u8,
pub padding_0: [u8; 6],
pub padding_1: [u8; 8],
pub mint: Pubkey,
pub vault: Pubkey,
pub funder: Pubkey,
pub reward_duration: u64,
pub reward_duration_end: u64,
pub reward_rate: u128,
pub reward_per_token_stored: [u8; 32],
pub last_update_time: u64,
pub cumulative_seconds_with_empty_liquidity_reward: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct Pool {
pub pool_fees: PoolFeesStruct,
pub token_a_mint: Pubkey,
pub token_b_mint: Pubkey,
pub token_a_vault: Pubkey,
pub token_b_vault: Pubkey,
pub whitelisted_vault: Pubkey,
pub partner: Pubkey,
pub liquidity: u128,
pub padding: u128,
pub protocol_a_fee: u64,
pub protocol_b_fee: u64,
pub partner_a_fee: u64,
pub partner_b_fee: u64,
pub sqrt_min_price: u128,
pub sqrt_max_price: u128,
pub sqrt_price: u128,
pub activation_point: u64,
pub activation_type: u8,
pub pool_status: u8,
pub token_a_flag: u8,
pub token_b_flag: u8,
pub collect_fee_mode: u8,
pub pool_type: u8,
pub padding_0: [u8; 2],
pub fee_a_per_liquidity: [u8; 32],
pub fee_b_per_liquidity: [u8; 32],
pub permanent_lock_liquidity: u128,
pub metrics: PoolMetrics,
pub padding_1: [u64; 10],
pub reward_infos: [RewardInfo; 2],
}
pub const POOL_SIZE: usize = 1104;
pub fn pool_decode(data: &[u8]) -> Option<Pool> {
if data.len() < POOL_SIZE {
return None;
}
borsh::from_slice::<Pool>(&data[..POOL_SIZE]).ok()
}
+3 -1
View File
@@ -3,9 +3,11 @@ pub mod pumpfun;
pub mod pumpswap;
pub mod raydium_amm_v4;
pub mod raydium_cpmm;
pub mod meteora_damm_v2;
// types
pub mod bonk_types;
pub mod pumpswap_types;
pub mod raydium_amm_v4_types;
pub mod raydium_cpmm_types;
pub mod raydium_cpmm_types;
pub mod meteora_damm_v2_types;
+13
View File
@@ -14,6 +14,7 @@ use crate::swqos::SwqosClient;
use crate::swqos::SwqosConfig;
use crate::swqos::TradeType;
use crate::trading::core::params::BonkParams;
use crate::trading::core::params::MeteoraDammV2Params;
use crate::trading::core::params::PumpFunParams;
use crate::trading::core::params::PumpSwapParams;
use crate::trading::core::params::RaydiumAmmV4Params;
@@ -104,6 +105,8 @@ pub struct TradeBuyParams {
pub open_seed_optimize: bool,
/// Durable nonce information
pub durable_nonce: Option<DurableNonceInfo>,
/// Optional fixed output token amount (If this value is set, it will be directly assigned to the output amount instead of being calculated)
pub fixed_output_token_amount: Option<u64>,
}
/// Parameters for executing sell orders across different DEX protocols
@@ -142,6 +145,8 @@ pub struct TradeSellParams {
pub open_seed_optimize: bool,
/// Durable nonce information
pub durable_nonce: Option<DurableNonceInfo>,
/// Optional fixed output token amount (If this value is set, it will be directly assigned to the output amount instead of being calculated)
pub fixed_output_token_amount: Option<u64>,
}
impl SolanaTrade {
@@ -300,6 +305,7 @@ impl SolanaTrade {
close_input_mint_ata: params.close_input_token_ata,
create_output_mint_ata: params.create_mint_ata,
close_output_mint_ata: false,
fixed_output_amount: params.fixed_output_token_amount,
};
// Validate protocol params
@@ -315,6 +321,9 @@ impl SolanaTrade {
DexType::RaydiumAmmV4 => {
protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
}
DexType::MeteoraDammV2 => {
protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
}
};
if !is_valid_params {
@@ -389,6 +398,7 @@ impl SolanaTrade {
close_input_mint_ata: false,
create_output_mint_ata: params.create_output_token_ata,
close_output_mint_ata: params.close_output_token_ata,
fixed_output_amount: params.fixed_output_token_amount,
};
// Validate protocol params
@@ -404,6 +414,9 @@ impl SolanaTrade {
DexType::RaydiumAmmV4 => {
protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
}
DexType::MeteoraDammV2 => {
protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
}
};
if !is_valid_params {
+64
View File
@@ -3,6 +3,7 @@ use crate::common::bonding_curve::BondingCurveAccount;
use crate::common::nonce_cache::DurableNonceInfo;
use crate::common::spl_associated_token_account::get_associated_token_address_with_program_id;
use crate::common::SolanaRpcClient;
use crate::constants::TOKEN_PROGRAM;
use crate::swqos::{SwqosClient, TradeType};
use crate::trading::common::get_multi_token_balances;
use crate::trading::MiddlewareManager;
@@ -36,6 +37,7 @@ pub struct SwapParams {
pub close_input_mint_ata: bool,
pub create_output_mint_ata: bool,
pub close_output_mint_ata: bool,
pub fixed_output_amount: Option<u64>,
}
impl std::fmt::Debug for SwapParams {
@@ -647,3 +649,65 @@ impl ProtocolParams for RaydiumAmmV4Params {
Box::new(self.clone())
}
}
/// MeteoraDammV2 protocol specific parameters
/// Configuration parameters specific to Meteora Damm V2 trading protocol
#[derive(Clone)]
pub struct MeteoraDammV2Params {
pub pool: Pubkey,
pub token_a_vault: Pubkey,
pub token_b_vault: Pubkey,
pub token_a_mint: Pubkey,
pub token_b_mint: Pubkey,
pub token_a_program: Pubkey,
pub token_b_program: Pubkey,
}
impl MeteoraDammV2Params {
pub fn new(
pool: Pubkey,
token_a_vault: Pubkey,
token_b_vault: Pubkey,
token_a_mint: Pubkey,
token_b_mint: Pubkey,
token_a_program: Pubkey,
token_b_program: Pubkey,
) -> Self {
Self {
pool,
token_a_vault,
token_b_vault,
token_a_mint,
token_b_mint,
token_a_program,
token_b_program,
}
}
pub async fn from_pool_address_by_rpc(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let pool_data =
crate::instruction::utils::meteora_damm_v2::fetch_pool(rpc, pool_address).await?;
Ok(Self {
pool: pool_address.clone(),
token_a_vault: pool_data.token_a_vault,
token_b_vault: pool_data.token_b_vault,
token_a_mint: pool_data.token_a_mint,
token_b_mint: pool_data.token_b_mint,
token_a_program: TOKEN_PROGRAM,
token_b_program: TOKEN_PROGRAM,
})
}
}
impl ProtocolParams for MeteoraDammV2Params {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn clone_box(&self) -> Box<dyn ProtocolParams> {
Box::new(self.clone())
}
}
+15 -3
View File
@@ -1,9 +1,9 @@
use std::sync::Arc;
use crate::instruction::{
bonk::BonkInstructionBuilder, pumpfun::PumpFunInstructionBuilder,
pumpswap::PumpSwapInstructionBuilder, raydium_amm_v4::RaydiumAmmV4InstructionBuilder,
raydium_cpmm::RaydiumCpmmInstructionBuilder,
bonk::BonkInstructionBuilder, meteora_damm_v2::MeteoraDammV2InstructionBuilder,
pumpfun::PumpFunInstructionBuilder, pumpswap::PumpSwapInstructionBuilder,
raydium_amm_v4::RaydiumAmmV4InstructionBuilder, raydium_cpmm::RaydiumCpmmInstructionBuilder,
};
use super::core::{executor::GenericTradeExecutor, traits::TradeExecutor};
@@ -16,6 +16,7 @@ pub enum DexType {
Bonk,
RaydiumCpmm,
RaydiumAmmV4,
MeteoraDammV2,
}
/// 交易工厂 - 用于创建不同协议的交易执行器
@@ -30,6 +31,7 @@ impl TradeFactory {
DexType::Bonk => Self::bonk_executor(),
DexType::RaydiumCpmm => Self::raydium_cpmm_executor(),
DexType::RaydiumAmmV4 => Self::raydium_amm_v4_executor(),
DexType::MeteoraDammV2 => Self::meteora_damm_v2_executor(),
}
}
@@ -83,4 +85,14 @@ impl TradeFactory {
});
INSTANCE.clone()
}
#[inline]
fn meteora_damm_v2_executor() -> Arc<dyn TradeExecutor> {
static INSTANCE: std::sync::LazyLock<Arc<dyn TradeExecutor>> =
std::sync::LazyLock::new(|| {
let instruction_builder = Arc::new(MeteoraDammV2InstructionBuilder);
Arc::new(GenericTradeExecutor::new(instruction_builder, "MeteoraDammV2"))
});
INSTANCE.clone()
}
}