From 2f5a63ed55c3abad6ed054e3b2b1293ba158dc9c Mon Sep 17 00:00:00 2001 From: ysq Date: Sun, 5 Oct 2025 22:27:04 +0800 Subject: [PATCH] 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. --- Cargo.toml | 3 +- README.md | 9 +- README_CN.md | 9 +- docs/TRADING_PARAMETERS.md | 7 +- docs/TRADING_PARAMETERS_CN.md | 7 +- examples/address_lookup/src/main.rs | 1 + examples/bonk_copy_trading/src/main.rs | 2 + examples/bonk_sniper_trading/src/main.rs | 2 + examples/cli_trading/src/main.rs | 10 + .../meteora_damm_v2_direct_trading/Cargo.toml | 12 + .../src/main.rs | 98 ++++++++ examples/middleware_system/src/main.rs | 1 + examples/nonce_cache/src/main.rs | 1 + examples/pumpfun_copy_trading/src/main.rs | 2 + examples/pumpfun_sniper_trading/src/main.rs | 2 + examples/pumpswap_direct_trading/src/main.rs | 2 + examples/pumpswap_trading/src/main.rs | 2 + examples/raydium_amm_v4_trading/src/main.rs | 2 + examples/raydium_cpmm_trading/src/main.rs | 2 + examples/seed_trading/src/main.rs | 2 + src/instruction/bonk.rs | 38 ++-- src/instruction/meteora_damm_v2.rs | 214 ++++++++++++++++++ src/instruction/mod.rs | 1 + src/instruction/pumpfun.rs | 34 +-- src/instruction/pumpswap.rs | 12 +- src/instruction/raydium_amm_v4.rs | 10 +- src/instruction/raydium_cpmm.rs | 26 ++- src/instruction/utils/meteora_damm_v2.rs | 54 +++++ .../utils/meteora_damm_v2_types.rs | 114 ++++++++++ src/instruction/utils/mod.rs | 4 +- src/lib.rs | 13 ++ src/trading/core/params.rs | 64 ++++++ src/trading/factory.rs | 18 +- 33 files changed, 719 insertions(+), 59 deletions(-) create mode 100644 examples/meteora_damm_v2_direct_trading/Cargo.toml create mode 100644 examples/meteora_damm_v2_direct_trading/src/main.rs create mode 100644 src/instruction/meteora_damm_v2.rs create mode 100644 src/instruction/utils/meteora_damm_v2.rs create mode 100644 src/instruction/utils/meteora_damm_v2_types.rs diff --git a/Cargo.toml b/Cargo.toml index bd5fa11..4935c21 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sol-trade-sdk" -version = "3.0.0" +version = "3.0.1" edition = "2021" authors = [ "William ", @@ -32,6 +32,7 @@ members = [ "examples/seed_trading", "examples/cli_trading", "examples/gas_fee_strategy", + "examples/meteora_damm_v2_direct_trading", ] [lib] diff --git a/README.md b/README.md index a1dc4bd..ad56534 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

- Integrate PumpFun, PumpSwap, Bonk, and Raydium trading functionality into your applications with powerful tools and unified interfaces. + Integrate PumpFun, PumpSwap, Bonk, Raydium, and Meteora trading functionality into your applications with powerful tools and unified interfaces.

