Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a048f3b6ad | |||
| feaac5ddd2 | |||
| 710a8d482f |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"url": "https://context7.com/0xfnzero/sol-trade-sdk",
|
||||
"public_key": "pk_ShleAZazFTUV8ORpmH4jy"
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
# Pump Cashback 集成说明
|
||||
|
||||
本 SDK 已支持 [Pump Cashback Rewards](https://github.com/pump-fun/pump-public-docs/blob/main/docs/PUMP_CASHBACK_README.md):在启用 cashback 的币种上交易时,用户可获得手续费返还而非支付给创作者。
|
||||
|
||||
## 行为概览
|
||||
|
||||
- **Bonding Curve (Pump)**
|
||||
- **Buy**:无需改指令,若币种启用 cashback 会自动累计。
|
||||
- **Sell**:当 `bonding_curve.is_cashback_coin == true` 时,SDK 会在指令中追加 `UserVolumeAccumulator` PDA(remaining account),用于累计可领取的 cashback。
|
||||
- **Pump Swap**
|
||||
- **Buy**:当 `PumpSwapParams.is_cashback_coin == true` 时,会追加 UserVolumeAccumulator 的 **WSOL ATA** 作为 remaining account。
|
||||
- **Sell**:当 `is_cashback_coin == true` 时,会追加 **WSOL ATA**(0th)和 **UserVolumeAccumulator PDA**(1st)作为 remaining accounts。
|
||||
|
||||
`PumpFunParams` 通过 `from_mint_by_rpc` 拉取 bonding curve 时会解析链上 `is_cashback_coin`;`PumpSwapParams` 通过 `from_pool_address_by_rpc` / `from_mint_by_rpc` 拉取 pool 时会解析 `is_cashback_coin`。
|
||||
|
||||
## 领取 Cashback
|
||||
|
||||
### 推荐:使用 TradingClient 一键领取
|
||||
|
||||
若已有 `TradingClient`(例如用于买卖的同一个客户端),可直接调用以下方法,内部会完成构建交易、签名与发送:
|
||||
|
||||
```rust
|
||||
// 领取 Pump 曲线产生的 Cashback(到账为 native SOL)
|
||||
let sig = client.claim_cashback_pumpfun().await?;
|
||||
|
||||
// 领取 PumpSwap 产生的 Cashback(到账为 WSOL,自动确保用户 WSOL ATA 存在)
|
||||
let sig = client.claim_cashback_pumpswap().await?;
|
||||
```
|
||||
|
||||
- **`claim_cashback_pumpfun()`**:领取 Bonding Curve (Pump) 的返还,到账为钱包 SOL。
|
||||
- **`claim_cashback_pumpswap()`**:领取 PumpSwap (AMM) 的返还,到账为用户的 WSOL ATA;若用户尚无 WSOL ATA 会先自动创建再领取。
|
||||
|
||||
### 仅构建指令(自行组交易时使用)
|
||||
|
||||
#### Bonding Curve (Pump)
|
||||
|
||||
将 native lamports 从 UserVolumeAccumulator 转到用户钱包:
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::instruction::pumpfun;
|
||||
|
||||
let ix = pumpfun::claim_cashback_pumpfun_instruction(&payer.pubkey());
|
||||
// 将 ix 放入交易并发送
|
||||
```
|
||||
|
||||
#### Pump Swap (AMM)
|
||||
|
||||
将 WSOL 从 UserVolumeAccumulator 的 WSOL ATA 转到用户的 WSOL ATA。**调用前需确保用户 WSOL ATA 已存在**(或使用上面的 `claim_cashback_pumpswap()` 会自动处理):
|
||||
|
||||
```rust
|
||||
use sol_trade_sdk::instruction::pumpswap;
|
||||
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
|
||||
use sol_trade_sdk::constants::TOKEN_PROGRAM;
|
||||
|
||||
let ix = pumpswap::claim_cashback_pumpswap_instruction(
|
||||
&payer.pubkey(),
|
||||
WSOL_TOKEN_ACCOUNT,
|
||||
TOKEN_PROGRAM,
|
||||
);
|
||||
```
|
||||
|
||||
## 读取未领取金额
|
||||
|
||||
- **Pump (Bonding Curve)**:读 Pump 程序的 `UserVolumeAccumulator` PDA 的 lamports,减去维持账户所需的 rent-exempt 金额,即为未领取 cashback(lamports)。
|
||||
- **Pump Swap**:读 Pump AMM 程序的 UserVolumeAccumulator 的 **WSOL ATA** 的 token balance,即为未领取 cashback(WSOL 数量)。
|
||||
|
||||
PDA 推导(本 SDK 已实现):
|
||||
|
||||
- Pump:`instruction::utils::pumpfun::get_user_volume_accumulator_pda(user)`
|
||||
- Pump AMM:`instruction::utils::pumpswap::get_user_volume_accumulator_pda(user)`,WSOL ATA:`instruction::utils::pumpswap::get_user_volume_accumulator_wsol_ata(user)`
|
||||
|
||||
## IDL 来源
|
||||
|
||||
根目录 `idl/` 下的 `pump.json`、`pump_amm.json`、`pump_fees.json` 从 [pump-fun/pump-public-docs](https://github.com/pump-fun/pump-public-docs) 的 `idl` 目录同步,便于与官方 IDL 对照和后续升级。
|
||||
+7098
File diff suppressed because it is too large
Load Diff
+6268
File diff suppressed because it is too large
Load Diff
+2761
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,8 @@ pub struct BondingCurveAccount {
|
||||
pub creator: Pubkey,
|
||||
/// Whether this is a mayhem mode token (Token2022)
|
||||
pub is_mayhem_mode: bool,
|
||||
/// Whether this coin has cashback enabled (creator fee redirected to users)
|
||||
pub is_cashback_coin: bool,
|
||||
}
|
||||
|
||||
impl BondingCurveAccount {
|
||||
@@ -87,6 +89,7 @@ impl BondingCurveAccount {
|
||||
complete: false,
|
||||
creator: creator,
|
||||
is_mayhem_mode: is_mayhem_mode,
|
||||
is_cashback_coin: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +119,7 @@ impl BondingCurveAccount {
|
||||
complete: false,
|
||||
creator: creator,
|
||||
is_mayhem_mode: is_mayhem_mode,
|
||||
is_cashback_coin: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ impl GasFeeStrategy {
|
||||
pub fn update_buy_tip(&self, buy_tip: f64) {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
for ((swqos_type, trade_type, strategy_type), value) in new_map.iter_mut() {
|
||||
for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
|
||||
if *trade_type == TradeType::Buy {
|
||||
value.tip = buy_tip;
|
||||
}
|
||||
@@ -314,7 +314,7 @@ impl GasFeeStrategy {
|
||||
pub fn update_sell_tip(&self, sell_tip: f64) {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
for ((swqos_type, trade_type, strategy_type), value) in new_map.iter_mut() {
|
||||
for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
|
||||
if *trade_type == TradeType::Sell {
|
||||
value.tip = sell_tip;
|
||||
}
|
||||
@@ -328,7 +328,7 @@ impl GasFeeStrategy {
|
||||
pub fn update_buy_cu_price(&self, buy_cu_price: u64) {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
for ((swqos_type, trade_type, strategy_type), value) in new_map.iter_mut() {
|
||||
for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
|
||||
if *trade_type == TradeType::Buy {
|
||||
value.cu_price = buy_cu_price;
|
||||
}
|
||||
@@ -342,7 +342,7 @@ impl GasFeeStrategy {
|
||||
pub fn update_sell_cu_price(&self, sell_cu_price: u64) {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
for ((swqos_type, trade_type, strategy_type), value) in new_map.iter_mut() {
|
||||
for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
|
||||
if *trade_type == TradeType::Sell {
|
||||
value.cu_price = sell_cu_price;
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
global_constants::FEE_RECIPIENT_META
|
||||
};
|
||||
|
||||
let accounts: [AccountMeta; 14] = [
|
||||
let mut accounts: Vec<AccountMeta> = vec![
|
||||
global_constants::GLOBAL_ACCOUNT_META,
|
||||
fee_recipient_meta,
|
||||
AccountMeta::new_readonly(params.input_mint, false),
|
||||
@@ -280,10 +280,17 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
accounts::FEE_PROGRAM_META,
|
||||
];
|
||||
|
||||
// Cashback: Bonding Curve Sell expects UserVolumeAccumulator PDA at 0th remaining account (writable)
|
||||
if bonding_curve.is_cashback_coin {
|
||||
let user_volume_accumulator =
|
||||
get_user_volume_accumulator_pda(¶ms.payer.pubkey()).unwrap();
|
||||
accounts.push(AccountMeta::new(user_volume_accumulator, false));
|
||||
}
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&sell_data,
|
||||
accounts.to_vec(),
|
||||
accounts,
|
||||
));
|
||||
|
||||
// Optional: Close token account
|
||||
@@ -302,3 +309,21 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
/// Claim cashback for Bonding Curve (Pump program). Transfers native lamports from UserVolumeAccumulator to user.
|
||||
pub fn claim_cashback_pumpfun_instruction(payer: &Pubkey) -> Option<Instruction> {
|
||||
const CLAIM_CASHBACK_DISCRIMINATOR: [u8; 8] = [37, 58, 35, 126, 190, 53, 228, 197];
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(payer)?;
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*payer, true), // user (signer, writable)
|
||||
AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable, not signer)
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
accounts::EVENT_AUTHORITY_META,
|
||||
accounts::PUMPFUN_META,
|
||||
];
|
||||
Some(Instruction::new_with_bytes(
|
||||
accounts::PUMPFUN,
|
||||
&CLAIM_CASHBACK_DISCRIMINATOR,
|
||||
accounts,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
constants::trade::trade::DEFAULT_SLIPPAGE,
|
||||
instruction::utils::pumpswap::{
|
||||
accounts, fee_recipient_ata, get_user_volume_accumulator_pda, BUY_DISCRIMINATOR,
|
||||
accounts, fee_recipient_ata, get_user_volume_accumulator_pda,
|
||||
get_user_volume_accumulator_wsol_ata, BUY_DISCRIMINATOR,
|
||||
BUY_EXACT_QUOTE_IN_DISCRIMINATOR, SELL_DISCRIMINATOR,
|
||||
},
|
||||
trading::{
|
||||
@@ -183,6 +184,12 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
}
|
||||
accounts.push(accounts::FEE_CONFIG_META);
|
||||
accounts.push(accounts::FEE_PROGRAM_META);
|
||||
// Cashback: remaining_accounts[0] = WSOL ATA of UserVolumeAccumulator (after named accounts per IDL)
|
||||
if protocol_params.is_cashback_coin {
|
||||
if let Some(wsol_ata) = get_user_volume_accumulator_wsol_ata(¶ms.payer.pubkey()) {
|
||||
accounts.push(AccountMeta::new(wsol_ata, false));
|
||||
}
|
||||
}
|
||||
|
||||
// Create instruction data
|
||||
let mut data = [0u8; 24];
|
||||
@@ -373,9 +380,18 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
accounts.push(accounts::FEE_CONFIG_META);
|
||||
accounts.push(accounts::FEE_PROGRAM_META);
|
||||
// Cashback: remaining_accounts[0] = WSOL ATA of UserVolumeAccumulator, remaining_accounts[1] = UserVolumeAccumulator PDA
|
||||
if protocol_params.is_cashback_coin {
|
||||
if let (Some(wsol_ata), Some(accumulator)) = (
|
||||
get_user_volume_accumulator_wsol_ata(¶ms.payer.pubkey()),
|
||||
get_user_volume_accumulator_pda(¶ms.payer.pubkey()),
|
||||
) {
|
||||
accounts.push(AccountMeta::new(wsol_ata, false));
|
||||
accounts.push(AccountMeta::new(accumulator, false));
|
||||
}
|
||||
}
|
||||
|
||||
// Create instruction data
|
||||
let mut data = [0u8; 24];
|
||||
@@ -420,3 +436,38 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
/// Claim cashback for PumpSwap (AMM). Transfers WSOL from UserVolumeAccumulator's WSOL ATA to user's WSOL ATA.
|
||||
/// Caller should ensure user's WSOL ATA exists (e.g. create idempotent ATA instruction) before this instruction.
|
||||
pub fn claim_cashback_pumpswap_instruction(
|
||||
payer: &Pubkey,
|
||||
quote_mint: Pubkey,
|
||||
quote_token_program: Pubkey,
|
||||
) -> Option<solana_sdk::instruction::Instruction> {
|
||||
const CLAIM_CASHBACK_DISCRIMINATOR: [u8; 8] = [37, 58, 35, 126, 190, 53, 228, 197];
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(payer)?;
|
||||
let user_volume_accumulator_wsol_ata = get_user_volume_accumulator_wsol_ata(payer)?;
|
||||
let user_wsol_ata = crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
payer,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
// IDL order: user, user_volume_accumulator, quote_mint, quote_token_program,
|
||||
// user_volume_accumulator_wsol_token_account, user_wsol_token_account, system_program, event_authority, program
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*payer, true), // user (signer, writable)
|
||||
AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable)
|
||||
AccountMeta::new_readonly(quote_mint, false),
|
||||
AccountMeta::new_readonly(quote_token_program, false),
|
||||
AccountMeta::new(user_volume_accumulator_wsol_ata, false), // writable
|
||||
AccountMeta::new(user_wsol_ata, false), // writable
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
accounts::EVENT_AUTHORITY_META,
|
||||
accounts::AMM_PROGRAM_META,
|
||||
];
|
||||
Some(solana_sdk::instruction::Instruction::new_with_bytes(
|
||||
accounts::AMM_PROGRAM,
|
||||
&CLAIM_CASHBACK_DISCRIMINATOR,
|
||||
accounts,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -185,6 +185,16 @@ pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Option<Pubkey> {
|
||||
)
|
||||
}
|
||||
|
||||
/// WSOL ATA of UserVolumeAccumulator for Pump AMM (used for cashback remaining accounts).
|
||||
pub fn get_user_volume_accumulator_wsol_ata(user: &Pubkey) -> Option<Pubkey> {
|
||||
let accumulator = get_user_volume_accumulator_pda(user)?;
|
||||
Some(crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&accumulator,
|
||||
&crate::constants::WSOL_TOKEN_ACCOUNT,
|
||||
&crate::constants::TOKEN_PROGRAM,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_global_volume_accumulator_pda() -> Option<Pubkey> {
|
||||
let seeds: &[&[u8]; 1] = &[&seeds::GLOBAL_VOLUME_ACCUMULATOR_SEED];
|
||||
let program_id: &Pubkey = &&accounts::AMM_PROGRAM;
|
||||
@@ -227,6 +237,7 @@ pub async fn find_by_base_mint(
|
||||
sort_results: None,
|
||||
};
|
||||
let program_id = accounts::AMM_PROGRAM;
|
||||
#[allow(deprecated)]
|
||||
let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?;
|
||||
if accounts.is_empty() {
|
||||
return Err(anyhow!("No pool found for mint {}", base_mint));
|
||||
@@ -277,6 +288,7 @@ pub async fn find_by_quote_mint(
|
||||
sort_results: None,
|
||||
};
|
||||
let program_id = accounts::AMM_PROGRAM;
|
||||
#[allow(deprecated)]
|
||||
let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?;
|
||||
if accounts.is_empty() {
|
||||
return Err(anyhow!("No pool found for mint {}", quote_mint));
|
||||
|
||||
@@ -15,9 +15,11 @@ pub struct Pool {
|
||||
pub lp_supply: u64,
|
||||
pub coin_creator: Pubkey,
|
||||
pub is_mayhem_mode: bool,
|
||||
/// Whether this pool's coin has cashback enabled
|
||||
pub is_cashback_coin: bool,
|
||||
}
|
||||
|
||||
pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1;
|
||||
pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1 + 1;
|
||||
|
||||
pub fn pool_decode(data: &[u8]) -> Option<Pool> {
|
||||
if data.len() < POOL_SIZE {
|
||||
|
||||
+128
-19
@@ -8,6 +8,7 @@ pub mod utils;
|
||||
use crate::common::nonce_cache::DurableNonceInfo;
|
||||
use crate::common::GasFeeStrategy;
|
||||
use crate::common::{TradeConfig, InfrastructureConfig};
|
||||
#[cfg(feature = "perf-trace")]
|
||||
use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
|
||||
use crate::constants::SOL_TOKEN_ACCOUNT;
|
||||
use crate::constants::USD1_TOKEN_ACCOUNT;
|
||||
@@ -286,7 +287,13 @@ impl TradingClient {
|
||||
crate::common::fast_fn::fast_init(&payer.pubkey());
|
||||
|
||||
if create_wsol_ata {
|
||||
Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await;
|
||||
// 在后台异步创建 WSOL ATA,不阻塞启动
|
||||
let payer_clone = payer.clone();
|
||||
let rpc_clone = infrastructure.rpc.clone();
|
||||
tokio::spawn(async move {
|
||||
Self::ensure_wsol_ata(&payer_clone, &rpc_clone).await;
|
||||
});
|
||||
println!("ℹ️ WSOL ATA 创建已在后台启动,不阻塞机器人启动");
|
||||
}
|
||||
|
||||
Self {
|
||||
@@ -309,6 +316,7 @@ impl TradingClient {
|
||||
match rpc.get_account(&wsol_ata).await {
|
||||
Ok(_) => {
|
||||
println!("✅ WSOL ATA已存在: {}", wsol_ata);
|
||||
return;
|
||||
}
|
||||
Err(_) => {
|
||||
println!("🔨 创建WSOL ATA: {}", wsol_ata);
|
||||
@@ -317,35 +325,87 @@ impl TradingClient {
|
||||
|
||||
if !create_ata_ixs.is_empty() {
|
||||
use solana_sdk::transaction::Transaction;
|
||||
let recent_blockhash = rpc.get_latest_blockhash().await.unwrap();
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
&create_ata_ixs,
|
||||
Some(&payer.pubkey()),
|
||||
&[payer.as_ref()],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
match rpc.send_and_confirm_transaction(&tx).await {
|
||||
Ok(signature) => {
|
||||
println!("✅ WSOL ATA创建成功: {}", signature);
|
||||
// 重试逻辑:最多尝试3次,每次超时10秒
|
||||
const MAX_RETRIES: usize = 3;
|
||||
const TIMEOUT_SECS: u64 = 10;
|
||||
let mut last_error = None;
|
||||
|
||||
for attempt in 1..=MAX_RETRIES {
|
||||
if attempt > 1 {
|
||||
println!("🔄 重试创建WSOL ATA (第{}/{}次)...", attempt, MAX_RETRIES);
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||
}
|
||||
Err(e) => {
|
||||
match rpc.get_account(&wsol_ata).await {
|
||||
Ok(_) => {
|
||||
|
||||
let recent_blockhash = match rpc.get_latest_blockhash().await {
|
||||
Ok(hash) => hash,
|
||||
Err(e) => {
|
||||
eprintln!("⚠️ 获取最新blockhash失败: {}", e);
|
||||
last_error = Some(format!("获取blockhash失败: {}", e));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
&create_ata_ixs,
|
||||
Some(&payer.pubkey()),
|
||||
&[payer.as_ref()],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
// 使用超时包装 send_and_confirm_transaction
|
||||
let send_result = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(TIMEOUT_SECS),
|
||||
rpc.send_and_confirm_transaction(&tx)
|
||||
).await;
|
||||
|
||||
match send_result {
|
||||
Ok(Ok(signature)) => {
|
||||
println!("✅ WSOL ATA创建成功: {}", signature);
|
||||
return;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
last_error = Some(format!("{}", e));
|
||||
|
||||
// 检查账户是否实际已存在
|
||||
if let Ok(_) = rpc.get_account(&wsol_ata).await {
|
||||
println!(
|
||||
"✅ WSOL ATA已存在(交易失败但账户存在): {}",
|
||||
wsol_ata
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(_) => {
|
||||
panic!(
|
||||
"❌ WSOL ATA创建失败且账户不存在: {}. 错误: {}",
|
||||
wsol_ata, e
|
||||
);
|
||||
|
||||
if attempt < MAX_RETRIES {
|
||||
eprintln!("⚠️ 第{}次尝试失败: {}", attempt, e);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
last_error = Some(format!("交易确认超时({}秒)", TIMEOUT_SECS));
|
||||
eprintln!("⚠️ 第{}次尝试超时", attempt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 所有重试都失败了
|
||||
if let Some(err) = last_error {
|
||||
eprintln!("❌ WSOL ATA创建失败(已重试{}次): {}", MAX_RETRIES, wsol_ata);
|
||||
eprintln!(" 错误详情: {}", err);
|
||||
eprintln!(" 💡 可能原因:");
|
||||
eprintln!(" 1. 钱包SOL余额不足(需要约0.002 SOL用于租金豁免)");
|
||||
eprintln!(" 2. RPC节点响应超时或网络拥堵");
|
||||
eprintln!(" 3. 交易费用不足");
|
||||
eprintln!(" 🔧 解决方案:");
|
||||
eprintln!(" 1. 给钱包充值至少0.1 SOL");
|
||||
eprintln!(" 2. 等待几秒后重试");
|
||||
eprintln!(" 3. 检查RPC节点连接");
|
||||
eprintln!(" ⚠️ 程序将在5秒后退出,请解决上述问题后重启");
|
||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
panic!(
|
||||
"❌ WSOL ATA创建失败且账户不存在: {}. 错误: {}",
|
||||
wsol_ata, err
|
||||
);
|
||||
}
|
||||
} else {
|
||||
println!("ℹ️ WSOL ATA已存在(无需创建)");
|
||||
}
|
||||
@@ -842,4 +902,53 @@ impl TradingClient {
|
||||
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
|
||||
Ok(signature.to_string())
|
||||
}
|
||||
|
||||
/// Claim Bonding Curve (Pump) cashback.
|
||||
///
|
||||
/// Transfers native SOL from the user's UserVolumeAccumulator to the wallet.
|
||||
/// If there is nothing to claim, the transaction may still succeed with no SOL transferred.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(String)` - Transaction signature
|
||||
/// * `Err(anyhow::Error)` - Build or send failure (e.g. invalid PDA)
|
||||
pub async fn claim_cashback_pumpfun(&self) -> Result<String, anyhow::Error> {
|
||||
use solana_sdk::transaction::Transaction;
|
||||
let ix = crate::instruction::pumpfun::claim_cashback_pumpfun_instruction(&self.payer.pubkey())
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to build PumpFun claim_cashback instruction"))?;
|
||||
let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
|
||||
let mut transaction = Transaction::new_with_payer(&[ix], Some(&self.payer.pubkey()));
|
||||
transaction.sign(&[&*self.payer], recent_blockhash);
|
||||
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
|
||||
Ok(signature.to_string())
|
||||
}
|
||||
|
||||
/// Claim PumpSwap (AMM) cashback.
|
||||
///
|
||||
/// Transfers WSOL from the UserVolumeAccumulator to the user's WSOL ATA.
|
||||
/// Creates the user's WSOL ATA idempotently if it does not exist, then claims.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(String)` - Transaction signature
|
||||
/// * `Err(anyhow::Error)` - Build or send failure
|
||||
pub async fn claim_cashback_pumpswap(&self) -> Result<String, anyhow::Error> {
|
||||
use solana_sdk::transaction::Transaction;
|
||||
let mut instructions = crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
|
||||
&self.payer.pubkey(),
|
||||
&self.payer.pubkey(),
|
||||
&WSOL_TOKEN_ACCOUNT,
|
||||
&crate::constants::TOKEN_PROGRAM,
|
||||
self.use_seed_optimize,
|
||||
);
|
||||
let ix = crate::instruction::pumpswap::claim_cashback_pumpswap_instruction(
|
||||
&self.payer.pubkey(),
|
||||
WSOL_TOKEN_ACCOUNT,
|
||||
crate::constants::TOKEN_PROGRAM,
|
||||
).ok_or_else(|| anyhow::anyhow!("Failed to build PumpSwap claim_cashback instruction"))?;
|
||||
instructions.push(ix);
|
||||
let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
|
||||
let mut transaction = Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
|
||||
transaction.sign(&[&*self.payer], recent_blockhash);
|
||||
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
|
||||
Ok(signature.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ impl BloxrouteClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec<VersionedTransaction>, wait_confirmation: bool) -> Result<()> {
|
||||
pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec<VersionedTransaction>, _wait_confirmation: bool) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
|
||||
let body = serde_json::json!({
|
||||
|
||||
@@ -11,14 +11,13 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
common::{nonce_cache::DurableNonceInfo, SolanaRpcClient},
|
||||
constants::swqos::NODE1_TIP_ACCOUNTS,
|
||||
trading::{MiddlewareManager, core::transaction_pool::{acquire_builder, release_builder}},
|
||||
};
|
||||
|
||||
/// Build standard RPC transaction
|
||||
pub async fn build_transaction(
|
||||
payer: Arc<Keypair>,
|
||||
rpc: Option<Arc<SolanaRpcClient>>,
|
||||
_rpc: Option<Arc<SolanaRpcClient>>,
|
||||
unit_limit: u32,
|
||||
unit_price: u64,
|
||||
business_instructions: Vec<Instruction>,
|
||||
|
||||
@@ -38,6 +38,7 @@ struct TaskResult {
|
||||
success: bool,
|
||||
signature: Signature,
|
||||
error: Option<anyhow::Error>,
|
||||
#[allow(dead_code)]
|
||||
swqos_type: SwqosType, // 🔧 增加:记录SWQOS类型
|
||||
landed_on_chain: bool, // 🔧 Whether tx landed on-chain (even if failed)
|
||||
}
|
||||
@@ -349,6 +350,7 @@ pub async fn execute_parallel(
|
||||
|
||||
let _send_start = Instant::now();
|
||||
let mut err: Option<anyhow::Error> = None;
|
||||
#[allow(unused_assignments)]
|
||||
let mut landed_on_chain = false;
|
||||
let success = match swqos_client
|
||||
.send_transaction(
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
perf::syscall_bypass::SystemCallBypassManager,
|
||||
trading::core::{
|
||||
async_executor::execute_parallel,
|
||||
execution::{ExecutionPath, InstructionProcessor, Prefetch},
|
||||
execution::{InstructionProcessor, Prefetch},
|
||||
traits::TradeExecutor,
|
||||
},
|
||||
trading::MiddlewareManager,
|
||||
|
||||
@@ -189,6 +189,7 @@ impl PumpFunParams {
|
||||
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,
|
||||
@@ -243,6 +244,8 @@ pub struct PumpSwapParams {
|
||||
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 {
|
||||
@@ -274,6 +277,7 @@ impl PumpSwapParams {
|
||||
base_token_program,
|
||||
quote_token_program,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +339,7 @@ impl PumpSwapParams {
|
||||
} 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 {
|
||||
|
||||
Reference in New Issue
Block a user