Sync supported DEX IDLs from bitquery/solana-idl-lib, upgrade protocol builders, and keep trade submission hot paths free of RPC query calls. Include exact-output coverage, token-account setup fixes for WSOL and stable pairs, and low-latency transaction build improvements.
198 lines
7.5 KiB
Rust
198 lines
7.5 KiB
Rust
use crate::{
|
|
common::SolanaRpcClient,
|
|
instruction::utils::bonk_types::{pool_state_decode, PoolState},
|
|
};
|
|
use anyhow::anyhow;
|
|
use solana_sdk::pubkey::Pubkey;
|
|
|
|
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
|
|
pub mod seeds {
|
|
pub const POOL_SEED: &[u8] = b"pool";
|
|
pub const POOL_VAULT_SEED: &[u8] = b"pool_vault";
|
|
}
|
|
|
|
/// Constants related to program accounts and authorities
|
|
pub mod accounts {
|
|
use solana_sdk::{pubkey, pubkey::Pubkey};
|
|
|
|
pub const AUTHORITY: Pubkey = pubkey!("WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh");
|
|
pub const GLOBAL_CONFIG: Pubkey = pubkey!("6s1xP3hpbAfFoNtUNF8mfHsjr2Bd97JxFJRWLbL6aHuX");
|
|
pub const USD1_GLOBAL_CONFIG: Pubkey = pubkey!("EPiZbnrThjyLnoQ6QQzkxeFqyL5uyg9RzNHHAudUPxBz");
|
|
pub const EVENT_AUTHORITY: Pubkey = pubkey!("2DPAtwB8L12vrMRExbLuyGnC7n2J5LNoZQSejeQGpwkr");
|
|
pub const BONK: Pubkey = pubkey!("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj");
|
|
|
|
pub const PLATFORM_FEE_RATE: u128 = 100; // 1%
|
|
pub const PROTOCOL_FEE_RATE: u128 = 25; // 0.25%
|
|
pub const SHARE_FEE_RATE: u128 = 0; // 0%
|
|
|
|
// META
|
|
pub const AUTHORITY_META: solana_sdk::instruction::AccountMeta =
|
|
solana_sdk::instruction::AccountMeta {
|
|
pubkey: AUTHORITY,
|
|
is_signer: false,
|
|
is_writable: false,
|
|
};
|
|
pub const GLOBAL_CONFIG_META: solana_sdk::instruction::AccountMeta =
|
|
solana_sdk::instruction::AccountMeta {
|
|
pubkey: GLOBAL_CONFIG,
|
|
is_signer: false,
|
|
is_writable: false,
|
|
};
|
|
|
|
pub const USD1_GLOBAL_CONFIG_META: solana_sdk::instruction::AccountMeta =
|
|
solana_sdk::instruction::AccountMeta {
|
|
pubkey: USD1_GLOBAL_CONFIG,
|
|
is_signer: false,
|
|
is_writable: false,
|
|
};
|
|
|
|
pub const EVENT_AUTHORITY_META: solana_sdk::instruction::AccountMeta =
|
|
solana_sdk::instruction::AccountMeta {
|
|
pubkey: EVENT_AUTHORITY,
|
|
is_signer: false,
|
|
is_writable: false,
|
|
};
|
|
pub const BONK_META: solana_sdk::instruction::AccountMeta =
|
|
solana_sdk::instruction::AccountMeta { pubkey: BONK, is_signer: false, is_writable: false };
|
|
}
|
|
|
|
pub const BUY_EXECT_IN_DISCRIMINATOR: [u8; 8] = [250, 234, 13, 123, 213, 156, 19, 236];
|
|
pub const BUY_EXECT_OUT_DISCRIMINATOR: [u8; 8] = [24, 211, 116, 40, 105, 3, 153, 56];
|
|
pub const SELL_EXECT_IN_DISCRIMINATOR: [u8; 8] = [149, 39, 222, 155, 211, 124, 152, 26];
|
|
pub const SELL_EXECT_OUT_DISCRIMINATOR: [u8; 8] = [95, 200, 71, 34, 8, 9, 11, 166];
|
|
|
|
pub async fn fetch_pool_state(
|
|
rpc: &SolanaRpcClient,
|
|
pool_address: &Pubkey,
|
|
) -> Result<PoolState, 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"));
|
|
}
|
|
let pool_state = pool_state_decode(&account.data[8..])
|
|
.ok_or_else(|| anyhow!("Failed to decode pool state"))?;
|
|
Ok(pool_state)
|
|
}
|
|
|
|
pub fn get_amount_in_net(
|
|
amount_in: u64,
|
|
protocol_fee_rate: u128,
|
|
platform_fee_rate: u128,
|
|
share_fee_rate: u128,
|
|
) -> u64 {
|
|
let amount_in_u128 = amount_in as u128;
|
|
let protocol_fee = (amount_in_u128 * protocol_fee_rate / 10000) as u128;
|
|
let platform_fee = (amount_in_u128 * platform_fee_rate / 10000) as u128;
|
|
let share_fee = (amount_in_u128 * share_fee_rate / 10000) as u128;
|
|
amount_in_u128
|
|
.checked_sub(protocol_fee)
|
|
.unwrap()
|
|
.checked_sub(platform_fee)
|
|
.unwrap()
|
|
.checked_sub(share_fee)
|
|
.unwrap() as u64
|
|
}
|
|
|
|
pub fn get_amount_in(
|
|
amount_out: u64,
|
|
protocol_fee_rate: u128,
|
|
platform_fee_rate: u128,
|
|
share_fee_rate: u128,
|
|
virtual_base: u128,
|
|
virtual_quote: u128,
|
|
real_base: u128,
|
|
real_quote: u128,
|
|
slippage_basis_points: u128,
|
|
) -> u64 {
|
|
let amount_out_u128 = amount_out as u128;
|
|
|
|
// Consider slippage, actual required output amount is higher
|
|
let amount_out_with_slippage = amount_out_u128 * 10000 / (10000 - slippage_basis_points);
|
|
|
|
let input_reserve = virtual_quote.checked_add(real_quote).unwrap();
|
|
let output_reserve = virtual_base.checked_sub(real_base).unwrap();
|
|
|
|
// Reverse calculate using AMM formula: amount_in_net = (amount_out * input_reserve) / (output_reserve - amount_out)
|
|
let numerator = amount_out_with_slippage.checked_mul(input_reserve).unwrap();
|
|
let denominator = output_reserve.checked_sub(amount_out_with_slippage).unwrap();
|
|
let amount_in_net = numerator.checked_div(denominator).unwrap();
|
|
|
|
// Calculate total fee rate
|
|
let total_fee_rate = protocol_fee_rate + platform_fee_rate + share_fee_rate;
|
|
|
|
let amount_in = amount_in_net * 10000 / (10000 - total_fee_rate);
|
|
|
|
amount_in as u64
|
|
}
|
|
|
|
pub fn get_amount_out(
|
|
amount_in: u64,
|
|
protocol_fee_rate: u128,
|
|
platform_fee_rate: u128,
|
|
share_fee_rate: u128,
|
|
virtual_base: u128,
|
|
virtual_quote: u128,
|
|
real_base: u128,
|
|
real_quote: u128,
|
|
slippage_basis_points: u128,
|
|
) -> u64 {
|
|
let amount_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).unwrap();
|
|
let output_reserve = virtual_base.checked_sub(real_base).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> {
|
|
crate::common::fast_fn::get_cached_pda(
|
|
crate::common::fast_fn::PdaCacheKey::BonkPool(*base_mint, *quote_mint),
|
|
|| {
|
|
let seeds: &[&[u8]; 3] = &[seeds::POOL_SEED, base_mint.as_ref(), quote_mint.as_ref()];
|
|
let program_id: &Pubkey = &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> {
|
|
crate::common::fast_fn::get_cached_pda(
|
|
crate::common::fast_fn::PdaCacheKey::BonkVault(*pool_state, *mint),
|
|
|| {
|
|
let seeds: &[&[u8]; 3] = &[seeds::POOL_VAULT_SEED, pool_state.as_ref(), mint.as_ref()];
|
|
let program_id: &Pubkey = &accounts::BONK;
|
|
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
|
pda.map(|pubkey| pubkey.0)
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn get_platform_associated_account(platform_config: &Pubkey) -> Option<Pubkey> {
|
|
let seeds: &[&[u8]; 2] =
|
|
&[platform_config.as_ref(), crate::constants::WSOL_TOKEN_ACCOUNT.as_ref()];
|
|
let program_id: &Pubkey = &accounts::BONK;
|
|
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
|
pda.map(|pubkey| pubkey.0)
|
|
}
|
|
|
|
pub fn get_creator_associated_account(creator: &Pubkey) -> Option<Pubkey> {
|
|
let seeds: &[&[u8]; 2] = &[creator.as_ref(), crate::constants::WSOL_TOKEN_ACCOUNT.as_ref()];
|
|
let program_id: &Pubkey = &accounts::BONK;
|
|
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
|
|
pda.map(|pubkey| pubkey.0)
|
|
}
|