feat: major refactor with calculation utilities and instruction optimization
- Add comprehensive calculation utilities for all protocols (bonk, pumpfun, pumpswap, raydium) - Refactor instruction modules to reduce code complexity and improve maintainability - Optimize trading parameters structure and enhance common utilities - Separate calculation logic from trading modules for better code organization - Update dependencies and module exports
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@ readme = "README.md"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
solana-streamer-sdk = "0.3.1"
|
||||
solana-streamer-sdk = "0.3.3"
|
||||
solana-sdk = "2.3.0"
|
||||
solana-client = "2.3.6"
|
||||
solana-program = "2.3.0"
|
||||
|
||||
@@ -25,6 +25,12 @@ pub mod accounts {
|
||||
pub const TOKEN_PROGRAM: Pubkey = spl_token::ID;
|
||||
pub const WSOL_TOKEN_ACCOUNT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
|
||||
pub const RAYDIUM_CPMM: Pubkey = pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C");
|
||||
|
||||
pub const FEE_RATE_DENOMINATOR_VALUE: u128 = 1_000_000;
|
||||
pub const TRADE_FEE_RATE: u64 = 2500;
|
||||
pub const CREATOR_FEE_RATE: u64 = 0;
|
||||
pub const PROTOCOL_FEE_RATE: u64 = 120000;
|
||||
pub const FUND_FEE_RATE: u64 = 40000;
|
||||
}
|
||||
|
||||
pub const SWAP_BASE_IN_DISCRIMINATOR: &[u8] = &[143, 190, 90, 218, 196, 30, 51, 222];
|
||||
|
||||
+59
-48
@@ -10,16 +10,19 @@ use crate::{
|
||||
trade::trade::DEFAULT_SLIPPAGE,
|
||||
},
|
||||
trading::{
|
||||
bonk::common::{fetch_pool_state, get_amount_out, get_pool_pda, get_vault_pda},
|
||||
bonk::common::{get_pool_pda, get_vault_pda},
|
||||
common::utils::get_token_balance,
|
||||
core::{
|
||||
params::{BonkParams, BuyParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
},
|
||||
utils::calc::bonk::{
|
||||
get_buy_token_amount_from_sol_amount, get_sell_sol_amount_from_token_amount,
|
||||
},
|
||||
};
|
||||
|
||||
/// Bonk协议的指令构建器
|
||||
/// Instruction builder for Bonk protocol
|
||||
pub struct BonkInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -37,7 +40,7 @@ impl InstructionBuilder for BonkInstructionBuilder {
|
||||
}
|
||||
|
||||
impl BonkInstructionBuilder {
|
||||
/// 使用提供的账户信息构建买入指令
|
||||
/// Build buy instructions with provided account information
|
||||
async fn build_buy_instructions_with_accounts(
|
||||
&self,
|
||||
params: &BuyParams,
|
||||
@@ -50,7 +53,7 @@ impl BonkInstructionBuilder {
|
||||
|
||||
let pool_state = get_pool_pda(¶ms.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
|
||||
// 创建用户代币账户
|
||||
// Create user token accounts
|
||||
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
@@ -60,31 +63,20 @@ impl BonkInstructionBuilder {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
// Get pool token accounts
|
||||
let base_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap();
|
||||
let quote_vault_account =
|
||||
get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
|
||||
let mut virtual_base = protocol_params.virtual_base.unwrap_or(0);
|
||||
let mut virtual_quote = protocol_params.virtual_quote.unwrap_or(0);
|
||||
let mut real_base = protocol_params.real_base.unwrap_or(0);
|
||||
let mut real_quote = protocol_params.real_quote.unwrap_or(0);
|
||||
|
||||
if virtual_base == 0 || virtual_quote == 0 || real_base == 0 || real_quote == 0 {
|
||||
let pool = fetch_pool_state(params.rpc.as_ref().unwrap(), &pool_state).await?;
|
||||
virtual_base = pool.virtual_base as u128;
|
||||
virtual_quote = pool.virtual_quote as u128;
|
||||
real_base = pool.real_base as u128;
|
||||
real_quote = pool.real_quote as u128;
|
||||
}
|
||||
let virtual_base = protocol_params.virtual_base;
|
||||
let virtual_quote = protocol_params.virtual_quote;
|
||||
let real_base = protocol_params.real_base;
|
||||
let real_quote = protocol_params.real_quote;
|
||||
|
||||
let amount_in: u64 = params.sol_amount;
|
||||
let share_fee_rate: u64 = 0;
|
||||
let minimum_amount_out: u64 = get_amount_out(
|
||||
let minimum_amount_out: u64 = get_buy_token_amount_from_sol_amount(
|
||||
amount_in,
|
||||
accounts::PROTOCOL_FEE_RATE,
|
||||
accounts::PLATFORM_FEE_RATE,
|
||||
accounts::SHARE_FEE_RATE,
|
||||
virtual_base,
|
||||
virtual_quote,
|
||||
real_base,
|
||||
@@ -95,9 +87,9 @@ impl BonkInstructionBuilder {
|
||||
let mut instructions = vec![];
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
// 插入wsol
|
||||
// Handle wSOL
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
// Create wSOL ATA account if it doesn't exist
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
@@ -106,11 +98,11 @@ impl BonkInstructionBuilder {
|
||||
),
|
||||
);
|
||||
instructions.push(
|
||||
// 将SOL转入wSOL ATA账户
|
||||
// Transfer SOL to wSOL ATA account
|
||||
transfer(¶ms.payer.pubkey(), &user_quote_token_account, amount_in),
|
||||
);
|
||||
|
||||
// 同步wSOL余额
|
||||
// Sync wSOL balance
|
||||
instructions.push(
|
||||
spl_token::instruction::sync_native(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
@@ -120,7 +112,7 @@ impl BonkInstructionBuilder {
|
||||
);
|
||||
}
|
||||
|
||||
// 创建用户的基础代币账户
|
||||
// Create user's base token account
|
||||
instructions.push(create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
@@ -128,7 +120,7 @@ impl BonkInstructionBuilder {
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
));
|
||||
|
||||
// 创建买入指令
|
||||
// Create buy instruction
|
||||
let accounts = vec![
|
||||
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
|
||||
@@ -141,12 +133,15 @@ impl BonkInstructionBuilder {
|
||||
solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Base Token Mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Quote Token Mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Base Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(
|
||||
protocol_params.mint_token_program,
|
||||
false,
|
||||
), // Base Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Quote Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // Event Authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::BONK, false), // Program (readonly)
|
||||
];
|
||||
// 创建指令数据
|
||||
// Create instruction data
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&BUY_EXECT_IN_DISCRIMINATOR);
|
||||
data.extend_from_slice(&amount_in.to_le_bytes());
|
||||
@@ -156,7 +151,7 @@ impl BonkInstructionBuilder {
|
||||
instructions.push(Instruction { program_id: accounts::BONK, accounts, data });
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
// 关闭wSOL ATA账户,回收租金
|
||||
// Close wSOL ATA account, reclaim rent
|
||||
instructions.push(
|
||||
spl_token::instruction::close_account(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
@@ -172,7 +167,7 @@ impl BonkInstructionBuilder {
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
/// 使用提供的账户信息构建卖出指令
|
||||
/// Build sell instructions with provided account information
|
||||
async fn build_sell_instructions_with_accounts(
|
||||
&self,
|
||||
params: &SellParams,
|
||||
@@ -180,9 +175,16 @@ impl BonkInstructionBuilder {
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<BonkParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?;
|
||||
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
|
||||
// 获取代币余额
|
||||
// Get token balance
|
||||
let mut amount = params.token_amount;
|
||||
if params.token_amount.is_none() || params.token_amount.unwrap_or(0) == 0 {
|
||||
let balance_u64 =
|
||||
@@ -195,12 +197,24 @@ impl BonkInstructionBuilder {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
}
|
||||
|
||||
// 计算预期的SOL数量
|
||||
let minimum_amount_out: u64 = 1;
|
||||
|
||||
let pool_state = get_pool_pda(¶ms.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
|
||||
// 创建用户代币账户
|
||||
let virtual_base = protocol_params.virtual_base;
|
||||
let virtual_quote = protocol_params.virtual_quote;
|
||||
let real_base = protocol_params.real_base;
|
||||
let real_quote = protocol_params.real_quote;
|
||||
|
||||
// Calculate expected SOL amount
|
||||
let minimum_amount_out: u64 = get_sell_sol_amount_from_token_amount(
|
||||
amount,
|
||||
virtual_base,
|
||||
virtual_quote,
|
||||
real_base,
|
||||
real_quote,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
|
||||
);
|
||||
|
||||
// Create user token accounts
|
||||
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
@@ -210,7 +224,7 @@ impl BonkInstructionBuilder {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
// Get pool token accounts
|
||||
let base_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap();
|
||||
let quote_vault_account =
|
||||
get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
@@ -219,9 +233,9 @@ impl BonkInstructionBuilder {
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
// 插入wsol
|
||||
// Handle wSOL
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
// Create wSOL ATA account if it doesn't exist
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
@@ -230,7 +244,7 @@ impl BonkInstructionBuilder {
|
||||
),
|
||||
);
|
||||
|
||||
// 创建卖出指令
|
||||
// Create sell instruction
|
||||
let accounts = vec![
|
||||
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
|
||||
@@ -243,13 +257,16 @@ impl BonkInstructionBuilder {
|
||||
solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Base Token Mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Quote Token Mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Base Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(
|
||||
protocol_params.mint_token_program,
|
||||
false,
|
||||
), // Base Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Quote Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // Event Authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::BONK, false), // Program (readonly)
|
||||
];
|
||||
|
||||
// 创建指令数据
|
||||
// Create instruction data
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&SELL_EXECT_IN_DISCRIMINATOR);
|
||||
data.extend_from_slice(&amount.to_le_bytes());
|
||||
@@ -258,12 +275,6 @@ impl BonkInstructionBuilder {
|
||||
|
||||
instructions.push(Instruction { program_id: accounts::BONK, accounts, data });
|
||||
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<BonkParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?;
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
instructions.push(
|
||||
close_account(
|
||||
|
||||
+41
-46
@@ -1,5 +1,5 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use solana_sdk::{instruction::Instruction, native_token::sol_str_to_lamports};
|
||||
use solana_sdk::instruction::Instruction;
|
||||
use spl_associated_token_account::{
|
||||
get_associated_token_address, instruction::create_associated_token_account,
|
||||
};
|
||||
@@ -10,6 +10,10 @@ use crate::{
|
||||
trading::pumpfun::common::{
|
||||
get_bonding_curve_pda, get_global_volume_accumulator_pda, get_user_volume_accumulator_pda,
|
||||
},
|
||||
utils::calc::{
|
||||
common::{calculate_with_slippage_buy, calculate_with_slippage_sell},
|
||||
pumpfun::{get_buy_token_amount_from_sol_amount, get_sell_sol_amount_from_token_amount},
|
||||
},
|
||||
};
|
||||
|
||||
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, signer::Signer};
|
||||
@@ -17,21 +21,20 @@ use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, s
|
||||
use crate::{
|
||||
constants::pumpfun::global_constants::FEE_RECIPIENT,
|
||||
constants::trade::trade::DEFAULT_SLIPPAGE,
|
||||
trading::common::utils::calculate_with_slippage_buy,
|
||||
trading::core::{
|
||||
params::{BuyParams, PumpFunParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
trading::pumpfun::common::{get_buy_token_amount_from_sol_amount, get_creator_vault_pda},
|
||||
trading::pumpfun::common::get_creator_vault_pda,
|
||||
};
|
||||
|
||||
/// PumpFun协议的指令构建器
|
||||
/// Instruction builder for PumpFun protocol
|
||||
pub struct PumpFunInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
|
||||
// 获取PumpFun特定参数
|
||||
// Get PumpFun specific parameters
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
@@ -42,11 +45,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
}
|
||||
|
||||
let bonding_curve = if protocol_params.bonding_curve.is_some() {
|
||||
protocol_params.bonding_curve.clone().unwrap()
|
||||
} else {
|
||||
return Err(anyhow!("Bonding curve not found"));
|
||||
};
|
||||
let bonding_curve = protocol_params.bonding_curve.clone();
|
||||
|
||||
let max_sol_cost = calculate_with_slippage_buy(
|
||||
params.sol_amount,
|
||||
@@ -54,19 +53,17 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
);
|
||||
let creator_vault_pda = bonding_curve.get_creator_vault_pda();
|
||||
|
||||
let mut buy_token_amount =
|
||||
get_buy_token_amount_from_sol_amount(&bonding_curve, params.sol_amount);
|
||||
if buy_token_amount <= 100 * 1_000_000_u64 {
|
||||
buy_token_amount = if max_sol_cost > sol_str_to_lamports("0.01").unwrap_or(0) {
|
||||
25547619 * 1_000_000_u64
|
||||
} else {
|
||||
255476 * 1_000_000_u64
|
||||
};
|
||||
}
|
||||
let buy_token_amount = get_buy_token_amount_from_sol_amount(
|
||||
bonding_curve.virtual_token_reserves as u128,
|
||||
bonding_curve.virtual_sol_reserves as u128,
|
||||
bonding_curve.real_token_reserves as u128,
|
||||
bonding_curve.creator,
|
||||
params.sol_amount,
|
||||
);
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
// 创建关联代币账户
|
||||
// Create associated token account
|
||||
instructions.push(create_associated_token_account(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
@@ -74,23 +71,29 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
&constants::pumpfun::accounts::TOKEN_PROGRAM,
|
||||
));
|
||||
|
||||
// 创建买入指令
|
||||
// Create buy instruction
|
||||
instructions.push(buy(
|
||||
params.payer.as_ref(),
|
||||
¶ms.mint,
|
||||
&bonding_curve.account,
|
||||
&creator_vault_pda,
|
||||
&FEE_RECIPIENT,
|
||||
Buy {
|
||||
_amount: buy_token_amount,
|
||||
_max_sol_cost: max_sol_cost,
|
||||
},
|
||||
Buy { _amount: buy_token_amount, _max_sol_cost: max_sol_cost },
|
||||
));
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
|
||||
// Get PumpFun specific parameters
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<PumpFunParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?;
|
||||
|
||||
let bonding_curve = protocol_params.bonding_curve.clone();
|
||||
|
||||
let token_amount = if let Some(amount) = params.token_amount {
|
||||
if amount == 0 {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
@@ -102,35 +105,27 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
let creator_vault_pda = get_creator_vault_pda(¶ms.creator).unwrap();
|
||||
let ata = get_associated_token_address(¶ms.payer.pubkey(), ¶ms.mint);
|
||||
|
||||
// 获取代币余额
|
||||
let balance_u64 = if let Some(rpc) = ¶ms.rpc {
|
||||
let balance = rpc.get_token_account_balance(&ata).await?;
|
||||
balance
|
||||
.amount
|
||||
.parse::<u64>()
|
||||
.map_err(|_| anyhow!("Failed to parse token balance"))?
|
||||
} else {
|
||||
return Err(anyhow!("RPC client is required to get token balance"));
|
||||
};
|
||||
|
||||
let mut token_amount = token_amount;
|
||||
if token_amount > balance_u64 {
|
||||
token_amount = balance_u64;
|
||||
}
|
||||
let sol_amount = get_sell_sol_amount_from_token_amount(
|
||||
bonding_curve.virtual_token_reserves as u128,
|
||||
bonding_curve.virtual_sol_reserves as u128,
|
||||
bonding_curve.creator,
|
||||
token_amount,
|
||||
);
|
||||
let min_sol_output = calculate_with_slippage_sell(
|
||||
sol_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
|
||||
let mut instructions = vec![sell(
|
||||
params.payer.as_ref(),
|
||||
¶ms.mint,
|
||||
&creator_vault_pda,
|
||||
&FEE_RECIPIENT,
|
||||
Sell {
|
||||
_amount: token_amount,
|
||||
_min_sol_output: 1,
|
||||
},
|
||||
Sell { _amount: token_amount, _min_sol_output: min_sol_output },
|
||||
)];
|
||||
|
||||
// 如果卖出全部代币,关闭账户
|
||||
if token_amount >= balance_u64 {
|
||||
// If selling all tokens, close the account
|
||||
if protocol_params.close_token_account_when_sell.unwrap_or(false) {
|
||||
instructions.push(close_account(
|
||||
&spl_token::ID,
|
||||
&ata,
|
||||
|
||||
+100
-181
@@ -10,20 +10,16 @@ use crate::{
|
||||
trade::trade::DEFAULT_SLIPPAGE,
|
||||
},
|
||||
trading::{
|
||||
common::utils::{
|
||||
calculate_with_slippage_buy, calculate_with_slippage_sell, get_token_balance,
|
||||
},
|
||||
core::{
|
||||
params::{BuyParams, PumpSwapParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
pumpswap::{
|
||||
self,
|
||||
common::{
|
||||
coin_creator_vault_ata, coin_creator_vault_authority, fee_recipient_ata, fetch_pool, find_pool, get_global_volume_accumulator_pda, get_token_amount, get_user_volume_accumulator_pda, get_wsol_amount
|
||||
},
|
||||
pumpswap::common::{
|
||||
coin_creator_vault_ata, coin_creator_vault_authority, fee_recipient_ata,
|
||||
get_global_volume_accumulator_pda, get_user_volume_accumulator_pda,
|
||||
},
|
||||
},
|
||||
utils::calc::pumpswap::{buy_quote_input_internal, sell_base_input_internal},
|
||||
};
|
||||
|
||||
/// Instruction builder for PumpSwap protocol
|
||||
@@ -44,40 +40,21 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
}
|
||||
|
||||
// Build instructions based on whether account information is provided
|
||||
match (&protocol_params.pool,) {
|
||||
(Some(pool),) => {
|
||||
let mut base_mint = params.mint;
|
||||
let mut quote_mint = accounts::WSOL_TOKEN_ACCOUNT;
|
||||
let mut pool_base_token_reserves = 0;
|
||||
let mut pool_quote_token_reserves = 0;
|
||||
let base_mint = protocol_params.base_mint;
|
||||
let quote_mint = protocol_params.quote_mint;
|
||||
let pool_base_token_reserves = protocol_params.pool_base_token_reserves;
|
||||
let pool_quote_token_reserves = protocol_params.pool_quote_token_reserves;
|
||||
|
||||
if let Some(p_base_mint) = protocol_params.base_mint {
|
||||
base_mint = p_base_mint;
|
||||
}
|
||||
if let Some(p_quote_mint) = protocol_params.quote_mint {
|
||||
quote_mint = p_quote_mint;
|
||||
}
|
||||
if let Some(p_pool_base_token_reserves) = protocol_params.pool_base_token_reserves {
|
||||
pool_base_token_reserves = p_pool_base_token_reserves;
|
||||
}
|
||||
if let Some(p_pool_quote_token_reserves) = protocol_params.pool_quote_token_reserves
|
||||
{
|
||||
pool_quote_token_reserves = p_pool_quote_token_reserves;
|
||||
}
|
||||
|
||||
self.build_buy_instructions_with_accounts(
|
||||
params,
|
||||
*pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
protocol_params.auto_handle_wsol,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => self.build_buy_instructions_auto_discover(params).await,
|
||||
}
|
||||
self.build_buy_instructions_with_accounts(
|
||||
params,
|
||||
protocol_params.pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
protocol_params.auto_handle_wsol,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
|
||||
@@ -88,104 +65,25 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
.downcast_ref::<PumpSwapParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
|
||||
// Build instructions based on whether account information is provided
|
||||
match (&protocol_params.pool,) {
|
||||
(Some(pool),) => {
|
||||
let mut base_mint = params.mint;
|
||||
let mut quote_mint = accounts::WSOL_TOKEN_ACCOUNT;
|
||||
let mut pool_base_token_reserves = 0;
|
||||
let mut pool_quote_token_reserves = 0;
|
||||
if let Some(p_base_mint) = protocol_params.base_mint {
|
||||
base_mint = p_base_mint;
|
||||
}
|
||||
if let Some(p_quote_mint) = protocol_params.quote_mint {
|
||||
quote_mint = p_quote_mint;
|
||||
}
|
||||
if let Some(p_pool_base_token_reserves) = protocol_params.pool_base_token_reserves {
|
||||
pool_base_token_reserves = p_pool_base_token_reserves;
|
||||
}
|
||||
if let Some(p_pool_quote_token_reserves) = protocol_params.pool_quote_token_reserves
|
||||
{
|
||||
pool_quote_token_reserves = p_pool_quote_token_reserves;
|
||||
}
|
||||
self.build_sell_instructions_with_accounts(
|
||||
params,
|
||||
*pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
protocol_params.auto_handle_wsol,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => self.build_sell_instructions_auto_discover(params).await,
|
||||
}
|
||||
let base_mint = protocol_params.base_mint;
|
||||
let quote_mint = protocol_params.quote_mint;
|
||||
let pool_base_token_reserves = protocol_params.pool_base_token_reserves;
|
||||
let pool_quote_token_reserves = protocol_params.pool_quote_token_reserves;
|
||||
|
||||
self.build_sell_instructions_with_accounts(
|
||||
params,
|
||||
protocol_params.pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
protocol_params.auto_handle_wsol,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl PumpSwapInstructionBuilder {
|
||||
/// Auto-discover pool and account information and build buy instructions
|
||||
async fn build_buy_instructions_auto_discover(
|
||||
&self,
|
||||
params: &BuyParams,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
println!("❗️Going through RPC request, increasing instruction building time");
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
// Find pool
|
||||
let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?;
|
||||
let pool_data = fetch_pool(rpc.as_ref(), &pool).await?;
|
||||
let pool_base_token_reserves =
|
||||
get_token_balance(rpc.as_ref(), &pool, &pool_data.base_mint).await?;
|
||||
let pool_quote_token_reserves =
|
||||
get_token_balance(rpc.as_ref(), &pool, &pool_data.quote_mint).await?;
|
||||
let mut params = params.clone();
|
||||
params.creator = pool_data.coin_creator;
|
||||
self.build_buy_instructions_with_accounts(
|
||||
¶ms,
|
||||
pool,
|
||||
pool_data.base_mint,
|
||||
pool_data.quote_mint,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Auto-discover pool and account information and build sell instructions
|
||||
async fn build_sell_instructions_auto_discover(
|
||||
&self,
|
||||
params: &SellParams,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
println!("❗️Going through RPC request, increasing instruction building time");
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
// Find pool
|
||||
let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?;
|
||||
let pool_data = fetch_pool(rpc.as_ref(), &pool).await?;
|
||||
let pool_base_token_reserves =
|
||||
get_token_balance(rpc.as_ref(), &pool, &pool_data.base_mint).await?;
|
||||
let pool_quote_token_reserves =
|
||||
get_token_balance(rpc.as_ref(), &pool, &pool_data.quote_mint).await?;
|
||||
let mut params = params.clone();
|
||||
params.creator = pool_data.coin_creator;
|
||||
self.build_sell_instructions_with_accounts(
|
||||
¶ms,
|
||||
pool,
|
||||
pool_data.base_mint,
|
||||
pool_data.quote_mint,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Build buy instructions with provided account information
|
||||
async fn build_buy_instructions_with_accounts(
|
||||
&self,
|
||||
@@ -201,38 +99,36 @@ impl PumpSwapInstructionBuilder {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
let quote_mint_is_wsol = quote_mint == accounts::WSOL_TOKEN_ACCOUNT;
|
||||
// Calculate token amount
|
||||
let mut token_amount = get_token_amount(
|
||||
quote_mint_is_wsol,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
params.sol_amount,
|
||||
accounts::LP_FEE_BASIS_POINTS,
|
||||
accounts::PROTOCOL_FEE_BASIS_POINTS,
|
||||
if params.creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
accounts::COIN_CREATOR_FEE_BASIS_POINTS
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
if !quote_mint_is_wsol {
|
||||
// min_quote_amount_out
|
||||
token_amount = calculate_with_slippage_sell(
|
||||
token_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
}
|
||||
let sol_amount = if quote_mint_is_wsol {
|
||||
// max_quote_amount_in
|
||||
calculate_with_slippage_buy(
|
||||
|
||||
let mut token_amount = 0;
|
||||
let mut sol_amount = 0;
|
||||
if quote_mint_is_wsol {
|
||||
let result = buy_quote_input_internal(
|
||||
params.sol_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
¶ms.creator,
|
||||
)
|
||||
.unwrap();
|
||||
// base_amount_out
|
||||
token_amount = result.base;
|
||||
// max_quote_amount_in
|
||||
sol_amount = result.max_quote;
|
||||
} else {
|
||||
let result = sell_base_input_internal(
|
||||
params.sol_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
¶ms.creator,
|
||||
)
|
||||
.unwrap();
|
||||
// min_quote_amount_out
|
||||
token_amount = result.min_quote;
|
||||
// base_amount_in
|
||||
params.sol_amount
|
||||
};
|
||||
sol_amount = params.sol_amount;
|
||||
}
|
||||
|
||||
// Create user token accounts
|
||||
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
@@ -351,11 +247,15 @@ impl PumpSwapInstructionBuilder {
|
||||
let mut data = vec![];
|
||||
if quote_mint_is_wsol {
|
||||
data.extend_from_slice(&BUY_DISCRIMINATOR);
|
||||
// base_amount_out
|
||||
data.extend_from_slice(&token_amount.to_le_bytes());
|
||||
// max_quote_amount_in
|
||||
data.extend_from_slice(&sol_amount.to_le_bytes());
|
||||
} else {
|
||||
data.extend_from_slice(&SELL_DISCRIMINATOR);
|
||||
// base_amount_in
|
||||
data.extend_from_slice(&sol_amount.to_le_bytes());
|
||||
// min_quote_amount_out
|
||||
data.extend_from_slice(&token_amount.to_le_bytes());
|
||||
}
|
||||
|
||||
@@ -394,27 +294,42 @@ impl PumpSwapInstructionBuilder {
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
if params.token_amount.is_none() {
|
||||
return Err(anyhow!("Token amount is not set"));
|
||||
}
|
||||
|
||||
let quote_mint_is_wsol = quote_mint == accounts::WSOL_TOKEN_ACCOUNT;
|
||||
let mut sol_amount = get_wsol_amount(
|
||||
quote_mint_is_wsol,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
params.token_amount.unwrap_or(0),
|
||||
accounts::LP_FEE_BASIS_POINTS,
|
||||
accounts::PROTOCOL_FEE_BASIS_POINTS,
|
||||
if params.creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
accounts::COIN_CREATOR_FEE_BASIS_POINTS
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
sol_amount = calculate_with_slippage_sell(
|
||||
sol_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
let token_amount = params.token_amount.unwrap_or(0);
|
||||
|
||||
let mut token_amount = 0;
|
||||
let mut sol_amount = 0;
|
||||
|
||||
if quote_mint_is_wsol {
|
||||
let result = sell_base_input_internal(
|
||||
params.token_amount.unwrap(),
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
¶ms.creator,
|
||||
)
|
||||
.unwrap();
|
||||
// base_amount_in
|
||||
token_amount = params.token_amount.unwrap();
|
||||
// min_quote_amount_out
|
||||
sol_amount = result.min_quote;
|
||||
} else {
|
||||
let result = buy_quote_input_internal(
|
||||
params.token_amount.unwrap(),
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
¶ms.creator,
|
||||
)
|
||||
.unwrap();
|
||||
// max_quote_amount_in
|
||||
token_amount = result.max_quote;
|
||||
// base_amount_out
|
||||
sol_amount = result.base;
|
||||
}
|
||||
|
||||
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator, quote_mint);
|
||||
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
|
||||
@@ -502,11 +417,15 @@ impl PumpSwapInstructionBuilder {
|
||||
let mut data = vec![];
|
||||
if quote_mint_is_wsol {
|
||||
data.extend_from_slice(&SELL_DISCRIMINATOR);
|
||||
// base_amount_in
|
||||
data.extend_from_slice(&token_amount.to_le_bytes());
|
||||
// min_quote_amount_out
|
||||
data.extend_from_slice(&sol_amount.to_le_bytes());
|
||||
} else {
|
||||
data.extend_from_slice(&BUY_DISCRIMINATOR);
|
||||
// base_amount_out
|
||||
data.extend_from_slice(&sol_amount.to_le_bytes());
|
||||
// max_quote_amount_in
|
||||
data.extend_from_slice(&token_amount.to_le_bytes());
|
||||
}
|
||||
|
||||
|
||||
+75
-124
@@ -5,20 +5,21 @@ use spl_associated_token_account::instruction::create_associated_token_account_i
|
||||
use spl_token::instruction::close_account;
|
||||
|
||||
use crate::{
|
||||
constants::raydium_cpmm::{accounts, SWAP_BASE_IN_DISCRIMINATOR},
|
||||
constants::trade::trade::DEFAULT_SLIPPAGE,
|
||||
trading::common::utils::get_token_balance,
|
||||
trading::core::{
|
||||
params::{BuyParams, RaydiumCpmmParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
constants::{
|
||||
raydium_cpmm::{accounts, SWAP_BASE_IN_DISCRIMINATOR},
|
||||
trade::trade::DEFAULT_SLIPPAGE,
|
||||
},
|
||||
trading::raydium_cpmm::{
|
||||
common::{get_observation_state_pda, get_pool_pda, get_vault_pda},
|
||||
// pool::Pool,
|
||||
trading::{
|
||||
core::{
|
||||
params::{BuyParams, RaydiumCpmmParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
raydium_cpmm::common::{get_observation_state_pda, get_pool_pda, get_vault_pda},
|
||||
},
|
||||
utils::calc::raydium_cpmm::compute_swap_amount,
|
||||
};
|
||||
|
||||
/// RaydiumCpmm协议的指令构建器
|
||||
/// Instruction builder for RaydiumCpmm protocol
|
||||
pub struct RaydiumCpmmInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -36,7 +37,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder {
|
||||
}
|
||||
|
||||
impl RaydiumCpmmInstructionBuilder {
|
||||
/// 使用提供的账户信息构建买入指令
|
||||
/// Build buy instructions with provided account information
|
||||
async fn build_buy_instructions_with_accounts(
|
||||
&self,
|
||||
params: &BuyParams,
|
||||
@@ -47,26 +48,12 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
.downcast_ref::<RaydiumCpmmParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?;
|
||||
|
||||
let pool_state = if protocol_params.pool_state.is_some() {
|
||||
protocol_params.pool_state.unwrap()
|
||||
} else {
|
||||
let mint_token_in_pool_state_index =
|
||||
protocol_params.mint_token_in_pool_state_index.unwrap_or(1);
|
||||
get_pool_pda(
|
||||
&accounts::AMM_CONFIG,
|
||||
if mint_token_in_pool_state_index == 1 {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT
|
||||
} else {
|
||||
¶ms.mint
|
||||
},
|
||||
if mint_token_in_pool_state_index == 1 {
|
||||
¶ms.mint
|
||||
} else {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let pool_state = get_pool_pda(
|
||||
&accounts::AMM_CONFIG,
|
||||
&protocol_params.base_mint,
|
||||
&protocol_params.quote_mint,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let wsol_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
@@ -77,36 +64,29 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
¶ms.mint,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
// Get pool token accounts
|
||||
let wsol_vault_account = get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
let mint_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap();
|
||||
|
||||
let observation_state_account = get_observation_state_pda(&pool_state).unwrap();
|
||||
let is_base_in = protocol_params.base_mint == accounts::WSOL_TOKEN_ACCOUNT;
|
||||
|
||||
let amount_in: u64 = params.sol_amount;
|
||||
let mut minimum_amount_out: u64 = if protocol_params.minimum_amount_out.is_some() {
|
||||
protocol_params.minimum_amount_out.unwrap()
|
||||
} else {
|
||||
println!("未提供minimum_amount_out,使用默认值0");
|
||||
0
|
||||
};
|
||||
if minimum_amount_out != 0 {
|
||||
let slippage_basis_points: u64 = if params.slippage_basis_points.is_some() {
|
||||
params.slippage_basis_points.unwrap()
|
||||
} else {
|
||||
DEFAULT_SLIPPAGE
|
||||
} as u64;
|
||||
minimum_amount_out = minimum_amount_out * (10000 - slippage_basis_points) / 10000;
|
||||
println!("slippage_basis_points: {}", slippage_basis_points);
|
||||
}
|
||||
println!("minimum_amount_out: {}", minimum_amount_out);
|
||||
let result = compute_swap_amount(
|
||||
protocol_params.base_reserve,
|
||||
protocol_params.quote_reserve,
|
||||
is_base_in,
|
||||
amount_in,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
let minimum_amount_out = result.min_amount_out;
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
// 插入wsol
|
||||
// Handle wSOL
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
// Create wSOL ATA account if it doesn't exist
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
@@ -115,26 +95,31 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
),
|
||||
);
|
||||
instructions.push(
|
||||
// 将SOL转入wSOL ATA账户
|
||||
// Transfer SOL to wSOL ATA account
|
||||
transfer(¶ms.payer.pubkey(), &wsol_token_account, amount_in),
|
||||
);
|
||||
|
||||
// 同步wSOL余额
|
||||
// Sync wSOL balance
|
||||
instructions.push(
|
||||
spl_token::instruction::sync_native(&accounts::TOKEN_PROGRAM, &wsol_token_account)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// 创建用户的基础代币账户
|
||||
let mint_token_program = if is_base_in {
|
||||
protocol_params.quote_token_program
|
||||
} else {
|
||||
protocol_params.base_token_program
|
||||
};
|
||||
|
||||
instructions.push(create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
&protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM),
|
||||
&mint_token_program,
|
||||
));
|
||||
|
||||
// 创建买入指令
|
||||
// Create buy instruction
|
||||
let accounts = vec![
|
||||
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
|
||||
@@ -145,15 +130,12 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Input Vault Account
|
||||
solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Output Vault Account
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Input Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(
|
||||
protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM),
|
||||
false,
|
||||
), // Output Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(mint_token_program, false), // Output Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Input token mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Output token mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account
|
||||
];
|
||||
// 创建指令数据
|
||||
// Create instruction data
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR);
|
||||
data.extend_from_slice(&amount_in.to_le_bytes());
|
||||
@@ -162,7 +144,7 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
instructions.push(Instruction { program_id: accounts::RAYDIUM_CPMM, accounts, data });
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
// 关闭wSOL ATA账户,回收租金
|
||||
// Close wSOL ATA account, reclaim rent
|
||||
instructions.push(
|
||||
spl_token::instruction::close_account(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
@@ -178,7 +160,7 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
/// 使用提供的账户信息构建卖出指令
|
||||
/// Build sell instructions with provided account information
|
||||
async fn build_sell_instructions_with_accounts(
|
||||
&self,
|
||||
params: &SellParams,
|
||||
@@ -189,61 +171,27 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
.downcast_ref::<RaydiumCpmmParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?;
|
||||
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
|
||||
// 获取代币余额
|
||||
let mut amount = params.token_amount;
|
||||
if params.token_amount.is_none() || params.token_amount.unwrap_or(0) == 0 {
|
||||
let balance_u64 =
|
||||
get_token_balance(rpc.as_ref(), ¶ms.payer.pubkey(), ¶ms.mint).await?;
|
||||
amount = Some(balance_u64);
|
||||
}
|
||||
let amount = amount.unwrap_or(0);
|
||||
|
||||
if amount == 0 {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
return Err(anyhow!("Token amount is not set"));
|
||||
}
|
||||
|
||||
let mut minimum_amount_out: u64 = if protocol_params.minimum_amount_out.is_some() {
|
||||
protocol_params.minimum_amount_out.unwrap()
|
||||
} else {
|
||||
println!("未提供minimum_amount_out,使用默认值0");
|
||||
0
|
||||
};
|
||||
if minimum_amount_out != 0 {
|
||||
let slippage_basis_points: u64 = if params.slippage_basis_points.is_some() {
|
||||
params.slippage_basis_points.unwrap()
|
||||
} else {
|
||||
DEFAULT_SLIPPAGE
|
||||
} as u64;
|
||||
minimum_amount_out = minimum_amount_out * (10000 - slippage_basis_points) / 10000;
|
||||
println!("slippage_basis_points: {}", slippage_basis_points);
|
||||
}
|
||||
println!("minimum_amount_out: {}", minimum_amount_out);
|
||||
let is_base_in = protocol_params.base_mint == params.mint;
|
||||
|
||||
let pool_state = if protocol_params.pool_state.is_some() {
|
||||
protocol_params.pool_state.unwrap()
|
||||
} else {
|
||||
let mint_token_in_pool_state_index =
|
||||
protocol_params.mint_token_in_pool_state_index.unwrap_or(1);
|
||||
get_pool_pda(
|
||||
&accounts::AMM_CONFIG,
|
||||
if mint_token_in_pool_state_index == 1 {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT
|
||||
} else {
|
||||
¶ms.mint
|
||||
},
|
||||
if mint_token_in_pool_state_index == 1 {
|
||||
¶ms.mint
|
||||
} else {
|
||||
&accounts::WSOL_TOKEN_ACCOUNT
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let minimum_amount_out: u64 = compute_swap_amount(
|
||||
protocol_params.base_reserve,
|
||||
protocol_params.quote_reserve,
|
||||
is_base_in,
|
||||
params.token_amount.unwrap_or(0),
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
)
|
||||
.min_amount_out;
|
||||
|
||||
let pool_state = get_pool_pda(
|
||||
&accounts::AMM_CONFIG,
|
||||
&protocol_params.base_mint,
|
||||
&protocol_params.quote_mint,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let wsol_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
@@ -254,7 +202,7 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
¶ms.mint,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
// Get pool token accounts
|
||||
let wsol_vault_account = get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
let mint_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap();
|
||||
|
||||
@@ -262,18 +210,24 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
// 插入wsol
|
||||
// Handle wSOL
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
// Create wSOL ATA account if it doesn't exist
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&protocol_params.mint_token_program.unwrap(),
|
||||
&spl_token::ID,
|
||||
),
|
||||
);
|
||||
|
||||
// 创建卖出指令
|
||||
let mint_token_program = if is_base_in {
|
||||
protocol_params.base_token_program
|
||||
} else {
|
||||
protocol_params.quote_token_program
|
||||
};
|
||||
|
||||
// Create sell instruction
|
||||
let accounts = vec![
|
||||
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
|
||||
@@ -283,19 +237,16 @@ impl RaydiumCpmmInstructionBuilder {
|
||||
solana_sdk::instruction::AccountMeta::new(wsol_token_account, false), // Output Token Account
|
||||
solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Input Vault Account
|
||||
solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Output Vault Account
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(
|
||||
protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM),
|
||||
false,
|
||||
), // Input Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(mint_token_program, false), // Input Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Output Token Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Input token mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Output token mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account
|
||||
];
|
||||
// 创建指令数据
|
||||
// Create instruction data
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR);
|
||||
data.extend_from_slice(&amount.to_le_bytes());
|
||||
data.extend_from_slice(¶ms.token_amount.unwrap_or(0).to_le_bytes());
|
||||
data.extend_from_slice(&minimum_amount_out.to_le_bytes());
|
||||
|
||||
instructions.push(Instruction { program_id: accounts::RAYDIUM_CPMM, accounts, data });
|
||||
|
||||
+5
-27
@@ -175,22 +175,11 @@ impl SolanaTrade {
|
||||
slippage_basis_points: Option<u64>,
|
||||
recent_blockhash: Hash,
|
||||
custom_buy_tip_fee: Option<f64>,
|
||||
extension_params: Option<Box<dyn ProtocolParams>>,
|
||||
extension_params: Box<dyn ProtocolParams>,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let executor = TradeFactory::create_executor(dex_type.clone());
|
||||
let protocol_params = if let Some(params) = extension_params {
|
||||
params
|
||||
} else {
|
||||
match dex_type {
|
||||
DexType::PumpFun => Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::PumpSwap => Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::Bonk => Box::new(BonkParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::RaydiumCpmm => {
|
||||
Box::new(RaydiumCpmmParams::default()) as Box<dyn ProtocolParams>
|
||||
}
|
||||
}
|
||||
};
|
||||
let protocol_params = extension_params;
|
||||
|
||||
let final_lookup_table_key = lookup_table_key.or(self.trade_config.lookup_table_key);
|
||||
|
||||
@@ -308,22 +297,11 @@ impl SolanaTrade {
|
||||
recent_blockhash: Hash,
|
||||
custom_buy_tip_fee: Option<f64>,
|
||||
with_tip: bool,
|
||||
extension_params: Option<Box<dyn ProtocolParams>>,
|
||||
extension_params: Box<dyn ProtocolParams>,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let executor = TradeFactory::create_executor(dex_type.clone());
|
||||
let protocol_params = if let Some(params) = extension_params {
|
||||
params
|
||||
} else {
|
||||
match dex_type {
|
||||
DexType::PumpFun => Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::PumpSwap => Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::Bonk => Box::new(BonkParams::default()) as Box<dyn ProtocolParams>,
|
||||
DexType::RaydiumCpmm => {
|
||||
Box::new(RaydiumCpmmParams::default()) as Box<dyn ProtocolParams>
|
||||
}
|
||||
}
|
||||
};
|
||||
let protocol_params = extension_params;
|
||||
|
||||
let final_lookup_table_key = lookup_table_key.or(self.trade_config.lookup_table_key);
|
||||
|
||||
@@ -454,7 +432,7 @@ impl SolanaTrade {
|
||||
recent_blockhash: Hash,
|
||||
custom_buy_tip_fee: Option<f64>,
|
||||
with_tip: bool,
|
||||
extension_params: Option<Box<dyn ProtocolParams>>,
|
||||
extension_params: Box<dyn ProtocolParams>,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if percent == 0 || percent > 100 {
|
||||
|
||||
+255
-238
@@ -1,11 +1,5 @@
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use sol_trade_sdk::{
|
||||
common::{bonding_curve::BondingCurveAccount, AnyResult, PriorityFee, TradeConfig},
|
||||
swqos::{SwqosConfig, SwqosRegion},
|
||||
trading::{core::params::{BonkParams, PumpFunParams, PumpSwapParams, RaydiumCpmmParams}, factory::DexType, raydium_cpmm::common::{get_buy_token_amount, get_sell_sol_amount}},
|
||||
SolanaTrade,
|
||||
};
|
||||
use sol_trade_sdk::solana_streamer_sdk::{
|
||||
match_event,
|
||||
streaming::{
|
||||
@@ -16,15 +10,33 @@ use sol_trade_sdk::solana_streamer_sdk::{
|
||||
pumpswap::{
|
||||
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
|
||||
PumpSwapSellEvent, PumpSwapWithdrawEvent,
|
||||
}, raydium_cpmm::RaydiumCpmmSwapEvent,
|
||||
},
|
||||
raydium_cpmm::RaydiumCpmmSwapEvent,
|
||||
},
|
||||
Protocol, UnifiedEvent,
|
||||
},
|
||||
ShredStreamGrpc, YellowstoneGrpc,
|
||||
},
|
||||
};
|
||||
use sol_trade_sdk::{
|
||||
common::{AnyResult, PriorityFee, TradeConfig},
|
||||
swqos::{SwqosConfig, SwqosRegion},
|
||||
trading::{
|
||||
core::params::{BonkParams, PumpFunParams, PumpSwapParams, RaydiumCpmmParams},
|
||||
factory::DexType,
|
||||
},
|
||||
SolanaTrade,
|
||||
};
|
||||
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
|
||||
use solana_streamer_sdk::streaming::{event_parser::protocols::{bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID, pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID}, yellowstone_grpc::{AccountFilter, TransactionFilter}};
|
||||
use solana_streamer_sdk::streaming::{
|
||||
event_parser::protocols::{
|
||||
bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID,
|
||||
pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID,
|
||||
raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID,
|
||||
raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID,
|
||||
},
|
||||
yellowstone_grpc::{AccountFilter, TransactionFilter},
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -37,7 +49,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 创建 SolanaTrade 客户端
|
||||
/// Create SolanaTrade client
|
||||
/// Initializes a new SolanaTrade client with configuration
|
||||
async fn test_create_solana_trade_client() -> AnyResult<SolanaTrade> {
|
||||
println!("Creating SolanaTrade client...");
|
||||
|
||||
@@ -57,7 +70,7 @@ fn create_swqos_configs(rpc_url: &str) -> Vec<SwqosConfig> {
|
||||
vec![
|
||||
SwqosConfig::Jito("your api_token".to_string(), SwqosRegion::Frankfurt),
|
||||
SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
|
||||
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
|
||||
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
|
||||
SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt),
|
||||
SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
|
||||
SwqosConfig::Default(rpc_url.to_string()),
|
||||
@@ -83,39 +96,40 @@ async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> Any
|
||||
let buy_sol_cost = 100_000;
|
||||
let slippage_basis_points = Some(100);
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
let bonding_curve = BondingCurveAccount::from_trade(&trade_info);
|
||||
|
||||
// Buy tokens
|
||||
println!("Buying tokens from PumpFun...");
|
||||
client.buy(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(PumpFunParams {
|
||||
bonding_curve: Some(Arc::new(bonding_curve.clone())),
|
||||
})),
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.buy(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Box::new(PumpFunParams::from_trade(&trade_info, None)),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Sell tokens
|
||||
// Sell tokens
|
||||
println!("Selling tokens from PumpFun...");
|
||||
let amount_token = 0;
|
||||
client.sell(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.sell(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
Box::new(PumpFunParams::from_trade(&trade_info, None)),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -133,45 +147,52 @@ async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) ->
|
||||
let slippage_basis_points = Some(100);
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
|
||||
let bonding_curve = BondingCurveAccount::from_dev_trade(
|
||||
&mint_pubkey,
|
||||
trade_info.token_amount,
|
||||
trade_info.max_sol_cost,
|
||||
creator,
|
||||
);
|
||||
|
||||
// Buy tokens
|
||||
println!("Buying tokens from PumpFun...");
|
||||
let buy_sol_amount = 100_000;
|
||||
client.buy(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_amount,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(PumpFunParams {
|
||||
bonding_curve: Some(Arc::new(bonding_curve.clone())),
|
||||
})),
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.buy(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_amount,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Box::new(PumpFunParams::from_dev_trade(
|
||||
&mint_pubkey,
|
||||
trade_info.token_amount,
|
||||
trade_info.max_sol_cost,
|
||||
creator,
|
||||
None,
|
||||
)),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Sell tokens
|
||||
println!("Selling tokens from PumpFun...");
|
||||
let amount_token = 0;
|
||||
client.sell(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.sell(
|
||||
DexType::PumpFun,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
Box::new(PumpFunParams::from_dev_trade(
|
||||
&mint_pubkey,
|
||||
trade_info.token_amount,
|
||||
trade_info.max_sol_cost,
|
||||
creator,
|
||||
None,
|
||||
)),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -186,60 +207,46 @@ async fn test_pumpswap() -> AnyResult<()> {
|
||||
let slippage_basis_points = Some(100);
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
let pool_address = Pubkey::from_str("xxxxxxx")?;
|
||||
let base_mint = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?;
|
||||
let quote_mint = Pubkey::from_str("So11111111111111111111111111111111111111112")?;
|
||||
let pool_base_token_reserves = 0; // Input the correct value
|
||||
let pool_quote_token_reserves = 0; // Input the correct value
|
||||
|
||||
// Buy tokens
|
||||
println!("Buying tokens from PumpSwap...");
|
||||
client.buy(
|
||||
DexType::PumpSwap,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(PumpSwapParams {
|
||||
pool: Some(pool_address),
|
||||
base_mint: Some(base_mint),
|
||||
quote_mint: Some(quote_mint),
|
||||
pool_base_token_reserves: Some(pool_base_token_reserves),
|
||||
pool_quote_token_reserves: Some(pool_quote_token_reserves),
|
||||
auto_handle_wsol: true,
|
||||
})),
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.buy(
|
||||
DexType::PumpSwap,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
// 经过 rpc,增加耗时,可以通过from_buy_trade或者自行初始化PumpSwapParams参数来优化耗时
|
||||
Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Sell tokens
|
||||
println!("Selling tokens from PumpSwap...");
|
||||
let amount_token = 0;
|
||||
client.sell(
|
||||
DexType::PumpSwap,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
Some(Box::new(PumpSwapParams {
|
||||
pool: Some(pool_address),
|
||||
base_mint: Some(base_mint),
|
||||
quote_mint: Some(quote_mint),
|
||||
pool_base_token_reserves: Some(pool_base_token_reserves),
|
||||
pool_quote_token_reserves: Some(pool_quote_token_reserves),
|
||||
auto_handle_wsol: true,
|
||||
})),
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.sell(
|
||||
DexType::PumpSwap,
|
||||
mint_pubkey,
|
||||
Some(creator),
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
// 经过 rpc,增加耗时,可以通过from_sell_trade或者自行初始化PumpSwapParams参数来优化耗时
|
||||
Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> {
|
||||
println!("Testing Bonk trading...");
|
||||
|
||||
@@ -251,33 +258,37 @@ async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult
|
||||
|
||||
// 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))),
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.buy(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Box::new(BonkParams::from_trade(trade_info.clone())),
|
||||
None,
|
||||
)
|
||||
.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,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.sell(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
Box::new(BonkParams::from_trade(trade_info)),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -297,38 +308,41 @@ async fn test_bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyRe
|
||||
|
||||
// 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))),
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.buy(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Box::new(BonkParams::from_dev_trade(trade_info.clone())),
|
||||
None,
|
||||
)
|
||||
.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,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.sell(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
Box::new(BonkParams::from_dev_trade(trade_info)),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Testing Bonk trading...");
|
||||
|
||||
@@ -340,38 +354,43 @@ async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// Buy tokens
|
||||
println!("Buying tokens from letsbonk.fun...");
|
||||
client.buy(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.buy(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
// 经过 rpc,增加耗时,可以通过from_trade或者自行初始化BonkParams参数来优化耗时
|
||||
Box::new(BonkParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?),
|
||||
None,
|
||||
)
|
||||
.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,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.sell(
|
||||
DexType::Bonk,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
// 经过 rpc,增加耗时,可以通过from_trade或者自行初始化BonkParams参数来优化耗时
|
||||
Box::new(BonkParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Testing Raydium Cpmm trading...");
|
||||
|
||||
@@ -380,56 +399,52 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let buy_sol_cost = 100_000;
|
||||
let slippage_basis_points = Some(100);
|
||||
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
|
||||
let pool_state = Pubkey::from_str("xxxxxxx")?;
|
||||
let buy_amount_out = get_buy_token_amount(&client.rpc, &pool_state, buy_sol_cost).await?;
|
||||
let pool_address = Pubkey::from_str("xxxxxxx")?;
|
||||
// Buy tokens
|
||||
println!("Buying tokens from Raydium Cpmm...");
|
||||
client.buy(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
Some(Box::new(RaydiumCpmmParams {
|
||||
pool_state: Some(pool_state), // 如果不传,会自动计算
|
||||
mint_token_program: Some(spl_token::ID), // spl_token_2022::ID
|
||||
mint_token_in_pool_state_index: Some(1), // mint_token 在 pool_state 中的索引,默认在索引1
|
||||
minimum_amount_out: Some(buy_amount_out), // 如果不传、默认为0
|
||||
auto_handle_wsol: true,
|
||||
})),
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.buy(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
buy_sol_cost,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
// 经过 rpc,增加耗时,或者自行初始化RaydiumCpmmParams参数
|
||||
Box::new(
|
||||
RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?,
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Sell tokens
|
||||
println!("Selling tokens from Raydium Cpmm...");
|
||||
let amount_token = 0;
|
||||
let sell_sol_amount = get_sell_sol_amount(&client.rpc, &pool_state, amount_token).await?;
|
||||
client.sell(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
Some(Box::new(RaydiumCpmmParams {
|
||||
pool_state: Some(pool_state), // 如果不传,会自动计算
|
||||
mint_token_program: Some(spl_token::ID), // spl_token_2022::ID
|
||||
mint_token_in_pool_state_index: Some(1), // mint_token 在 pool_state 中的索引,默认在索引1
|
||||
minimum_amount_out: Some(sell_sol_amount), // 如果不传、默认为0
|
||||
auto_handle_wsol: true,
|
||||
})),
|
||||
None,
|
||||
).await?;
|
||||
client
|
||||
.sell(
|
||||
DexType::RaydiumCpmm,
|
||||
mint_pubkey,
|
||||
None,
|
||||
amount_token,
|
||||
slippage_basis_points,
|
||||
recent_blockhash,
|
||||
None,
|
||||
false,
|
||||
// 经过 rpc,增加耗时,或者自行初始化RaydiumCpmmParams参数
|
||||
Box::new(
|
||||
RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?,
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("正在订阅 GRPC 事件...");
|
||||
println!("Subscribing to GRPC events...");
|
||||
|
||||
let grpc = YellowstoneGrpc::new(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
@@ -437,37 +452,39 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
)?;
|
||||
|
||||
let callback = create_event_callback();
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
|
||||
let protocols =
|
||||
vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
|
||||
// Filter accounts
|
||||
let account_include = vec![
|
||||
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
|
||||
PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID
|
||||
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
|
||||
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
|
||||
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
|
||||
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
|
||||
PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID
|
||||
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
|
||||
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
|
||||
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
|
||||
RAYDIUM_AMM_V4_PROGRAM_ID.to_string(), // Listen to raydium_amm_v4 program ID
|
||||
"xxxxxxxx".to_string(), // Listen to xxxxx account
|
||||
"xxxxxxxx".to_string(), // Listen to xxxxx account
|
||||
];
|
||||
let account_exclude = vec![];
|
||||
let account_required = vec![];
|
||||
|
||||
// 监听交易数据
|
||||
let transaction_filter = TransactionFilter {
|
||||
// Listen to transaction data
|
||||
let transaction_filter = TransactionFilter {
|
||||
account_include: account_include.clone(),
|
||||
account_exclude,
|
||||
account_required,
|
||||
};
|
||||
|
||||
// 监听属于owner程序的账号数据 -> 账号事件监听
|
||||
// Listen to account data belonging to owner programs -> account event monitoring
|
||||
let account_filter = AccountFilter { account: vec![], owner: account_include.clone() };
|
||||
|
||||
println!("开始监听事件,按 Ctrl+C 停止...");
|
||||
println!("Starting to listen for events, press Ctrl+C to stop...");
|
||||
grpc.subscribe_events_immediate(
|
||||
protocols,
|
||||
None,
|
||||
transaction_filter,
|
||||
account_filter,
|
||||
None,
|
||||
None,
|
||||
callback,
|
||||
)
|
||||
.await?;
|
||||
@@ -476,14 +493,14 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("正在订阅 ShredStream 事件...");
|
||||
println!("Subscribing to ShredStream events...");
|
||||
|
||||
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
|
||||
let callback = create_event_callback();
|
||||
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
|
||||
|
||||
println!("开始监听事件,按 Ctrl+C 停止...");
|
||||
shred_stream.shredstream_subscribe(protocols, None, callback).await?;
|
||||
println!("Starting to listen for events, press Ctrl+C to stop...");
|
||||
shred_stream.shredstream_subscribe(protocols, None, None, callback).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -521,8 +538,8 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|
||||
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
|
||||
println!("RaydiumCpmmSwapEvent: {:?}", e);
|
||||
},
|
||||
// .....
|
||||
// 更多的事件和说明请参考 https://github.com/0xfnzero/solana-streamer
|
||||
// .....
|
||||
// For more events and documentation, please refer to https://github.com/0xfnzero/solana-streamer
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+15
-30
@@ -28,22 +28,6 @@ pub async fn get_sol_balance(
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
// Calculate slippage for buy operations
|
||||
#[inline]
|
||||
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
|
||||
amount + (amount * basis_points / 10000)
|
||||
}
|
||||
|
||||
// Calculate slippage for sell operations
|
||||
#[inline]
|
||||
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
|
||||
if amount <= basis_points / 10000 {
|
||||
1
|
||||
} else {
|
||||
amount - (amount * basis_points / 10000)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn transfer_sol(
|
||||
rpc: &SolanaRpcClient,
|
||||
payer: &Keypair,
|
||||
@@ -75,38 +59,39 @@ pub async fn transfer_sol(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 关闭代币账户
|
||||
/// Close token account
|
||||
///
|
||||
/// 此函数用于关闭指定代币的关联代币账户,将账户中的代币余额转移给账户所有者。
|
||||
/// This function is used to close the associated token account for a specified token,
|
||||
/// transferring the token balance in the account to the account owner.
|
||||
///
|
||||
/// # 参数
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rpc` - Solana RPC客户端
|
||||
/// * `payer` - 支付交易费用的账户
|
||||
/// * `mint` - 代币的Mint地址
|
||||
/// * `rpc` - Solana RPC client
|
||||
/// * `payer` - Account that pays transaction fees
|
||||
/// * `mint` - Token mint address
|
||||
///
|
||||
/// # 返回值
|
||||
/// # Returns
|
||||
///
|
||||
/// 返回一个Result,成功时返回(),失败时返回错误
|
||||
/// Returns a Result, success returns (), failure returns error
|
||||
pub async fn close_token_account(
|
||||
rpc: &SolanaRpcClient,
|
||||
payer: &Keypair,
|
||||
mint: &Pubkey,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// 获取关联代币账户地址
|
||||
// Get associated token account address
|
||||
let ata = get_associated_token_address(&payer.pubkey(), mint);
|
||||
|
||||
// 检查账户是否存在
|
||||
// Check if account exists
|
||||
let account_exists = rpc.get_account(&ata).await.is_ok();
|
||||
if !account_exists {
|
||||
return Ok(()); // 如果账户不存在,直接返回成功
|
||||
return Ok(()); // If account doesn't exist, return success directly
|
||||
}
|
||||
|
||||
// 构建关闭账户指令
|
||||
// Build close account instruction
|
||||
let close_account_ix =
|
||||
close_account(&spl_token::ID, &ata, &payer.pubkey(), &payer.pubkey(), &[&payer.pubkey()])?;
|
||||
|
||||
// 构建交易
|
||||
// Build transaction
|
||||
let recent_blockhash = rpc.get_latest_blockhash().await?;
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&[close_account_ix],
|
||||
@@ -115,7 +100,7 @@ pub async fn close_token_account(
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
// 发送交易
|
||||
// Send transaction
|
||||
rpc.send_and_confirm_transaction(&transaction).await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
+160
-75
@@ -1,17 +1,26 @@
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
|
||||
use solana_streamer_sdk::streaming::event_parser::protocols::pumpswap::{
|
||||
PumpSwapBuyEvent, PumpSwapSellEvent,
|
||||
};
|
||||
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::constants::bonk::accounts::{
|
||||
self, PLATFORM_FEE_RATE, PROTOCOL_FEE_RATE, SHARE_FEE_RATE,
|
||||
};
|
||||
use crate::solana_streamer_sdk::streaming::event_parser::common::EventType;
|
||||
use crate::solana_streamer_sdk::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};
|
||||
use crate::trading::pumpswap::common::get_token_balances;
|
||||
use crate::trading::raydium_cpmm::common::get_pool_token_balances;
|
||||
|
||||
/// 通用买入参数
|
||||
/// Common buy parameters
|
||||
/// Contains all necessary information for executing buy transactions
|
||||
#[derive(Clone)]
|
||||
pub struct BuyParams {
|
||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||
@@ -27,7 +36,8 @@ pub struct BuyParams {
|
||||
pub protocol_params: Box<dyn ProtocolParams>,
|
||||
}
|
||||
|
||||
/// 带MEV服务的买入参数
|
||||
/// Buy parameters with MEV service support
|
||||
/// Extends BuyParams with MEV client configurations for transaction acceleration
|
||||
#[derive(Clone)]
|
||||
pub struct BuyWithTipParams {
|
||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||
@@ -44,7 +54,8 @@ pub struct BuyWithTipParams {
|
||||
pub protocol_params: Box<dyn ProtocolParams>,
|
||||
}
|
||||
|
||||
/// 通用卖出参数
|
||||
/// Common sell parameters
|
||||
/// Contains all necessary information for executing sell transactions
|
||||
#[derive(Clone)]
|
||||
pub struct SellParams {
|
||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||
@@ -59,7 +70,8 @@ pub struct SellParams {
|
||||
pub protocol_params: Box<dyn ProtocolParams>,
|
||||
}
|
||||
|
||||
/// 带MEV服务的卖出参数
|
||||
/// Sell parameters with MEV service support
|
||||
/// Extends SellParams with MEV client configurations for transaction acceleration
|
||||
#[derive(Clone)]
|
||||
pub struct SellWithTipParams {
|
||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||
@@ -75,16 +87,39 @@ pub struct SellWithTipParams {
|
||||
pub protocol_params: Box<dyn ProtocolParams>,
|
||||
}
|
||||
|
||||
/// PumpFun协议特定参数
|
||||
/// PumpFun protocol specific parameters
|
||||
/// Configuration parameters specific to PumpFun trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct PumpFunParams {
|
||||
pub bonding_curve: Option<Arc<BondingCurveAccount>>,
|
||||
pub bonding_curve: Arc<BondingCurveAccount>,
|
||||
/// Whether to close token account when selling, only effective during sell operations
|
||||
pub close_token_account_when_sell: Option<bool>,
|
||||
}
|
||||
|
||||
impl PumpFunParams {
|
||||
pub fn default() -> Self {
|
||||
pub fn from_dev_trade(
|
||||
mint: &Pubkey,
|
||||
dev_token_amount: u64,
|
||||
dev_sol_amount: u64,
|
||||
creator: Pubkey,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
) -> Self {
|
||||
let bonding_curve =
|
||||
BondingCurveAccount::from_dev_trade(mint, dev_token_amount, dev_sol_amount, creator);
|
||||
Self {
|
||||
bonding_curve: None,
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_trade(
|
||||
event: &PumpFunTradeEvent,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
) -> Self {
|
||||
let bonding_curve = BondingCurveAccount::from_trade(event);
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,41 +145,61 @@ impl ProtocolParams for PumpFunParams {
|
||||
#[derive(Clone)]
|
||||
pub struct PumpSwapParams {
|
||||
/// Liquidity pool address
|
||||
/// If None, it will be queried via RPC, which adds latency
|
||||
pub pool: Option<Pubkey>,
|
||||
|
||||
pub pool: Pubkey,
|
||||
/// Base token mint address
|
||||
/// The mint account address of the base token in the trading pair
|
||||
/// If None, it will be queried via RPC, which adds latency
|
||||
pub base_mint: Option<Pubkey>,
|
||||
|
||||
pub base_mint: Pubkey,
|
||||
/// Quote token mint address
|
||||
/// The mint account address of the quote token in the trading pair, usually SOL or USDC
|
||||
/// If None, it will be queried via RPC, which adds latency
|
||||
pub quote_mint: Option<Pubkey>,
|
||||
|
||||
pub quote_mint: Pubkey,
|
||||
/// Base token reserves in the pool
|
||||
pub pool_base_token_reserves: Option<u64>,
|
||||
|
||||
pub pool_base_token_reserves: u64,
|
||||
/// Quote token reserves in the pool
|
||||
pub pool_quote_token_reserves: Option<u64>,
|
||||
|
||||
pub pool_quote_token_reserves: u64,
|
||||
/// Automatically handle WSOL wrapping
|
||||
/// When true, automatically handles wrapping and unwrapping operations between SOL and WSOL
|
||||
pub auto_handle_wsol: bool,
|
||||
}
|
||||
|
||||
impl PumpSwapParams {
|
||||
pub fn default() -> Self {
|
||||
pub fn from_buy_trade(event: &PumpSwapBuyEvent) -> Self {
|
||||
Self {
|
||||
pool: None,
|
||||
base_mint: None,
|
||||
quote_mint: None,
|
||||
pool_base_token_reserves: None,
|
||||
pool_quote_token_reserves: None,
|
||||
pool: event.pool,
|
||||
base_mint: event.base_mint,
|
||||
quote_mint: event.quote_mint,
|
||||
pool_base_token_reserves: event.pool_base_token_reserves,
|
||||
pool_quote_token_reserves: event.pool_quote_token_reserves,
|
||||
auto_handle_wsol: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_sell_trade(event: &PumpSwapSellEvent) -> Self {
|
||||
Self {
|
||||
pool: event.pool,
|
||||
base_mint: event.base_mint,
|
||||
quote_mint: event.quote_mint,
|
||||
pool_base_token_reserves: event.pool_base_token_reserves,
|
||||
pool_quote_token_reserves: event.pool_quote_token_reserves,
|
||||
auto_handle_wsol: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_data = crate::trading::pumpswap::common::fetch_pool(rpc, pool_address).await?;
|
||||
let (pool_base_token_reserves, pool_quote_token_reserves) =
|
||||
get_token_balances(&pool_data, rpc).await?;
|
||||
Ok(Self {
|
||||
pool: pool_address.clone(),
|
||||
base_mint: pool_data.base_mint,
|
||||
quote_mint: pool_data.quote_mint,
|
||||
pool_base_token_reserves: pool_base_token_reserves,
|
||||
pool_quote_token_reserves: pool_quote_token_reserves,
|
||||
auto_handle_wsol: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolParams for PumpSwapParams {
|
||||
@@ -157,32 +212,28 @@ impl ProtocolParams for PumpSwapParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// Bonk协议特定参数
|
||||
/// Bonk protocol specific parameters
|
||||
/// Configuration parameters specific to Bonk trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct BonkParams {
|
||||
pub virtual_base: Option<u128>,
|
||||
pub virtual_quote: Option<u128>,
|
||||
pub real_base: Option<u128>,
|
||||
pub real_quote: Option<u128>,
|
||||
pub virtual_base: u128,
|
||||
pub virtual_quote: u128,
|
||||
pub real_base: u128,
|
||||
pub real_quote: u128,
|
||||
/// Token program ID
|
||||
/// Specifies the program used by the token, usually spl_token::ID or spl_token_2022::ID
|
||||
pub mint_token_program: Pubkey,
|
||||
pub auto_handle_wsol: bool,
|
||||
}
|
||||
|
||||
impl BonkParams {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
virtual_base: None,
|
||||
virtual_quote: None,
|
||||
real_base: None,
|
||||
real_quote: None,
|
||||
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),
|
||||
virtual_base: trade_info.virtual_base as u128,
|
||||
virtual_quote: trade_info.virtual_quote as u128,
|
||||
real_base: trade_info.real_base_after as u128,
|
||||
real_quote: trade_info.real_quote_after as u128,
|
||||
mint_token_program: trade_info.base_token_program,
|
||||
auto_handle_wsol: true,
|
||||
}
|
||||
}
|
||||
@@ -205,12 +256,9 @@ impl BonkParams {
|
||||
0,
|
||||
)
|
||||
};
|
||||
let real_quote = get_amount_in_net(
|
||||
amount_in,
|
||||
PROTOCOL_FEE_RATE,
|
||||
PLATFORM_FEE_RATE,
|
||||
SHARE_FEE_RATE,
|
||||
) as u128;
|
||||
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,
|
||||
@@ -228,13 +276,33 @@ impl BonkParams {
|
||||
};
|
||||
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),
|
||||
virtual_base: DEFAULT_VIRTUAL_BASE,
|
||||
virtual_quote: DEFAULT_VIRTUAL_QUOTE,
|
||||
real_base: real_base,
|
||||
real_quote: real_quote,
|
||||
mint_token_program: trade_info.base_token_program,
|
||||
auto_handle_wsol: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_address =
|
||||
crate::trading::bonk::common::get_pool_pda(mint, &accounts::WSOL_TOKEN_ACCOUNT)
|
||||
.unwrap();
|
||||
let pool_data = crate::trading::bonk::common::fetch_pool_state(rpc, &pool_address).await?;
|
||||
let token_account = rpc.get_account(&pool_data.base_mint).await?;
|
||||
Ok(Self {
|
||||
virtual_base: pool_data.virtual_base as u128,
|
||||
virtual_quote: pool_data.virtual_quote as u128,
|
||||
real_base: pool_data.real_base as u128,
|
||||
real_quote: pool_data.real_quote as u128,
|
||||
mint_token_program: token_account.owner,
|
||||
auto_handle_wsol: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolParams for BonkParams {
|
||||
@@ -247,30 +315,45 @@ impl ProtocolParams for BonkParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// RaydiumCpmm协议特定参数
|
||||
/// RaydiumCpmm protocol specific parameters
|
||||
/// Configuration parameters specific to Raydium CPMM trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct RaydiumCpmmParams {
|
||||
/// 池子状态账户地址
|
||||
pub pool_state: Option<Pubkey>,
|
||||
/// 代币程序ID
|
||||
/// 指定代币使用的程序,通常为 spl_token::ID 或 spl_token_2022::ID
|
||||
pub mint_token_program: Option<Pubkey>,
|
||||
/// 指定 mint_token 在 pool_state 账户数据中的索引位置
|
||||
/// 默认值为1,表示在索引1的位置
|
||||
pub mint_token_in_pool_state_index: Option<usize>,
|
||||
pub minimum_amount_out: Option<u64>,
|
||||
/// Base token mint address
|
||||
pub base_mint: Pubkey,
|
||||
/// Quote token mint address
|
||||
pub quote_mint: Pubkey,
|
||||
/// Base token reserve amount in the pool
|
||||
pub base_reserve: u64,
|
||||
/// Quote token reserve amount in the pool
|
||||
pub quote_reserve: u64,
|
||||
/// Base token program ID (usually spl_token::ID or spl_token_2022::ID)
|
||||
pub base_token_program: Pubkey,
|
||||
/// Quote token program ID (usually spl_token::ID or spl_token_2022::ID)
|
||||
pub quote_token_program: Pubkey,
|
||||
/// Whether to automatically handle wSOL wrapping and unwrapping
|
||||
pub auto_handle_wsol: bool,
|
||||
}
|
||||
|
||||
impl RaydiumCpmmParams {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
pool_state: None,
|
||||
mint_token_program: Some(spl_token::ID),
|
||||
mint_token_in_pool_state_index: Some(1),
|
||||
minimum_amount_out: None,
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool =
|
||||
crate::trading::raydium_cpmm::common::fetch_pool_state(rpc, pool_address).await?;
|
||||
let (token0_balance, token1_balance) =
|
||||
get_pool_token_balances(rpc, pool_address, &pool.token0_mint, &pool.token1_mint)
|
||||
.await?;
|
||||
Ok(Self {
|
||||
base_mint: pool.token0_mint,
|
||||
quote_mint: pool.token1_mint,
|
||||
base_reserve: token0_balance,
|
||||
quote_reserve: token1_balance,
|
||||
base_token_program: pool.token0_program,
|
||||
quote_token_program: pool.token1_program,
|
||||
auto_handle_wsol: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +368,8 @@ impl ProtocolParams for RaydiumCpmmParams {
|
||||
}
|
||||
|
||||
impl BuyParams {
|
||||
/// 转换为BuyWithTipParams
|
||||
/// Convert to BuyWithTipParams
|
||||
/// Transforms basic buy parameters into MEV-enabled parameters
|
||||
pub fn with_tip(self, swqos_clients: Vec<Arc<SwqosClient>>) -> BuyWithTipParams {
|
||||
BuyWithTipParams {
|
||||
rpc: self.rpc,
|
||||
@@ -305,7 +389,8 @@ impl BuyParams {
|
||||
}
|
||||
|
||||
impl SellParams {
|
||||
/// 转换为SellWithTipParams
|
||||
/// Convert to SellWithTipParams
|
||||
/// Transforms basic sell parameters into MEV-enabled parameters
|
||||
pub fn with_tip(self, swqos_clients: Vec<Arc<SwqosClient>>) -> SellWithTipParams {
|
||||
SellWithTipParams {
|
||||
rpc: self.rpc,
|
||||
|
||||
+39
-102
@@ -1,19 +1,16 @@
|
||||
use anyhow::anyhow;
|
||||
use tokio::sync::RwLock;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use solana_sdk::{
|
||||
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey
|
||||
};
|
||||
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
|
||||
use crate::{
|
||||
common::{
|
||||
bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient
|
||||
bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient,
|
||||
},
|
||||
constants::{
|
||||
self, pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::trade::DEFAULT_SLIPPAGE
|
||||
},
|
||||
trading::common::calculate_with_slippage_buy
|
||||
constants::{self, trade::trade::DEFAULT_SLIPPAGE},
|
||||
};
|
||||
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
|
||||
use anyhow::anyhow;
|
||||
use solana_sdk::{
|
||||
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey,
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<GlobalAccount>>> = RwLock::new(HashMap::new());
|
||||
@@ -24,15 +21,18 @@ pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instru
|
||||
let mut instructions = Vec::with_capacity(2);
|
||||
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit));
|
||||
instructions.push(ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price));
|
||||
|
||||
|
||||
instructions
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
pub fn get_global_pda() -> Pubkey {
|
||||
static GLOBAL_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
|
||||
Pubkey::find_program_address(&[constants::pumpfun::seeds::GLOBAL_SEED], &constants::pumpfun::accounts::PUMPFUN).0
|
||||
Pubkey::find_program_address(
|
||||
&[constants::pumpfun::seeds::GLOBAL_SEED],
|
||||
&constants::pumpfun::accounts::PUMPFUN,
|
||||
)
|
||||
.0
|
||||
});
|
||||
*GLOBAL_PDA
|
||||
}
|
||||
@@ -40,7 +40,11 @@ pub fn get_global_pda() -> Pubkey {
|
||||
#[inline]
|
||||
pub fn get_mint_authority_pda() -> Pubkey {
|
||||
static MINT_AUTHORITY_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
|
||||
Pubkey::find_program_address(&[constants::pumpfun::seeds::MINT_AUTHORITY_SEED], &constants::pumpfun::accounts::PUMPFUN).0
|
||||
Pubkey::find_program_address(
|
||||
&[constants::pumpfun::seeds::MINT_AUTHORITY_SEED],
|
||||
&constants::pumpfun::accounts::PUMPFUN,
|
||||
)
|
||||
.0
|
||||
});
|
||||
*MINT_AUTHORITY_PDA
|
||||
}
|
||||
@@ -63,7 +67,8 @@ pub fn get_creator_vault_pda(creator: &Pubkey) -> Option<Pubkey> {
|
||||
|
||||
#[inline]
|
||||
pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 2] = &[constants::pumpfun::seeds::USER_VOLUME_ACCUMULATOR_SEED, user.as_ref()];
|
||||
let seeds: &[&[u8]; 2] =
|
||||
&[constants::pumpfun::seeds::USER_VOLUME_ACCUMULATOR_SEED, user.as_ref()];
|
||||
let program_id: &Pubkey = &constants::pumpfun::accounts::PUMPFUN;
|
||||
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
||||
pda.map(|pubkey| pubkey.0)
|
||||
@@ -85,48 +90,35 @@ pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
|
||||
constants::pumpfun::accounts::MPL_TOKEN_METADATA.as_ref(),
|
||||
mint.as_ref(),
|
||||
],
|
||||
&constants::pumpfun::accounts::MPL_TOKEN_METADATA
|
||||
).0
|
||||
&constants::pumpfun::accounts::MPL_TOKEN_METADATA,
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_global_account(/*rpc: &SolanaRpcClient*/) -> Result<Arc<GlobalAccount>, anyhow::Error> {
|
||||
pub async fn get_global_account(/*rpc: &SolanaRpcClient*/
|
||||
) -> Result<Arc<GlobalAccount>, anyhow::Error> {
|
||||
let global_account = GlobalAccount::new();
|
||||
let global_account = Arc::new(global_account);
|
||||
Ok(global_account)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_initial_buy_price(global_account: &Arc<GlobalAccount>, amount_sol: u64) -> Result<u64, anyhow::Error> {
|
||||
pub async fn get_initial_buy_price(
|
||||
global_account: &Arc<GlobalAccount>,
|
||||
amount_sol: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let buy_amount = global_account.get_initial_buy_price(amount_sol);
|
||||
Ok(buy_amount)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_bonding_curve_account(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<(Arc<BondingCurveAccount>, Pubkey), anyhow::Error> {
|
||||
let bonding_curve_pda = get_bonding_curve_pda(mint)
|
||||
.ok_or(anyhow!("Bonding curve not found"))?;
|
||||
|
||||
let account = rpc.get_account(&bonding_curve_pda).await?;
|
||||
if account.data.is_empty() {
|
||||
return Err(anyhow!("Bonding curve not found"));
|
||||
}
|
||||
|
||||
let bonding_curve = Arc::new(bincode::deserialize::<BondingCurveAccount>(&account.data)?);
|
||||
|
||||
Ok((bonding_curve, bonding_curve_pda))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn fetch_bonding_curve_account(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<(Arc<crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::types::BondingCurve>, Pubkey), anyhow::Error> {
|
||||
let bonding_curve_pda: Pubkey = get_bonding_curve_pda(mint)
|
||||
.ok_or(anyhow!("Bonding curve not found"))?;
|
||||
) -> Result<(Arc<crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::types::BondingCurve>, Pubkey), anyhow::Error>{
|
||||
let bonding_curve_pda: Pubkey =
|
||||
get_bonding_curve_pda(mint).ok_or(anyhow!("Bonding curve not found"))?;
|
||||
|
||||
let account = rpc.get_account(&bonding_curve_pda).await?;
|
||||
if account.data.is_empty() {
|
||||
@@ -139,63 +131,6 @@ pub async fn fetch_bonding_curve_account(
|
||||
Ok((Arc::new(bonding_curve), bonding_curve_pda))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_buy_token_amount(
|
||||
bonding_curve_account: &BondingCurveAccount,
|
||||
buy_sol_cost: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
) -> anyhow::Result<(u64, u64)> {
|
||||
let buy_token = bonding_curve_account.get_buy_price(buy_sol_cost).map_err(|e| anyhow!(e))?;
|
||||
|
||||
let max_sol_cost = calculate_with_slippage_buy(buy_sol_cost, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE));
|
||||
|
||||
Ok((buy_token, max_sol_cost))
|
||||
}
|
||||
|
||||
pub fn get_buy_token_amount_from_sol_amount(
|
||||
bonding_curve: &BondingCurveAccount,
|
||||
amount: u64,
|
||||
) -> u64 {
|
||||
if amount == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if bonding_curve.virtual_token_reserves == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let total_fee_basis_points = FEE_BASIS_POINTS
|
||||
+ if bonding_curve.creator != Pubkey::default() {
|
||||
CREATOR_FEE
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// 转为 u128 防止溢出
|
||||
let amount_128 = amount as u128;
|
||||
let total_fee_basis_points_128 = total_fee_basis_points as u128;
|
||||
let input_amount = amount_128
|
||||
.checked_mul(10_000)
|
||||
.unwrap()
|
||||
.checked_div(total_fee_basis_points_128 + 10_000)
|
||||
.unwrap();
|
||||
|
||||
let virtual_token_reserves = bonding_curve.virtual_token_reserves as u128;
|
||||
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves as u128;
|
||||
let real_token_reserves = bonding_curve.real_token_reserves as u128;
|
||||
|
||||
let denominator = virtual_sol_reserves + input_amount;
|
||||
|
||||
let tokens_received = input_amount
|
||||
.checked_mul(virtual_token_reserves)
|
||||
.unwrap()
|
||||
.checked_div(denominator)
|
||||
.unwrap();
|
||||
|
||||
tokens_received.min(real_token_reserves) as u64
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
pub async fn init_bonding_curve_account(
|
||||
mint: &Pubkey,
|
||||
@@ -203,7 +138,8 @@ pub async fn init_bonding_curve_account(
|
||||
dev_sol_cost: u64,
|
||||
creator: Pubkey,
|
||||
) -> Result<Arc<BondingCurveAccount>, anyhow::Error> {
|
||||
let bonding_curve = BondingCurveAccount::from_dev_trade(mint, dev_buy_token, dev_sol_cost, creator);
|
||||
let bonding_curve =
|
||||
BondingCurveAccount::from_dev_trade(mint, dev_buy_token, dev_sol_cost, creator);
|
||||
let bonding_curve = Arc::new(bonding_curve);
|
||||
Ok(bonding_curve)
|
||||
}
|
||||
@@ -220,11 +156,12 @@ pub fn get_buy_price(amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let n: u128 = (trade_info.virtual_sol_reserves as u128) * (trade_info.virtual_token_reserves as u128);
|
||||
let n: u128 =
|
||||
(trade_info.virtual_sol_reserves as u128) * (trade_info.virtual_token_reserves as u128);
|
||||
let i: u128 = (trade_info.virtual_sol_reserves as u128) + (amount as u128);
|
||||
let r: u128 = n / i + 1;
|
||||
let s: u128 = (trade_info.virtual_token_reserves as u128) - r;
|
||||
let s_u64 = s as u64;
|
||||
|
||||
|
||||
s_u64.min(trade_info.real_token_reserves)
|
||||
}
|
||||
|
||||
@@ -11,118 +11,6 @@ pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result<Pubkey, a
|
||||
Ok(pool_address)
|
||||
}
|
||||
|
||||
pub async fn get_token_amount(
|
||||
quote_mint_is_wsol: bool,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
sol_amount: u64,
|
||||
lp_fee_basis_points: u64,
|
||||
protocol_fee_basis_points: u64,
|
||||
coin_creator_fee_basis_points: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let product = pool_base_token_reserves as u128 * pool_quote_token_reserves as u128;
|
||||
if quote_mint_is_wsol {
|
||||
// base_amount_out
|
||||
let mut sol_amount = sol_amount as u128;
|
||||
sol_amount = sol_amount
|
||||
.checked_mul(10000)
|
||||
.unwrap()
|
||||
.checked_div(
|
||||
(10000
|
||||
+ lp_fee_basis_points
|
||||
+ protocol_fee_basis_points
|
||||
+ coin_creator_fee_basis_points) as u128,
|
||||
)
|
||||
.unwrap()
|
||||
.checked_sub(1)
|
||||
.unwrap();
|
||||
let new_quote_amount = pool_quote_token_reserves as u128 + sol_amount as u128;
|
||||
let new_base_amount = product / new_quote_amount;
|
||||
let token_amount = pool_base_token_reserves as u128 - new_base_amount;
|
||||
return Ok(token_amount as u64);
|
||||
} else {
|
||||
// min_quote_amount_out
|
||||
let new_base_amount = pool_base_token_reserves as u128 + sol_amount as u128;
|
||||
let new_quote_amount = product / new_base_amount;
|
||||
let token_amount = pool_quote_token_reserves as u128 - new_quote_amount;
|
||||
let lp_fee = token_amount
|
||||
.checked_mul(lp_fee_basis_points as u128)
|
||||
.unwrap()
|
||||
.checked_div(10000)
|
||||
.unwrap();
|
||||
let protocol_fee = token_amount
|
||||
.checked_mul(protocol_fee_basis_points as u128)
|
||||
.unwrap()
|
||||
.checked_div(10000)
|
||||
.unwrap();
|
||||
let coin_creator_fee = token_amount
|
||||
.checked_mul(coin_creator_fee_basis_points as u128)
|
||||
.unwrap()
|
||||
.checked_div(10000)
|
||||
.unwrap();
|
||||
let token_amount = token_amount.checked_sub(lp_fee).unwrap();
|
||||
let token_amount = token_amount.checked_sub(protocol_fee).unwrap();
|
||||
let token_amount = token_amount.checked_sub(coin_creator_fee).unwrap();
|
||||
return Ok(token_amount as u64);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_wsol_amount(
|
||||
quote_mint_is_wsol: bool,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
token_amount: u64,
|
||||
lp_fee_basis_points: u64,
|
||||
protocol_fee_basis_points: u64,
|
||||
coin_creator_fee_basis_points: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let product = pool_base_token_reserves as u128 * pool_quote_token_reserves as u128;
|
||||
if !quote_mint_is_wsol {
|
||||
// base_amount_out
|
||||
let mut token_amount = token_amount as u128;
|
||||
token_amount = token_amount
|
||||
.checked_mul(10000)
|
||||
.unwrap()
|
||||
.checked_div(
|
||||
(10000
|
||||
+ lp_fee_basis_points
|
||||
+ protocol_fee_basis_points
|
||||
+ coin_creator_fee_basis_points) as u128,
|
||||
)
|
||||
.unwrap()
|
||||
.checked_sub(1)
|
||||
.unwrap();
|
||||
let new_quote_amount = pool_quote_token_reserves as u128 + token_amount as u128;
|
||||
let new_base_amount = product / new_quote_amount;
|
||||
let wsol_amount = pool_base_token_reserves as u128 - new_base_amount;
|
||||
Ok(wsol_amount as u64)
|
||||
} else {
|
||||
// min_quote_amount_out
|
||||
let new_base_amount = pool_base_token_reserves as u128 + token_amount as u128;
|
||||
let new_quote_amount = product / new_base_amount;
|
||||
let token_amount = pool_quote_token_reserves as u128 - new_quote_amount;
|
||||
let lp_fee = token_amount
|
||||
.checked_mul(lp_fee_basis_points as u128)
|
||||
.unwrap()
|
||||
.checked_div(10000)
|
||||
.unwrap();
|
||||
let protocol_fee = token_amount
|
||||
.checked_mul(protocol_fee_basis_points as u128)
|
||||
.unwrap()
|
||||
.checked_div(10000)
|
||||
.unwrap();
|
||||
let coin_creator_fee = token_amount
|
||||
.checked_mul(coin_creator_fee_basis_points as u128)
|
||||
.unwrap()
|
||||
.checked_div(10000)
|
||||
.unwrap();
|
||||
let wsol_amount = token_amount.checked_sub(lp_fee).unwrap();
|
||||
let wsol_amount = wsol_amount.checked_sub(protocol_fee).unwrap();
|
||||
let wsol_amount = wsol_amount.checked_sub(coin_creator_fee).unwrap();
|
||||
Ok(wsol_amount as u64)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn coin_creator_vault_authority(coin_creator: Pubkey) -> Pubkey {
|
||||
let (pump_pool_authority, _) = Pubkey::find_program_address(
|
||||
&[b"creator_vault", &coin_creator.to_bytes()],
|
||||
@@ -276,37 +164,3 @@ pub async fn get_token_balances(
|
||||
|
||||
Ok((base_amount, quote_amount))
|
||||
}
|
||||
|
||||
pub async fn calculate_buy_amount(
|
||||
pool: &Pool,
|
||||
rpc: &SolanaRpcClient,
|
||||
sol_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let (base_amount, quote_amount) = get_token_balances(pool, rpc).await?;
|
||||
|
||||
// 使用常数乘积公式 (x * y = k) 计算
|
||||
let product = base_amount as u128 * quote_amount as u128;
|
||||
let new_quote_amount = quote_amount as u128 + sol_amount as u128;
|
||||
let new_base_amount = product / new_quote_amount;
|
||||
|
||||
let token_amount = base_amount as u128 - new_base_amount;
|
||||
|
||||
Ok(token_amount as u64)
|
||||
}
|
||||
|
||||
pub async fn calculate_sell_amount(
|
||||
pool: &Pool,
|
||||
rpc: &SolanaRpcClient,
|
||||
token_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let (base_amount, quote_amount) = get_token_balances(pool, rpc).await?;
|
||||
|
||||
// 使用常数乘积公式 (x * y = k) 计算
|
||||
let product = base_amount as u128 * quote_amount as u128;
|
||||
let new_base_amount = base_amount as u128 + token_amount as u128;
|
||||
let new_quote_amount = product / new_base_amount;
|
||||
|
||||
let sol_amount = quote_amount as u128 - new_quote_amount;
|
||||
|
||||
Ok(sol_amount as u64)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
common::SolanaRpcClient,
|
||||
constants::{
|
||||
self,
|
||||
raydium_cpmm::accounts::{self, WSOL_TOKEN_ACCOUNT},
|
||||
raydium_cpmm::accounts::{self},
|
||||
},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
@@ -52,94 +52,6 @@ pub fn get_observation_state_pda(pool_state: &Pubkey) -> Option<Pubkey> {
|
||||
pda.map(|pubkey| pubkey.0)
|
||||
}
|
||||
|
||||
pub async fn get_buy_token_amount(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_state: &Pubkey,
|
||||
sol_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let pool = fetch_pool_state(rpc, pool_state).await?;
|
||||
let is_token0_input = if pool.token0_mint == WSOL_TOKEN_ACCOUNT { true } else { false };
|
||||
let (token0_balance, token1_balance) =
|
||||
get_pool_token_balances(rpc, pool_state, &pool.token0_mint, &pool.token1_mint).await?;
|
||||
|
||||
// 使用恒定乘积公式计算
|
||||
|
||||
let (reserve_in, reserve_out) = if is_token0_input {
|
||||
(token0_balance, token1_balance)
|
||||
} else {
|
||||
(token1_balance, token0_balance)
|
||||
};
|
||||
|
||||
if reserve_in == 0 || reserve_out == 0 {
|
||||
return Err(anyhow!("池子储备金为零,无法进行交换"));
|
||||
}
|
||||
|
||||
// 使用 u128 防止溢出
|
||||
let amount_in_128 = sol_amount as u128;
|
||||
let reserve_in_128 = reserve_in as u128;
|
||||
let reserve_out_128 = reserve_out as u128;
|
||||
|
||||
// 恒定乘积公式: amount_out = (amount_in * reserve_out) / (reserve_in + amount_in)
|
||||
let numerator = amount_in_128 * reserve_out_128;
|
||||
let denominator = reserve_in_128 + amount_in_128;
|
||||
|
||||
if denominator == 0 {
|
||||
return Err(anyhow!("分母为零,计算错误"));
|
||||
}
|
||||
|
||||
let amount_out = numerator / denominator;
|
||||
|
||||
// 检查是否超出储备金
|
||||
if amount_out >= reserve_out_128 {
|
||||
return Err(anyhow!("输出数量超过池子储备金"));
|
||||
}
|
||||
|
||||
Ok(amount_out as u64)
|
||||
}
|
||||
|
||||
pub async fn get_sell_sol_amount(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_state: &Pubkey,
|
||||
token_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let pool = fetch_pool_state(rpc, pool_state).await?;
|
||||
let is_token0_sol = if pool.token0_mint == WSOL_TOKEN_ACCOUNT { true } else { false };
|
||||
let (token0_balance, token1_balance) =
|
||||
get_pool_token_balances(rpc, pool_state, &pool.token0_mint, &pool.token1_mint).await?;
|
||||
|
||||
let (reserve_in, reserve_out) = if is_token0_sol {
|
||||
(token1_balance, token0_balance)
|
||||
} else {
|
||||
(token0_balance, token1_balance)
|
||||
};
|
||||
|
||||
if reserve_in == 0 || reserve_out == 0 {
|
||||
return Err(anyhow!("池子储备金为零,无法进行交换"));
|
||||
}
|
||||
|
||||
// 使用 u128 防止溢出
|
||||
let amount_in_128 = token_amount as u128;
|
||||
let reserve_in_128 = reserve_in as u128;
|
||||
let reserve_out_128 = reserve_out as u128;
|
||||
|
||||
// 恒定乘积公式: amount_out = (amount_in * reserve_out) / (reserve_in + amount_in)
|
||||
let numerator = amount_in_128 * reserve_out_128;
|
||||
let denominator = reserve_in_128 + amount_in_128;
|
||||
|
||||
if denominator == 0 {
|
||||
return Err(anyhow!("分母为零,计算错误"));
|
||||
}
|
||||
|
||||
let amount_out = numerator / denominator;
|
||||
|
||||
// 检查是否超出储备金
|
||||
if amount_out >= reserve_out_128 {
|
||||
return Err(anyhow!("输出数量超过池子储备金"));
|
||||
}
|
||||
|
||||
Ok(amount_out as u64)
|
||||
}
|
||||
|
||||
/// 获取池子中两个代币的余额
|
||||
///
|
||||
/// # 返回值
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
use crate::constants::bonk::accounts;
|
||||
|
||||
/// Calculates the amount of tokens to receive when buying with SOL
|
||||
///
|
||||
/// This function implements the constant product formula (x * y = k) for token swaps,
|
||||
/// taking into account various fees and slippage protection.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `amount_in` - The amount of SOL to spend (in lamports)
|
||||
/// * `virtual_base` - Virtual base token reserves
|
||||
/// * `virtual_quote` - Virtual quote token (SOL) reserves
|
||||
/// * `real_base` - Real base token reserves
|
||||
/// * `real_quote` - Real quote token (SOL) reserves
|
||||
/// * `slippage_basis_points` - Maximum slippage tolerance in basis points (e.g., 100 = 1%)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The minimum amount of tokens that will be received after fees and slippage
|
||||
pub fn get_buy_token_amount_from_sol_amount(
|
||||
amount_in: u64,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base: u128,
|
||||
real_quote: u128,
|
||||
slippage_basis_points: u128,
|
||||
) -> u64 {
|
||||
let amount_in_u128 = amount_in as u128;
|
||||
|
||||
// Calculate various fees deducted from input amount
|
||||
let protocol_fee = (amount_in_u128 * accounts::PROTOCOL_FEE_RATE / 10000) as u128;
|
||||
let platform_fee = (amount_in_u128 * accounts::PLATFORM_FEE_RATE / 10000) as u128;
|
||||
let share_fee = (amount_in_u128 * accounts::SHARE_FEE_RATE / 10000) as u128;
|
||||
|
||||
// Calculate net input amount after deducting all fees
|
||||
let amount_in_net = amount_in_u128
|
||||
.checked_sub(protocol_fee)
|
||||
.unwrap()
|
||||
.checked_sub(platform_fee)
|
||||
.unwrap()
|
||||
.checked_sub(share_fee)
|
||||
.unwrap();
|
||||
|
||||
// Calculate total reserves (virtual + real)
|
||||
let input_reserve = virtual_quote.checked_add(real_quote).unwrap();
|
||||
let output_reserve = virtual_base.checked_sub(real_base).unwrap();
|
||||
|
||||
// Apply constant product formula: amount_out = (amount_in * output_reserve) / (input_reserve + amount_in)
|
||||
let numerator = amount_in_net.checked_mul(output_reserve).unwrap();
|
||||
let denominator = input_reserve.checked_add(amount_in_net).unwrap();
|
||||
let mut amount_out = numerator.checked_div(denominator).unwrap();
|
||||
|
||||
// Apply slippage protection
|
||||
amount_out = amount_out - (amount_out * slippage_basis_points) / 10000;
|
||||
amount_out as u64
|
||||
}
|
||||
|
||||
/// Calculates the amount of SOL to receive when selling tokens
|
||||
///
|
||||
/// This function implements the constant product formula (x * y = k) for token swaps,
|
||||
/// calculating the SOL output for a given token input amount, accounting for fees and slippage.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `amount_in` - The amount of tokens to sell
|
||||
/// * `virtual_base` - Virtual base token reserves
|
||||
/// * `virtual_quote` - Virtual quote token (SOL) reserves
|
||||
/// * `real_base` - Real base token reserves
|
||||
/// * `real_quote` - Real quote token (SOL) reserves
|
||||
/// * `slippage_basis_points` - Maximum slippage tolerance in basis points (e.g., 100 = 1%)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The minimum amount of SOL that will be received after fees and slippage
|
||||
pub fn get_sell_sol_amount_from_token_amount(
|
||||
amount_in: u64,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base: u128,
|
||||
real_quote: u128,
|
||||
slippage_basis_points: u128,
|
||||
) -> u64 {
|
||||
let amount_in_u128 = amount_in as u128;
|
||||
|
||||
// For sell operation, input_reserve is token reserves, output_reserve is SOL reserves
|
||||
let input_reserve = virtual_base.checked_add(real_base).unwrap();
|
||||
let output_reserve = virtual_quote.checked_add(real_quote).unwrap();
|
||||
|
||||
// Use constant product formula to calculate SOL amount received from selling tokens
|
||||
let numerator = amount_in_u128.checked_mul(output_reserve).unwrap();
|
||||
let denominator = input_reserve.checked_add(amount_in_u128).unwrap();
|
||||
let sol_amount_out = numerator.checked_div(denominator).unwrap();
|
||||
|
||||
// Calculate various fees
|
||||
let protocol_fee = (sol_amount_out * accounts::PROTOCOL_FEE_RATE / 10000) as u128;
|
||||
let platform_fee = (sol_amount_out * accounts::PLATFORM_FEE_RATE / 10000) as u128;
|
||||
let share_fee = (sol_amount_out * accounts::SHARE_FEE_RATE / 10000) as u128;
|
||||
|
||||
// Net SOL amount after deducting fees
|
||||
let sol_amount_net = sol_amount_out
|
||||
.checked_sub(protocol_fee)
|
||||
.unwrap()
|
||||
.checked_sub(platform_fee)
|
||||
.unwrap()
|
||||
.checked_sub(share_fee)
|
||||
.unwrap();
|
||||
|
||||
// Apply slippage protection
|
||||
let final_amount = sol_amount_net - (sol_amount_net * slippage_basis_points) / 10000;
|
||||
|
||||
final_amount as u64
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/// Calculate transaction fee based on amount and fee basis points
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `amount` - Transaction amount
|
||||
/// * `fee_basis_points` - Fee basis points, 1 basis point = 0.01%
|
||||
///
|
||||
/// # Examples
|
||||
/// * fee_basis_points = 1 -> 0.01% fee
|
||||
/// * fee_basis_points = 10 -> 0.1% fee
|
||||
/// * fee_basis_points = 25 -> 0.25% fee (common exchange rate)
|
||||
/// * fee_basis_points = 100 -> 1% fee
|
||||
pub fn compute_fee(amount: u128, fee_basis_points: u128) -> u128 {
|
||||
ceil_div(amount * fee_basis_points, 10_000)
|
||||
}
|
||||
|
||||
/// Ceiling division implementation
|
||||
/// Ceiling division that ensures results are not lost due to integer division precision
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `a` - Dividend
|
||||
/// * `b` - Divisor
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns the ceiling result of a/b
|
||||
pub fn ceil_div(a: u128, b: u128) -> u128 {
|
||||
(a + b - 1) / b
|
||||
}
|
||||
|
||||
/// Calculate buy amount with slippage protection
|
||||
/// Add slippage percentage to the amount to ensure successful purchase
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `amount` - Original transaction amount
|
||||
/// * `basis_points` - Slippage basis points, 1 basis point = 0.01%
|
||||
///
|
||||
/// # Examples
|
||||
/// * basis_points = 1 -> 0.01% slippage
|
||||
/// * basis_points = 10 -> 0.1% slippage
|
||||
/// * basis_points = 100 -> 1% slippage
|
||||
/// * basis_points = 500 -> 5% slippage
|
||||
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
|
||||
amount + (amount * basis_points / 10000)
|
||||
}
|
||||
|
||||
/// Calculate sell amount with slippage protection
|
||||
/// Subtract slippage percentage from the amount to ensure successful sale
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `amount` - Original transaction amount
|
||||
/// * `basis_points` - Slippage basis points, 1 basis point = 0.01%
|
||||
///
|
||||
/// # Examples
|
||||
/// * basis_points = 1 -> 0.01% slippage
|
||||
/// * basis_points = 10 -> 0.1% slippage
|
||||
/// * basis_points = 100 -> 1% slippage
|
||||
/// * basis_points = 500 -> 5% slippage
|
||||
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
|
||||
if amount <= basis_points / 10000 {
|
||||
1
|
||||
} else {
|
||||
amount - (amount * basis_points / 10000)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod pumpfun;
|
||||
pub mod common;
|
||||
pub mod pumpswap;
|
||||
pub mod bonk;
|
||||
pub mod raydium_amm_v4;
|
||||
pub mod raydium_cpmm;
|
||||
@@ -0,0 +1,108 @@
|
||||
use solana_sdk::{native_token::sol_str_to_lamports, pubkey::Pubkey};
|
||||
|
||||
use crate::{
|
||||
constants::pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS},
|
||||
utils::calc::common::compute_fee,
|
||||
};
|
||||
|
||||
/// Calculates the amount of tokens that can be purchased with a given SOL amount
|
||||
/// using the bonding curve formula.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `virtual_token_reserves` - Virtual token reserves in the bonding curve
|
||||
/// * `virtual_sol_reserves` - Virtual SOL reserves in the bonding curve
|
||||
/// * `real_token_reserves` - Actual token reserves available for purchase
|
||||
/// * `creator` - Creator's public key (affects fee calculation)
|
||||
/// * `amount` - SOL amount to spend (in lamports)
|
||||
///
|
||||
/// # Returns
|
||||
/// The amount of tokens that will be received (in token's smallest unit)
|
||||
pub fn get_buy_token_amount_from_sol_amount(
|
||||
virtual_token_reserves: u128,
|
||||
virtual_sol_reserves: u128,
|
||||
real_token_reserves: u128,
|
||||
creator: Pubkey,
|
||||
amount: u64,
|
||||
) -> u64 {
|
||||
if amount == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if virtual_token_reserves == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let total_fee_basis_points =
|
||||
FEE_BASIS_POINTS + if creator != Pubkey::default() { CREATOR_FEE } else { 0 };
|
||||
|
||||
// Convert to u128 to prevent overflow
|
||||
let amount_128 = amount as u128;
|
||||
let total_fee_basis_points_128 = total_fee_basis_points as u128;
|
||||
|
||||
let input_amount = amount_128
|
||||
.checked_mul(10_000)
|
||||
.unwrap()
|
||||
.checked_div(total_fee_basis_points_128 + 10_000)
|
||||
.unwrap();
|
||||
|
||||
let denominator = virtual_sol_reserves + input_amount;
|
||||
|
||||
let mut tokens_received =
|
||||
input_amount.checked_mul(virtual_token_reserves).unwrap().checked_div(denominator).unwrap();
|
||||
|
||||
tokens_received = tokens_received.min(real_token_reserves);
|
||||
|
||||
if tokens_received <= 100 * 1_000_000_u128 {
|
||||
tokens_received = if amount > sol_str_to_lamports("0.01").unwrap_or(0) {
|
||||
25547619 * 1_000_000_u128
|
||||
} else {
|
||||
255476 * 1_000_000_u128
|
||||
};
|
||||
}
|
||||
|
||||
tokens_received as u64
|
||||
}
|
||||
|
||||
/// Calculates the amount of SOL that will be received when selling a given token amount
|
||||
/// using the bonding curve formula with transaction fees deducted.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `virtual_token_reserves` - Virtual token reserves in the bonding curve
|
||||
/// * `virtual_sol_reserves` - Virtual SOL reserves in the bonding curve
|
||||
/// * `creator` - Creator's public key (affects fee calculation)
|
||||
/// * `amount` - Token amount to sell (in token's smallest unit)
|
||||
///
|
||||
/// # Returns
|
||||
/// The amount of SOL that will be received after fees (in lamports)
|
||||
pub fn get_sell_sol_amount_from_token_amount(
|
||||
virtual_token_reserves: u128,
|
||||
virtual_sol_reserves: u128,
|
||||
creator: Pubkey,
|
||||
amount: u64,
|
||||
) -> u64 {
|
||||
if amount == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// migrated bonding curve
|
||||
if virtual_token_reserves == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let amount_128 = amount as u128;
|
||||
|
||||
// Calculate SOL amount received from selling tokens using constant product formula
|
||||
let numerator = amount_128.checked_mul(virtual_sol_reserves).unwrap_or(0);
|
||||
let denominator = virtual_token_reserves.checked_add(amount_128).unwrap_or(1);
|
||||
|
||||
let sol_cost = numerator.checked_div(denominator).unwrap_or(0);
|
||||
|
||||
let total_fee_basis_points =
|
||||
FEE_BASIS_POINTS + if creator != Pubkey::default() { CREATOR_FEE } else { 0 };
|
||||
let total_fee_basis_points_128 = total_fee_basis_points as u128;
|
||||
|
||||
// Calculate transaction fee
|
||||
let fee = compute_fee(sol_cost, total_fee_basis_points_128);
|
||||
|
||||
sol_cost.saturating_sub(fee) as u64
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
use super::common::{
|
||||
calculate_with_slippage_buy, calculate_with_slippage_sell, ceil_div, compute_fee,
|
||||
};
|
||||
use crate::constants::pumpswap::accounts::{
|
||||
COIN_CREATOR_FEE_BASIS_POINTS, LP_FEE_BASIS_POINTS, PROTOCOL_FEE_BASIS_POINTS,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// Result for buying base tokens with base amount input
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BuyBaseInputResult {
|
||||
/// Raw quote amount needed before fees
|
||||
pub internal_quote_amount: u64,
|
||||
/// Total quote amount including all fees
|
||||
pub ui_quote: u64,
|
||||
/// Maximum quote amount with slippage protection
|
||||
pub max_quote: u64,
|
||||
}
|
||||
|
||||
/// Result for buying base tokens with quote amount input
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BuyQuoteInputResult {
|
||||
/// Amount of base tokens received
|
||||
pub base: u64,
|
||||
/// Effective quote amount after fee deduction
|
||||
pub internal_quote_without_fees: u64,
|
||||
/// Maximum quote amount with slippage protection
|
||||
pub max_quote: u64,
|
||||
}
|
||||
|
||||
/// Result for selling base tokens with base amount input
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SellBaseInputResult {
|
||||
/// Final quote amount received after fees
|
||||
pub ui_quote: u64,
|
||||
/// Minimum quote amount with slippage protection
|
||||
pub min_quote: u64,
|
||||
/// Raw quote amount before fee deduction
|
||||
pub internal_quote_amount_out: u64,
|
||||
}
|
||||
|
||||
/// Result for selling base tokens with quote amount input
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SellQuoteInputResult {
|
||||
/// Raw quote amount including fees
|
||||
pub internal_raw_quote: u64,
|
||||
/// Amount of base tokens needed to sell
|
||||
pub base: u64,
|
||||
/// Minimum quote amount with slippage protection
|
||||
pub min_quote: u64,
|
||||
}
|
||||
|
||||
/// Calculate quote amount needed to buy a specific amount of base tokens
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base` - Amount of base tokens to buy
|
||||
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
|
||||
/// * `base_reserve` - Base token reserves in the pool
|
||||
/// * `quote_reserve` - Quote token reserves in the pool
|
||||
/// * `coin_creator` - Token creator address
|
||||
///
|
||||
/// # Returns
|
||||
/// * `BuyBaseInputResult` containing quote amounts and slippage calculations
|
||||
pub fn buy_base_input_internal(
|
||||
base: u64,
|
||||
slippage_basis_points: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
) -> Result<BuyBaseInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
}
|
||||
if base > base_reserve {
|
||||
return Err("Cannot buy more base tokens than the pool reserves.".to_string());
|
||||
}
|
||||
|
||||
// Calculate required quote amount using constant product formula
|
||||
let numerator = (quote_reserve as u128) * (base as u128);
|
||||
let denominator = base_reserve - base;
|
||||
|
||||
if denominator == 0 {
|
||||
return Err("Pool would be depleted; denominator is zero.".to_string());
|
||||
}
|
||||
|
||||
let quote_amount_in = ceil_div(numerator, denominator as u128) as u64;
|
||||
|
||||
// Calculate fees
|
||||
let lp_fee = compute_fee(quote_amount_in as u128, LP_FEE_BASIS_POINTS as u128) as u64;
|
||||
let protocol_fee =
|
||||
compute_fee(quote_amount_in as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
|
||||
let coin_creator_fee = if *coin_creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
compute_fee(quote_amount_in as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
|
||||
};
|
||||
let total_quote = quote_amount_in + lp_fee + protocol_fee + coin_creator_fee;
|
||||
|
||||
// Calculate max quote with slippage
|
||||
let max_quote = calculate_with_slippage_buy(total_quote, slippage_basis_points);
|
||||
|
||||
Ok(BuyBaseInputResult {
|
||||
internal_quote_amount: quote_amount_in,
|
||||
ui_quote: total_quote,
|
||||
max_quote,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate base tokens received for a specific quote amount
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `quote` - Amount of quote tokens to spend
|
||||
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
|
||||
/// * `base_reserve` - Base token reserves in the pool
|
||||
/// * `quote_reserve` - Quote token reserves in the pool
|
||||
/// * `coin_creator` - Token creator address
|
||||
///
|
||||
/// # Returns
|
||||
/// * `BuyQuoteInputResult` containing base amount and slippage calculations
|
||||
pub fn buy_quote_input_internal(
|
||||
quote: u64,
|
||||
slippage_basis_points: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
) -> Result<BuyQuoteInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
}
|
||||
|
||||
// Calculate total fee basis points
|
||||
let total_fee_bps = LP_FEE_BASIS_POINTS
|
||||
+ PROTOCOL_FEE_BASIS_POINTS
|
||||
+ if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS };
|
||||
let denominator = 10_000 + total_fee_bps;
|
||||
|
||||
// Calculate effective quote amount after fees
|
||||
let effective_quote = (quote as u128 * 10_000) / denominator as u128;
|
||||
|
||||
// Calculate base amount out using constant product formula
|
||||
let numerator = (base_reserve as u128) * effective_quote;
|
||||
let denominator_effective = (quote_reserve as u128) + effective_quote;
|
||||
|
||||
if denominator_effective == 0 {
|
||||
return Err("Pool would be depleted; denominator is zero.".to_string());
|
||||
}
|
||||
|
||||
let base_amount_out = (numerator / denominator_effective) as u64;
|
||||
|
||||
// Calculate max quote with slippage
|
||||
let max_quote = calculate_with_slippage_buy(quote, slippage_basis_points);
|
||||
|
||||
Ok(BuyQuoteInputResult {
|
||||
base: base_amount_out,
|
||||
internal_quote_without_fees: effective_quote as u64,
|
||||
max_quote,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate quote tokens received for selling a specific amount of base tokens
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base` - Amount of base tokens to sell
|
||||
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
|
||||
/// * `base_reserve` - Base token reserves in the pool
|
||||
/// * `quote_reserve` - Quote token reserves in the pool
|
||||
/// * `coin_creator` - Token creator address
|
||||
///
|
||||
/// # Returns
|
||||
/// * `SellBaseInputResult` containing quote amounts and slippage calculations
|
||||
pub fn sell_base_input_internal(
|
||||
base: u64,
|
||||
slippage_basis_points: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
) -> Result<SellBaseInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
}
|
||||
|
||||
// Calculate quote amount out using constant product formula
|
||||
let quote_amount_out = ((quote_reserve as u128) * (base as u128)
|
||||
/ ((base_reserve as u128) + (base as u128))) as u64;
|
||||
|
||||
// Calculate fees
|
||||
let lp_fee = compute_fee(quote_amount_out as u128, LP_FEE_BASIS_POINTS as u128) as u64;
|
||||
let protocol_fee =
|
||||
compute_fee(quote_amount_out as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
|
||||
let coin_creator_fee = if *coin_creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
compute_fee(quote_amount_out as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
|
||||
};
|
||||
|
||||
// Calculate final quote after fees
|
||||
let total_fees = lp_fee + protocol_fee + coin_creator_fee;
|
||||
if total_fees > quote_amount_out {
|
||||
return Err("Fees exceed total output; final quote is negative.".to_string());
|
||||
}
|
||||
let final_quote = quote_amount_out - total_fees;
|
||||
|
||||
// Calculate min quote with slippage
|
||||
let min_quote = calculate_with_slippage_sell(final_quote, slippage_basis_points);
|
||||
|
||||
Ok(SellBaseInputResult {
|
||||
ui_quote: final_quote,
|
||||
min_quote,
|
||||
internal_quote_amount_out: quote_amount_out,
|
||||
})
|
||||
}
|
||||
|
||||
const MAX_FEE_BASIS_POINTS: u64 = 10_000;
|
||||
|
||||
/// Calculate quote amount out including fees
|
||||
fn calculate_quote_amount_out(
|
||||
user_quote_amount_out: u64,
|
||||
lp_fee_basis_points: u64,
|
||||
protocol_fee_basis_points: u64,
|
||||
coin_creator_fee_basis_points: u64,
|
||||
) -> u64 {
|
||||
let total_fee_basis_points =
|
||||
lp_fee_basis_points + protocol_fee_basis_points + coin_creator_fee_basis_points;
|
||||
let denominator = MAX_FEE_BASIS_POINTS - total_fee_basis_points;
|
||||
ceil_div((user_quote_amount_out as u128) * (MAX_FEE_BASIS_POINTS as u128), denominator as u128)
|
||||
as u64
|
||||
}
|
||||
|
||||
/// Calculate base tokens needed to receive a specific amount of quote tokens
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `quote` - Desired amount of quote tokens to receive
|
||||
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
|
||||
/// * `base_reserve` - Base token reserves in the pool
|
||||
/// * `quote_reserve` - Quote token reserves in the pool
|
||||
/// * `coin_creator` - Token creator address
|
||||
///
|
||||
/// # Returns
|
||||
/// * `SellQuoteInputResult` containing base amount and slippage calculations
|
||||
pub fn sell_quote_input_internal(
|
||||
quote: u64,
|
||||
slippage_basis_points: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
) -> Result<SellQuoteInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
}
|
||||
if quote > quote_reserve {
|
||||
return Err("Cannot receive more quote tokens than the pool quote reserves.".to_string());
|
||||
}
|
||||
|
||||
// Calculate raw quote amount including fees
|
||||
let raw_quote = calculate_quote_amount_out(
|
||||
quote,
|
||||
LP_FEE_BASIS_POINTS,
|
||||
PROTOCOL_FEE_BASIS_POINTS,
|
||||
if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS },
|
||||
);
|
||||
|
||||
// Calculate base amount needed using inverse constant product formula
|
||||
if raw_quote >= quote_reserve {
|
||||
return Err("Invalid input: Desired quote amount exceeds available reserve.".to_string());
|
||||
}
|
||||
|
||||
let base_amount_in =
|
||||
ceil_div((base_reserve as u128) * (raw_quote as u128), (quote_reserve - raw_quote) as u128)
|
||||
as u64;
|
||||
|
||||
// Calculate min quote with slippage
|
||||
let min_quote = calculate_with_slippage_sell(quote, slippage_basis_points);
|
||||
|
||||
Ok(SellQuoteInputResult { internal_raw_quote: raw_quote, base: base_amount_in, min_quote })
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
@@ -0,0 +1,190 @@
|
||||
use crate::constants::raydium_cpmm::accounts::{
|
||||
CREATOR_FEE_RATE, FEE_RATE_DENOMINATOR_VALUE, FUND_FEE_RATE, PROTOCOL_FEE_RATE, TRADE_FEE_RATE,
|
||||
};
|
||||
|
||||
/// Computes trading fee using ceiling division.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `amount` - The amount to calculate fee for
|
||||
/// * `fee_rate` - The fee rate to apply
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated trading fee
|
||||
fn compute_trading_fee(amount: u64, fee_rate: u64) -> u64 {
|
||||
let numerator = (amount as u128) * (fee_rate as u128);
|
||||
((numerator + FEE_RATE_DENOMINATOR_VALUE - 1) / FEE_RATE_DENOMINATOR_VALUE) as u64
|
||||
}
|
||||
|
||||
/// Computes protocol or fund fee using floor division.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `amount` - The amount to calculate fee for
|
||||
/// * `fee_rate` - The fee rate to apply
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated protocol or fund fee
|
||||
fn compute_protocol_fund_fee(amount: u64, fee_rate: u64) -> u64 {
|
||||
let numerator = (amount as u128) * (fee_rate as u128);
|
||||
(numerator / FEE_RATE_DENOMINATOR_VALUE) as u64
|
||||
}
|
||||
|
||||
/// Computes creator fee using ceiling division.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `amount` - The amount to calculate fee for
|
||||
/// * `fee_rate` - The fee rate to apply
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated creator fee
|
||||
fn compute_creator_fee_new(amount: u64, fee_rate: u64) -> u64 {
|
||||
let numerator = (amount as u128) * (fee_rate as u128);
|
||||
((numerator + FEE_RATE_DENOMINATOR_VALUE - 1) / FEE_RATE_DENOMINATOR_VALUE) as u64
|
||||
}
|
||||
|
||||
/// Parameters for computing swap amounts and fees.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ComputeSwapParams {
|
||||
/// Whether the entire input amount is traded
|
||||
pub all_trade: bool,
|
||||
/// The input amount for the swap
|
||||
pub amount_in: u64,
|
||||
/// The expected output amount from the swap
|
||||
pub amount_out: u64,
|
||||
/// The minimum acceptable output amount (considering slippage_basis_points)
|
||||
pub min_amount_out: u64,
|
||||
/// The trading fee amount
|
||||
pub fee: u64,
|
||||
}
|
||||
|
||||
/// Result of a swap calculation containing all relevant amounts and fees.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SwapResult {
|
||||
/// The new amount in the input vault after the swap
|
||||
pub new_input_vault_amount: u64,
|
||||
/// The new amount in the output vault after the swap
|
||||
pub new_output_vault_amount: u64,
|
||||
/// The actual input amount used in the swap
|
||||
pub input_amount: u64,
|
||||
/// The actual output amount received from the swap
|
||||
pub output_amount: u64,
|
||||
/// The trading fee charged
|
||||
pub trade_fee: u64,
|
||||
/// The protocol fee charged
|
||||
pub protocol_fee: u64,
|
||||
/// The fund fee charged
|
||||
pub fund_fee: u64,
|
||||
/// The creator fee charged
|
||||
pub creator_fee: u64,
|
||||
}
|
||||
|
||||
/// Performs a swap calculation based on input amount.
|
||||
///
|
||||
/// Calculates the output amount and all associated fees when swapping a specific input amount.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `input_amount` - The amount of input tokens to swap
|
||||
/// * `input_vault_amount` - Current amount in the input token vault
|
||||
/// * `output_vault_amount` - Current amount in the output token vault
|
||||
/// * `trade_fee_rate` - The trading fee rate
|
||||
/// * `creator_fee_rate` - The creator fee rate
|
||||
/// * `protocol_fee_rate` - The protocol fee rate
|
||||
/// * `fund_fee_rate` - The fund fee rate
|
||||
/// * `is_creator_fee_on_input` - Whether creator fee is charged on input tokens
|
||||
///
|
||||
/// # Returns
|
||||
/// A `SwapResult` containing all swap calculations and fees
|
||||
fn swap_base_input(
|
||||
input_amount: u64,
|
||||
input_vault_amount: u64,
|
||||
output_vault_amount: u64,
|
||||
trade_fee_rate: u64,
|
||||
creator_fee_rate: u64,
|
||||
protocol_fee_rate: u64,
|
||||
fund_fee_rate: u64,
|
||||
is_creator_fee_on_input: bool,
|
||||
) -> SwapResult {
|
||||
let mut creator_fee = 0u64;
|
||||
|
||||
let trade_fee = compute_trading_fee(input_amount, trade_fee_rate);
|
||||
|
||||
let input_amount_less_fees = if is_creator_fee_on_input {
|
||||
creator_fee = compute_creator_fee_new(input_amount, creator_fee_rate);
|
||||
input_amount.saturating_sub(trade_fee).saturating_sub(creator_fee)
|
||||
} else {
|
||||
input_amount.saturating_sub(trade_fee)
|
||||
};
|
||||
|
||||
let protocol_fee = compute_protocol_fund_fee(trade_fee, protocol_fee_rate);
|
||||
let fund_fee = compute_protocol_fund_fee(trade_fee, fund_fee_rate);
|
||||
|
||||
let output_amount_swapped = ((output_vault_amount as u128)
|
||||
.saturating_mul(input_amount_less_fees as u128)
|
||||
/ (input_vault_amount as u128).saturating_add(input_amount_less_fees as u128))
|
||||
as u64;
|
||||
|
||||
let output_amount = if is_creator_fee_on_input {
|
||||
output_amount_swapped
|
||||
} else {
|
||||
creator_fee = compute_creator_fee_new(output_amount_swapped, creator_fee_rate);
|
||||
output_amount_swapped.saturating_sub(creator_fee)
|
||||
};
|
||||
|
||||
SwapResult {
|
||||
new_input_vault_amount: input_vault_amount.saturating_add(input_amount_less_fees),
|
||||
new_output_vault_amount: output_vault_amount.saturating_sub(output_amount_swapped),
|
||||
input_amount,
|
||||
output_amount,
|
||||
trade_fee,
|
||||
protocol_fee,
|
||||
fund_fee,
|
||||
creator_fee,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes swap parameters including amounts, fees, and slippage protection.
|
||||
///
|
||||
/// This function calculates the expected output amount, minimum output amount (with slippage),
|
||||
/// and trading fees for a given input amount in a CPMM (Constant Product Market Maker) pool.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base_reserve` - The current reserve amount of the base token in the pool
|
||||
/// * `quote_reserve` - The current reserve amount of the quote token in the pool
|
||||
/// * `is_base_in` - Whether the input token is the base token (true) or quote token (false)
|
||||
/// * `amount_in` - The amount of input tokens to swap
|
||||
/// * `slippage_basis_points` - The acceptable slippage in basis points (e.g., 100 for 1%)
|
||||
///
|
||||
/// # Returns
|
||||
/// A `ComputeSwapParams` struct containing all computed swap parameters
|
||||
pub fn compute_swap_amount(
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
is_base_in: bool,
|
||||
amount_in: u64,
|
||||
slippage_basis_points: u64,
|
||||
) -> ComputeSwapParams {
|
||||
let (input_reserve, output_reserve) =
|
||||
if is_base_in { (base_reserve, quote_reserve) } else { (quote_reserve, base_reserve) };
|
||||
|
||||
let swap_result = swap_base_input(
|
||||
amount_in,
|
||||
input_reserve,
|
||||
output_reserve,
|
||||
TRADE_FEE_RATE,
|
||||
CREATOR_FEE_RATE,
|
||||
PROTOCOL_FEE_RATE,
|
||||
FUND_FEE_RATE,
|
||||
true,
|
||||
);
|
||||
|
||||
let min_amount_out = ((swap_result.output_amount as f64) * (1.0 - (slippage_basis_points as f64) / 10000.0)) as u64;
|
||||
|
||||
let all_trade = swap_result.input_amount == amount_in;
|
||||
|
||||
ComputeSwapParams {
|
||||
all_trade,
|
||||
amount_in,
|
||||
amount_out: swap_result.output_amount,
|
||||
min_amount_out,
|
||||
fee: swap_result.trade_fee,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod price;
|
||||
pub mod calc;
|
||||
|
||||
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
|
||||
use crate::trading;
|
||||
|
||||
Reference in New Issue
Block a user