Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12af055bff |
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sol-trade-sdk"
|
||||
version = "4.0.5"
|
||||
version = "4.0.3"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"William <byteblock6@gmail.com>",
|
||||
@@ -37,7 +37,7 @@ members = [
|
||||
]
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib"]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -74,7 +74,7 @@ This SDK is available in multiple languages:
|
||||
|
||||
## ✨ Features
|
||||
|
||||
1. **PumpFun Trading**: Support for `buy` and `sell` operations
|
||||
1. **PumpFun Trading**: Support for `buy`, `sell`, `buy_exact_sol_in`, and the new unified `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` instructions (SOL + USDC)
|
||||
2. **PumpSwap Trading**: Support for PumpSwap pool trading operations
|
||||
3. **Bonk Trading**: Support for Bonk trading operations
|
||||
4. **Raydium CPMM Trading**: Support for Raydium CPMM (Concentrated Pool Market Maker) trading operations
|
||||
@@ -335,6 +335,52 @@ PumpFun and PumpSwap support **cashback** for eligible tokens: part of the tradi
|
||||
- The **pumpfun_copy_trading** and **pumpfun_sniper_trading** examples use sol-parser-sdk for gRPC subscription and pass `e.is_cashback_coin` when building params.
|
||||
- **Claim**: Use `client.claim_cashback_pumpfun()` and `client.claim_cashback_pumpswap(...)` to claim accumulated cashback.
|
||||
|
||||
### Pump.fun Bonding Curve v2 (buy_v2 / sell_v2 / buy_exact_quote_in_v2)
|
||||
|
||||
Pump.fun has upgraded the Bonding Curve contract with **unified v2 instructions** that support both SOL-paired and USDC-paired coins through a single fixed account layout. The legacy `buy`/`sell`/`buy_exact_sol_in` instructions continue to work for SOL-paired coins and remain the default.
|
||||
|
||||
**Key changes in v2 instructions:**
|
||||
- `quote_mint` parameter — pass wrapped SOL (`So11111111111111111111111111111111111111112`) for SOL-paired, or USDC mint for USDC-paired
|
||||
- 27 fixed accounts (buy) / 26 fixed accounts (sell) — **no optional accounts**
|
||||
- `buyback_fee_recipient`, `sharing_config`, and 6 `associated_quote_*` ATAs are now mandatory
|
||||
- Same pricing and cost as legacy instructions for SOL-paired coins
|
||||
|
||||
**Using v2 instructions:**
|
||||
|
||||
To use v2 instructions, set `quote_mint` on your `PumpFunParams`. The SDK automatically switches to `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` discriminators and the 27/26-account layout:
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
|
||||
use sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT;
|
||||
|
||||
// SOL-paired coin (legacy bonding curves — use wrapped SOL mint)
|
||||
let params = PumpFunParams::from_trade(
|
||||
bonding_curve, associated_bonding_curve, mint, creator, creator_vault,
|
||||
virtual_token_reserves, virtual_sol_reserves,
|
||||
real_token_reserves, real_sol_reserves,
|
||||
close_token_account_when_sell, fee_recipient, token_program,
|
||||
is_cashback_coin, mayhem_mode,
|
||||
).with_quote_mint(WSOL_TOKEN_ACCOUNT); // enables use_v2_ix
|
||||
|
||||
// USDC-paired coin (coming soon — requires v2)
|
||||
let params = PumpFunParams::from_trade(/* ... */)
|
||||
.with_quote_mint(USDC_TOKEN_ACCOUNT);
|
||||
|
||||
// From RPC (auto-detects quote_mint when available)
|
||||
let params = PumpFunParams::from_mint_by_rpc(&rpc, &mint).await?
|
||||
.with_quote_mint(WSOL_TOKEN_ACCOUNT);
|
||||
|
||||
// Then trade as usual
|
||||
client.buy(buy_params).await?;
|
||||
client.sell(sell_params).await?;
|
||||
```
|
||||
|
||||
| Quote Mint | `use_v2_ix` | Instruction Used | Notes |
|
||||
|-----------|-------------|-----------------|-------|
|
||||
| Not set (default) | `false` | Legacy `buy`/`sell`/`buy_exact_sol_in` | Backward compatible, SOL-only |
|
||||
| `WSOL_TOKEN_ACCOUNT` | `true` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | SOL-paired, unified layout |
|
||||
| `USDC_TOKEN_ACCOUNT` | `true` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | USDC-paired (requires v2) |
|
||||
|
||||
#### PumpFun: Creator Rewards Sharing (creator_vault)
|
||||
|
||||
Some PumpFun coins use **Creator Rewards Sharing**, so the on-chain `creator_vault` can differ from the default derivation. If you reuse cached params from a **buy** when **selling**, you may see program error **2006 (seeds constraint violated)**. To avoid this:
|
||||
|
||||
+47
-1
@@ -74,7 +74,7 @@
|
||||
|
||||
## ✨ 项目特性
|
||||
|
||||
1. **PumpFun 交易**: 支持`购买`、`卖出`功能
|
||||
1. **PumpFun 交易**: 支持 `buy`、`sell`、`buy_exact_sol_in` 以及全新的统一化 `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` 指令(SOL + USDC)
|
||||
2. **PumpSwap 交易**: 支持 PumpSwap 池的交易操作
|
||||
3. **Bonk 交易**: 支持 Bonk 的交易操作
|
||||
4. **Raydium CPMM 交易**: 支持 Raydium CPMM (Concentrated Pool Market Maker) 的交易操作
|
||||
@@ -352,6 +352,52 @@ SDK 不会在每次卖出时通过 RPC 拉取 creator_vault(以避免延迟)
|
||||
- **sol-parser-sdk**:指令解析从账户 17、18 写入;若事件来自日志,账户填充器也会从指令补全。用 `PumpSwapParams::from_trade(..., e.coin_creator_vault_ata, e.coin_creator_vault_authority, ...)` 即可。
|
||||
- **solana-streamer**:指令解析从 `accounts.get(17)`、`accounts.get(18)` 写入。同样用事件的 `coin_creator_vault_ata`、`coin_creator_vault_authority` 调用 `from_trade`。
|
||||
|
||||
### Pump.fun Bonding Curve v2(buy_v2 / sell_v2 / buy_exact_quote_in_v2)
|
||||
|
||||
Pump.fun 已升级 Bonding Curve 合约,推出**统一化 v2 指令**,通过固定账户布局同时支持 SOL 和 USDC 配对币。旧版 `buy`/`sell`/`buy_exact_sol_in` 仍可用于 SOL 配对币,且保持为默认选项。
|
||||
|
||||
**v2 指令关键变化:**
|
||||
- 新增 `quote_mint` 参数 — SOL 配对传包装 SOL(`So11111111111111111111111111111111111111112`),USDC 配对传 USDC mint
|
||||
- 27 个固定账户(buy)/ 26 个固定账户(sell)— **无可选账户**
|
||||
- `buyback_fee_recipient`、`sharing_config` 和 6 个 `associated_quote_*` ATA 变为强制账户
|
||||
- SOL 配对币的报价和成本与旧版一致,无额外开销
|
||||
|
||||
**使用方式:**
|
||||
|
||||
设置 `PumpFunParams` 的 `quote_mint` 即可,SDK 会自动切换到 v2 discriminator 和新账户布局:
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
|
||||
use sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT;
|
||||
|
||||
// SOL 配对币(旧 bonding curve — 传包装 SOL mint)
|
||||
let params = PumpFunParams::from_trade(
|
||||
bonding_curve, associated_bonding_curve, mint, creator, creator_vault,
|
||||
virtual_token_reserves, virtual_sol_reserves,
|
||||
real_token_reserves, real_sol_reserves,
|
||||
close_token_account_when_sell, fee_recipient, token_program,
|
||||
is_cashback_coin, mayhem_mode,
|
||||
).with_quote_mint(WSOL_TOKEN_ACCOUNT); // 自动启用 use_v2_ix
|
||||
|
||||
// USDC 配对币(即将开放 — 必须使用 v2)
|
||||
let params = PumpFunParams::from_trade(/* ... */)
|
||||
.with_quote_mint(USDC_TOKEN_ACCOUNT);
|
||||
|
||||
// 从 RPC 获取参数(链上 quote_mint 可用时自动识别)
|
||||
let params = PumpFunParams::from_mint_by_rpc(&rpc, &mint).await?
|
||||
.with_quote_mint(WSOL_TOKEN_ACCOUNT);
|
||||
|
||||
// 之后正常交易
|
||||
client.buy(buy_params).await?;
|
||||
client.sell(sell_params).await?;
|
||||
```
|
||||
|
||||
| quote_mint | use_v2_ix | 实际使用的指令 | 说明 |
|
||||
|-----------|-------------|---------|------|
|
||||
| 未设置(默认) | `false` | 旧版 `buy`/`sell`/`buy_exact_sol_in` | 向后兼容,仅 SOL |
|
||||
| `WSOL_TOKEN_ACCOUNT` | `true` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | SOL 配对,统一布局 |
|
||||
| `USDC_TOKEN_ACCOUNT` | `true` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | USDC 配对(必须使用 v2) |
|
||||
|
||||
## 🛡️ MEV 保护服务
|
||||
|
||||
可以通过官网申请密钥:[社区官网](https://fnzero.dev/swqos)
|
||||
|
||||
@@ -162,9 +162,7 @@ async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> Any
|
||||
trade_info.base_token_program,
|
||||
trade_info.quote_token_program,
|
||||
trade_info.protocol_fee_recipient,
|
||||
pool_data.coin_creator,
|
||||
pool_data.is_cashback_coin,
|
||||
trade_info.cashback_fee_basis_points,
|
||||
);
|
||||
let mint = if trade_info.base_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT
|
||||
|| trade_info.base_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||
@@ -193,9 +191,7 @@ async fn pumpswap_trade_with_grpc_sell_event(trade_info: PumpSwapSellEvent) -> A
|
||||
trade_info.base_token_program,
|
||||
trade_info.quote_token_program,
|
||||
trade_info.protocol_fee_recipient,
|
||||
pool_data.coin_creator,
|
||||
pool_data.is_cashback_coin,
|
||||
trade_info.cashback_fee_basis_points,
|
||||
);
|
||||
let mint = if trade_info.base_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT
|
||||
|| trade_info.base_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||
|
||||
-1222
File diff suppressed because it is too large
Load Diff
@@ -57,3 +57,9 @@ pub const RENT_META: solana_sdk::instruction::AccountMeta =
|
||||
|
||||
pub const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey =
|
||||
pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
||||
pub const ASSOCIATED_TOKEN_PROGRAM_META: solana_sdk::instruction::AccountMeta =
|
||||
solana_sdk::instruction::AccountMeta {
|
||||
pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
};
|
||||
|
||||
+269
-10
@@ -9,10 +9,12 @@ use crate::{
|
||||
use crate::{
|
||||
instruction::utils::pumpfun::{
|
||||
accounts, get_bonding_curve_pda, get_bonding_curve_v2_pda,
|
||||
get_protocol_extra_fee_recipient_random, get_user_volume_accumulator_pda,
|
||||
get_buyback_fee_recipient_random, get_protocol_extra_fee_recipient_random,
|
||||
get_quote_token_program, get_user_volume_accumulator_pda,
|
||||
pump_fun_fee_recipient_meta, resolve_creator_vault_for_ix,
|
||||
global_constants::{self},
|
||||
BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, SELL_DISCRIMINATOR,
|
||||
BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR,
|
||||
BUY_V2_DISCRIMINATOR, SELL_DISCRIMINATOR, SELL_V2_DISCRIMINATOR,
|
||||
},
|
||||
utils::calc::{
|
||||
common::{calculate_with_slippage_buy, calculate_with_slippage_sell},
|
||||
@@ -132,6 +134,155 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
|
||||
// IDL: buy/buy_exact_sol_in 第三参数 track_volume: OptionBool,仅代币支持返现时传 Some(true)
|
||||
let track_volume = if bonding_curve.is_cashback_coin { [1u8, 1u8] } else { [1u8, 0u8] }; // Some(true) / Some(false)
|
||||
|
||||
// Fee recipient: gRPC/ShredStream 填入的 `PumpFunParams.fee_recipient`(同笔 create_v2+buy 或 trade 日志)优先;热路径无 RPC。
|
||||
let fee_recipient_meta =
|
||||
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
|
||||
|
||||
let bonding_curve_v2 = get_bonding_curve_v2_pda(¶ms.output_mint).ok_or_else(|| {
|
||||
anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.output_mint)
|
||||
})?;
|
||||
|
||||
// ========================================
|
||||
// V2 instructions: unified 27-account layout (quote_mint-aware)
|
||||
// ========================================
|
||||
if protocol_params.use_v2_ix {
|
||||
let quote_mint = if protocol_params.quote_mint != Pubkey::default() {
|
||||
protocol_params.quote_mint
|
||||
} else {
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
};
|
||||
let quote_token_program = get_quote_token_program("e_mint);
|
||||
let quote_token_program_meta = if quote_token_program == crate::constants::TOKEN_PROGRAM {
|
||||
crate::constants::TOKEN_PROGRAM_META
|
||||
} else {
|
||||
crate::constants::TOKEN_PROGRAM_2022_META
|
||||
};
|
||||
|
||||
let associated_quote_bonding_curve =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&bonding_curve_addr,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_quote_user =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
params.open_seed_optimize,
|
||||
);
|
||||
let fee_recipient_pk = fee_recipient_meta.pubkey;
|
||||
let associated_quote_fee_recipient =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&fee_recipient_pk,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let buyback_fee_recipient = get_buyback_fee_recipient_random();
|
||||
let associated_quote_buyback_fee_recipient =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&buyback_fee_recipient,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_creator_vault =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&creator_vault_pda,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(¶ms.payer.pubkey())
|
||||
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
|
||||
let associated_user_volume_accumulator =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&user_volume_accumulator,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let sharing_config =
|
||||
crate::instruction::utils::pumpfun::get_fee_sharing_config_pda(¶ms.output_mint)
|
||||
.ok_or_else(|| anyhow!("sharing_config PDA derivation failed"))?;
|
||||
|
||||
let mut v2_data = [0u8; 24];
|
||||
if params.use_exact_sol_amount.unwrap_or(true) {
|
||||
// buy_exact_quote_in_v2(spendable_quote_in: u64, min_tokens_out: u64)
|
||||
let min_tokens_out = calculate_with_slippage_sell(
|
||||
buy_token_amount,
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
);
|
||||
v2_data[..8].copy_from_slice(&BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR);
|
||||
v2_data[8..16].copy_from_slice(¶ms.input_amount.unwrap_or(0).to_le_bytes());
|
||||
v2_data[16..24].copy_from_slice(&min_tokens_out.to_le_bytes());
|
||||
} else {
|
||||
// buy_v2(amount: u64, max_sol_cost: u64)
|
||||
v2_data[..8].copy_from_slice(&BUY_V2_DISCRIMINATOR);
|
||||
v2_data[8..16].copy_from_slice(&buy_token_amount.to_le_bytes());
|
||||
v2_data[16..24].copy_from_slice(&max_sol_cost.to_le_bytes());
|
||||
}
|
||||
|
||||
let v2_accounts: Vec<AccountMeta> = vec![
|
||||
global_constants::GLOBAL_ACCOUNT_META, // 1: global
|
||||
AccountMeta::new_readonly(params.output_mint, false), // 2: base_mint
|
||||
AccountMeta::new_readonly(quote_mint, false), // 3: quote_mint
|
||||
token_program_meta, // 4: base_token_program
|
||||
quote_token_program_meta, // 5: quote_token_program
|
||||
crate::constants::ASSOCIATED_TOKEN_PROGRAM_META, // 6: associated_token_program
|
||||
fee_recipient_meta, // 7: fee_recipient
|
||||
AccountMeta::new(associated_quote_fee_recipient, false), // 8: associated_quote_fee_recipient
|
||||
AccountMeta::new_readonly(buyback_fee_recipient, false), // 9: buyback_fee_recipient
|
||||
AccountMeta::new(associated_quote_buyback_fee_recipient, false), // 10: associated_quote_buyback_fee_recipient
|
||||
AccountMeta::new(bonding_curve_addr, false), // 11: bonding_curve
|
||||
AccountMeta::new(associated_bonding_curve, false), // 12: associated_base_bonding_curve
|
||||
AccountMeta::new(associated_quote_bonding_curve, false), // 13: associated_quote_bonding_curve
|
||||
AccountMeta::new(params.payer.pubkey(), true), // 14: user
|
||||
AccountMeta::new(user_token_account, false), // 15: associated_base_user
|
||||
AccountMeta::new(associated_quote_user, false), // 16: associated_quote_user
|
||||
AccountMeta::new(creator_vault_pda, false), // 17: creator_vault
|
||||
AccountMeta::new(associated_creator_vault, false), // 18: associated_creator_vault
|
||||
AccountMeta::new_readonly(sharing_config, false), // 19: sharing_config
|
||||
accounts::GLOBAL_VOLUME_ACCUMULATOR_META, // 20: global_volume_accumulator
|
||||
AccountMeta::new(user_volume_accumulator, false), // 21: user_volume_accumulator
|
||||
AccountMeta::new(associated_user_volume_accumulator, false), // 22: associated_user_volume_accumulator
|
||||
accounts::FEE_CONFIG_META, // 23: fee_config
|
||||
accounts::FEE_PROGRAM_META, // 24: fee_program
|
||||
crate::constants::SYSTEM_PROGRAM_META, // 25: system_program
|
||||
accounts::EVENT_AUTHORITY_META, // 26: event_authority
|
||||
accounts::PUMPFUN_META, // 27: program
|
||||
];
|
||||
|
||||
if params.create_output_mint_ata {
|
||||
instructions.extend(
|
||||
crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.output_mint,
|
||||
&token_program,
|
||||
params.open_seed_optimize,
|
||||
),
|
||||
);
|
||||
}
|
||||
// Also create quote ATA if it doesn't exist (SOL-paired: no-op since SOL is native)
|
||||
if quote_mint != crate::constants::WSOL_TOKEN_ACCOUNT {
|
||||
let create_quote_ata = crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
params.open_seed_optimize,
|
||||
);
|
||||
if !create_quote_ata.is_empty() {
|
||||
instructions.extend(create_quote_ata);
|
||||
}
|
||||
}
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &v2_data, v2_accounts));
|
||||
return Ok(instructions);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Legacy instructions (backward compatible)
|
||||
// ========================================
|
||||
let mut buy_data = [0u8; 26];
|
||||
if params.use_exact_sol_amount.unwrap_or(true) {
|
||||
// buy_exact_sol_in(spendable_sol_in: u64, min_tokens_out: u64, track_volume)
|
||||
@@ -151,13 +302,6 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
buy_data[24..26].copy_from_slice(&track_volume);
|
||||
}
|
||||
|
||||
// Fee recipient: gRPC/ShredStream 填入的 `PumpFunParams.fee_recipient`(同笔 create_v2+buy 或 trade 日志)优先;热路径无 RPC。
|
||||
let fee_recipient_meta =
|
||||
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
|
||||
|
||||
let bonding_curve_v2 = get_bonding_curve_v2_pda(¶ms.output_mint).ok_or_else(|| {
|
||||
anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.output_mint)
|
||||
})?;
|
||||
let mut accounts: Vec<AccountMeta> = vec![
|
||||
global_constants::GLOBAL_ACCOUNT_META,
|
||||
fee_recipient_meta,
|
||||
@@ -265,10 +409,125 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// Build instructions
|
||||
// V2 sell instruction: unified 26-account layout (quote_mint-aware)
|
||||
// ========================================
|
||||
let mut instructions = Vec::with_capacity(2);
|
||||
|
||||
if protocol_params.use_v2_ix {
|
||||
let quote_mint = if protocol_params.quote_mint != Pubkey::default() {
|
||||
protocol_params.quote_mint
|
||||
} else {
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
};
|
||||
let quote_token_program = get_quote_token_program("e_mint);
|
||||
let quote_token_program_meta = if quote_token_program == crate::constants::TOKEN_PROGRAM {
|
||||
crate::constants::TOKEN_PROGRAM_META
|
||||
} else {
|
||||
crate::constants::TOKEN_PROGRAM_2022_META
|
||||
};
|
||||
let sell_fee_recipient_meta =
|
||||
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
|
||||
|
||||
let associated_quote_bonding_curve =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&bonding_curve_addr,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_quote_user =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
params.open_seed_optimize,
|
||||
);
|
||||
let fee_recipient_pk = sell_fee_recipient_meta.pubkey;
|
||||
let associated_quote_fee_recipient =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&fee_recipient_pk,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let buyback_fee_recipient = get_buyback_fee_recipient_random();
|
||||
let associated_quote_buyback_fee_recipient =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&buyback_fee_recipient,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_creator_vault =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&creator_vault_pda,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(¶ms.payer.pubkey())
|
||||
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
|
||||
let associated_user_volume_accumulator =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&user_volume_accumulator,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let sharing_config =
|
||||
crate::instruction::utils::pumpfun::get_fee_sharing_config_pda(¶ms.input_mint)
|
||||
.ok_or_else(|| anyhow!("sharing_config PDA derivation failed"))?;
|
||||
|
||||
let mut v2_sell_data = [0u8; 24];
|
||||
v2_sell_data[..8].copy_from_slice(&SELL_V2_DISCRIMINATOR);
|
||||
v2_sell_data[8..16].copy_from_slice(&token_amount.to_le_bytes());
|
||||
v2_sell_data[16..24].copy_from_slice(&min_sol_output.to_le_bytes());
|
||||
|
||||
let v2_sell_accounts: Vec<AccountMeta> = vec![
|
||||
global_constants::GLOBAL_ACCOUNT_META, // 1: global
|
||||
AccountMeta::new_readonly(params.input_mint, false), // 2: base_mint
|
||||
AccountMeta::new_readonly(quote_mint, false), // 3: quote_mint
|
||||
token_program_meta, // 4: base_token_program
|
||||
quote_token_program_meta, // 5: quote_token_program
|
||||
crate::constants::ASSOCIATED_TOKEN_PROGRAM_META, // 6: associated_token_program
|
||||
sell_fee_recipient_meta, // 7: fee_recipient
|
||||
AccountMeta::new(associated_quote_fee_recipient, false), // 8: associated_quote_fee_recipient
|
||||
AccountMeta::new_readonly(buyback_fee_recipient, false), // 9: buyback_fee_recipient
|
||||
AccountMeta::new(associated_quote_buyback_fee_recipient, false), // 10: associated_quote_buyback_fee_recipient
|
||||
AccountMeta::new(bonding_curve_addr, false), // 11: bonding_curve
|
||||
AccountMeta::new(associated_bonding_curve, false), // 12: associated_base_bonding_curve
|
||||
AccountMeta::new(associated_quote_bonding_curve, false), // 13: associated_quote_bonding_curve
|
||||
AccountMeta::new(params.payer.pubkey(), true), // 14: user
|
||||
AccountMeta::new(user_token_account, false), // 15: associated_base_user
|
||||
AccountMeta::new(associated_quote_user, false), // 16: associated_quote_user
|
||||
AccountMeta::new(creator_vault_pda, false), // 17: creator_vault
|
||||
AccountMeta::new(associated_creator_vault, false), // 18: associated_creator_vault
|
||||
AccountMeta::new_readonly(sharing_config, false), // 19: sharing_config
|
||||
AccountMeta::new(user_volume_accumulator, false), // 20: user_volume_accumulator
|
||||
AccountMeta::new(associated_user_volume_accumulator, false), // 21: associated_user_volume_accumulator
|
||||
accounts::FEE_CONFIG_META, // 22: fee_config
|
||||
accounts::FEE_PROGRAM_META, // 23: fee_program
|
||||
crate::constants::SYSTEM_PROGRAM_META, // 24: system_program
|
||||
accounts::EVENT_AUTHORITY_META, // 25: event_authority
|
||||
accounts::PUMPFUN_META, // 26: program
|
||||
];
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &v2_sell_data, v2_sell_accounts));
|
||||
|
||||
if protocol_params.close_token_account_when_sell.unwrap_or(false)
|
||||
|| params.close_input_mint_ata
|
||||
{
|
||||
instructions.push(close_account(
|
||||
&token_program,
|
||||
&user_token_account,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[¶ms.payer.pubkey()],
|
||||
)?);
|
||||
}
|
||||
|
||||
return Ok(instructions);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Legacy sell instructions (backward compatible)
|
||||
// ========================================
|
||||
|
||||
let mut sell_data = [0u8; 24];
|
||||
sell_data[..8].copy_from_slice(&SELL_DISCRIMINATOR);
|
||||
sell_data[8..16].copy_from_slice(&token_amount.to_le_bytes());
|
||||
|
||||
@@ -76,7 +76,6 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
if params_coin_creator_vault_authority != accounts::DEFAULT_COIN_CREATOR_VAULT_AUTHORITY {
|
||||
creator = params_coin_creator_vault_authority;
|
||||
}
|
||||
let cashback_fee_bps = protocol_params.cashback_fee_basis_points;
|
||||
|
||||
let (mut token_amount, sol_amount) = if quote_is_wsol_or_usdc {
|
||||
let result = buy_quote_input_internal(
|
||||
@@ -85,7 +84,6 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
&creator,
|
||||
cashback_fee_bps,
|
||||
)
|
||||
.unwrap();
|
||||
// base_amount_out, max_quote_amount_in
|
||||
@@ -97,7 +95,6 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
&creator,
|
||||
cashback_fee_bps,
|
||||
)
|
||||
.unwrap();
|
||||
// min_quote_amount_out, base_amount_in
|
||||
@@ -205,15 +202,11 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
accounts.push(AccountMeta::new(wsol_ata, false));
|
||||
}
|
||||
}
|
||||
// `pool-v2` only when coin_creator ≠ default (@pump-fun/pump-swap-sdk remainingAccounts);
|
||||
// 否则多出的一格会把 buyback pubkey 错位,触发 BuybackFeeRecipientNotAuthorized(6053)。
|
||||
if protocol_params.coin_creator != Pubkey::default() {
|
||||
let pool_v2 = get_pool_v2_pda(&base_mint).ok_or_else(|| {
|
||||
anyhow!("pool_v2 PDA derivation failed for base_mint {}", base_mint)
|
||||
})?;
|
||||
accounts.push(AccountMeta::new_readonly(pool_v2, false));
|
||||
}
|
||||
// Trailing accounts: GlobalConfig.buyback_fee_recipients 中任 pubkey + quote ATA(与 pump-swap-sdk 静态池对齐;轮换时需查链上)。
|
||||
// remainingAccounts: @pump-fun/pump-swap-sdk 要求末尾传 poolV2Pda(baseMint),勿删
|
||||
let pool_v2 = get_pool_v2_pda(&base_mint)
|
||||
.ok_or_else(|| anyhow!("pool_v2 PDA derivation failed for base_mint {}", base_mint))?;
|
||||
accounts.push(AccountMeta::new_readonly(pool_v2, false));
|
||||
// Apr 2026: protocol fee recipient + quote ATA (after pool-v2)
|
||||
let protocol_extra = get_protocol_extra_fee_recipient_random();
|
||||
accounts.push(AccountMeta::new_readonly(protocol_extra, false));
|
||||
accounts.push(AccountMeta::new(
|
||||
@@ -306,7 +299,6 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
if params_coin_creator_vault_authority != accounts::DEFAULT_COIN_CREATOR_VAULT_AUTHORITY {
|
||||
creator = params_coin_creator_vault_authority;
|
||||
}
|
||||
let cashback_fee_bps = protocol_params.cashback_fee_basis_points;
|
||||
|
||||
let (token_amount, mut sol_amount) = if quote_is_wsol_or_usdc {
|
||||
let result = sell_base_input_internal(
|
||||
@@ -315,7 +307,6 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
&creator,
|
||||
cashback_fee_bps,
|
||||
)
|
||||
.unwrap();
|
||||
// base_amount_in, min_quote_amount_out
|
||||
@@ -327,7 +318,6 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
&creator,
|
||||
cashback_fee_bps,
|
||||
)
|
||||
.unwrap();
|
||||
// max_quote_amount_in, base_amount_out
|
||||
@@ -420,12 +410,10 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
accounts.push(AccountMeta::new(accumulator, false));
|
||||
}
|
||||
}
|
||||
if protocol_params.coin_creator != Pubkey::default() {
|
||||
let pool_v2 = get_pool_v2_pda(&base_mint).ok_or_else(|| {
|
||||
anyhow!("pool_v2 PDA derivation failed for base_mint {}", base_mint)
|
||||
})?;
|
||||
accounts.push(AccountMeta::new_readonly(pool_v2, false));
|
||||
}
|
||||
// remainingAccounts: @pump-fun/pump-swap-sdk sell 要求末尾传 poolV2Pda(baseMint),勿删
|
||||
let pool_v2 = get_pool_v2_pda(&base_mint)
|
||||
.ok_or_else(|| anyhow!("pool_v2 PDA derivation failed for base_mint {}", base_mint))?;
|
||||
accounts.push(AccountMeta::new_readonly(pool_v2, false));
|
||||
let protocol_extra = get_protocol_extra_fee_recipient_random();
|
||||
accounts.push(AccountMeta::new_readonly(protocol_extra, false));
|
||||
accounts.push(AccountMeta::new(
|
||||
|
||||
@@ -149,6 +149,19 @@ pub mod global_constants {
|
||||
pubkey!("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||
pubkey!("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||
];
|
||||
|
||||
/// Buyback fee recipients (v2 account #9 in buy_v2/sell_v2).
|
||||
/// Selected randomly from this pool — distinct from main fee recipients.
|
||||
pub const BUYBACK_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"),
|
||||
pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"),
|
||||
pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"),
|
||||
pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"),
|
||||
pubkey!("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz"),
|
||||
pubkey!("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX"),
|
||||
pubkey!("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"),
|
||||
pubkey!("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt"),
|
||||
];
|
||||
}
|
||||
|
||||
/// Constants related to program accounts and authorities
|
||||
@@ -218,6 +231,12 @@ pub mod accounts {
|
||||
pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
|
||||
pub const BUY_EXACT_SOL_IN_DISCRIMINATOR: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95];
|
||||
pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
|
||||
/// buy_v2: unified buy with quote_mint support (SOL + USDC), 27 fixed accounts
|
||||
pub const BUY_V2_DISCRIMINATOR: [u8; 8] = [184, 23, 238, 97, 103, 197, 211, 61];
|
||||
/// sell_v2: unified sell with quote_mint support (SOL + USDC), 26 fixed accounts
|
||||
pub const SELL_V2_DISCRIMINATOR: [u8; 8] = [93, 246, 130, 60, 231, 233, 64, 178];
|
||||
/// buy_exact_quote_in_v2: spend exact quote amount for min tokens out (SOL + USDC), 27 fixed accounts
|
||||
pub const BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR: [u8; 8] = [194, 171, 28, 70, 104, 77, 91, 47];
|
||||
|
||||
/// Check if a pubkey is one of the Mayhem fee recipients
|
||||
#[inline]
|
||||
@@ -279,6 +298,41 @@ pub fn get_protocol_extra_fee_recipient_random() -> Pubkey {
|
||||
.unwrap_or(&global_constants::PROTOCOL_EXTRA_FEE_RECIPIENTS[0])
|
||||
}
|
||||
|
||||
/// Resolve the effective quote_mint for a bonding curve.
|
||||
///
|
||||
/// - If `is_sol_paired` or `bonding_curve.quote_mint == Pubkey::default()` → wrapped SOL (`So1111...12`)
|
||||
/// - Otherwise → the bonding curve's quote_mint (e.g. USDC)
|
||||
#[inline]
|
||||
pub fn resolve_quote_mint(
|
||||
_bonding_curve: &BondingCurveAccount,
|
||||
override_quote_mint: Option<Pubkey>,
|
||||
) -> Pubkey {
|
||||
if let Some(qm) = override_quote_mint {
|
||||
if qm != Pubkey::default() {
|
||||
return qm;
|
||||
}
|
||||
}
|
||||
// Legacy bonding curves have no quote_mint field; default() means SOL-paired
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
}
|
||||
|
||||
/// Random buyback fee recipient from static pool (used as account #9 in buy_v2/sell_v2).
|
||||
#[inline]
|
||||
pub fn get_buyback_fee_recipient_random() -> Pubkey {
|
||||
*global_constants::BUYBACK_FEE_RECIPIENTS
|
||||
.choose(&mut rand::rng())
|
||||
.unwrap_or(&global_constants::BUYBACK_FEE_RECIPIENTS[0])
|
||||
}
|
||||
|
||||
/// Get the quote token program for a given quote_mint.
|
||||
/// SOL → Token Program; USDC → Token Program.
|
||||
/// Both use the legacy SPL Token Program on mainnet.
|
||||
#[inline]
|
||||
pub fn get_quote_token_program(_quote_mint: &Pubkey) -> Pubkey {
|
||||
// Both WSOL and USDC use the legacy Token Program
|
||||
crate::constants::TOKEN_PROGRAM
|
||||
}
|
||||
|
||||
/// 账户 #2 fee recipient:优先使用 gRPC/ShredStream 解析值(同笔 create_v2+buy 的 `observed_fee_recipient` 或 `tradeEvent.feeRecipient`);未提供时按 mayhem 从静态池随机。
|
||||
#[inline]
|
||||
pub fn pump_fun_fee_recipient_meta(from_stream: Pubkey, is_mayhem_mode: bool) -> AccountMeta {
|
||||
|
||||
@@ -91,9 +91,7 @@ pub mod accounts {
|
||||
/// Default Mayhem fee recipient (first of MAYHEM_FEE_RECIPIENTS)
|
||||
pub const MAYHEM_FEE_RECIPIENT: Pubkey = MAYHEM_FEE_RECIPIENTS[0];
|
||||
|
||||
/// Buyback trailing fee recipients (`GlobalConfig.buyback_fee_recipients` on Pump AMM).
|
||||
/// Must match one of these for the pubkey passed after optional `pool-v2` (`@pump-fun/pump-swap-sdk` `getBuybackFeeRecipient`).
|
||||
/// Static mirror of pump-public-docs; if protocol rotates configs, decode global_config from RPC.
|
||||
/// Protocol extra fee recipients (Apr 2026 breaking upgrade). After `pool-v2`: recipient (readonly), then quote ATA (writable).
|
||||
pub const PROTOCOL_EXTRA_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||
pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||
|
||||
+1220
-6
File diff suppressed because it is too large
Load Diff
@@ -23,11 +23,7 @@ use std::collections::HashMap;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::{
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::{str::FromStr, sync::Arc, time::Instant};
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use fnv::FnvHasher;
|
||||
@@ -406,31 +402,14 @@ impl ResultCollector {
|
||||
timeout_secs: u64,
|
||||
) -> Option<(bool, Vec<Signature>, Option<anyhow::Error>, Vec<(SwqosType, i64)>)> {
|
||||
let start = Instant::now();
|
||||
let primary = Duration::from_secs(timeout_secs);
|
||||
let poll_interval = Duration::from_millis(2);
|
||||
let timeout = std::time::Duration::from_secs(timeout_secs);
|
||||
let poll_interval = std::time::Duration::from_millis(2);
|
||||
while self.completed_count.load(Ordering::Acquire) < self.total_tasks {
|
||||
if start.elapsed() > primary {
|
||||
if start.elapsed() > timeout {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(poll_interval).await;
|
||||
}
|
||||
// 「不等待链上确认」仍会等各 SWQOS 的 HTTP 回包;主循环在收齐或触达 `timeout_secs` 后结束。
|
||||
// 若主窗口到时仍有未回包通道,晚到的 TaskResult 若立刻 drain 会丢签名——仅在该路径上拉长 grace。
|
||||
let all_submitted =
|
||||
self.completed_count.load(Ordering::Acquire) >= self.total_tasks;
|
||||
if all_submitted {
|
||||
// 全数已登记:仅留极短 settle,避免极端情况下最后一笔与计数可见性竞态。
|
||||
tokio::time::sleep(Duration::from_millis(35)).await;
|
||||
} else {
|
||||
tokio::time::sleep(Duration::from_millis(600)).await;
|
||||
while self.completed_count.load(Ordering::Acquire) < self.total_tasks {
|
||||
if start.elapsed() > primary + Duration::from_secs(6) {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(120)).await;
|
||||
}
|
||||
self.get_first()
|
||||
}
|
||||
}
|
||||
@@ -513,11 +492,7 @@ pub async fn execute_parallel(
|
||||
}
|
||||
|
||||
// Task preparation completed: one shared context (clone once per batch), then minimal per-task data.
|
||||
let channel_count = task_configs.len().max(1);
|
||||
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 collector = Arc::new(ResultCollector::new(task_configs.len()));
|
||||
let shared = Arc::new(SwqosSharedContext {
|
||||
payer,
|
||||
instructions,
|
||||
@@ -587,20 +562,13 @@ pub async fn execute_parallel(
|
||||
// All jobs enqueued (no spawn on hot path)
|
||||
|
||||
if !wait_transaction_confirmed {
|
||||
// submit_timeout_secs 为「等齐各 SWQOS HTTP 应答」的上限;收齐后会立刻进入短 settle,不会睡满整段秒数。
|
||||
// `wait_for_all_submitted` 仅在未收齐时追加长 grace,避免过早 drain 丢晚到签名。
|
||||
let ret = collector
|
||||
.wait_for_all_submitted(submit_timeout_secs)
|
||||
.await
|
||||
.unwrap_or((
|
||||
false,
|
||||
vec![],
|
||||
Some(anyhow!(
|
||||
"No SWQOS result within grace window (primary {}s)",
|
||||
submit_timeout_secs
|
||||
)),
|
||||
vec![],
|
||||
));
|
||||
const SUBMIT_TIMEOUT_SECS: u64 = 2;//无需确认的交易,一般2秒合适了 一般2秒内发送全都返回 没返回的也不等了,没返回的就是太慢的swqos
|
||||
let ret = collector.wait_for_all_submitted(SUBMIT_TIMEOUT_SECS).await.unwrap_or((
|
||||
false,
|
||||
vec![],
|
||||
Some(anyhow!("No SWQOS result within {}s", SUBMIT_TIMEOUT_SECS)),
|
||||
vec![],
|
||||
));
|
||||
let (success, signatures, last_error, submit_timings) = ret;
|
||||
return Ok((success, signatures, last_error, submit_timings));
|
||||
}
|
||||
|
||||
Executable
+906
@@ -0,0 +1,906 @@
|
||||
use crate::common::bonding_curve::BondingCurveAccount;
|
||||
use crate::common::nonce_cache::DurableNonceInfo;
|
||||
use crate::common::spl_associated_token_account::get_associated_token_address_with_program_id;
|
||||
use crate::common::{GasFeeStrategy, SolanaRpcClient};
|
||||
use core_affinity::CoreId;
|
||||
use crate::instruction::utils::pumpfun::is_mayhem_fee_recipient;
|
||||
|
||||
/// Concurrency + core binding config for parallel submit (precomputed at SDK init, one param on hot path). Uses Arc so no borrow of SwapParams.
|
||||
#[derive(Clone)]
|
||||
pub struct SenderConcurrencyConfig {
|
||||
pub sender_thread_cores: Option<Arc<Vec<usize>>>,
|
||||
pub effective_core_ids: Arc<Vec<CoreId>>,
|
||||
pub max_sender_concurrency: usize,
|
||||
}
|
||||
use crate::instruction::utils::pumpswap::accounts::MAYHEM_FEE_RECIPIENT as MAYHEM_FEE_RECIPIENT_SWAP;
|
||||
use crate::swqos::{SwqosClient, TradeType};
|
||||
use crate::trading::common::get_multi_token_balances;
|
||||
use crate::trading::MiddlewareManager;
|
||||
use solana_hash::Hash;
|
||||
use solana_message::AddressLookupTableAccount;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// DEX 参数枚举 - 零开销抽象替代 Box<dyn ProtocolParams>
|
||||
#[derive(Clone)]
|
||||
pub enum DexParamEnum {
|
||||
PumpFun(PumpFunParams),
|
||||
PumpSwap(PumpSwapParams),
|
||||
Bonk(BonkParams),
|
||||
RaydiumCpmm(RaydiumCpmmParams),
|
||||
RaydiumAmmV4(RaydiumAmmV4Params),
|
||||
MeteoraDammV2(MeteoraDammV2Params),
|
||||
}
|
||||
|
||||
impl DexParamEnum {
|
||||
/// 获取内部参数的 Any 引用,用于向后兼容的类型检查
|
||||
#[inline]
|
||||
pub fn as_any(&self) -> &dyn std::any::Any {
|
||||
match self {
|
||||
DexParamEnum::PumpFun(p) => p,
|
||||
DexParamEnum::PumpSwap(p) => p,
|
||||
DexParamEnum::Bonk(p) => p,
|
||||
DexParamEnum::RaydiumCpmm(p) => p,
|
||||
DexParamEnum::RaydiumAmmV4(p) => p,
|
||||
DexParamEnum::MeteoraDammV2(p) => p,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Swap parameters
|
||||
#[derive(Clone)]
|
||||
pub struct SwapParams {
|
||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||
pub payer: Arc<Keypair>,
|
||||
pub trade_type: TradeType,
|
||||
pub input_mint: Pubkey,
|
||||
pub input_token_program: Option<Pubkey>,
|
||||
pub output_mint: Pubkey,
|
||||
pub output_token_program: Option<Pubkey>,
|
||||
pub input_amount: Option<u64>,
|
||||
pub slippage_basis_points: Option<u64>,
|
||||
pub address_lookup_table_account: Option<AddressLookupTableAccount>,
|
||||
pub recent_blockhash: Option<Hash>,
|
||||
pub wait_tx_confirmed: bool,
|
||||
pub protocol_params: DexParamEnum,
|
||||
pub open_seed_optimize: bool,
|
||||
/// Arc<Vec<..>> so cloning from infrastructure is a single Arc clone.
|
||||
pub swqos_clients: Arc<Vec<Arc<SwqosClient>>>,
|
||||
pub middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
pub durable_nonce: Option<DurableNonceInfo>,
|
||||
pub with_tip: bool,
|
||||
pub create_input_mint_ata: bool,
|
||||
pub close_input_mint_ata: bool,
|
||||
pub create_output_mint_ata: bool,
|
||||
pub close_output_mint_ata: bool,
|
||||
pub fixed_output_amount: Option<u64>,
|
||||
pub gas_fee_strategy: GasFeeStrategy,
|
||||
pub simulate: bool,
|
||||
/// Whether to output SDK logs (from TradeConfig.log_enabled).
|
||||
pub log_enabled: bool,
|
||||
/// Use dedicated sender threads (internal; set via client.with_dedicated_sender_threads()).
|
||||
pub use_dedicated_sender_threads: bool,
|
||||
/// Core indices for dedicated sender threads (from TradeConfig.sender_thread_cores). Arc avoids cloning the Vec on hot path.
|
||||
pub sender_thread_cores: Option<Arc<Vec<usize>>>,
|
||||
/// Precomputed at SDK init: min(swqos_count, 2/3*cores). Avoids get_core_ids() on trade hot path.
|
||||
pub max_sender_concurrency: usize,
|
||||
/// Precomputed at SDK init: first max_sender_concurrency CoreIds for job affinity. Arc clone only.
|
||||
pub effective_core_ids: Arc<Vec<CoreId>>,
|
||||
/// Whether to check minimum tip per SWQOS (from TradeConfig.check_min_tip). When false, skip filter for lower latency.
|
||||
pub check_min_tip: bool,
|
||||
/// Optional event receive time in microseconds (same scale as sol-parser-sdk clock::now_micros). Used as timing start when log_enabled.
|
||||
pub grpc_recv_us: Option<i64>,
|
||||
/// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).
|
||||
/// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens.
|
||||
/// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
|
||||
/// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes.
|
||||
pub use_exact_sol_amount: Option<bool>,
|
||||
}
|
||||
|
||||
impl SwapParams {
|
||||
/// One struct for execute_parallel: merges sender_thread_cores, effective_core_ids, max_sender_concurrency. Arc clone only.
|
||||
#[inline]
|
||||
pub fn sender_concurrency_config(&self) -> SenderConcurrencyConfig {
|
||||
SenderConcurrencyConfig {
|
||||
sender_thread_cores: self.sender_thread_cores.clone(),
|
||||
effective_core_ids: self.effective_core_ids.clone(),
|
||||
max_sender_concurrency: self.max_sender_concurrency,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SwapParams {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SwapParams: ...")
|
||||
}
|
||||
}
|
||||
|
||||
/// PumpFun protocol specific parameters
|
||||
/// Configuration parameters specific to PumpFun trading protocol.
|
||||
///
|
||||
/// **Creator vault**: Pump buy/sell instructions always pass `creator_vault` =
|
||||
/// `PDA(["creator-vault", bonding_curve.creator])` derived from [`BondingCurveAccount::creator`].
|
||||
/// Keep `bonding_curve.creator` in sync with chain (gRPC / RPC); stale `creator_vault` in this struct
|
||||
/// does not affect ix building.
|
||||
///
|
||||
/// **V2 instructions**: Set `use_v2_ix = true` to use `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2`
|
||||
/// instructions with unified account layout. Required for USDC-paired coins (`quote_mint != WSOL`).
|
||||
/// For SOL-paired coins, legacy instructions still work and are the default.
|
||||
#[derive(Clone)]
|
||||
pub struct PumpFunParams {
|
||||
pub bonding_curve: Arc<BondingCurveAccount>,
|
||||
pub associated_bonding_curve: Pubkey,
|
||||
/// Resolved by [`resolve_creator_vault_for_ix`](crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix): use ix vault when it matches `PDA(creator)` or fee-sharing vault; else `PDA(creator)`.
|
||||
pub creator_vault: Pubkey,
|
||||
pub token_program: Pubkey,
|
||||
/// Whether to close token account when selling, only effective during sell operations
|
||||
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。
|
||||
/// `Pubkey::default()` 时按 mayhem 从静态池随机(与 npm 静态池一致,可能落后于主网 Global)。
|
||||
pub fee_recipient: Pubkey,
|
||||
/// Quote mint for v2 instructions (default: `So11111111111111111111111111111111111111112` for SOL-paired).
|
||||
/// For USDC-paired coins, set to `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`.
|
||||
pub quote_mint: Pubkey,
|
||||
/// Whether to use v2 instructions (`buy_v2`/`sell_v2`/`buy_exact_quote_in_v2`).
|
||||
/// Default `false` for backward compatibility. Must be `true` for USDC-paired coins.
|
||||
pub use_v2_ix: bool,
|
||||
}
|
||||
|
||||
impl PumpFunParams {
|
||||
pub fn immediate_sell(
|
||||
creator_vault: Pubkey,
|
||||
token_program: Pubkey,
|
||||
close_token_account_when_sell: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
bonding_curve: Arc::new(BondingCurveAccount { ..Default::default() }),
|
||||
associated_bonding_curve: Pubkey::default(),
|
||||
creator_vault: creator_vault,
|
||||
token_program: token_program,
|
||||
close_token_account_when_sell: Some(close_token_account_when_sell),
|
||||
fee_recipient: Pubkey::default(),
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
||||
/// so that sell instructions include the correct remaining accounts for cashback.
|
||||
/// `mayhem_mode`: `Some` when known from Create/Trade event (`is_mayhem_mode` / `mayhem_mode`).
|
||||
/// `None` falls back to detecting Mayhem via reserved fee recipient pubkeys only (not AMM protocol fee accounts).
|
||||
pub fn from_dev_trade(
|
||||
mint: Pubkey,
|
||||
token_amount: u64,
|
||||
max_sol_cost: u64,
|
||||
creator: Pubkey,
|
||||
bonding_curve: Pubkey,
|
||||
associated_bonding_curve: Pubkey,
|
||||
creator_vault: Pubkey,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
fee_recipient: Pubkey,
|
||||
token_program: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
mayhem_mode: Option<bool>,
|
||||
) -> Self {
|
||||
let is_mayhem_mode =
|
||||
mayhem_mode.unwrap_or_else(|| is_mayhem_fee_recipient(&fee_recipient));
|
||||
let bonding_curve_account = BondingCurveAccount::from_dev_trade(
|
||||
bonding_curve,
|
||||
&mint,
|
||||
token_amount,
|
||||
max_sol_cost,
|
||||
creator,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
);
|
||||
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix(
|
||||
&bonding_curve_account.creator,
|
||||
creator_vault,
|
||||
&mint,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve_account.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve_account),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
creator_vault: creator_vault_resolved,
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
token_program: token_program,
|
||||
fee_recipient,
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
||||
/// so that sell instructions include the correct remaining accounts for cashback.
|
||||
///
|
||||
/// `mayhem_mode`:
|
||||
/// - **`Some(v)`**(推荐):显式使用链上事件中的值。gRPC 日志解析对应 Explorer 的 `tradeEvent.mayhemMode`;
|
||||
/// **不会**再用 `fee_recipient` 覆盖。
|
||||
/// - **`None`**:无该字段时(例如 ShredStream 仅解外层指令、或冷路径),才用 `fee_recipient` 是否落在 Mayhem 静态列表上推断。
|
||||
pub fn from_trade(
|
||||
bonding_curve: Pubkey,
|
||||
associated_bonding_curve: Pubkey,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
creator_vault: Pubkey,
|
||||
virtual_token_reserves: u64,
|
||||
virtual_sol_reserves: u64,
|
||||
real_token_reserves: u64,
|
||||
real_sol_reserves: u64,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
fee_recipient: Pubkey,
|
||||
token_program: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
mayhem_mode: Option<bool>,
|
||||
) -> Self {
|
||||
let is_mayhem_mode = match mayhem_mode {
|
||||
Some(v) => v,
|
||||
None => is_mayhem_fee_recipient(&fee_recipient),
|
||||
};
|
||||
let bonding_curve = BondingCurveAccount::from_trade(
|
||||
bonding_curve,
|
||||
mint,
|
||||
creator,
|
||||
virtual_token_reserves,
|
||||
virtual_sol_reserves,
|
||||
real_token_reserves,
|
||||
real_sol_reserves,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
);
|
||||
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix(
|
||||
&bonding_curve.creator,
|
||||
creator_vault,
|
||||
&mint,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
creator_vault: creator_vault_resolved,
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
token_program: token_program,
|
||||
fee_recipient,
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let account =
|
||||
crate::instruction::utils::pumpfun::fetch_bonding_curve_account(rpc, mint).await?;
|
||||
let mint_account = rpc.get_account(&mint).await?;
|
||||
let bonding_curve = BondingCurveAccount {
|
||||
discriminator: 0,
|
||||
account: account.1,
|
||||
virtual_token_reserves: account.0.virtual_token_reserves,
|
||||
virtual_sol_reserves: account.0.virtual_sol_reserves,
|
||||
real_token_reserves: account.0.real_token_reserves,
|
||||
real_sol_reserves: account.0.real_sol_reserves,
|
||||
token_total_supply: account.0.token_total_supply,
|
||||
complete: account.0.complete,
|
||||
creator: account.0.creator,
|
||||
is_mayhem_mode: account.0.is_mayhem_mode,
|
||||
is_cashback_coin: account.0.is_cashback_coin,
|
||||
};
|
||||
let associated_bonding_curve = get_associated_token_address_with_program_id(
|
||||
&bonding_curve.account,
|
||||
mint,
|
||||
&mint_account.owner,
|
||||
);
|
||||
let creator_vault =
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator);
|
||||
Ok(Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
creator_vault: creator_vault.unwrap(),
|
||||
close_token_account_when_sell: None,
|
||||
token_program: mint_account.owner,
|
||||
fee_recipient: Pubkey::default(),
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets `quote_mint` and enables v2 instructions. Required for USDC-paired coins.
|
||||
/// For SOL-paired coins, pass `WSOL_TOKEN_ACCOUNT` or leave default.
|
||||
#[inline]
|
||||
pub fn with_quote_mint(mut self, quote_mint: Pubkey) -> Self {
|
||||
self.quote_mint = quote_mint;
|
||||
self.use_v2_ix = quote_mint != Pubkey::default();
|
||||
self
|
||||
}
|
||||
|
||||
/// Updates the cached `creator_vault` field only. Buy/sell ix use [`BondingCurveAccount::creator`].
|
||||
#[inline]
|
||||
pub fn with_creator_vault(mut self, creator_vault: Pubkey) -> Self {
|
||||
self.creator_vault = creator_vault;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// PumpSwap Protocol Specific Parameters
|
||||
///
|
||||
/// Parameters for configuring PumpSwap trading protocol, including liquidity pool information,
|
||||
/// token configuration, and transaction amounts.
|
||||
///
|
||||
/// **Performance Note**: If these parameters are not provided, the system will attempt to
|
||||
/// retrieve the relevant information from RPC, which will increase transaction time.
|
||||
/// For optimal performance, it is recommended to provide all necessary parameters in advance.
|
||||
#[derive(Clone)]
|
||||
pub struct PumpSwapParams {
|
||||
/// Liquidity pool address
|
||||
pub pool: Pubkey,
|
||||
/// Base token mint address
|
||||
/// The mint account address of the base token in the trading pair
|
||||
pub base_mint: Pubkey,
|
||||
/// Quote token mint address
|
||||
/// The mint account address of the quote token in the trading pair, usually SOL or USDC
|
||||
pub quote_mint: Pubkey,
|
||||
/// Pool base token account
|
||||
pub pool_base_token_account: Pubkey,
|
||||
/// Pool quote token account
|
||||
pub pool_quote_token_account: Pubkey,
|
||||
/// Base token reserves in the pool
|
||||
pub pool_base_token_reserves: u64,
|
||||
/// Quote token reserves in the pool
|
||||
pub pool_quote_token_reserves: u64,
|
||||
/// Coin creator vault ATA
|
||||
pub coin_creator_vault_ata: Pubkey,
|
||||
/// Coin creator vault authority
|
||||
pub coin_creator_vault_authority: Pubkey,
|
||||
/// Token program ID
|
||||
pub base_token_program: Pubkey,
|
||||
/// Quote token program ID
|
||||
pub quote_token_program: Pubkey,
|
||||
/// Whether the pool is in mayhem mode
|
||||
pub is_mayhem_mode: bool,
|
||||
/// Whether the pool's coin has cashback enabled
|
||||
pub is_cashback_coin: bool,
|
||||
}
|
||||
|
||||
impl PumpSwapParams {
|
||||
pub fn new(
|
||||
pool: Pubkey,
|
||||
base_mint: Pubkey,
|
||||
quote_mint: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
coin_creator_vault_ata: Pubkey,
|
||||
coin_creator_vault_authority: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
quote_token_program: Pubkey,
|
||||
fee_recipient: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
) -> Self {
|
||||
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT_SWAP;
|
||||
Self {
|
||||
pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
base_token_program,
|
||||
quote_token_program,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fast-path constructor for building PumpSwap parameters directly from decoded
|
||||
/// trade/event data and the accompanying instruction accounts, avoiding RPC
|
||||
/// lookups and associated latency. Token program IDs should be sourced from
|
||||
/// the instruction accounts themselves to respect Token Program vs Token-2022
|
||||
/// differences.
|
||||
///
|
||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin`
|
||||
/// from the event so that buy/sell instructions include the correct remaining
|
||||
/// accounts for cashback.
|
||||
pub fn from_trade(
|
||||
pool: Pubkey,
|
||||
base_mint: Pubkey,
|
||||
quote_mint: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
coin_creator_vault_ata: Pubkey,
|
||||
coin_creator_vault_authority: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
quote_token_program: Pubkey,
|
||||
fee_recipient: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
base_token_program,
|
||||
quote_token_program,
|
||||
fee_recipient,
|
||||
is_cashback_coin,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
if let Ok((pool_address, _)) =
|
||||
crate::instruction::utils::pumpswap::find_by_base_mint(rpc, mint).await
|
||||
{
|
||||
Self::from_pool_address_by_rpc(rpc, &pool_address).await
|
||||
} else if let Ok((pool_address, _)) =
|
||||
crate::instruction::utils::pumpswap::find_by_quote_mint(rpc, mint).await
|
||||
{
|
||||
Self::from_pool_address_by_rpc(rpc, &pool_address).await
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("No pool found for mint"));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_data = crate::instruction::utils::pumpswap::fetch_pool(rpc, pool_address).await?;
|
||||
Self::from_pool_data(rpc, pool_address, &pool_data).await
|
||||
}
|
||||
|
||||
/// Build params from an already-decoded Pool, only fetching token balances.
|
||||
///
|
||||
/// Saves 1 RPC `getAccount` call vs `from_pool_address_by_rpc` when pool data
|
||||
/// is already available (e.g. from `pumpswap::find_by_mint` which returns the
|
||||
/// decoded Pool).
|
||||
pub async fn from_pool_data(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
pool_data: &crate::instruction::utils::pumpswap_types::Pool,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let (pool_base_token_reserves, pool_quote_token_reserves) =
|
||||
crate::instruction::utils::pumpswap::get_token_balances(pool_data, rpc).await?;
|
||||
let creator = pool_data.coin_creator;
|
||||
let coin_creator_vault_ata = crate::instruction::utils::pumpswap::coin_creator_vault_ata(
|
||||
creator,
|
||||
pool_data.quote_mint,
|
||||
);
|
||||
let coin_creator_vault_authority =
|
||||
crate::instruction::utils::pumpswap::coin_creator_vault_authority(creator);
|
||||
|
||||
let base_token_program_ata = get_associated_token_address_with_program_id(
|
||||
pool_address,
|
||||
&pool_data.base_mint,
|
||||
&crate::constants::TOKEN_PROGRAM,
|
||||
);
|
||||
let quote_token_program_ata = get_associated_token_address_with_program_id(
|
||||
pool_address,
|
||||
&pool_data.quote_mint,
|
||||
&crate::constants::TOKEN_PROGRAM,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
pool: *pool_address,
|
||||
base_mint: pool_data.base_mint,
|
||||
quote_mint: pool_data.quote_mint,
|
||||
pool_base_token_account: pool_data.pool_base_token_account,
|
||||
pool_quote_token_account: pool_data.pool_quote_token_account,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
base_token_program: if pool_data.pool_base_token_account == base_token_program_ata {
|
||||
crate::constants::TOKEN_PROGRAM
|
||||
} else {
|
||||
crate::constants::TOKEN_PROGRAM_2022
|
||||
},
|
||||
is_cashback_coin: pool_data.is_cashback_coin,
|
||||
quote_token_program: if pool_data.pool_quote_token_account == quote_token_program_ata {
|
||||
crate::constants::TOKEN_PROGRAM
|
||||
} else {
|
||||
crate::constants::TOKEN_PROGRAM_2022
|
||||
},
|
||||
is_mayhem_mode: pool_data.is_mayhem_mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Bonk protocol specific parameters
|
||||
/// Configuration parameters specific to Bonk trading protocol
|
||||
#[derive(Clone, Default)]
|
||||
pub struct BonkParams {
|
||||
pub virtual_base: u128,
|
||||
pub virtual_quote: u128,
|
||||
pub real_base: u128,
|
||||
pub real_quote: u128,
|
||||
pub pool_state: Pubkey,
|
||||
pub base_vault: Pubkey,
|
||||
pub quote_vault: Pubkey,
|
||||
/// Token program ID
|
||||
pub mint_token_program: Pubkey,
|
||||
pub platform_config: Pubkey,
|
||||
pub platform_associated_account: Pubkey,
|
||||
pub creator_associated_account: Pubkey,
|
||||
pub global_config: Pubkey,
|
||||
}
|
||||
|
||||
impl BonkParams {
|
||||
pub fn immediate_sell(
|
||||
mint_token_program: Pubkey,
|
||||
platform_config: Pubkey,
|
||||
platform_associated_account: Pubkey,
|
||||
creator_associated_account: Pubkey,
|
||||
global_config: Pubkey,
|
||||
) -> Self {
|
||||
Self {
|
||||
mint_token_program,
|
||||
platform_config,
|
||||
platform_associated_account,
|
||||
creator_associated_account,
|
||||
global_config,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn from_trade(
|
||||
virtual_base: u64,
|
||||
virtual_quote: u64,
|
||||
real_base_after: u64,
|
||||
real_quote_after: u64,
|
||||
pool_state: Pubkey,
|
||||
base_vault: Pubkey,
|
||||
quote_vault: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
platform_config: Pubkey,
|
||||
platform_associated_account: Pubkey,
|
||||
creator_associated_account: Pubkey,
|
||||
global_config: Pubkey,
|
||||
) -> Self {
|
||||
Self {
|
||||
virtual_base: virtual_base as u128,
|
||||
virtual_quote: virtual_quote as u128,
|
||||
real_base: real_base_after as u128,
|
||||
real_quote: real_quote_after as u128,
|
||||
pool_state: pool_state,
|
||||
base_vault: base_vault,
|
||||
quote_vault: quote_vault,
|
||||
mint_token_program: base_token_program,
|
||||
platform_config: platform_config,
|
||||
platform_associated_account: platform_associated_account,
|
||||
creator_associated_account: creator_associated_account,
|
||||
global_config: global_config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_dev_trade(
|
||||
is_exact_in: bool,
|
||||
amount_in: u64,
|
||||
amount_out: u64,
|
||||
pool_state: Pubkey,
|
||||
base_vault: Pubkey,
|
||||
quote_vault: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
platform_config: Pubkey,
|
||||
platform_associated_account: Pubkey,
|
||||
creator_associated_account: Pubkey,
|
||||
global_config: Pubkey,
|
||||
) -> Self {
|
||||
const DEFAULT_VIRTUAL_BASE: u128 = 1073025605596382;
|
||||
const DEFAULT_VIRTUAL_QUOTE: u128 = 30000852951;
|
||||
let _amount_in = if is_exact_in {
|
||||
amount_in
|
||||
} else {
|
||||
crate::instruction::utils::bonk::get_amount_in(
|
||||
amount_out,
|
||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||
DEFAULT_VIRTUAL_BASE,
|
||||
DEFAULT_VIRTUAL_QUOTE,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
let real_quote = crate::instruction::utils::bonk::get_amount_in_net(
|
||||
amount_in,
|
||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||
) as u128;
|
||||
let _amount_out = if is_exact_in {
|
||||
crate::instruction::utils::bonk::get_amount_out(
|
||||
amount_in,
|
||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||
DEFAULT_VIRTUAL_BASE,
|
||||
DEFAULT_VIRTUAL_QUOTE,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
) as u128
|
||||
} else {
|
||||
amount_out as u128
|
||||
};
|
||||
let real_base = _amount_out;
|
||||
Self {
|
||||
virtual_base: DEFAULT_VIRTUAL_BASE,
|
||||
virtual_quote: DEFAULT_VIRTUAL_QUOTE,
|
||||
real_base: real_base,
|
||||
real_quote: real_quote,
|
||||
pool_state: pool_state,
|
||||
base_vault: base_vault,
|
||||
quote_vault: quote_vault,
|
||||
mint_token_program: base_token_program,
|
||||
platform_config: platform_config,
|
||||
platform_associated_account: platform_associated_account,
|
||||
creator_associated_account: creator_associated_account,
|
||||
global_config: global_config,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
usd1_pool: bool,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_address = crate::instruction::utils::bonk::get_pool_pda(
|
||||
mint,
|
||||
if usd1_pool {
|
||||
&crate::constants::USD1_TOKEN_ACCOUNT
|
||||
} else {
|
||||
&crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let pool_data =
|
||||
crate::instruction::utils::bonk::fetch_pool_state(rpc, &pool_address).await?;
|
||||
let token_account = rpc.get_account(&pool_data.base_mint).await?;
|
||||
let platform_associated_account =
|
||||
crate::instruction::utils::bonk::get_platform_associated_account(
|
||||
&pool_data.platform_config,
|
||||
);
|
||||
let creator_associated_account =
|
||||
crate::instruction::utils::bonk::get_creator_associated_account(&pool_data.creator);
|
||||
let platform_associated_account = platform_associated_account.unwrap();
|
||||
let creator_associated_account = creator_associated_account.unwrap();
|
||||
Ok(Self {
|
||||
virtual_base: pool_data.virtual_base as u128,
|
||||
virtual_quote: pool_data.virtual_quote as u128,
|
||||
real_base: pool_data.real_base as u128,
|
||||
real_quote: pool_data.real_quote as u128,
|
||||
pool_state: pool_address,
|
||||
base_vault: pool_data.base_vault,
|
||||
quote_vault: pool_data.quote_vault,
|
||||
mint_token_program: token_account.owner,
|
||||
platform_config: pool_data.platform_config,
|
||||
platform_associated_account,
|
||||
creator_associated_account,
|
||||
global_config: pool_data.global_config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// RaydiumCpmm protocol specific parameters
|
||||
/// Configuration parameters specific to Raydium CPMM trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct RaydiumCpmmParams {
|
||||
/// Pool address
|
||||
pub pool_state: Pubkey,
|
||||
/// Amm config address
|
||||
pub amm_config: Pubkey,
|
||||
/// Base token mint address
|
||||
pub base_mint: Pubkey,
|
||||
/// Quote token mint address
|
||||
pub quote_mint: Pubkey,
|
||||
/// Base token reserve amount in the pool
|
||||
pub base_reserve: u64,
|
||||
/// Quote token reserve amount in the pool
|
||||
pub quote_reserve: u64,
|
||||
/// Base token vault address
|
||||
pub base_vault: Pubkey,
|
||||
/// Quote token vault address
|
||||
pub quote_vault: Pubkey,
|
||||
/// Base token program ID
|
||||
pub base_token_program: Pubkey,
|
||||
/// Quote token program ID
|
||||
pub quote_token_program: Pubkey,
|
||||
/// Observation state account
|
||||
pub observation_state: Pubkey,
|
||||
}
|
||||
|
||||
impl RaydiumCpmmParams {
|
||||
pub fn from_trade(
|
||||
pool_state: Pubkey,
|
||||
amm_config: Pubkey,
|
||||
input_token_mint: Pubkey,
|
||||
output_token_mint: Pubkey,
|
||||
input_vault: Pubkey,
|
||||
output_vault: Pubkey,
|
||||
input_token_program: Pubkey,
|
||||
output_token_program: Pubkey,
|
||||
observation_state: Pubkey,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
pool_state: pool_state,
|
||||
amm_config: amm_config,
|
||||
base_mint: input_token_mint,
|
||||
quote_mint: output_token_mint,
|
||||
base_reserve: base_reserve,
|
||||
quote_reserve: quote_reserve,
|
||||
base_vault: input_vault,
|
||||
quote_vault: output_vault,
|
||||
base_token_program: input_token_program,
|
||||
quote_token_program: output_token_program,
|
||||
observation_state: observation_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool =
|
||||
crate::instruction::utils::raydium_cpmm::fetch_pool_state(rpc, pool_address).await?;
|
||||
let (token0_balance, token1_balance) =
|
||||
crate::instruction::utils::raydium_cpmm::get_pool_token_balances(
|
||||
rpc,
|
||||
pool_address,
|
||||
&pool.token0_mint,
|
||||
&pool.token1_mint,
|
||||
)
|
||||
.await?;
|
||||
Ok(Self {
|
||||
pool_state: *pool_address,
|
||||
amm_config: pool.amm_config,
|
||||
base_mint: pool.token0_mint,
|
||||
quote_mint: pool.token1_mint,
|
||||
base_reserve: token0_balance,
|
||||
quote_reserve: token1_balance,
|
||||
base_vault: pool.token0_vault,
|
||||
quote_vault: pool.token1_vault,
|
||||
base_token_program: pool.token0_program,
|
||||
quote_token_program: pool.token1_program,
|
||||
observation_state: pool.observation_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// RaydiumCpmm protocol specific parameters
|
||||
/// Configuration parameters specific to Raydium CPMM trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct RaydiumAmmV4Params {
|
||||
/// AMM pool address
|
||||
pub amm: Pubkey,
|
||||
/// Base token (coin) mint address
|
||||
pub coin_mint: Pubkey,
|
||||
/// Quote token (pc) mint address
|
||||
pub pc_mint: Pubkey,
|
||||
/// Pool's coin token account address
|
||||
pub token_coin: Pubkey,
|
||||
/// Pool's pc token account address
|
||||
pub token_pc: Pubkey,
|
||||
/// Current coin reserve amount in the pool
|
||||
pub coin_reserve: u64,
|
||||
/// Current pc reserve amount in the pool
|
||||
pub pc_reserve: u64,
|
||||
}
|
||||
|
||||
impl RaydiumAmmV4Params {
|
||||
pub fn new(
|
||||
amm: Pubkey,
|
||||
coin_mint: Pubkey,
|
||||
pc_mint: Pubkey,
|
||||
token_coin: Pubkey,
|
||||
token_pc: Pubkey,
|
||||
coin_reserve: u64,
|
||||
pc_reserve: u64,
|
||||
) -> Self {
|
||||
Self { amm, coin_mint, pc_mint, token_coin, token_pc, coin_reserve, pc_reserve }
|
||||
}
|
||||
pub async fn from_amm_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
amm: Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let amm_info = crate::instruction::utils::raydium_amm_v4::fetch_amm_info(rpc, amm).await?;
|
||||
let (coin_reserve, pc_reserve) =
|
||||
get_multi_token_balances(rpc, &amm_info.token_coin, &amm_info.token_pc).await?;
|
||||
Ok(Self {
|
||||
amm,
|
||||
coin_mint: amm_info.coin_mint,
|
||||
pc_mint: amm_info.pc_mint,
|
||||
token_coin: amm_info.token_coin,
|
||||
token_pc: amm_info.token_pc,
|
||||
coin_reserve,
|
||||
pc_reserve,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// MeteoraDammV2 protocol specific parameters
|
||||
/// Configuration parameters specific to Meteora Damm V2 trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct MeteoraDammV2Params {
|
||||
pub pool: Pubkey,
|
||||
pub token_a_vault: Pubkey,
|
||||
pub token_b_vault: Pubkey,
|
||||
pub token_a_mint: Pubkey,
|
||||
pub token_b_mint: Pubkey,
|
||||
pub token_a_program: Pubkey,
|
||||
pub token_b_program: Pubkey,
|
||||
}
|
||||
|
||||
impl MeteoraDammV2Params {
|
||||
pub fn new(
|
||||
pool: Pubkey,
|
||||
token_a_vault: Pubkey,
|
||||
token_b_vault: Pubkey,
|
||||
token_a_mint: Pubkey,
|
||||
token_b_mint: Pubkey,
|
||||
token_a_program: Pubkey,
|
||||
token_b_program: Pubkey,
|
||||
) -> Self {
|
||||
Self {
|
||||
pool,
|
||||
token_a_vault,
|
||||
token_b_vault,
|
||||
token_a_mint,
|
||||
token_b_mint,
|
||||
token_a_program,
|
||||
token_b_program,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_data =
|
||||
crate::instruction::utils::meteora_damm_v2::fetch_pool(rpc, pool_address).await?;
|
||||
let mint_accounts = rpc
|
||||
.get_multiple_accounts(&[pool_data.token_a_mint, pool_data.token_b_mint])
|
||||
.await?;
|
||||
let token_a_program = mint_accounts
|
||||
.get(0)
|
||||
.and_then(|a| a.as_ref())
|
||||
.map(|a| a.owner)
|
||||
.ok_or_else(|| anyhow::anyhow!("Token A mint account not found"))?;
|
||||
let token_b_program = mint_accounts
|
||||
.get(1)
|
||||
.and_then(|a| a.as_ref())
|
||||
.map(|a| a.owner)
|
||||
.ok_or_else(|| anyhow::anyhow!("Token B mint account not found"))?;
|
||||
Ok(Self {
|
||||
pool: *pool_address,
|
||||
token_a_vault: pool_data.token_a_vault,
|
||||
token_b_vault: pool_data.token_b_vault,
|
||||
token_a_mint: pool_data.token_a_mint,
|
||||
token_b_mint: pool_data.token_b_mint,
|
||||
token_a_program,
|
||||
token_b_program,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
use crate::common::SolanaRpcClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// Bonk protocol specific parameters
|
||||
/// Configuration parameters specific to Bonk trading protocol
|
||||
#[derive(Clone, Default)]
|
||||
pub struct BonkParams {
|
||||
pub virtual_base: u128,
|
||||
pub virtual_quote: u128,
|
||||
pub real_base: u128,
|
||||
pub real_quote: u128,
|
||||
pub pool_state: Pubkey,
|
||||
pub base_vault: Pubkey,
|
||||
pub quote_vault: Pubkey,
|
||||
/// Token program ID
|
||||
pub mint_token_program: Pubkey,
|
||||
pub platform_config: Pubkey,
|
||||
pub platform_associated_account: Pubkey,
|
||||
pub creator_associated_account: Pubkey,
|
||||
pub global_config: Pubkey,
|
||||
}
|
||||
|
||||
impl BonkParams {
|
||||
pub fn immediate_sell(
|
||||
mint_token_program: Pubkey,
|
||||
platform_config: Pubkey,
|
||||
platform_associated_account: Pubkey,
|
||||
creator_associated_account: Pubkey,
|
||||
global_config: Pubkey,
|
||||
) -> Self {
|
||||
Self {
|
||||
mint_token_program,
|
||||
platform_config,
|
||||
platform_associated_account,
|
||||
creator_associated_account,
|
||||
global_config,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn from_trade(
|
||||
virtual_base: u64,
|
||||
virtual_quote: u64,
|
||||
real_base_after: u64,
|
||||
real_quote_after: u64,
|
||||
pool_state: Pubkey,
|
||||
base_vault: Pubkey,
|
||||
quote_vault: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
platform_config: Pubkey,
|
||||
platform_associated_account: Pubkey,
|
||||
creator_associated_account: Pubkey,
|
||||
global_config: Pubkey,
|
||||
) -> Self {
|
||||
Self {
|
||||
virtual_base: virtual_base as u128,
|
||||
virtual_quote: virtual_quote as u128,
|
||||
real_base: real_base_after as u128,
|
||||
real_quote: real_quote_after as u128,
|
||||
pool_state: pool_state,
|
||||
base_vault: base_vault,
|
||||
quote_vault: quote_vault,
|
||||
mint_token_program: base_token_program,
|
||||
platform_config: platform_config,
|
||||
platform_associated_account: platform_associated_account,
|
||||
creator_associated_account: creator_associated_account,
|
||||
global_config: global_config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_dev_trade(
|
||||
is_exact_in: bool,
|
||||
amount_in: u64,
|
||||
amount_out: u64,
|
||||
pool_state: Pubkey,
|
||||
base_vault: Pubkey,
|
||||
quote_vault: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
platform_config: Pubkey,
|
||||
platform_associated_account: Pubkey,
|
||||
creator_associated_account: Pubkey,
|
||||
global_config: Pubkey,
|
||||
) -> Self {
|
||||
const DEFAULT_VIRTUAL_BASE: u128 = 1073025605596382;
|
||||
const DEFAULT_VIRTUAL_QUOTE: u128 = 30000852951;
|
||||
let _amount_in = if is_exact_in {
|
||||
amount_in
|
||||
} else {
|
||||
crate::instruction::utils::bonk::get_amount_in(
|
||||
amount_out,
|
||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||
DEFAULT_VIRTUAL_BASE,
|
||||
DEFAULT_VIRTUAL_QUOTE,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
let real_quote = crate::instruction::utils::bonk::get_amount_in_net(
|
||||
amount_in,
|
||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||
) as u128;
|
||||
let _amount_out = if is_exact_in {
|
||||
crate::instruction::utils::bonk::get_amount_out(
|
||||
amount_in,
|
||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||
DEFAULT_VIRTUAL_BASE,
|
||||
DEFAULT_VIRTUAL_QUOTE,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
) as u128
|
||||
} else {
|
||||
amount_out as u128
|
||||
};
|
||||
let real_base = _amount_out;
|
||||
Self {
|
||||
virtual_base: DEFAULT_VIRTUAL_BASE,
|
||||
virtual_quote: DEFAULT_VIRTUAL_QUOTE,
|
||||
real_base: real_base,
|
||||
real_quote: real_quote,
|
||||
pool_state: pool_state,
|
||||
base_vault: base_vault,
|
||||
quote_vault: quote_vault,
|
||||
mint_token_program: base_token_program,
|
||||
platform_config: platform_config,
|
||||
platform_associated_account: platform_associated_account,
|
||||
creator_associated_account: creator_associated_account,
|
||||
global_config: global_config,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
usd1_pool: bool,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_address = crate::instruction::utils::bonk::get_pool_pda(
|
||||
mint,
|
||||
if usd1_pool {
|
||||
&crate::constants::USD1_TOKEN_ACCOUNT
|
||||
} else {
|
||||
&crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let pool_data =
|
||||
crate::instruction::utils::bonk::fetch_pool_state(rpc, &pool_address).await?;
|
||||
let token_account = rpc.get_account(&pool_data.base_mint).await?;
|
||||
let platform_associated_account =
|
||||
crate::instruction::utils::bonk::get_platform_associated_account(
|
||||
&pool_data.platform_config,
|
||||
);
|
||||
let creator_associated_account =
|
||||
crate::instruction::utils::bonk::get_creator_associated_account(&pool_data.creator);
|
||||
let platform_associated_account = platform_associated_account.unwrap();
|
||||
let creator_associated_account = creator_associated_account.unwrap();
|
||||
Ok(Self {
|
||||
virtual_base: pool_data.virtual_base as u128,
|
||||
virtual_quote: pool_data.virtual_quote as u128,
|
||||
real_base: pool_data.real_base as u128,
|
||||
real_quote: pool_data.real_quote as u128,
|
||||
pool_state: pool_address,
|
||||
base_vault: pool_data.base_vault,
|
||||
quote_vault: pool_data.quote_vault,
|
||||
mint_token_program: token_account.owner,
|
||||
platform_config: pool_data.platform_config,
|
||||
platform_associated_account,
|
||||
creator_associated_account,
|
||||
global_config: pool_data.global_config,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
use crate::common::nonce_cache::DurableNonceInfo;
|
||||
use crate::common::{GasFeeStrategy, SolanaRpcClient};
|
||||
use crate::swqos::{SwqosClient, TradeType};
|
||||
use crate::trading::MiddlewareManager;
|
||||
use core_affinity::CoreId;
|
||||
use solana_hash::Hash;
|
||||
use solana_message::AddressLookupTableAccount;
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::bonk::BonkParams;
|
||||
use super::meteora_damm_v2::MeteoraDammV2Params;
|
||||
use super::pumpfun::PumpFunParams;
|
||||
use super::pumpswap::PumpSwapParams;
|
||||
use super::raydium_amm_v4::RaydiumAmmV4Params;
|
||||
use super::raydium_cpmm::RaydiumCpmmParams;
|
||||
|
||||
/// Concurrency + core binding config for parallel submit (precomputed at SDK init, one param on hot path). Uses Arc so no borrow of SwapParams.
|
||||
#[derive(Clone)]
|
||||
pub struct SenderConcurrencyConfig {
|
||||
pub sender_thread_cores: Option<Arc<Vec<usize>>>,
|
||||
pub effective_core_ids: Arc<Vec<CoreId>>,
|
||||
pub max_sender_concurrency: usize,
|
||||
}
|
||||
|
||||
/// DEX 参数枚举 - 零开销抽象替代 Box<dyn ProtocolParams>
|
||||
#[derive(Clone)]
|
||||
pub enum DexParamEnum {
|
||||
PumpFun(PumpFunParams),
|
||||
PumpSwap(PumpSwapParams),
|
||||
Bonk(BonkParams),
|
||||
RaydiumCpmm(RaydiumCpmmParams),
|
||||
RaydiumAmmV4(RaydiumAmmV4Params),
|
||||
MeteoraDammV2(MeteoraDammV2Params),
|
||||
}
|
||||
|
||||
impl DexParamEnum {
|
||||
/// 获取内部参数的 Any 引用,用于向后兼容的类型检查
|
||||
#[inline]
|
||||
pub fn as_any(&self) -> &dyn std::any::Any {
|
||||
match self {
|
||||
DexParamEnum::PumpFun(p) => p,
|
||||
DexParamEnum::PumpSwap(p) => p,
|
||||
DexParamEnum::Bonk(p) => p,
|
||||
DexParamEnum::RaydiumCpmm(p) => p,
|
||||
DexParamEnum::RaydiumAmmV4(p) => p,
|
||||
DexParamEnum::MeteoraDammV2(p) => p,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Swap parameters
|
||||
#[derive(Clone)]
|
||||
pub struct SwapParams {
|
||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||
pub payer: Arc<Keypair>,
|
||||
pub trade_type: TradeType,
|
||||
pub input_mint: Pubkey,
|
||||
pub input_token_program: Option<Pubkey>,
|
||||
pub output_mint: Pubkey,
|
||||
pub output_token_program: Option<Pubkey>,
|
||||
pub input_amount: Option<u64>,
|
||||
pub slippage_basis_points: Option<u64>,
|
||||
pub address_lookup_table_account: Option<AddressLookupTableAccount>,
|
||||
pub recent_blockhash: Option<Hash>,
|
||||
pub wait_tx_confirmed: bool,
|
||||
pub protocol_params: DexParamEnum,
|
||||
pub open_seed_optimize: bool,
|
||||
/// Arc<Vec<..>> so cloning from infrastructure is a single Arc clone.
|
||||
pub swqos_clients: Arc<Vec<Arc<SwqosClient>>>,
|
||||
pub middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
pub durable_nonce: Option<DurableNonceInfo>,
|
||||
pub with_tip: bool,
|
||||
pub create_input_mint_ata: bool,
|
||||
pub close_input_mint_ata: bool,
|
||||
pub create_output_mint_ata: bool,
|
||||
pub close_output_mint_ata: bool,
|
||||
pub fixed_output_amount: Option<u64>,
|
||||
pub gas_fee_strategy: GasFeeStrategy,
|
||||
pub simulate: bool,
|
||||
/// Whether to output SDK logs (from TradeConfig.log_enabled).
|
||||
pub log_enabled: bool,
|
||||
/// Use dedicated sender threads (internal; set via client.with_dedicated_sender_threads()).
|
||||
pub use_dedicated_sender_threads: bool,
|
||||
/// Core indices for dedicated sender threads (from TradeConfig.sender_thread_cores). Arc avoids cloning the Vec on hot path.
|
||||
pub sender_thread_cores: Option<Arc<Vec<usize>>>,
|
||||
/// Precomputed at SDK init: min(swqos_count, 2/3*cores). Avoids get_core_ids() on trade hot path.
|
||||
pub max_sender_concurrency: usize,
|
||||
/// Precomputed at SDK init: first max_sender_concurrency CoreIds for job affinity. Arc clone only.
|
||||
pub effective_core_ids: Arc<Vec<CoreId>>,
|
||||
/// Whether to check minimum tip per SWQOS (from TradeConfig.check_min_tip). When false, skip filter for lower latency.
|
||||
pub check_min_tip: bool,
|
||||
/// Optional event receive time in microseconds (same scale as sol-parser-sdk clock::now_micros). Used as timing start when log_enabled.
|
||||
pub grpc_recv_us: Option<i64>,
|
||||
/// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).
|
||||
/// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens.
|
||||
/// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
|
||||
/// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes.
|
||||
pub use_exact_sol_amount: Option<bool>,
|
||||
}
|
||||
|
||||
impl SwapParams {
|
||||
/// One struct for execute_parallel: merges sender_thread_cores, effective_core_ids, max_sender_concurrency. Arc clone only.
|
||||
#[inline]
|
||||
pub fn sender_concurrency_config(&self) -> SenderConcurrencyConfig {
|
||||
SenderConcurrencyConfig {
|
||||
sender_thread_cores: self.sender_thread_cores.clone(),
|
||||
effective_core_ids: self.effective_core_ids.clone(),
|
||||
max_sender_concurrency: self.max_sender_concurrency,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SwapParams {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SwapParams: ...")
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
use crate::common::SolanaRpcClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// MeteoraDammV2 protocol specific parameters
|
||||
/// Configuration parameters specific to Meteora Damm V2 trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct MeteoraDammV2Params {
|
||||
pub pool: Pubkey,
|
||||
pub token_a_vault: Pubkey,
|
||||
pub token_b_vault: Pubkey,
|
||||
pub token_a_mint: Pubkey,
|
||||
pub token_b_mint: Pubkey,
|
||||
pub token_a_program: Pubkey,
|
||||
pub token_b_program: Pubkey,
|
||||
}
|
||||
|
||||
impl MeteoraDammV2Params {
|
||||
pub fn new(
|
||||
pool: Pubkey,
|
||||
token_a_vault: Pubkey,
|
||||
token_b_vault: Pubkey,
|
||||
token_a_mint: Pubkey,
|
||||
token_b_mint: Pubkey,
|
||||
token_a_program: Pubkey,
|
||||
token_b_program: Pubkey,
|
||||
) -> Self {
|
||||
Self {
|
||||
pool,
|
||||
token_a_vault,
|
||||
token_b_vault,
|
||||
token_a_mint,
|
||||
token_b_mint,
|
||||
token_a_program,
|
||||
token_b_program,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_data =
|
||||
crate::instruction::utils::meteora_damm_v2::fetch_pool(rpc, pool_address).await?;
|
||||
let mint_accounts = rpc
|
||||
.get_multiple_accounts(&[pool_data.token_a_mint, pool_data.token_b_mint])
|
||||
.await?;
|
||||
let token_a_program = mint_accounts
|
||||
.get(0)
|
||||
.and_then(|a| a.as_ref())
|
||||
.map(|a| a.owner)
|
||||
.ok_or_else(|| anyhow::anyhow!("Token A mint account not found"))?;
|
||||
let token_b_program = mint_accounts
|
||||
.get(1)
|
||||
.and_then(|a| a.as_ref())
|
||||
.map(|a| a.owner)
|
||||
.ok_or_else(|| anyhow::anyhow!("Token B mint account not found"))?;
|
||||
Ok(Self {
|
||||
pool: *pool_address,
|
||||
token_a_vault: pool_data.token_a_vault,
|
||||
token_b_vault: pool_data.token_b_vault,
|
||||
token_a_mint: pool_data.token_a_mint,
|
||||
token_b_mint: pool_data.token_b_mint,
|
||||
token_a_program,
|
||||
token_b_program,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
//! DEX protocol parameter types and [`SwapParams`].
|
||||
|
||||
mod bonk;
|
||||
mod dex_swap;
|
||||
mod meteora_damm_v2;
|
||||
mod pumpfun;
|
||||
mod pumpswap;
|
||||
mod raydium_amm_v4;
|
||||
mod raydium_cpmm;
|
||||
|
||||
pub use bonk::BonkParams;
|
||||
pub use dex_swap::{DexParamEnum, SenderConcurrencyConfig, SwapParams};
|
||||
pub use meteora_damm_v2::MeteoraDammV2Params;
|
||||
pub use pumpfun::PumpFunParams;
|
||||
pub use pumpswap::PumpSwapParams;
|
||||
pub use raydium_amm_v4::RaydiumAmmV4Params;
|
||||
pub use raydium_cpmm::RaydiumCpmmParams;
|
||||
@@ -1,240 +0,0 @@
|
||||
use crate::common::bonding_curve::BondingCurveAccount;
|
||||
use crate::common::spl_associated_token_account::get_associated_token_address_with_program_id;
|
||||
use crate::common::SolanaRpcClient;
|
||||
use crate::instruction::utils::pumpfun::reconcile_mayhem_mode_for_trade;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
/// PumpFun protocol specific parameters
|
||||
/// Configuration parameters specific to PumpFun trading protocol.
|
||||
///
|
||||
/// **Creator vault**: Pump buy/sell pass `creator_vault` = `PDA(["creator-vault", authority])`.
|
||||
/// Usually `authority` is [`BondingCurveAccount::creator`]; with **Creator Rewards Sharing** it is
|
||||
/// `fee_sharing_config_pda(mint)` (see [`fetch_fee_sharing_creator_vault_if_active`](crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active)).
|
||||
/// Keep `bonding_curve.creator` in sync with chain; ix building uses [`resolve_creator_vault_for_ix_with_fee_sharing`](crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing).
|
||||
#[derive(Clone)]
|
||||
pub struct PumpFunParams {
|
||||
pub bonding_curve: Arc<BondingCurveAccount>,
|
||||
pub associated_bonding_curve: Pubkey,
|
||||
/// Resolved by [`resolve_creator_vault_for_ix_with_fee_sharing`](crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing): ix vault when it matches `PDA(creator)`, fee-sharing vault, or RPC hint.
|
||||
pub creator_vault: Pubkey,
|
||||
/// `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 token_program: Pubkey,
|
||||
/// Whether to close token account when selling, only effective during sell operations
|
||||
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。
|
||||
/// `Pubkey::default()` 时按 mayhem 从静态池随机(与 npm 静态池一致,可能落后于主网 Global)。
|
||||
pub fee_recipient: Pubkey,
|
||||
}
|
||||
|
||||
impl PumpFunParams {
|
||||
pub fn immediate_sell(
|
||||
creator_vault: Pubkey,
|
||||
token_program: Pubkey,
|
||||
close_token_account_when_sell: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
bonding_curve: Arc::new(BondingCurveAccount { ..Default::default() }),
|
||||
associated_bonding_curve: Pubkey::default(),
|
||||
creator_vault: creator_vault,
|
||||
fee_sharing_creator_vault_if_active: None,
|
||||
token_program: token_program,
|
||||
close_token_account_when_sell: Some(close_token_account_when_sell),
|
||||
fee_recipient: Pubkey::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
||||
/// so that sell instructions include the correct remaining accounts for cashback.
|
||||
/// `mayhem_mode`: `Some` when known from Create/Trade event (`is_mayhem_mode` / `mayhem_mode`).
|
||||
/// `None` falls back to detecting Mayhem via reserved fee recipient pubkeys only (not AMM protocol fee accounts).
|
||||
pub fn from_dev_trade(
|
||||
mint: Pubkey,
|
||||
token_amount: u64,
|
||||
max_sol_cost: u64,
|
||||
creator: Pubkey,
|
||||
bonding_curve: Pubkey,
|
||||
associated_bonding_curve: Pubkey,
|
||||
creator_vault: Pubkey,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
fee_recipient: Pubkey,
|
||||
token_program: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
mayhem_mode: Option<bool>,
|
||||
) -> Self {
|
||||
let is_mayhem_mode = reconcile_mayhem_mode_for_trade(mayhem_mode, &fee_recipient);
|
||||
let bonding_curve_account = BondingCurveAccount::from_dev_trade(
|
||||
bonding_curve,
|
||||
&mint,
|
||||
token_amount,
|
||||
max_sol_cost,
|
||||
creator,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
);
|
||||
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve_account.creator,
|
||||
creator_vault,
|
||||
&mint,
|
||||
None,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve_account.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve_account),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
creator_vault: creator_vault_resolved,
|
||||
fee_sharing_creator_vault_if_active: None,
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
token_program: token_program,
|
||||
fee_recipient,
|
||||
}
|
||||
}
|
||||
|
||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
||||
/// so that sell instructions include the correct remaining accounts for cashback.
|
||||
///
|
||||
/// `mayhem_mode`:
|
||||
/// - **`Some(v)`**:优先采用 gRPC / `tradeEvent`,但与 **`fee_recipient` 所属池**(Mayhem vs 普通,见 pump-public-docs)不一致时,以 fee 地址为准纠偏,避免链上 `NotAuthorized`。
|
||||
/// - **`None`**:用 `fee_recipient` 是否落在 Mayhem 静态列表推断。
|
||||
pub fn from_trade(
|
||||
bonding_curve: Pubkey,
|
||||
associated_bonding_curve: Pubkey,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
creator_vault: Pubkey,
|
||||
virtual_token_reserves: u64,
|
||||
virtual_sol_reserves: u64,
|
||||
real_token_reserves: u64,
|
||||
real_sol_reserves: u64,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
fee_recipient: Pubkey,
|
||||
token_program: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
mayhem_mode: Option<bool>,
|
||||
) -> Self {
|
||||
let is_mayhem_mode = reconcile_mayhem_mode_for_trade(mayhem_mode, &fee_recipient);
|
||||
let bonding_curve = BondingCurveAccount::from_trade(
|
||||
bonding_curve,
|
||||
mint,
|
||||
creator,
|
||||
virtual_token_reserves,
|
||||
virtual_sol_reserves,
|
||||
real_token_reserves,
|
||||
real_sol_reserves,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
);
|
||||
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve.creator,
|
||||
creator_vault,
|
||||
&mint,
|
||||
None,
|
||||
)
|
||||
.or_else(|| {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
creator_vault: creator_vault_resolved,
|
||||
fee_sharing_creator_vault_if_active: None,
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
token_program: token_program,
|
||||
fee_recipient,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let account =
|
||||
crate::instruction::utils::pumpfun::fetch_bonding_curve_account(rpc, mint).await?;
|
||||
let mint_account = rpc.get_account(&mint).await?;
|
||||
let bonding_curve = BondingCurveAccount {
|
||||
discriminator: 0,
|
||||
account: account.1,
|
||||
virtual_token_reserves: account.0.virtual_token_reserves,
|
||||
virtual_sol_reserves: account.0.virtual_sol_reserves,
|
||||
real_token_reserves: account.0.real_token_reserves,
|
||||
real_sol_reserves: account.0.real_sol_reserves,
|
||||
token_total_supply: account.0.token_total_supply,
|
||||
complete: account.0.complete,
|
||||
creator: account.0.creator,
|
||||
is_mayhem_mode: account.0.is_mayhem_mode,
|
||||
is_cashback_coin: account.0.is_cashback_coin,
|
||||
};
|
||||
let associated_bonding_curve = get_associated_token_address_with_program_id(
|
||||
&bonding_curve.account,
|
||||
mint,
|
||||
&mint_account.owner,
|
||||
);
|
||||
let fee_sharing_creator_vault_if_active =
|
||||
crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active(rpc, mint)
|
||||
.await?;
|
||||
let creator_vault = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&bonding_curve.creator,
|
||||
Pubkey::default(),
|
||||
mint,
|
||||
fee_sharing_creator_vault_if_active,
|
||||
)
|
||||
.or_else(|| crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator))
|
||||
.unwrap_or_default();
|
||||
Ok(Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
creator_vault,
|
||||
fee_sharing_creator_vault_if_active,
|
||||
close_token_account_when_sell: None,
|
||||
token_program: mint_account.owner,
|
||||
fee_recipient: Pubkey::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// One `getAccount` on pump-fees `SharingConfig` + re-resolves [`Self::creator_vault`]. Call before sell
|
||||
/// when params come from gRPC/cache so migrated fee-sharing mints do not hit Anchor 2006.
|
||||
pub async fn refresh_fee_sharing_creator_vault_from_rpc(
|
||||
mut self,
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
self.fee_sharing_creator_vault_if_active =
|
||||
crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active(rpc, mint)
|
||||
.await?;
|
||||
let c = self.bonding_curve.creator;
|
||||
if let Some(v) =
|
||||
crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
&c,
|
||||
self.creator_vault,
|
||||
mint,
|
||||
self.fee_sharing_creator_vault_if_active,
|
||||
)
|
||||
{
|
||||
self.creator_vault = v;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Updates the cached `creator_vault` field only. Buy/sell ix use [`BondingCurveAccount::creator`].
|
||||
#[inline]
|
||||
pub fn with_creator_vault(mut self, creator_vault: Pubkey) -> Self {
|
||||
self.creator_vault = creator_vault;
|
||||
self
|
||||
}
|
||||
|
||||
/// Override fee-sharing vault hint (e.g. from an off-chain indexer). `None` clears the hint.
|
||||
#[inline]
|
||||
pub fn with_fee_sharing_creator_vault_if_active(
|
||||
mut self,
|
||||
fee_sharing_creator_vault_if_active: Option<Pubkey>,
|
||||
) -> Self {
|
||||
self.fee_sharing_creator_vault_if_active = fee_sharing_creator_vault_if_active;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
use crate::common::spl_associated_token_account::get_associated_token_address_with_program_id;
|
||||
use crate::common::SolanaRpcClient;
|
||||
use crate::instruction::utils::pumpswap::accounts::MAYHEM_FEE_RECIPIENT as MAYHEM_FEE_RECIPIENT_SWAP;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// PumpSwap Protocol Specific Parameters
|
||||
///
|
||||
/// Parameters for configuring PumpSwap trading protocol, including liquidity pool information,
|
||||
/// token configuration, and transaction amounts.
|
||||
///
|
||||
/// **Performance Note**: If these parameters are not provided, the system will attempt to
|
||||
/// retrieve the relevant information from RPC, which will increase transaction time.
|
||||
/// For optimal performance, it is recommended to provide all necessary parameters in advance.
|
||||
#[derive(Clone)]
|
||||
pub struct PumpSwapParams {
|
||||
/// Liquidity pool address
|
||||
pub pool: Pubkey,
|
||||
/// Base token mint address
|
||||
/// The mint account address of the base token in the trading pair
|
||||
pub base_mint: Pubkey,
|
||||
/// Quote token mint address
|
||||
/// The mint account address of the quote token in the trading pair, usually SOL or USDC
|
||||
pub quote_mint: Pubkey,
|
||||
/// Pool base token account
|
||||
pub pool_base_token_account: Pubkey,
|
||||
/// Pool quote token account
|
||||
pub pool_quote_token_account: Pubkey,
|
||||
/// Base token reserves in the pool
|
||||
pub pool_base_token_reserves: u64,
|
||||
/// Quote token reserves in the pool
|
||||
pub pool_quote_token_reserves: u64,
|
||||
/// Coin creator vault ATA
|
||||
pub coin_creator_vault_ata: Pubkey,
|
||||
/// Coin creator vault authority
|
||||
pub coin_creator_vault_authority: Pubkey,
|
||||
/// Token program ID
|
||||
pub base_token_program: Pubkey,
|
||||
/// Quote token program ID
|
||||
pub quote_token_program: Pubkey,
|
||||
/// Whether the pool is in mayhem mode
|
||||
pub is_mayhem_mode: bool,
|
||||
/// Pool [`Pool::coin_creator`](crate::instruction::utils::pumpswap_types::Pool). Used for PumpSwap
|
||||
/// `remaining_accounts`: **`pool-v2` is appended only when this is not `Pubkey::default()`
|
||||
/// (matches `@pump-fun/pump-swap-sdk`); wrong flag causes buys to revert with buyback recipient errors (e.g. 6053).
|
||||
pub coin_creator: Pubkey,
|
||||
/// Whether the pool's coin has cashback enabled
|
||||
pub is_cashback_coin: bool,
|
||||
/// Cashback fee in basis points (from trade events / sol-parser-sdk). For quote-in buy and base-in sell
|
||||
/// math, this is summed with [`COIN_CREATOR_FEE_BASIS_POINTS`](crate::instruction::utils::pumpswap::accounts::COIN_CREATOR_FEE_BASIS_POINTS)
|
||||
/// when a creator vault applies — matching on-chain treating creator + cashback as one fee bucket.
|
||||
/// Use `0` when unknown (e.g. RPC-only pool decode has no per-mint cashback bps).
|
||||
pub cashback_fee_basis_points: u64,
|
||||
}
|
||||
|
||||
impl PumpSwapParams {
|
||||
pub fn new(
|
||||
pool: Pubkey,
|
||||
base_mint: Pubkey,
|
||||
quote_mint: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
coin_creator_vault_ata: Pubkey,
|
||||
coin_creator_vault_authority: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
quote_token_program: Pubkey,
|
||||
fee_recipient: Pubkey,
|
||||
coin_creator: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Self {
|
||||
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT_SWAP;
|
||||
Self {
|
||||
pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
base_token_program,
|
||||
quote_token_program,
|
||||
is_mayhem_mode,
|
||||
coin_creator,
|
||||
is_cashback_coin,
|
||||
cashback_fee_basis_points,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fast-path constructor for building PumpSwap parameters directly from decoded
|
||||
/// trade/event data and the accompanying instruction accounts, avoiding RPC
|
||||
/// lookups and associated latency. Token program IDs should be sourced from
|
||||
/// the instruction accounts themselves to respect Token Program vs Token-2022
|
||||
/// differences.
|
||||
///
|
||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin`
|
||||
/// from the event so that buy/sell instructions include the correct remaining
|
||||
/// accounts for cashback.
|
||||
pub fn from_trade(
|
||||
pool: Pubkey,
|
||||
base_mint: Pubkey,
|
||||
quote_mint: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
coin_creator_vault_ata: Pubkey,
|
||||
coin_creator_vault_authority: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
quote_token_program: Pubkey,
|
||||
fee_recipient: Pubkey,
|
||||
coin_creator: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
base_token_program,
|
||||
quote_token_program,
|
||||
fee_recipient,
|
||||
coin_creator,
|
||||
is_cashback_coin,
|
||||
cashback_fee_basis_points,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
if let Ok((pool_address, _)) =
|
||||
crate::instruction::utils::pumpswap::find_by_base_mint(rpc, mint).await
|
||||
{
|
||||
Self::from_pool_address_by_rpc(rpc, &pool_address).await
|
||||
} else if let Ok((pool_address, _)) =
|
||||
crate::instruction::utils::pumpswap::find_by_quote_mint(rpc, mint).await
|
||||
{
|
||||
Self::from_pool_address_by_rpc(rpc, &pool_address).await
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("No pool found for mint"));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool_data = crate::instruction::utils::pumpswap::fetch_pool(rpc, pool_address).await?;
|
||||
Self::from_pool_data(rpc, pool_address, &pool_data).await
|
||||
}
|
||||
|
||||
/// Build params from an already-decoded Pool, only fetching token balances.
|
||||
///
|
||||
/// Saves 1 RPC `getAccount` call vs `from_pool_address_by_rpc` when pool data
|
||||
/// is already available (e.g. from `pumpswap::find_by_mint` which returns the
|
||||
/// decoded Pool).
|
||||
pub async fn from_pool_data(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
pool_data: &crate::instruction::utils::pumpswap_types::Pool,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let (pool_base_token_reserves, pool_quote_token_reserves) =
|
||||
crate::instruction::utils::pumpswap::get_token_balances(pool_data, rpc).await?;
|
||||
let creator = pool_data.coin_creator;
|
||||
let coin_creator_vault_ata = crate::instruction::utils::pumpswap::coin_creator_vault_ata(
|
||||
creator,
|
||||
pool_data.quote_mint,
|
||||
);
|
||||
let coin_creator_vault_authority =
|
||||
crate::instruction::utils::pumpswap::coin_creator_vault_authority(creator);
|
||||
|
||||
let base_token_program_ata = get_associated_token_address_with_program_id(
|
||||
pool_address,
|
||||
&pool_data.base_mint,
|
||||
&crate::constants::TOKEN_PROGRAM,
|
||||
);
|
||||
let quote_token_program_ata = get_associated_token_address_with_program_id(
|
||||
pool_address,
|
||||
&pool_data.quote_mint,
|
||||
&crate::constants::TOKEN_PROGRAM,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
pool: *pool_address,
|
||||
base_mint: pool_data.base_mint,
|
||||
quote_mint: pool_data.quote_mint,
|
||||
pool_base_token_account: pool_data.pool_base_token_account,
|
||||
pool_quote_token_account: pool_data.pool_quote_token_account,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
base_token_program: if pool_data.pool_base_token_account == base_token_program_ata {
|
||||
crate::constants::TOKEN_PROGRAM
|
||||
} else {
|
||||
crate::constants::TOKEN_PROGRAM_2022
|
||||
},
|
||||
is_cashback_coin: pool_data.is_cashback_coin,
|
||||
quote_token_program: if pool_data.pool_quote_token_account == quote_token_program_ata {
|
||||
crate::constants::TOKEN_PROGRAM
|
||||
} else {
|
||||
crate::constants::TOKEN_PROGRAM_2022
|
||||
},
|
||||
is_mayhem_mode: pool_data.is_mayhem_mode,
|
||||
coin_creator: pool_data.coin_creator,
|
||||
cashback_fee_basis_points: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use crate::common::SolanaRpcClient;
|
||||
use crate::trading::common::get_multi_token_balances;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// RaydiumCpmm protocol specific parameters
|
||||
/// Configuration parameters specific to Raydium CPMM trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct RaydiumAmmV4Params {
|
||||
/// AMM pool address
|
||||
pub amm: Pubkey,
|
||||
/// Base token (coin) mint address
|
||||
pub coin_mint: Pubkey,
|
||||
/// Quote token (pc) mint address
|
||||
pub pc_mint: Pubkey,
|
||||
/// Pool's coin token account address
|
||||
pub token_coin: Pubkey,
|
||||
/// Pool's pc token account address
|
||||
pub token_pc: Pubkey,
|
||||
/// Current coin reserve amount in the pool
|
||||
pub coin_reserve: u64,
|
||||
/// Current pc reserve amount in the pool
|
||||
pub pc_reserve: u64,
|
||||
}
|
||||
|
||||
impl RaydiumAmmV4Params {
|
||||
pub fn new(
|
||||
amm: Pubkey,
|
||||
coin_mint: Pubkey,
|
||||
pc_mint: Pubkey,
|
||||
token_coin: Pubkey,
|
||||
token_pc: Pubkey,
|
||||
coin_reserve: u64,
|
||||
pc_reserve: u64,
|
||||
) -> Self {
|
||||
Self { amm, coin_mint, pc_mint, token_coin, token_pc, coin_reserve, pc_reserve }
|
||||
}
|
||||
pub async fn from_amm_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
amm: Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let amm_info = crate::instruction::utils::raydium_amm_v4::fetch_amm_info(rpc, amm).await?;
|
||||
let (coin_reserve, pc_reserve) =
|
||||
get_multi_token_balances(rpc, &amm_info.token_coin, &amm_info.token_pc).await?;
|
||||
Ok(Self {
|
||||
amm,
|
||||
coin_mint: amm_info.coin_mint,
|
||||
pc_mint: amm_info.pc_mint,
|
||||
token_coin: amm_info.token_coin,
|
||||
token_pc: amm_info.token_pc,
|
||||
coin_reserve,
|
||||
pc_reserve,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
use crate::common::SolanaRpcClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// RaydiumCpmm protocol specific parameters
|
||||
/// Configuration parameters specific to Raydium CPMM trading protocol
|
||||
#[derive(Clone)]
|
||||
pub struct RaydiumCpmmParams {
|
||||
/// Pool address
|
||||
pub pool_state: Pubkey,
|
||||
/// Amm config address
|
||||
pub amm_config: Pubkey,
|
||||
/// Base token mint address
|
||||
pub base_mint: Pubkey,
|
||||
/// Quote token mint address
|
||||
pub quote_mint: Pubkey,
|
||||
/// Base token reserve amount in the pool
|
||||
pub base_reserve: u64,
|
||||
/// Quote token reserve amount in the pool
|
||||
pub quote_reserve: u64,
|
||||
/// Base token vault address
|
||||
pub base_vault: Pubkey,
|
||||
/// Quote token vault address
|
||||
pub quote_vault: Pubkey,
|
||||
/// Base token program ID
|
||||
pub base_token_program: Pubkey,
|
||||
/// Quote token program ID
|
||||
pub quote_token_program: Pubkey,
|
||||
/// Observation state account
|
||||
pub observation_state: Pubkey,
|
||||
}
|
||||
|
||||
impl RaydiumCpmmParams {
|
||||
pub fn from_trade(
|
||||
pool_state: Pubkey,
|
||||
amm_config: Pubkey,
|
||||
input_token_mint: Pubkey,
|
||||
output_token_mint: Pubkey,
|
||||
input_vault: Pubkey,
|
||||
output_vault: Pubkey,
|
||||
input_token_program: Pubkey,
|
||||
output_token_program: Pubkey,
|
||||
observation_state: Pubkey,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
pool_state: pool_state,
|
||||
amm_config: amm_config,
|
||||
base_mint: input_token_mint,
|
||||
quote_mint: output_token_mint,
|
||||
base_reserve: base_reserve,
|
||||
quote_reserve: quote_reserve,
|
||||
base_vault: input_vault,
|
||||
quote_vault: output_vault,
|
||||
base_token_program: input_token_program,
|
||||
quote_token_program: output_token_program,
|
||||
observation_state: observation_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_pool_address_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
pool_address: &Pubkey,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let pool =
|
||||
crate::instruction::utils::raydium_cpmm::fetch_pool_state(rpc, pool_address).await?;
|
||||
let (token0_balance, token1_balance) =
|
||||
crate::instruction::utils::raydium_cpmm::get_pool_token_balances(
|
||||
rpc,
|
||||
pool_address,
|
||||
&pool.token0_mint,
|
||||
&pool.token1_mint,
|
||||
)
|
||||
.await?;
|
||||
Ok(Self {
|
||||
pool_state: *pool_address,
|
||||
amm_config: pool.amm_config,
|
||||
base_mint: pool.token0_mint,
|
||||
quote_mint: pool.token1_mint,
|
||||
base_reserve: token0_balance,
|
||||
quote_reserve: token1_balance,
|
||||
base_vault: pool.token0_vault,
|
||||
quote_vault: pool.token1_vault,
|
||||
base_token_program: pool.token0_program,
|
||||
quote_token_program: pool.token1_program,
|
||||
observation_state: pool.observation_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -72,9 +72,6 @@ pub const fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64
|
||||
/// * basis_points = 500 -> 5% slippage
|
||||
#[inline(always)]
|
||||
pub const fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
|
||||
if amount == 0 {
|
||||
return 0;
|
||||
}
|
||||
if amount <= basis_points / 10000 {
|
||||
1
|
||||
} else {
|
||||
|
||||
+12
-31
@@ -6,21 +6,6 @@ use crate::instruction::utils::pumpswap::accounts::{
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// Creator-side fee bps: fixed coin-creator fee when a creator vault applies, plus optional
|
||||
/// cashback fee bps for cashback-enabled coins (see Pump AMM / parser event field).
|
||||
#[inline]
|
||||
pub(crate) fn creator_side_fee_basis_points(
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> u64 {
|
||||
let creator_bps = if *coin_creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
COIN_CREATOR_FEE_BASIS_POINTS
|
||||
};
|
||||
creator_bps.saturating_add(cashback_fee_basis_points)
|
||||
}
|
||||
|
||||
/// Result for buying base tokens with base amount input
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BuyBaseInputResult {
|
||||
@@ -73,7 +58,6 @@ pub struct SellQuoteInputResult {
|
||||
/// * `base_reserve` - Base token reserves in the pool
|
||||
/// * `quote_reserve` - Quote token reserves in the pool
|
||||
/// * `coin_creator` - Token creator address
|
||||
/// * `cashback_fee_basis_points` - Extra fee bps for cashback coins (from on-chain / events); use `0` if unknown
|
||||
///
|
||||
/// # Returns
|
||||
/// * `BuyBaseInputResult` containing quote amounts and slippage calculations
|
||||
@@ -83,7 +67,6 @@ pub fn buy_base_input_internal(
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Result<BuyBaseInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
@@ -106,9 +89,11 @@ pub fn buy_base_input_internal(
|
||||
let lp_fee = compute_fee(quote_amount_in as u128, LP_FEE_BASIS_POINTS as u128) as u64;
|
||||
let protocol_fee =
|
||||
compute_fee(quote_amount_in as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
|
||||
let creator_bps =
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points) as u128;
|
||||
let coin_creator_fee = compute_fee(quote_amount_in as u128, creator_bps) as u64;
|
||||
let coin_creator_fee = if *coin_creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
compute_fee(quote_amount_in as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
|
||||
};
|
||||
let total_quote = quote_amount_in + lp_fee + protocol_fee + coin_creator_fee;
|
||||
|
||||
// Calculate max quote with slippage
|
||||
@@ -129,7 +114,6 @@ pub fn buy_base_input_internal(
|
||||
/// * `base_reserve` - Base token reserves in the pool
|
||||
/// * `quote_reserve` - Quote token reserves in the pool
|
||||
/// * `coin_creator` - Token creator address
|
||||
/// * `cashback_fee_basis_points` - Extra fee bps for cashback coins; use `0` if unknown
|
||||
///
|
||||
/// # Returns
|
||||
/// * `BuyQuoteInputResult` containing base amount and slippage calculations
|
||||
@@ -139,7 +123,6 @@ pub fn buy_quote_input_internal(
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Result<BuyQuoteInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
@@ -148,7 +131,7 @@ pub fn buy_quote_input_internal(
|
||||
// Calculate total fee basis points
|
||||
let total_fee_bps = LP_FEE_BASIS_POINTS
|
||||
+ PROTOCOL_FEE_BASIS_POINTS
|
||||
+ creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points);
|
||||
+ if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS };
|
||||
let denominator = 10_000 + total_fee_bps;
|
||||
|
||||
// Calculate effective quote amount after fees
|
||||
@@ -182,7 +165,6 @@ pub fn buy_quote_input_internal(
|
||||
/// * `base_reserve` - Base token reserves in the pool
|
||||
/// * `quote_reserve` - Quote token reserves in the pool
|
||||
/// * `coin_creator` - Token creator address
|
||||
/// * `cashback_fee_basis_points` - Extra fee bps for cashback coins; use `0` if unknown
|
||||
///
|
||||
/// # Returns
|
||||
/// * `SellBaseInputResult` containing quote amounts and slippage calculations
|
||||
@@ -192,7 +174,6 @@ pub fn sell_base_input_internal(
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Result<SellBaseInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
@@ -206,9 +187,11 @@ pub fn sell_base_input_internal(
|
||||
let lp_fee = compute_fee(quote_amount_out as u128, LP_FEE_BASIS_POINTS as u128) as u64;
|
||||
let protocol_fee =
|
||||
compute_fee(quote_amount_out as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
|
||||
let creator_bps =
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points) as u128;
|
||||
let coin_creator_fee = compute_fee(quote_amount_out as u128, creator_bps) as u64;
|
||||
let coin_creator_fee = if *coin_creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
compute_fee(quote_amount_out as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
|
||||
};
|
||||
|
||||
// Calculate final quote after fees
|
||||
let total_fees = lp_fee + protocol_fee + coin_creator_fee;
|
||||
@@ -251,7 +234,6 @@ fn calculate_quote_amount_out(
|
||||
/// * `base_reserve` - Base token reserves in the pool
|
||||
/// * `quote_reserve` - Quote token reserves in the pool
|
||||
/// * `coin_creator` - Token creator address
|
||||
/// * `cashback_fee_basis_points` - Extra fee bps for cashback coins; use `0` if unknown
|
||||
///
|
||||
/// # Returns
|
||||
/// * `SellQuoteInputResult` containing base amount and slippage calculations
|
||||
@@ -261,7 +243,6 @@ pub fn sell_quote_input_internal(
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Result<SellQuoteInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
@@ -275,7 +256,7 @@ pub fn sell_quote_input_internal(
|
||||
quote,
|
||||
LP_FEE_BASIS_POINTS,
|
||||
PROTOCOL_FEE_BASIS_POINTS,
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points),
|
||||
if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS },
|
||||
);
|
||||
|
||||
// Calculate base amount needed using inverse constant product formula
|
||||
|
||||
Reference in New Issue
Block a user