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
This commit is contained in:
ysq
2025-07-14 22:26:03 +08:00
parent 1ca0dfdd53
commit aef9fe6b14
5 changed files with 433 additions and 7 deletions
+88 -2
View File
@@ -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<dyn std::error::Error>> {
println!("Testing Bonk trading...");
+88 -2
View File
@@ -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<dyn std::error::Error>> {
println!("Testing Bonk trading...");
+88 -3
View File
@@ -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<SwqosConfig>) -> 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<dyn std::error::Error>> {
println!("Testing Bonk trading...");
+107
View File
@@ -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);
}
}
+62
View File
@@ -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 {