@@ -66,7 +66,7 @@ 3. **Bonk Trading**: Support for Bonk trading operations 4. **Raydium CPMM Trading**: Support for Raydium CPMM (Concentrated Pool Market Maker) trading operations 5. **Raydium AMM V4 Trading**: Support for Raydium AMM V4 (Automated Market Maker) trading operations -6. **Event Subscription**: SDK integrates solana-streamer SDK, supports subscribing to PumpFun, PumpSwap, Bonk, Raydium CPMM, and Raydium AMM V4 program trading events, the description of this SDK can be found in [solana-streamer SDK](https://github.com/0xfnzero/solana-streamer). +6. **Meteora DAMM V2 Trading**: Support for Meteora DAMM V2 (Dynamic AMM) trading operations 7. **Multiple MEV Protection**: Support for Jito, Nextblock, ZeroSlot, Temporal, Bloxroute, FlashBlock, BlockRazor, Node1, Astralane and other services 8. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail 9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations @@ -87,14 +87,14 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.0.0" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.0.1" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -sol-trade-sdk = "3.0.0" +sol-trade-sdk = "3.0.1" ``` ## 🛠️ Usage Examples @@ -185,6 +185,7 @@ Please ensure that the parameters your trading logic depends on are available in | PumpSwap trading operations | `cargo run --package pumpswap_trading` | [examples/pumpswap_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpswap_trading/src/main.rs) | | Raydium CPMM trading operations | `cargo run --package raydium_cpmm_trading` | [examples/raydium_cpmm_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_cpmm_trading/src/main.rs) | | Raydium AMM V4 trading operations | `cargo run --package raydium_amm_v4_trading` | [examples/raydium_amm_v4_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_amm_v4_trading/src/main.rs) | +| Meteora DAMM V2 trading operations | `cargo run --package meteora_damm_v2_direct_trading` | [examples/meteora_damm_v2_direct_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/meteora_damm_v2_direct_trading/src/main.rs) | | Bonk token sniping trading | `cargo run --package bonk_sniper_trading` | [examples/bonk_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_sniper_trading/src/main.rs) | | Bonk token copy trading | `cargo run --package bonk_copy_trading` | [examples/bonk_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_copy_trading/src/main.rs) | | Custom instruction middleware example | `cargo run --package middleware_system` | [examples/middleware_system](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/middleware_system/src/main.rs) | diff --git a/README_CN.md b/README_CN.md index c4eb77e..e509429 100755 --- a/README_CN.md +++ b/README_CN.md @@ -4,7 +4,7 @@

- 将 PumpFun、PumpSwap、Bonk 和 Raydium 交易功能集成到您的应用程序中,提供强大的工具和统一的接口。 + 将 PumpFun、PumpSwap、Bonk、Raydium 和 Meteora 交易功能集成到您的应用程序中,提供强大的工具和统一的接口。

@@ -66,7 +66,7 @@ 3. **Bonk 交易**: 支持 Bonk 的交易操作 4. **Raydium CPMM 交易**: 支持 Raydium CPMM (Concentrated Pool Market Maker) 的交易操作 5. **Raydium AMM V4 交易**: 支持 Raydium AMM V4 (Automated Market Maker) 的交易操作 -6. **事件订阅**: SDK 集成了 solana-streamer SDK,支持引用该 SDK 订阅 PumpFun、PumpSwap、Bonk、Raydium CPMM 和 Raydium AMM V4 程序的交易事件,该 SDK 的说明可以查阅:[solana-streamer SDK](https://github.com/0xfnzero/solana-streamer)。 +6. **Meteora DAMM V2 交易**: 支持 Meteora DAMM V2 (Dynamic AMM) 的交易操作 7. **多种 MEV 保护**: 支持 Jito、Nextblock、ZeroSlot、Temporal、Bloxroute、FlashBlock、BlockRazor、Node1、Astralane 等服务 8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败 9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作 @@ -87,14 +87,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.0.0" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.0.1" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = "3.0.0" +sol-trade-sdk = "3.0.1" ``` ## 🛠️ 使用示例 @@ -186,6 +186,7 @@ client.buy(buy_params).await?; | PumpSwap 交易操作 | `cargo run --package pumpswap_trading` | [examples/pumpswap_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpswap_trading/src/main.rs) | | Raydium CPMM 交易操作 | `cargo run --package raydium_cpmm_trading` | [examples/raydium_cpmm_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_cpmm_trading/src/main.rs) | | Raydium AMM V4 交易操作 | `cargo run --package raydium_amm_v4_trading` | [examples/raydium_amm_v4_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/raydium_amm_v4_trading/src/main.rs) | +| Meteora DAMM V2 交易操作 | `cargo run --package meteora_damm_v2_direct_trading` | [examples/meteora_damm_v2_direct_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/meteora_damm_v2_direct_trading/src/main.rs) | | Bonk 代币狙击交易 | `cargo run --package bonk_sniper_trading` | [examples/bonk_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_sniper_trading/src/main.rs) | | Bonk 代币跟单交易 | `cargo run --package bonk_copy_trading` | [examples/bonk_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_copy_trading/src/main.rs) | | 自定义指令中间件示例 | `cargo run --package middleware_system` | [examples/middleware_system](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/middleware_system/src/main.rs) | diff --git a/docs/TRADING_PARAMETERS.md b/docs/TRADING_PARAMETERS.md index f3892f2..15f3283 100644 --- a/docs/TRADING_PARAMETERS.md +++ b/docs/TRADING_PARAMETERS.md @@ -17,7 +17,7 @@ The `TradeBuyParams` struct contains all parameters required for executing buy o | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `dex_type` | `DexType` | ✅ | The trading protocol to use (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) | +| `dex_type` | `DexType` | ✅ | The trading protocol to use (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4, MeteoraDammV2) | | `input_token_type` | `TradeTokenType` | ✅ | The type of input token to use (SOL, WSOL, USD1) | | `mint` | `Pubkey` | ✅ | The public key of the token mint to purchase | | `input_token_amount` | `u64` | ✅ | Amount of input token to spend (in smallest token units) | @@ -36,6 +36,7 @@ The `TradeBuyParams` struct contains all parameters required for executing buy o | `create_mint_ata` | `bool` | ✅ | Whether to create token mint ATA | | `open_seed_optimize` | `bool` | ✅ | Whether to use seed optimization for reduced CU consumption | | `durable_nonce` | `Option` | ❌ | Durable nonce information containing nonce account and current nonce value | +| `fixed_output_token_amount` | `Option` | ❌ | Optional fixed output token amount. If set, this value will be directly assigned to the output amount instead of being calculated (required for Meteora DAMM V2) | ## TradeSellParams @@ -46,7 +47,7 @@ The `TradeSellParams` struct contains all parameters required for executing sell | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `dex_type` | `DexType` | ✅ | The trading protocol to use (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) | +| `dex_type` | `DexType` | ✅ | The trading protocol to use (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4, MeteoraDammV2) | | `output_token_type` | `TradeTokenType` | ✅ | The type of output token to receive (SOL, WSOL, USD1) | | `mint` | `Pubkey` | ✅ | The public key of the token mint to sell | | `input_token_amount` | `u64` | ✅ | Amount of tokens to sell (in smallest token units) | @@ -65,6 +66,7 @@ The `TradeSellParams` struct contains all parameters required for executing sell | `close_output_token_ata` | `bool` | ✅ | Whether to close output token ATA after transaction | | `open_seed_optimize` | `bool` | ✅ | Whether to use seed optimization for reduced CU consumption | | `durable_nonce` | `Option` | ❌ | Durable nonce information containing nonce account and current nonce value | +| `fixed_output_token_amount` | `Option` | ❌ | Optional fixed output token amount. If set, this value will be directly assigned to the output amount instead of being calculated (required for Meteora DAMM V2) | ## Parameter Categories @@ -152,5 +154,6 @@ Each DEX protocol requires specific `extension_params`: - **Bonk**: `BonkParams` - **Raydium CPMM**: `RaydiumCpmmParams` - **Raydium AMM V4**: `RaydiumAmmV4Params` +- **Meteora DAMM V2**: `MeteoraDammV2Params` Refer to the respective protocol documentation for detailed parameter specifications. diff --git a/docs/TRADING_PARAMETERS_CN.md b/docs/TRADING_PARAMETERS_CN.md index 42e2460..cfcf418 100644 --- a/docs/TRADING_PARAMETERS_CN.md +++ b/docs/TRADING_PARAMETERS_CN.md @@ -17,7 +17,7 @@ | 参数 | 类型 | 必需 | 描述 | |------|------|------|------| -| `dex_type` | `DexType` | ✅ | 要使用的交易协议 (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) | +| `dex_type` | `DexType` | ✅ | 要使用的交易协议 (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4, MeteoraDammV2) | | `input_token_type` | `TradeTokenType` | ✅ | 要使用的输入代币类型 (SOL, WSOL, USD1) | | `mint` | `Pubkey` | ✅ | 要购买的代币 mint 公钥 | | `input_token_amount` | `u64` | ✅ | 要花费的输入代币数量(最小代币单位) | @@ -36,6 +36,7 @@ | `create_mint_ata` | `bool` | ✅ | 是否创建代币 mint ATA | | `open_seed_optimize` | `bool` | ✅ | 是否使用 seed 优化以减少 CU 消耗 | | `durable_nonce` | `Option` | ❌ | 持久 nonce 信息,包含 nonce 账户和当前 nonce 值 | +| `fixed_output_token_amount` | `Option` | ❌ | 可选的固定输出代币数量。如果设置,此值将直接分配给输出数量而不是通过计算得出(Meteora DAMM V2 必需) | ## TradeSellParams @@ -46,7 +47,7 @@ | 参数 | 类型 | 必需 | 描述 | |------|------|------|------| -| `dex_type` | `DexType` | ✅ | 要使用的交易协议 (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4) | +| `dex_type` | `DexType` | ✅ | 要使用的交易协议 (PumpFun, PumpSwap, Bonk, RaydiumCpmm, RaydiumAmmV4, MeteoraDammV2) | | `output_token_type` | `TradeTokenType` | ✅ | 要接收的输出代币类型 (SOL, WSOL, USD1) | | `mint` | `Pubkey` | ✅ | 要出售的代币 mint 公钥 | | `input_token_amount` | `u64` | ✅ | 要出售的代币数量(最小代币单位) | @@ -65,6 +66,7 @@ | `close_output_token_ata` | `bool` | ✅ | 交易后是否关闭输出代币 ATA | | `open_seed_optimize` | `bool` | ✅ | 是否使用 seed 优化以减少 CU 消耗 | | `durable_nonce` | `Option` | ❌ | 持久 nonce 信息,包含 nonce 账户和当前 nonce 值 | +| `fixed_output_token_amount` | `Option` | ❌ | 可选的固定输出代币数量。如果设置,此值将直接分配给输出数量而不是通过计算得出(Meteora DAMM V2 必需) | ## 参数分类 @@ -152,5 +154,6 @@ - **Bonk**: `BonkParams` - **Raydium CPMM**: `RaydiumCpmmParams` - **Raydium AMM V4**: `RaydiumAmmV4Params` +- **Meteora DAMM V2**: `MeteoraDammV2Params` 请参阅相应的协议文档了解详细的参数规格。 diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index f150257..8b9ecbd 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -168,6 +168,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index 429f61e..ee84ee6 100644 --- a/examples/bonk_copy_trading/src/main.rs +++ b/examples/bonk_copy_trading/src/main.rs @@ -163,6 +163,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -205,6 +206,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> durable_nonce: None, create_output_token_ata: false, close_output_token_ata: false, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index 5e77cff..ce0a02d 100644 --- a/examples/bonk_sniper_trading/src/main.rs +++ b/examples/bonk_sniper_trading/src/main.rs @@ -133,6 +133,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -168,6 +169,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< open_seed_optimize: false, with_tip: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/examples/cli_trading/src/main.rs b/examples/cli_trading/src/main.rs index cd0e7f7..f6d394b 100644 --- a/examples/cli_trading/src/main.rs +++ b/examples/cli_trading/src/main.rs @@ -629,6 +629,7 @@ async fn handle_buy_pumpfun( create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -678,6 +679,7 @@ async fn handle_buy_pumpswap( create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -726,6 +728,7 @@ async fn handle_buy_bonk( create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -778,6 +781,7 @@ async fn handle_buy_raydium_v4( create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -830,6 +834,7 @@ async fn handle_buy_raydium_cpmm( create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.buy(buy_params).await { Ok((_, signature)) => { @@ -992,6 +997,7 @@ async fn handle_sell_pumpfun( close_output_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.sell(sell_params).await { @@ -1044,6 +1050,7 @@ async fn handle_sell_pumpswap( close_output_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.sell(sell_params).await { Ok((_, signature)) => { @@ -1095,6 +1102,7 @@ async fn handle_sell_bonk( close_output_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.sell(sell_params).await { Ok((_, signature)) => { @@ -1149,6 +1157,7 @@ async fn handle_sell_raydium_v4( close_output_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.sell(sell_params).await { Ok((_, signature)) => { @@ -1203,6 +1212,7 @@ async fn handle_sell_raydium_cpmm( close_output_token_ata: false, open_seed_optimize: use_seed, durable_nonce: None, + fixed_output_token_amount: None, }; match client.sell(sell_params).await { Ok((_, signature)) => { diff --git a/examples/meteora_damm_v2_direct_trading/Cargo.toml b/examples/meteora_damm_v2_direct_trading/Cargo.toml new file mode 100644 index 0000000..c0737c2 --- /dev/null +++ b/examples/meteora_damm_v2_direct_trading/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "meteora_damm_v2_direct_trading" +version = "0.1.0" +edition = "2021" + +[dependencies] +sol-trade-sdk = { path = "../.." } +solana-streamer-sdk = "0.5.0" +solana-sdk = "3.0.0" +solana-commitment-config = { version = "3.0.0", features = ["serde"] } +tokio = { version = "1", features = ["full"] } +spl-token-2022 = { version = "8.0.0", features = ["no-entrypoint"] } diff --git a/examples/meteora_damm_v2_direct_trading/src/main.rs b/examples/meteora_damm_v2_direct_trading/src/main.rs new file mode 100644 index 0000000..1c96f58 --- /dev/null +++ b/examples/meteora_damm_v2_direct_trading/src/main.rs @@ -0,0 +1,98 @@ +use sol_trade_sdk::{ + common::{ + spl_associated_token_account::get_associated_token_address_with_program_id, AnyResult, + TradeConfig, + }, + swqos::SwqosConfig, + trading::{core::params::MeteoraDammV2Params, factory::DexType}, + SolanaTrade, TradeTokenType, +}; +use solana_commitment_config::CommitmentConfig; +use solana_sdk::signature::Keypair; +use solana_sdk::{pubkey::Pubkey, signer::Signer}; +use std::{str::FromStr, sync::Arc}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Testing Metaora Damm V2 trading..."); + + let client = create_solana_trade_client().await?; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + let pool = Pubkey::from_str("35EFyWd9cH8pdHxVgXHF68L1oZxSWd1FfJASSNTUtoTC").unwrap(); + let mint_pubkey = Pubkey::from_str("FhTRoy63ZiLcjLEVCMCTLc5Cu5ozrzouNg6cDp1ASZMC").unwrap(); + + // Buy tokens + println!("Buying tokens from Metaora Damm V2..."); + let buy_sol_amount = 100_000; + let buy_params = sol_trade_sdk::TradeBuyParams { + dex_type: DexType::MeteoraDammV2, + input_token_type: TradeTokenType::WSOL, + mint: mint_pubkey, + input_token_amount: buy_sol_amount, + slippage_basis_points: slippage_basis_points, + recent_blockhash: Some(recent_blockhash), + extension_params: Box::new( + MeteoraDammV2Params::from_pool_address_by_rpc(&client.rpc, &pool).await?, + ), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_input_token_ata: true, + close_input_token_ata: true, + create_mint_ata: true, + open_seed_optimize: false, + durable_nonce: None, + fixed_output_token_amount: Some(1), + }; + client.buy(buy_params).await?; + + // Sell tokens + println!("Selling tokens from Metaora Damm V2..."); + + let rpc = client.rpc.clone(); + let payer = client.payer.pubkey(); + let program_id = sol_trade_sdk::constants::TOKEN_PROGRAM; + let account = get_associated_token_address_with_program_id(&payer, &mint_pubkey, &program_id); + let balance = rpc.get_token_account_balance(&account).await?; + let amount_token = balance.amount.parse::().unwrap(); + println!("Token balance: {}", amount_token); + let sell_params = sol_trade_sdk::TradeSellParams { + dex_type: DexType::MeteoraDammV2, + output_token_type: TradeTokenType::WSOL, + mint: mint_pubkey, + input_token_amount: amount_token, + slippage_basis_points: slippage_basis_points, + recent_blockhash: Some(recent_blockhash), + with_tip: false, + extension_params: Box::new( + MeteoraDammV2Params::from_pool_address_by_rpc(&client.rpc, &pool).await?, + ), + lookup_table_key: None, + wait_transaction_confirmed: true, + create_output_token_ata: true, + close_output_token_ata: true, + open_seed_optimize: false, + durable_nonce: None, + fixed_output_token_amount: Some(1), + }; + client.sell(sell_params).await?; + + tokio::signal::ctrl_c().await?; + Ok(()) +} + +/// Create SolanaTrade client +/// Initializes a new SolanaTrade client with configuration +async fn create_solana_trade_client() -> AnyResult { + println!("🚀 Initializing SolanaTrade client..."); + let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); + let rpc_url = "https://api.mainnet-beta.solana.com".to_string(); + let commitment = CommitmentConfig::confirmed(); + let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); + let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; + // set global strategy + sol_trade_sdk::common::GasFeeStrategy::set_global_fee_strategy(150000, 500000, 0.001, 0.001); + println!("✅ SolanaTrade client initialized successfully!"); + Ok(solana_trade) +} diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index 3e0efb5..c1108e4 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -99,6 +99,7 @@ async fn test_middleware() -> AnyResult<()> { create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; println!("tip: This transaction will not succeed because we're using a test account. You can modify the code to initialize the payer with your own private key"); diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index 8a91976..5d14c7d 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -155,6 +155,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul create_mint_ata: true, open_seed_optimize: false, durable_nonce: Some(durable_nonce), + fixed_output_token_amount: None, }; client.buy(buy_params).await?; diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index 6e95403..4921204 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -151,6 +151,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -191,6 +192,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul close_output_token_ata: false, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index 6d9a7b4..82801d9 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -117,6 +117,7 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -146,6 +147,7 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR close_output_token_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index cd15476..e6ee260 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -42,6 +42,7 @@ async fn main() -> Result<(), Box> { create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -71,6 +72,7 @@ async fn main() -> Result<(), Box> { close_output_token_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/examples/pumpswap_trading/src/main.rs b/examples/pumpswap_trading/src/main.rs index 894f20e..02e0c69 100644 --- a/examples/pumpswap_trading/src/main.rs +++ b/examples/pumpswap_trading/src/main.rs @@ -201,6 +201,7 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -232,6 +233,7 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - close_output_token_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/examples/raydium_amm_v4_trading/src/main.rs b/examples/raydium_amm_v4_trading/src/main.rs index 91df000..a319a04 100644 --- a/examples/raydium_amm_v4_trading/src/main.rs +++ b/examples/raydium_amm_v4_trading/src/main.rs @@ -157,6 +157,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -187,6 +188,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) close_output_token_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index bac0464..fca1eb0 100644 --- a/examples/raydium_cpmm_trading/src/main.rs +++ b/examples/raydium_cpmm_trading/src/main.rs @@ -148,6 +148,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> create_mint_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -180,6 +181,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> close_output_token_ata: true, open_seed_optimize: false, durable_nonce: None, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/examples/seed_trading/src/main.rs b/examples/seed_trading/src/main.rs index f3eabb5..941cc81 100644 --- a/examples/seed_trading/src/main.rs +++ b/examples/seed_trading/src/main.rs @@ -41,6 +41,7 @@ async fn main() -> Result<(), Box> { create_mint_ata: true, open_seed_optimize: true, // ❗️❗️❗️❗️ open seed optimize durable_nonce: None, + fixed_output_token_amount: None, }; client.buy(buy_params).await?; @@ -78,6 +79,7 @@ async fn main() -> Result<(), Box> { close_output_token_ata: true, open_seed_optimize: true, // ❗️❗️❗️❗️ open seed optimize durable_nonce: None, + fixed_output_token_amount: None, }; client.sell(sell_params).await?; diff --git a/src/instruction/bonk.rs b/src/instruction/bonk.rs index e529fe2..dde7794 100755 --- a/src/instruction/bonk.rs +++ b/src/instruction/bonk.rs @@ -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( diff --git a/src/instruction/meteora_damm_v2.rs b/src/instruction/meteora_damm_v2.rs new file mode 100644 index 0000000..c90be65 --- /dev/null +++ b/src/instruction/meteora_damm_v2.rs @@ -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> { + // ======================================== + // 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::() + .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( + ¶ms.payer.pubkey(), + ¶ms.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( + ¶ms.payer.pubkey(), + ¶ms.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(¶ms.payer.pubkey(), amount_in)); + } + + if params.create_output_mint_ata { + instructions.extend( + crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.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(¶ms.payer.pubkey())); + } + + Ok(instructions) + } + + async fn build_sell_instructions(&self, params: &SwapParams) -> Result> { + // ======================================== + // Parameter validation and basic data preparation + // ======================================== + let protocol_params = params + .protocol_params + .as_any() + .downcast_ref::() + .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( + ¶ms.payer.pubkey(), + ¶ms.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( + ¶ms.payer.pubkey(), + ¶ms.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(¶ms.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(¶ms.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(¶ms.payer.pubkey())); + } + + Ok(instructions) + } +} diff --git a/src/instruction/mod.rs b/src/instruction/mod.rs index 3f94772..5afd618 100755 --- a/src/instruction/mod.rs +++ b/src/instruction/mod.rs @@ -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; \ No newline at end of file diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index 0dfd3f2..e52e106 100755 --- a/src/instruction/pumpfun.rs +++ b/src/instruction/pumpfun.rs @@ -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(¶ms.input_mint).unwrap() diff --git a/src/instruction/pumpswap.rs b/src/instruction/pumpswap.rs index 3481c76..273a701 100755 --- a/src/instruction/pumpswap.rs +++ b/src/instruction/pumpswap.rs @@ -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( ¶ms.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( diff --git a/src/instruction/raydium_amm_v4.rs b/src/instruction/raydium_amm_v4.rs index ee3a424..cd692cd 100755 --- a/src/instruction/raydium_amm_v4.rs +++ b/src/instruction/raydium_amm_v4.rs @@ -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( diff --git a/src/instruction/raydium_cpmm.rs b/src/instruction/raydium_cpmm.rs index 4106bd2..ea793d9 100755 --- a/src/instruction/raydium_cpmm.rs +++ b/src/instruction/raydium_cpmm.rs @@ -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( ¶ms.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( ¶ms.payer.pubkey(), diff --git a/src/instruction/utils/meteora_damm_v2.rs b/src/instruction/utils/meteora_damm_v2.rs new file mode 100644 index 0000000..7804ac1 --- /dev/null +++ b/src/instruction/utils/meteora_damm_v2.rs @@ -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 { + 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 +} diff --git a/src/instruction/utils/meteora_damm_v2_types.rs b/src/instruction/utils/meteora_damm_v2_types.rs new file mode 100644 index 0000000..ec55be7 --- /dev/null +++ b/src/instruction/utils/meteora_damm_v2_types.rs @@ -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 { + if data.len() < POOL_SIZE { + return None; + } + borsh::from_slice::(&data[..POOL_SIZE]).ok() +} diff --git a/src/instruction/utils/mod.rs b/src/instruction/utils/mod.rs index 05b57e9..aac39c5 100644 --- a/src/instruction/utils/mod.rs +++ b/src/instruction/utils/mod.rs @@ -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; \ No newline at end of file +pub mod raydium_cpmm_types; +pub mod meteora_damm_v2_types; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d790339..9927557 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, + /// 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, } /// 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, + /// 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, } 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::().is_some() } + DexType::MeteoraDammV2 => { + protocol_params.as_any().downcast_ref::().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::().is_some() } + DexType::MeteoraDammV2 => { + protocol_params.as_any().downcast_ref::().is_some() + } }; if !is_valid_params { diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index a2c3ca7..0061230 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -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, } 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 { + 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 { + Box::new(self.clone()) + } +} diff --git a/src/trading/factory.rs b/src/trading/factory.rs index fa6bfb1..0b37ad4 100755 --- a/src/trading/factory.rs +++ b/src/trading/factory.rs @@ -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 { + static INSTANCE: std::sync::LazyLock> = + std::sync::LazyLock::new(|| { + let instruction_builder = Arc::new(MeteoraDammV2InstructionBuilder); + Arc::new(GenericTradeExecutor::new(instruction_builder, "MeteoraDammV2")) + }); + INSTANCE.clone() + } }