From 82438479d37fdb7964e27e1b31881682b7aecdbc Mon Sep 17 00:00:00 2001 From: Wood Date: Fri, 27 Feb 2026 13:42:15 +0800 Subject: [PATCH] chore: fix all examples to compile and expand workspace - Add pumpfun_copy_trading and pumpfun_sniper_trading to workspace members - Add grpc_recv_us field to all TradeBuyParams/TradeSellParams in examples - Switch address_lookup and nonce_cache to sol-parser-sdk, use event is_cashback_coin - Remove invalid rustls call from pumpfun examples for successful build - All 18 examples pass cargo check --workspace Made-with: Cursor --- Cargo.toml | 30 +-- examples/address_lookup/Cargo.toml | 4 +- examples/address_lookup/src/main.rs | 173 +++++++++--------- examples/bonk_copy_trading/src/main.rs | 2 + examples/bonk_sniper_trading/src/main.rs | 2 + examples/cli_trading/src/main.rs | 10 + .../src/main.rs | 2 + examples/middleware_system/src/main.rs | 1 + examples/nonce_cache/Cargo.toml | 2 +- examples/nonce_cache/src/main.rs | 157 ++++++++-------- examples/pumpfun_copy_trading/Cargo.toml | 2 +- examples/pumpfun_copy_trading/src/main.rs | 3 +- examples/pumpfun_sniper_trading/Cargo.toml | 2 +- examples/pumpfun_sniper_trading/src/main.rs | 3 +- examples/pumpswap_direct_trading/src/main.rs | 2 + examples/pumpswap_trading/src/main.rs | 25 ++- examples/raydium_amm_v4_trading/src/main.rs | 2 + examples/raydium_cpmm_trading/src/main.rs | 2 + examples/seed_trading/src/main.rs | 2 + src/trading/core/params.rs | 44 ++++- 20 files changed, 273 insertions(+), 197 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 18f5dac..fb97753 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ members = [ "examples/shared_infrastructure", "examples/middleware_system", "examples/pumpswap_trading", + "examples/pumpfun_copy_trading", + "examples/pumpfun_sniper_trading", "examples/bonk_sniper_trading", "examples/bonk_copy_trading", "examples/raydium_cpmm_trading", @@ -43,23 +45,23 @@ perf-trace = [] # 性能追踪特性,生产环境应禁用以获得最佳性 [dependencies] solana-sdk = "3.0.0" -solana-client = "3.0.8" +solana-client = "3.1.9" solana-program = "3.0.0" -solana-rpc-client = "3.0.8" -solana-rpc-client-api = "3.0.8" -solana-transaction-status = "3.0.8" -solana-account-decoder = "3.0.8" +solana-rpc-client = "3.1.9" +solana-rpc-client-api = "3.1.9" +solana-transaction-status = "3.1.9" +solana-account-decoder = "3.1.9" solana-hash = "3.0.0" -solana-entry = "3.0.8" -solana-rpc-client-nonce-utils = "3.0.8" -solana-perf = "3.0.8" -solana-metrics = "3.0.8" -solana-nonce = "3.0.0" -solana-address-lookup-table-interface = "3.0.0" +solana-entry = "3.1.9" +solana-rpc-client-nonce-utils = "3.1.9" +solana-perf = "3.1.9" +solana-metrics = "3.1.9" +solana-nonce = "3.1.0" +solana-address-lookup-table-interface = "3.0.1" solana-compute-budget-interface = "3.0.0" -solana-commitment-config = { version = "3.0.0", features = ["serde"] } -solana-transaction-status-client-types = "3.0.0" -solana-tls-utils = "3.0.8" +solana-commitment-config = { version = "3.1.1", features = ["serde"] } +solana-transaction-status-client-types = "3.1.9" +solana-tls-utils = "3.1.9" borsh = { version = "1.5.3", features = ["derive"] } isahc = "1.7.2" diff --git a/examples/address_lookup/Cargo.toml b/examples/address_lookup/Cargo.toml index 61c2097..b062cbe 100644 --- a/examples/address_lookup/Cargo.toml +++ b/examples/address_lookup/Cargo.toml @@ -5,9 +5,7 @@ edition = "2021" [dependencies] sol-trade-sdk = { path = "../.." } -solana-streamer-sdk = "0.5.0" +sol-parser-sdk = "0.2.2" solana-sdk = "3.0.0" -solana-address-lookup-table-interface = "3.0.0" solana-commitment-config = { version = "3.0.0", features = ["serde"] } tokio = { version = "1", features = ["full"] } -anyhow = "1.0.94" diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index 869a856..3e5f70f 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -1,5 +1,18 @@ +use std::{ + str::FromStr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use sol_parser_sdk::grpc::{ + AccountFilter, ClientConfig, EventType, EventTypeFilter, OrderMode, Protocol, + TransactionFilter, YellowstoneGrpc, +}; +use sol_parser_sdk::DexEvent; use sol_trade_sdk::common::address_lookup::fetch_address_lookup_table_account; -use sol_trade_sdk::common::{gas_fee_strategy, GasFeeStrategy, TradeConfig}; +use sol_trade_sdk::common::{GasFeeStrategy, TradeConfig}; use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, @@ -9,95 +22,82 @@ use sol_trade_sdk::{ use solana_commitment_config::CommitmentConfig; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; -use solana_streamer_sdk::match_event; -use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; -use solana_streamer_sdk::streaming::event_parser::common::EventType; -use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID; -use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; -use solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; -use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter}; -use solana_streamer_sdk::streaming::YellowstoneGrpc; -use std::{ - str::FromStr, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; -// Global static flag to ensure transaction is executed only once static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false); #[tokio::main] async fn main() -> Result<(), Box> { - println!("Subscribing to GRPC events..."); + println!("Subscribing to GRPC events (sol-parser-sdk, is_cashback_coin from event)..."); - let grpc = YellowstoneGrpc::new( - "https://solana-yellowstone-grpc.publicnode.com:443".to_string(), - None, - )?; - - let callback = create_event_callback(); - let protocols = vec![Protocol::PumpFun]; - // Filter accounts - let account_include = vec![ - PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID - ]; - let account_exclude = vec![]; - let account_required = vec![]; - - // Listen to transaction data - let transaction_filter = TransactionFilter { - account_include: account_include.clone(), - account_exclude, - account_required, + let config = ClientConfig { + enable_metrics: false, + connection_timeout_ms: 10000, + request_timeout_ms: 30000, + enable_tls: true, + order_mode: OrderMode::Unordered, + ..Default::default() }; - // Listen to account data belonging to owner programs -> account event monitoring - let account_filter = AccountFilter { account: vec![], owner: vec![], filters: vec![] }; + let grpc_endpoint = std::env::var("GRPC_ENDPOINT") + .unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string()); + let grpc = YellowstoneGrpc::new_with_config( + grpc_endpoint, + std::env::var("GRPC_AUTH_TOKEN").ok(), + config, + )?; - // listen to specific event type - let event_type_filter = - EventTypeFilter { include: vec![EventType::PumpFunBuy, EventType::PumpFunSell] }; + let protocols = vec![Protocol::PumpFun]; + let transaction_filter = TransactionFilter::for_protocols(&protocols); + let account_filter = AccountFilter::for_protocols(&protocols); + let event_filter = EventTypeFilter::include_only(vec![ + EventType::PumpFunBuy, + EventType::PumpFunSell, + EventType::PumpFunBuyExactSolIn, + EventType::PumpFunTrade, + ]); - grpc.subscribe_events_immediate( - protocols, - None, - vec![transaction_filter], - vec![account_filter], - Some(event_type_filter), - None, - callback, - ) - .await?; + let queue = grpc + .subscribe_dex_events(vec![transaction_filter], vec![account_filter], Some(event_filter)) + .await?; + + loop { + if let Some(event) = queue.pop() { + let run = match &event { + DexEvent::PumpFunBuy(e) | DexEvent::PumpFunSell(e) | DexEvent::PumpFunBuyExactSolIn(e) => { + if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { + Some(e.clone()) + } else { + None + } + } + DexEvent::PumpFunTrade(e) => { + if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { + Some(e.clone()) + } else { + None + } + } + _ => None, + }; + if let Some(e) = run { + tokio::spawn(async move { + if let Err(err) = pumpfun_copy_trade_with_grpc(e).await { + eprintln!("Error in copy trade: {:?}", err); + std::process::exit(1); + } + std::process::exit(0); + }); + break; + } + } else { + tokio::time::sleep(tokio::time::Duration::from_millis(5)).await; + } + } tokio::signal::ctrl_c().await?; - Ok(()) } -/// Create an event callback function that handles different types of events -fn create_event_callback() -> impl Fn(Box) { - |event: Box| { - match_event!(event, { - PumpFunTradeEvent => |e: PumpFunTradeEvent| { - // Test code, only test one transaction - if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { - let event_clone = e.clone(); - tokio::spawn(async move { - if let Err(err) = pumpfun_copy_trade_with_grpc(event_clone).await { - eprintln!("Error in copy trade: {:?}", err); - std::process::exit(0); - } - }); - } - }, - }); - } -} - -/// Create SolanaTrade client -/// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { println!("🚀 Initializing SolanaTrade client..."); let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); @@ -110,9 +110,10 @@ async fn create_solana_trade_client() -> AnyResult { Ok(solana_trade) } -/// PumpFun sniper trade -/// This function demonstrates how to snipe a new token from a PumpFun trade event -async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { +/// PumpFun copy trade: use is_cashback_coin from gRPC event (sol-parser-sdk) +async fn pumpfun_copy_trade_with_grpc( + trade_info: sol_parser_sdk::core::events::PumpFunTradeEvent, +) -> AnyResult<()> { println!("Testing PumpFun trading..."); let client = create_solana_trade_client().await?; @@ -127,15 +128,13 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul let gas_fee_strategy = GasFeeStrategy::new(); gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001); - // Buy tokens - println!("Buying tokens from PumpFun..."); - let buy_sol_amount = 100_000; + // is_cashback_coin from gRPC event (sol-parser-sdk parses it from trade event) let buy_params = sol_trade_sdk::TradeBuyParams { dex_type: DexType::PumpFun, input_token_type: sol_trade_sdk::TradeTokenType::SOL, mint: mint_pubkey, - input_token_amount: buy_sol_amount, - slippage_basis_points: slippage_basis_points, + input_token_amount: 100_000, + slippage_basis_points, recent_blockhash: Some(recent_blockhash), extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade( trade_info.bonding_curve, @@ -150,21 +149,21 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul None, trade_info.fee_recipient, trade_info.token_program, - false, // is_cashback_coin: set from event/parser when available + trade_info.is_cashback_coin, )), - address_lookup_table_account: address_lookup_table_account, + address_lookup_table_account, wait_transaction_confirmed: true, create_input_token_ata: false, close_input_token_ata: false, create_mint_ata: true, durable_nonce: None, fixed_output_token_amount: None, - gas_fee_strategy: gas_fee_strategy, + gas_fee_strategy, simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; - // Exit program std::process::exit(0); } diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index ac2b1b4..20cbf0a 100644 --- a/examples/bonk_copy_trading/src/main.rs +++ b/examples/bonk_copy_trading/src/main.rs @@ -176,6 +176,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -226,6 +227,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index 6a27d07..dac5411 100644 --- a/examples/bonk_sniper_trading/src/main.rs +++ b/examples/bonk_sniper_trading/src/main.rs @@ -144,6 +144,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -187,6 +188,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/examples/cli_trading/src/main.rs b/examples/cli_trading/src/main.rs index 0ad0e11..d608999 100644 --- a/examples/cli_trading/src/main.rs +++ b/examples/cli_trading/src/main.rs @@ -635,6 +635,7 @@ async fn handle_buy_pumpfun( gas_fee_strategy: gas_fee_strategy, simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -690,6 +691,7 @@ async fn handle_buy_pumpswap( gas_fee_strategy: gas_fee_strategy, simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -744,6 +746,7 @@ async fn handle_buy_bonk( gas_fee_strategy: gas_fee_strategy, simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -802,6 +805,7 @@ async fn handle_buy_raydium_v4( gas_fee_strategy: gas_fee_strategy, simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -860,6 +864,7 @@ async fn handle_buy_raydium_cpmm( gas_fee_strategy: gas_fee_strategy, simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; match client.buy(buy_params).await { Ok((_, signature, _)) => { @@ -1028,6 +1033,7 @@ async fn handle_sell_pumpfun( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; match client.sell(sell_params).await { @@ -1086,6 +1092,7 @@ async fn handle_sell_pumpswap( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; match client.sell(sell_params).await { Ok((_, signature, _)) => { @@ -1143,6 +1150,7 @@ async fn handle_sell_bonk( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; match client.sell(sell_params).await { Ok((_, signature, _)) => { @@ -1203,6 +1211,7 @@ async fn handle_sell_raydium_v4( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; match client.sell(sell_params).await { Ok((_, signature, _)) => { @@ -1263,6 +1272,7 @@ async fn handle_sell_raydium_cpmm( fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; match client.sell(sell_params).await { Ok((_, signature, _)) => { diff --git a/examples/meteora_damm_v2_direct_trading/src/main.rs b/examples/meteora_damm_v2_direct_trading/src/main.rs index 2c04d73..6af152b 100644 --- a/examples/meteora_damm_v2_direct_trading/src/main.rs +++ b/examples/meteora_damm_v2_direct_trading/src/main.rs @@ -44,6 +44,7 @@ async fn main() -> Result<(), Box> { gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -77,6 +78,7 @@ async fn main() -> Result<(), Box> { fixed_output_token_amount: Some(1), gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index 54040e1..be5b250 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -103,6 +103,7 @@ async fn test_middleware() -> AnyResult<()> { gas_fee_strategy: gas_fee_strategy, simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; println!("tip: This transaction will not succeed because we're using a test account. You can modify the code to initialize the payer with your own private key"); diff --git a/examples/nonce_cache/Cargo.toml b/examples/nonce_cache/Cargo.toml index dff0001..3a47599 100644 --- a/examples/nonce_cache/Cargo.toml +++ b/examples/nonce_cache/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] sol-trade-sdk = { path = "../.." } -solana-streamer-sdk = "0.5.0" +sol-parser-sdk = "0.2.2" solana-sdk = "3.0.0" solana-commitment-config = { version = "3.0.0", features = ["serde"] } spl-associated-token-account = "7.0.0" diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index 8727fc2..b49952f 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -6,6 +6,11 @@ use std::{ }, }; +use sol_parser_sdk::grpc::{ + AccountFilter, ClientConfig, EventType, EventTypeFilter, OrderMode, Protocol, + TransactionFilter, YellowstoneGrpc, +}; +use sol_parser_sdk::DexEvent; use sol_trade_sdk::common::{nonce_cache::fetch_nonce_info, TradeConfig}; use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::{ @@ -16,88 +21,82 @@ use sol_trade_sdk::{ }; use solana_commitment_config::CommitmentConfig; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use solana_streamer_sdk::match_event; -use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; -use solana_streamer_sdk::streaming::event_parser::common::EventType; -use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID; -use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; -use solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; -use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter}; -use solana_streamer_sdk::streaming::YellowstoneGrpc; -// Global static flag to ensure transaction is executed only once static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false); #[tokio::main] async fn main() -> Result<(), Box> { - println!("Subscribing to GRPC events..."); + println!("Subscribing to GRPC events (sol-parser-sdk, is_cashback_coin from event)..."); - let grpc = YellowstoneGrpc::new( - "https://solana-yellowstone-grpc.publicnode.com:443".to_string(), - None, - )?; - - let callback = create_event_callback(); - let protocols = vec![Protocol::PumpFun]; - // Filter accounts - let account_include = vec![ - PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID - ]; - let account_exclude = vec![]; - let account_required = vec![]; - - // Listen to transaction data - let transaction_filter = TransactionFilter { - account_include: account_include.clone(), - account_exclude, - account_required, + let config = ClientConfig { + enable_metrics: false, + connection_timeout_ms: 10000, + request_timeout_ms: 30000, + enable_tls: true, + order_mode: OrderMode::Unordered, + ..Default::default() }; - // Listen to account data belonging to owner programs -> account event monitoring - let account_filter = AccountFilter { account: vec![], owner: vec![], filters: vec![] }; + let grpc_endpoint = std::env::var("GRPC_ENDPOINT") + .unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string()); + let grpc = YellowstoneGrpc::new_with_config( + grpc_endpoint, + std::env::var("GRPC_AUTH_TOKEN").ok(), + config, + )?; - // listen to specific event type - let event_type_filter = - EventTypeFilter { include: vec![EventType::PumpFunBuy, EventType::PumpFunSell] }; + let protocols = vec![Protocol::PumpFun]; + let transaction_filter = TransactionFilter::for_protocols(&protocols); + let account_filter = AccountFilter::for_protocols(&protocols); + let event_filter = EventTypeFilter::include_only(vec![ + EventType::PumpFunBuy, + EventType::PumpFunSell, + EventType::PumpFunBuyExactSolIn, + EventType::PumpFunTrade, + ]); - grpc.subscribe_events_immediate( - protocols, - None, - vec![transaction_filter], - vec![account_filter], - Some(event_type_filter), - None, - callback, - ) - .await?; + let queue = grpc + .subscribe_dex_events(vec![transaction_filter], vec![account_filter], Some(event_filter)) + .await?; + + loop { + if let Some(event) = queue.pop() { + let run = match &event { + DexEvent::PumpFunBuy(e) | DexEvent::PumpFunSell(e) | DexEvent::PumpFunBuyExactSolIn(e) => { + if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { + Some(e.clone()) + } else { + None + } + } + DexEvent::PumpFunTrade(e) => { + if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { + Some(e.clone()) + } else { + None + } + } + _ => None, + }; + if let Some(e) = run { + tokio::spawn(async move { + if let Err(err) = pumpfun_copy_trade_with_grpc(e).await { + eprintln!("Error in copy trade: {:?}", err); + std::process::exit(1); + } + std::process::exit(0); + }); + break; + } + } else { + tokio::time::sleep(tokio::time::Duration::from_millis(5)).await; + } + } tokio::signal::ctrl_c().await?; - Ok(()) } -/// Create an event callback function that handles different types of events -fn create_event_callback() -> impl Fn(Box) { - |event: Box| { - match_event!(event, { - PumpFunTradeEvent => |e: PumpFunTradeEvent| { - // Test code, only test one transaction - if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { - let event_clone = e.clone(); - tokio::spawn(async move { - if let Err(err) = pumpfun_copy_trade_with_grpc(event_clone).await { - eprintln!("Error in copy trade: {:?}", err); - std::process::exit(0); - } - }); - } - }, - }); - } -} - -/// Create SolanaTrade client -/// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { println!("🚀 Initializing SolanaTrade client..."); let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); @@ -110,9 +109,10 @@ async fn create_solana_trade_client() -> AnyResult { Ok(solana_trade) } -/// PumpFun sniper trade -/// This function demonstrates how to snipe a new token from a PumpFun trade event -async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { +/// PumpFun copy trade: use is_cashback_coin from gRPC event (sol-parser-sdk) +async fn pumpfun_copy_trade_with_grpc( + trade_info: sol_parser_sdk::core::events::PumpFunTradeEvent, +) -> AnyResult<()> { println!("Testing PumpFun trading..."); let client = create_solana_trade_client().await?; @@ -120,22 +120,19 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul let slippage_basis_points = Some(100); let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; - // Setup nonce cache let nonce_account_str = Pubkey::from_str("use_your_nonce_account_here")?; let durable_nonce = fetch_nonce_info(&client.infrastructure.rpc, nonce_account_str).await; let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001); - // Buy tokens - println!("Buying tokens from PumpFun..."); - let buy_sol_amount = 100_000; + // is_cashback_coin from gRPC event (sol-parser-sdk parses it from trade event) let buy_params = sol_trade_sdk::TradeBuyParams { dex_type: DexType::PumpFun, input_token_type: TradeTokenType::SOL, mint: mint_pubkey, - input_token_amount: buy_sol_amount, - slippage_basis_points: slippage_basis_points, + input_token_amount: 100_000, + slippage_basis_points, recent_blockhash: Some(recent_blockhash), extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade( trade_info.bonding_curve, @@ -150,21 +147,21 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul None, trade_info.fee_recipient, trade_info.token_program, - false, // is_cashback_coin: set from event/parser when available + trade_info.is_cashback_coin, )), address_lookup_table_account: None, wait_transaction_confirmed: true, create_input_token_ata: false, close_input_token_ata: false, create_mint_ata: true, - durable_nonce: durable_nonce, + durable_nonce, fixed_output_token_amount: None, - gas_fee_strategy: gas_fee_strategy, + gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; - // Exit program std::process::exit(0); } diff --git a/examples/pumpfun_copy_trading/Cargo.toml b/examples/pumpfun_copy_trading/Cargo.toml index a89fe30..a6b24e2 100644 --- a/examples/pumpfun_copy_trading/Cargo.toml +++ b/examples/pumpfun_copy_trading/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] sol-trade-sdk = { path = "../.." } -sol-parser-sdk = { path = "../../../sol-parser-sdk" } +sol-parser-sdk = "0.2.2" solana-sdk = "3.0.0" solana-commitment-config = { version = "3.0.0", features = ["serde"] } tokio = { version = "1", features = ["full"] } diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index 532d910..eb873d8 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -27,7 +27,6 @@ static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false); #[tokio::main] async fn main() -> Result<(), Box> { - let _ = rustls::crypto::ring::default_provider().install_default(); println!("PumpFun 跟单示例(sol-parser-sdk gRPC)..."); let config = ClientConfig { @@ -155,6 +154,7 @@ async fn pumpfun_copy_trade( gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -202,6 +202,7 @@ async fn pumpfun_copy_trade( fixed_output_token_amount: None, gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/examples/pumpfun_sniper_trading/Cargo.toml b/examples/pumpfun_sniper_trading/Cargo.toml index c72830b..eec88d7 100644 --- a/examples/pumpfun_sniper_trading/Cargo.toml +++ b/examples/pumpfun_sniper_trading/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] sol-trade-sdk = { path = "../.." } -sol-parser-sdk = { path = "../../../sol-parser-sdk" } +sol-parser-sdk = "0.2.2" solana-sdk = "3.0.0" solana-commitment-config = { version = "3.0.0", features = ["serde"] } tokio = { version = "1", features = ["full"] } diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index 8e2e732..f53e76a 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -27,7 +27,6 @@ static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false); #[tokio::main] async fn main() -> Result<(), Box> { - let _ = rustls::crypto::ring::default_provider().install_default(); println!("PumpFun 狙击示例(sol-parser-sdk gRPC)..."); let config = ClientConfig { @@ -147,6 +146,7 @@ async fn pumpfun_sniper_trade( gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -188,6 +188,7 @@ async fn pumpfun_sniper_trade( fixed_output_token_amount: None, gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index 51922de..e432f7c 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -44,6 +44,7 @@ async fn main() -> Result<(), Box> { gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -72,6 +73,7 @@ async fn main() -> Result<(), Box> { create_output_token_ata: true, close_output_token_ata: true, close_mint_token_ata: false, + grpc_recv_us: None, durable_nonce: None, fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, diff --git a/examples/pumpswap_trading/src/main.rs b/examples/pumpswap_trading/src/main.rs index 297e4c2..82c6419 100644 --- a/examples/pumpswap_trading/src/main.rs +++ b/examples/pumpswap_trading/src/main.rs @@ -1,6 +1,7 @@ use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed; use sol_trade_sdk::common::TradeConfig; use sol_trade_sdk::TradeTokenType; +use sol_trade_sdk::instruction::utils::pumpswap::fetch_pool; use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, @@ -136,7 +137,9 @@ async fn create_solana_trade_client() -> AnyResult { } async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> AnyResult<()> { - let params = PumpSwapParams::new( + let client = create_solana_trade_client().await?; + let pool_data = fetch_pool(&client.infrastructure.rpc, &trade_info.pool).await?; + let params = PumpSwapParams::from_trade( trade_info.pool, trade_info.base_mint, trade_info.quote_mint, @@ -149,6 +152,7 @@ async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> Any trade_info.base_token_program, trade_info.quote_token_program, trade_info.protocol_fee_recipient, + pool_data.is_cashback_coin, ); let mint = if trade_info.base_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT || trade_info.base_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT @@ -157,12 +161,14 @@ async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> Any } else { trade_info.base_mint }; - pumpswap_trade_with_grpc(mint, params).await?; + pumpswap_trade_with_grpc(&client, mint, params).await?; Ok(()) } async fn pumpswap_trade_with_grpc_sell_event(trade_info: PumpSwapSellEvent) -> AnyResult<()> { - let params = PumpSwapParams::new( + let client = create_solana_trade_client().await?; + let pool_data = fetch_pool(&client.infrastructure.rpc, &trade_info.pool).await?; + let params = PumpSwapParams::from_trade( trade_info.pool, trade_info.base_mint, trade_info.quote_mint, @@ -175,6 +181,7 @@ async fn pumpswap_trade_with_grpc_sell_event(trade_info: PumpSwapSellEvent) -> A trade_info.base_token_program, trade_info.quote_token_program, trade_info.protocol_fee_recipient, + pool_data.is_cashback_coin, ); let mint = if trade_info.base_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT || trade_info.base_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT @@ -183,14 +190,16 @@ async fn pumpswap_trade_with_grpc_sell_event(trade_info: PumpSwapSellEvent) -> A } else { trade_info.base_mint }; - pumpswap_trade_with_grpc(mint, params).await?; + pumpswap_trade_with_grpc(&client, mint, params).await?; Ok(()) } -async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) -> AnyResult<()> { +async fn pumpswap_trade_with_grpc( + client: &SolanaTrade, + mint_pubkey: Pubkey, + params: PumpSwapParams, +) -> AnyResult<()> { println!("Testing PumpSwap trading..."); - - let client = create_solana_trade_client().await?; let slippage_basis_points = Some(500); let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; @@ -221,6 +230,7 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -255,6 +265,7 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/examples/raydium_amm_v4_trading/src/main.rs b/examples/raydium_amm_v4_trading/src/main.rs index 05dafb1..1010dba 100644 --- a/examples/raydium_amm_v4_trading/src/main.rs +++ b/examples/raydium_amm_v4_trading/src/main.rs @@ -174,6 +174,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -212,6 +213,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index faa4e5f..2b36f4c 100644 --- a/examples/raydium_cpmm_trading/src/main.rs +++ b/examples/raydium_cpmm_trading/src/main.rs @@ -159,6 +159,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -194,6 +195,7 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/examples/seed_trading/src/main.rs b/examples/seed_trading/src/main.rs index 3af6c4e..6da5c5a 100644 --- a/examples/seed_trading/src/main.rs +++ b/examples/seed_trading/src/main.rs @@ -47,6 +47,7 @@ async fn main() -> Result<(), Box> { gas_fee_strategy: gas_fee_strategy.clone(), simulate: false, use_exact_sol_amount: None, + grpc_recv_us: None, }; client.buy(buy_params).await?; @@ -87,6 +88,7 @@ async fn main() -> Result<(), Box> { fixed_output_token_amount: None, gas_fee_strategy: gas_fee_strategy, simulate: false, + grpc_recv_us: None, }; client.sell(sell_params).await?; diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index cc24503..09495dc 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -276,6 +276,7 @@ impl PumpSwapParams { base_token_program: Pubkey, quote_token_program: Pubkey, fee_recipient: Pubkey, + is_cashback_coin: bool, ) -> Self { let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT_SWAP; Self { @@ -291,10 +292,51 @@ impl PumpSwapParams { base_token_program, quote_token_program, is_mayhem_mode, - is_cashback_coin: false, + is_cashback_coin, } } + /// Fast-path constructor for building PumpSwap parameters directly from decoded + /// trade/event data and the accompanying instruction accounts, avoiding RPC + /// lookups and associated latency. Token program IDs should be sourced from + /// the instruction accounts themselves to respect Token Program vs Token-2022 + /// differences. + /// + /// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` + /// from the event so that buy/sell instructions include the correct remaining + /// accounts for cashback. + pub fn from_trade( + pool: Pubkey, + base_mint: Pubkey, + quote_mint: Pubkey, + pool_base_token_account: Pubkey, + pool_quote_token_account: Pubkey, + pool_base_token_reserves: u64, + pool_quote_token_reserves: u64, + coin_creator_vault_ata: Pubkey, + coin_creator_vault_authority: Pubkey, + base_token_program: Pubkey, + quote_token_program: Pubkey, + fee_recipient: Pubkey, + is_cashback_coin: bool, + ) -> Self { + Self::new( + pool, + base_mint, + quote_mint, + pool_base_token_account, + pool_quote_token_account, + pool_base_token_reserves, + pool_quote_token_reserves, + coin_creator_vault_ata, + coin_creator_vault_authority, + base_token_program, + quote_token_program, + fee_recipient, + is_cashback_coin, + ) + } + pub async fn from_mint_by_rpc( rpc: &SolanaRpcClient, mint: &Pubkey,