Merge commit '7fb4fd35d18bb0ec9c87d893958d4ac0c506b39e'
This commit is contained in:
@@ -0,0 +1,118 @@
|
|||||||
|
# Node1 最小小费金额限制
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
Node1 节点要求最小小费金额为 **0.002 SOL**,低于此金额的交易可能会被拒绝。
|
||||||
|
|
||||||
|
**重要**:这个限制**仅对 Node1** 生效,其他 swqos(Jito、BlockRazor、Astralane 等)不受影响。
|
||||||
|
|
||||||
|
sol-trade-sdk 已自动添加智能检测,只对 Node1 的 tip_account 应用最小小费金额检查。
|
||||||
|
|
||||||
|
## 实现位置
|
||||||
|
|
||||||
|
**文件**: `sol-trade-sdk/src/trading/common/transaction_builder.rs`
|
||||||
|
|
||||||
|
**修改内容**:
|
||||||
|
```rust
|
||||||
|
// Add tip transfer instruction
|
||||||
|
if with_tip && tip_amount > 0.0 {
|
||||||
|
// 🔧 Node1 最小小费金额限制:0.002 SOL(仅限 Node1)
|
||||||
|
const MIN_TIP_AMOUNT: f64 = 0.002;
|
||||||
|
|
||||||
|
// 检查是否是 Node1 的 tip_account
|
||||||
|
let is_node1 = NODE1_TIP_ACCOUNTS.iter().any(|&account| account == *tip_account);
|
||||||
|
|
||||||
|
let actual_tip_amount = if is_node1 && tip_amount < MIN_TIP_AMOUNT {
|
||||||
|
// Node1 要求最小 0.002 SOL
|
||||||
|
MIN_TIP_AMOUNT
|
||||||
|
} else {
|
||||||
|
// 其他 swqos 使用原始金额
|
||||||
|
tip_amount
|
||||||
|
};
|
||||||
|
|
||||||
|
instructions.push(transfer(
|
||||||
|
&payer.pubkey(),
|
||||||
|
tip_account,
|
||||||
|
sol_str_to_lamports(actual_tip_amount.to_string().as_str()).unwrap_or(0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|
1. **识别 Node1**: 检查 tip_account 是否属于 Node1 的账户列表
|
||||||
|
2. **条件检查**: 只对 Node1 且 `tip_amount < 0.002 SOL` 时才调整
|
||||||
|
3. **自动调整**: Node1 的小费金额提升到 `0.002 SOL`
|
||||||
|
4. **其他保持**: 其他 swqos 使用原始配置的小费金额
|
||||||
|
5. **透明处理**: 对上层调用者透明,无需修改配置
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### Node1: 配置的小费 < 0.002 SOL
|
||||||
|
```yaml
|
||||||
|
# config/app.prod.yaml
|
||||||
|
trading:
|
||||||
|
gas_fee:
|
||||||
|
global_buy_tip: 0.001 # 0.001 SOL(低于 Node1 最小值)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Node1 实际执行**: 自动调整为 `0.002 SOL`
|
||||||
|
**其他 swqos 实际执行**: 保持 `0.001 SOL`(不调整)
|
||||||
|
|
||||||
|
### Node1: 配置的小费 >= 0.002 SOL
|
||||||
|
```yaml
|
||||||
|
# config/app.prod.yaml
|
||||||
|
trading:
|
||||||
|
gas_fee:
|
||||||
|
global_buy_tip: 0.005 # 0.005 SOL(高于 Node1 最小值)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Node1 实际执行**: 使用 `0.005 SOL`(不调整)
|
||||||
|
**其他 swqos 实际执行**: 使用 `0.005 SOL`(不调整)
|
||||||
|
|
||||||
|
## 影响范围
|
||||||
|
|
||||||
|
这个修改**只影响使用 Node1** 的交易:
|
||||||
|
|
||||||
|
- ✅ Node1 买入交易(小费 < 0.002 时自动调整)
|
||||||
|
- ✅ Node1 卖出交易(小费 < 0.002 时自动调整)
|
||||||
|
- ❌ 其他 swqos 交易(保持原始小费金额)
|
||||||
|
|
||||||
|
## 日志示例
|
||||||
|
|
||||||
|
当小费金额被自动调整时,交易仍会正常执行,不会有额外日志输出。
|
||||||
|
|
||||||
|
这是因为调整是在 SDK 内部完成的,对调用者完全透明。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **最小值固定**: `MIN_TIP_AMOUNT = 0.002 SOL` 是硬编码的常量
|
||||||
|
2. **只增不减**: 只会向上调整小费,不会减少配置的小费金额
|
||||||
|
3. **Node1 专用**: 这个限制是 Node1 节点的要求
|
||||||
|
|
||||||
|
## 如果需要修改最小值
|
||||||
|
|
||||||
|
如果将来 Node1 调整最小小费要求,只需修改:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// 修改这个常量即可
|
||||||
|
const MIN_TIP_AMOUNT: f64 = 0.002; // 改为新的最小值
|
||||||
|
```
|
||||||
|
|
||||||
|
**文件位置**: `sol-trade-sdk/src/trading/common/transaction_builder.rs:49`
|
||||||
|
|
||||||
|
## 相关配置
|
||||||
|
|
||||||
|
主项目配置文件中的小费设置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# config/app.prod.yaml
|
||||||
|
trading:
|
||||||
|
gas_fee:
|
||||||
|
global_buy_tip: 0.001 # 买入小费(Node1 会自动调整为 0.002)
|
||||||
|
global_sell_tip: 0.0001 # 卖出小费(Node1 会自动调整为 0.002)
|
||||||
|
```
|
||||||
|
|
||||||
|
**建议**:
|
||||||
|
- 如果主要使用 Node1,可以直接配置为 `>= 0.002 SOL`
|
||||||
|
- 如果混合使用多个 swqos,配置任意值即可,SDK 会智能处理
|
||||||
@@ -100,7 +100,7 @@ pub fn _create_associated_token_account_idempotent_fast(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Only use seed if the mint address is not wSOL or SOL
|
// Only use seed if the mint address is not wSOL or SOL
|
||||||
// token 2022 测试不成功(TODO)
|
// 🔧 修复:Token-2022 也支持 seed 方式(白名单方式更安全)
|
||||||
if use_seed
|
if use_seed
|
||||||
&& !mint.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
|
&& !mint.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
|
||||||
&& !mint.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
|
&& !mint.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
|
||||||
@@ -236,7 +236,7 @@ fn _get_associated_token_address_with_program_id_fast(
|
|||||||
|
|
||||||
// Slow path: compute new ATA
|
// Slow path: compute new ATA
|
||||||
// Only use seed if the token mint address is not wSOL or SOL
|
// Only use seed if the token mint address is not wSOL or SOL
|
||||||
// token 2022 测试不成功(TODO)
|
// 🔧 修复:Token-2022 也支持 seed 方式(白名单方式更安全)
|
||||||
let ata = if use_seed
|
let ata = if use_seed
|
||||||
&& !token_mint_address.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
|
&& !token_mint_address.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
|
||||||
&& !token_mint_address.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
|
&& !token_mint_address.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
|
||||||
|
|||||||
+6
-7
@@ -74,9 +74,12 @@ pub fn create_associated_token_account_use_seed(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
|
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
|
||||||
|
// 🔧 修复:使用传入的 token_program 生成地址(支持 Token 和 Token-2022)
|
||||||
|
// 买入和卖出只要都使用事件中的 token_program,地址自然一致
|
||||||
let ata_like = Pubkey::create_with_seed(payer, seed, token_program)?;
|
let ata_like = Pubkey::create_with_seed(payer, seed, token_program)?;
|
||||||
|
|
||||||
let len = 165;
|
let len = 165;
|
||||||
|
// 但账户的 owner 仍然使用正确的 token_program(Token 或 Token-2022)
|
||||||
let create_acc =
|
let create_acc =
|
||||||
create_account_with_seed(payer, &ata_like, owner, seed, rent, len, token_program);
|
create_account_with_seed(payer, &ata_like, owner, seed, rent, len, token_program);
|
||||||
|
|
||||||
@@ -106,13 +109,9 @@ pub fn get_associated_token_address_with_program_id_use_seed(
|
|||||||
_ => b'a' + (nibble - 10),
|
_ => b'a' + (nibble - 10),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let is_2022_token = token_program_id == &crate::constants::TOKEN_PROGRAM_2022;
|
|
||||||
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
|
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
|
||||||
let token_program = if is_2022_token {
|
// 🔧 修复:使用传入的 token_program_id 生成地址(支持 Token 和 Token-2022)
|
||||||
&crate::constants::TOKEN_PROGRAM_2022
|
// 买入和卖出只要都使用事件中的 token_program_id,地址自然一致
|
||||||
} else {
|
let ata_like = Pubkey::create_with_seed(wallet_address, seed, token_program_id)?;
|
||||||
&crate::constants::TOKEN_PROGRAM
|
|
||||||
};
|
|
||||||
let ata_like = Pubkey::create_with_seed(wallet_address, seed, token_program)?;
|
|
||||||
Ok(ata_like)
|
Ok(ata_like)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,11 +200,13 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
data[16..24].copy_from_slice(&token_amount.to_le_bytes());
|
data[16..24].copy_from_slice(&token_amount.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
instructions.push(Instruction {
|
let buy_instruction = Instruction {
|
||||||
program_id: accounts::AMM_PROGRAM,
|
program_id: accounts::AMM_PROGRAM,
|
||||||
accounts,
|
accounts: accounts.clone(),
|
||||||
data: data.to_vec(),
|
data: data.to_vec(),
|
||||||
});
|
};
|
||||||
|
|
||||||
|
instructions.push(buy_instruction);
|
||||||
if close_wsol_ata {
|
if close_wsol_ata {
|
||||||
// Close wSOL ATA account, reclaim rent
|
// Close wSOL ATA account, reclaim rent
|
||||||
instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey()));
|
instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey()));
|
||||||
@@ -375,11 +377,13 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
data[16..24].copy_from_slice(&token_amount.to_le_bytes());
|
data[16..24].copy_from_slice(&token_amount.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
instructions.push(Instruction {
|
let sell_instruction = Instruction {
|
||||||
program_id: accounts::AMM_PROGRAM,
|
program_id: accounts::AMM_PROGRAM,
|
||||||
accounts,
|
accounts: accounts.clone(),
|
||||||
data: data.to_vec(),
|
data: data.to_vec(),
|
||||||
});
|
};
|
||||||
|
|
||||||
|
instructions.push(sell_instruction);
|
||||||
|
|
||||||
if close_wsol_ata {
|
if close_wsol_ata {
|
||||||
instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey()));
|
instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey()));
|
||||||
|
|||||||
@@ -230,10 +230,17 @@ pub async fn find_by_base_mint(
|
|||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(anyhow!("No pool found for mint {}", base_mint));
|
return Err(anyhow!("No pool found for mint {}", base_mint));
|
||||||
}
|
}
|
||||||
|
let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts
|
||||||
let mut pools: Vec<_> = accounts
|
let mut pools: Vec<_> = accounts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(addr, acc)| pool_decode(&acc.data).map(|pool| (addr, pool)))
|
.filter_map(|(addr, acc)| pool_decode(&acc.data).map(|pool| (addr, pool)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// 🔧 修复:检查过滤后的 pools 是否为空(accounts 可能不为空但解码全部失败)
|
||||||
|
if pools.is_empty() {
|
||||||
|
return Err(anyhow!("No valid pool decoded for mint {} (found {} accounts but all decode failed)", base_mint, accounts_count));
|
||||||
|
}
|
||||||
|
|
||||||
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
||||||
let (address, pool) = pools[0].clone();
|
let (address, pool) = pools[0].clone();
|
||||||
Ok((address, pool))
|
Ok((address, pool))
|
||||||
@@ -266,10 +273,17 @@ pub async fn find_by_quote_mint(
|
|||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(anyhow!("No pool found for mint {}", quote_mint));
|
return Err(anyhow!("No pool found for mint {}", quote_mint));
|
||||||
}
|
}
|
||||||
|
let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts
|
||||||
let mut pools: Vec<_> = accounts
|
let mut pools: Vec<_> = accounts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(addr, acc)| pool_decode(&acc.data).map(|pool| (addr, pool)))
|
.filter_map(|(addr, acc)| pool_decode(&acc.data).map(|pool| (addr, pool)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// 🔧 修复:检查过滤后的 pools 是否为空(accounts 可能不为空但解码全部失败)
|
||||||
|
if pools.is_empty() {
|
||||||
|
return Err(anyhow!("No valid pool decoded for quote_mint {} (found {} accounts but all decode failed)", quote_mint, accounts_count));
|
||||||
|
}
|
||||||
|
|
||||||
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
||||||
let (address, pool) = pools[0].clone();
|
let (address, pool) = pools[0].clone();
|
||||||
Ok((address, pool))
|
Ok((address, pool))
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ impl FormatBase64VersionedTransaction for VersionedTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn poll_transaction_confirmation(rpc: &SolanaRpcClient, txt_sig: Signature) -> Result<Signature> {
|
pub async fn poll_transaction_confirmation(rpc: &SolanaRpcClient, txt_sig: Signature) -> Result<Signature> {
|
||||||
let timeout: Duration = Duration::from_secs(10);
|
let timeout: Duration = Duration::from_secs(15); // 🔧 增加到15秒,避免网络拥堵时超时
|
||||||
let interval: Duration = Duration::from_millis(1000);
|
let interval: Duration = Duration::from_millis(1000);
|
||||||
let start: Instant = Instant::now();
|
let start: Instant = Instant::now();
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{nonce_cache::DurableNonceInfo, SolanaRpcClient},
|
common::{nonce_cache::DurableNonceInfo, SolanaRpcClient},
|
||||||
|
constants::swqos::NODE1_TIP_ACCOUNTS,
|
||||||
trading::{MiddlewareManager, core::transaction_pool::{acquire_builder, release_builder}},
|
trading::{MiddlewareManager, core::transaction_pool::{acquire_builder, release_builder}},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,10 +46,24 @@ pub async fn build_transaction(
|
|||||||
|
|
||||||
// Add tip transfer instruction
|
// Add tip transfer instruction
|
||||||
if with_tip && tip_amount > 0.0 {
|
if with_tip && tip_amount > 0.0 {
|
||||||
|
// 🔧 Node1 最小小费金额限制:0.002 SOL(仅限 Node1)
|
||||||
|
const MIN_TIP_AMOUNT: f64 = 0.002;
|
||||||
|
|
||||||
|
// 检查是否是 Node1 的 tip_account
|
||||||
|
let is_node1 = NODE1_TIP_ACCOUNTS.iter().any(|&account| account == *tip_account);
|
||||||
|
|
||||||
|
let actual_tip_amount = if is_node1 && tip_amount < MIN_TIP_AMOUNT {
|
||||||
|
// Node1 要求最小 0.002 SOL
|
||||||
|
MIN_TIP_AMOUNT
|
||||||
|
} else {
|
||||||
|
// 其他 swqos 使用原始金额
|
||||||
|
tip_amount
|
||||||
|
};
|
||||||
|
|
||||||
instructions.push(transfer(
|
instructions.push(transfer(
|
||||||
&payer.pubkey(),
|
&payer.pubkey(),
|
||||||
tip_account,
|
tip_account,
|
||||||
sol_str_to_lamports(tip_amount.to_string().as_str()).unwrap_or(0),
|
sol_str_to_lamports(actual_tip_amount.to_string().as_str()).unwrap_or(0),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user