From aef9fe6b14fa6118f73639cc4b8af5f53d8e7463 Mon Sep 17 00:00:00 2001 From: ysq Date: Mon, 14 Jul 2025 22:26:03 +0800 Subject: [PATCH] feat: add Bonk trading examples and fix function naming - Add comprehensive Bonk trading examples in README - Fix function naming consistency (width -> with) - Enhance BonkParams with from_trade and from_dev_trade methods - Add amount calculation utilities and tests --- README.md | 90 ++++++++++++++++++++++++++++++- README_CN.md | 90 ++++++++++++++++++++++++++++++- src/main.rs | 91 +++++++++++++++++++++++++++++-- src/trading/bonk/common.rs | 107 +++++++++++++++++++++++++++++++++++++ src/trading/core/params.rs | 62 +++++++++++++++++++++ 5 files changed, 433 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4edfffe..030d17f 100755 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ use sol_trade_sdk::{ }; // pumpfun sniper trade -async fn test_pumpfun_sniper_trade_width_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> { +async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> { println!("Testing PumpFun trading..."); // if not dev trade, return @@ -263,7 +263,7 @@ async fn test_pumpfun_sniper_trade_width_shreds(trade_info: PumpFunTradeEvent) - } // pumpfun copy trade -async fn test_pumpfun_copy_trade_width_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { +async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { println!("Testing PumpFun trading..."); let trade_client = test_create_solana_trade_client().await?; @@ -367,6 +367,92 @@ async fn test_pumpswap() -> AnyResult<()> { ### 5. Bonk Trading Operations ```rust + +// bonk sniper trade +async fn test_bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult<()> { + println!("Testing Bonk trading..."); + + if !trade_info.is_dev_create_token_trade { + return Ok(()); + } + + let trade_client = test_create_solana_trade_client().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = trade_client.rpc.get_latest_blockhash().await?; + + println!("Buying tokens from letsbonk.fun..."); + + // Use dev trade info to build BonkParams, can save transaction time + trade_client.buy( + DexType::Bonk, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + Some(Box::new(BonkParams::from_dev_trade(trade_info))), + ).await?; + + println!("Selling tokens from letsbonk.fun..."); + let amount_token = 0; + trade_client.sell( + DexType::Bonk, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + None, + ).await?; + + Ok(()) +} + +// bonk copy trade +async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> { + println!("Testing Bonk trading..."); + + let trade_client = test_create_solana_trade_client().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = trade_client.rpc.get_latest_blockhash().await?; + + println!("Buying tokens from letsbonk.fun..."); + + // Use trade event info to build BonkParams, can save transaction time + trade_client.buy( + DexType::Bonk, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + Some(Box::new(BonkParams::from_trade(trade_info))), + ).await?; + + println!("Selling tokens from letsbonk.fun..."); + let amount_token = 0; + trade_client.sell( + DexType::Bonk, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + None, + ).await?; + + Ok(()) +} + +// bonk regular trade async fn test_bonk() -> Result<(), Box> { println!("Testing Bonk trading..."); diff --git a/README_CN.md b/README_CN.md index ac1984d..28a706b 100755 --- a/README_CN.md +++ b/README_CN.md @@ -216,7 +216,7 @@ use sol_trade_sdk::{ }; // pumpfun 狙击者交易 -async fn test_pumpfun_sniper_trade_width_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> { +async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> { println!("Testing PumpFun trading..."); // 如果不是开发者购买,则返回 @@ -263,7 +263,7 @@ async fn test_pumpfun_sniper_trade_width_shreds(trade_info: PumpFunTradeEvent) - } // pumpfun 跟单交易 -async fn test_pumpfun_copy_trade_width_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { +async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { println!("Testing PumpFun trading..."); let trade_client = test_create_solana_trade_client().await?; @@ -365,6 +365,92 @@ async fn test_pumpswap() -> AnyResult<()> { ### 5. Bonk 交易操作 ```rust + +// bonk 狙击者交易 +async fn test_bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult<()> { + println!("Testing Bonk trading..."); + + if !trade_info.is_dev_create_token_trade { + return Ok(()); + } + + let trade_client = test_create_solana_trade_client().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = trade_client.rpc.get_latest_blockhash().await?; + + println!("Buying tokens from letsbonk.fun..."); + + // 使用开发者交易信息构建 BonkParams,可以节约交易时间 + trade_client.buy( + DexType::Bonk, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + Some(Box::new(BonkParams::from_dev_trade(trade_info))), + ).await?; + + println!("Selling tokens from letsbonk.fun..."); + let amount_token = 0; + trade_client.sell( + DexType::Bonk, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + None, + ).await?; + + Ok(()) +} + +// bonk 跟单交易 +async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> { + println!("Testing Bonk trading..."); + + let trade_client = test_create_solana_trade_client().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = trade_client.rpc.get_latest_blockhash().await?; + + println!("Buying tokens from letsbonk.fun..."); + + // 使用交易事件信息构建 BonkParams,可以节约交易时间 + trade_client.buy( + DexType::Bonk, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + Some(Box::new(BonkParams::from_trade(trade_info))), + ).await?; + + println!("Selling tokens from letsbonk.fun..."); + let amount_token = 0; + trade_client.sell( + DexType::Bonk, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + None, + ).await?; + + Ok(()) +} + +// bonk 普通交易 async fn test_bonk() -> Result<(), Box> { println!("Testing Bonk trading..."); diff --git a/src/main.rs b/src/main.rs index 21a9a45..695e948 100755 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use sol_trade_sdk::{ ShredStreamGrpc, YellowstoneGrpc, }, swqos::{SwqosConfig, SwqosRegion}, - trading::{core::params::PumpFunParams, factory::DexType}, + trading::{core::params::{BonkParams, PumpFunParams}, factory::DexType}, SolanaTrade, }; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair}; @@ -70,7 +70,7 @@ fn create_trade_config(rpc_url: String, swqos_configs: Vec) -> Trad } } -async fn test_pumpfun_copy_trade_width_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { +async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { println!("Testing PumpFun trading..."); let client = test_create_solana_trade_client().await?; @@ -113,7 +113,7 @@ async fn test_pumpfun_copy_trade_width_grpc(trade_info: PumpFunTradeEvent) -> An Ok(()) } -async fn test_pumpfun_sniper_trade_width_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> { +async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> { println!("Testing PumpFun trading..."); if !trade_info.is_dev_create_token_trade { @@ -206,6 +206,91 @@ async fn test_pumpswap() -> AnyResult<()> { Ok(()) } + + +async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> { + println!("Testing Bonk trading..."); + + let client = test_create_solana_trade_client().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + + // Buy tokens + println!("Buying tokens from letsbonk.fun..."); + client.buy( + DexType::Bonk, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + Some(Box::new(BonkParams::from_trade(trade_info))), + ).await?; + + // Sell tokens + println!("Selling tokens from letsbonk.fun..."); + let amount_token = 0; + client.sell( + DexType::Bonk, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + None, + ).await?; + + Ok(()) +} + +async fn test_bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult<()> { + println!("Testing Bonk trading..."); + + if !trade_info.is_dev_create_token_trade { + return Ok(()); + } + + let client = test_create_solana_trade_client().await?; + let mint_pubkey = Pubkey::from_str("xxxxxxx")?; + let buy_sol_cost = 100_000; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + + // Buy tokens + println!("Buying tokens from letsbonk.fun..."); + client.buy( + DexType::Bonk, + mint_pubkey, + None, + buy_sol_cost, + slippage_basis_points, + recent_blockhash, + None, + Some(Box::new(BonkParams::from_dev_trade(trade_info))), + ).await?; + + // Sell tokens + println!("Selling tokens from letsbonk.fun..."); + let amount_token = 0; + client.sell( + DexType::Bonk, + mint_pubkey, + None, + amount_token, + slippage_basis_points, + recent_blockhash, + None, + None, + ).await?; + + Ok(()) +} + + async fn test_bonk() -> Result<(), Box> { println!("Testing Bonk trading..."); diff --git a/src/trading/bonk/common.rs b/src/trading/bonk/common.rs index ddacf8c..f89ffef 100755 --- a/src/trading/bonk/common.rs +++ b/src/trading/bonk/common.rs @@ -1,6 +1,59 @@ use crate::constants; use solana_sdk::pubkey::Pubkey; +pub fn get_amount_in_net( + amount_in: u64, + protocol_fee_rate: u128, + platform_fee_rate: u128, + share_fee_rate: u128, +) -> u64 { + let amount_in_u128 = amount_in as u128; + let protocol_fee = (amount_in_u128 * protocol_fee_rate / 10000) as u128; + let platform_fee = (amount_in_u128 * platform_fee_rate / 10000) as u128; + let share_fee = (amount_in_u128 * share_fee_rate / 10000) as u128; + amount_in_u128 + .checked_sub(protocol_fee) + .unwrap() + .checked_sub(platform_fee) + .unwrap() + .checked_sub(share_fee) + .unwrap() as u64 +} + +pub fn get_amount_in( + amount_out: u64, + protocol_fee_rate: u128, + platform_fee_rate: u128, + share_fee_rate: u128, + virtual_base: u128, + virtual_quote: u128, + real_base: u128, + real_quote: u128, + slippage_basis_points: u128, +) -> u64 { + let amount_out_u128 = amount_out as u128; + + // 考虑滑点,实际需要的输出金额更高 + let amount_out_with_slippage = amount_out_u128 * 10000 / (10000 - slippage_basis_points); + + let input_reserve = virtual_quote.checked_add(real_quote).unwrap(); + let output_reserve = virtual_base.checked_sub(real_base).unwrap(); + + // 根据 AMM 公式反推: amount_in_net = (amount_out * input_reserve) / (output_reserve - amount_out) + let numerator = amount_out_with_slippage.checked_mul(input_reserve).unwrap(); + let denominator = output_reserve + .checked_sub(amount_out_with_slippage) + .unwrap(); + let amount_in_net = numerator.checked_div(denominator).unwrap(); + + // 计算总费用率 + let total_fee_rate = protocol_fee_rate + platform_fee_rate + share_fee_rate; + + let amount_in = amount_in_net * 10000 / (10000 - total_fee_rate); + + amount_in as u64 +} + pub fn get_amount_out( amount_in: u64, protocol_fee_rate: u128, @@ -80,3 +133,57 @@ pub fn get_token_price( price } + +#[cfg(test)] +mod tests { + use crate::constants::bonk::accounts::{PLATFORM_FEE_RATE, PROTOCOL_FEE_RATE, SHARE_FEE_RATE}; + + use super::*; + + #[test] + fn test_amount_in_out_consistency() { + // 测试参数 + let protocol_fee_rate = PROTOCOL_FEE_RATE; + let platform_fee_rate = PLATFORM_FEE_RATE; + let share_fee_rate = SHARE_FEE_RATE; + let virtual_base = 1073025605596382; + let virtual_quote = 30000852951; + let real_base = 0; + let real_quote = 0; + let slippage_basis_points = 0; + + let original_amount_in = 2000000000; + + let geet_amount_out_result = get_amount_out( + original_amount_in, + protocol_fee_rate, + platform_fee_rate, + share_fee_rate, + virtual_base, + virtual_quote, + real_base, + real_quote, + slippage_basis_points, + ); + + let amount_out = 25959582643397; + let get_amount_in_result = get_amount_in( + amount_out, + protocol_fee_rate, + platform_fee_rate, + share_fee_rate, + virtual_base, + virtual_quote, + real_base, + real_quote, + slippage_basis_points, + ); + + println!("Original amount_in: {}", original_amount_in); + println!("Amount_out: {}", geet_amount_out_result); + println!("Calculated amount_in: {}", get_amount_in_result); + + assert!(geet_amount_out_result == 66275810509273); + assert!(get_amount_in_result == 753217040); + } +} diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 192b732..d9ad774 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -5,7 +5,11 @@ use std::sync::Arc; use super::traits::ProtocolParams; use crate::common::bonding_curve::BondingCurveAccount; use crate::common::{PriorityFee, SolanaRpcClient}; +use crate::constants::bonk::accounts::{PLATFORM_FEE_RATE, PROTOCOL_FEE_RATE, SHARE_FEE_RATE}; +use crate::streaming::event_parser::common::EventType; +use crate::streaming::event_parser::protocols::bonk::BonkTradeEvent; use crate::swqos::SwqosClient; +use crate::trading::bonk::common::{get_amount_in, get_amount_in_net, get_amount_out}; /// 通用买入参数 #[derive(Clone)] @@ -141,6 +145,64 @@ impl BonkParams { auto_handle_wsol: true, } } + pub fn from_trade(trade_info: BonkTradeEvent) -> Self { + Self { + virtual_base: Some(trade_info.virtual_base as u128), + virtual_quote: Some(trade_info.virtual_quote as u128), + real_base: Some(trade_info.real_base_after as u128), + real_quote: Some(trade_info.real_quote_after as u128), + auto_handle_wsol: true, + } + } + + pub fn from_dev_trade(trade_info: BonkTradeEvent) -> Self { + const DEFAULT_VIRTUAL_BASE: u128 = 1073025605596382; + const DEFAULT_VIRTUAL_QUOTE: u128 = 30000852951; + let amount_in = if trade_info.metadata.event_type == EventType::BonkBuyExactIn { + trade_info.amount_in + } else { + get_amount_in( + trade_info.amount_out, + PROTOCOL_FEE_RATE, + PLATFORM_FEE_RATE, + SHARE_FEE_RATE, + DEFAULT_VIRTUAL_BASE, + DEFAULT_VIRTUAL_QUOTE, + 0, + 0, + 0, + ) + }; + let real_quote = get_amount_in_net( + amount_in, + PROTOCOL_FEE_RATE, + PLATFORM_FEE_RATE, + SHARE_FEE_RATE, + ) as u128; + let amount_out = if trade_info.metadata.event_type == EventType::BonkBuyExactIn { + get_amount_out( + trade_info.amount_in, + PROTOCOL_FEE_RATE, + PLATFORM_FEE_RATE, + SHARE_FEE_RATE, + DEFAULT_VIRTUAL_BASE, + DEFAULT_VIRTUAL_QUOTE, + 0, + 0, + 0, + ) as u128 + } else { + trade_info.amount_out as u128 + }; + let real_base = amount_out; + Self { + virtual_base: Some(DEFAULT_VIRTUAL_BASE), + virtual_quote: Some(DEFAULT_VIRTUAL_QUOTE), + real_base: Some(real_base), + real_quote: Some(real_quote), + auto_handle_wsol: true, + } + } } impl ProtocolParams for BonkParams {