Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ed2aef931 | ||
|
|
0cf0064e80 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sol-trade-sdk"
|
name = "sol-trade-sdk"
|
||||||
version = "4.0.10"
|
version = "4.0.11"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [
|
authors = [
|
||||||
"William <byteblock6@gmail.com>",
|
"William <byteblock6@gmail.com>",
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ let trade_config = TradeConfig::builder(rpc_url, swqos_configs, commitment)
|
|||||||
// .check_min_tip(false) // default: false - filter SWQOS below min tip
|
// .check_min_tip(false) // default: false - filter SWQOS below min tip
|
||||||
// .swqos_cores_from_end(false) // default: false - bind SWQOS to last N CPU cores
|
// .swqos_cores_from_end(false) // default: false - bind SWQOS to last N CPU cores
|
||||||
// .mev_protection(false) // default: false - MEV (Astralane QUIC :9000 or HTTP mev-protect / BlockRazor)
|
// .mev_protection(false) // default: false - MEV (Astralane QUIC :9000 or HTTP mev-protect / BlockRazor)
|
||||||
// .use_pumpfun_v2(false) // default: false - V1 (18 accounts); set true for V2 (27 accounts, quote_mint) when PumpFun deploys V2
|
// .use_pumpfun_v2(true) // PumpFun V2 (27 accounts, quote_mint); required for USDC-paired PumpFun coins
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Create TradingClient
|
// Create TradingClient
|
||||||
@@ -376,7 +376,7 @@ PumpFun has two instruction sets for bonding-curve trading:
|
|||||||
|
|
||||||
**How to enable V2:**
|
**How to enable V2:**
|
||||||
|
|
||||||
**Method 1 — Global runtime flag** (recommended when PumpFun officially deploys V2 on mainnet):
|
**Method 1 — Global runtime flag** (recommended for `TradingClient`; required for USDC-paired coins):
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let trade_config = TradeConfig::builder(rpc_url, swqos_configs, commitment)
|
let trade_config = TradeConfig::builder(rpc_url, swqos_configs, commitment)
|
||||||
@@ -384,7 +384,9 @@ let trade_config = TradeConfig::builder(rpc_url, swqos_configs, commitment)
|
|||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
|
|
||||||
**Method 2 — Per-trade via `quote_mint`** (for USDC-paired coins or mixed V1/V2 scenarios):
|
**Method 2 — Set the quote mint on `PumpFunParams`**:
|
||||||
|
|
||||||
|
When using the high-level `TradingClient`, keep `.use_pumpfun_v2(true)` enabled in `TradeConfig` and set `quote_mint` on the PumpFun params to select the pair:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
|
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
|
||||||
@@ -399,6 +401,8 @@ let params = PumpFunParams::from_trade(/* ... */)
|
|||||||
.with_quote_mint(USDC_TOKEN_ACCOUNT);
|
.with_quote_mint(USDC_TOKEN_ACCOUNT);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`with_quote_mint(...)` also marks the params as V2-capable for lower-level instruction builders, but the high-level `TradingClient` uses the client-level `use_pumpfun_v2` runtime flag when choosing V1 vs V2.
|
||||||
|
|
||||||
> **Note**: V2 transactions with ATA creation + durable nonce may exceed `PACKET_DATA_SIZE`. Enable an Address Lookup Table (`address_lookup_table_account`) when using V2.
|
> **Note**: V2 transactions with ATA creation + durable nonce may exceed `PACKET_DATA_SIZE`. Enable an Address Lookup Table (`address_lookup_table_account`) when using V2.
|
||||||
|
|
||||||
#### PumpSwap: coin_creator_vault from events (no RPC)
|
#### PumpSwap: coin_creator_vault from events (no RPC)
|
||||||
|
|||||||
+12
-1
@@ -152,6 +152,7 @@ let trade_config = TradeConfig::builder(rpc_url, swqos_configs, commitment)
|
|||||||
// .check_min_tip(false) // 默认: false - 过滤低于最低小费的 SWQOS
|
// .check_min_tip(false) // 默认: false - 过滤低于最低小费的 SWQOS
|
||||||
// .swqos_cores_from_end(false) // 默认: false - 将 SWQOS 绑定到末尾 N 个 CPU 核心
|
// .swqos_cores_from_end(false) // 默认: false - 将 SWQOS 绑定到末尾 N 个 CPU 核心
|
||||||
// .mev_protection(false) // 默认: false - MEV(Astralane QUIC :9000 或 HTTP mev-protect / BlockRazor)
|
// .mev_protection(false) // 默认: false - MEV(Astralane QUIC :9000 或 HTTP mev-protect / BlockRazor)
|
||||||
|
// .use_pumpfun_v2(true) // PumpFun V2(27 个账户,quote_mint);USDC 配对 PumpFun 币必须开启
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 创建 TradingClient
|
// 创建 TradingClient
|
||||||
@@ -372,7 +373,15 @@ Pump.fun 已升级 Bonding Curve 合约,推出**统一化 v2 指令**,通过
|
|||||||
|
|
||||||
**使用方式:**
|
**使用方式:**
|
||||||
|
|
||||||
设置 `PumpFunParams` 的 `quote_mint` 即可,SDK 会自动切换到 v2 discriminator 和新账户布局:
|
高层 `TradingClient` 需要先在 `TradeConfig` 开启 PumpFun V2;USDC 配对币必须开启:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let trade_config = TradeConfig::builder(rpc_url, swqos_configs, commitment)
|
||||||
|
.use_pumpfun_v2(true)
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在 `PumpFunParams` 设置 `quote_mint` 来选择 SOL/USDC 配对:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
|
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
|
||||||
@@ -391,6 +400,8 @@ client.buy(buy_params).await?;
|
|||||||
client.sell(sell_params).await?;
|
client.sell(sell_params).await?;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`with_quote_mint(...)` 会把参数标记为可使用 V2,底层 instruction builder 可据此走 V2;但高层 `TradingClient` 选择 V1/V2 时使用的是 client 级别的 `use_pumpfun_v2` 运行时开关。
|
||||||
|
|
||||||
| quote_mint | use_v2_ix | 实际使用的指令 | 说明 |
|
| quote_mint | use_v2_ix | 实际使用的指令 | 说明 |
|
||||||
|-----------|-------------|---------|------|
|
|-----------|-------------|---------|------|
|
||||||
| 未设置(默认) | `false` | 旧版 `buy`/`sell`/`buy_exact_sol_in` | 向后兼容,仅 SOL |
|
| 未设置(默认) | `false` | 旧版 `buy`/`sell`/`buy_exact_sol_in` | 向后兼容,仅 SOL |
|
||||||
|
|||||||
+64
-25
@@ -4,7 +4,7 @@ use solana_sdk::{
|
|||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::{hash::Hash, sync::Arc};
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
spl_associated_token_account::get_associated_token_address_with_program_id,
|
spl_associated_token_account::get_associated_token_address_with_program_id,
|
||||||
@@ -21,6 +21,22 @@ const MAX_PDA_CACHE_SIZE: usize = 100_000;
|
|||||||
const MAX_ATA_CACHE_SIZE: usize = 100_000;
|
const MAX_ATA_CACHE_SIZE: usize = 100_000;
|
||||||
const MAX_INSTRUCTION_CACHE_SIZE: usize = 100_000;
|
const MAX_INSTRUCTION_CACHE_SIZE: usize = 100_000;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn prune_cache<K, V>(cache: &DashMap<K, V>, max_size: usize)
|
||||||
|
where
|
||||||
|
K: Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
let len = cache.len();
|
||||||
|
if len <= max_size {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let remove_count = (len - max_size).max(max_size / 16).min(len);
|
||||||
|
let keys: Vec<K> = cache.iter().take(remove_count).map(|entry| entry.key().clone()).collect();
|
||||||
|
for key in keys {
|
||||||
|
cache.remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------- Instruction Cache ---------------------
|
// --------------------- Instruction Cache ---------------------
|
||||||
|
|
||||||
/// Instruction cache key for uniquely identifying instruction types and parameters
|
/// Instruction cache key for uniquely identifying instruction types and parameters
|
||||||
@@ -66,7 +82,10 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Lock-free cache lookup with entry API
|
// Lock-free cache lookup with entry API
|
||||||
INSTRUCTION_CACHE.entry(cache_key).or_insert_with(|| Arc::new(compute_fn())).clone()
|
let instructions =
|
||||||
|
INSTRUCTION_CACHE.entry(cache_key).or_insert_with(|| Arc::new(compute_fn())).clone();
|
||||||
|
prune_cache(&INSTRUCTION_CACHE, MAX_INSTRUCTION_CACHE_SIZE);
|
||||||
|
instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------- Associated Token Account ---------------------
|
// --------------------- Associated Token Account ---------------------
|
||||||
@@ -106,8 +125,26 @@ pub fn _create_associated_token_account_idempotent_fast(
|
|||||||
use_seed,
|
use_seed,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only use seed if the mint address is not wSOL or SOL
|
let build_standard_create = || {
|
||||||
// 🔧 修复:Token-2022 也支持 seed 方式(白名单方式更安全)
|
let associated_token_address =
|
||||||
|
get_associated_token_address_with_program_id_fast(owner, mint, token_program);
|
||||||
|
vec![Instruction {
|
||||||
|
program_id: crate::constants::ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
|
accounts: vec![
|
||||||
|
AccountMeta::new(*payer, true), // Payer (signer, writable)
|
||||||
|
AccountMeta::new(associated_token_address, false), // ATA address (writable, non-signer)
|
||||||
|
AccountMeta::new_readonly(*owner, false), // Token account owner (readonly, non-signer)
|
||||||
|
AccountMeta::new_readonly(*mint, false), // Token mint address (readonly, non-signer)
|
||||||
|
crate::constants::SYSTEM_PROGRAM_META,
|
||||||
|
AccountMeta::new_readonly(*token_program, false), // Token program (readonly, non-signer)
|
||||||
|
],
|
||||||
|
data: vec![1],
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only use seed if the mint address is not wSOL or SOL.
|
||||||
|
// Seed accounts are deterministic token accounts, not ATAs; callers must ensure they are not
|
||||||
|
// recreating an existing seeded account. Fall back to idempotent ATA if seed assembly fails.
|
||||||
let arc_instructions = if use_seed
|
let arc_instructions = if use_seed
|
||||||
&& !mint.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
|
&& !mint.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
|
||||||
&& !mint.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
|
&& !mint.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
|
||||||
@@ -117,29 +154,18 @@ pub fn _create_associated_token_account_idempotent_fast(
|
|||||||
// Use cache to get instruction
|
// Use cache to get instruction
|
||||||
get_cached_instructions(cache_key, || {
|
get_cached_instructions(cache_key, || {
|
||||||
super::seed::create_associated_token_account_use_seed(payer, owner, mint, token_program)
|
super::seed::create_associated_token_account_use_seed(payer, owner, mint, token_program)
|
||||||
.unwrap()
|
.unwrap_or_else(|err| {
|
||||||
|
tracing::warn!(
|
||||||
|
"seed token account setup failed for mint {}: {}; fallback to idempotent ATA",
|
||||||
|
mint,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
build_standard_create()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Use cache to get instruction
|
// Use cache to get instruction
|
||||||
get_cached_instructions(cache_key, || {
|
get_cached_instructions(cache_key, build_standard_create)
|
||||||
// Get Associated Token Address using cache
|
|
||||||
let associated_token_address =
|
|
||||||
get_associated_token_address_with_program_id_fast(owner, mint, token_program);
|
|
||||||
// Create Associated Token Account instruction
|
|
||||||
// Reference implementation of spl_associated_token_account::instruction::create_associated_token_account
|
|
||||||
vec![Instruction {
|
|
||||||
program_id: crate::constants::ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
||||||
accounts: vec![
|
|
||||||
AccountMeta::new(*payer, true), // Payer (signer, writable)
|
|
||||||
AccountMeta::new(associated_token_address, false), // ATA address (writable, non-signer)
|
|
||||||
AccountMeta::new_readonly(*owner, false), // Token account owner (readonly, non-signer)
|
|
||||||
AccountMeta::new_readonly(*mint, false), // Token mint address (readonly, non-signer)
|
|
||||||
crate::constants::SYSTEM_PROGRAM_META,
|
|
||||||
AccountMeta::new_readonly(*token_program, false), // Token program (readonly, non-signer)
|
|
||||||
],
|
|
||||||
data: vec![1],
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🚀 性能优化:尝试零开销解包 Arc,如果引用计数=1则直接移出,否则克隆
|
// 🚀 性能优化:尝试零开销解包 Arc,如果引用计数=1则直接移出,否则克隆
|
||||||
@@ -183,6 +209,7 @@ where
|
|||||||
|
|
||||||
if let Some(pda) = pda_result {
|
if let Some(pda) = pda_result {
|
||||||
PDA_CACHE.insert(cache_key, pda);
|
PDA_CACHE.insert(cache_key, pda);
|
||||||
|
prune_cache(&PDA_CACHE, MAX_PDA_CACHE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
pda_result
|
pda_result
|
||||||
@@ -265,7 +292,18 @@ fn _get_associated_token_address_with_program_id_fast(
|
|||||||
token_mint_address,
|
token_mint_address,
|
||||||
token_program_id,
|
token_program_id,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap_or_else(|err| {
|
||||||
|
tracing::warn!(
|
||||||
|
"seed token account address failed for mint {}: {}; fallback to ATA",
|
||||||
|
token_mint_address,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
get_associated_token_address_with_program_id(
|
||||||
|
wallet_address,
|
||||||
|
token_mint_address,
|
||||||
|
token_program_id,
|
||||||
|
)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
get_associated_token_address_with_program_id(
|
get_associated_token_address_with_program_id(
|
||||||
wallet_address,
|
wallet_address,
|
||||||
@@ -276,6 +314,7 @@ fn _get_associated_token_address_with_program_id_fast(
|
|||||||
|
|
||||||
// Store computation result in cache (lock-free)
|
// Store computation result in cache (lock-free)
|
||||||
ATA_CACHE.insert(cache_key, ata);
|
ATA_CACHE.insert(cache_key, ata);
|
||||||
|
prune_cache(&ATA_CACHE, MAX_ATA_CACHE_SIZE);
|
||||||
|
|
||||||
ata
|
ata
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-34
@@ -1,5 +1,4 @@
|
|||||||
use crate::common::SolanaRpcClient;
|
use crate::common::SolanaRpcClient;
|
||||||
use anyhow::anyhow;
|
|
||||||
use fnv::FnvHasher;
|
use fnv::FnvHasher;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use solana_sdk::{instruction::Instruction, pubkey::Pubkey};
|
use solana_sdk::{instruction::Instruction, pubkey::Pubkey};
|
||||||
@@ -50,6 +49,26 @@ async fn fetch_rent_for_token_account(
|
|||||||
Ok(client.get_minimum_balance_for_rent_exemption(165).await?)
|
Ok(client.get_minimum_balance_for_rent_exemption(165).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn derive_seed_from_mint(mint: &Pubkey) -> String {
|
||||||
|
// Keep the legacy 8-hex seed stable. Changing this derivation changes the token account
|
||||||
|
// address and can strand balances created by earlier buys.
|
||||||
|
let mut hasher = FnvHasher::default();
|
||||||
|
hasher.write(mint.as_ref());
|
||||||
|
let hash = hasher.finish();
|
||||||
|
let v = (hash & 0xFFFF_FFFF) as u32;
|
||||||
|
let mut seed = String::with_capacity(8);
|
||||||
|
for i in 0..8 {
|
||||||
|
let nibble = ((v >> (28 - i * 4)) & 0xF) as u8;
|
||||||
|
let byte = match nibble {
|
||||||
|
0..=9 => b'0' + nibble,
|
||||||
|
_ => b'a' + (nibble - 10),
|
||||||
|
};
|
||||||
|
seed.push(byte as char);
|
||||||
|
}
|
||||||
|
seed
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_associated_token_account_use_seed(
|
pub fn create_associated_token_account_use_seed(
|
||||||
payer: &Pubkey,
|
payer: &Pubkey,
|
||||||
owner: &Pubkey,
|
owner: &Pubkey,
|
||||||
@@ -63,33 +82,23 @@ pub fn create_associated_token_account_use_seed(
|
|||||||
let rent = if is_2022_token {
|
let rent = if is_2022_token {
|
||||||
let v = SPL_TOKEN_2022_RENT.load(Ordering::Relaxed);
|
let v = SPL_TOKEN_2022_RENT.load(Ordering::Relaxed);
|
||||||
if v == u64::MAX {
|
if v == u64::MAX {
|
||||||
return Err(anyhow!("Rent not initialized"));
|
DEFAULT_TOKEN_ACCOUNT_RENT
|
||||||
|
} else {
|
||||||
|
v
|
||||||
}
|
}
|
||||||
v
|
|
||||||
} else {
|
} else {
|
||||||
let v = SPL_TOKEN_RENT.load(Ordering::Relaxed);
|
let v = SPL_TOKEN_RENT.load(Ordering::Relaxed);
|
||||||
if v == u64::MAX {
|
if v == u64::MAX {
|
||||||
return Err(anyhow!("Rent not initialized"));
|
DEFAULT_TOKEN_ACCOUNT_RENT
|
||||||
|
} else {
|
||||||
|
v
|
||||||
}
|
}
|
||||||
v
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buf = [0u8; 8];
|
let seed = derive_seed_from_mint(mint);
|
||||||
let mut hasher = FnvHasher::default();
|
|
||||||
hasher.write(mint.as_ref());
|
|
||||||
let hash = hasher.finish();
|
|
||||||
let v = (hash & 0xFFFF_FFFF) as u32;
|
|
||||||
for i in 0..8 {
|
|
||||||
let nibble = ((v >> (28 - i * 4)) & 0xF) as u8;
|
|
||||||
buf[i] = match nibble {
|
|
||||||
0..=9 => b'0' + nibble,
|
|
||||||
_ => b'a' + (nibble - 10),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
|
|
||||||
// 🔧 修复:使用传入的 token_program 生成地址(支持 Token 和 Token-2022)
|
// 🔧 修复:使用传入的 token_program 生成地址(支持 Token 和 Token-2022)
|
||||||
// 买入和卖出只要都使用事件中的 token_program,地址自然一致
|
// 买入和卖出只要都使用事件中的 token_program,地址自然一致
|
||||||
let ata_like = Pubkey::create_with_seed(payer, seed, token_program)?;
|
let ata_like = Pubkey::create_with_seed(payer, &seed, token_program)?;
|
||||||
|
|
||||||
let len = 165;
|
let len = 165;
|
||||||
// 🔧 修复:create_account_with_seed 的第3个参数必须是 payer(与第92行生成地址时使用的 base 一致)
|
// 🔧 修复:create_account_with_seed 的第3个参数必须是 payer(与第92行生成地址时使用的 base 一致)
|
||||||
@@ -98,7 +107,7 @@ pub fn create_associated_token_account_use_seed(
|
|||||||
payer,
|
payer,
|
||||||
&ata_like,
|
&ata_like,
|
||||||
payer,
|
payer,
|
||||||
seed,
|
&seed,
|
||||||
rent,
|
rent,
|
||||||
len,
|
len,
|
||||||
token_program,
|
token_program,
|
||||||
@@ -118,21 +127,9 @@ pub fn get_associated_token_address_with_program_id_use_seed(
|
|||||||
token_mint_address: &Pubkey,
|
token_mint_address: &Pubkey,
|
||||||
token_program_id: &Pubkey,
|
token_program_id: &Pubkey,
|
||||||
) -> Result<Pubkey, anyhow::Error> {
|
) -> Result<Pubkey, anyhow::Error> {
|
||||||
let mut buf = [0u8; 8];
|
let seed = derive_seed_from_mint(token_mint_address);
|
||||||
let mut hasher = FnvHasher::default();
|
|
||||||
hasher.write(token_mint_address.as_ref());
|
|
||||||
let hash = hasher.finish();
|
|
||||||
let v = (hash & 0xFFFF_FFFF) as u32;
|
|
||||||
for i in 0..8 {
|
|
||||||
let nibble = ((v >> (28 - i * 4)) & 0xF) as u8;
|
|
||||||
buf[i] = match nibble {
|
|
||||||
0..=9 => b'0' + nibble,
|
|
||||||
_ => b'a' + (nibble - 10),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
|
|
||||||
// 🔧 修复:使用传入的 token_program_id 生成地址(支持 Token 和 Token-2022)
|
// 🔧 修复:使用传入的 token_program_id 生成地址(支持 Token 和 Token-2022)
|
||||||
// 买入和卖出只要都使用事件中的 token_program_id,地址自然一致
|
// 买入和卖出只要都使用事件中的 token_program_id,地址自然一致
|
||||||
let ata_like = Pubkey::create_with_seed(wallet_address, seed, token_program_id)?;
|
let ata_like = Pubkey::create_with_seed(wallet_address, &seed, token_program_id)?;
|
||||||
Ok(ata_like)
|
Ok(ata_like)
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-23
@@ -30,24 +30,20 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use solana_sdk::instruction::AccountMeta;
|
use solana_sdk::{
|
||||||
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer};
|
instruction::{AccountMeta, Instruction},
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signer::Signer,
|
||||||
|
};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_pump_suffix_mint(mint: &Pubkey) -> bool {
|
fn effective_pump_mint_token_program(protocol_params: &PumpFunParams) -> Pubkey {
|
||||||
mint.to_string().ends_with("pump")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn effective_pump_mint_token_program(mint: &Pubkey, protocol_params: &PumpFunParams) -> Pubkey {
|
|
||||||
let tp = protocol_params.token_program;
|
let tp = protocol_params.token_program;
|
||||||
if tp == Pubkey::default() || tp == TOKEN_PROGRAM_2022 {
|
if tp == Pubkey::default() {
|
||||||
return TOKEN_PROGRAM_2022;
|
TOKEN_PROGRAM_2022
|
||||||
|
} else {
|
||||||
|
tp
|
||||||
}
|
}
|
||||||
if is_pump_suffix_mint(mint) {
|
|
||||||
return TOKEN_PROGRAM_2022;
|
|
||||||
}
|
|
||||||
tp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve quote mint and its token program from PumpFunParams.
|
/// Resolve quote mint and its token program from PumpFunParams.
|
||||||
@@ -123,7 +119,7 @@ fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||||
let token_program = effective_pump_mint_token_program(¶ms.output_mint, protocol_params);
|
let token_program = effective_pump_mint_token_program(protocol_params);
|
||||||
let token_program_meta = if token_program == TOKEN_PROGRAM_2022 {
|
let token_program_meta = if token_program == TOKEN_PROGRAM_2022 {
|
||||||
crate::constants::TOKEN_PROGRAM_2022_META
|
crate::constants::TOKEN_PROGRAM_2022_META
|
||||||
} else {
|
} else {
|
||||||
@@ -277,7 +273,7 @@ fn build_sell_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||||
let token_program = effective_pump_mint_token_program(¶ms.input_mint, protocol_params);
|
let token_program = effective_pump_mint_token_program(protocol_params);
|
||||||
let token_program_meta = if token_program == TOKEN_PROGRAM_2022 {
|
let token_program_meta = if token_program == TOKEN_PROGRAM_2022 {
|
||||||
crate::constants::TOKEN_PROGRAM_2022_META
|
crate::constants::TOKEN_PROGRAM_2022_META
|
||||||
} else {
|
} else {
|
||||||
@@ -391,8 +387,7 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||||
let base_token_program =
|
let base_token_program = effective_pump_mint_token_program(protocol_params);
|
||||||
effective_pump_mint_token_program(¶ms.output_mint, protocol_params);
|
|
||||||
let base_token_program_meta = if base_token_program == TOKEN_PROGRAM_2022 {
|
let base_token_program_meta = if base_token_program == TOKEN_PROGRAM_2022 {
|
||||||
crate::constants::TOKEN_PROGRAM_2022_META
|
crate::constants::TOKEN_PROGRAM_2022_META
|
||||||
} else {
|
} else {
|
||||||
@@ -612,7 +607,7 @@ fn build_sell_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||||
let base_token_program = effective_pump_mint_token_program(¶ms.input_mint, protocol_params);
|
let base_token_program = effective_pump_mint_token_program(protocol_params);
|
||||||
let base_token_program_meta = if base_token_program == TOKEN_PROGRAM_2022 {
|
let base_token_program_meta = if base_token_program == TOKEN_PROGRAM_2022 {
|
||||||
crate::constants::TOKEN_PROGRAM_2022_META
|
crate::constants::TOKEN_PROGRAM_2022_META
|
||||||
} else {
|
} else {
|
||||||
@@ -773,7 +768,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{bonding_curve::BondingCurveAccount, GasFeeStrategy},
|
common::{bonding_curve::BondingCurveAccount, GasFeeStrategy},
|
||||||
constants::{TOKEN_PROGRAM, TOKEN_PROGRAM_2022},
|
constants::TOKEN_PROGRAM,
|
||||||
trading::core::params::{DexParamEnum, PumpFunParams, SwapParams},
|
trading::core::params::{DexParamEnum, PumpFunParams, SwapParams},
|
||||||
};
|
};
|
||||||
use solana_sdk::signature::Keypair;
|
use solana_sdk::signature::Keypair;
|
||||||
@@ -857,14 +852,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pump_suffix_buy_forces_token_2022_even_when_params_legacy() {
|
fn pump_suffix_buy_respects_explicit_legacy_token_program() {
|
||||||
crate::common::seed::set_default_rents();
|
crate::common::seed::set_default_rents();
|
||||||
let params = swap_params_for_buy(pump_mint(), TOKEN_PROGRAM);
|
let params = swap_params_for_buy(pump_mint(), TOKEN_PROGRAM);
|
||||||
let instructions = build_buy_v1(¶ms).unwrap();
|
let instructions = build_buy_v1(¶ms).unwrap();
|
||||||
|
|
||||||
assert_eq!(instructions.len(), 3);
|
assert_eq!(instructions.len(), 3);
|
||||||
assert_eq!(instructions[2].accounts[8].pubkey, TOKEN_PROGRAM_2022);
|
assert_eq!(instructions[2].accounts[8].pubkey, TOKEN_PROGRAM);
|
||||||
assert_eq!(instructions[1].program_id, TOKEN_PROGRAM_2022);
|
assert_eq!(instructions[1].program_id, TOKEN_PROGRAM);
|
||||||
assert_eq!(instructions[2].accounts.len(), 18);
|
assert_eq!(instructions[2].accounts.len(), 18);
|
||||||
assert_eq!(instructions[2].data.len(), 25);
|
assert_eq!(instructions[2].data.len(), 25);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ use once_cell::sync::Lazy;
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use solana_compute_budget_interface::ComputeBudgetInstruction;
|
use solana_compute_budget_interface::ComputeBudgetInstruction;
|
||||||
use solana_sdk::instruction::Instruction;
|
use solana_sdk::instruction::Instruction;
|
||||||
use std::sync::Arc;
|
use std::{hash::Hash, sync::Arc};
|
||||||
|
|
||||||
|
const MAX_COMPUTE_BUDGET_CACHE_SIZE: usize = 4_096;
|
||||||
|
|
||||||
/// Cache key containing all parameters for compute budget instructions
|
/// Cache key containing all parameters for compute budget instructions
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
@@ -17,6 +19,22 @@ struct ComputeBudgetCacheKey {
|
|||||||
static COMPUTE_BUDGET_CACHE: Lazy<DashMap<ComputeBudgetCacheKey, Arc<SmallVec<[Instruction; 2]>>>> =
|
static COMPUTE_BUDGET_CACHE: Lazy<DashMap<ComputeBudgetCacheKey, Arc<SmallVec<[Instruction; 2]>>>> =
|
||||||
Lazy::new(|| DashMap::new());
|
Lazy::new(|| DashMap::new());
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn prune_cache<K, V>(cache: &DashMap<K, V>, max_size: usize)
|
||||||
|
where
|
||||||
|
K: Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
let len = cache.len();
|
||||||
|
if len <= max_size {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let remove_count = (len - max_size).max(max_size / 16).min(len);
|
||||||
|
let keys: Vec<K> = cache.iter().take(remove_count).map(|entry| entry.key().clone()).collect();
|
||||||
|
for key in keys {
|
||||||
|
cache.remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Extend `instructions` with compute budget instructions; on cache hit extends from cached Arc (no SmallVec clone).
|
/// Extend `instructions` with compute budget instructions; on cache hit extends from cached Arc (no SmallVec clone).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn extend_compute_budget_instructions(
|
pub fn extend_compute_budget_instructions(
|
||||||
@@ -41,6 +59,7 @@ pub fn extend_compute_budget_instructions(
|
|||||||
let arc = Arc::new(insts);
|
let arc = Arc::new(insts);
|
||||||
instructions.extend(arc.iter().cloned());
|
instructions.extend(arc.iter().cloned());
|
||||||
COMPUTE_BUDGET_CACHE.insert(cache_key, arc);
|
COMPUTE_BUDGET_CACHE.insert(cache_key, arc);
|
||||||
|
prune_cache(&COMPUTE_BUDGET_CACHE, MAX_COMPUTE_BUDGET_CACHE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns compute budget instructions (allocates on cache hit; prefer `extend_compute_budget_instructions` on hot path).
|
/// Returns compute budget instructions (allocates on cache hit; prefer `extend_compute_budget_instructions` on hot path).
|
||||||
@@ -59,5 +78,6 @@ pub fn compute_budget_instructions(unit_price: u64, unit_limit: u32) -> SmallVec
|
|||||||
}
|
}
|
||||||
let arc = Arc::new(insts.clone());
|
let arc = Arc::new(insts.clone());
|
||||||
COMPUTE_BUDGET_CACHE.insert(cache_key, arc);
|
COMPUTE_BUDGET_CACHE.insert(cache_key, arc);
|
||||||
|
prune_cache(&COMPUTE_BUDGET_CACHE, MAX_COMPUTE_BUDGET_CACHE_SIZE);
|
||||||
insts
|
insts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
use solana_hash::Hash;
|
use solana_hash::Hash;
|
||||||
use solana_message::AddressLookupTableAccount;
|
use solana_message::AddressLookupTableAccount;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
@@ -93,19 +94,19 @@ fn build_versioned_transaction(
|
|||||||
// 使用预分配的交易构建器以降低延迟
|
// 使用预分配的交易构建器以降低延迟
|
||||||
let mut builder = acquire_builder();
|
let mut builder = acquire_builder();
|
||||||
|
|
||||||
let versioned_msg = builder.build_zero_alloc(
|
let build_result = builder.build_zero_alloc(
|
||||||
&payer.pubkey(),
|
&payer.pubkey(),
|
||||||
&full_instructions,
|
&full_instructions,
|
||||||
address_lookup_table_account,
|
address_lookup_table_account,
|
||||||
blockhash,
|
blockhash,
|
||||||
);
|
);
|
||||||
|
release_builder(builder);
|
||||||
|
let versioned_msg = build_result?;
|
||||||
|
|
||||||
let msg_bytes = versioned_msg.serialize();
|
let msg_bytes = versioned_msg.serialize();
|
||||||
let signature = payer.as_ref().try_sign_message(&msg_bytes).expect("sign failed");
|
let signature =
|
||||||
|
payer.as_ref().try_sign_message(&msg_bytes).map_err(|e| anyhow!("sign failed: {e}"))?;
|
||||||
let tx = VersionedTransaction { signatures: vec![signature], message: versioned_msg };
|
let tx = VersionedTransaction { signatures: vec![signature], message: versioned_msg };
|
||||||
|
|
||||||
// 归还构建器到池
|
|
||||||
release_builder(builder);
|
|
||||||
|
|
||||||
Ok(tx)
|
Ok(tx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -471,8 +471,31 @@ impl ResultCollector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fast submit mode for callers that do not wait for on-chain confirmation.
|
||||||
|
/// Return as soon as one route accepts, a landed failure consumes the nonce, all routes finish,
|
||||||
|
/// or a short submit window expires. Slow HTTP routes continue in worker tasks but no longer
|
||||||
|
/// block post-buy monitoring / sell scheduling.
|
||||||
|
async fn wait_for_first_submitted(
|
||||||
|
&self,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Option<(bool, Vec<Signature>, Option<anyhow::Error>, Vec<SwqosSubmitTiming>)> {
|
||||||
|
let start = Instant::now();
|
||||||
|
let poll_interval = Duration::from_millis(1);
|
||||||
|
loop {
|
||||||
|
if self.success_flag.load(Ordering::Acquire)
|
||||||
|
|| self.landed_failed_flag.load(Ordering::Acquire)
|
||||||
|
|| self.completed_count.load(Ordering::Acquire) >= self.total_tasks
|
||||||
|
|| start.elapsed() >= timeout
|
||||||
|
{
|
||||||
|
return self.get_first();
|
||||||
|
}
|
||||||
|
tokio::time::sleep(poll_interval).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 等待全部任务完成(不等待链上确认),然后收集并返回所有签名。用于「多路提交」时返回多笔签名。
|
/// 等待全部任务完成(不等待链上确认),然后收集并返回所有签名。用于「多路提交」时返回多笔签名。
|
||||||
/// 轮询间隔 2ms,避免 50ms 间隔在最后一笔返回时多等几十 ms 拉高 submit 耗时。
|
/// 轮询间隔 2ms,避免 50ms 间隔在最后一笔返回时多等几十 ms 拉高 submit 耗时。
|
||||||
|
#[allow(dead_code)]
|
||||||
async fn wait_for_all_submitted(
|
async fn wait_for_all_submitted(
|
||||||
&self,
|
&self,
|
||||||
timeout_secs: u64,
|
timeout_secs: u64,
|
||||||
@@ -627,9 +650,6 @@ pub async fn execute_parallel(
|
|||||||
// Task preparation completed: one shared context (clone once per batch), then minimal per-task data.
|
// Task preparation completed: one shared context (clone once per batch), then minimal per-task data.
|
||||||
let channel_count = selected_task_configs.len().max(1);
|
let channel_count = selected_task_configs.len().max(1);
|
||||||
let collector = Arc::new(ResultCollector::new(channel_count));
|
let collector = Arc::new(ResultCollector::new(channel_count));
|
||||||
// 上限最多 5s:单路卡死时才会等满;若所有通道在窗口内回完,主循环会提前结束(不睡满秒数)。
|
|
||||||
let submit_timeout_secs: u64 =
|
|
||||||
(3u64 + (channel_count.min(16) as u64).div_ceil(2).min(6)).clamp(3, 5);
|
|
||||||
let shared = Arc::new(SwqosSharedContext {
|
let shared = Arc::new(SwqosSharedContext {
|
||||||
payer,
|
payer,
|
||||||
instructions,
|
instructions,
|
||||||
@@ -714,12 +734,10 @@ pub async fn execute_parallel(
|
|||||||
// All jobs enqueued (no spawn on hot path)
|
// All jobs enqueued (no spawn on hot path)
|
||||||
|
|
||||||
if !wait_transaction_confirmed {
|
if !wait_transaction_confirmed {
|
||||||
// submit_timeout_secs 为「等齐各 SWQOS HTTP 应答」的上限;收齐后会立刻进入短 settle,不会睡满整段秒数。
|
let ret = collector.wait_for_first_submitted(Duration::from_millis(500)).await.unwrap_or((
|
||||||
// `wait_for_all_submitted` 仅在未收齐时追加长 grace,避免过早 drain 丢晚到签名。
|
|
||||||
let ret = collector.wait_for_all_submitted(submit_timeout_secs).await.unwrap_or((
|
|
||||||
false,
|
false,
|
||||||
vec![],
|
vec![],
|
||||||
Some(anyhow!("No SWQOS result within grace window (primary {}s)", submit_timeout_secs)),
|
Some(anyhow!("No SWQOS result within fast submit window")),
|
||||||
vec![],
|
vec![],
|
||||||
));
|
));
|
||||||
let (success, signatures, last_error, submit_timings) = ret;
|
let (success, signatures, last_error, submit_timings) = ret;
|
||||||
|
|||||||
@@ -32,12 +32,13 @@ pub struct PumpFunParams {
|
|||||||
/// `Some(PDA(["creator-vault", fee_sharing_config]))` when pump-fees `SharingConfig` is **Active**; set by `from_mint_by_rpc` / [`refresh_fee_sharing_creator_vault_from_rpc`](Self::refresh_fee_sharing_creator_vault_from_rpc).
|
/// `Some(PDA(["creator-vault", fee_sharing_config]))` when pump-fees `SharingConfig` is **Active**; set by `from_mint_by_rpc` / [`refresh_fee_sharing_creator_vault_from_rpc`](Self::refresh_fee_sharing_creator_vault_from_rpc).
|
||||||
pub fee_sharing_creator_vault_if_active: Option<Pubkey>,
|
pub fee_sharing_creator_vault_if_active: Option<Pubkey>,
|
||||||
/// SPL Token or Token-2022 program id owning the **mint** (from gRPC / parser / cache).
|
/// SPL Token or Token-2022 program id owning the **mint** (from gRPC / parser / cache).
|
||||||
/// **`Pubkey::default()`**:ix 构建时使用 SDK 默认 **Token-2022**(与多数 Pump.fun 新发一致);显式传入 Legacy 或 Token-2022 id 可覆盖该默认值。
|
/// **`Pubkey::default()`**:ix 构建时使用 SDK 默认 **Token-2022**(与多数 Pump.fun 新发一致)。
|
||||||
|
/// 显式传入 Legacy 或 Token-2022 id 时严格按该值组装,不再用 mint 字符串后缀猜测。
|
||||||
pub token_program: Pubkey,
|
pub token_program: Pubkey,
|
||||||
/// Whether to close token account when selling, only effective during sell operations
|
/// Whether to close token account when selling, only effective during sell operations
|
||||||
pub close_token_account_when_sell: Option<bool>,
|
pub close_token_account_when_sell: Option<bool>,
|
||||||
/// Fee recipient for buy/sell account #2. Set from sol-parser-sdk (`tradeEvent.feeRecipient` / 同笔 create_v2+buy 回填的 `observed_fee_recipient`);热路径不查 RPC。
|
/// Fee recipient for buy/sell account #2. Set from sol-parser-sdk (`tradeEvent.feeRecipient` / 同笔 create_v2+buy 回填的 `observed_fee_recipient`);热路径不查 RPC。
|
||||||
/// `Pubkey::default()` 时按 mayhem 从静态池随机(与 npm 静态池一致,可能落后于主网 Global)。
|
/// `Pubkey::default()` 时只能使用 SDK 静态 fallback,可能落后于主网 Global;交易热路径应优先传入 gRPC / parser 观测值。
|
||||||
pub fee_recipient: Pubkey,
|
pub fee_recipient: Pubkey,
|
||||||
/// Quote mint for v2 instructions (default: `So11111111111111111111111111111111111111112` for SOL-paired).
|
/// Quote mint for v2 instructions (default: `So11111111111111111111111111111111111111112` for SOL-paired).
|
||||||
/// For USDC-paired coins, set to `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`.
|
/// For USDC-paired coins, set to `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const PARALLEL_SENDER_COUNT: usize = 18;
|
|||||||
/// 启动时预填充数量,必须 >= PARALLEL_SENDER_COUNT,否则 18 路并发 build 会触发分配或争抢
|
/// 启动时预填充数量,必须 >= PARALLEL_SENDER_COUNT,否则 18 路并发 build 会触发分配或争抢
|
||||||
const TX_BUILDER_POOL_PREFILL: usize = 64;
|
const TX_BUILDER_POOL_PREFILL: usize = 64;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use crossbeam_queue::ArrayQueue;
|
use crossbeam_queue::ArrayQueue;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use solana_message::AddressLookupTableAccount;
|
use solana_message::AddressLookupTableAccount;
|
||||||
@@ -82,7 +83,7 @@ impl PreallocatedTxBuilder {
|
|||||||
instructions: &[Instruction],
|
instructions: &[Instruction],
|
||||||
address_lookup_table_account: Option<&AddressLookupTableAccount>,
|
address_lookup_table_account: Option<&AddressLookupTableAccount>,
|
||||||
recent_blockhash: Hash,
|
recent_blockhash: Hash,
|
||||||
) -> VersionedMessage {
|
) -> Result<VersionedMessage> {
|
||||||
self.reset();
|
self.reset();
|
||||||
self.instructions.extend_from_slice(instructions);
|
self.instructions.extend_from_slice(instructions);
|
||||||
|
|
||||||
@@ -92,14 +93,13 @@ impl PreallocatedTxBuilder {
|
|||||||
&self.instructions,
|
&self.instructions,
|
||||||
std::slice::from_ref(alt),
|
std::slice::from_ref(alt),
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
)
|
)?;
|
||||||
.expect("v0 message compile failed");
|
Ok(VersionedMessage::V0(message))
|
||||||
VersionedMessage::V0(message)
|
|
||||||
} else {
|
} else {
|
||||||
// ✅ 没有查找表,使用 Legacy 消息(兼容所有 RPC)
|
// ✅ 没有查找表,使用 Legacy 消息(兼容所有 RPC)
|
||||||
let message =
|
let message =
|
||||||
Message::new_with_blockhash(&self.instructions, Some(payer), &recent_blockhash);
|
Message::new_with_blockhash(&self.instructions, Some(payer), &recent_blockhash);
|
||||||
VersionedMessage::Legacy(message)
|
Ok(VersionedMessage::Legacy(message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-29
@@ -1,22 +1,10 @@
|
|||||||
// Note: sol_to_lamports moved to solana_native_token crate in 3.x
|
|
||||||
// Using manual conversion: 1 SOL = 1_000_000_000 lamports
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
fn sol_to_lamports(sol: f64) -> u64 {
|
|
||||||
(sol * 1_000_000_000.0) as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
instruction::utils::pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS},
|
instruction::utils::pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS},
|
||||||
utils::calc::common::compute_fee,
|
utils::calc::common::compute_fee,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Converts SOL string to lamports (wrapper for sol_to_lamports)
|
|
||||||
#[inline]
|
|
||||||
fn sol_str_to_lamports(sol: &str) -> Option<u64> {
|
|
||||||
sol.parse::<f64>().ok().map(|s| sol_to_lamports(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the amount of tokens that can be purchased with a given SOL amount
|
/// Calculates the amount of tokens that can be purchased with a given SOL amount
|
||||||
/// using the bonding curve formula.
|
/// using the bonding curve formula.
|
||||||
///
|
///
|
||||||
@@ -54,26 +42,21 @@ pub fn get_buy_token_amount_from_sol_amount(
|
|||||||
|
|
||||||
let input_amount = amount_128
|
let input_amount = amount_128
|
||||||
.checked_mul(10_000)
|
.checked_mul(10_000)
|
||||||
.unwrap()
|
.and_then(|v| v.checked_div(total_fee_basis_points_128 + 10_000))
|
||||||
.checked_div(total_fee_basis_points_128 + 10_000)
|
.unwrap_or(0);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let denominator = virtual_sol_reserves + input_amount;
|
let Some(denominator) = virtual_sol_reserves.checked_add(input_amount) else { return 0 };
|
||||||
|
if denominator == 0 {
|
||||||
let mut tokens_received =
|
return 0;
|
||||||
input_amount.checked_mul(virtual_token_reserves).unwrap().checked_div(denominator).unwrap();
|
|
||||||
|
|
||||||
tokens_received = tokens_received.min(real_token_reserves);
|
|
||||||
|
|
||||||
if tokens_received <= 100 * 1_000_000_u128 {
|
|
||||||
tokens_received = if amount > sol_str_to_lamports("0.01").unwrap_or(0) {
|
|
||||||
25547619 * 1_000_000_u128
|
|
||||||
} else {
|
|
||||||
255476 * 1_000_000_u128
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens_received as u64
|
let tokens_received = input_amount
|
||||||
|
.checked_mul(virtual_token_reserves)
|
||||||
|
.and_then(|v| v.checked_div(denominator))
|
||||||
|
.unwrap_or(0)
|
||||||
|
.min(real_token_reserves);
|
||||||
|
|
||||||
|
tokens_received.min(u64::MAX as u128) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the amount of SOL that will be received when selling a given token amount
|
/// Calculates the amount of SOL that will be received when selling a given token amount
|
||||||
|
|||||||
Reference in New Issue
Block a user