Files
sol-trade-sdk/examples/address_lookup/src/main.rs
T
HelvetiCrypt bd1772bed2 Add SwapParams.wait_for_all_submits to restore all-routes signature collection
v4.0.11 switched the fast-submit (`wait_tx_confirmed = false`) result loop
from `wait_for_all_submitted` to `wait_for_first_submitted`. The new
function returns as soon as one SWQOS route's HTTP submit completes and
drains whatever signatures are in `ResultCollector.results` at that
moment — slower routes' signatures arrive after the caller has returned.

This is correct for fire-and-forget low-latency submits, but breaks
callers that do their own on-chain confirmation against a pinned durable
nonce. Only one of the submitted txs can land (nonce is consumed by
whichever lands first), but the caller has no way of knowing in advance
which route it will be. Their post-submit logic feeds every returned
signature to `getSignatureStatuses` to find the landed one — with only
the fastest HTTP responder returned (often not the one that lands), the
landed tx isn't in the polled set.

Adds an opt-in `SwapParams.wait_for_all_submits: bool` (default false,
plumbed through `TradeBuyParams` / `TradeSellParams`). When true and
`wait_tx_confirmed = false`, `execute_parallel` uses
`wait_for_all_submitted` (still in the codebase, was
`#[allow(dead_code)]`) so every submitted signature is returned. Zero
behaviour change for current users.
2026-05-27 08:02:49 +00:00

197 lines
6.9 KiB
Rust

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::{GasFeeStrategy, TradeConfig};
use sol_trade_sdk::{
common::AnyResult,
swqos::SwqosConfig,
trading::{
core::params::{DexParamEnum, PumpFunParams},
factory::DexType,
},
SolanaTrade,
};
use solana_commitment_config::CommitmentConfig;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false);
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Subscribing to GRPC events (sol-parser-sdk, is_cashback_coin from event)...");
let config = ClientConfig {
enable_metrics: false,
connection_timeout_ms: 10000,
request_timeout_ms: 30000,
enable_tls: true,
order_mode: OrderMode::Unordered,
..Default::default()
};
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,
)?;
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,
]);
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(())
}
async fn create_solana_trade_client() -> AnyResult<SolanaTrade> {
println!("🚀 Initializing SolanaTrade client...");
let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
let rpc_url = "https://api.mainnet-beta.solana.com".to_string();
let commitment = CommitmentConfig::confirmed();
let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
let trade_config = TradeConfig::builder(rpc_url, swqos_configs, commitment)
// .create_wsol_ata_on_startup(true) // default: true
// .use_seed_optimize(true) // default: true
// .log_enabled(true) // default: true
// .check_min_tip(false) // default: false
// .swqos_cores_from_end(false) // default: false
// .mev_protection(false) // default: false
.build();
let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await;
println!("✅ SolanaTrade client initialized successfully!");
Ok(solana_trade)
}
/// 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?;
let mint_pubkey = trade_info.mint;
let virtual_quote_reserves = if trade_info.virtual_quote_reserves != 0 {
trade_info.virtual_quote_reserves
} else {
trade_info.virtual_sol_reserves
};
let real_quote_reserves = if trade_info.virtual_quote_reserves != 0 {
trade_info.real_quote_reserves
} else {
trade_info.real_sol_reserves
};
let slippage_basis_points = Some(100);
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
let lookup_table_key = Pubkey::from_str("use_your_lookup_table_key_here").unwrap();
let address_lookup_table_account =
fetch_address_lookup_table_account(&client.infrastructure.rpc, &lookup_table_key)
.await
.ok();
let gas_fee_strategy = GasFeeStrategy::new();
gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001);
// 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: 100_000,
slippage_basis_points,
recent_blockhash: Some(recent_blockhash),
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
trade_info.bonding_curve,
trade_info.associated_bonding_curve,
trade_info.mint,
trade_info.quote_mint,
trade_info.creator,
trade_info.creator_vault,
trade_info.virtual_token_reserves,
virtual_quote_reserves,
trade_info.real_token_reserves,
real_quote_reserves,
None,
trade_info.fee_recipient,
trade_info.token_program,
trade_info.is_cashback_coin,
Some(trade_info.mayhem_mode),
)),
address_lookup_table_account,
wait_tx_confirmed: true,
wait_for_all_submits: false,
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,
simulate: false,
use_exact_sol_amount: None,
grpc_recv_us: None,
};
client.buy(buy_params).await?;
std::process::exit(0);
}