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:
@@ -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
@@ -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
@@ -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...");
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user