rebuild sdk dir
This commit is contained in:
Executable
+107
@@ -0,0 +1,107 @@
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::swqos::SwqosClient;
|
||||
use crate::trading::{
|
||||
core::params::{PumpSwapParams, BonkParams},
|
||||
factory::Protocol,
|
||||
BuyParams, TradeFactory,
|
||||
};
|
||||
use crate::{common::PriorityFee, SolanaRpcClient};
|
||||
|
||||
// Constants for compute budget
|
||||
// Increased from 64KB to 256KB to handle larger transactions
|
||||
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
|
||||
|
||||
// Buy tokens from a Pumpswap pool
|
||||
pub async fn buy(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
amount_sol: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
auto_handle_wsol: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// 创建执行器
|
||||
let executor = TradeFactory::create_executor(Protocol::Bonk);
|
||||
// 创建协议特定参数
|
||||
let protocol_params = Box::new(BonkParams {
|
||||
auto_handle_wsol: auto_handle_wsol,
|
||||
virtual_base: Some(virtual_base),
|
||||
virtual_quote: Some(virtual_quote),
|
||||
real_base_before: Some(real_base_before),
|
||||
real_quote_before: Some(real_quote_before),
|
||||
});
|
||||
// 创建买入参数
|
||||
let buy_params = BuyParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer,
|
||||
mint: mint,
|
||||
creator: Pubkey::default(),
|
||||
amount_sol: amount_sol,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee,
|
||||
lookup_table_key: lookup_table_key,
|
||||
recent_blockhash: recent_blockhash,
|
||||
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
|
||||
protocol_params,
|
||||
};
|
||||
// 执行买入
|
||||
executor.buy(buy_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Buy tokens using a MEV service
|
||||
pub async fn buy_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
amount_sol: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
auto_handle_wsol: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// 创建执行器
|
||||
let executor = TradeFactory::create_executor(Protocol::Bonk);
|
||||
// 创建协议特定参数
|
||||
let protocol_params = Box::new(BonkParams {
|
||||
auto_handle_wsol: auto_handle_wsol,
|
||||
virtual_base: Some(virtual_base),
|
||||
virtual_quote: Some(virtual_quote),
|
||||
real_base_before: Some(real_base_before),
|
||||
real_quote_before: Some(real_quote_before),
|
||||
});
|
||||
// 创建买入参数
|
||||
let buy_params = BuyParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer,
|
||||
mint: mint,
|
||||
creator: Pubkey::default(),
|
||||
amount_sol: amount_sol,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee,
|
||||
lookup_table_key: lookup_table_key,
|
||||
recent_blockhash: recent_blockhash,
|
||||
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
|
||||
protocol_params,
|
||||
};
|
||||
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
|
||||
// 执行买入
|
||||
executor.buy_with_tip(buy_with_tip_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
use anyhow::anyhow;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_associated_token_account::get_associated_token_address;
|
||||
|
||||
use crate::{common::SolanaRpcClient, constants};
|
||||
|
||||
pub fn get_amount_out(
|
||||
amount_in: u64,
|
||||
protocol_fee_rate: u128,
|
||||
platform_fee_rate: u128,
|
||||
share_fee_rate: u128,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
slippage_basis_points: u128,
|
||||
) -> u64 {
|
||||
let amount_in_u128 = amount_in as u128;
|
||||
let protocol_fee = (amount_in_u128 * protocol_fee_rate / 10000) as u128;
|
||||
let platform_fee = (amount_in_u128 * platform_fee_rate / 10000) as u128;
|
||||
let share_fee = (amount_in_u128 * share_fee_rate / 10000) as u128;
|
||||
let amount_in_net = amount_in_u128
|
||||
.checked_sub(protocol_fee)
|
||||
.unwrap()
|
||||
.checked_sub(platform_fee)
|
||||
.unwrap()
|
||||
.checked_sub(share_fee)
|
||||
.unwrap();
|
||||
let input_reserve = virtual_quote.checked_add(real_quote_before).unwrap();
|
||||
let output_reserve = virtual_base.checked_sub(real_base_before).unwrap();
|
||||
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();
|
||||
|
||||
amount_out = amount_out - (amount_out * slippage_basis_points) / 10000;
|
||||
amount_out as u64
|
||||
}
|
||||
|
||||
pub fn get_pool_pda(base_mint: &Pubkey, quote_mint: &Pubkey) -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 3] = &[
|
||||
constants::bonk::seeds::POOL_SEED,
|
||||
base_mint.as_ref(),
|
||||
quote_mint.as_ref(),
|
||||
];
|
||||
let program_id: &Pubkey = &constants::bonk::accounts::BONK;
|
||||
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
||||
pda.map(|pubkey| pubkey.0)
|
||||
}
|
||||
|
||||
pub fn get_vault_pda(pool_state: &Pubkey, mint: &Pubkey) -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 3] = &[
|
||||
constants::bonk::seeds::POOL_VAULT_SEED,
|
||||
pool_state.as_ref(),
|
||||
mint.as_ref(),
|
||||
];
|
||||
let program_id: &Pubkey = &constants::bonk::accounts::BONK;
|
||||
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
||||
pda.map(|pubkey| pubkey.0)
|
||||
}
|
||||
|
||||
pub async fn get_token_balance(
|
||||
rpc: &SolanaRpcClient,
|
||||
payer: &Pubkey,
|
||||
mint: &Pubkey,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
println!("payer: {:?}", payer);
|
||||
println!("mint: {:?}", mint);
|
||||
let ata = get_associated_token_address(payer, mint);
|
||||
let balance = rpc.get_token_account_balance(&ata).await?;
|
||||
let balance_u64 = balance
|
||||
.amount
|
||||
.parse::<u64>()
|
||||
.map_err(|_| anyhow!("Failed to parse token balance"))?;
|
||||
Ok(balance_u64)
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
pub mod buy;
|
||||
pub mod sell;
|
||||
pub mod common;
|
||||
pub mod pool;
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
use crate::{common::SolanaRpcClient, constants::bonk::accounts};
|
||||
use anyhow::anyhow;
|
||||
use borsh::BorshDeserialize;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
#[derive(Debug, Clone, BorshDeserialize)]
|
||||
pub struct VestingSchedule {
|
||||
pub total_locked_amount: u64,
|
||||
pub cliff_period: u64,
|
||||
pub unlock_period: u64,
|
||||
pub start_time: u64,
|
||||
pub allocated_share_amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, BorshDeserialize)]
|
||||
pub struct Pool {
|
||||
pub epoch: u64,
|
||||
pub auth_bump: u8,
|
||||
pub status: u8,
|
||||
pub base_decimals: u8,
|
||||
pub quote_decimals: u8,
|
||||
pub migrate_type: u8,
|
||||
pub supply: u64,
|
||||
pub total_base_sell: u64,
|
||||
pub virtual_base: u64,
|
||||
pub virtual_quote: u64,
|
||||
pub real_base: u64,
|
||||
pub real_quote: u64,
|
||||
pub total_quote_fund_raising: u64,
|
||||
pub quote_protocol_fee: u64,
|
||||
pub platform_fee: u64,
|
||||
pub migrate_fee: u64,
|
||||
pub vesting_schedule: VestingSchedule,
|
||||
pub global_config: Pubkey,
|
||||
pub platform_config: Pubkey,
|
||||
pub base_mint: Pubkey,
|
||||
pub quote_mint: Pubkey,
|
||||
pub base_vault: Pubkey,
|
||||
pub quote_vault: Pubkey,
|
||||
pub creator: Pubkey,
|
||||
pub padding: [u64; 8],
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
|
||||
let pool = Pool::try_from_slice(&data[8..])?;
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
pub async fn fetch(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let account = rpc.get_account(pool_address).await?;
|
||||
|
||||
if account.owner != accounts::BONK {
|
||||
return Err(anyhow!("Account is not owned by Bonk program"));
|
||||
}
|
||||
|
||||
Self::from_bytes(&account.data)
|
||||
}
|
||||
}
|
||||
Executable
+240
@@ -0,0 +1,240 @@
|
||||
use anyhow::anyhow;
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::common::{PriorityFee, SolanaRpcClient};
|
||||
use crate::pumpswap::common::get_token_balance;
|
||||
use crate::swqos::SwqosClient;
|
||||
use crate::trading::{
|
||||
core::params::BonkParams, factory::Protocol, SellParams, TradeFactory,
|
||||
};
|
||||
|
||||
// Sell tokens to a Pumpswap pool
|
||||
pub async fn sell(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
amount_token: Option<u64>,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let executor = TradeFactory::create_executor(Protocol::Bonk);
|
||||
// 创建PumpFun协议参数
|
||||
let protocol_params = Box::new(BonkParams {
|
||||
virtual_base: Some(virtual_base),
|
||||
virtual_quote: Some(virtual_quote),
|
||||
real_base_before: Some(real_base_before),
|
||||
real_quote_before: Some(real_quote_before),
|
||||
auto_handle_wsol: true,
|
||||
});
|
||||
// 创建卖出参数
|
||||
let sell_params = SellParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer.clone(),
|
||||
mint,
|
||||
creator: Pubkey::default(),
|
||||
amount_token: amount_token,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee.clone(),
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
protocol_params,
|
||||
};
|
||||
// 执行卖出交易
|
||||
executor.sell(sell_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Sell tokens by percentage
|
||||
pub async fn sell_by_percent(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
percent: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if percent == 0 || percent > 100 {
|
||||
return Err(anyhow!("Percentage must be between 1 and 100"));
|
||||
}
|
||||
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
|
||||
let amount = balance_u64 * percent / 100;
|
||||
sell(
|
||||
rpc,
|
||||
payer,
|
||||
mint,
|
||||
virtual_base,
|
||||
virtual_quote,
|
||||
real_base_before,
|
||||
real_quote_before,
|
||||
Some(amount),
|
||||
slippage_basis_points,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sell tokens by amount
|
||||
pub async fn sell_by_amount(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
amount: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
sell(
|
||||
rpc,
|
||||
payer,
|
||||
mint,
|
||||
virtual_base,
|
||||
virtual_quote,
|
||||
real_base_before,
|
||||
real_quote_before,
|
||||
Some(amount),
|
||||
slippage_basis_points,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Sell tokens using a MEV service
|
||||
pub async fn sell_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
amount_token: Option<u64>,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let executor = TradeFactory::create_executor(Protocol::Bonk);
|
||||
// 创建PumpFun协议参数
|
||||
let protocol_params = Box::new(BonkParams {
|
||||
virtual_base: Some(virtual_base),
|
||||
virtual_quote: Some(virtual_quote),
|
||||
real_base_before: Some(real_base_before),
|
||||
real_quote_before: Some(real_quote_before),
|
||||
auto_handle_wsol: true,
|
||||
});
|
||||
// 创建卖出参数
|
||||
let sell_params = SellParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer.clone(),
|
||||
mint,
|
||||
creator: Pubkey::default(),
|
||||
amount_token: amount_token,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee.clone(),
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
protocol_params,
|
||||
};
|
||||
let sell_with_tip_params = sell_params.with_tip(swqos_clients);
|
||||
// 执行卖出交易
|
||||
executor.sell_with_tip(sell_with_tip_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Sell tokens by percentage using a MEV service
|
||||
pub async fn sell_by_percent_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
percent: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if percent == 0 || percent > 100 {
|
||||
return Err(anyhow!("Percentage must be between 1 and 100"));
|
||||
}
|
||||
|
||||
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
|
||||
let amount = balance_u64 * percent / 100;
|
||||
sell_with_tip(
|
||||
rpc,
|
||||
swqos_clients,
|
||||
payer,
|
||||
mint,
|
||||
virtual_base,
|
||||
virtual_quote,
|
||||
real_base_before,
|
||||
real_quote_before,
|
||||
Some(amount),
|
||||
slippage_basis_points,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Sell tokens by amount using a MEV service
|
||||
pub async fn sell_by_amount_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base_before: u128,
|
||||
real_quote_before: u128,
|
||||
amount: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
sell_with_tip(
|
||||
rpc,
|
||||
swqos_clients,
|
||||
payer,
|
||||
mint,
|
||||
virtual_base,
|
||||
virtual_quote,
|
||||
real_base_before,
|
||||
real_quote_before,
|
||||
Some(amount),
|
||||
slippage_basis_points,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::trading::protocols::bonk::BonkInstructionBuilder;
|
||||
use crate::instruction::{bonk::BonkInstructionBuilder, pumpfun::PumpFunInstructionBuilder, pumpswap::PumpSwapInstructionBuilder};
|
||||
|
||||
use super::{
|
||||
core::{executor::GenericTradeExecutor, traits::TradeExecutor},
|
||||
protocols::{pumpfun::PumpFunInstructionBuilder, pumpswap::PumpSwapInstructionBuilder},
|
||||
};
|
||||
|
||||
/// 支持的交易协议
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
pub mod common;
|
||||
pub mod core;
|
||||
pub mod factory;
|
||||
pub mod protocols;
|
||||
pub mod bonk;
|
||||
pub mod pumpfun;
|
||||
pub mod pumpswap;
|
||||
|
||||
pub use core::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams};
|
||||
pub use core::traits::{InstructionBuilder, TradeExecutor};
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer};
|
||||
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
|
||||
|
||||
use crate::{
|
||||
constants::bonk::{
|
||||
accounts, trade::DEFAULT_SLIPPAGE, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR,
|
||||
},
|
||||
bonk::{
|
||||
common::{get_amount_out, get_pool_pda, get_token_balance, get_vault_pda},
|
||||
pool::Pool,
|
||||
},
|
||||
trading::core::{
|
||||
params::{BuyParams, BonkParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
};
|
||||
|
||||
/// Bonk协议的指令构建器
|
||||
pub struct BonkInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl InstructionBuilder for BonkInstructionBuilder {
|
||||
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
|
||||
if params.amount_sol == 0 {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
}
|
||||
self.build_buy_instructions_with_accounts(params).await
|
||||
}
|
||||
|
||||
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
|
||||
self.build_sell_instructions_with_accounts(params).await
|
||||
}
|
||||
}
|
||||
|
||||
impl BonkInstructionBuilder {
|
||||
/// 使用提供的账户信息构建买入指令
|
||||
async fn build_buy_instructions_with_accounts(
|
||||
&self,
|
||||
params: &BuyParams,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<BonkParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?;
|
||||
|
||||
let pool_state = get_pool_pda(¶ms.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
|
||||
// 创建用户代币账户
|
||||
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
);
|
||||
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
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_before = protocol_params.real_base_before.unwrap_or(0);
|
||||
let mut real_quote_before = protocol_params.real_quote_before.unwrap_or(0);
|
||||
|
||||
if virtual_base == 0
|
||||
|| virtual_quote == 0
|
||||
|| real_base_before == 0
|
||||
|| real_quote_before == 0
|
||||
{
|
||||
let pool = Pool::fetch(params.rpc.as_ref().unwrap(), &pool_state).await?;
|
||||
virtual_base = pool.virtual_base as u128;
|
||||
virtual_quote = pool.virtual_quote as u128;
|
||||
real_base_before = pool.real_base as u128;
|
||||
real_quote_before = pool.real_quote as u128;
|
||||
}
|
||||
|
||||
let amount_in: u64 = params.amount_sol;
|
||||
let share_fee_rate: u64 = 0;
|
||||
let minimum_amount_out: u64 = get_amount_out(
|
||||
amount_in,
|
||||
accounts::PROTOCOL_FEE_RATE,
|
||||
accounts::PLATFORM_FEE_RATE,
|
||||
accounts::SHARE_FEE_RATE,
|
||||
virtual_base,
|
||||
virtual_quote,
|
||||
real_base_before,
|
||||
real_quote_before,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
|
||||
);
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
// 插入wsol
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
),
|
||||
);
|
||||
instructions.push(
|
||||
// 将SOL转入wSOL ATA账户
|
||||
solana_sdk::system_instruction::transfer(
|
||||
¶ms.payer.pubkey(),
|
||||
&user_quote_token_account,
|
||||
amount_in,
|
||||
),
|
||||
);
|
||||
|
||||
// 同步wSOL余额
|
||||
instructions.push(
|
||||
spl_token::instruction::sync_native(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
&user_quote_token_account,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// 创建用户的基础代币账户
|
||||
instructions.push(create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
));
|
||||
|
||||
// 创建买入指令
|
||||
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)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_CONFIG, false), // Global Config (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::PLATFORM_CONFIG, false), // Platform Config (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State
|
||||
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // User Base Token
|
||||
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // User Quote Token
|
||||
solana_sdk::instruction::AccountMeta::new(base_vault_account, false), // Base Vault
|
||||
solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault
|
||||
solana_sdk::instruction::AccountMeta::new(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(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)
|
||||
];
|
||||
// 创建指令数据
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&BUY_EXECT_IN_DISCRIMINATOR);
|
||||
data.extend_from_slice(&amount_in.to_le_bytes());
|
||||
data.extend_from_slice(&minimum_amount_out.to_le_bytes());
|
||||
data.extend_from_slice(&share_fee_rate.to_le_bytes());
|
||||
|
||||
instructions.push(Instruction {
|
||||
program_id: accounts::BONK,
|
||||
accounts,
|
||||
data,
|
||||
});
|
||||
|
||||
if protocol_params.auto_handle_wsol {
|
||||
// 关闭wSOL ATA账户,回收租金
|
||||
instructions.push(
|
||||
spl_token::instruction::close_account(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
&user_quote_token_account,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
/// 使用提供的账户信息构建卖出指令
|
||||
async fn build_sell_instructions_with_accounts(
|
||||
&self,
|
||||
params: &SellParams,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
|
||||
// 获取代币余额
|
||||
let mut amount = params.amount_token;
|
||||
if params.amount_token.is_none() || params.amount_token.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"));
|
||||
}
|
||||
|
||||
// 计算预期的SOL数量
|
||||
let minimum_amount_out: u64 = 1;
|
||||
|
||||
let pool_state = get_pool_pda(¶ms.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
|
||||
|
||||
// 创建用户代币账户
|
||||
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
);
|
||||
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
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 share_fee_rate: u64 = 0;
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
// 插入wsol
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
),
|
||||
);
|
||||
|
||||
// 创建用户的代币账户
|
||||
instructions.push(create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
));
|
||||
|
||||
// 创建卖出指令
|
||||
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)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_CONFIG, false), // Global Config (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::PLATFORM_CONFIG, false), // Platform Config (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State
|
||||
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // User Base Token
|
||||
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // User Quote Token
|
||||
solana_sdk::instruction::AccountMeta::new(base_vault_account, false), // Base Vault
|
||||
solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault
|
||||
solana_sdk::instruction::AccountMeta::new(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(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)
|
||||
];
|
||||
|
||||
// 创建指令数据
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&SELL_EXECT_IN_DISCRIMINATOR);
|
||||
data.extend_from_slice(&amount.to_le_bytes());
|
||||
data.extend_from_slice(&minimum_amount_out.to_le_bytes());
|
||||
data.extend_from_slice(&share_fee_rate.to_le_bytes());
|
||||
|
||||
instructions.push(Instruction {
|
||||
program_id: accounts::BONK,
|
||||
accounts,
|
||||
data,
|
||||
});
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod pumpfun;
|
||||
pub mod pumpswap;
|
||||
pub mod bonk;
|
||||
@@ -1,144 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use solana_sdk::{
|
||||
instruction::Instruction, native_token::sol_to_lamports, pubkey::Pubkey, signer::Signer,
|
||||
};
|
||||
use spl_associated_token_account::{
|
||||
get_associated_token_address, instruction::create_associated_token_account,
|
||||
};
|
||||
use spl_token::instruction::close_account;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
accounts::BondingCurveAccount,
|
||||
constants::{self, pumpfun::{global_constants::FEE_RECIPIENT, trade::DEFAULT_SLIPPAGE}, trade_type::SNIPER_BUY},
|
||||
instruction,
|
||||
pumpfun::common::{
|
||||
calculate_with_slippage_buy, get_bonding_curve_account_v2, get_bonding_curve_pda,
|
||||
get_buy_token_amount_from_sol_amount, get_creator_vault_pda, init_bonding_curve_account,
|
||||
},
|
||||
trading::core::{
|
||||
params::{BuyParams, PumpFunParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
};
|
||||
|
||||
/// PumpFun协议的指令构建器
|
||||
pub struct PumpFunInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
|
||||
// 获取PumpFun特定参数
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<PumpFunParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?;
|
||||
|
||||
if params.amount_sol == 0 {
|
||||
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 max_sol_cost = calculate_with_slippage_buy(
|
||||
params.amount_sol,
|
||||
params
|
||||
.slippage_basis_points
|
||||
.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
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.amount_sol);
|
||||
if buy_token_amount <= 100 * 1_000_000_u64 {
|
||||
buy_token_amount = if max_sol_cost > sol_to_lamports(0.01) {
|
||||
25547619 * 1_000_000_u64
|
||||
} else {
|
||||
255476 * 1_000_000_u64
|
||||
};
|
||||
}
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
// 创建关联代币账户
|
||||
instructions.push(create_associated_token_account(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
&constants::pumpfun::accounts::TOKEN_PROGRAM,
|
||||
));
|
||||
|
||||
// 创建买入指令
|
||||
instructions.push(instruction::buy(
|
||||
params.payer.as_ref(),
|
||||
¶ms.mint,
|
||||
&bonding_curve.account,
|
||||
&creator_vault_pda,
|
||||
&FEE_RECIPIENT,
|
||||
instruction::Buy {
|
||||
_amount: buy_token_amount,
|
||||
_max_sol_cost: max_sol_cost,
|
||||
},
|
||||
));
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
|
||||
let amount_token = if let Some(amount) = params.amount_token {
|
||||
if amount == 0 {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
}
|
||||
amount
|
||||
} else {
|
||||
return Err(anyhow!("Amount token is required"));
|
||||
};
|
||||
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 amount_token = amount_token;
|
||||
if amount_token > balance_u64 {
|
||||
amount_token = balance_u64;
|
||||
}
|
||||
|
||||
let mut instructions = vec![instruction::sell(
|
||||
params.payer.as_ref(),
|
||||
¶ms.mint,
|
||||
&creator_vault_pda,
|
||||
&FEE_RECIPIENT,
|
||||
instruction::Sell {
|
||||
_amount: amount_token,
|
||||
_min_sol_output: 1,
|
||||
},
|
||||
)];
|
||||
|
||||
// 如果卖出全部代币,关闭账户
|
||||
if amount_token >= balance_u64 {
|
||||
instructions.push(close_account(
|
||||
&spl_token::ID,
|
||||
&ata,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[¶ms.payer.pubkey()],
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
@@ -1,428 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer};
|
||||
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
constants::pumpswap::{
|
||||
accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR,
|
||||
},
|
||||
pumpswap::common::{
|
||||
calculate_with_slippage_buy, calculate_with_slippage_sell, coin_creator_vault_ata,
|
||||
coin_creator_vault_authority, find_pool, get_buy_token_amount, get_sell_sol_amount,
|
||||
get_token_balance,
|
||||
},
|
||||
trading::core::{
|
||||
params::{BuyParams, PumpSwapParams, SellParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
};
|
||||
|
||||
/// PumpSwap协议的指令构建器
|
||||
pub struct PumpSwapInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
|
||||
// 获取PumpSwap特定参数
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<PumpSwapParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
|
||||
|
||||
if params.amount_sol == 0 {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
}
|
||||
|
||||
// 根据是否提供了账户信息来构建指令
|
||||
match (
|
||||
&protocol_params.pool,
|
||||
&protocol_params.pool_base_token_account,
|
||||
&protocol_params.pool_quote_token_account,
|
||||
&protocol_params.user_base_token_account,
|
||||
&protocol_params.user_quote_token_account,
|
||||
) {
|
||||
(
|
||||
Some(pool),
|
||||
Some(pool_base_token_account),
|
||||
Some(pool_quote_token_account),
|
||||
Some(user_base_token_account),
|
||||
Some(user_quote_token_account),
|
||||
) => {
|
||||
self.build_buy_instructions_with_accounts(
|
||||
params,
|
||||
*pool,
|
||||
*pool_base_token_account,
|
||||
*pool_quote_token_account,
|
||||
*user_base_token_account,
|
||||
*user_quote_token_account,
|
||||
protocol_params.auto_handle_wsol,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => self.build_buy_instructions_auto_discover(params).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
|
||||
// 获取PumpSwap特定参数
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<PumpSwapParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
|
||||
|
||||
// 根据是否提供了账户信息来构建指令
|
||||
match (
|
||||
&protocol_params.pool,
|
||||
&protocol_params.pool_base_token_account,
|
||||
&protocol_params.pool_quote_token_account,
|
||||
&protocol_params.user_base_token_account,
|
||||
&protocol_params.user_quote_token_account,
|
||||
) {
|
||||
(
|
||||
Some(pool),
|
||||
Some(pool_base_token_account),
|
||||
Some(pool_quote_token_account),
|
||||
Some(user_base_token_account),
|
||||
Some(user_quote_token_account),
|
||||
) => {
|
||||
self.build_sell_instructions_with_accounts(
|
||||
params,
|
||||
*pool,
|
||||
*pool_base_token_account,
|
||||
*pool_quote_token_account,
|
||||
*user_base_token_account,
|
||||
*user_quote_token_account,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => self.build_sell_instructions_auto_discover(params).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PumpSwapInstructionBuilder {
|
||||
/// 自动发现池和账户信息并构建买入指令
|
||||
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"));
|
||||
}
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
// 查找池
|
||||
let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?;
|
||||
|
||||
// 创建用户代币账户
|
||||
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
);
|
||||
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
let pool_base_token_account =
|
||||
spl_associated_token_account::get_associated_token_address_with_program_id(
|
||||
&pool,
|
||||
¶ms.mint,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
);
|
||||
|
||||
let pool_quote_token_account =
|
||||
spl_associated_token_account::get_associated_token_address_with_program_id(
|
||||
&pool,
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
);
|
||||
|
||||
self.build_buy_instructions_with_accounts(
|
||||
params,
|
||||
pool,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// 自动发现池和账户信息并构建卖出指令
|
||||
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"));
|
||||
}
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
|
||||
// 查找池
|
||||
let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?;
|
||||
|
||||
// 创建用户代币账户
|
||||
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
);
|
||||
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
);
|
||||
|
||||
// 获取池的代币账户
|
||||
let pool_base_token_account =
|
||||
spl_associated_token_account::get_associated_token_address_with_program_id(
|
||||
&pool,
|
||||
¶ms.mint,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
);
|
||||
|
||||
let pool_quote_token_account =
|
||||
spl_associated_token_account::get_associated_token_address_with_program_id(
|
||||
&pool,
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
);
|
||||
|
||||
self.build_sell_instructions_with_accounts(
|
||||
params,
|
||||
pool,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// 使用提供的账户信息构建买入指令
|
||||
async fn build_buy_instructions_with_accounts(
|
||||
&self,
|
||||
params: &BuyParams,
|
||||
pool: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
user_base_token_account: Pubkey,
|
||||
user_quote_token_account: Pubkey,
|
||||
auto_handle_wsol: bool,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
// 计算预期的代币数量
|
||||
let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, params.amount_sol).await?;
|
||||
|
||||
// 计算滑点后的最大SOL数量
|
||||
let max_sol_amount = calculate_with_slippage_buy(
|
||||
params.amount_sol,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
if auto_handle_wsol {
|
||||
// 插入wsol
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
),
|
||||
);
|
||||
instructions.push(
|
||||
// 将SOL转入wSOL ATA账户
|
||||
solana_sdk::system_instruction::transfer(
|
||||
¶ms.payer.pubkey(),
|
||||
&user_quote_token_account,
|
||||
max_sol_amount,
|
||||
),
|
||||
);
|
||||
|
||||
// 同步wSOL余额
|
||||
instructions.push(
|
||||
spl_token::instruction::sync_native(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
&user_quote_token_account,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// 创建用户的基础代币账户
|
||||
instructions.push(create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
));
|
||||
|
||||
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator);
|
||||
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
|
||||
|
||||
// 创建买入指令
|
||||
let accounts = vec![
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // user (signer)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account
|
||||
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account
|
||||
solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account
|
||||
solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(
|
||||
accounts::ASSOCIATED_TOKEN_PROGRAM,
|
||||
false,
|
||||
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
|
||||
];
|
||||
|
||||
// 创建指令数据
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&BUY_DISCRIMINATOR);
|
||||
data.extend_from_slice(&token_amount.to_le_bytes());
|
||||
data.extend_from_slice(&max_sol_amount.to_le_bytes());
|
||||
|
||||
instructions.push(Instruction {
|
||||
program_id: accounts::AMM_PROGRAM,
|
||||
accounts,
|
||||
data,
|
||||
});
|
||||
|
||||
if auto_handle_wsol {
|
||||
// 关闭wSOL ATA账户,回收租金
|
||||
instructions.push(
|
||||
spl_token::instruction::close_account(
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
&user_quote_token_account,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
/// 使用提供的账户信息构建卖出指令
|
||||
async fn build_sell_instructions_with_accounts(
|
||||
&self,
|
||||
params: &SellParams,
|
||||
pool: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
user_base_token_account: Pubkey,
|
||||
user_quote_token_account: Pubkey,
|
||||
) -> Result<Vec<Instruction>> {
|
||||
if params.rpc.is_none() {
|
||||
return Err(anyhow!("RPC is not set"));
|
||||
}
|
||||
let rpc = params.rpc.as_ref().unwrap().clone();
|
||||
|
||||
// 获取代币余额
|
||||
let mut amount = params.amount_token;
|
||||
if params.amount_token.is_none() {
|
||||
let (balance_u64, _) =
|
||||
get_token_balance(rpc.as_ref(), params.payer.as_ref(), ¶ms.mint).await?;
|
||||
amount = Some(balance_u64);
|
||||
}
|
||||
let amount = amount.unwrap_or(0);
|
||||
|
||||
if amount == 0 {
|
||||
return Err(anyhow!("Amount cannot be zero"));
|
||||
}
|
||||
|
||||
// 计算预期的SOL数量
|
||||
let sol_amount = get_sell_sol_amount(rpc.as_ref(), &pool, amount).await?;
|
||||
|
||||
// 计算滑点后的最小SOL数量
|
||||
let min_sol_amount = calculate_with_slippage_sell(
|
||||
sol_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
|
||||
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator);
|
||||
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
|
||||
|
||||
let mut instructions = vec![];
|
||||
|
||||
// 插入wsol
|
||||
instructions.push(
|
||||
// 创建wSOL ATA账户,如果不存在
|
||||
create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
),
|
||||
);
|
||||
|
||||
// 创建用户的代币账户
|
||||
instructions.push(create_associated_token_account_idempotent(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.mint,
|
||||
&accounts::TOKEN_PROGRAM,
|
||||
));
|
||||
|
||||
// 创建卖出指令
|
||||
let accounts = vec![
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // user (signer)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // mint (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account
|
||||
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account
|
||||
solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account
|
||||
solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(
|
||||
accounts::ASSOCIATED_TOKEN_PROGRAM,
|
||||
false,
|
||||
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
|
||||
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
|
||||
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
|
||||
];
|
||||
|
||||
// 创建指令数据
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(&SELL_DISCRIMINATOR);
|
||||
data.extend_from_slice(&amount.to_le_bytes());
|
||||
data.extend_from_slice(&min_sol_amount.to_le_bytes());
|
||||
|
||||
instructions.push(Instruction {
|
||||
program_id: accounts::AMM_PROGRAM,
|
||||
accounts,
|
||||
data,
|
||||
});
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
use crate::accounts::BondingCurveAccount;
|
||||
use crate::{
|
||||
common::{PriorityFee, SolanaRpcClient},
|
||||
swqos::SwqosClient,
|
||||
trading::{core::params::PumpFunParams, factory::Protocol, BuyParams, TradeFactory},
|
||||
};
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use std::sync::Arc;
|
||||
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 250000;
|
||||
|
||||
pub async fn buy(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
buy_sol_cost: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
bonding_curve: Option<Arc<BondingCurveAccount>>,
|
||||
trade_type: String,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// 创建执行器
|
||||
let executor = TradeFactory::create_executor(Protocol::PumpFun);
|
||||
// 创建协议特定参数
|
||||
let protocol_params = Box::new(PumpFunParams {
|
||||
trade_type: trade_type,
|
||||
bonding_curve: bonding_curve,
|
||||
});
|
||||
// 创建买入参数
|
||||
let buy_params = BuyParams {
|
||||
rpc: Some(rpc),
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
amount_sol: buy_sol_cost,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee,
|
||||
lookup_table_key: lookup_table_key,
|
||||
recent_blockhash,
|
||||
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
|
||||
protocol_params,
|
||||
};
|
||||
// 执行买入
|
||||
executor.buy(buy_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn buy_with_tip(
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
buy_sol_cost: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
bonding_curve: Option<Arc<BondingCurveAccount>>,
|
||||
trade_type: String,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// 创建执行器
|
||||
let executor = TradeFactory::create_executor(Protocol::PumpFun);
|
||||
// 创建协议特定参数
|
||||
let protocol_params = Box::new(PumpFunParams {
|
||||
trade_type: trade_type,
|
||||
bonding_curve: bonding_curve,
|
||||
});
|
||||
// 创建买入参数
|
||||
let buy_params = BuyParams {
|
||||
rpc: None,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
amount_sol: buy_sol_cost,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee,
|
||||
lookup_table_key: lookup_table_key,
|
||||
recent_blockhash,
|
||||
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
|
||||
protocol_params,
|
||||
};
|
||||
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
|
||||
// 执行买入
|
||||
executor.buy_with_tip(buy_with_tip_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
Executable
+352
@@ -0,0 +1,352 @@
|
||||
use anyhow::anyhow;
|
||||
use borsh::BorshDeserialize;
|
||||
use spl_token::instruction::close_account;
|
||||
use tokio::sync::RwLock;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use solana_sdk::{
|
||||
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::Transaction
|
||||
};
|
||||
use spl_associated_token_account::get_associated_token_address;
|
||||
use pumpfun_program::accounts::BondingCurveAccount as PumpfunBondingCurveAccount;
|
||||
use crate::{accounts::{self, BondingCurveAccount}, common::{PriorityFee, SolanaRpcClient}, constants::{self, pumpfun::{self, global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::DEFAULT_SLIPPAGE}}, event_parser::protocols::pumpfun::PumpFunTradeEvent};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<accounts::GlobalAccount>>> = RwLock::new(HashMap::new());
|
||||
}
|
||||
|
||||
pub async fn transfer_sol(rpc: &SolanaRpcClient, payer: &Keypair, receive_wallet: &Pubkey, amount: u64) -> Result<(), anyhow::Error> {
|
||||
if amount == 0 {
|
||||
return Err(anyhow!("transfer_sol: Amount cannot be zero"));
|
||||
}
|
||||
|
||||
let balance = get_sol_balance(rpc, &payer.pubkey()).await?;
|
||||
if balance < amount {
|
||||
return Err(anyhow!("Insufficient balance"));
|
||||
}
|
||||
|
||||
let transfer_instruction = system_instruction::transfer(
|
||||
&payer.pubkey(),
|
||||
receive_wallet,
|
||||
amount,
|
||||
);
|
||||
|
||||
let recent_blockhash = rpc.get_latest_blockhash().await?;
|
||||
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&[transfer_instruction],
|
||||
Some(&payer.pubkey()),
|
||||
&[payer],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
rpc.send_and_confirm_transaction(&transaction).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 关闭代币账户
|
||||
///
|
||||
/// 此函数用于关闭指定代币的关联代币账户,将账户中的代币余额转移给账户所有者。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// * `rpc` - Solana RPC客户端
|
||||
/// * `payer` - 支付交易费用的账户
|
||||
/// * `mint` - 代币的Mint地址
|
||||
///
|
||||
/// # 返回值
|
||||
///
|
||||
/// 返回一个Result,成功时返回(),失败时返回错误
|
||||
pub async fn close_token_account(rpc: &SolanaRpcClient, payer: &Keypair, mint: &Pubkey) -> Result<(), anyhow::Error> {
|
||||
// 获取关联代币账户地址
|
||||
let ata = get_associated_token_address(&payer.pubkey(), mint);
|
||||
|
||||
// 检查账户是否存在
|
||||
let account_exists = rpc.get_account(&ata).await.is_ok();
|
||||
if !account_exists {
|
||||
return Ok(()); // 如果账户不存在,直接返回成功
|
||||
}
|
||||
|
||||
// 构建关闭账户指令
|
||||
let close_account_ix = close_account(
|
||||
&spl_token::ID,
|
||||
&ata,
|
||||
&payer.pubkey(),
|
||||
&payer.pubkey(),
|
||||
&[&payer.pubkey()],
|
||||
)?;
|
||||
|
||||
// 构建交易
|
||||
let recent_blockhash = rpc.get_latest_blockhash().await?;
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&[close_account_ix],
|
||||
Some(&payer.pubkey()),
|
||||
&[payer],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
// 发送交易
|
||||
rpc.send_and_confirm_transaction(&transaction).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instruction> {
|
||||
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 async fn get_token_balance(rpc: &SolanaRpcClient, payer: &Pubkey, mint: &Pubkey) -> Result<u64, anyhow::Error> {
|
||||
let ata = get_associated_token_address(payer, mint);
|
||||
// let account_data = rpc.get_account_data(&ata).await?;
|
||||
// let token_account = Account::unpack(&account_data.as_slice())?;
|
||||
|
||||
// Ok(token_account.amount)
|
||||
|
||||
// println!("get_token_balance ata: {}", ata);
|
||||
let balance = rpc.get_token_account_balance(&ata).await?;
|
||||
let balance_u64 = balance.amount.parse::<u64>()
|
||||
.map_err(|_| anyhow!("Failed to parse token balance"))?;
|
||||
Ok(balance_u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_token_balance_and_ata(rpc: &SolanaRpcClient, payer: &Keypair, mint: &Pubkey) -> Result<(u64, Pubkey), anyhow::Error> {
|
||||
let ata = get_associated_token_address(&payer.pubkey(), mint);
|
||||
// let account_data = rpc.get_account_data(&ata).await?;
|
||||
// let token_account = Account::unpack(&account_data)?;
|
||||
|
||||
// Ok((token_account.amount, ata))
|
||||
|
||||
let balance = rpc.get_token_account_balance(&ata).await?;
|
||||
let balance_u64 = balance.amount.parse::<u64>()
|
||||
.map_err(|_| anyhow!("Failed to parse token balance"))?;
|
||||
|
||||
if balance_u64 == 0 {
|
||||
return Err(anyhow!("Balance is 0"));
|
||||
}
|
||||
|
||||
Ok((balance_u64, ata))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_sol_balance(rpc: &SolanaRpcClient, account: &Pubkey) -> Result<u64, anyhow::Error> {
|
||||
let balance = rpc.get_balance(account).await?;
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
#[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
|
||||
});
|
||||
*GLOBAL_PDA
|
||||
}
|
||||
|
||||
#[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
|
||||
});
|
||||
*MINT_AUTHORITY_PDA
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 2] = &[constants::pumpfun::seeds::BONDING_CURVE_SEED, mint.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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_creator_vault_pda(creator: &Pubkey) -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 2] = &[constants::pumpfun::seeds::CREATOR_VAULT_SEED, creator.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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&[
|
||||
constants::pumpfun::seeds::METADATA_SEED,
|
||||
constants::pumpfun::accounts::MPL_TOKEN_METADATA.as_ref(),
|
||||
mint.as_ref(),
|
||||
],
|
||||
&constants::pumpfun::accounts::MPL_TOKEN_METADATA
|
||||
).0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_global_account(/*rpc: &SolanaRpcClient*/) -> Result<Arc<accounts::GlobalAccount>, anyhow::Error> {
|
||||
// let global = constants::global_constants::GLOBAL_ACCOUNT;
|
||||
// if let Some(account) = ACCOUNT_CACHE.read().await.get(&global) {
|
||||
// return Ok(account.clone());
|
||||
// }
|
||||
|
||||
let global_account = accounts::GlobalAccount::new();
|
||||
|
||||
// let account = rpc.get_account(&global).await?;
|
||||
// let global_account = bincode::deserialize::<accounts::GlobalAccount>(&account.data)?;
|
||||
let global_account = Arc::new(global_account);
|
||||
|
||||
// ACCOUNT_CACHE.write().await.insert(global, global_account.clone());
|
||||
Ok(global_account)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_initial_buy_price(global_account: &Arc<accounts::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<accounts::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::<accounts::BondingCurveAccount>(&account.data)?);
|
||||
|
||||
Ok((bonding_curve, bonding_curve_pda))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn get_bonding_curve_account_v2(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<(Arc<PumpfunBondingCurveAccount>, 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 = solana_sdk::borsh1::try_from_slice_unchecked::<PumpfunBondingCurveAccount>(&account.data)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to deserialize bonding curve account: {}", e))?;
|
||||
|
||||
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,
|
||||
dev_buy_token: u64,
|
||||
dev_sol_cost: u64,
|
||||
creator: Pubkey,
|
||||
) -> Result<Arc<BondingCurveAccount>, anyhow::Error> {
|
||||
let bonding_curve = BondingCurveAccount::new(mint, dev_buy_token, dev_sol_cost, creator);
|
||||
let bonding_curve = Arc::new(bonding_curve);
|
||||
Ok(bonding_curve)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_buy_amount_with_slippage(amount_sol: u64, slippage_basis_points: Option<u64>) -> u64 {
|
||||
let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE);
|
||||
amount_sol + (amount_sol * slippage / 10000)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_token_price(virtual_sol_reserves: u64, virtual_token_reserves: u64) -> f64 {
|
||||
let v_sol = virtual_sol_reserves as f64 / 100_000_000.0;
|
||||
let v_tokens = virtual_token_reserves as f64 / 100_000.0;
|
||||
v_sol / v_tokens
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_buy_price(amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
|
||||
if amount == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
|
||||
amount + (amount * basis_points) / 10000
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
|
||||
amount - (amount * basis_points) / 10000
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
pub mod buy;
|
||||
pub mod sell;
|
||||
pub mod common;
|
||||
Executable
+184
@@ -0,0 +1,184 @@
|
||||
use crate::trading::{
|
||||
core::params::PumpFunSellParams, factory::Protocol, SellParams, TradeFactory,
|
||||
};
|
||||
use crate::{
|
||||
common::{PriorityFee, SolanaRpcClient},
|
||||
swqos::SwqosClient,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub async fn sell(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount_token: u64,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let executor = TradeFactory::create_executor(Protocol::PumpFun);
|
||||
// 创建PumpFun协议参数
|
||||
let protocol_params = Box::new(PumpFunSellParams {});
|
||||
// 创建卖出参数
|
||||
let sell_params = SellParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer.clone(),
|
||||
mint,
|
||||
creator,
|
||||
amount_token: Some(amount_token),
|
||||
slippage_basis_points: None,
|
||||
priority_fee: priority_fee.clone(),
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
protocol_params,
|
||||
};
|
||||
// 执行卖出交易
|
||||
executor.sell(sell_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sell tokens by percentage
|
||||
pub async fn sell_by_percent(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
percent: u64,
|
||||
amount_token: u64,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if percent == 0 || percent > 100 {
|
||||
return Err(anyhow!("Percentage must be between 1 and 100"));
|
||||
}
|
||||
let amount = amount_token * percent / 100;
|
||||
sell(
|
||||
rpc,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
amount,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sell tokens by amount
|
||||
pub async fn sell_by_amount(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount: u64,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
sell(
|
||||
rpc,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
amount,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn sell_by_percent_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
fee_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
percent: u64,
|
||||
amount_token: u64,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if percent == 0 || percent > 100 {
|
||||
return Err(anyhow!("Percentage must be between 1 and 100"));
|
||||
}
|
||||
let amount = amount_token * percent / 100;
|
||||
sell_with_tip(
|
||||
rpc,
|
||||
fee_clients,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
amount,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn sell_by_amount_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
fee_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount: u64,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
sell_with_tip(
|
||||
rpc,
|
||||
fee_clients,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
amount,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sell tokens using Jito
|
||||
pub async fn sell_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
fee_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount_token: u64,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let executor = TradeFactory::create_executor(Protocol::PumpFun);
|
||||
// 创建PumpFun协议参数
|
||||
let protocol_params = Box::new(PumpFunSellParams {});
|
||||
// 创建卖出参数
|
||||
let sell_params = SellParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer.clone(),
|
||||
mint,
|
||||
creator,
|
||||
amount_token: Some(amount_token),
|
||||
slippage_basis_points: None,
|
||||
priority_fee: priority_fee.clone(),
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
protocol_params,
|
||||
};
|
||||
let sell_with_tip_params = sell_params.with_tip(fee_clients);
|
||||
// 执行卖出交易
|
||||
executor.sell_with_tip(sell_with_tip_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::swqos::SwqosClient;
|
||||
use crate::trading::{core::params::PumpSwapParams, factory::Protocol, BuyParams, TradeFactory};
|
||||
use crate::{common::PriorityFee, SolanaRpcClient};
|
||||
|
||||
// Constants for compute budget
|
||||
// Increased from 64KB to 256KB to handle larger transactions
|
||||
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
|
||||
|
||||
// Buy tokens from a Pumpswap pool
|
||||
pub async fn buy(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount_sol: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
// 可选(必须全部传)
|
||||
pool: Option<Pubkey>,
|
||||
pool_base_token_account: Option<Pubkey>,
|
||||
pool_quote_token_account: Option<Pubkey>,
|
||||
user_base_token_account: Option<Pubkey>,
|
||||
user_quote_token_account: Option<Pubkey>,
|
||||
auto_handle_wsol: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// 创建执行器
|
||||
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
|
||||
// 创建协议特定参数
|
||||
let protocol_params = Box::new(PumpSwapParams {
|
||||
pool: pool,
|
||||
pool_base_token_account: pool_base_token_account,
|
||||
pool_quote_token_account: pool_quote_token_account,
|
||||
user_base_token_account: user_base_token_account,
|
||||
user_quote_token_account: user_quote_token_account,
|
||||
auto_handle_wsol: auto_handle_wsol,
|
||||
});
|
||||
// 创建买入参数
|
||||
let buy_params = BuyParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer,
|
||||
mint: mint,
|
||||
creator: creator,
|
||||
amount_sol: amount_sol,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee,
|
||||
lookup_table_key: lookup_table_key,
|
||||
recent_blockhash: recent_blockhash,
|
||||
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
|
||||
protocol_params,
|
||||
};
|
||||
// 执行买入
|
||||
executor.buy(buy_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Buy tokens using a MEV service
|
||||
pub async fn buy_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount_sol: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
// 可选(必须全部传)
|
||||
pool: Option<Pubkey>,
|
||||
pool_base_token_account: Option<Pubkey>,
|
||||
pool_quote_token_account: Option<Pubkey>,
|
||||
user_base_token_account: Option<Pubkey>,
|
||||
user_quote_token_account: Option<Pubkey>,
|
||||
auto_handle_wsol: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// 创建执行器
|
||||
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
|
||||
// 创建协议特定参数
|
||||
let protocol_params = Box::new(PumpSwapParams {
|
||||
pool: pool,
|
||||
pool_base_token_account: pool_base_token_account,
|
||||
pool_quote_token_account: pool_quote_token_account,
|
||||
user_base_token_account: user_base_token_account,
|
||||
user_quote_token_account: user_quote_token_account,
|
||||
auto_handle_wsol: auto_handle_wsol,
|
||||
});
|
||||
// 创建买入参数
|
||||
let buy_params = BuyParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer,
|
||||
mint: mint,
|
||||
creator: creator,
|
||||
amount_sol: amount_sol,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee,
|
||||
lookup_table_key: lookup_table_key,
|
||||
recent_blockhash: recent_blockhash,
|
||||
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
|
||||
protocol_params,
|
||||
};
|
||||
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
|
||||
// 执行买入
|
||||
executor.buy_with_tip(buy_with_tip_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
use anyhow::anyhow;
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use crate::common::SolanaRpcClient;
|
||||
use crate::trading::pumpswap;
|
||||
|
||||
// Calculate slippage for buy operations
|
||||
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
|
||||
amount + (amount * basis_points / 10000)
|
||||
}
|
||||
|
||||
// Calculate slippage for sell operations
|
||||
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
|
||||
if amount <= basis_points / 10000 {
|
||||
1
|
||||
} else {
|
||||
amount - (amount * basis_points / 10000)
|
||||
}
|
||||
}
|
||||
|
||||
// Get token balance for a specific mint and owner
|
||||
pub async fn get_token_balance(
|
||||
rpc: &SolanaRpcClient,
|
||||
owner: &Keypair,
|
||||
mint: &Pubkey,
|
||||
) -> Result<(u64, Pubkey), anyhow::Error> {
|
||||
let ata = spl_associated_token_account::get_associated_token_address(&owner.pubkey(), mint);
|
||||
|
||||
match rpc.get_token_account_balance(&ata).await {
|
||||
Ok(balance) => {
|
||||
let amount = balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
|
||||
Ok((amount, ata))
|
||||
}
|
||||
Err(_) => Ok((0, ata)),
|
||||
}
|
||||
}
|
||||
|
||||
// Find a pool for a specific mint
|
||||
pub async fn find_pool(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<Pubkey, anyhow::Error> {
|
||||
let (pool_address, _) = pumpswap::pool::Pool::find_by_mint(rpc, mint).await?;
|
||||
Ok(pool_address)
|
||||
}
|
||||
|
||||
// Calculate the amount of tokens to receive for a given SOL amount
|
||||
pub async fn get_buy_token_amount(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool: &Pubkey,
|
||||
sol_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let pool_data = pumpswap::pool::Pool::fetch(rpc, pool).await?;
|
||||
pool_data.calculate_buy_amount(rpc, sol_amount).await
|
||||
}
|
||||
|
||||
// Calculate the amount of SOL to receive for a given token amount
|
||||
pub async fn get_sell_sol_amount(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool: &Pubkey,
|
||||
token_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let pool_data = pumpswap::pool::Pool::fetch(rpc, pool).await?;
|
||||
pool_data.calculate_sell_amount(rpc, token_amount).await
|
||||
}
|
||||
|
||||
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()],
|
||||
&crate::constants::pumpswap::accounts::AMM_PROGRAM,
|
||||
);
|
||||
pump_pool_authority
|
||||
}
|
||||
|
||||
pub(crate) fn coin_creator_vault_ata(coin_creator: Pubkey) -> Pubkey {
|
||||
let creator_vault_authority = coin_creator_vault_authority(coin_creator);
|
||||
let associated_token_creator_vault_authority =
|
||||
spl_associated_token_account::get_associated_token_address_with_program_id(
|
||||
&creator_vault_authority,
|
||||
&crate::constants::pumpswap::accounts::WSOL_TOKEN_ACCOUNT,
|
||||
&crate::constants::pumpswap::accounts::TOKEN_PROGRAM,
|
||||
);
|
||||
associated_token_creator_vault_authority
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
pub mod buy;
|
||||
pub mod sell;
|
||||
pub mod common;
|
||||
pub mod pool;
|
||||
Executable
+189
@@ -0,0 +1,189 @@
|
||||
use crate::{common::SolanaRpcClient, constants::pumpswap::accounts};
|
||||
use anyhow::anyhow;
|
||||
use solana_account_decoder::UiAccountEncoding;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pool {
|
||||
pub pool_bump: u8,
|
||||
pub index: u16,
|
||||
pub creator: Pubkey,
|
||||
pub base_mint: Pubkey,
|
||||
pub quote_mint: Pubkey,
|
||||
pub lp_mint: Pubkey,
|
||||
pub pool_base_token_account: Pubkey,
|
||||
pub pool_quote_token_account: Pubkey,
|
||||
pub lp_supply: u64,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
|
||||
if data.len() < 211 {
|
||||
return Err(anyhow!("Data too short for Pool account"));
|
||||
}
|
||||
|
||||
// 跳过discriminator (8字节)
|
||||
let data = &data[8..];
|
||||
|
||||
let pool_bump = data[0];
|
||||
let index = u16::from_le_bytes([data[1], data[2]]);
|
||||
|
||||
let creator = Pubkey::new_from_array(
|
||||
data[3..35]
|
||||
.try_into()
|
||||
.map_err(|e| anyhow!("Failed to convert creator: {:?}", e))?,
|
||||
);
|
||||
let base_mint = Pubkey::new_from_array(
|
||||
data[35..67]
|
||||
.try_into()
|
||||
.map_err(|e| anyhow!("Failed to convert base_mint: {:?}", e))?,
|
||||
);
|
||||
let quote_mint = Pubkey::new_from_array(
|
||||
data[67..99]
|
||||
.try_into()
|
||||
.map_err(|e| anyhow!("Failed to convert quote_mint: {:?}", e))?,
|
||||
);
|
||||
let lp_mint = Pubkey::new_from_array(
|
||||
data[99..131]
|
||||
.try_into()
|
||||
.map_err(|e| anyhow!("Failed to convert lp_mint: {:?}", e))?,
|
||||
);
|
||||
let pool_base_token_account = Pubkey::new_from_array(
|
||||
data[131..163]
|
||||
.try_into()
|
||||
.map_err(|e| anyhow!("Failed to convert pool_base_token_account: {:?}", e))?,
|
||||
);
|
||||
let pool_quote_token_account = Pubkey::new_from_array(
|
||||
data[163..195]
|
||||
.try_into()
|
||||
.map_err(|e| anyhow!("Failed to convert pool_quote_token_account: {:?}", e))?,
|
||||
);
|
||||
|
||||
let lp_supply = u64::from_le_bytes([
|
||||
data[195], data[196], data[197], data[198], data[199], data[200], data[201], data[202],
|
||||
]);
|
||||
|
||||
Ok(Self {
|
||||
pool_bump,
|
||||
index,
|
||||
creator,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
lp_mint,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
lp_supply,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn fetch(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let account = rpc.get_account(pool_address).await?;
|
||||
|
||||
if account.owner != accounts::AMM_PROGRAM {
|
||||
return Err(anyhow!("Account is not owned by PumpSwap program"));
|
||||
}
|
||||
|
||||
Self::from_bytes(&account.data)
|
||||
}
|
||||
|
||||
pub async fn find_by_mint(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<(Pubkey, Self), anyhow::Error> {
|
||||
// 使用getProgramAccounts查找给定mint的池子
|
||||
let filters = vec![
|
||||
// solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
|
||||
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
|
||||
solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &mint.to_bytes()),
|
||||
),
|
||||
];
|
||||
|
||||
let config = solana_rpc_client_api::config::RpcProgramAccountsConfig {
|
||||
filters: Some(filters),
|
||||
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
data_slice: None,
|
||||
commitment: None,
|
||||
min_context_slot: None,
|
||||
},
|
||||
with_context: None,
|
||||
sort_results: None,
|
||||
};
|
||||
|
||||
let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
|
||||
let accounts = rpc
|
||||
.get_program_accounts_with_config(&program_id, config)
|
||||
.await?;
|
||||
|
||||
if accounts.is_empty() {
|
||||
return Err(anyhow!("No pool found for mint {}", mint));
|
||||
}
|
||||
|
||||
let mut pools: Vec<_> = accounts
|
||||
.into_iter()
|
||||
.filter_map(|(addr, acc)| Self::from_bytes(&acc.data).map(|pool| (addr, pool)).ok())
|
||||
.collect();
|
||||
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
||||
|
||||
let (address, pool) = pools[0].clone();
|
||||
Ok((address, pool))
|
||||
}
|
||||
|
||||
pub async fn get_token_balances(
|
||||
&self,
|
||||
rpc: &SolanaRpcClient,
|
||||
) -> Result<(u64, u64), anyhow::Error> {
|
||||
let base_balance = rpc
|
||||
.get_token_account_balance(&self.pool_base_token_account)
|
||||
.await?;
|
||||
let quote_balance = rpc
|
||||
.get_token_account_balance(&self.pool_quote_token_account)
|
||||
.await?;
|
||||
|
||||
let base_amount = base_balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
|
||||
let quote_amount = quote_balance
|
||||
.amount
|
||||
.parse::<u64>()
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
|
||||
Ok((base_amount, quote_amount))
|
||||
}
|
||||
|
||||
pub async fn calculate_buy_amount(
|
||||
&self,
|
||||
rpc: &SolanaRpcClient,
|
||||
sol_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let (base_amount, quote_amount) = self.get_token_balances(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(
|
||||
&self,
|
||||
rpc: &SolanaRpcClient,
|
||||
token_amount: u64,
|
||||
) -> Result<u64, anyhow::Error> {
|
||||
let (base_amount, quote_amount) = self.get_token_balances(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)
|
||||
}
|
||||
}
|
||||
Executable
+266
@@ -0,0 +1,266 @@
|
||||
use anyhow::anyhow;
|
||||
use solana_hash::Hash;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::common::{PriorityFee, SolanaRpcClient};
|
||||
use crate::swqos::SwqosClient;
|
||||
use crate::trading::pumpswap::common::get_token_balance;
|
||||
use crate::trading::{core::params::PumpSwapParams, factory::Protocol, SellParams, TradeFactory};
|
||||
|
||||
// Sell tokens to a Pumpswap pool
|
||||
pub async fn sell(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount_token: Option<u64>,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
// 可选(必须全部传)
|
||||
pool: Option<Pubkey>,
|
||||
pool_base_token_account: Option<Pubkey>,
|
||||
pool_quote_token_account: Option<Pubkey>,
|
||||
user_base_token_account: Option<Pubkey>,
|
||||
user_quote_token_account: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
|
||||
// 创建PumpFun协议参数
|
||||
let protocol_params = Box::new(PumpSwapParams {
|
||||
pool,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
auto_handle_wsol: true,
|
||||
});
|
||||
// 创建卖出参数
|
||||
let sell_params = SellParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer.clone(),
|
||||
mint,
|
||||
creator,
|
||||
amount_token: amount_token,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee.clone(),
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
protocol_params,
|
||||
};
|
||||
// 执行卖出交易
|
||||
executor.sell(sell_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Sell tokens by percentage
|
||||
pub async fn sell_by_percent(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
percent: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
// 可选(必须全部传)
|
||||
pool: Option<Pubkey>,
|
||||
pool_base_token_account: Option<Pubkey>,
|
||||
pool_quote_token_account: Option<Pubkey>,
|
||||
user_base_token_account: Option<Pubkey>,
|
||||
user_quote_token_account: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if percent == 0 || percent > 100 {
|
||||
return Err(anyhow!("Percentage must be between 1 and 100"));
|
||||
}
|
||||
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
|
||||
let amount = balance_u64 * percent / 100;
|
||||
sell(
|
||||
rpc,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
Some(amount),
|
||||
slippage_basis_points,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
pool,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sell tokens by amount
|
||||
pub async fn sell_by_amount(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
// 可选(必须全部传)
|
||||
pool: Option<Pubkey>,
|
||||
pool_base_token_account: Option<Pubkey>,
|
||||
pool_quote_token_account: Option<Pubkey>,
|
||||
user_base_token_account: Option<Pubkey>,
|
||||
user_quote_token_account: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
sell(
|
||||
rpc,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
Some(amount),
|
||||
slippage_basis_points,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
pool,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Sell tokens using a MEV service
|
||||
pub async fn sell_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount_token: Option<u64>,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
// 可选(必须全部传)
|
||||
pool: Option<Pubkey>,
|
||||
pool_base_token_account: Option<Pubkey>,
|
||||
pool_quote_token_account: Option<Pubkey>,
|
||||
user_base_token_account: Option<Pubkey>,
|
||||
user_quote_token_account: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
|
||||
// 创建PumpFun协议参数
|
||||
let protocol_params = Box::new(PumpSwapParams {
|
||||
pool,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
auto_handle_wsol: true,
|
||||
});
|
||||
// 创建卖出参数
|
||||
let sell_params = SellParams {
|
||||
rpc: Some(rpc.clone()),
|
||||
payer: payer.clone(),
|
||||
mint,
|
||||
creator,
|
||||
amount_token: amount_token,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: priority_fee.clone(),
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
protocol_params,
|
||||
};
|
||||
let sell_with_tip_params = sell_params.with_tip(swqos_clients);
|
||||
// 执行卖出交易
|
||||
executor.sell_with_tip(sell_with_tip_params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Sell tokens by percentage using a MEV service
|
||||
pub async fn sell_by_percent_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
percent: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
// 可选(必须全部传)
|
||||
pool: Option<Pubkey>,
|
||||
pool_base_token_account: Option<Pubkey>,
|
||||
pool_quote_token_account: Option<Pubkey>,
|
||||
user_base_token_account: Option<Pubkey>,
|
||||
user_quote_token_account: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if percent == 0 || percent > 100 {
|
||||
return Err(anyhow!("Percentage must be between 1 and 100"));
|
||||
}
|
||||
|
||||
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
|
||||
let amount = balance_u64 * percent / 100;
|
||||
sell_with_tip(
|
||||
rpc,
|
||||
swqos_clients,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
Some(amount),
|
||||
slippage_basis_points,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
pool,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Sell tokens by amount using a MEV service
|
||||
pub async fn sell_by_amount_with_tip(
|
||||
rpc: Arc<SolanaRpcClient>,
|
||||
swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
payer: Arc<Keypair>,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
amount: u64,
|
||||
slippage_basis_points: Option<u64>,
|
||||
priority_fee: PriorityFee,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
// 可选(必须全部传)
|
||||
pool: Option<Pubkey>,
|
||||
pool_base_token_account: Option<Pubkey>,
|
||||
pool_quote_token_account: Option<Pubkey>,
|
||||
user_base_token_account: Option<Pubkey>,
|
||||
user_quote_token_account: Option<Pubkey>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
sell_with_tip(
|
||||
rpc,
|
||||
swqos_clients,
|
||||
payer,
|
||||
mint,
|
||||
creator,
|
||||
Some(amount),
|
||||
slippage_basis_points,
|
||||
priority_fee,
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
pool,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
user_base_token_account,
|
||||
user_quote_token_account,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Reference in New Issue
Block a user