diff --git a/Cargo.toml b/Cargo.toml index 24cfc5d..2ed707a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,11 @@ name = "sol-trade-sdk" version = "0.1.0" edition = "2021" -authors = ["William "] +authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/sol-trade-sdk" description = "Rust SDK to interact with the dex trade Solana program." license = "MIT" -keywords = ["solana", "memecoins", "pumpfun", "pumpswap", "raydium"] +keywords = ["solana", "memecoins", "pumpfun", "pumpswap", "raydium", "bonk", "shreds", "yellowstone"] readme = "README.md" [lib] @@ -65,7 +65,6 @@ lazy_static = "1.5.0" once_cell = "1.20.3" prost = "0.13.5" prost-types = "0.13.5" -arrform = { git = "https://github.com/raydium-io/arrform" } num_enum = "0.7.3" num-derive = "0.4.2" num-traits = "0.2.19" diff --git a/README.md b/README.md index 1f8369f..6b24e65 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A comprehensive Rust SDK for seamless interaction with Solana DEX trading progra 6. **ShredStream Support**: Subscribe to program events using ShredStream 7. **Multiple MEV Protection**: Support for Jito, Nextblock, ZeroSlot, Temporal, Bloxroute, 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 parameter structures for trading operations +9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations ## Installation @@ -38,18 +38,20 @@ sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.1.0" } ```rust use sol_trade_sdk::{ - event_parser::{ - protocols::{ - pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, - pumpswap::{ - PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, - PumpSwapSellEvent, PumpSwapWithdrawEvent, + streaming::{ + event_parser::{ + protocols::{ + bonk::{BonkPoolCreateEvent, BonkTradeEvent}, + pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, + pumpswap::{ + PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, + PumpSwapSellEvent, PumpSwapWithdrawEvent, + }, }, - bonk::{BonkPoolCreateEvent, BonkTradeEvent}, + Protocol, UnifiedEvent, }, - Protocol, UnifiedEvent, + YellowstoneGrpc, }, - grpc::YellowstoneGrpc, match_event, }; @@ -97,11 +99,7 @@ async fn test_grpc() -> Result<(), Box> { // Subscribe to events from multiple protocols println!("Starting to listen for events, press Ctrl+C to stop..."); - let protocols = vec![ - Protocol::PumpFun, - Protocol::PumpSwap, - Protocol::Bonk, - ]; + let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk]; grpc.subscribe_events(protocols, None, None, None, callback) .await?; @@ -112,7 +110,7 @@ async fn test_grpc() -> Result<(), Box> { #### 1.2 Subscribe to Events Using ShredStream ```rust -use sol_trade_sdk::grpc::ShredStreamGrpc; +use sol_trade_sdk::streaming::ShredStreamGrpc; async fn test_shreds() -> Result<(), Box> { // Subscribe to events using ShredStream client @@ -155,11 +153,7 @@ async fn test_shreds() -> Result<(), Box> { // Subscribe to events println!("Starting to listen for events, press Ctrl+C to stop..."); - let protocols = vec![ - Protocol::PumpFun, - Protocol::PumpSwap, - Protocol::Bonk, - ]; + let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk]; shred_stream .shredstream_subscribe(protocols, None, callback) .await?; @@ -177,127 +171,120 @@ use sol_trade_sdk::{ swqos::{SwqosConfig, SwqosRegion}, SolanaTrade }; -use solana_client::rpc_client::RpcClient; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; -// Create trader account -let payer = Keypair::new(); -let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); +/// Example of creating a SolanaTrade client +async fn test_create_solana_trade_client() -> AnyResult { + println!("Creating SolanaTrade client..."); -// Configure multiple MEV services to support concurrent trading -let swqos_configs = vec![ - SwqosConfig::Jito(SwqosRegion::Frankfurt), - SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Default(rpc_url.clone()), -]; + let payer = Keypair::new(); + let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); -// Define trading configuration -let trade_config = TradeConfig { - rpc_url: rpc_url.clone(), - commitment: CommitmentConfig::confirmed(), - priority_fee: PriorityFee::default(), - swqos_configs, - lookup_table_key: None, -}; + // Configure various SWQOS services + let swqos_configs = vec![ + SwqosConfig::Jito(SwqosRegion::Frankfurt), + SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::Default(rpc_url.clone()), + ]; -// Create SolanaTrade instance -let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + // Define trading configuration + let trade_config = TradeConfig { + rpc_url: rpc_url.clone(), + commitment: CommitmentConfig::confirmed(), + priority_fee: PriorityFee::default(), + swqos_configs, + lookup_table_key: None, + }; + + let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + println!("SolanaTrade client created successfully!"); + + Ok(solana_trade_client) +} ``` ### 3. PumpFun Trading Operations ```rust use sol_trade_sdk::{ - accounts::BondingCurveAccount, - constants::{pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trade_type}, - pumpfun::common::get_bonding_curve_account_v2, + common::bonding_curve::BondingCurveAccount, + constants::pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trading::{ - core::params::{PumpFunParams, PumpFunSellParams}, - BuyParams, SellParams, + core::params::PumpFunParams, + factory::TradingProtocol, + pumpfun::common::get_bonding_curve_account_v2, }, }; async fn test_pumpfun() -> AnyResult<()> { - // Basic parameter setup - let creator = Pubkey::from_str("xxxxxx")?; // Developer account - let mint_pubkey = Pubkey::from_str("xxxxxx")?; // Token address + println!("Testing PumpFun trading..."); + + let solana_trade_client = test_create_solana_trade_client().await?; + let creator = Pubkey::from_str("xxxxxx")?; // dev account let buy_sol_cost = 100_000; // 0.0001 SOL let slippage_basis_points = Some(100); - let rpc = RpcClient::new(rpc_url); - let recent_blockhash = rpc.get_latest_blockhash().unwrap(); + let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; + let mint_pubkey = Pubkey::from_str("xxxxxx")?; // token mint println!("Buying tokens from PumpFun..."); - - // Get bonding curve information + // get bonding curve let (bonding_curve, bonding_curve_pda) = get_bonding_curve_account_v2(&solana_trade_client.rpc, &mint_pubkey).await?; - + let virtual_token_reserves = bonding_curve.virtual_token_reserves; + let virtual_sol_reserves = bonding_curve.virtual_sol_reserves; + let real_token_reserves = bonding_curve.real_token_reserves; + let real_sol_reserves = bonding_curve.real_sol_reserves; let bonding_curve = BondingCurveAccount { discriminator: bonding_curve.discriminator, account: bonding_curve_pda, - virtual_token_reserves: bonding_curve.virtual_token_reserves, - virtual_sol_reserves: bonding_curve.virtual_sol_reserves, - real_token_reserves: bonding_curve.real_token_reserves, - real_sol_reserves: bonding_curve.real_sol_reserves, + virtual_token_reserves: virtual_token_reserves, + virtual_sol_reserves: virtual_sol_reserves, + real_token_reserves: real_token_reserves, + real_sol_reserves: real_sol_reserves, token_total_supply: TOKEN_TOTAL_SUPPLY, complete: false, creator: creator, }; - - // Buy operation - let buy_protocol_params = PumpFunParams { - trade_type: trade_type::COPY_BUY.to_string(), - bonding_curve: Some(Arc::new(bonding_curve)), - }; - - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_sol: buy_sol_cost, - slippage_basis_points: slippage_basis_points, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(buy_protocol_params.clone()), - }; - - // Buy with MEV protection - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); - + // For sniping developers + // let bonding_curve = + // BondingCurveAccount::new(&mint_pubkey, dev_buy_token, dev_cost_sol, creator); + + // buy solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpFun, + Some(Box::new(PumpFunParams { + bonding_curve: Some(Arc::new(bonding_curve.clone())), + })), + ) .await?; - - // Sell operation + + // sell println!("Selling tokens from PumpFun..."); - let sell_protocol_params = PumpFunSellParams {}; - let amount_token = 1000000; // Enter the actual token amount - - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_token: Some(amount_token), - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(sell_protocol_params.clone()), - }; - + let amount_token = 0; // Enter the actual amount_token solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + Some(creator), + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpFun, + None, + ) .await?; - Ok(()) } ``` @@ -305,73 +292,48 @@ async fn test_pumpfun() -> AnyResult<()> { ### 4. PumpSwap Trading Operations ```rust -use sol_trade_sdk::trading::core::params::PumpSwapParams; - async fn test_pumpswap() -> AnyResult<()> { - // Basic parameter setup - let creator = Pubkey::from_str("11111111111111111111111111111111")?; // Developer account - let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // Token address + println!("Testing PumpSwap trading..."); + + let solana_trade_client = test_create_solana_trade_client().await?; + let creator = Pubkey::from_str("11111111111111111111111111111111")?; // dev account let buy_sol_cost = 100_000; // 0.0001 SOL let slippage_basis_points = Some(100); - let rpc = RpcClient::new(rpc_url); - let recent_blockhash = rpc.get_latest_blockhash().unwrap(); + let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; + let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // token mint println!("Buying tokens from PumpSwap..."); - - // PumpSwap parameter configuration - let protocol_params = PumpSwapParams { - pool: None, - pool_base_token_account: None, - pool_quote_token_account: None, - user_base_token_account: None, - user_quote_token_account: None, - auto_handle_wsol: true, - }; - - // Buy operation - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_sol: buy_sol_cost, - slippage_basis_points: slippage_basis_points, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(protocol_params.clone()), - }; - - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); - + // buy solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpSwap, + None, + ) .await?; - - // Sell operation + + // sell println!("Selling tokens from PumpSwap..."); - let amount_token = 1000000; // Enter the actual token amount - - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_token: Some(amount_token), - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(protocol_params.clone()), - }; - + let amount_token = 0; // Enter the actual amount_token solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + Some(creator), + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpSwap, + None, + ) .await?; - Ok(()) } ``` @@ -379,68 +341,47 @@ async fn test_pumpswap() -> AnyResult<()> { ### 5. Bonk Trading Operations ```rust -use sol_trade_sdk::trading::core::params::BonkParams; - async fn test_bonk() -> Result<(), Box> { - // Basic parameter setup - let amount = 100_000; // 0.0001 SOL - let mint = Pubkey::from_str("xxxxxxx")?; - let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; + println!("Testing Bonk trading..."); - // Bonk parameter configuration - let bonk_params = BonkParams { - virtual_base: None, - virtual_quote: None, - real_base_before: None, - real_quote_before: None, - auto_handle_wsol: true, - }; + let solana_trade_client = test_create_solana_trade_client().await?; + let buy_sol_cost = 100_000; // 0.0001 SOL + let slippage_basis_points = Some(100); // 1% + let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; println!("Buying tokens from letsbonk.fun..."); - - // Buy operation - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint, - creator: Pubkey::default(), - amount_sol: amount, - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(bonk_params.clone()), - }; - - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); - + // buy solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::Bonk, + None, + ) .await?; - - // Sell operation + + // sell println!("Selling tokens from letsbonk.fun..."); - - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint, - creator: Pubkey::default(), - amount_token: None, - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(bonk_params.clone()), - }; - + let amount_token = 0; // Enter the actual amount_token solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::Bonk, + None, + ) .await?; - Ok(()) } ``` @@ -487,11 +428,11 @@ let trade_config = TradeConfig { ## New Architecture Features -### Unified Parameter Structure +### Unified Trading Interface -- **BuyParams**: Unified buy parameter structure -- **SellParams**: Unified sell parameter structure -- **Protocol-specific Parameters**: Each protocol has its own parameter structure (PumpFunParams, PumpSwapParams, BonkParams) +- **TradingProtocol Enum**: Use unified protocol enums (PumpFun, PumpSwap, Bonk) +- **Unified buy/sell Methods**: All protocols use the same trading method signatures +- **Protocol-specific Parameters**: Each protocol has its own parameter structure (PumpFunParams, etc.) ### Event Parsing System @@ -509,29 +450,28 @@ let trade_config = TradeConfig { ``` src/ -├── accounts/ # Account-related definitions ├── common/ # Common functionality and tools ├── constants/ # Constant definitions -├── error/ # Error handling -├── event_parser/ # Event parsing system -│ ├── common/ # Common event parsing tools -│ ├── core/ # Core parsing traits and interfaces -│ ├── protocols/ # Protocol-specific parsers -│ │ ├── pumpfun/ # PumpFun event parsing -│ │ ├── pumpswap/ # PumpSwap event parsing -│ │ └── bonk/ # Bonk event parsing -│ └── factory.rs # Parser factory -├── grpc/ # gRPC clients ├── instruction/ # Instruction building -├── protos/ # Protocol buffer definitions -├── pumpfun/ # PumpFun trading functionality -├── pumpswap/ # PumpSwap trading functionality -├── bonk/ # Bonk trading functionality +├── streaming/ # Event stream processing +│ ├── event_parser/ # Event parsing system +│ │ ├── common/ # Common event parsing tools +│ │ ├── core/ # Core parsing traits and interfaces +│ │ ├── protocols/# Protocol-specific parsers +│ │ │ ├── bonk/ # Bonk event parsing +│ │ │ ├── pumpfun/ # PumpFun event parsing +│ │ │ └── pumpswap/ # PumpSwap event parsing +│ │ └── factory.rs # Parser factory +│ ├── shred_stream.rs # ShredStream client +│ └── yellowstone_grpc.rs # Yellowstone gRPC client ├── swqos/ # MEV service clients ├── trading/ # Unified trading engine │ ├── common/ # Common trading tools │ ├── core/ # Core trading engine -│ └── protocols/ # Protocol-specific trading implementations +│ ├── bonk/ # Bonk trading implementation +│ ├── pumpfun/ # PumpFun trading implementation +│ ├── pumpswap/ # PumpSwap trading implementation +│ └── factory.rs # Trading factory ├── lib.rs # Main library file └── main.rs # Example program ``` diff --git a/README_CN.md b/README_CN.md index 4efc2b7..0d29b91 100755 --- a/README_CN.md +++ b/README_CN.md @@ -12,7 +12,7 @@ 6. **ShredStream 支持**: 使用 ShredStream 订阅程序事件 7. **多种 MEV 保护**: 支持 Jito、Nextblock、ZeroSlot、Temporal、Bloxroute 等服务 8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败 -9. **统一交易接口**: 使用统一的参数结构进行交易操作 +9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作 ## 安装 @@ -38,18 +38,20 @@ sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.1.0" } ```rust use sol_trade_sdk::{ - event_parser::{ - protocols::{ - pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, - pumpswap::{ - PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, - PumpSwapSellEvent, PumpSwapWithdrawEvent, + streaming::{ + event_parser::{ + protocols::{ + bonk::{BonkPoolCreateEvent, BonkTradeEvent}, + pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, + pumpswap::{ + PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, + PumpSwapSellEvent, PumpSwapWithdrawEvent, + }, }, - bonk::{BonkPoolCreateEvent, BonkTradeEvent}, + Protocol, UnifiedEvent, }, - Protocol, UnifiedEvent, + YellowstoneGrpc, }, - grpc::YellowstoneGrpc, match_event, }; @@ -97,11 +99,7 @@ async fn test_grpc() -> Result<(), Box> { // 订阅多个协议的事件 println!("开始监听事件,按 Ctrl+C 停止..."); - let protocols = vec![ - Protocol::PumpFun, - Protocol::PumpSwap, - Protocol::Bonk, - ]; + let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk]; grpc.subscribe_events(protocols, None, None, None, callback) .await?; @@ -112,7 +110,7 @@ async fn test_grpc() -> Result<(), Box> { #### 1.2 使用 ShredStream 订阅事件 ```rust -use sol_trade_sdk::grpc::ShredStreamGrpc; +use sol_trade_sdk::streaming::ShredStreamGrpc; async fn test_shreds() -> Result<(), Box> { // 使用 ShredStream 客户端订阅事件 @@ -155,11 +153,7 @@ async fn test_shreds() -> Result<(), Box> { // 订阅事件 println!("开始监听事件,按 Ctrl+C 停止..."); - let protocols = vec![ - Protocol::PumpFun, - Protocol::PumpSwap, - Protocol::Bonk, - ]; + let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk]; shred_stream .shredstream_subscribe(protocols, None, callback) .await?; @@ -177,127 +171,120 @@ use sol_trade_sdk::{ swqos::{SwqosConfig, SwqosRegion}, SolanaTrade }; -use solana_client::rpc_client::RpcClient; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; -// 创建交易者账户 -let payer = Keypair::new(); -let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); +/// 创建 SolanaTrade 客户端的示例 +async fn test_create_solana_trade_client() -> AnyResult { + println!("Creating SolanaTrade client..."); -// 配置多个MEV服务,支持并发交易 -let swqos_configs = vec![ - SwqosConfig::Jito(SwqosRegion::Frankfurt), - SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Default(rpc_url.clone()), -]; + let payer = Keypair::new(); + let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); -// 定义交易配置 -let trade_config = TradeConfig { - rpc_url: rpc_url.clone(), - commitment: CommitmentConfig::confirmed(), - priority_fee: PriorityFee::default(), - swqos_configs, - lookup_table_key: None, -}; + // 配置各种 SWQOS 服务 + let swqos_configs = vec![ + SwqosConfig::Jito(SwqosRegion::Frankfurt), + SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::Default(rpc_url.clone()), + ]; -// 创建SolanaTrade实例 -let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + // 定义交易配置 + let trade_config = TradeConfig { + rpc_url: rpc_url.clone(), + commitment: CommitmentConfig::confirmed(), + priority_fee: PriorityFee::default(), + swqos_configs, + lookup_table_key: None, + }; + + let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + println!("SolanaTrade client created successfully!"); + + Ok(solana_trade_client) +} ``` ### 3. PumpFun 交易操作 ```rust use sol_trade_sdk::{ - accounts::BondingCurveAccount, - constants::{pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trade_type}, - pumpfun::common::get_bonding_curve_account_v2, + common::bonding_curve::BondingCurveAccount, + constants::pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trading::{ - core::params::{PumpFunParams, PumpFunSellParams}, - BuyParams, SellParams, + core::params::PumpFunParams, + factory::TradingProtocol, + pumpfun::common::get_bonding_curve_account_v2, }, }; async fn test_pumpfun() -> AnyResult<()> { - // 基本参数设置 - let creator = Pubkey::from_str("xxxxxx")?; // 开发者账户 - let mint_pubkey = Pubkey::from_str("xxxxxx")?; // 代币地址 + println!("Testing PumpFun trading..."); + + let solana_trade_client = test_create_solana_trade_client().await?; + let creator = Pubkey::from_str("xxxxxx")?; // dev account let buy_sol_cost = 100_000; // 0.0001 SOL let slippage_basis_points = Some(100); - let rpc = RpcClient::new(rpc_url); - let recent_blockhash = rpc.get_latest_blockhash().unwrap(); + let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; + let mint_pubkey = Pubkey::from_str("xxxxxx")?; // token mint println!("Buying tokens from PumpFun..."); - - // 获取bonding curve信息 + // get bonding curve let (bonding_curve, bonding_curve_pda) = get_bonding_curve_account_v2(&solana_trade_client.rpc, &mint_pubkey).await?; - + let virtual_token_reserves = bonding_curve.virtual_token_reserves; + let virtual_sol_reserves = bonding_curve.virtual_sol_reserves; + let real_token_reserves = bonding_curve.real_token_reserves; + let real_sol_reserves = bonding_curve.real_sol_reserves; let bonding_curve = BondingCurveAccount { discriminator: bonding_curve.discriminator, account: bonding_curve_pda, - virtual_token_reserves: bonding_curve.virtual_token_reserves, - virtual_sol_reserves: bonding_curve.virtual_sol_reserves, - real_token_reserves: bonding_curve.real_token_reserves, - real_sol_reserves: bonding_curve.real_sol_reserves, + virtual_token_reserves: virtual_token_reserves, + virtual_sol_reserves: virtual_sol_reserves, + real_token_reserves: real_token_reserves, + real_sol_reserves: real_sol_reserves, token_total_supply: TOKEN_TOTAL_SUPPLY, complete: false, creator: creator, }; - - // 购买操作 - let buy_protocol_params = PumpFunParams { - trade_type: trade_type::COPY_BUY.to_string(), - bonding_curve: Some(Arc::new(bonding_curve)), - }; - - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_sol: buy_sol_cost, - slippage_basis_points: slippage_basis_points, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(buy_protocol_params.clone()), - }; - - // 使用MEV保护的购买 - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); - + // 如果是狙击开发者 + // let bonding_curve = + // BondingCurveAccount::new(&mint_pubkey, dev_buy_token, dev_cost_sol, creator); + + // buy solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpFun, + Some(Box::new(PumpFunParams { + bonding_curve: Some(Arc::new(bonding_curve.clone())), + })), + ) .await?; - - // 卖出操作 + + // sell println!("Selling tokens from PumpFun..."); - let sell_protocol_params = PumpFunSellParams {}; - let amount_token = 1000000; // 写上真实的代币数量 - - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_token: Some(amount_token), - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(sell_protocol_params.clone()), - }; - + let amount_token = 0; // 写上真实的amount_token solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + Some(creator), + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpFun, + None, + ) .await?; - Ok(()) } ``` @@ -305,73 +292,48 @@ async fn test_pumpfun() -> AnyResult<()> { ### 4. PumpSwap 交易操作 ```rust -use sol_trade_sdk::trading::core::params::PumpSwapParams; - async fn test_pumpswap() -> AnyResult<()> { - // 基本参数设置 - let creator = Pubkey::from_str("11111111111111111111111111111111")?; // 开发者账户 - let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // 代币地址 + println!("Testing PumpSwap trading..."); + + let solana_trade_client = test_create_solana_trade_client().await?; + let creator = Pubkey::from_str("11111111111111111111111111111111")?; // dev account let buy_sol_cost = 100_000; // 0.0001 SOL let slippage_basis_points = Some(100); - let rpc = RpcClient::new(rpc_url); - let recent_blockhash = rpc.get_latest_blockhash().unwrap(); + let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; + let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // token mint println!("Buying tokens from PumpSwap..."); - - // PumpSwap参数配置 - let protocol_params = PumpSwapParams { - pool: None, - pool_base_token_account: None, - pool_quote_token_account: None, - user_base_token_account: None, - user_quote_token_account: None, - auto_handle_wsol: true, - }; - - // 购买操作 - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_sol: buy_sol_cost, - slippage_basis_points: slippage_basis_points, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(protocol_params.clone()), - }; - - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); - + // buy solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpSwap, + None, + ) .await?; - - // 卖出操作 + + // sell println!("Selling tokens from PumpSwap..."); - let amount_token = 1000000; // 写上真实的代币数量 - - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_token: Some(amount_token), - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(protocol_params.clone()), - }; - + let amount_token = 0; // 写上真实的amount_token solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + Some(creator), + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpSwap, + None, + ) .await?; - Ok(()) } ``` @@ -379,68 +341,47 @@ async fn test_pumpswap() -> AnyResult<()> { ### 5. Bonk 交易操作 ```rust -use sol_trade_sdk::trading::core::params::BonkParams; - async fn test_bonk() -> Result<(), Box> { - // 基本参数设置 - let amount = 100_000; // 0.0001 SOL - let mint = Pubkey::from_str("xxxxxxx")?; - let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; + println!("Testing Bonk trading..."); - // Bonk参数配置 - let bonk_params = BonkParams { - virtual_base: None, - virtual_quote: None, - real_base_before: None, - real_quote_before: None, - auto_handle_wsol: true, - }; + let solana_trade_client = test_create_solana_trade_client().await?; + let buy_sol_cost = 100_000; // 0.0001 SOL + let slippage_basis_points = Some(100); // 1% + let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; println!("Buying tokens from letsbonk.fun..."); - - // 购买操作 - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint, - creator: Pubkey::default(), - amount_sol: amount, - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(bonk_params.clone()), - }; - - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); - + // buy solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::Bonk, + None, + ) .await?; - - // 卖出操作 + + // sell println!("Selling tokens from letsbonk.fun..."); - - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint, - creator: Pubkey::default(), - amount_token: None, - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(bonk_params.clone()), - }; - + let amount_token = 0; // 写上真实的amount_token solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::Bonk, + None, + ) .await?; - Ok(()) } ``` @@ -487,11 +428,11 @@ let trade_config = TradeConfig { ## 新架构特性 -### 统一参数结构 +### 统一交易接口 -- **BuyParams**: 统一的购买参数结构 -- **SellParams**: 统一的卖出参数结构 -- **协议特定参数**: 每个协议都有自己的参数结构(PumpFunParams、PumpSwapParams、BonkParams) +- **TradingProtocol 枚举**: 使用统一的协议枚举(PumpFun、PumpSwap、Bonk) +- **统一的 buy/sell 方法**: 所有协议都使用相同的交易方法签名 +- **协议特定参数**: 每个协议都有自己的参数结构(PumpFunParams 等) ### 事件解析系统 @@ -509,29 +450,28 @@ let trade_config = TradeConfig { ``` src/ -├── accounts/ # 账户相关定义 ├── common/ # 通用功能和工具 ├── constants/ # 常量定义 -├── error/ # 错误处理 -├── event_parser/ # 事件解析系统 -│ ├── common/ # 通用事件解析工具 -│ ├── core/ # 核心解析特征和接口 -│ ├── protocols/ # 协议特定解析器 -│ │ ├── pumpfun/ # PumpFun事件解析 -│ │ ├── pumpswap/ # PumpSwap事件解析 -│ │ └── bonk/ # Bonk事件解析 -│ └── factory.rs # 解析器工厂 -├── grpc/ # gRPC客户端 ├── instruction/ # 指令构建 -├── protos/ # 协议缓冲区定义 -├── pumpfun/ # PumpFun交易功能 -├── pumpswap/ # PumpSwap交易功能 -├── bonk/ # Bonk交易功能 +├── streaming/ # 事件流处理 +│ ├── event_parser/ # 事件解析系统 +│ │ ├── common/ # 通用事件解析工具 +│ │ ├── core/ # 核心解析特征和接口 +│ │ ├── protocols/# 协议特定解析器 +│ │ │ ├── bonk/ # Bonk事件解析 +│ │ │ ├── pumpfun/ # PumpFun事件解析 +│ │ │ └── pumpswap/ # PumpSwap事件解析 +│ │ └── factory.rs # 解析器工厂 +│ ├── shred_stream.rs # ShredStream客户端 +│ └── yellowstone_grpc.rs # Yellowstone gRPC客户端 ├── swqos/ # MEV服务客户端 ├── trading/ # 统一交易引擎 │ ├── common/ # 通用交易工具 │ ├── core/ # 核心交易引擎 -│ └── protocols/ # 协议特定交易实现 +│ ├── bonk/ # Bonk交易实现 +│ ├── pumpfun/ # PumpFun交易实现 +│ ├── pumpswap/ # PumpSwap交易实现 +│ └── factory.rs # 交易工厂 ├── lib.rs # 主库文件 └── main.rs # 示例程序 ``` diff --git a/src/common/types.rs b/src/common/types.rs index 784de85..75c9e1a 100755 --- a/src/common/types.rs +++ b/src/common/types.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use solana_client::rpc_client::RpcClient; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; use serde::Deserialize; -use crate::{constants::pumpfun::trade::{DEFAULT_BUY_TIP_FEE, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE}, swqos::{SwqosClient, SwqosConfig, SwqosRegion}}; +use crate::{constants::trade::trade::{DEFAULT_BUY_TIP_FEE, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE}, swqos::{SwqosClient, SwqosConfig}}; #[derive(Debug, Clone)] pub struct TradeConfig { diff --git a/src/constants/bonk.rs b/src/constants/bonk.rs index 18306a7..e677d60 100755 --- a/src/constants/bonk.rs +++ b/src/constants/bonk.rs @@ -35,12 +35,3 @@ pub mod accounts { pub const BUY_EXECT_IN_DISCRIMINATOR: [u8; 8] = [250, 234, 13, 123, 213, 156, 19, 236]; pub const SELL_EXECT_IN_DISCRIMINATOR: [u8; 8] = [149, 39, 222, 155, 211, 124, 152, 26]; - -pub mod trade { - pub const TRADER_TIP_AMOUNT: u64 = 100000; // 0.0001 SOL in lamports - pub const DEFAULT_SLIPPAGE: u64 = 100; // 1% - pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000; - pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000; - pub const DEFAULT_BUY_TIP_FEE: u64 = 600000; // 0.0006 SOL in lamports - pub const DEFAULT_SELL_TIP_FEE: u64 = 100000; // 0.0001 SOL in lamports -} diff --git a/src/constants/mod.rs b/src/constants/mod.rs index 393375d..27ef1f4 100755 --- a/src/constants/mod.rs +++ b/src/constants/mod.rs @@ -1,17 +1,11 @@ +pub mod bonk; pub mod pumpfun; pub mod pumpswap; -pub mod bonk; pub mod swqos; - -pub mod trade_type { - pub const COPY_BUY: &'static str = "copy_buy"; - pub const COPY_SELL: &'static str = "copy_sell"; - pub const SNIPER_BUY: &'static str = "sniper_buy"; - pub const SNIPER_SELL: &'static str = "sniper_sell"; -} +pub mod trade; pub mod trade_platform { - pub const PUMPFUN: &'static str = "pumpfun"; - pub const PUMPFUN_SWAP: &'static str = "pumpswap"; - pub const BONK: &'static str = "bonk"; -} \ No newline at end of file + pub const PUMPFUN: &'static str = "pumpfun"; + pub const PUMPFUN_SWAP: &'static str = "pumpswap"; + pub const BONK: &'static str = "bonk"; +} diff --git a/src/constants/pumpfun.rs b/src/constants/pumpfun.rs index 6baaa30..a163109 100755 --- a/src/constants/pumpfun.rs +++ b/src/constants/pumpfun.rs @@ -38,7 +38,7 @@ pub mod global_constants { pub const INITIAL_REAL_TOKEN_RESERVES: u64 = 793_100_000_000_000; pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000; - + pub const FEE_BASIS_POINTS: u64 = 95; pub const ENABLE_MIGRATE: bool = false; @@ -75,8 +75,8 @@ pub mod global_constants { pub const PUMPFUN_AMM_FEE_4: Pubkey = pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"); // Pump.fun AMM: Protocol Fee 4 pub const PUMPFUN_AMM_FEE_5: Pubkey = pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"); // Pump.fun AMM: Protocol Fee 5 pub const PUMPFUN_AMM_FEE_6: Pubkey = pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"); // Pump.fun AMM: Protocol Fee 6 - pub const PUMPFUN_AMM_FEE_7: Pubkey = pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"); // Pump.fun AMM: Protocol Fee 7 - + pub const PUMPFUN_AMM_FEE_7: Pubkey = pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"); + // Pump.fun AMM: Protocol Fee 7 } /// Constants related to program accounts and authorities @@ -99,7 +99,8 @@ pub mod accounts { pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); /// Associated Token Program ID - pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = + pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); /// Rent Sysvar ID pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111"); @@ -107,20 +108,8 @@ pub mod accounts { pub const AMM_PROGRAM: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); } -pub mod trade { - pub const TRADER_TIP_AMOUNT: f64 = 0.0001; - pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10% - pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000; - pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000; - pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006; - pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001; - pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 1000000; - pub const DEFAULT_RPC_UNIT_PRICE: u64 = 500000; -} - pub struct Symbol; impl Symbol { pub const SOLANA: &'static str = "solana"; } - diff --git a/src/constants/pumpswap.rs b/src/constants/pumpswap.rs index 4785bd9..db2ee4a 100755 --- a/src/constants/pumpswap.rs +++ b/src/constants/pumpswap.rs @@ -32,7 +32,7 @@ pub mod accounts { /// Public key for the fee recipient pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"); - pub const FEE_RECIPIENT_ATA:Pubkey = pubkey!("94qWNrtmfn42h3ZjUZwWvK1MEo9uVmmrBPd2hpNjYDjb"); + pub const FEE_RECIPIENT_ATA: Pubkey = pubkey!("94qWNrtmfn42h3ZjUZwWvK1MEo9uVmmrBPd2hpNjYDjb"); /// Public key for the global PDA pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw"); @@ -49,26 +49,18 @@ pub mod accounts { pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); /// Associated Token Program ID - pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = + pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); // PumpSwap 协议费用接收者 - pub const PROTOCOL_FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"); + pub const PROTOCOL_FEE_RECIPIENT: Pubkey = + pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"); /// Rent Sysvar ID pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111"); pub const AMM_PROGRAM: Pubkey = pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"); - } pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234]; pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173]; - -pub mod trade { - pub const TRADER_TIP_AMOUNT: u64 = 100000; // 0.0001 SOL in lamports - pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10% - pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000; - pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000; - pub const DEFAULT_BUY_TIP_FEE: u64 = 600000; // 0.0006 SOL in lamports - pub const DEFAULT_SELL_TIP_FEE: u64 = 100000; // 0.0001 SOL in lamports -} diff --git a/src/constants/trade.rs b/src/constants/trade.rs new file mode 100644 index 0000000..578aeae --- /dev/null +++ b/src/constants/trade.rs @@ -0,0 +1,9 @@ +pub mod trade { + pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10% + pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 78000; + pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 500000; + pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006; + pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001; + pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 1000000; + pub const DEFAULT_RPC_UNIT_PRICE: u64 = 500000; +} diff --git a/src/instruction/bonk.rs b/src/instruction/bonk.rs index 43000c5..dd2459e 100755 --- a/src/instruction/bonk.rs +++ b/src/instruction/bonk.rs @@ -1,15 +1,17 @@ use anyhow::{anyhow, Result}; -use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer}; +use solana_sdk::{instruction::Instruction, signer::Signer}; use spl_associated_token_account::instruction::create_associated_token_account_idempotent; use crate::{ constants::bonk::{ - accounts, trade::DEFAULT_SLIPPAGE, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR, + accounts, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR, }, + constants::trade::trade::DEFAULT_SLIPPAGE, trading::bonk::{ - common::{get_amount_out, get_pool_pda, get_token_balance, get_vault_pda}, + common::{get_amount_out, get_pool_pda, get_vault_pda}, pool::Pool, }, + trading::common::utils::get_token_balance, trading::core::{ params::{BuyParams, BonkParams, SellParams}, traits::InstructionBuilder, diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index b2e1b80..687e169 100755 --- a/src/instruction/pumpfun.rs +++ b/src/instruction/pumpfun.rs @@ -1,34 +1,28 @@ use anyhow::{anyhow, Result}; -use solana_sdk::{ - instruction::Instruction, native_token::sol_to_lamports, -}; +use solana_sdk::{instruction::Instruction, native_token::sol_to_lamports}; use spl_associated_token_account::{ get_associated_token_address, instruction::create_associated_token_account, }; use spl_token::instruction::close_account; use crate::{ - constants, trading::pumpfun::common::{ - get_bonding_curve_pda, get_global_pda, get_metadata_pda, get_mint_authority_pda - } + constants, + trading::pumpfun::common::{ + get_bonding_curve_pda, get_global_pda, get_metadata_pda, get_mint_authority_pda, + }, }; -use solana_sdk::{ - instruction::AccountMeta, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, -}; +use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, signer::Signer}; use crate::{ - constants::pumpfun::{global_constants::FEE_RECIPIENT, trade::DEFAULT_SLIPPAGE}, - trading::pumpfun::common::{ - calculate_with_slippage_buy, get_buy_token_amount_from_sol_amount, get_creator_vault_pda, - }, + constants::pumpfun::global_constants::FEE_RECIPIENT, + constants::trade::trade::DEFAULT_SLIPPAGE, + trading::common::utils::calculate_with_slippage_buy, trading::core::{ params::{BuyParams, PumpFunParams, SellParams}, traits::InstructionBuilder, }, + trading::pumpfun::common::{get_buy_token_amount_from_sol_amount, get_creator_vault_pda}, }; /// PumpFun协议的指令构建器 @@ -56,9 +50,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder { let max_sol_cost = calculate_with_slippage_buy( params.amount_sol, - params - .slippage_basis_points - .unwrap_or(DEFAULT_SLIPPAGE), + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), ); let creator_vault_pda = bonding_curve.get_creator_vault_pda(); @@ -161,22 +153,24 @@ pub struct Create { impl Create { pub fn data(&self) -> Vec { - let mut data = Vec::with_capacity(8 + 4 + self._name.len() + 4 + self._symbol.len() + 4 + self._uri.len() + 32); + let mut data = Vec::with_capacity( + 8 + 4 + self._name.len() + 4 + self._symbol.len() + 4 + self._uri.len() + 32, + ); // 追加 discriminator data.extend_from_slice(&[24, 30, 200, 40, 5, 28, 7, 119]); // discriminator // 添加 name 字符串长度和内容 - data.extend_from_slice(&(self._name.len() as u32).to_le_bytes()); // 添加 name 长度 - data.extend_from_slice(self._name.as_bytes()); // 添加 name 内容 + data.extend_from_slice(&(self._name.len() as u32).to_le_bytes()); // 添加 name 长度 + data.extend_from_slice(self._name.as_bytes()); // 添加 name 内容 // 添加 symbol 字符串长度和内容 - data.extend_from_slice(&(self._symbol.len() as u32).to_le_bytes()); // 添加 symbol 长度 - data.extend_from_slice(self._symbol.as_bytes()); // 添加 symbol 内容 + data.extend_from_slice(&(self._symbol.len() as u32).to_le_bytes()); // 添加 symbol 长度 + data.extend_from_slice(self._symbol.as_bytes()); // 添加 symbol 内容 // 添加 uri 字符串长度和内容 - data.extend_from_slice(&(self._uri.len() as u32).to_le_bytes()); // 添加 uri 长度 - data.extend_from_slice(self._uri.as_bytes()); // 添加 uri 内容 + data.extend_from_slice(&(self._uri.len() as u32).to_le_bytes()); // 添加 uri 长度 + data.extend_from_slice(self._uri.as_bytes()); // 添加 uri 内容 data.extend_from_slice(&self._creator.to_bytes()); @@ -233,7 +227,10 @@ pub fn create(payer: &Keypair, mint: &Keypair, args: Create) -> Instruction { AccountMeta::new(payer.pubkey(), true), AccountMeta::new_readonly(constants::pumpfun::accounts::SYSTEM_PROGRAM, false), AccountMeta::new_readonly(constants::pumpfun::accounts::TOKEN_PROGRAM, false), - AccountMeta::new_readonly(constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM, false), + AccountMeta::new_readonly( + constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM, + false, + ), AccountMeta::new_readonly(constants::pumpfun::accounts::RENT, false), AccountMeta::new_readonly(constants::pumpfun::accounts::EVENT_AUTHORITY, false), AccountMeta::new_readonly(constants::pumpfun::accounts::PUMPFUN, false), diff --git a/src/instruction/pumpswap.rs b/src/instruction/pumpswap.rs index 660fd95..4689660 100755 --- a/src/instruction/pumpswap.rs +++ b/src/instruction/pumpswap.rs @@ -3,18 +3,19 @@ use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer}; use spl_associated_token_account::instruction::create_associated_token_account_idempotent; use crate::{ - constants::pumpswap::{ - accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR, - }, - trading::pumpswap::common::{ - calculate_with_slippage_buy, calculate_with_slippage_sell, coin_creator_vault_ata, - coin_creator_vault_authority, find_pool, get_buy_token_amount, get_sell_sol_amount, - get_token_balance, + constants::pumpswap::{accounts, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR}, + constants::trade::trade::DEFAULT_SLIPPAGE, + trading::common::utils::{ + calculate_with_slippage_buy, calculate_with_slippage_sell, get_token_balance, }, trading::core::{ params::{BuyParams, PumpSwapParams, SellParams}, traits::InstructionBuilder, }, + trading::pumpswap::common::{ + coin_creator_vault_ata, coin_creator_vault_authority, find_pool, get_buy_token_amount, + get_sell_sol_amount, + }, }; /// PumpSwap协议的指令构建器 @@ -35,27 +36,11 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { } // 根据是否提供了账户信息来构建指令 - match ( - &protocol_params.pool, - &protocol_params.pool_base_token_account, - &protocol_params.pool_quote_token_account, - &protocol_params.user_base_token_account, - &protocol_params.user_quote_token_account, - ) { - ( - Some(pool), - Some(pool_base_token_account), - Some(pool_quote_token_account), - Some(user_base_token_account), - Some(user_quote_token_account), - ) => { + match (&protocol_params.pool,) { + (Some(pool),) => { self.build_buy_instructions_with_accounts( params, *pool, - *pool_base_token_account, - *pool_quote_token_account, - *user_base_token_account, - *user_quote_token_account, protocol_params.auto_handle_wsol, ) .await @@ -73,29 +58,10 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { .ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?; // 根据是否提供了账户信息来构建指令 - match ( - &protocol_params.pool, - &protocol_params.pool_base_token_account, - &protocol_params.pool_quote_token_account, - &protocol_params.user_base_token_account, - &protocol_params.user_quote_token_account, - ) { - ( - Some(pool), - Some(pool_base_token_account), - Some(pool_quote_token_account), - Some(user_base_token_account), - Some(user_quote_token_account), - ) => { - self.build_sell_instructions_with_accounts( - params, - *pool, - *pool_base_token_account, - *pool_quote_token_account, - *user_base_token_account, - *user_quote_token_account, - ) - .await + match (&protocol_params.pool,) { + (Some(pool),) => { + self.build_sell_instructions_with_accounts(params, *pool) + .await } _ => self.build_sell_instructions_auto_discover(params).await, } @@ -115,41 +81,8 @@ impl PumpSwapInstructionBuilder { // 查找池 let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?; - // 创建用户代币账户 - let user_base_token_account = spl_associated_token_account::get_associated_token_address( - ¶ms.payer.pubkey(), - ¶ms.mint, - ); - let user_quote_token_account = spl_associated_token_account::get_associated_token_address( - ¶ms.payer.pubkey(), - &accounts::WSOL_TOKEN_ACCOUNT, - ); - - // 获取池的代币账户 - let pool_base_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( - &pool, - ¶ms.mint, - &accounts::TOKEN_PROGRAM, - ); - - let pool_quote_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( - &pool, - &accounts::WSOL_TOKEN_ACCOUNT, - &accounts::TOKEN_PROGRAM, - ); - - self.build_buy_instructions_with_accounts( - params, - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - true, - ) - .await + self.build_buy_instructions_with_accounts(params, pool, true) + .await } /// 自动发现池和账户信息并构建卖出指令 @@ -165,6 +98,30 @@ impl PumpSwapInstructionBuilder { // 查找池 let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?; + self.build_sell_instructions_with_accounts(params, pool) + .await + } + + /// 使用提供的账户信息构建买入指令 + async fn build_buy_instructions_with_accounts( + &self, + params: &BuyParams, + pool: Pubkey, + auto_handle_wsol: bool, + ) -> Result> { + if params.rpc.is_none() { + return Err(anyhow!("RPC is not set")); + } + let rpc = params.rpc.as_ref().unwrap().clone(); + // 计算预期的代币数量 + let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, params.amount_sol).await?; + + // 计算滑点后的最大SOL数量 + let max_sol_amount = calculate_with_slippage_buy( + params.amount_sol, + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ); + // 创建用户代币账户 let user_base_token_account = spl_associated_token_account::get_associated_token_address( ¶ms.payer.pubkey(), @@ -190,41 +147,6 @@ impl PumpSwapInstructionBuilder { &accounts::TOKEN_PROGRAM, ); - self.build_sell_instructions_with_accounts( - params, - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - ) - .await - } - - /// 使用提供的账户信息构建买入指令 - async fn build_buy_instructions_with_accounts( - &self, - params: &BuyParams, - pool: Pubkey, - pool_base_token_account: Pubkey, - pool_quote_token_account: Pubkey, - user_base_token_account: Pubkey, - user_quote_token_account: Pubkey, - auto_handle_wsol: bool, - ) -> Result> { - if params.rpc.is_none() { - return Err(anyhow!("RPC is not set")); - } - let rpc = params.rpc.as_ref().unwrap().clone(); - // 计算预期的代币数量 - let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, params.amount_sol).await?; - - // 计算滑点后的最大SOL数量 - let max_sol_amount = calculate_with_slippage_buy( - params.amount_sol, - params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), - ); - let mut instructions = vec![]; if auto_handle_wsol { @@ -328,10 +250,6 @@ impl PumpSwapInstructionBuilder { &self, params: &SellParams, pool: Pubkey, - pool_base_token_account: Pubkey, - pool_quote_token_account: Pubkey, - user_base_token_account: Pubkey, - user_quote_token_account: Pubkey, ) -> Result> { if params.rpc.is_none() { return Err(anyhow!("RPC is not set")); @@ -341,8 +259,8 @@ impl PumpSwapInstructionBuilder { // 获取代币余额 let mut amount = params.amount_token; if params.amount_token.is_none() { - let (balance_u64, _) = - get_token_balance(rpc.as_ref(), params.payer.as_ref(), ¶ms.mint).await?; + let balance_u64 = + get_token_balance(rpc.as_ref(), ¶ms.payer.pubkey(), ¶ms.mint).await?; amount = Some(balance_u64); } let amount = amount.unwrap_or(0); @@ -363,6 +281,27 @@ impl PumpSwapInstructionBuilder { let coin_creator_vault_ata = coin_creator_vault_ata(params.creator); let coin_creator_vault_authority = coin_creator_vault_authority(params.creator); + let user_base_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + ¶ms.mint, + ); + let user_quote_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + ); + let pool_base_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &pool, + ¶ms.mint, + &accounts::TOKEN_PROGRAM, + ); + let pool_quote_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &pool, + &accounts::WSOL_TOKEN_ACCOUNT, + &accounts::TOKEN_PROGRAM, + ); + let mut instructions = vec![]; // 插入wsol diff --git a/src/lib.rs b/src/lib.rs index f0608e9..de2f9f7 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,34 +2,28 @@ pub mod common; pub mod constants; pub mod instruction; pub mod protos; -pub mod swqos; pub mod streaming; +pub mod swqos; pub mod trading; +pub mod utils; +use crate::swqos::SwqosConfig; +use crate::trading::core::params::BonkParams; +use crate::trading::core::params::PumpFunParams; +use crate::trading::core::params::PumpSwapParams; +use crate::trading::core::traits::ProtocolParams; +use crate::trading::factory::TradingProtocol; +use crate::trading::BuyParams; +use crate::trading::SellParams; +use crate::trading::TradeFactory; +use common::{PriorityFee, SolanaRpcClient, TradeConfig}; +use rustls::crypto::{ring::default_provider, CryptoProvider}; +use solana_sdk::hash::Hash; +use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use std::sync::Arc; use std::sync::Mutex; - -use rustls::crypto::{ring::default_provider, CryptoProvider}; -use solana_sdk::{ - pubkey::Pubkey, - signature::{Keypair, Signer}, -}; use swqos::SwqosClient; -use common::{PriorityFee, SolanaRpcClient, TradeConfig}; - -use constants::trade_type::COPY_BUY; - -use crate::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; -use crate::swqos::SwqosConfig; -use crate::trading::core::params::PumpFunParams; -use crate::trading::core::params::PumpFunSellParams; -use crate::trading::core::params::PumpSwapParams; -use crate::trading::core::params::BonkParams; -use crate::trading::BuyWithTipParams; -use crate::trading::SellParams; -use crate::trading::SellWithTipParams; - pub struct SolanaTrade { pub payer: Arc, pub rpc: Arc, @@ -107,11 +101,95 @@ impl SolanaTrade { .clone() } - pub async fn buy_use_buy_params( + /// Execute a buy order for a specified token + /// + /// # Arguments + /// + /// * `mint` - The public key of the token mint to buy + /// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None) + /// * `amount_sol` - Amount of SOL to spend on the purchase (in lamports) + /// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%) + /// * `recent_blockhash` - Recent blockhash for transaction validity + /// * `custom_buy_tip_fee` - Optional custom tip fee for priority processing (in SOL) + /// * `with_tip` - Whether to include tip for MEV protection and priority processing + /// * `protocol` - Trading protocol to use (PumpFun, PumpSwap, or Bonk) + /// * `protocol_params` - Optional protocol-specific parameters (uses defaults if None) + /// + /// # Returns + /// + /// Returns `Ok(())` if the buy order is successfully executed, or an error if the transaction fails. + /// + /// # Errors + /// + /// This function will return an error if: + /// - Invalid protocol parameters are provided + /// - The transaction fails to execute + /// - Network or RPC errors occur + /// - Insufficient SOL balance for the purchase + /// + /// # Example + /// + /// ```rust + /// use solana_sdk::pubkey::Pubkey; + /// use solana_sdk::hash::Hash; + /// use crate::trading::factory::TradingProtocol; + /// + /// let mint = Pubkey::new_unique(); + /// let amount_sol = 1_000_000_000; // 1 SOL in lamports + /// let slippage = Some(500); // 5% slippage + /// let recent_blockhash = Hash::default(); + /// + /// solana_trade.buy( + /// mint, + /// None, + /// amount_sol, + /// slippage, + /// recent_blockhash, + /// None, + /// true, + /// TradingProtocol::PumpFun, + /// None, + /// ).await?; + /// ``` + pub async fn buy( &self, - buy_params: BuyWithTipParams, + mint: Pubkey, + creator: Option, + amount_sol: u64, + slippage_basis_points: Option, + recent_blockhash: Hash, custom_buy_tip_fee: Option, + with_tip: bool, + protocol: TradingProtocol, + protocol_params: Option>, ) -> Result<(), anyhow::Error> { + let executor = TradeFactory::create_executor(protocol.clone()); + let protocol_params = if let Some(params) = protocol_params { + params + } else { + match protocol { + TradingProtocol::PumpFun => { + Box::new(PumpFunParams::default()) as Box + } + TradingProtocol::PumpSwap => { + Box::new(PumpSwapParams::default()) as Box + } + TradingProtocol::Bonk => Box::new(BonkParams::default()) as Box, + } + }; + let buy_params = BuyParams { + rpc: Some(self.rpc.clone()), + payer: self.payer.clone(), + mint: mint, + creator: creator.unwrap_or(Pubkey::default()), + amount_sol: amount_sol, + slippage_basis_points: slippage_basis_points, + priority_fee: self.trade_config.priority_fee.clone(), + lookup_table_key: self.trade_config.lookup_table_key, + recent_blockhash, + data_size_limit: 0, + protocol_params: protocol_params.clone(), + }; let mut priority_fee = buy_params.priority_fee.clone(); if custom_buy_tip_fee.is_some() { priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap(); @@ -122,85 +200,126 @@ impl SolanaTrade { custom_buy_tip_fee.unwrap(), ]; } - let mint = buy_params.mint; - let creator = buy_params.creator; - let buy_sol_cost = buy_params.amount_sol; - let slippage_basis_points = buy_params.slippage_basis_points; - let recent_blockhash = buy_params.recent_blockhash; - if let Some(protocol_params) = buy_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpfun::buy::buy( - self.rpc.clone(), - self.payer.clone(), - mint, - creator, - buy_sol_cost, - slippage_basis_points, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.bonding_curve.clone(), - COPY_BUY.to_string(), - ) - .await - } else if let Some(protocol_params) = buy_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpswap::buy::buy( - self.rpc.clone(), - self.payer.clone(), - mint, - creator, - buy_sol_cost, - slippage_basis_points, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.pool.clone(), - protocol_params.pool_base_token_account.clone(), - protocol_params.pool_quote_token_account.clone(), - protocol_params.user_base_token_account.clone(), - protocol_params.user_quote_token_account.clone(), - protocol_params.auto_handle_wsol, - ) - .await - } else if let Some(protocol_params) = buy_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::bonk::buy::buy( - self.rpc.clone(), - self.payer.clone(), - mint, - protocol_params.virtual_base.unwrap_or(0), - protocol_params.virtual_quote.unwrap_or(0), - protocol_params.real_base_before.unwrap_or(0), - protocol_params.real_quote_before.unwrap_or(0), - buy_sol_cost, - slippage_basis_points, - priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.auto_handle_wsol, - ) - .await - } else { + let buy_with_tip_params = buy_params.clone().with_tip(self.swqos_clients.clone()); + + // Validate protocol params + let is_valid_params = match protocol { + TradingProtocol::PumpFun => protocol_params + .as_any() + .downcast_ref::() + .is_some(), + TradingProtocol::PumpSwap => protocol_params + .as_any() + .downcast_ref::() + .is_some(), + TradingProtocol::Bonk => protocol_params + .as_any() + .downcast_ref::() + .is_some(), + }; + + if !is_valid_params { return Err(anyhow::anyhow!("Invalid protocol params for Trade")); } + + // Execute buy based on tip preference + if with_tip { + executor.buy_with_tip(buy_with_tip_params).await + } else { + executor.buy(buy_params).await + } } - pub async fn buy_with_tip_use_buy_params( + /// Execute a sell order for a specified token + /// + /// # Arguments + /// + /// * `mint` - The public key of the token mint to sell + /// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None) + /// * `amount_token` - Amount of tokens to sell (in smallest token units) + /// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%) + /// * `recent_blockhash` - Recent blockhash for transaction validity + /// * `custom_buy_tip_fee` - Optional custom tip fee for priority processing (in SOL) + /// * `with_tip` - Whether to include tip for MEV protection and priority processing + /// * `protocol` - Trading protocol to use (PumpFun, PumpSwap, or Bonk) + /// * `protocol_params` - Optional protocol-specific parameters (uses defaults if None) + /// + /// # Returns + /// + /// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails. + /// + /// # Errors + /// + /// This function will return an error if: + /// - Invalid protocol parameters are provided + /// - The transaction fails to execute + /// - Network or RPC errors occur + /// - Insufficient token balance for the sale + /// - Token account doesn't exist or is not properly initialized + /// + /// # Example + /// + /// ```rust + /// use solana_sdk::pubkey::Pubkey; + /// use solana_sdk::hash::Hash; + /// use crate::trading::factory::TradingProtocol; + /// + /// let mint = Pubkey::new_unique(); + /// let amount_token = 1_000_000; // Amount of tokens to sell + /// let slippage = Some(500); // 5% slippage + /// let recent_blockhash = Hash::default(); + /// + /// solana_trade.sell( + /// mint, + /// None, + /// amount_token, + /// slippage, + /// recent_blockhash, + /// None, + /// true, + /// TradingProtocol::PumpFun, + /// None, + /// ).await?; + /// ``` + pub async fn sell( &self, - buy_params: BuyWithTipParams, + mint: Pubkey, + creator: Option, + amount_token: u64, + slippage_basis_points: Option, + recent_blockhash: Hash, custom_buy_tip_fee: Option, + with_tip: bool, + protocol: TradingProtocol, + protocol_params: Option>, ) -> Result<(), anyhow::Error> { - let mut priority_fee = buy_params.priority_fee.clone(); + let executor = TradeFactory::create_executor(protocol.clone()); + let protocol_params = if let Some(params) = protocol_params { + params + } else { + match protocol { + TradingProtocol::PumpFun => { + Box::new(PumpFunParams::default()) as Box + } + TradingProtocol::PumpSwap => { + Box::new(PumpSwapParams::default()) as Box + } + TradingProtocol::Bonk => Box::new(BonkParams::default()) as Box, + } + }; + let sell_params = SellParams { + rpc: Some(self.rpc.clone()), + payer: self.payer.clone(), + mint: mint, + creator: creator.unwrap_or(Pubkey::default()), + amount_token: Some(amount_token), + slippage_basis_points: slippage_basis_points, + priority_fee: self.trade_config.priority_fee.clone(), + lookup_table_key: self.trade_config.lookup_table_key, + recent_blockhash, + protocol_params: protocol_params.clone(), + }; + let mut priority_fee = sell_params.priority_fee.clone(); if custom_buy_tip_fee.is_some() { priority_fee.buy_tip_fee = custom_buy_tip_fee.unwrap(); priority_fee.buy_tip_fees = vec![ @@ -210,516 +329,123 @@ impl SolanaTrade { custom_buy_tip_fee.unwrap(), ]; } - let mint = buy_params.mint; - let creator = buy_params.creator; - let buy_sol_cost = buy_params.amount_sol; - let slippage_basis_points = buy_params.slippage_basis_points; - let recent_blockhash = buy_params.recent_blockhash; - if let Some(protocol_params) = buy_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpfun::buy::buy_with_tip( - self.swqos_clients.clone(), - self.payer.clone(), - mint, - creator, - buy_sol_cost, - slippage_basis_points, - priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.bonding_curve.clone(), - COPY_BUY.to_string(), - ) - .await - } else if let Some(protocol_params) = buy_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpswap::buy::buy_with_tip( - self.rpc.clone(), - self.swqos_clients.clone(), - self.payer.clone(), - mint, - creator, - buy_sol_cost, - slippage_basis_points, - priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.pool.clone(), - protocol_params.pool_base_token_account.clone(), - protocol_params.pool_quote_token_account.clone(), - protocol_params.user_base_token_account.clone(), - protocol_params.user_quote_token_account.clone(), - protocol_params.auto_handle_wsol, - ) - .await - } else if let Some(protocol_params) = buy_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::bonk::buy::buy( - self.rpc.clone(), - self.payer.clone(), - mint, - protocol_params.virtual_base.unwrap_or(0), - protocol_params.virtual_quote.unwrap_or(0), - protocol_params.real_base_before.unwrap_or(0), - protocol_params.real_quote_before.unwrap_or(0), - buy_sol_cost, - slippage_basis_points, - priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.auto_handle_wsol, - ) - .await - } else { + let sell_with_tip_params = sell_params.clone().with_tip(self.swqos_clients.clone()); + + // Validate protocol params + let is_valid_params = match protocol { + TradingProtocol::PumpFun => protocol_params + .as_any() + .downcast_ref::() + .is_some(), + TradingProtocol::PumpSwap => protocol_params + .as_any() + .downcast_ref::() + .is_some(), + TradingProtocol::Bonk => protocol_params + .as_any() + .downcast_ref::() + .is_some(), + }; + + if !is_valid_params { return Err(anyhow::anyhow!("Invalid protocol params for Trade")); } + + // Execute sell based on tip preference + if with_tip { + executor.sell_with_tip(sell_with_tip_params).await + } else { + executor.sell(sell_params).await + } } - /// Sell tokens by percentage - pub async fn sell_by_percent_use_sell_params( + /// Execute a sell order for a percentage of the specified token amount + /// + /// This is a convenience function that calculates the exact amount to sell based on + /// a percentage of the total token amount and then calls the `sell` function. + /// + /// # Arguments + /// + /// * `mint` - The public key of the token mint to sell + /// * `creator` - Optional creator public key for the token (defaults to Pubkey::default() if None) + /// * `amount_token` - Total amount of tokens available (in smallest token units) + /// * `percent` - Percentage of tokens to sell (1-100, where 100 = 100%) + /// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%) + /// * `recent_blockhash` - Recent blockhash for transaction validity + /// * `custom_buy_tip_fee` - Optional custom tip fee for priority processing (in SOL) + /// * `with_tip` - Whether to include tip for MEV protection and priority processing + /// * `protocol` - Trading protocol to use (PumpFun, PumpSwap, or Bonk) + /// * `protocol_params` - Optional protocol-specific parameters (uses defaults if None) + /// + /// # Returns + /// + /// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails. + /// + /// # Errors + /// + /// This function will return an error if: + /// - `percent` is 0 or greater than 100 + /// - Invalid protocol parameters are provided + /// - The transaction fails to execute + /// - Network or RPC errors occur + /// - Insufficient token balance for the calculated sale amount + /// - Token account doesn't exist or is not properly initialized + /// + /// # Example + /// + /// ```rust + /// use solana_sdk::pubkey::Pubkey; + /// use solana_sdk::hash::Hash; + /// use crate::trading::factory::TradingProtocol; + /// + /// let mint = Pubkey::new_unique(); + /// let total_tokens = 10_000_000; // Total tokens available + /// let percent = 50; // Sell 50% of tokens + /// let slippage = Some(500); // 5% slippage + /// let recent_blockhash = Hash::default(); + /// + /// // This will sell 5_000_000 tokens (50% of 10_000_000) + /// solana_trade.sell_by_percent( + /// mint, + /// None, + /// total_tokens, + /// percent, + /// slippage, + /// recent_blockhash, + /// None, + /// true, + /// TradingProtocol::PumpFun, + /// None, + /// ).await?; + /// ``` + pub async fn sell_by_percent( &self, - sell_params: SellParams, + mint: Pubkey, + creator: Option, + amount_token: u64, percent: u64, + slippage_basis_points: Option, + recent_blockhash: Hash, + custom_buy_tip_fee: Option, + with_tip: bool, + protocol: TradingProtocol, + protocol_params: Option>, ) -> Result<(), anyhow::Error> { - let mint = sell_params.mint; - let creator = sell_params.creator; - let amount_token = sell_params.amount_token; - let recent_blockhash = sell_params.recent_blockhash; - if let Some(_) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpfun::sell::sell_by_percent( - self.rpc.clone(), - self.payer.clone(), - mint.clone(), - creator, - percent, - amount_token.unwrap_or(0), - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - ) - .await - } else if let Some(protocol_params) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpswap::sell::sell_by_percent( - self.rpc.clone(), - self.payer.clone(), - mint.clone(), - creator, - percent, - None, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.pool.clone(), - protocol_params.pool_base_token_account.clone(), - protocol_params.pool_quote_token_account.clone(), - protocol_params.user_base_token_account.clone(), - protocol_params.user_quote_token_account.clone(), - ) - .await - } else if let Some(protocol_params) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::bonk::sell::sell_by_percent( - self.rpc.clone(), - self.payer.clone(), - mint.clone(), - protocol_params.virtual_base.unwrap_or(0), - protocol_params.virtual_quote.unwrap_or(0), - protocol_params.real_base_before.unwrap_or(0), - protocol_params.real_quote_before.unwrap_or(0), - percent, - None, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - ) - .await - } else { - return Err(anyhow::anyhow!("Invalid protocol params for Trade")); + if percent == 0 || percent > 100 { + return Err(anyhow::anyhow!("Percentage must be between 1 and 100")); } - } - - /// Sell tokens by amount - pub async fn sell_by_amount_use_sell_params( - &self, - sell_params: SellParams, - ) -> Result<(), anyhow::Error> { - let mint = sell_params.mint; - let creator = sell_params.creator; - let amount = sell_params.amount_token; - let recent_blockhash = sell_params.recent_blockhash; - if let Some(_) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpfun::sell::sell_by_amount( - self.rpc.clone(), - self.payer.clone(), - mint.clone(), - creator, - amount.unwrap_or(0), - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - ) - .await - } else if let Some(protocol_params) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpswap::sell::sell_by_amount( - self.rpc.clone(), - self.payer.clone(), - mint.clone(), - creator, - amount.unwrap_or(0), - None, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.pool.clone(), - protocol_params.pool_base_token_account.clone(), - protocol_params.pool_quote_token_account.clone(), - protocol_params.user_base_token_account.clone(), - protocol_params.user_quote_token_account.clone(), - ) - .await - } else if let Some(protocol_params) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::bonk::sell::sell_by_amount( - self.rpc.clone(), - self.payer.clone(), - mint.clone(), - protocol_params.virtual_base.unwrap_or(0), - protocol_params.virtual_quote.unwrap_or(0), - protocol_params.real_base_before.unwrap_or(0), - protocol_params.real_quote_before.unwrap_or(0), - amount.unwrap_or(0), - None, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - ) - .await - } else { - Err(anyhow::anyhow!("Invalid protocol params for Trade")) - } - } - - pub async fn sell_by_percent_with_tip_use_sell_params( - &self, - sell_params: SellWithTipParams, - percent: u64, - ) -> Result<(), anyhow::Error> { - let mint = sell_params.mint; - let creator = sell_params.creator; - let amount_token = sell_params.amount_token; - let recent_blockhash = sell_params.recent_blockhash; - if let Some(_) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpfun::sell::sell_by_percent_with_tip( - self.rpc.clone(), - self.swqos_clients.clone(), - self.payer.clone(), - mint, - creator, - percent, - amount_token.unwrap_or(0), - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - ) - .await - } else if let Some(protocol_params) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpswap::sell::sell_by_percent_with_tip( - self.rpc.clone(), - self.swqos_clients.clone(), - self.payer.clone(), - mint, - creator, - percent, - sell_params.slippage_basis_points, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.pool.clone(), - protocol_params.pool_base_token_account.clone(), - protocol_params.pool_quote_token_account.clone(), - protocol_params.user_base_token_account.clone(), - protocol_params.user_quote_token_account.clone(), - ) - .await - } else if let Some(protocol_params) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::bonk::sell::sell_by_percent_with_tip( - self.rpc.clone(), - self.swqos_clients.clone(), - self.payer.clone(), - mint, - protocol_params.virtual_base.unwrap_or(0), - protocol_params.virtual_quote.unwrap_or(0), - protocol_params.real_base_before.unwrap_or(0), - protocol_params.real_quote_before.unwrap_or(0), - percent, - sell_params.slippage_basis_points, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - ) - .await - } else { - Err(anyhow::anyhow!("Invalid protocol params for Trade")) - } - } - - pub async fn sell_by_amount_with_tip_use_sell_params( - &self, - sell_params: SellWithTipParams, - ) -> Result<(), anyhow::Error> { - let mint = sell_params.mint; - let creator = sell_params.creator; - let amount = sell_params.amount_token; - let recent_blockhash = sell_params.recent_blockhash; - if let Some(_) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpfun::sell::sell_by_amount_with_tip( - self.rpc.clone(), - self.swqos_clients.clone(), - self.payer.clone(), - mint, - creator, - amount.unwrap_or(0), - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - ) - .await - } else if let Some(protocol_params) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::pumpswap::sell::sell_by_amount_with_tip( - self.rpc.clone(), - self.swqos_clients.clone(), - self.payer.clone(), - mint, - creator, - amount.unwrap_or(0), - sell_params.slippage_basis_points, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - protocol_params.pool.clone(), - protocol_params.pool_base_token_account.clone(), - protocol_params.pool_quote_token_account.clone(), - protocol_params.user_base_token_account.clone(), - protocol_params.user_quote_token_account.clone(), - ) - .await - } else if let Some(protocol_params) = sell_params - .protocol_params - .as_any() - .downcast_ref::() - { - trading::bonk::sell::sell_by_amount_with_tip( - self.rpc.clone(), - self.swqos_clients.clone(), - self.payer.clone(), - mint, - protocol_params.virtual_base.unwrap_or(0), - protocol_params.virtual_quote.unwrap_or(0), - protocol_params.real_base_before.unwrap_or(0), - protocol_params.real_quote_before.unwrap_or(0), - amount.unwrap_or(0), - sell_params.slippage_basis_points, - self.priority_fee.clone(), - self.trade_config.lookup_table_key, - recent_blockhash, - ) - .await - } else { - Err(anyhow::anyhow!("Invalid protocol params for Trade")) - } - } - - #[inline] - pub async fn get_sol_balance(&self, payer: &Pubkey) -> Result { - trading::pumpfun::common::get_sol_balance(&self.rpc, payer).await - } - - #[inline] - pub async fn get_payer_sol_balance(&self) -> Result { - trading::pumpfun::common::get_sol_balance(&self.rpc, &self.payer.pubkey()).await - } - - #[inline] - pub async fn get_token_balance( - &self, - payer: &Pubkey, - mint: &Pubkey, - ) -> Result { - println!( - "get_token_balance payer: {}, mint: {}, rpc_url: {}", - payer, mint, self.trade_config.rpc_url - ); - trading::pumpfun::common::get_token_balance(&self.rpc, payer, mint).await - } - - #[inline] - pub async fn get_payer_token_balance(&self, mint: &Pubkey) -> Result { - trading::pumpfun::common::get_token_balance(&self.rpc, &self.payer.pubkey(), mint).await - } - - #[inline] - pub fn get_payer_pubkey(&self) -> Pubkey { - self.payer.pubkey() - } - - #[inline] - pub fn get_payer(&self) -> &Keypair { - self.payer.as_ref() - } - - #[inline] - pub fn get_token_price(&self, virtual_sol_reserves: u64, virtual_token_reserves: u64) -> f64 { - trading::pumpfun::common::get_token_price(virtual_sol_reserves, virtual_token_reserves) - } - - #[inline] - pub fn get_buy_price(&self, amount: u64, trade_info: &PumpFunTradeEvent) -> u64 { - trading::pumpfun::common::get_buy_price(amount, trade_info) - } - - #[inline] - pub async fn transfer_sol( - &self, - payer: &Keypair, - receive_wallet: &Pubkey, - amount: u64, - ) -> Result<(), anyhow::Error> { - trading::pumpfun::common::transfer_sol(&self.rpc, payer, receive_wallet, amount).await - } - - #[inline] - pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> { - trading::pumpfun::common::close_token_account(&self.rpc, self.payer.as_ref(), mint).await - } - - #[inline] - pub async fn get_current_price(&self, mint: &Pubkey) -> Result { - let (bonding_curve, _) = - trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?; - - let virtual_sol_reserves = bonding_curve.virtual_sol_reserves; - let virtual_token_reserves = bonding_curve.virtual_token_reserves; - - Ok(trading::pumpfun::common::get_token_price( - virtual_sol_reserves, - virtual_token_reserves, - )) - } - - #[inline] - pub async fn get_real_sol_reserves(&self, mint: &Pubkey) -> Result { - let (bonding_curve, _) = - trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?; - - let actual_sol_reserves = bonding_curve.real_sol_reserves; - - Ok(actual_sol_reserves) - } - - #[inline] - pub async fn get_creator(&self, mint: &Pubkey) -> Result { - let (bonding_curve, _) = - trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?; - - let creator = bonding_curve.creator; - - Ok(creator) - } - - #[inline] - pub async fn get_current_price_with_pumpswap( - &self, - pool_address: &Pubkey, - ) -> Result { - let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?; - - let (base_amount, quote_amount) = pool.get_token_balances(&self.rpc).await?; - - // Calculate price using constant product formula (x * y = k) - // Price = quote_amount / base_amount - if base_amount == 0 { - return Err(anyhow::anyhow!( - "Base amount is zero, cannot calculate price" - )); - } - - let price = quote_amount as f64 / base_amount as f64; - - Ok(price) - } - - #[inline] - pub async fn get_real_sol_reserves_with_pumpswap( - &self, - pool_address: &Pubkey, - ) -> Result { - let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?; - - let (_, quote_amount) = pool.get_token_balances(&self.rpc).await?; - - Ok(quote_amount) - } - - #[inline] - pub async fn get_payer_token_balance_with_pumpswap( - &self, - pool_address: &Pubkey, - ) -> Result { - let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?; - - let (base_amount, _) = pool.get_token_balances(&self.rpc).await?; - - Ok(base_amount) + let amount = amount_token * percent / 100; + self.sell( + mint, + creator, + amount, + slippage_basis_points, + recent_blockhash, + custom_buy_tip_fee, + with_tip, + protocol, + protocol_params, + ) + .await } } diff --git a/src/main.rs b/src/main.rs index ccdefe9..d096338 100755 --- a/src/main.rs +++ b/src/main.rs @@ -2,40 +2,50 @@ use std::{str::FromStr, sync::Arc}; use sol_trade_sdk::{ common::{bonding_curve::BondingCurveAccount, AnyResult, PriorityFee, TradeConfig}, - constants::{pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, trade_type}, - streaming::event_parser::{ - protocols::{ - bonk::{BonkPoolCreateEvent, BonkTradeEvent}, pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, pumpswap::{ - PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, PumpSwapSellEvent, - PumpSwapWithdrawEvent, - } - }, - Protocol, UnifiedEvent, - }, - streaming::{ShredStreamGrpc, YellowstoneGrpc}, + constants::pumpfun::global_constants::TOKEN_TOTAL_SUPPLY, match_event, + streaming::{ + event_parser::{ + protocols::{ + bonk::{BonkPoolCreateEvent, BonkTradeEvent}, + pumpfun::{PumpFunCreateTokenEvent, PumpFunTradeEvent}, + pumpswap::{ + PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent, + PumpSwapSellEvent, PumpSwapWithdrawEvent, + }, + }, + Protocol, UnifiedEvent, + }, + ShredStreamGrpc, YellowstoneGrpc, + }, swqos::{SwqosConfig, SwqosRegion}, trading::{ - core::params::{BonkParams, PumpFunParams, PumpFunSellParams, PumpSwapParams}, pumpfun::common::get_bonding_curve_account_v2, BuyParams, SellParams + core::params::PumpFunParams, factory::TradingProtocol, + pumpfun::common::get_bonding_curve_account_v2, }, SolanaTrade, }; -use solana_client::rpc_client::RpcClient; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; #[tokio::main] async fn main() -> Result<(), Box> { + test_create_solana_trade_client().await?; test_pumpfun().await?; - // test_pumpswap().await?; - // test_bonk().await?; - // test_grpc().await?; - // test_shreds().await?; + test_pumpswap().await?; + test_bonk().await?; + test_grpc().await?; + test_shreds().await?; Ok(()) } -async fn test_pumpfun() -> AnyResult<()> { +/// 创建 SolanaTrade 客户端的示例 +async fn test_create_solana_trade_client() -> AnyResult { + println!("Creating SolanaTrade client..."); + let payer = Keypair::new(); let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); + + // 配置各种 SWQOS 服务 let swqos_configs = vec![ SwqosConfig::Jito(SwqosRegion::Frankfurt), SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt), @@ -44,7 +54,8 @@ async fn test_pumpfun() -> AnyResult<()> { SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Default(rpc_url.clone()), ]; - // Define cluster configuration + + // 定义交易配置 let trade_config = TradeConfig { rpc_url: rpc_url.clone(), commitment: CommitmentConfig::confirmed(), @@ -52,13 +63,23 @@ async fn test_pumpfun() -> AnyResult<()> { swqos_configs, lookup_table_key: None, }; + let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + println!("SolanaTrade client created successfully!"); + + Ok(solana_trade_client) +} + +async fn test_pumpfun() -> AnyResult<()> { + println!("Testing PumpFun trading..."); + + let solana_trade_client = test_create_solana_trade_client().await?; let creator = Pubkey::from_str("xxxxxx")?; // dev account let buy_sol_cost = 100_000; // 0.0001 SOL let slippage_basis_points = Some(100); - let rpc = RpcClient::new(rpc_url); - let recent_blockhash = rpc.get_latest_blockhash().unwrap(); + let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; let mint_pubkey = Pubkey::from_str("xxxxxx")?; // token mint + println!("Buying tokens from PumpFun..."); // get bonding curve let (bonding_curve, bonding_curve_pda) = @@ -82,195 +103,123 @@ async fn test_pumpfun() -> AnyResult<()> { // let bonding_curve = // BondingCurveAccount::new(&mint_pubkey, dev_buy_token, dev_cost_sol, creator); // buy - let buy_protocol_params = PumpFunParams { - trade_type: trade_type::COPY_BUY.to_string(), - bonding_curve: Some(Arc::new(bonding_curve)), - }; - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_sol: buy_sol_cost, - slippage_basis_points: slippage_basis_points, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(buy_protocol_params.clone()), - }; - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpFun, + Some(Box::new(PumpFunParams { + bonding_curve: Some(Arc::new(bonding_curve.clone())), + })), + ) .await?; // sell println!("Selling tokens from PumpFun..."); - let sell_protocol_params = PumpFunSellParams {}; let amount_token = 0; // 写上真实的amount_token - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_token: Some(amount_token), - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(sell_protocol_params.clone()), - }; solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + Some(creator), + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpFun, + None, + ) .await?; Ok(()) } async fn test_pumpswap() -> AnyResult<()> { - let payer = Keypair::new(); - let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); - let swqos_configs = vec![ - SwqosConfig::Jito(SwqosRegion::Frankfurt), - SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Default(rpc_url.clone()), - ]; - // Define cluster configuration - let trade_config = TradeConfig { - rpc_url: rpc_url.clone(), - commitment: CommitmentConfig::confirmed(), - priority_fee: PriorityFee::default(), - swqos_configs, - lookup_table_key: None, - }; - let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + println!("Testing PumpSwap trading..."); + + let solana_trade_client = test_create_solana_trade_client().await?; let creator = Pubkey::from_str("11111111111111111111111111111111")?; // dev account let buy_sol_cost = 100_000; // 0.0001 SOL let slippage_basis_points = Some(100); - let rpc = RpcClient::new(rpc_url); - let recent_blockhash = rpc.get_latest_blockhash().unwrap(); + let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; // token mint + println!("Buying tokens from PumpSwap..."); // buy - let protocol_params = PumpSwapParams { - pool: None, - pool_base_token_account: None, - pool_quote_token_account: None, - user_base_token_account: None, - user_quote_token_account: None, - auto_handle_wsol: true, - }; - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_sol: buy_sol_cost, - slippage_basis_points: slippage_basis_points, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(protocol_params.clone()), - }; - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + Some(creator), + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpSwap, + None, + ) .await?; // sell println!("Selling tokens from PumpSwap..."); let amount_token = 0; // 写上真实的amount_token - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint_pubkey, - creator: creator, - amount_token: Some(amount_token), - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(protocol_params.clone()), - }; solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + Some(creator), + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::PumpSwap, + None, + ) .await?; Ok(()) } async fn test_bonk() -> Result<(), Box> { - // 创建一个随机账户作为交易者 - let payer = Keypair::new(); - let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); - let swqos_configs = vec![ - SwqosConfig::Jito(SwqosRegion::Frankfurt), - SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), - SwqosConfig::Default(rpc_url.clone()), - ]; - // Define cluster configuration - let trade_config = TradeConfig { - rpc_url: rpc_url.clone(), - commitment: CommitmentConfig::confirmed(), - priority_fee: PriorityFee::default(), - swqos_configs, - lookup_table_key: None, - }; - let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; - let amount = 100_000; // 0.0001 SOL + println!("Testing Bonk trading..."); + + let solana_trade_client = test_create_solana_trade_client().await?; + let buy_sol_cost = 100_000; // 0.0001 SOL + let slippage_basis_points = Some(100); // 1% let recent_blockhash = solana_trade_client.rpc.get_latest_blockhash().await?; - let mint = Pubkey::from_str("xxxxxxx")?; - let bonk_params = BonkParams { - virtual_base: None, - virtual_quote: None, - real_base_before: None, - real_quote_before: None, - auto_handle_wsol: true, - }; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; + println!("Buying tokens from letsbonk.fun..."); // buy - let buy_params = BuyParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint, - creator: Pubkey::default(), - amount_sol: amount, - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - data_size_limit: 0, - protocol_params: Box::new(bonk_params.clone()), - }; - let buy_with_tip_params = buy_params - .clone() - .with_tip(solana_trade_client.swqos_clients.clone()); solana_trade_client - .buy_use_buy_params(buy_with_tip_params, None) + .buy( + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::Bonk, + None, + ) .await?; // sell println!("Selling tokens from letsbonk.fun..."); - let sell_params = SellParams { - rpc: Some(solana_trade_client.rpc.clone()), - payer: solana_trade_client.payer.clone(), - mint: mint, - creator: Pubkey::default(), - amount_token: None, - slippage_basis_points: None, - priority_fee: solana_trade_client.trade_config.clone().priority_fee, - lookup_table_key: solana_trade_client.trade_config.clone().lookup_table_key, - recent_blockhash, - protocol_params: Box::new(bonk_params.clone()), - }; + let amount_token = 0; // 写上真实的amount_token solana_trade_client - .sell_by_amount_use_sell_params(sell_params) + .sell( + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + false, + TradingProtocol::Bonk, + None, + ) .await?; Ok(()) } @@ -319,11 +268,7 @@ async fn test_grpc() -> Result<(), Box> { // 订阅 PumpSwap 事件 println!("开始监听事件,按 Ctrl+C 停止..."); - let protocols = vec![ - Protocol::PumpFun, - Protocol::PumpSwap, - Protocol::Bonk, - ]; + let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk]; grpc.subscribe_events(protocols, None, None, None, callback) .await?; @@ -371,11 +316,7 @@ async fn test_shreds() -> Result<(), Box> { // 订阅 PumpSwap 事件 println!("开始监听事件,按 Ctrl+C 停止..."); - let protocols = vec![ - Protocol::PumpFun, - Protocol::PumpSwap, - Protocol::Bonk, - ]; + let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk]; shred_stream .shredstream_subscribe(protocols, None, callback) .await?; diff --git a/src/streaming/event_parser/protocols/bonk/events.rs b/src/streaming/event_parser/protocols/bonk/events.rs index 83ab491..93cea47 100755 --- a/src/streaming/event_parser/protocols/bonk/events.rs +++ b/src/streaming/event_parser/protocols/bonk/events.rs @@ -1,7 +1,7 @@ use crate::streaming::event_parser::protocols::bonk::types::{ CurveParams, MintParams, PoolStatus, TradeDirection, VestingParams, }; -use crate::streaming::event_parser::{common::EventMetadata, core::traits::UnifiedEvent}; +use crate::streaming::event_parser::common::EventMetadata; use crate::impl_unified_event; use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; diff --git a/src/streaming/event_parser/protocols/pumpfun/events.rs b/src/streaming/event_parser/protocols/pumpfun/events.rs index 34be579..99c04ac 100755 --- a/src/streaming/event_parser/protocols/pumpfun/events.rs +++ b/src/streaming/event_parser/protocols/pumpfun/events.rs @@ -2,7 +2,7 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::event_parser::{common::EventMetadata, core::traits::UnifiedEvent}; +use crate::streaming::event_parser::common::EventMetadata; use crate::impl_unified_event; #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] diff --git a/src/streaming/event_parser/protocols/pumpfun/parser.rs b/src/streaming/event_parser/protocols/pumpfun/parser.rs index 3fcb70e..4a19b7e 100755 --- a/src/streaming/event_parser/protocols/pumpfun/parser.rs +++ b/src/streaming/event_parser/protocols/pumpfun/parser.rs @@ -2,7 +2,7 @@ use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; use solana_transaction_status::UiCompiledInstruction; use crate::streaming::event_parser::{ - common::{utils::*, EventMetadata, EventType, ProtocolType}, + common::{EventMetadata, EventType, ProtocolType}, core::traits::{EventParser, GenericEventParseConfig, GenericEventParser, UnifiedEvent}, protocols::pumpfun::{discriminators, PumpFunCreateTokenEvent, PumpFunTradeEvent}, }; diff --git a/src/streaming/event_parser/protocols/pumpswap/events.rs b/src/streaming/event_parser/protocols/pumpswap/events.rs index b20d7ef..f1447df 100755 --- a/src/streaming/event_parser/protocols/pumpswap/events.rs +++ b/src/streaming/event_parser/protocols/pumpswap/events.rs @@ -2,7 +2,7 @@ use borsh::BorshDeserialize; use serde::{Deserialize, Serialize}; use solana_sdk::pubkey::Pubkey; -use crate::streaming::event_parser::{common::EventMetadata, core::traits::UnifiedEvent}; +use crate::streaming::event_parser::common::EventMetadata; use crate::impl_unified_event; /// 买入事件 diff --git a/src/streaming/yellowstone_grpc.rs b/src/streaming/yellowstone_grpc.rs index 0290fca..909aebc 100755 --- a/src/streaming/yellowstone_grpc.rs +++ b/src/streaming/yellowstone_grpc.rs @@ -4,10 +4,8 @@ use chrono::Local; use futures::{channel::mpsc, sink::Sink, SinkExt, Stream, StreamExt}; use log::{error, info}; use rustls::crypto::{ring::default_provider, CryptoProvider}; -use solana_sdk::{pubkey, pubkey::Pubkey, signature::Signature}; -use solana_transaction_status::{ - option_serializer::OptionSerializer, EncodedTransactionWithStatusMeta, UiTransactionEncoding, -}; +use solana_sdk::{pubkey::Pubkey, signature::Signature}; +use solana_transaction_status::{EncodedTransactionWithStatusMeta, UiTransactionEncoding}; use tonic::{transport::channel::ClientTlsConfig, Status}; use yellowstone_grpc_client::{GeyserGrpcClient, Interceptor}; use yellowstone_grpc_proto::geyser::{ @@ -21,8 +19,6 @@ use crate::streaming::event_parser::{EventParserFactory, Protocol, UnifiedEvent} type TransactionsFilterMap = HashMap; -const PUMP_PROGRAM_ID: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); -const SYSTEM_PROGRAM_ID: Pubkey = pubkey!("11111111111111111111111111111111"); const CONNECT_TIMEOUT: u64 = 10; const REQUEST_TIMEOUT: u64 = 60; const CHANNEL_SIZE: usize = 1000; diff --git a/src/swqos/temporal.rs b/src/swqos/temporal.rs index b6de6c8..2cb741e 100755 --- a/src/swqos/temporal.rs +++ b/src/swqos/temporal.rs @@ -4,9 +4,6 @@ use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; use std::{sync::Arc, time::Instant}; - -use solana_sdk::{signature::Signature}; - use std::time::Duration; use solana_transaction_status::UiTransactionEncoding; diff --git a/src/trading/bonk/buy.rs b/src/trading/bonk/buy.rs deleted file mode 100755 index 4a992f8..0000000 --- a/src/trading/bonk/buy.rs +++ /dev/null @@ -1,107 +0,0 @@ -use solana_hash::Hash; -use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use std::sync::Arc; - -use crate::swqos::SwqosClient; -use crate::trading::{ - core::params::{PumpSwapParams, BonkParams}, - factory::Protocol, - BuyParams, TradeFactory, -}; -use crate::{common::PriorityFee, SolanaRpcClient}; - -// Constants for compute budget -// Increased from 64KB to 256KB to handle larger transactions -const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024; - -// Buy tokens from a Pumpswap pool -pub async fn buy( - rpc: Arc, - payer: Arc, - mint: Pubkey, - virtual_base: u128, - virtual_quote: u128, - real_base_before: u128, - real_quote_before: u128, - amount_sol: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - auto_handle_wsol: bool, -) -> Result<(), anyhow::Error> { - // 创建执行器 - let executor = TradeFactory::create_executor(Protocol::Bonk); - // 创建协议特定参数 - let protocol_params = Box::new(BonkParams { - auto_handle_wsol: auto_handle_wsol, - virtual_base: Some(virtual_base), - virtual_quote: Some(virtual_quote), - real_base_before: Some(real_base_before), - real_quote_before: Some(real_quote_before), - }); - // 创建买入参数 - let buy_params = BuyParams { - rpc: Some(rpc.clone()), - payer: payer, - mint: mint, - creator: Pubkey::default(), - amount_sol: amount_sol, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee, - lookup_table_key: lookup_table_key, - recent_blockhash: recent_blockhash, - data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, - protocol_params, - }; - // 执行买入 - executor.buy(buy_params).await?; - Ok(()) -} - -// Buy tokens using a MEV service -pub async fn buy_with_tip( - rpc: Arc, - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - virtual_base: u128, - virtual_quote: u128, - real_base_before: u128, - real_quote_before: u128, - amount_sol: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - auto_handle_wsol: bool, -) -> Result<(), anyhow::Error> { - // 创建执行器 - let executor = TradeFactory::create_executor(Protocol::Bonk); - // 创建协议特定参数 - let protocol_params = Box::new(BonkParams { - auto_handle_wsol: auto_handle_wsol, - virtual_base: Some(virtual_base), - virtual_quote: Some(virtual_quote), - real_base_before: Some(real_base_before), - real_quote_before: Some(real_quote_before), - }); - // 创建买入参数 - let buy_params = BuyParams { - rpc: Some(rpc.clone()), - payer: payer, - mint: mint, - creator: Pubkey::default(), - amount_sol: amount_sol, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee, - lookup_table_key: lookup_table_key, - recent_blockhash: recent_blockhash, - data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, - protocol_params, - }; - let buy_with_tip_params = buy_params.with_tip(swqos_clients); - // 执行买入 - executor.buy_with_tip(buy_with_tip_params).await?; - Ok(()) -} diff --git a/src/trading/bonk/common.rs b/src/trading/bonk/common.rs index 70203d8..9440a0a 100755 --- a/src/trading/bonk/common.rs +++ b/src/trading/bonk/common.rs @@ -1,8 +1,5 @@ -use anyhow::anyhow; use solana_sdk::pubkey::Pubkey; -use spl_associated_token_account::get_associated_token_address; - -use crate::{common::SolanaRpcClient, constants}; +use crate::constants; pub fn get_amount_out( amount_in: u64, @@ -56,20 +53,4 @@ pub fn get_vault_pda(pool_state: &Pubkey, mint: &Pubkey) -> Option { let program_id: &Pubkey = &constants::bonk::accounts::BONK; let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); pda.map(|pubkey| pubkey.0) -} - -pub async fn get_token_balance( - rpc: &SolanaRpcClient, - payer: &Pubkey, - mint: &Pubkey, -) -> Result { - println!("payer: {:?}", payer); - println!("mint: {:?}", mint); - let ata = get_associated_token_address(payer, mint); - let balance = rpc.get_token_account_balance(&ata).await?; - let balance_u64 = balance - .amount - .parse::() - .map_err(|_| anyhow!("Failed to parse token balance"))?; - Ok(balance_u64) -} +} \ No newline at end of file diff --git a/src/trading/bonk/mod.rs b/src/trading/bonk/mod.rs index 9c60348..53868a7 100755 --- a/src/trading/bonk/mod.rs +++ b/src/trading/bonk/mod.rs @@ -1,4 +1,2 @@ -pub mod buy; -pub mod sell; pub mod common; pub mod pool; diff --git a/src/trading/bonk/sell.rs b/src/trading/bonk/sell.rs deleted file mode 100755 index 3f519fb..0000000 --- a/src/trading/bonk/sell.rs +++ /dev/null @@ -1,241 +0,0 @@ -use anyhow::anyhow; -use solana_hash::Hash; -use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use solana_sdk::signature::Signer; -use std::sync::Arc; - -use crate::common::{PriorityFee, SolanaRpcClient}; -use crate::trading::bonk::common::get_token_balance; -use crate::swqos::SwqosClient; -use crate::trading::{ - core::params::BonkParams, factory::Protocol, SellParams, TradeFactory, -}; - -// Sell tokens to a Pumpswap pool -pub async fn sell( - rpc: Arc, - payer: Arc, - mint: Pubkey, - virtual_base: u128, - virtual_quote: u128, - real_base_before: u128, - real_quote_before: u128, - amount_token: Option, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - let executor = TradeFactory::create_executor(Protocol::Bonk); - // 创建PumpFun协议参数 - let protocol_params = Box::new(BonkParams { - virtual_base: Some(virtual_base), - virtual_quote: Some(virtual_quote), - real_base_before: Some(real_base_before), - real_quote_before: Some(real_quote_before), - auto_handle_wsol: true, - }); - // 创建卖出参数 - let sell_params = SellParams { - rpc: Some(rpc.clone()), - payer: payer.clone(), - mint, - creator: Pubkey::default(), - amount_token: amount_token, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee.clone(), - lookup_table_key, - recent_blockhash, - protocol_params, - }; - // 执行卖出交易 - executor.sell(sell_params).await?; - Ok(()) -} - -// Sell tokens by percentage -pub async fn sell_by_percent( - rpc: Arc, - payer: Arc, - mint: Pubkey, - virtual_base: u128, - virtual_quote: u128, - real_base_before: u128, - real_quote_before: u128, - percent: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - if percent == 0 || percent > 100 { - return Err(anyhow!("Percentage must be between 1 and 100")); - } - let balance_u64 = get_token_balance(&rpc, &payer.pubkey(), &mint).await?; - let amount = balance_u64 * percent / 100; - sell( - rpc, - payer, - mint, - virtual_base, - virtual_quote, - real_base_before, - real_quote_before, - Some(amount), - slippage_basis_points, - priority_fee, - lookup_table_key, - recent_blockhash, - ) - .await -} - -/// Sell tokens by amount -pub async fn sell_by_amount( - rpc: Arc, - payer: Arc, - mint: Pubkey, - virtual_base: u128, - virtual_quote: u128, - real_base_before: u128, - real_quote_before: u128, - amount: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - sell( - rpc, - payer, - mint, - virtual_base, - virtual_quote, - real_base_before, - real_quote_before, - Some(amount), - slippage_basis_points, - priority_fee, - lookup_table_key, - recent_blockhash, - ) - .await -} - -// Sell tokens using a MEV service -pub async fn sell_with_tip( - rpc: Arc, - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - virtual_base: u128, - virtual_quote: u128, - real_base_before: u128, - real_quote_before: u128, - amount_token: Option, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - let executor = TradeFactory::create_executor(Protocol::Bonk); - // 创建PumpFun协议参数 - let protocol_params = Box::new(BonkParams { - virtual_base: Some(virtual_base), - virtual_quote: Some(virtual_quote), - real_base_before: Some(real_base_before), - real_quote_before: Some(real_quote_before), - auto_handle_wsol: true, - }); - // 创建卖出参数 - let sell_params = SellParams { - rpc: Some(rpc.clone()), - payer: payer.clone(), - mint, - creator: Pubkey::default(), - amount_token: amount_token, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee.clone(), - lookup_table_key, - recent_blockhash, - protocol_params, - }; - let sell_with_tip_params = sell_params.with_tip(swqos_clients); - // 执行卖出交易 - executor.sell_with_tip(sell_with_tip_params).await?; - Ok(()) -} - -// Sell tokens by percentage using a MEV service -pub async fn sell_by_percent_with_tip( - rpc: Arc, - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - virtual_base: u128, - virtual_quote: u128, - real_base_before: u128, - real_quote_before: u128, - percent: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - if percent == 0 || percent > 100 { - return Err(anyhow!("Percentage must be between 1 and 100")); - } - - let balance_u64 = get_token_balance(&rpc, &payer.pubkey(), &mint).await?; - let amount = balance_u64 * percent / 100; - sell_with_tip( - rpc, - swqos_clients, - payer, - mint, - virtual_base, - virtual_quote, - real_base_before, - real_quote_before, - Some(amount), - slippage_basis_points, - priority_fee, - lookup_table_key, - recent_blockhash, - ) - .await -} - -// Sell tokens by amount using a MEV service -pub async fn sell_by_amount_with_tip( - rpc: Arc, - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - virtual_base: u128, - virtual_quote: u128, - real_base_before: u128, - real_quote_before: u128, - amount: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - sell_with_tip( - rpc, - swqos_clients, - payer, - mint, - virtual_base, - virtual_quote, - real_base_before, - real_quote_before, - Some(amount), - slippage_basis_points, - priority_fee, - lookup_table_key, - recent_blockhash, - ) - .await -} diff --git a/src/trading/common/mod.rs b/src/trading/common/mod.rs index 8100de8..b1c8777 100755 --- a/src/trading/common/mod.rs +++ b/src/trading/common/mod.rs @@ -2,9 +2,11 @@ pub mod nonce_manager; pub mod transaction_builder; pub mod compute_budget_manager; pub mod address_lookup_manager; +pub mod utils; // Re-export commonly used functions pub use nonce_manager::*; pub use transaction_builder::*; pub use compute_budget_manager::*; -pub use address_lookup_manager::*; \ No newline at end of file +pub use address_lookup_manager::*; +pub use utils::*; \ No newline at end of file diff --git a/src/trading/common/transaction_builder.rs b/src/trading/common/transaction_builder.rs index 2bc9745..1c454df 100755 --- a/src/trading/common/transaction_builder.rs +++ b/src/trading/common/transaction_builder.rs @@ -1,4 +1,3 @@ -use anyhow::anyhow; use solana_hash::Hash; use solana_sdk::{ instruction::Instruction, diff --git a/src/trading/common/utils.rs b/src/trading/common/utils.rs new file mode 100644 index 0000000..0362859 --- /dev/null +++ b/src/trading/common/utils.rs @@ -0,0 +1,134 @@ +use solana_sdk::{ + pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, + transaction::Transaction, +}; +use spl_associated_token_account::get_associated_token_address; +use spl_token::instruction::close_account; + +use crate::common::SolanaRpcClient; +use anyhow::anyhow; + +#[inline] +pub async fn get_token_balance( + rpc: &SolanaRpcClient, + payer: &Pubkey, + mint: &Pubkey, +) -> Result { + println!("payer: {:?}", payer); + println!("mint: {:?}", mint); + let ata = get_associated_token_address(payer, mint); + let balance = rpc.get_token_account_balance(&ata).await?; + let balance_u64 = balance + .amount + .parse::() + .map_err(|_| anyhow!("Failed to parse token balance"))?; + Ok(balance_u64) +} + +#[inline] +pub async fn get_sol_balance( + rpc: &SolanaRpcClient, + account: &Pubkey, +) -> Result { + let balance = rpc.get_balance(account).await?; + Ok(balance) +} + +// Calculate slippage for buy operations +#[inline] +pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 { + amount + (amount * basis_points / 10000) +} + +// Calculate slippage for sell operations +#[inline] +pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 { + if amount <= basis_points / 10000 { + 1 + } else { + amount - (amount * basis_points / 10000) + } +} + +pub async fn transfer_sol( + rpc: &SolanaRpcClient, + payer: &Keypair, + receive_wallet: &Pubkey, + amount: u64, +) -> Result<(), anyhow::Error> { + if amount == 0 { + return Err(anyhow!("transfer_sol: Amount cannot be zero")); + } + + let balance = get_sol_balance(rpc, &payer.pubkey()).await?; + if balance < amount { + return Err(anyhow!("Insufficient balance")); + } + + let transfer_instruction = + system_instruction::transfer(&payer.pubkey(), receive_wallet, amount); + + let recent_blockhash = rpc.get_latest_blockhash().await?; + + let transaction = Transaction::new_signed_with_payer( + &[transfer_instruction], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + + rpc.send_and_confirm_transaction(&transaction).await?; + + Ok(()) +} + +/// 关闭代币账户 +/// +/// 此函数用于关闭指定代币的关联代币账户,将账户中的代币余额转移给账户所有者。 +/// +/// # 参数 +/// +/// * `rpc` - Solana RPC客户端 +/// * `payer` - 支付交易费用的账户 +/// * `mint` - 代币的Mint地址 +/// +/// # 返回值 +/// +/// 返回一个Result,成功时返回(),失败时返回错误 +pub async fn close_token_account( + rpc: &SolanaRpcClient, + payer: &Keypair, + mint: &Pubkey, +) -> Result<(), anyhow::Error> { + // 获取关联代币账户地址 + let ata = get_associated_token_address(&payer.pubkey(), mint); + + // 检查账户是否存在 + let account_exists = rpc.get_account(&ata).await.is_ok(); + if !account_exists { + return Ok(()); // 如果账户不存在,直接返回成功 + } + + // 构建关闭账户指令 + let close_account_ix = close_account( + &spl_token::ID, + &ata, + &payer.pubkey(), + &payer.pubkey(), + &[&payer.pubkey()], + )?; + + // 构建交易 + let recent_blockhash = rpc.get_latest_blockhash().await?; + let transaction = Transaction::new_signed_with_payer( + &[close_account_ix], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + + // 发送交易 + rpc.send_and_confirm_transaction(&transaction).await?; + + Ok(()) +} diff --git a/src/trading/core/executor.rs b/src/trading/core/executor.rs index c2130f6..d9292ff 100755 --- a/src/trading/core/executor.rs +++ b/src/trading/core/executor.rs @@ -1,5 +1,4 @@ use anyhow::{anyhow, Result}; -use solana_sdk::signer::Signer; use std::sync::Arc; use super::{ @@ -13,6 +12,8 @@ use crate::{ trading::common::{build_rpc_transaction, build_sell_transaction}, }; +const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024; + /// 通用交易执行器实现 pub struct GenericTradeExecutor { instruction_builder: Arc, @@ -29,26 +30,14 @@ impl GenericTradeExecutor { protocol_name, } } - - /// 获取代币余额 - async fn get_token_balance( - &self, - rpc: Arc, - payer: &solana_sdk::signature::Keypair, - mint: &solana_sdk::pubkey::Pubkey, - ) -> Result { - let ata = spl_associated_token_account::get_associated_token_address(&payer.pubkey(), mint); - let balance = rpc.get_token_account_balance(&ata).await?; - balance - .amount - .parse::() - .map_err(|_| anyhow!("Failed to parse token balance")) - } } #[async_trait::async_trait] impl TradeExecutor for GenericTradeExecutor { - async fn buy(&self, params: BuyParams) -> Result<()> { + async fn buy(&self, mut params: BuyParams) -> Result<()> { + if params.data_size_limit == 0 { + params.data_size_limit = MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT; + } if params.rpc.is_none() { return Err(anyhow!("RPC is not set")); } @@ -80,7 +69,10 @@ impl TradeExecutor for GenericTradeExecutor { Ok(()) } - async fn buy_with_tip(&self, params: BuyWithTipParams) -> Result<()> { + async fn buy_with_tip(&self, mut params: BuyWithTipParams) -> Result<()> { + if params.data_size_limit == 0 { + params.data_size_limit = MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT; + } let mut timer = TradeTimer::new("构建买入交易指令"); // 验证参数 - 转换为BuyParams进行验证 diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index e50403f..3553a56 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -3,9 +3,9 @@ use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use std::sync::Arc; use super::traits::ProtocolParams; +use crate::common::bonding_curve::BondingCurveAccount; use crate::common::{PriorityFee, SolanaRpcClient}; use crate::swqos::SwqosClient; -use crate::common::bonding_curve::BondingCurveAccount; /// 通用买入参数 #[derive(Clone)] @@ -74,24 +74,18 @@ pub struct SellWithTipParams { /// PumpFun协议特定参数 #[derive(Clone)] pub struct PumpFunParams { - pub trade_type: String, pub bonding_curve: Option>, } -impl ProtocolParams for PumpFunParams { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) +impl PumpFunParams { + pub fn default() -> Self { + Self { + bonding_curve: None, + } } } -#[derive(Clone)] -pub struct PumpFunSellParams {} - -impl ProtocolParams for PumpFunSellParams { +impl ProtocolParams for PumpFunParams { fn as_any(&self) -> &dyn std::any::Any { self } @@ -105,13 +99,18 @@ impl ProtocolParams for PumpFunSellParams { #[derive(Clone)] pub struct PumpSwapParams { pub pool: Option, - pub pool_base_token_account: Option, - pub pool_quote_token_account: Option, - pub user_base_token_account: Option, - pub user_quote_token_account: Option, pub auto_handle_wsol: bool, } +impl PumpSwapParams { + pub fn default() -> Self { + Self { + pool: None, + auto_handle_wsol: true, + } + } +} + impl ProtocolParams for PumpSwapParams { fn as_any(&self) -> &dyn std::any::Any { self @@ -132,6 +131,18 @@ pub struct BonkParams { pub auto_handle_wsol: bool, } +impl BonkParams { + pub fn default() -> Self { + Self { + virtual_base: None, + virtual_quote: None, + real_base_before: None, + real_quote_before: None, + auto_handle_wsol: true, + } + } +} + impl ProtocolParams for BonkParams { fn as_any(&self) -> &dyn std::any::Any { self diff --git a/src/trading/core/traits.rs b/src/trading/core/traits.rs index 5231e0a..80a05b9 100755 --- a/src/trading/core/traits.rs +++ b/src/trading/core/traits.rs @@ -1,7 +1,5 @@ use anyhow::Result; use solana_sdk::instruction::Instruction; -use std::sync::Arc; - use super::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams}; /// 交易执行器trait - 定义了所有交易协议都需要实现的核心方法 diff --git a/src/trading/factory.rs b/src/trading/factory.rs index 32bd2f5..923f793 100755 --- a/src/trading/factory.rs +++ b/src/trading/factory.rs @@ -9,30 +9,30 @@ use super::{ /// 支持的交易协议 #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Protocol { +pub enum TradingProtocol { PumpFun, PumpSwap, Bonk, } -impl std::fmt::Display for Protocol { +impl std::fmt::Display for TradingProtocol { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Protocol::PumpFun => write!(f, "PumpFun"), - Protocol::PumpSwap => write!(f, "PumpSwap"), - Protocol::Bonk => write!(f, "Bonk"), + TradingProtocol::PumpFun => write!(f, "PumpFun"), + TradingProtocol::PumpSwap => write!(f, "PumpSwap"), + TradingProtocol::Bonk => write!(f, "Bonk"), } } } -impl std::str::FromStr for Protocol { +impl std::str::FromStr for TradingProtocol { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { - "pumpfun" => Ok(Protocol::PumpFun), - "pumpswap" => Ok(Protocol::PumpSwap), - "bonk" => Ok(Protocol::Bonk), + "pumpfun" => Ok(TradingProtocol::PumpFun), + "pumpswap" => Ok(TradingProtocol::PumpSwap), + "bonk" => Ok(TradingProtocol::Bonk), _ => Err(anyhow!("Unsupported protocol: {}", s)), } } @@ -43,17 +43,17 @@ pub struct TradeFactory; impl TradeFactory { /// 创建指定协议的交易执行器 - pub fn create_executor(protocol: Protocol) -> Arc { + pub fn create_executor(protocol: TradingProtocol) -> Arc { match protocol { - Protocol::PumpFun => { + TradingProtocol::PumpFun => { let instruction_builder = Arc::new(PumpFunInstructionBuilder); Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpFun")) } - Protocol::PumpSwap => { + TradingProtocol::PumpSwap => { let instruction_builder = Arc::new(PumpSwapInstructionBuilder); Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpSwap")) } - Protocol::Bonk => { + TradingProtocol::Bonk => { let instruction_builder = Arc::new(BonkInstructionBuilder); Arc::new(GenericTradeExecutor::new( instruction_builder, @@ -64,16 +64,16 @@ impl TradeFactory { } /// 获取所有支持的协议 - pub fn supported_protocols() -> Vec { + pub fn supported_protocols() -> Vec { vec![ - Protocol::PumpFun, - Protocol::PumpSwap, - Protocol::Bonk, + TradingProtocol::PumpFun, + TradingProtocol::PumpSwap, + TradingProtocol::Bonk, ] } /// 检查协议是否支持 - pub fn is_supported(protocol: &Protocol) -> bool { + pub fn is_supported(protocol: &TradingProtocol) -> bool { Self::supported_protocols().contains(protocol) } } diff --git a/src/trading/pumpfun/buy.rs b/src/trading/pumpfun/buy.rs deleted file mode 100755 index 73c4c7b..0000000 --- a/src/trading/pumpfun/buy.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::{ - common::{bonding_curve::BondingCurveAccount, PriorityFee, SolanaRpcClient}, - swqos::SwqosClient, - trading::{core::params::PumpFunParams, factory::Protocol, BuyParams, TradeFactory}, -}; -use solana_hash::Hash; -use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use std::sync::Arc; -const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 250000; - -pub async fn buy( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - buy_sol_cost: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - bonding_curve: Option>, - trade_type: String, -) -> Result<(), anyhow::Error> { - // 创建执行器 - let executor = TradeFactory::create_executor(Protocol::PumpFun); - // 创建协议特定参数 - let protocol_params = Box::new(PumpFunParams { - trade_type: trade_type, - bonding_curve: bonding_curve, - }); - // 创建买入参数 - let buy_params = BuyParams { - rpc: Some(rpc), - payer, - mint, - creator, - amount_sol: buy_sol_cost, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee, - lookup_table_key: lookup_table_key, - recent_blockhash, - data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, - protocol_params, - }; - // 执行买入 - executor.buy(buy_params).await?; - Ok(()) -} - -pub async fn buy_with_tip( - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - buy_sol_cost: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - bonding_curve: Option>, - trade_type: String, -) -> Result<(), anyhow::Error> { - // 创建执行器 - let executor = TradeFactory::create_executor(Protocol::PumpFun); - // 创建协议特定参数 - let protocol_params = Box::new(PumpFunParams { - trade_type: trade_type, - bonding_curve: bonding_curve, - }); - // 创建买入参数 - let buy_params = BuyParams { - rpc: None, - payer, - mint, - creator, - amount_sol: buy_sol_cost, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee, - lookup_table_key: lookup_table_key, - recent_blockhash, - data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, - protocol_params, - }; - let buy_with_tip_params = buy_params.with_tip(swqos_clients); - // 执行买入 - executor.buy_with_tip(buy_with_tip_params).await?; - Ok(()) -} diff --git a/src/trading/pumpfun/common.rs b/src/trading/pumpfun/common.rs index 9cc6730..3e5e5f6 100755 --- a/src/trading/pumpfun/common.rs +++ b/src/trading/pumpfun/common.rs @@ -1,104 +1,24 @@ use anyhow::anyhow; -use borsh::BorshDeserialize; -use spl_token::instruction::close_account; use tokio::sync::RwLock; use std::{collections::HashMap, sync::Arc}; use solana_sdk::{ - compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::Transaction + compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey }; -use spl_associated_token_account::get_associated_token_address; use pumpfun_program::accounts::BondingCurveAccount as PumpfunBondingCurveAccount; use crate::{ common::{ bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient }, constants::{ - self, pumpfun::{global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::DEFAULT_SLIPPAGE} + self, pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::trade::DEFAULT_SLIPPAGE }, - streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent + streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent, trading::common::calculate_with_slippage_buy }; lazy_static::lazy_static! { static ref ACCOUNT_CACHE: RwLock>> = RwLock::new(HashMap::new()); } -pub async fn transfer_sol(rpc: &SolanaRpcClient, payer: &Keypair, receive_wallet: &Pubkey, amount: u64) -> Result<(), anyhow::Error> { - if amount == 0 { - return Err(anyhow!("transfer_sol: Amount cannot be zero")); - } - - let balance = get_sol_balance(rpc, &payer.pubkey()).await?; - if balance < amount { - return Err(anyhow!("Insufficient balance")); - } - - let transfer_instruction = system_instruction::transfer( - &payer.pubkey(), - receive_wallet, - amount, - ); - - let recent_blockhash = rpc.get_latest_blockhash().await?; - - let transaction = Transaction::new_signed_with_payer( - &[transfer_instruction], - Some(&payer.pubkey()), - &[payer], - recent_blockhash, - ); - - rpc.send_and_confirm_transaction(&transaction).await?; - - Ok(()) -} - -/// 关闭代币账户 -/// -/// 此函数用于关闭指定代币的关联代币账户,将账户中的代币余额转移给账户所有者。 -/// -/// # 参数 -/// -/// * `rpc` - Solana RPC客户端 -/// * `payer` - 支付交易费用的账户 -/// * `mint` - 代币的Mint地址 -/// -/// # 返回值 -/// -/// 返回一个Result,成功时返回(),失败时返回错误 -pub async fn close_token_account(rpc: &SolanaRpcClient, payer: &Keypair, mint: &Pubkey) -> Result<(), anyhow::Error> { - // 获取关联代币账户地址 - let ata = get_associated_token_address(&payer.pubkey(), mint); - - // 检查账户是否存在 - let account_exists = rpc.get_account(&ata).await.is_ok(); - if !account_exists { - return Ok(()); // 如果账户不存在,直接返回成功 - } - - // 构建关闭账户指令 - let close_account_ix = close_account( - &spl_token::ID, - &ata, - &payer.pubkey(), - &payer.pubkey(), - &[&payer.pubkey()], - )?; - - // 构建交易 - let recent_blockhash = rpc.get_latest_blockhash().await?; - let transaction = Transaction::new_signed_with_payer( - &[close_account_ix], - Some(&payer.pubkey()), - &[payer], - recent_blockhash, - ); - - // 发送交易 - rpc.send_and_confirm_transaction(&transaction).await?; - - Ok(()) -} - #[inline] pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec { let mut instructions = Vec::with_capacity(2); @@ -108,45 +28,6 @@ pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec Result { - let ata = get_associated_token_address(payer, mint); - // let account_data = rpc.get_account_data(&ata).await?; - // let token_account = Account::unpack(&account_data.as_slice())?; - - // Ok(token_account.amount) - - // println!("get_token_balance ata: {}", ata); - let balance = rpc.get_token_account_balance(&ata).await?; - let balance_u64 = balance.amount.parse::() - .map_err(|_| anyhow!("Failed to parse token balance"))?; - Ok(balance_u64) -} - -#[inline] -pub async fn get_token_balance_and_ata(rpc: &SolanaRpcClient, payer: &Keypair, mint: &Pubkey) -> Result<(u64, Pubkey), anyhow::Error> { - let ata = get_associated_token_address(&payer.pubkey(), mint); - // let account_data = rpc.get_account_data(&ata).await?; - // let token_account = Account::unpack(&account_data)?; - - // Ok((token_account.amount, ata)) - - let balance = rpc.get_token_account_balance(&ata).await?; - let balance_u64 = balance.amount.parse::() - .map_err(|_| anyhow!("Failed to parse token balance"))?; - - if balance_u64 == 0 { - return Err(anyhow!("Balance is 0")); - } - - Ok((balance_u64, ata)) -} - -#[inline] -pub async fn get_sol_balance(rpc: &SolanaRpcClient, account: &Pubkey) -> Result { - let balance = rpc.get_balance(account).await?; - Ok(balance) -} #[inline] pub fn get_global_pda() -> Pubkey { @@ -194,18 +75,8 @@ pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey { #[inline] pub async fn get_global_account(/*rpc: &SolanaRpcClient*/) -> Result, anyhow::Error> { - // let global = constants::global_constants::GLOBAL_ACCOUNT; - // if let Some(account) = ACCOUNT_CACHE.read().await.get(&global) { - // return Ok(account.clone()); - // } - let global_account = GlobalAccount::new(); - - // let account = rpc.get_account(&global).await?; - // let global_account = bincode::deserialize::(&account.data)?; let global_account = Arc::new(global_account); - - // ACCOUNT_CACHE.write().await.insert(global, global_account.clone()); Ok(global_account) } @@ -348,13 +219,3 @@ pub fn get_buy_price(amount: u64, trade_info: &PumpFunTradeEvent) -> u64 { s_u64.min(trade_info.real_token_reserves) } - -#[inline] -pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 { - amount + (amount * basis_points) / 10000 -} - -#[inline] -pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 { - amount - (amount * basis_points) / 10000 -} diff --git a/src/trading/pumpfun/mod.rs b/src/trading/pumpfun/mod.rs index 663177e..ffac467 100755 --- a/src/trading/pumpfun/mod.rs +++ b/src/trading/pumpfun/mod.rs @@ -1,3 +1 @@ -pub mod buy; -pub mod sell; pub mod common; \ No newline at end of file diff --git a/src/trading/pumpfun/sell.rs b/src/trading/pumpfun/sell.rs deleted file mode 100755 index 2817c35..0000000 --- a/src/trading/pumpfun/sell.rs +++ /dev/null @@ -1,184 +0,0 @@ -use crate::trading::{ - core::params::PumpFunSellParams, factory::Protocol, SellParams, TradeFactory, -}; -use crate::{ - common::{PriorityFee, SolanaRpcClient}, - swqos::SwqosClient, -}; -use anyhow::anyhow; -use solana_hash::Hash; -use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use std::sync::Arc; - -pub async fn sell( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount_token: u64, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - let executor = TradeFactory::create_executor(Protocol::PumpFun); - // 创建PumpFun协议参数 - let protocol_params = Box::new(PumpFunSellParams {}); - // 创建卖出参数 - let sell_params = SellParams { - rpc: Some(rpc.clone()), - payer: payer.clone(), - mint, - creator, - amount_token: Some(amount_token), - slippage_basis_points: None, - priority_fee: priority_fee.clone(), - lookup_table_key, - recent_blockhash, - protocol_params, - }; - // 执行卖出交易 - executor.sell(sell_params).await?; - Ok(()) -} - -/// Sell tokens by percentage -pub async fn sell_by_percent( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - percent: u64, - amount_token: u64, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - if percent == 0 || percent > 100 { - return Err(anyhow!("Percentage must be between 1 and 100")); - } - let amount = amount_token * percent / 100; - sell( - rpc, - payer, - mint, - creator, - amount, - priority_fee, - lookup_table_key, - recent_blockhash, - ) - .await -} - -/// Sell tokens by amount -pub async fn sell_by_amount( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount: u64, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - sell( - rpc, - payer, - mint, - creator, - amount, - priority_fee, - lookup_table_key, - recent_blockhash, - ) - .await -} - -pub async fn sell_by_percent_with_tip( - rpc: Arc, - fee_clients: Vec>, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - percent: u64, - amount_token: u64, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - if percent == 0 || percent > 100 { - return Err(anyhow!("Percentage must be between 1 and 100")); - } - let amount = amount_token * percent / 100; - sell_with_tip( - rpc, - fee_clients, - payer, - mint, - creator, - amount, - priority_fee, - lookup_table_key, - recent_blockhash, - ) - .await -} - -pub async fn sell_by_amount_with_tip( - rpc: Arc, - fee_clients: Vec>, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount: u64, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - sell_with_tip( - rpc, - fee_clients, - payer, - mint, - creator, - amount, - priority_fee, - lookup_table_key, - recent_blockhash, - ) - .await -} - -/// Sell tokens using Jito -pub async fn sell_with_tip( - rpc: Arc, - fee_clients: Vec>, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount_token: u64, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result<(), anyhow::Error> { - let executor = TradeFactory::create_executor(Protocol::PumpFun); - // 创建PumpFun协议参数 - let protocol_params = Box::new(PumpFunSellParams {}); - // 创建卖出参数 - let sell_params = SellParams { - rpc: Some(rpc.clone()), - payer: payer.clone(), - mint, - creator, - amount_token: Some(amount_token), - slippage_basis_points: None, - priority_fee: priority_fee.clone(), - lookup_table_key, - recent_blockhash, - protocol_params, - }; - let sell_with_tip_params = sell_params.with_tip(fee_clients); - // 执行卖出交易 - executor.sell_with_tip(sell_with_tip_params).await?; - Ok(()) -} diff --git a/src/trading/pumpswap/buy.rs b/src/trading/pumpswap/buy.rs deleted file mode 100755 index 409b732..0000000 --- a/src/trading/pumpswap/buy.rs +++ /dev/null @@ -1,111 +0,0 @@ -use solana_hash::Hash; -use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use std::sync::Arc; - -use crate::swqos::SwqosClient; -use crate::trading::{core::params::PumpSwapParams, factory::Protocol, BuyParams, TradeFactory}; -use crate::{common::PriorityFee, SolanaRpcClient}; - -// Constants for compute budget -// Increased from 64KB to 256KB to handle larger transactions -const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024; - -// Buy tokens from a Pumpswap pool -pub async fn buy( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount_sol: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - // 可选(必须全部传) - pool: Option, - pool_base_token_account: Option, - pool_quote_token_account: Option, - user_base_token_account: Option, - user_quote_token_account: Option, - auto_handle_wsol: bool, -) -> Result<(), anyhow::Error> { - // 创建执行器 - let executor = TradeFactory::create_executor(Protocol::PumpSwap); - // 创建协议特定参数 - let protocol_params = Box::new(PumpSwapParams { - pool: pool, - pool_base_token_account: pool_base_token_account, - pool_quote_token_account: pool_quote_token_account, - user_base_token_account: user_base_token_account, - user_quote_token_account: user_quote_token_account, - auto_handle_wsol: auto_handle_wsol, - }); - // 创建买入参数 - let buy_params = BuyParams { - rpc: Some(rpc.clone()), - payer: payer, - mint: mint, - creator: creator, - amount_sol: amount_sol, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee, - lookup_table_key: lookup_table_key, - recent_blockhash: recent_blockhash, - data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, - protocol_params, - }; - // 执行买入 - executor.buy(buy_params).await?; - Ok(()) -} - -// Buy tokens using a MEV service -pub async fn buy_with_tip( - rpc: Arc, - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount_sol: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - // 可选(必须全部传) - pool: Option, - pool_base_token_account: Option, - pool_quote_token_account: Option, - user_base_token_account: Option, - user_quote_token_account: Option, - auto_handle_wsol: bool, -) -> Result<(), anyhow::Error> { - // 创建执行器 - let executor = TradeFactory::create_executor(Protocol::PumpSwap); - // 创建协议特定参数 - let protocol_params = Box::new(PumpSwapParams { - pool: pool, - pool_base_token_account: pool_base_token_account, - pool_quote_token_account: pool_quote_token_account, - user_base_token_account: user_base_token_account, - user_quote_token_account: user_quote_token_account, - auto_handle_wsol: auto_handle_wsol, - }); - // 创建买入参数 - let buy_params = BuyParams { - rpc: Some(rpc.clone()), - payer: payer, - mint: mint, - creator: creator, - amount_sol: amount_sol, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee, - lookup_table_key: lookup_table_key, - recent_blockhash: recent_blockhash, - data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, - protocol_params, - }; - let buy_with_tip_params = buy_params.with_tip(swqos_clients); - // 执行买入 - executor.buy_with_tip(buy_with_tip_params).await?; - Ok(()) -} diff --git a/src/trading/pumpswap/common.rs b/src/trading/pumpswap/common.rs index b2d411f..3eef183 100755 --- a/src/trading/pumpswap/common.rs +++ b/src/trading/pumpswap/common.rs @@ -1,47 +1,9 @@ -use anyhow::anyhow; -use solana_sdk::{ - pubkey::Pubkey, - signature::{Keypair, Signer}, -}; use crate::common::SolanaRpcClient; use crate::trading::pumpswap; - -// Calculate slippage for buy operations -pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 { - amount + (amount * basis_points / 10000) -} - -// Calculate slippage for sell operations -pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 { - if amount <= basis_points / 10000 { - 1 - } else { - amount - (amount * basis_points / 10000) - } -} - -// Get token balance for a specific mint and owner -pub async fn get_token_balance( - rpc: &SolanaRpcClient, - owner: &Keypair, - mint: &Pubkey, -) -> Result<(u64, Pubkey), anyhow::Error> { - let ata = spl_associated_token_account::get_associated_token_address(&owner.pubkey(), mint); - - match rpc.get_token_account_balance(&ata).await { - Ok(balance) => { - let amount = balance.amount.parse::().map_err(|e| anyhow!(e))?; - Ok((amount, ata)) - } - Err(_) => Ok((0, ata)), - } -} +use solana_sdk::pubkey::Pubkey; // Find a pool for a specific mint -pub async fn find_pool( - rpc: &SolanaRpcClient, - mint: &Pubkey, -) -> Result { +pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result { let (pool_address, _) = pumpswap::pool::Pool::find_by_mint(rpc, mint).await?; Ok(pool_address) } @@ -83,4 +45,4 @@ pub(crate) fn coin_creator_vault_ata(coin_creator: Pubkey) -> Pubkey { &crate::constants::pumpswap::accounts::TOKEN_PROGRAM, ); associated_token_creator_vault_authority -} \ No newline at end of file +} diff --git a/src/trading/pumpswap/mod.rs b/src/trading/pumpswap/mod.rs index 9c60348..53868a7 100755 --- a/src/trading/pumpswap/mod.rs +++ b/src/trading/pumpswap/mod.rs @@ -1,4 +1,2 @@ -pub mod buy; -pub mod sell; pub mod common; pub mod pool; diff --git a/src/trading/pumpswap/pool.rs b/src/trading/pumpswap/pool.rs index 7d3e643..a840a68 100755 --- a/src/trading/pumpswap/pool.rs +++ b/src/trading/pumpswap/pool.rs @@ -2,7 +2,6 @@ use crate::{common::SolanaRpcClient, constants::pumpswap::accounts}; use anyhow::anyhow; use solana_account_decoder::UiAccountEncoding; use solana_sdk::pubkey::Pubkey; -use std::str::FromStr; #[derive(Debug, Clone)] pub struct Pool { diff --git a/src/trading/pumpswap/sell.rs b/src/trading/pumpswap/sell.rs deleted file mode 100755 index 86c4306..0000000 --- a/src/trading/pumpswap/sell.rs +++ /dev/null @@ -1,266 +0,0 @@ -use anyhow::anyhow; -use solana_hash::Hash; -use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use std::sync::Arc; - -use crate::common::{PriorityFee, SolanaRpcClient}; -use crate::swqos::SwqosClient; -use crate::trading::pumpswap::common::get_token_balance; -use crate::trading::{core::params::PumpSwapParams, factory::Protocol, SellParams, TradeFactory}; - -// Sell tokens to a Pumpswap pool -pub async fn sell( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount_token: Option, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - // 可选(必须全部传) - pool: Option, - pool_base_token_account: Option, - pool_quote_token_account: Option, - user_base_token_account: Option, - user_quote_token_account: Option, -) -> Result<(), anyhow::Error> { - let executor = TradeFactory::create_executor(Protocol::PumpSwap); - // 创建PumpFun协议参数 - let protocol_params = Box::new(PumpSwapParams { - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - auto_handle_wsol: true, - }); - // 创建卖出参数 - let sell_params = SellParams { - rpc: Some(rpc.clone()), - payer: payer.clone(), - mint, - creator, - amount_token: amount_token, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee.clone(), - lookup_table_key, - recent_blockhash, - protocol_params, - }; - // 执行卖出交易 - executor.sell(sell_params).await?; - Ok(()) -} - -// Sell tokens by percentage -pub async fn sell_by_percent( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - percent: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - // 可选(必须全部传) - pool: Option, - pool_base_token_account: Option, - pool_quote_token_account: Option, - user_base_token_account: Option, - user_quote_token_account: Option, -) -> Result<(), anyhow::Error> { - if percent == 0 || percent > 100 { - return Err(anyhow!("Percentage must be between 1 and 100")); - } - let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?; - let amount = balance_u64 * percent / 100; - sell( - rpc, - payer, - mint, - creator, - Some(amount), - slippage_basis_points, - priority_fee, - lookup_table_key, - recent_blockhash, - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - ) - .await -} - -/// Sell tokens by amount -pub async fn sell_by_amount( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - // 可选(必须全部传) - pool: Option, - pool_base_token_account: Option, - pool_quote_token_account: Option, - user_base_token_account: Option, - user_quote_token_account: Option, -) -> Result<(), anyhow::Error> { - sell( - rpc, - payer, - mint, - creator, - Some(amount), - slippage_basis_points, - priority_fee, - lookup_table_key, - recent_blockhash, - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - ) - .await -} - -// Sell tokens using a MEV service -pub async fn sell_with_tip( - rpc: Arc, - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount_token: Option, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - // 可选(必须全部传) - pool: Option, - pool_base_token_account: Option, - pool_quote_token_account: Option, - user_base_token_account: Option, - user_quote_token_account: Option, -) -> Result<(), anyhow::Error> { - let executor = TradeFactory::create_executor(Protocol::PumpSwap); - // 创建PumpFun协议参数 - let protocol_params = Box::new(PumpSwapParams { - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - auto_handle_wsol: true, - }); - // 创建卖出参数 - let sell_params = SellParams { - rpc: Some(rpc.clone()), - payer: payer.clone(), - mint, - creator, - amount_token: amount_token, - slippage_basis_points: slippage_basis_points, - priority_fee: priority_fee.clone(), - lookup_table_key, - recent_blockhash, - protocol_params, - }; - let sell_with_tip_params = sell_params.with_tip(swqos_clients); - // 执行卖出交易 - executor.sell_with_tip(sell_with_tip_params).await?; - Ok(()) -} - -// Sell tokens by percentage using a MEV service -pub async fn sell_by_percent_with_tip( - rpc: Arc, - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - percent: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - // 可选(必须全部传) - pool: Option, - pool_base_token_account: Option, - pool_quote_token_account: Option, - user_base_token_account: Option, - user_quote_token_account: Option, -) -> Result<(), anyhow::Error> { - if percent == 0 || percent > 100 { - return Err(anyhow!("Percentage must be between 1 and 100")); - } - - let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?; - let amount = balance_u64 * percent / 100; - sell_with_tip( - rpc, - swqos_clients, - payer, - mint, - creator, - Some(amount), - slippage_basis_points, - priority_fee, - lookup_table_key, - recent_blockhash, - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - ) - .await -} - -// Sell tokens by amount using a MEV service -pub async fn sell_by_amount_with_tip( - rpc: Arc, - swqos_clients: Vec>, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount: u64, - slippage_basis_points: Option, - priority_fee: PriorityFee, - lookup_table_key: Option, - recent_blockhash: Hash, - // 可选(必须全部传) - pool: Option, - pool_base_token_account: Option, - pool_quote_token_account: Option, - user_base_token_account: Option, - user_quote_token_account: Option, -) -> Result<(), anyhow::Error> { - sell_with_tip( - rpc, - swqos_clients, - payer, - mint, - creator, - Some(amount), - slippage_basis_points, - priority_fee, - lookup_table_key, - recent_blockhash, - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - ) - .await -} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..bdc9f12 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,165 @@ +use crate::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; +use crate::trading; +use crate::SolanaTrade; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; + +impl SolanaTrade { + #[inline] + pub async fn get_sol_balance(&self, payer: &Pubkey) -> Result { + trading::common::utils::get_sol_balance(&self.rpc, payer).await + } + + #[inline] + pub async fn get_payer_sol_balance(&self) -> Result { + trading::common::utils::get_sol_balance(&self.rpc, &self.payer.pubkey()).await + } + + #[inline] + pub async fn get_token_balance( + &self, + payer: &Pubkey, + mint: &Pubkey, + ) -> Result { + println!( + "get_token_balance payer: {}, mint: {}, rpc_url: {}", + payer, mint, self.trade_config.rpc_url + ); + trading::common::utils::get_token_balance(&self.rpc, payer, mint).await + } + + #[inline] + pub async fn get_payer_token_balance(&self, mint: &Pubkey) -> Result { + trading::common::utils::get_token_balance(&self.rpc, &self.payer.pubkey(), mint).await + } + + #[inline] + pub fn get_payer_pubkey(&self) -> Pubkey { + self.payer.pubkey() + } + + #[inline] + pub fn get_payer(&self) -> &Keypair { + self.payer.as_ref() + } + + #[inline] + pub async fn transfer_sol( + &self, + payer: &Keypair, + receive_wallet: &Pubkey, + amount: u64, + ) -> Result<(), anyhow::Error> { + trading::common::utils::transfer_sol(&self.rpc, payer, receive_wallet, amount).await + } + + #[inline] + pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> { + trading::common::utils::close_token_account(&self.rpc, self.payer.as_ref(), mint).await + } + + // -------------------------------- PumpFun -------------------------------- + + #[inline] + pub fn get_pumpfun_token_price( + &self, + virtual_sol_reserves: u64, + virtual_token_reserves: u64, + ) -> f64 { + trading::pumpfun::common::get_token_price(virtual_sol_reserves, virtual_token_reserves) + } + + #[inline] + pub fn get_pumpfun_token_buy_price(&self, amount: u64, trade_info: &PumpFunTradeEvent) -> u64 { + trading::pumpfun::common::get_buy_price(amount, trade_info) + } + + #[inline] + pub async fn get_pumpfun_token_current_price( + &self, + mint: &Pubkey, + ) -> Result { + let (bonding_curve, _) = + trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?; + + let virtual_sol_reserves = bonding_curve.virtual_sol_reserves; + let virtual_token_reserves = bonding_curve.virtual_token_reserves; + + Ok(trading::pumpfun::common::get_token_price( + virtual_sol_reserves, + virtual_token_reserves, + )) + } + + #[inline] + pub async fn get_pumpfun_token_real_sol_reserves( + &self, + mint: &Pubkey, + ) -> Result { + let (bonding_curve, _) = + trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?; + + let actual_sol_reserves = bonding_curve.real_sol_reserves; + + Ok(actual_sol_reserves) + } + + #[inline] + pub async fn get_pumpfun_token_creator(&self, mint: &Pubkey) -> Result { + let (bonding_curve, _) = + trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?; + + let creator = bonding_curve.creator; + + Ok(creator) + } + + // -------------------------------- PumpSwap -------------------------------- + + #[inline] + pub async fn get_pumpswap_token_current_price( + &self, + pool_address: &Pubkey, + ) -> Result { + let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?; + + let (base_amount, quote_amount) = pool.get_token_balances(&self.rpc).await?; + + // Calculate price using constant product formula (x * y = k) + // Price = quote_amount / base_amount + if base_amount == 0 { + return Err(anyhow::anyhow!( + "Base amount is zero, cannot calculate price" + )); + } + + let price = quote_amount as f64 / base_amount as f64; + + Ok(price) + } + + #[inline] + pub async fn get_pumpswap_token_real_sol_reserves( + &self, + pool_address: &Pubkey, + ) -> Result { + let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?; + + let (_, quote_amount) = pool.get_token_balances(&self.rpc).await?; + + Ok(quote_amount) + } + + #[inline] + pub async fn get_pumpswap_payer_token_balance( + &self, + pool_address: &Pubkey, + ) -> Result { + let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?; + + let (base_amount, _) = pool.get_token_balances(&self.rpc).await?; + + Ok(base_amount) + } +}