four-flap-meme-sdk 1.3.92 → 1.3.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clients/blockrazor.js +0 -1
- package/dist/contracts/tm-bundle-merkle/config.d.ts +5 -0
- package/dist/contracts/tm-bundle-merkle/config.js +10 -0
- package/dist/contracts/tm-bundle-merkle/core.js +92 -24
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +103 -54
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +0 -1
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +36 -6
- package/dist/contracts/tm-bundle-merkle/swap.d.ts +0 -3
- package/dist/contracts/tm-bundle-merkle/swap.js +59 -6
- package/dist/flap/portal-bundle-merkle/config.d.ts +8 -0
- package/dist/flap/portal-bundle-merkle/config.js +17 -0
- package/dist/flap/portal-bundle-merkle/core.js +120 -68
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
- package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +136 -78
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +0 -2
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +49 -30
- package/dist/flap/portal-bundle-merkle/swap.d.ts +0 -2
- package/dist/flap/portal-bundle-merkle/swap.js +75 -47
- package/dist/flap/portal-bundle-merkle/types.d.ts +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/pancake/bundle-buy-first.d.ts +1 -1
- package/dist/pancake/bundle-buy-first.js +49 -17
- package/dist/pancake/bundle-swap.d.ts +1 -4
- package/dist/pancake/bundle-swap.js +98 -33
- package/dist/utils/erc20.d.ts +108 -2
- package/dist/utils/erc20.js +65 -17
- package/package.json +4 -39
- package/dist/sol/constants.d.ts +0 -126
- package/dist/sol/constants.js +0 -145
- package/dist/sol/dex/index.d.ts +0 -8
- package/dist/sol/dex/index.js +0 -12
- package/dist/sol/dex/meteora/client.d.ts +0 -76
- package/dist/sol/dex/meteora/client.js +0 -219
- package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +0 -61
- package/dist/sol/dex/meteora/damm-v1-bundle.js +0 -112
- package/dist/sol/dex/meteora/damm-v1.d.ts +0 -118
- package/dist/sol/dex/meteora/damm-v1.js +0 -315
- package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +0 -82
- package/dist/sol/dex/meteora/damm-v2-bundle.js +0 -242
- package/dist/sol/dex/meteora/damm-v2.d.ts +0 -172
- package/dist/sol/dex/meteora/damm-v2.js +0 -632
- package/dist/sol/dex/meteora/dbc-bundle.d.ts +0 -123
- package/dist/sol/dex/meteora/dbc-bundle.js +0 -304
- package/dist/sol/dex/meteora/dbc.d.ts +0 -192
- package/dist/sol/dex/meteora/dbc.js +0 -619
- package/dist/sol/dex/meteora/dlmm-bundle.d.ts +0 -39
- package/dist/sol/dex/meteora/dlmm-bundle.js +0 -189
- package/dist/sol/dex/meteora/dlmm.d.ts +0 -146
- package/dist/sol/dex/meteora/dlmm.js +0 -593
- package/dist/sol/dex/meteora/index.d.ts +0 -25
- package/dist/sol/dex/meteora/index.js +0 -65
- package/dist/sol/dex/meteora/types.d.ts +0 -787
- package/dist/sol/dex/meteora/types.js +0 -110
- package/dist/sol/dex/orca/index.d.ts +0 -10
- package/dist/sol/dex/orca/index.js +0 -16
- package/dist/sol/dex/orca/orca-bundle.d.ts +0 -41
- package/dist/sol/dex/orca/orca-bundle.js +0 -173
- package/dist/sol/dex/orca/orca.d.ts +0 -65
- package/dist/sol/dex/orca/orca.js +0 -474
- package/dist/sol/dex/orca/types.d.ts +0 -263
- package/dist/sol/dex/orca/types.js +0 -38
- package/dist/sol/dex/orca/wavebreak-bundle.d.ts +0 -34
- package/dist/sol/dex/orca/wavebreak-bundle.js +0 -198
- package/dist/sol/dex/orca/wavebreak-types.d.ts +0 -227
- package/dist/sol/dex/orca/wavebreak-types.js +0 -23
- package/dist/sol/dex/orca/wavebreak.d.ts +0 -78
- package/dist/sol/dex/orca/wavebreak.js +0 -497
- package/dist/sol/dex/pump/index.d.ts +0 -9
- package/dist/sol/dex/pump/index.js +0 -14
- package/dist/sol/dex/pump/pump-bundle.d.ts +0 -92
- package/dist/sol/dex/pump/pump-bundle.js +0 -383
- package/dist/sol/dex/pump/pump-swap-bundle.d.ts +0 -103
- package/dist/sol/dex/pump/pump-swap-bundle.js +0 -380
- package/dist/sol/dex/pump/pump-swap.d.ts +0 -46
- package/dist/sol/dex/pump/pump-swap.js +0 -199
- package/dist/sol/dex/pump/pump.d.ts +0 -35
- package/dist/sol/dex/pump/pump.js +0 -352
- package/dist/sol/dex/pump/types.d.ts +0 -215
- package/dist/sol/dex/pump/types.js +0 -5
- package/dist/sol/dex/raydium/index.d.ts +0 -8
- package/dist/sol/dex/raydium/index.js +0 -12
- package/dist/sol/dex/raydium/launchlab.d.ts +0 -68
- package/dist/sol/dex/raydium/launchlab.js +0 -210
- package/dist/sol/dex/raydium/raydium-bundle.d.ts +0 -64
- package/dist/sol/dex/raydium/raydium-bundle.js +0 -324
- package/dist/sol/dex/raydium/raydium.d.ts +0 -40
- package/dist/sol/dex/raydium/raydium.js +0 -366
- package/dist/sol/dex/raydium/types.d.ts +0 -240
- package/dist/sol/dex/raydium/types.js +0 -5
- package/dist/sol/index.d.ts +0 -10
- package/dist/sol/index.js +0 -16
- package/dist/sol/jito/bundle.d.ts +0 -90
- package/dist/sol/jito/bundle.js +0 -263
- package/dist/sol/jito/index.d.ts +0 -7
- package/dist/sol/jito/index.js +0 -7
- package/dist/sol/jito/tip.d.ts +0 -51
- package/dist/sol/jito/tip.js +0 -83
- package/dist/sol/jito/types.d.ts +0 -100
- package/dist/sol/jito/types.js +0 -5
- package/dist/sol/token/create-complete.d.ts +0 -115
- package/dist/sol/token/create-complete.js +0 -235
- package/dist/sol/token/create-token.d.ts +0 -57
- package/dist/sol/token/create-token.js +0 -230
- package/dist/sol/token/index.d.ts +0 -9
- package/dist/sol/token/index.js +0 -14
- package/dist/sol/token/metadata-upload.d.ts +0 -86
- package/dist/sol/token/metadata-upload.js +0 -173
- package/dist/sol/token/metadata.d.ts +0 -92
- package/dist/sol/token/metadata.js +0 -274
- package/dist/sol/token/types.d.ts +0 -153
- package/dist/sol/token/types.js +0 -5
- package/dist/sol/types.d.ts +0 -176
- package/dist/sol/types.js +0 -7
- package/dist/sol/utils/balance.d.ts +0 -160
- package/dist/sol/utils/balance.js +0 -638
- package/dist/sol/utils/connection.d.ts +0 -78
- package/dist/sol/utils/connection.js +0 -168
- package/dist/sol/utils/index.d.ts +0 -9
- package/dist/sol/utils/index.js +0 -9
- package/dist/sol/utils/lp-inspect.d.ts +0 -129
- package/dist/sol/utils/lp-inspect.js +0 -796
- package/dist/sol/utils/transfer.d.ts +0 -125
- package/dist/sol/utils/transfer.js +0 -220
- package/dist/sol/utils/wallet.d.ts +0 -107
- package/dist/sol/utils/wallet.js +0 -210
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECDH + AES-GCM 加密工具(浏览器兼容)
|
|
3
|
+
* 用于将签名交易用服务器公钥加密
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 获取全局 crypto 对象(最简单直接的方式)
|
|
7
|
+
*/
|
|
8
|
+
function getCryptoAPI() {
|
|
9
|
+
// 尝试所有可能的全局对象,优先浏览器环境
|
|
10
|
+
const cryptoObj = (typeof window !== 'undefined' && window.crypto) ||
|
|
11
|
+
(typeof self !== 'undefined' && self.crypto) ||
|
|
12
|
+
(typeof global !== 'undefined' && global.crypto) ||
|
|
13
|
+
(typeof globalThis !== 'undefined' && globalThis.crypto);
|
|
14
|
+
if (!cryptoObj) {
|
|
15
|
+
const env = typeof window !== 'undefined' ? 'Browser' : 'Node.js';
|
|
16
|
+
const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
|
|
17
|
+
throw new Error(`❌ Crypto API 不可用。环境: ${env}, 协议: ${protocol}. ` +
|
|
18
|
+
'请确保在 HTTPS 或 localhost 下运行');
|
|
19
|
+
}
|
|
20
|
+
return cryptoObj;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 获取 SubtleCrypto(用于加密操作)
|
|
24
|
+
*/
|
|
25
|
+
function getSubtleCrypto() {
|
|
26
|
+
const crypto = getCryptoAPI();
|
|
27
|
+
if (!crypto.subtle) {
|
|
28
|
+
const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
|
|
29
|
+
const hostname = typeof location !== 'undefined' ? location.hostname : 'unknown';
|
|
30
|
+
throw new Error(`❌ SubtleCrypto API 不可用。协议: ${protocol}, 主机: ${hostname}. ` +
|
|
31
|
+
'请确保:1) 使用 HTTPS (或 localhost);2) 浏览器支持 Web Crypto API;' +
|
|
32
|
+
'3) 不在无痕/隐私浏览模式下');
|
|
33
|
+
}
|
|
34
|
+
return crypto.subtle;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Base64 转 ArrayBuffer(优先使用浏览器 API)
|
|
38
|
+
*/
|
|
39
|
+
function base64ToArrayBuffer(base64) {
|
|
40
|
+
// 浏览器环境(优先)
|
|
41
|
+
if (typeof atob !== 'undefined') {
|
|
42
|
+
const binaryString = atob(base64);
|
|
43
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
44
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
45
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
46
|
+
}
|
|
47
|
+
return bytes.buffer;
|
|
48
|
+
}
|
|
49
|
+
// Node.js 环境(fallback)
|
|
50
|
+
if (typeof Buffer !== 'undefined') {
|
|
51
|
+
return Buffer.from(base64, 'base64').buffer;
|
|
52
|
+
}
|
|
53
|
+
throw new Error('❌ Base64 解码不可用');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* ArrayBuffer 转 Base64(优先使用浏览器 API)
|
|
57
|
+
*/
|
|
58
|
+
function arrayBufferToBase64(buffer) {
|
|
59
|
+
// 浏览器环境(优先)
|
|
60
|
+
if (typeof btoa !== 'undefined') {
|
|
61
|
+
const bytes = new Uint8Array(buffer);
|
|
62
|
+
let binary = '';
|
|
63
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
64
|
+
binary += String.fromCharCode(bytes[i]);
|
|
65
|
+
}
|
|
66
|
+
return btoa(binary);
|
|
67
|
+
}
|
|
68
|
+
// Node.js 环境(fallback)
|
|
69
|
+
if (typeof Buffer !== 'undefined') {
|
|
70
|
+
return Buffer.from(buffer).toString('base64');
|
|
71
|
+
}
|
|
72
|
+
throw new Error('❌ Base64 编码不可用');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 生成随机 Hex 字符串
|
|
76
|
+
*/
|
|
77
|
+
function randomHex(length) {
|
|
78
|
+
const crypto = getCryptoAPI();
|
|
79
|
+
const array = new Uint8Array(length);
|
|
80
|
+
crypto.getRandomValues(array);
|
|
81
|
+
return Array.from(array)
|
|
82
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
83
|
+
.join('');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 用服务器公钥加密签名交易(ECDH + AES-GCM)
|
|
87
|
+
*
|
|
88
|
+
* @param signedTransactions 签名后的交易数组
|
|
89
|
+
* @param publicKeyBase64 服务器提供的公钥(Base64 格式)
|
|
90
|
+
* @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
|
|
91
|
+
*/
|
|
92
|
+
export async function encryptWithPublicKey(signedTransactions, publicKeyBase64) {
|
|
93
|
+
try {
|
|
94
|
+
// 0. 获取 SubtleCrypto 和 Crypto API
|
|
95
|
+
const subtle = getSubtleCrypto();
|
|
96
|
+
const crypto = getCryptoAPI();
|
|
97
|
+
// 1. 准备数据
|
|
98
|
+
const payload = {
|
|
99
|
+
signedTransactions,
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
nonce: randomHex(8)
|
|
102
|
+
};
|
|
103
|
+
const plaintext = JSON.stringify(payload);
|
|
104
|
+
// 2. 生成临时 ECDH 密钥对
|
|
105
|
+
const ephemeralKeyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);
|
|
106
|
+
// 3. 导入服务器公钥
|
|
107
|
+
const publicKeyBuffer = base64ToArrayBuffer(publicKeyBase64);
|
|
108
|
+
const publicKey = await subtle.importKey('raw', publicKeyBuffer, { name: 'ECDH', namedCurve: 'P-256' }, false, []);
|
|
109
|
+
// 4. 派生共享密钥(AES-256)
|
|
110
|
+
const sharedKey = await subtle.deriveKey({ name: 'ECDH', public: publicKey }, ephemeralKeyPair.privateKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
|
|
111
|
+
// 5. AES-GCM 加密
|
|
112
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
113
|
+
const encrypted = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, new TextEncoder().encode(plaintext));
|
|
114
|
+
// 6. 导出临时公钥
|
|
115
|
+
const ephemeralPublicKeyRaw = await subtle.exportKey('raw', ephemeralKeyPair.publicKey);
|
|
116
|
+
// 7. 返回加密包(JSON 格式)
|
|
117
|
+
return JSON.stringify({
|
|
118
|
+
e: arrayBufferToBase64(ephemeralPublicKeyRaw), // 临时公钥
|
|
119
|
+
i: arrayBufferToBase64(iv.buffer), // IV
|
|
120
|
+
d: arrayBufferToBase64(encrypted) // 密文
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw new Error(`加密失败: ${error?.message || String(error)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 验证公钥格式(Base64)
|
|
129
|
+
*/
|
|
130
|
+
export function validatePublicKey(publicKeyBase64) {
|
|
131
|
+
try {
|
|
132
|
+
if (!publicKeyBase64)
|
|
133
|
+
return false;
|
|
134
|
+
// Base64 字符集验证
|
|
135
|
+
if (!/^[A-Za-z0-9+/=]+$/.test(publicKeyBase64))
|
|
136
|
+
return false;
|
|
137
|
+
// ECDH P-256 公钥固定长度 65 字节(未压缩)
|
|
138
|
+
// Base64 编码后约 88 字符
|
|
139
|
+
if (publicKeyBase64.length < 80 || publicKeyBase64.length > 100)
|
|
140
|
+
return false;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
|
|
2
2
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
3
3
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
4
|
-
import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
|
|
4
|
+
import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA } from './config.js';
|
|
5
5
|
import { batchCheckAllowances } from '../../utils/erc20.js';
|
|
6
6
|
const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
|
|
7
7
|
const MULTICALL3_ABI = [
|
|
@@ -56,16 +56,27 @@ function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
|
|
|
56
56
|
return BigInt(calculatedGas);
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
* 查询代币 decimals
|
|
59
|
+
* 查询代币 decimals(带缓存)
|
|
60
|
+
* ✅ 代币精度不会变化,缓存后永久有效
|
|
60
61
|
*/
|
|
61
62
|
async function getTokenDecimals(tokenAddress, provider) {
|
|
63
|
+
const cacheKey = tokenAddress.toLowerCase();
|
|
64
|
+
// ✅ 检查缓存
|
|
65
|
+
const cached = tokenDecimalsCache.get(cacheKey);
|
|
66
|
+
if (cached !== undefined) {
|
|
67
|
+
return cached;
|
|
68
|
+
}
|
|
62
69
|
try {
|
|
63
70
|
const token = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
64
71
|
const decimals = await token.decimals();
|
|
65
|
-
|
|
72
|
+
const result = Number(decimals);
|
|
73
|
+
// ✅ 缓存结果
|
|
74
|
+
tokenDecimalsCache.set(cacheKey, result);
|
|
75
|
+
return result;
|
|
66
76
|
}
|
|
67
77
|
catch {
|
|
68
|
-
// 默认返回 18,兼容大部分 ERC20
|
|
78
|
+
// 默认返回 18,兼容大部分 ERC20(也缓存)
|
|
79
|
+
tokenDecimalsCache.set(cacheKey, 18);
|
|
69
80
|
return 18;
|
|
70
81
|
}
|
|
71
82
|
}
|
|
@@ -281,11 +292,21 @@ export async function pancakeProxyBatchBuyMerkle(params) {
|
|
|
281
292
|
const divisor = BigInt(10 ** decimalsDiff);
|
|
282
293
|
actualAmountsWei = remainingAmounts.map(amount => amount / divisor);
|
|
283
294
|
}
|
|
284
|
-
// ✅
|
|
295
|
+
// ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
|
|
296
|
+
const presetGasPrice = config.gasPrice;
|
|
297
|
+
const presetNonces = config.nonces;
|
|
298
|
+
// ✅ 只获取必需的数据(跳过已有的)
|
|
285
299
|
const [gasPrice, tokenDecimals, nonces] = await Promise.all([
|
|
286
|
-
|
|
300
|
+
// gasPrice:优先使用前端传入的
|
|
301
|
+
presetGasPrice !== undefined
|
|
302
|
+
? Promise.resolve(presetGasPrice)
|
|
303
|
+
: getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
304
|
+
// tokenDecimals:有缓存
|
|
287
305
|
getTokenDecimals(tokenAddress, provider),
|
|
288
|
-
|
|
306
|
+
// nonces:优先使用前端传入的
|
|
307
|
+
presetNonces && presetNonces.length === buyers.length
|
|
308
|
+
? Promise.resolve(presetNonces)
|
|
309
|
+
: allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
|
|
289
310
|
]);
|
|
290
311
|
const minOuts = resolveBuyMinOutputs(params, buyers.length, tokenDecimals);
|
|
291
312
|
const needBNB = needSendBNB(routeType, params, useNativeToken);
|
|
@@ -301,8 +322,26 @@ export async function pancakeProxyBatchBuyMerkle(params) {
|
|
|
301
322
|
minOuts,
|
|
302
323
|
needBNB
|
|
303
324
|
});
|
|
304
|
-
// ✅
|
|
305
|
-
const
|
|
325
|
+
// ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
|
|
326
|
+
const bribeAmount = getBribeAmount(config);
|
|
327
|
+
const bribeTxs = [];
|
|
328
|
+
if (bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0) {
|
|
329
|
+
const bribeNonce = nonces[maxFundsIndex];
|
|
330
|
+
const bribeTx = await buyers[maxFundsIndex].signTransaction({
|
|
331
|
+
to: BLOCKRAZOR_BUILDER_EOA,
|
|
332
|
+
value: bribeAmount,
|
|
333
|
+
nonce: bribeNonce,
|
|
334
|
+
gasPrice,
|
|
335
|
+
gasLimit: 21000n,
|
|
336
|
+
chainId,
|
|
337
|
+
type: getTxType(config)
|
|
338
|
+
});
|
|
339
|
+
bribeTxs.push(bribeTx);
|
|
340
|
+
// 调整 maxFundsIndex 钱包的 nonce(买入交易 +1)
|
|
341
|
+
nonces[maxFundsIndex] = bribeNonce + 1;
|
|
342
|
+
}
|
|
343
|
+
// ✅ 并行签名所有买入交易
|
|
344
|
+
const signedBuys = await Promise.all(unsignedBuys.map((unsigned, i) => {
|
|
306
345
|
// ✅ ERC20 购买时 value 只需要 FLAT_FEE,不需要发送代币金额
|
|
307
346
|
const txValue = useNativeToken ? unsigned.value : FLAT_FEE;
|
|
308
347
|
return buyers[i].signTransaction({
|
|
@@ -316,20 +355,25 @@ export async function pancakeProxyBatchBuyMerkle(params) {
|
|
|
316
355
|
value: txValue
|
|
317
356
|
});
|
|
318
357
|
}));
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
358
|
+
// ✅ 利润交易放在末尾
|
|
359
|
+
const profitTxs = [];
|
|
360
|
+
if (shouldExtractProfitForBuy && nativeProfitAmount > 0n && maxFundsIndex >= 0) {
|
|
361
|
+
const profitNonce = nonces[maxFundsIndex] + 1;
|
|
362
|
+
const profitTx = await buyers[maxFundsIndex].signTransaction({
|
|
363
|
+
to: getProfitRecipient(),
|
|
364
|
+
value: nativeProfitAmount,
|
|
365
|
+
nonce: profitNonce,
|
|
366
|
+
gasPrice,
|
|
367
|
+
gasLimit: 21000n,
|
|
368
|
+
chainId,
|
|
369
|
+
type: getTxType(config)
|
|
370
|
+
});
|
|
371
|
+
profitTxs.push(profitTx);
|
|
372
|
+
}
|
|
330
373
|
nonceManager.clearTemp();
|
|
374
|
+
// ✅ 组装顺序:贿赂 → 买入 → 利润
|
|
331
375
|
return {
|
|
332
|
-
signedTransactions:
|
|
376
|
+
signedTransactions: [...bribeTxs, ...signedBuys, ...profitTxs]
|
|
333
377
|
};
|
|
334
378
|
}
|
|
335
379
|
/**
|
|
@@ -350,10 +394,18 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
350
394
|
const finalGasLimit = getGasLimit(config);
|
|
351
395
|
const extractProfit = shouldExtractProfit(config);
|
|
352
396
|
const nonceManager = new NonceManager(provider);
|
|
397
|
+
// ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
|
|
398
|
+
const presetGasPrice = config.gasPrice;
|
|
399
|
+
const presetNonces = config.nonces;
|
|
353
400
|
// ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
|
|
354
401
|
const [gasPrice, tokenDecimals] = await Promise.all([
|
|
355
|
-
|
|
402
|
+
// gasPrice:优先使用前端传入的
|
|
403
|
+
presetGasPrice !== undefined
|
|
404
|
+
? Promise.resolve(presetGasPrice)
|
|
405
|
+
: getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
406
|
+
// tokenDecimals:有缓存
|
|
356
407
|
getTokenDecimals(tokenAddress, provider),
|
|
408
|
+
// allowances:必须检查
|
|
357
409
|
ensureAllowances({
|
|
358
410
|
provider,
|
|
359
411
|
tokenAddress,
|
|
@@ -362,8 +414,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
362
414
|
})
|
|
363
415
|
]);
|
|
364
416
|
const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
|
|
365
|
-
// ✅ 优化:第二批并行 - resolveSellOutputs 和 buildSellTransactions
|
|
366
417
|
const proxies = createPancakeProxies(sellers, ADDRESSES.BSC.PancakeProxy);
|
|
418
|
+
// 获取报价(用于计算利润)
|
|
367
419
|
const { minOuts, quotedOutputs } = await resolveSellOutputs({
|
|
368
420
|
params,
|
|
369
421
|
provider,
|
|
@@ -388,13 +440,27 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
388
440
|
}
|
|
389
441
|
}
|
|
390
442
|
}
|
|
391
|
-
// ✅
|
|
443
|
+
// ✅ 修复:根据是否需要贿赂/利润交易,统一分配 nonces
|
|
444
|
+
const bribeAmount = getBribeAmount(config);
|
|
445
|
+
const needBribeTx = bribeAmount > 0n && maxRevenueIndex >= 0;
|
|
392
446
|
const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
|
|
447
|
+
// 计算 maxRevenueIndex 钱包需要的 nonce 数量:贿赂(可选) + 卖出 + 利润(可选)
|
|
448
|
+
const maxRevenueNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
|
|
393
449
|
let nonces;
|
|
450
|
+
let bribeNonce;
|
|
394
451
|
let profitNonce;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
452
|
+
// ✅ 优化:如果前端传入了 nonces,直接使用
|
|
453
|
+
if (presetNonces && presetNonces.length === sellers.length) {
|
|
454
|
+
nonces = [...presetNonces];
|
|
455
|
+
if (needBribeTx) {
|
|
456
|
+
bribeNonce = nonces[maxRevenueIndex];
|
|
457
|
+
nonces[maxRevenueIndex] = bribeNonce + 1; // 卖出交易 nonce +1
|
|
458
|
+
}
|
|
459
|
+
profitNonce = needProfitTx ? nonces[maxRevenueIndex] + 1 : undefined;
|
|
460
|
+
}
|
|
461
|
+
else if (maxRevenueNonceCount > 1 && maxRevenueIndex >= 0) {
|
|
462
|
+
// maxRevenueIndex 钱包需要多个连续 nonce
|
|
463
|
+
const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], maxRevenueNonceCount);
|
|
398
464
|
// 其他钱包各需要 1 个 nonce
|
|
399
465
|
const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
|
|
400
466
|
const otherNonces = otherSellers.length > 0
|
|
@@ -403,20 +469,40 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
403
469
|
// 组装最终的 nonces 数组(保持原顺序)
|
|
404
470
|
nonces = [];
|
|
405
471
|
let otherIdx = 0;
|
|
472
|
+
let nonceIdx = 0;
|
|
473
|
+
if (needBribeTx) {
|
|
474
|
+
bribeNonce = maxRevenueNonces[nonceIdx++]; // 贿赂交易用第一个 nonce
|
|
475
|
+
}
|
|
406
476
|
for (let i = 0; i < sellers.length; i++) {
|
|
407
477
|
if (i === maxRevenueIndex) {
|
|
408
|
-
nonces.push(maxRevenueNonces[
|
|
478
|
+
nonces.push(maxRevenueNonces[nonceIdx++]); // 卖出交易
|
|
409
479
|
}
|
|
410
480
|
else {
|
|
411
481
|
nonces.push(otherNonces[otherIdx++]);
|
|
412
482
|
}
|
|
413
483
|
}
|
|
414
|
-
|
|
484
|
+
if (needProfitTx) {
|
|
485
|
+
profitNonce = maxRevenueNonces[nonceIdx]; // 利润交易用最后一个 nonce
|
|
486
|
+
}
|
|
415
487
|
}
|
|
416
488
|
else {
|
|
417
|
-
//
|
|
489
|
+
// 不需要额外交易,所有钱包各 1 个 nonce
|
|
418
490
|
nonces = await nonceManager.getNextNoncesForWallets(sellers);
|
|
419
491
|
}
|
|
492
|
+
// ✅ 贿赂交易放在首位
|
|
493
|
+
const bribeTxs = [];
|
|
494
|
+
if (needBribeTx && bribeNonce !== undefined) {
|
|
495
|
+
const bribeTx = await sellers[maxRevenueIndex].signTransaction({
|
|
496
|
+
to: BLOCKRAZOR_BUILDER_EOA,
|
|
497
|
+
value: bribeAmount,
|
|
498
|
+
nonce: bribeNonce,
|
|
499
|
+
gasPrice,
|
|
500
|
+
gasLimit: 21000n,
|
|
501
|
+
chainId,
|
|
502
|
+
type: getTxType(config)
|
|
503
|
+
});
|
|
504
|
+
bribeTxs.push(bribeTx);
|
|
505
|
+
}
|
|
420
506
|
const unsignedSells = await buildSellTransactions({
|
|
421
507
|
routeType,
|
|
422
508
|
params,
|
|
@@ -426,8 +512,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
426
512
|
amountsWei,
|
|
427
513
|
minOuts
|
|
428
514
|
});
|
|
429
|
-
// ✅
|
|
430
|
-
const
|
|
515
|
+
// ✅ 并行签名所有卖出交易
|
|
516
|
+
const signedSells = await Promise.all(unsignedSells.map((unsigned, i) => {
|
|
431
517
|
const txValue = unsigned.value;
|
|
432
518
|
return sellers[i].signTransaction({
|
|
433
519
|
...unsigned,
|
|
@@ -440,7 +526,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
440
526
|
value: txValue
|
|
441
527
|
});
|
|
442
528
|
}));
|
|
443
|
-
// ✅
|
|
529
|
+
// ✅ 利润交易放在末尾
|
|
530
|
+
const profitTxs = [];
|
|
444
531
|
if (needProfitTx && profitNonce !== undefined) {
|
|
445
532
|
const profitTx = await sellers[maxRevenueIndex].signTransaction({
|
|
446
533
|
to: getProfitRecipient(),
|
|
@@ -451,16 +538,19 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
451
538
|
chainId,
|
|
452
539
|
type: getTxType(config)
|
|
453
540
|
});
|
|
454
|
-
|
|
541
|
+
profitTxs.push(profitTx);
|
|
455
542
|
}
|
|
456
543
|
nonceManager.clearTemp();
|
|
544
|
+
// ✅ 组装顺序:贿赂 → 卖出 → 利润
|
|
457
545
|
return {
|
|
458
|
-
signedTransactions:
|
|
546
|
+
signedTransactions: [...bribeTxs, ...signedSells, ...profitTxs]
|
|
459
547
|
};
|
|
460
548
|
}
|
|
461
549
|
// ✅ Provider 缓存(复用连接,减少初始化开销)
|
|
462
550
|
const providerCache = new Map();
|
|
463
551
|
const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
|
|
552
|
+
// ✅ Token Decimals 缓存(代币精度不会变化)
|
|
553
|
+
const tokenDecimalsCache = new Map();
|
|
464
554
|
function createChainContext(chain, rpcUrl) {
|
|
465
555
|
const chainId = CHAIN_ID_MAP[chain];
|
|
466
556
|
const cacheKey = `${chain}-${rpcUrl}`;
|
|
@@ -492,10 +582,8 @@ function findMaxAmountIndex(amounts) {
|
|
|
492
582
|
}
|
|
493
583
|
return maxIndex;
|
|
494
584
|
}
|
|
495
|
-
function resolveBuyMinOutputs(
|
|
496
|
-
|
|
497
|
-
return params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseUnits(m, tokenDecimals) : m);
|
|
498
|
-
}
|
|
585
|
+
function resolveBuyMinOutputs(_params, walletCount, _tokenDecimals) {
|
|
586
|
+
// ✅ 已移除滑点保护:minOutput 固定为 0
|
|
499
587
|
return new Array(walletCount).fill(0n);
|
|
500
588
|
}
|
|
501
589
|
function createPancakeProxies(wallets, proxyAddress) {
|
|
@@ -551,22 +639,6 @@ async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, total
|
|
|
551
639
|
}
|
|
552
640
|
return nonces;
|
|
553
641
|
}
|
|
554
|
-
async function appendProfitTransfer({ extractProfit, totalProfit, wallets, maxIndex, nonces, gasPrice, chainId, config, signedTxs }) {
|
|
555
|
-
if (!extractProfit || totalProfit === 0n || wallets.length === 0 || maxIndex < 0) {
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
const profitNonce = (nonces[maxIndex] ?? 0) + 1;
|
|
559
|
-
const profitTx = await wallets[maxIndex].signTransaction({
|
|
560
|
-
to: getProfitRecipient(),
|
|
561
|
-
value: totalProfit,
|
|
562
|
-
nonce: profitNonce,
|
|
563
|
-
gasPrice,
|
|
564
|
-
gasLimit: 21000n,
|
|
565
|
-
chainId,
|
|
566
|
-
type: getTxType(config)
|
|
567
|
-
});
|
|
568
|
-
signedTxs.push(profitTx);
|
|
569
|
-
}
|
|
570
642
|
async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
|
|
571
643
|
const allowances = await batchCheckAllowances(provider, tokenAddress, owners, spender);
|
|
572
644
|
const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
|
|
@@ -576,26 +648,18 @@ async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
|
|
|
576
648
|
}
|
|
577
649
|
}
|
|
578
650
|
/**
|
|
579
|
-
* ✅
|
|
651
|
+
* ✅ 获取卖出报价(用于计算利润,不用于滑点保护)
|
|
652
|
+
* ✅ 已移除滑点保护:minOutput 固定为 0
|
|
580
653
|
*/
|
|
581
654
|
async function resolveSellOutputs({ params, provider, tokenAddress, routeType, amountsWei }) {
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
const minOuts = params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
|
|
585
|
-
return {
|
|
586
|
-
minOuts,
|
|
587
|
-
quotedOutputs: minOuts.map(m => m * 100n / 95n)
|
|
588
|
-
};
|
|
589
|
-
}
|
|
655
|
+
// ✅ 已移除滑点保护:minOutput 固定为 0
|
|
656
|
+
const minOuts = new Array(amountsWei.length).fill(0n);
|
|
590
657
|
// 如果只有 1 个,直接调用(避免 multicall 开销)
|
|
591
658
|
if (amountsWei.length === 1) {
|
|
592
659
|
const quotedOutput = await getSingleQuote(params, provider, tokenAddress, routeType, amountsWei[0]);
|
|
593
|
-
return {
|
|
594
|
-
quotedOutputs: [quotedOutput],
|
|
595
|
-
minOuts: [0n] // ✅ minOutput = 0,不设置滑点限制
|
|
596
|
-
};
|
|
660
|
+
return { quotedOutputs: [quotedOutput], minOuts };
|
|
597
661
|
}
|
|
598
|
-
// ✅ 使用 Multicall3 批量获取报价(仅 V2
|
|
662
|
+
// ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持,用于计算利润)
|
|
599
663
|
if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
|
|
600
664
|
try {
|
|
601
665
|
const v2RouterIface = new Interface(PANCAKE_V2_ROUTER_ABI);
|
|
@@ -619,10 +683,7 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
|
|
|
619
683
|
}
|
|
620
684
|
return 0n;
|
|
621
685
|
});
|
|
622
|
-
return {
|
|
623
|
-
quotedOutputs,
|
|
624
|
-
minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
|
|
625
|
-
};
|
|
686
|
+
return { quotedOutputs, minOuts };
|
|
626
687
|
}
|
|
627
688
|
catch {
|
|
628
689
|
// Multicall 失败,回退到并行调用
|
|
@@ -630,10 +691,7 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
|
|
|
630
691
|
}
|
|
631
692
|
// 回退:并行调用(V3 路由或 Multicall 失败时)
|
|
632
693
|
const quotedOutputs = await Promise.all(amountsWei.map(amount => getSingleQuote(params, provider, tokenAddress, routeType, amount)));
|
|
633
|
-
return {
|
|
634
|
-
quotedOutputs,
|
|
635
|
-
minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
|
|
636
|
-
};
|
|
694
|
+
return { quotedOutputs, minOuts };
|
|
637
695
|
}
|
|
638
696
|
/**
|
|
639
697
|
* 获取单个报价
|
|
@@ -701,4 +759,4 @@ async function buildSellTransactions({ routeType, params, proxies, wallets, toke
|
|
|
701
759
|
}
|
|
702
760
|
throw new Error(`Unsupported routeType: ${routeType}`);
|
|
703
761
|
}
|
|
704
|
-
// ✅
|
|
762
|
+
// ✅ 贿赂交易和利润交易已内联到各函数中,确保正确的顺序:贿赂 → 主交易 → 利润
|
|
@@ -8,7 +8,6 @@ import { FlapSignConfig } from './config.js';
|
|
|
8
8
|
export type FlapChain = 'bsc' | 'xlayer' | 'base';
|
|
9
9
|
export interface FlapBuyFirstSignConfig extends FlapSignConfig {
|
|
10
10
|
reserveGasETH?: number;
|
|
11
|
-
slippageBps?: number;
|
|
12
11
|
skipQuoteOnError?: boolean;
|
|
13
12
|
}
|
|
14
13
|
export interface FlapBuyFirstConfig extends CommonBundleConfig {
|
|
@@ -16,7 +15,6 @@ export interface FlapBuyFirstConfig extends CommonBundleConfig {
|
|
|
16
15
|
customRpcUrl?: string;
|
|
17
16
|
bundleBlockOffset?: number;
|
|
18
17
|
reserveGasETH?: number;
|
|
19
|
-
slippageBps?: number;
|
|
20
18
|
skipQuoteOnError?: boolean;
|
|
21
19
|
waitForConfirmation?: boolean;
|
|
22
20
|
waitTimeoutMs?: number;
|