four-flap-meme-sdk 1.7.22 → 1.7.24
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/eip7702/xlayer/bundle-create.js +26 -15
- package/dist/eip7702/xlayer/bundle-sell.js +137 -18
- package/dist/eip7702/xlayer/bundle-swap.js +105 -11
- package/dist/eip7702/xlayer/constants.d.ts +26 -2
- package/dist/eip7702/xlayer/constants.js +29 -1
- package/dist/eip7702/xlayer/index.d.ts +1 -1
- package/dist/eip7702/xlayer/index.js +5 -1
- package/dist/eip7702/xlayer/types.d.ts +22 -0
- package/dist/eip7702/xlayer/utils.d.ts +18 -0
- package/dist/eip7702/xlayer/utils.js +156 -3
- package/package.json +1 -1
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* 所有函数只返回签名后的交易,由调用方决定如何提交
|
|
10
10
|
*/
|
|
11
11
|
import { ethers, Interface, Contract } from 'ethers';
|
|
12
|
-
import { FLAP_PORTAL_ADDRESS, UNIFIED_DELEGATE_ADDRESS, UNIFIED_DELEGATE_ABI, FLAP_PORTAL_ABI, WOKB_ADDRESS, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, V3_FEE_TIERS, } from './constants.js';
|
|
13
|
-
import { getCachedProvider, createWallet, signAuthorizationsWithNonces, batchGetNonces, buildEIP7702TransactionSync,
|
|
12
|
+
import { FLAP_PORTAL_ADDRESS, UNIFIED_DELEGATE_ADDRESS, UNIFIED_DELEGATE_ABI, FLAP_PORTAL_ABI, WOKB_ADDRESS, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, V3_FEE_TIERS, GAS_ESTIMATE, } from './constants.js';
|
|
13
|
+
import { getCachedProvider, createWallet, signAuthorizationsWithNonces, batchGetNonces, buildEIP7702TransactionSync, calculateProfitAmountByUserType, getProfitRecipient, } from './utils.js';
|
|
14
14
|
// ========================================
|
|
15
15
|
// 常量
|
|
16
16
|
// ========================================
|
|
@@ -125,7 +125,8 @@ function buildProfitCall(walletAddress, profitAmount, profitRecipient, delegateI
|
|
|
125
125
|
* @returns 只返回签名后的交易,不发送
|
|
126
126
|
*/
|
|
127
127
|
export async function bundleCreateBuy(params) {
|
|
128
|
-
const { tokenInfo, tokenAddress = ZERO_ADDRESS, devPrivateKey, buyerPrivateKeys, buyAmounts,
|
|
128
|
+
const { tokenInfo, tokenAddress = ZERO_ADDRESS, devPrivateKey, buyerPrivateKeys, buyAmounts, userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
129
|
+
config, } = params;
|
|
129
130
|
if (buyerPrivateKeys.length !== buyAmounts.length) {
|
|
130
131
|
throw new Error('buyerPrivateKeys 和 buyAmounts 长度必须相同');
|
|
131
132
|
}
|
|
@@ -137,10 +138,10 @@ export async function bundleCreateBuy(params) {
|
|
|
137
138
|
const buyerWallets = buyerPrivateKeys.map(pk => createWallet(pk, provider));
|
|
138
139
|
const allWallets = [devWallet, ...buyerWallets];
|
|
139
140
|
const mainWallet = devWallet;
|
|
140
|
-
//
|
|
141
|
+
// 计算买入金额和利润(单边模式:只有买入)
|
|
141
142
|
const buyAmountsWei = buyAmounts.map(amt => ethers.parseEther(amt));
|
|
142
143
|
const totalBuyAmount = buyAmountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
143
|
-
const profitAmount =
|
|
144
|
+
const profitAmount = calculateProfitAmountByUserType(totalBuyAmount, userType, false); // ✅ 支持 userType
|
|
144
145
|
const profitRecipient = getProfitRecipient(config);
|
|
145
146
|
// 并行获取 nonce 和 fee data
|
|
146
147
|
const [nonces, feeData] = await Promise.all([
|
|
@@ -272,7 +273,8 @@ export async function bundleCreateBuy(params) {
|
|
|
272
273
|
* @returns 只返回签名后的交易,不发送
|
|
273
274
|
*/
|
|
274
275
|
export async function bundleCreateToDex(params) {
|
|
275
|
-
const { tokenInfo, tokenAddress = ZERO_ADDRESS, payerPrivateKey, curveBuyerPrivateKeys, curveBuyAmounts, enableDexBuy = false, dexBuyerPrivateKeys = [], dexBuyAmounts = [],
|
|
276
|
+
const { tokenInfo, tokenAddress = ZERO_ADDRESS, payerPrivateKey, curveBuyerPrivateKeys, curveBuyAmounts, enableDexBuy = false, dexBuyerPrivateKeys = [], dexBuyAmounts = [], userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
277
|
+
config, } = params;
|
|
276
278
|
if (curveBuyerPrivateKeys.length !== curveBuyAmounts.length) {
|
|
277
279
|
throw new Error('curveBuyerPrivateKeys 和 curveBuyAmounts 长度必须相同');
|
|
278
280
|
}
|
|
@@ -294,13 +296,13 @@ export async function bundleCreateToDex(params) {
|
|
|
294
296
|
const allWallets = Array.from(allWalletsMap.values());
|
|
295
297
|
const mainWallet = payerWallet;
|
|
296
298
|
const mainWalletIndex = allWallets.findIndex(w => w.address.toLowerCase() === mainWallet.address.toLowerCase());
|
|
297
|
-
//
|
|
299
|
+
// 计算金额和利润(单边模式:只有买入)
|
|
298
300
|
const curveBuyAmountsWei = curveBuyAmounts.map(amt => ethers.parseEther(amt));
|
|
299
301
|
const dexBuyAmountsWei = dexBuyAmounts.map(amt => ethers.parseEther(amt));
|
|
300
302
|
const totalCurveBuyAmount = curveBuyAmountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
301
303
|
const totalDexBuyAmount = dexBuyAmountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
302
304
|
const totalBuyAmount = totalCurveBuyAmount + totalDexBuyAmount;
|
|
303
|
-
const profitAmount =
|
|
305
|
+
const profitAmount = calculateProfitAmountByUserType(totalBuyAmount, userType, false); // ✅ 支持 userType
|
|
304
306
|
const profitRecipient = getProfitRecipient(config);
|
|
305
307
|
// 并行获取 nonce 和 fee data
|
|
306
308
|
const [nonces, feeData] = await Promise.all([
|
|
@@ -396,8 +398,12 @@ export async function bundleCreateToDex(params) {
|
|
|
396
398
|
}
|
|
397
399
|
// 计算总价值
|
|
398
400
|
const totalValue = totalBuyAmount + profitAmount + (params.quoteAmt ?? 0n);
|
|
399
|
-
//
|
|
400
|
-
const
|
|
401
|
+
// 构建签名交易(一键发射会触发毕业,使用高 gas limit)
|
|
402
|
+
const toDexConfig = {
|
|
403
|
+
...config,
|
|
404
|
+
gasLimit: GAS_ESTIMATE.GRADUATE, // ✅ 一键发射(创建+毕业)需要约 800 万 gas
|
|
405
|
+
};
|
|
406
|
+
const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, nonces[mainWalletIndex], feeData, toDexConfig);
|
|
401
407
|
return {
|
|
402
408
|
signedTransaction,
|
|
403
409
|
tokenAddress,
|
|
@@ -426,7 +432,8 @@ export async function bundleCreateToDex(params) {
|
|
|
426
432
|
* @returns 只返回签名后的交易,不发送
|
|
427
433
|
*/
|
|
428
434
|
export async function bundleGraduateBuy(params) {
|
|
429
|
-
const { tokenAddress, privateKeys, payerPrivateKey, amountMode, totalBuyAmount, minAmount, maxAmount, walletAmounts, enableDexBuy = false, extensionData = '0x', curveAddresses, dexAddresses, graduationAmount,
|
|
435
|
+
const { tokenAddress, privateKeys, payerPrivateKey, amountMode, totalBuyAmount, minAmount, maxAmount, walletAmounts, enableDexBuy = false, extensionData = '0x', curveAddresses, dexAddresses, graduationAmount, userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
436
|
+
config, } = params;
|
|
430
437
|
const provider = getCachedProvider(config?.rpcUrl);
|
|
431
438
|
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
432
439
|
const delegateInterface = new Interface(UNIFIED_DELEGATE_ABI);
|
|
@@ -519,11 +526,11 @@ export async function bundleGraduateBuy(params) {
|
|
|
519
526
|
const allWallets = Array.from(allWalletsMap.values());
|
|
520
527
|
const mainWallet = payerWallet;
|
|
521
528
|
const mainWalletIndex = allWallets.findIndex(w => w.address.toLowerCase() === mainWallet.address.toLowerCase());
|
|
522
|
-
//
|
|
529
|
+
// 计算金额和利润(单边模式:只有买入)
|
|
523
530
|
const curveTotalWei = curveBuyers.reduce((sum, b) => sum + b.amount, 0n);
|
|
524
531
|
const dexTotalWei = dexBuyers.reduce((sum, b) => sum + b.amount, 0n);
|
|
525
532
|
const totalAmount = curveTotalWei + dexTotalWei;
|
|
526
|
-
const profitAmount =
|
|
533
|
+
const profitAmount = calculateProfitAmountByUserType(totalAmount, userType, false); // ✅ 支持 userType
|
|
527
534
|
const profitRecipient = getProfitRecipient(config);
|
|
528
535
|
// 并行获取 nonce 和 fee data
|
|
529
536
|
const [nonces, feeData] = await Promise.all([
|
|
@@ -586,8 +593,12 @@ export async function bundleGraduateBuy(params) {
|
|
|
586
593
|
}
|
|
587
594
|
// 计算总价值
|
|
588
595
|
const totalValue = totalAmount + profitAmount;
|
|
589
|
-
//
|
|
590
|
-
const
|
|
596
|
+
// 构建签名交易(毕业操作强制使用高 gas limit)
|
|
597
|
+
const graduateConfig = {
|
|
598
|
+
...config,
|
|
599
|
+
gasLimit: GAS_ESTIMATE.GRADUATE, // ✅ 毕业操作需要约 800 万 gas
|
|
600
|
+
};
|
|
601
|
+
const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, nonces[mainWalletIndex], feeData, graduateConfig);
|
|
591
602
|
return {
|
|
592
603
|
signedTransaction,
|
|
593
604
|
metadata: {
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 只生成签名,由调用方决定如何提交
|
|
8
8
|
*/
|
|
9
|
-
import { ethers, Contract } from 'ethers';
|
|
10
|
-
import { getCachedProvider, createWallet, signAuthorizationsWithNonces, batchGetNonces, buildEIP7702TransactionSync,
|
|
11
|
-
import { UNIFIED_DELEGATE_ADDRESS, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, FLAP_PORTAL_ADDRESS, WOKB_ADDRESS, V3_FEE_TIERS, UNIFIED_DELEGATE_ABI, FLAP_PORTAL_ABI, ERC20_ABI, } from './constants.js';
|
|
9
|
+
import { ethers, Contract, JsonRpcProvider } from 'ethers';
|
|
10
|
+
import { getCachedProvider, createWallet, signAuthorizationsWithNonces, batchGetNonces, buildEIP7702TransactionSync, calculateProfitAmountByUserType, getProfitRecipient, } from './utils.js';
|
|
11
|
+
import { UNIFIED_DELEGATE_ADDRESS, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, FLAP_PORTAL_ADDRESS, WOKB_ADDRESS, V3_FEE_TIERS, UNIFIED_DELEGATE_ABI, FLAP_PORTAL_ABI, ERC20_ABI, XLAYER_RPC_URL, XLAYER_CHAIN_ID, } from './constants.js';
|
|
12
|
+
import { quoteV2, quoteV3 } from '../../utils/quote-helpers.js';
|
|
12
13
|
// ========================================
|
|
13
14
|
// 路由地址获取
|
|
14
15
|
// ========================================
|
|
@@ -23,6 +24,88 @@ function getDefaultRouter(tradeType) {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
// ========================================
|
|
27
|
+
// 卖出报价(Token → OKB)
|
|
28
|
+
// ========================================
|
|
29
|
+
/**
|
|
30
|
+
* 获取卖出报价:预估卖出代币能得到多少 OKB
|
|
31
|
+
*
|
|
32
|
+
* @param totalSellAmount - 总卖出代币数量(wei)
|
|
33
|
+
* @param tokenAddress - 代币地址
|
|
34
|
+
* @param tradeType - 交易类型(FLAP/V2/V3)
|
|
35
|
+
* @param fee - V3 费率
|
|
36
|
+
* @param rpcUrl - RPC URL
|
|
37
|
+
* @returns 预估得到的 OKB 数量(wei)
|
|
38
|
+
*/
|
|
39
|
+
async function quoteSellOutput(totalSellAmount, tokenAddress, tradeType, fee, rpcUrl) {
|
|
40
|
+
if (totalSellAmount <= 0n)
|
|
41
|
+
return 0n;
|
|
42
|
+
const provider = new JsonRpcProvider(rpcUrl || XLAYER_RPC_URL, { chainId: XLAYER_CHAIN_ID, name: 'xlayer', ensAddress: undefined });
|
|
43
|
+
try {
|
|
44
|
+
switch (tradeType) {
|
|
45
|
+
case 'FLAP': {
|
|
46
|
+
// FLAP Portal 报价
|
|
47
|
+
const portalContract = new Contract(FLAP_PORTAL_ADDRESS, FLAP_PORTAL_ABI, provider);
|
|
48
|
+
// 先尝试 quoteSell(Token → OKB)
|
|
49
|
+
try {
|
|
50
|
+
const okbOut = await portalContract.quoteSell(tokenAddress, totalSellAmount);
|
|
51
|
+
console.log(`[Bundle Sell] FLAP quoteSell 成功: ${ethers.formatEther(okbOut)} OKB`);
|
|
52
|
+
return BigInt(okbOut.toString());
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
console.warn(`[Bundle Sell] FLAP quoteSell 失败,尝试 quoteExactInput...`);
|
|
56
|
+
}
|
|
57
|
+
// 再尝试 quoteExactInput
|
|
58
|
+
try {
|
|
59
|
+
const okbOut = await portalContract.quoteExactInput({
|
|
60
|
+
inputToken: tokenAddress,
|
|
61
|
+
outputToken: ethers.ZeroAddress, // OKB
|
|
62
|
+
inputAmount: totalSellAmount,
|
|
63
|
+
});
|
|
64
|
+
console.log(`[Bundle Sell] FLAP quoteExactInput 成功: ${ethers.formatEther(okbOut)} OKB`);
|
|
65
|
+
return BigInt(okbOut.toString());
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
console.warn(`[Bundle Sell] FLAP quoteExactInput 失败,尝试 V2...`);
|
|
69
|
+
}
|
|
70
|
+
// 回退到 V2
|
|
71
|
+
const v2Result = await quoteV2(provider, tokenAddress, WOKB_ADDRESS, totalSellAmount, 'XLAYER');
|
|
72
|
+
if (v2Result.amountOut > 0n) {
|
|
73
|
+
console.log(`[Bundle Sell] FLAP 回退 V2 报价成功: ${ethers.formatEther(v2Result.amountOut)} OKB`);
|
|
74
|
+
return v2Result.amountOut;
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'V2': {
|
|
79
|
+
const result = await quoteV2(provider, tokenAddress, WOKB_ADDRESS, totalSellAmount, 'XLAYER');
|
|
80
|
+
if (result.amountOut > 0n) {
|
|
81
|
+
console.log(`[Bundle Sell] V2 报价成功: ${ethers.formatEther(result.amountOut)} OKB`);
|
|
82
|
+
return result.amountOut;
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'V3': {
|
|
87
|
+
const result = await quoteV3(provider, tokenAddress, WOKB_ADDRESS, totalSellAmount, 'XLAYER', fee);
|
|
88
|
+
if (result.amountOut > 0n) {
|
|
89
|
+
console.log(`[Bundle Sell] V3 报价成功 (fee=${result.fee}): ${ethers.formatEther(result.amountOut)} OKB`);
|
|
90
|
+
return result.amountOut;
|
|
91
|
+
}
|
|
92
|
+
// 回退到 V2
|
|
93
|
+
const v2Result = await quoteV2(provider, tokenAddress, WOKB_ADDRESS, totalSellAmount, 'XLAYER');
|
|
94
|
+
if (v2Result.amountOut > 0n) {
|
|
95
|
+
console.log(`[Bundle Sell] V3 回退 V2 报价成功: ${ethers.formatEther(v2Result.amountOut)} OKB`);
|
|
96
|
+
return v2Result.amountOut;
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error(`[Bundle Sell] 报价失败:`, error);
|
|
104
|
+
}
|
|
105
|
+
console.warn(`[Bundle Sell] 无法获取报价,利润计算可能不准确`);
|
|
106
|
+
return 0n;
|
|
107
|
+
}
|
|
108
|
+
// ========================================
|
|
26
109
|
// 代币余额查询
|
|
27
110
|
// ========================================
|
|
28
111
|
async function getTokenBalances(wallets, tokenAddress) {
|
|
@@ -255,27 +338,60 @@ export async function bundleSell(params) {
|
|
|
255
338
|
}
|
|
256
339
|
const totalSellAmount = sellAmountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
257
340
|
const validSellCount = wallets.filter((_, i) => sellAmountsWei[i] > 0n).length;
|
|
258
|
-
// ✅
|
|
259
|
-
//
|
|
260
|
-
const
|
|
341
|
+
// ✅ 修复:获取卖出报价,基于预估的 OKB 输出计算利润
|
|
342
|
+
// 1. 先获取报价:卖出 totalSellAmount 代币能得到多少 OKB
|
|
343
|
+
const estimatedOkbOut = await quoteSellOutput(totalSellAmount, tokenAddress, tradeType, fee, config?.rpcUrl);
|
|
344
|
+
// 2. 基于预估的 OKB 输出计算利润(单边模式,isDoubleMode = false)
|
|
345
|
+
const profitAmount = estimatedOkbOut > 0n
|
|
346
|
+
? calculateProfitAmountByUserType(estimatedOkbOut, userType, false)
|
|
347
|
+
: 0n;
|
|
348
|
+
console.log(`[Bundle Sell] 卖出数量: ${ethers.formatUnits(totalSellAmount, tokenDecimals)} Token`);
|
|
349
|
+
console.log(`[Bundle Sell] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
|
|
350
|
+
console.log(`[Bundle Sell] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
|
|
261
351
|
// ========================================
|
|
262
352
|
// 同步构建授权和调用
|
|
263
353
|
// ========================================
|
|
264
354
|
const authorizations = signAuthorizationsWithNonces(allWallets, walletNonces, delegateAddress, mainWalletIndex === -1 ? 0 : mainWalletIndex);
|
|
265
|
-
// ✅
|
|
355
|
+
// ✅ 计算每个钱包的利润(按卖出数量比例分配)
|
|
356
|
+
const profitPerWallet = [];
|
|
357
|
+
if (profitAmount > 0n && totalSellAmount > 0n) {
|
|
358
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
359
|
+
const sellAmount = sellAmountsWei[i];
|
|
360
|
+
if (sellAmount > 0n) {
|
|
361
|
+
// 按比例分配利润
|
|
362
|
+
const walletProfit = (profitAmount * sellAmount) / totalSellAmount;
|
|
363
|
+
profitPerWallet.push(walletProfit);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
profitPerWallet.push(0n);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
372
|
+
profitPerWallet.push(0n);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// ✅ 构建调用列表(卖出调用 + 每个钱包的利润刮取)
|
|
266
376
|
const calls = [];
|
|
267
|
-
//
|
|
268
|
-
// 由于 EIP-7702 是单笔交易原子执行,利润刮取调用放在卖出调用之后
|
|
377
|
+
// 1. 先执行卖出,每个钱包卖出代币获得 OKB
|
|
269
378
|
const sellCalls = buildSellCalls(wallets, sellAmountsWei, tokenAddress, tradeType, actualRouter, fee);
|
|
270
379
|
calls.push(...sellCalls);
|
|
271
|
-
// ✅
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
380
|
+
// 2. ✅ 每个卖出钱包从自己的卖出所得中扣除利润
|
|
381
|
+
// 使用 transferAmount 传递精确金额
|
|
382
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
383
|
+
const walletProfit = profitPerWallet[i];
|
|
384
|
+
if (walletProfit > 0n) {
|
|
385
|
+
calls.push({
|
|
386
|
+
target: wallets[i].address,
|
|
387
|
+
allowFailure: false,
|
|
388
|
+
value: 0n,
|
|
389
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [
|
|
390
|
+
profitRecipient,
|
|
391
|
+
walletProfit,
|
|
392
|
+
]),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
279
395
|
}
|
|
280
396
|
// ========================================
|
|
281
397
|
// 同步构建并签名交易
|
|
@@ -286,7 +402,10 @@ export async function bundleSell(params) {
|
|
|
286
402
|
metadata: {
|
|
287
403
|
totalSellAmount: ethers.formatUnits(totalSellAmount, tokenDecimals),
|
|
288
404
|
walletCount: validSellCount,
|
|
289
|
-
|
|
405
|
+
estimatedOkbOut: ethers.formatEther(estimatedOkbOut), // ✅ 预估卖出所得 OKB
|
|
406
|
+
profitAmount: ethers.formatEther(profitAmount), // ✅ 利润是 OKB,用 18 位精度
|
|
407
|
+
profitRecipient,
|
|
408
|
+
userType,
|
|
290
409
|
},
|
|
291
410
|
};
|
|
292
411
|
}
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* 支持 FLAP 内盘、V2 和 V3 三种交易类型
|
|
10
10
|
* 只生成签名,由调用方决定如何提交
|
|
11
11
|
*/
|
|
12
|
-
import { ethers } from 'ethers';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { ethers, Contract, JsonRpcProvider } from 'ethers';
|
|
13
|
+
import { quoteV2, quoteV3 } from '../../utils/quote-helpers.js';
|
|
14
|
+
import { getCachedProvider, createWallet, signAuthorization, signAuthorizationsWithNonces, batchGetNonces, buildEIP7702TransactionSync, calculateProfitAmountByUserType, calculateProfitAmountByTxCount, getProfitRecipient, generateRandomWallets, } from './utils.js';
|
|
15
|
+
import { UNIFIED_DELEGATE_ADDRESS, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, FLAP_PORTAL_ADDRESS, WOKB_ADDRESS, V3_FEE_TIERS, UNIFIED_DELEGATE_ABI, FLAP_PORTAL_ABI, ERC20_ABI, XLAYER_RPC_URL, XLAYER_CHAIN_ID, } from './constants.js';
|
|
15
16
|
// ========================================
|
|
16
17
|
// 路由地址获取
|
|
17
18
|
// ========================================
|
|
@@ -26,6 +27,81 @@ function getDefaultRouter(tradeType) {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
// ========================================
|
|
30
|
+
// 卖出报价(Token → OKB)
|
|
31
|
+
// ========================================
|
|
32
|
+
/**
|
|
33
|
+
* 获取卖出报价:预估卖出代币能得到多少 OKB
|
|
34
|
+
*/
|
|
35
|
+
async function quoteSellOutput(sellAmount, tokenAddress, tradeType, fee, rpcUrl) {
|
|
36
|
+
if (sellAmount <= 0n)
|
|
37
|
+
return 0n;
|
|
38
|
+
const provider = new JsonRpcProvider(rpcUrl || XLAYER_RPC_URL, { chainId: XLAYER_CHAIN_ID, name: 'xlayer', ensAddress: undefined });
|
|
39
|
+
try {
|
|
40
|
+
switch (tradeType) {
|
|
41
|
+
case 'FLAP': {
|
|
42
|
+
// FLAP Portal 报价
|
|
43
|
+
const portalContract = new Contract(FLAP_PORTAL_ADDRESS, FLAP_PORTAL_ABI, provider);
|
|
44
|
+
// 先尝试 quoteSell
|
|
45
|
+
try {
|
|
46
|
+
const okbOut = await portalContract.quoteSell(tokenAddress, sellAmount);
|
|
47
|
+
console.log(`[Bundle Swap] FLAP quoteSell 成功: ${ethers.formatEther(okbOut)} OKB`);
|
|
48
|
+
return BigInt(okbOut.toString());
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
console.warn(`[Bundle Swap] FLAP quoteSell 失败,尝试 quoteExactInput...`);
|
|
52
|
+
}
|
|
53
|
+
// 再尝试 quoteExactInput
|
|
54
|
+
try {
|
|
55
|
+
const okbOut = await portalContract.quoteExactInput({
|
|
56
|
+
inputToken: tokenAddress,
|
|
57
|
+
outputToken: ethers.ZeroAddress,
|
|
58
|
+
inputAmount: sellAmount,
|
|
59
|
+
});
|
|
60
|
+
console.log(`[Bundle Swap] FLAP quoteExactInput 成功: ${ethers.formatEther(okbOut)} OKB`);
|
|
61
|
+
return BigInt(okbOut.toString());
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
console.warn(`[Bundle Swap] FLAP quoteExactInput 失败,尝试 V2...`);
|
|
65
|
+
}
|
|
66
|
+
// 回退到 V2
|
|
67
|
+
const v2Result = await quoteV2(provider, tokenAddress, WOKB_ADDRESS, sellAmount, 'XLAYER');
|
|
68
|
+
if (v2Result.amountOut > 0n) {
|
|
69
|
+
console.log(`[Bundle Swap] FLAP 回退 V2 报价成功: ${ethers.formatEther(v2Result.amountOut)} OKB`);
|
|
70
|
+
return v2Result.amountOut;
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'V2': {
|
|
75
|
+
const result = await quoteV2(provider, tokenAddress, WOKB_ADDRESS, sellAmount, 'XLAYER');
|
|
76
|
+
if (result.amountOut > 0n) {
|
|
77
|
+
console.log(`[Bundle Swap] V2 报价成功: ${ethers.formatEther(result.amountOut)} OKB`);
|
|
78
|
+
return result.amountOut;
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'V3': {
|
|
83
|
+
const result = await quoteV3(provider, tokenAddress, WOKB_ADDRESS, sellAmount, 'XLAYER', fee);
|
|
84
|
+
if (result.amountOut > 0n) {
|
|
85
|
+
console.log(`[Bundle Swap] V3 报价成功 (fee=${result.fee}): ${ethers.formatEther(result.amountOut)} OKB`);
|
|
86
|
+
return result.amountOut;
|
|
87
|
+
}
|
|
88
|
+
// 回退到 V2
|
|
89
|
+
const v2Result = await quoteV2(provider, tokenAddress, WOKB_ADDRESS, sellAmount, 'XLAYER');
|
|
90
|
+
if (v2Result.amountOut > 0n) {
|
|
91
|
+
console.log(`[Bundle Swap] V3 回退 V2 报价成功: ${ethers.formatEther(v2Result.amountOut)} OKB`);
|
|
92
|
+
return v2Result.amountOut;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(`[Bundle Swap] 报价失败:`, error);
|
|
100
|
+
}
|
|
101
|
+
console.warn(`[Bundle Swap] 无法获取报价,利润计算可能不准确`);
|
|
102
|
+
return 0n;
|
|
103
|
+
}
|
|
104
|
+
// ========================================
|
|
29
105
|
// 构建卖出并转账调用
|
|
30
106
|
// ========================================
|
|
31
107
|
function buildSellAndTransferCall(sellerWallet, sellAmount, tokenAddress, recipient, tradeType, routerAddress, fee) {
|
|
@@ -202,10 +278,16 @@ export async function bundleSwap(params) {
|
|
|
202
278
|
const hopWalletInfos = hopCount > 0 ? generateRandomWallets(hopCount) : [];
|
|
203
279
|
const hopWallets = hopWalletInfos.map(info => createWallet(info.privateKey, provider));
|
|
204
280
|
const sellAmountWei = ethers.parseUnits(sellAmount, tokenDecimals);
|
|
205
|
-
// ✅
|
|
206
|
-
const
|
|
207
|
-
|
|
281
|
+
// ✅ 修复:获取卖出报价,基于预估的 OKB 输出计算利润
|
|
282
|
+
const estimatedOkbOut = await quoteSellOutput(sellAmountWei, tokenAddress, tradeType, fee, config?.rpcUrl);
|
|
283
|
+
// 使用双边费率计算利润(换手 = 卖 + 买)
|
|
284
|
+
const profitAmount = estimatedOkbOut > 0n
|
|
285
|
+
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true) // isDoubleMode = true
|
|
286
|
+
: 0n;
|
|
208
287
|
const profitRecipient = getProfitRecipient(config);
|
|
288
|
+
console.log(`[Bundle Swap] 卖出数量: ${sellAmount} Token`);
|
|
289
|
+
console.log(`[Bundle Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
|
|
290
|
+
console.log(`[Bundle Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
|
|
209
291
|
// ========================================
|
|
210
292
|
// 并行获取所有异步数据
|
|
211
293
|
// ========================================
|
|
@@ -260,8 +342,11 @@ export async function bundleSwap(params) {
|
|
|
260
342
|
sellerAddress: sellerWallet.address,
|
|
261
343
|
buyerAddresses: [buyerWallet.address],
|
|
262
344
|
sellAmount: ethers.formatUnits(sellAmountWei, tokenDecimals),
|
|
345
|
+
estimatedOkbOut: ethers.formatEther(estimatedOkbOut), // ✅ 预估卖出所得 OKB
|
|
263
346
|
estimatedBuyAmount: '0',
|
|
264
|
-
profitAmount: ethers.formatEther(profitAmount),
|
|
347
|
+
profitAmount: ethers.formatEther(profitAmount), // ✅ 利润是 OKB
|
|
348
|
+
profitRecipient,
|
|
349
|
+
userType,
|
|
265
350
|
},
|
|
266
351
|
};
|
|
267
352
|
}
|
|
@@ -296,10 +381,16 @@ export async function bundleBatchSwap(params) {
|
|
|
296
381
|
const hopWalletInfos = hopCount > 0 ? generateRandomWallets(hopCount) : [];
|
|
297
382
|
const hopWallets = hopWalletInfos.map(info => createWallet(info.privateKey, provider));
|
|
298
383
|
const sellAmountWei = ethers.parseUnits(sellAmount, tokenDecimals);
|
|
299
|
-
// ✅
|
|
300
|
-
const
|
|
301
|
-
|
|
384
|
+
// ✅ 修复:获取卖出报价,基于预估的 OKB 输出计算利润
|
|
385
|
+
const estimatedOkbOut = await quoteSellOutput(sellAmountWei, tokenAddress, tradeType, fee, config?.rpcUrl);
|
|
386
|
+
// 使用双边费率计算利润(换手 = 卖 + 买)
|
|
387
|
+
const profitAmount = estimatedOkbOut > 0n
|
|
388
|
+
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true) // isDoubleMode = true
|
|
389
|
+
: 0n;
|
|
302
390
|
const profitRecipient = getProfitRecipient(config);
|
|
391
|
+
console.log(`[Bundle Batch Swap] 卖出数量: ${sellAmount} Token`);
|
|
392
|
+
console.log(`[Bundle Batch Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
|
|
393
|
+
console.log(`[Bundle Batch Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
|
|
303
394
|
// ========================================
|
|
304
395
|
// 并行获取所有异步数据
|
|
305
396
|
// ========================================
|
|
@@ -387,8 +478,11 @@ export async function bundleBatchSwap(params) {
|
|
|
387
478
|
sellerAddress: sellerWallet.address,
|
|
388
479
|
buyerAddresses: buyerWallets.map(w => w.address),
|
|
389
480
|
sellAmount: ethers.formatUnits(sellAmountWei, tokenDecimals),
|
|
481
|
+
estimatedOkbOut: ethers.formatEther(estimatedOkbOut), // ✅ 预估卖出所得 OKB
|
|
390
482
|
estimatedBuyAmount: '0',
|
|
391
|
-
profitAmount: ethers.formatEther(profitAmount),
|
|
483
|
+
profitAmount: ethers.formatEther(profitAmount), // ✅ 利润是 OKB
|
|
484
|
+
profitRecipient,
|
|
485
|
+
userType,
|
|
392
486
|
},
|
|
393
487
|
};
|
|
394
488
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export declare const XLAYER_CHAIN_ID = 196;
|
|
5
5
|
export declare const XLAYER_RPC_URL = "https://xlayerrpc.okx.com";
|
|
6
6
|
/** 已部署的 UnifiedDelegate 合约地址 */
|
|
7
|
-
export declare const UNIFIED_DELEGATE_ADDRESS = "
|
|
7
|
+
export declare const UNIFIED_DELEGATE_ADDRESS = "0x33C577Fa6A701E30F35B4B0DE3A361bfF400F0Cc";
|
|
8
8
|
/** Multicall3 地址 (标准部署) */
|
|
9
9
|
export declare const MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
10
10
|
/** WOKB 地址 */
|
|
@@ -42,6 +42,30 @@ export declare const PROFIT_CONFIG: {
|
|
|
42
42
|
export declare const DEFAULT_GAS_LIMIT = 2000000n;
|
|
43
43
|
export declare const DEFAULT_DEADLINE_MINUTES = 20;
|
|
44
44
|
export declare const DEFAULT_SLIPPAGE_BPS = 100;
|
|
45
|
+
export declare const GAS_ESTIMATE: {
|
|
46
|
+
/** 基础 gas(交易本身 + Multicall3 调用) */
|
|
47
|
+
readonly BASE: 80000n;
|
|
48
|
+
/** 每个 EIP-7702 授权的 gas */
|
|
49
|
+
readonly PER_AUTHORIZATION: 25000n;
|
|
50
|
+
/** 每个 Multicall 调用的基础 gas */
|
|
51
|
+
readonly PER_CALL_BASE: 50000n;
|
|
52
|
+
/** 买入操作额外 gas(DEX swap) */
|
|
53
|
+
readonly BUY_EXTRA: 150000n;
|
|
54
|
+
/** 卖出操作额外 gas(DEX swap + approve) */
|
|
55
|
+
readonly SELL_EXTRA: 180000n;
|
|
56
|
+
/** 转账操作 gas */
|
|
57
|
+
readonly TRANSFER: 30000n;
|
|
58
|
+
/** 创建代币额外 gas(普通创建) */
|
|
59
|
+
readonly CREATE_TOKEN: 500000n;
|
|
60
|
+
/** 毕业操作额外 gas(流动性迁移到 DEX,约 800 万) */
|
|
61
|
+
readonly GRADUATE: 8000000n;
|
|
62
|
+
/** 最小 gas limit */
|
|
63
|
+
readonly MIN: 200000n;
|
|
64
|
+
/** 最大 gas limit */
|
|
65
|
+
readonly MAX: 15000000n;
|
|
66
|
+
/** 安全系数(1.3 = 130%) */
|
|
67
|
+
readonly SAFETY_MULTIPLIER: 1.3;
|
|
68
|
+
};
|
|
45
69
|
export declare const V3_FEE_TIERS: {
|
|
46
70
|
readonly LOWEST: 100;
|
|
47
71
|
readonly LOW: 500;
|
|
@@ -49,7 +73,7 @@ export declare const V3_FEE_TIERS: {
|
|
|
49
73
|
readonly STANDARD: 3000;
|
|
50
74
|
readonly HIGH: 10000;
|
|
51
75
|
};
|
|
52
|
-
export declare const UNIFIED_DELEGATE_ABI: readonly ["function transferTo(address recipient) external payable", "function executeBuy(address router, address wokb, address tokenOut, uint24 fee, uint256 amountIn) external payable", "function executeSell(address router, address tokenIn, address wokb, uint24 fee, uint256 amountIn) external", "function executeSellAndTransfer(address router, address tokenIn, address wokb, uint24 fee, uint256 amountIn, address recipient) external", "function executeBuyV2(address router, address[] calldata path, uint256 amountIn) external payable", "function executeSellV2(address router, address[] calldata path, uint256 amountIn) external", "function executeSellAndTransferV2(address router, address[] calldata path, uint256 amountIn, address payable recipient) external", "function executeApprove(address token, address spender, uint256 amount) external", "function executeTransfer(address token, address to, uint256 amount) external", "function executeTransferAll(address token, address to) external", "function executeBatch((address target, uint256 value, bytes data)[] calls) external payable"];
|
|
76
|
+
export declare const UNIFIED_DELEGATE_ABI: readonly ["function transferTo(address recipient) external payable", "function transferAmount(address recipient, uint256 amount) external", "function executeBuy(address router, address wokb, address tokenOut, uint24 fee, uint256 amountIn) external payable", "function executeSell(address router, address tokenIn, address wokb, uint24 fee, uint256 amountIn) external", "function executeSellAndTransfer(address router, address tokenIn, address wokb, uint24 fee, uint256 amountIn, address recipient) external", "function executeBuyV2(address router, address[] calldata path, uint256 amountIn) external payable", "function executeSellV2(address router, address[] calldata path, uint256 amountIn) external", "function executeSellAndTransferV2(address router, address[] calldata path, uint256 amountIn, address payable recipient) external", "function executeApprove(address token, address spender, uint256 amount) external", "function executeTransfer(address token, address to, uint256 amount) external", "function executeTransferAll(address token, address to) external", "function executeBatch((address target, uint256 value, bytes data)[] calls) external payable"];
|
|
53
77
|
export declare const ERC20_ABI: readonly ["function approve(address spender, uint256 amount) external returns (bool)", "function transfer(address to, uint256 amount) external returns (bool)", "function transferFrom(address from, address to, uint256 amount) external returns (bool)", "function balanceOf(address account) external view returns (uint256)", "function allowance(address owner, address spender) external view returns (uint256)", "function decimals() external view returns (uint8)", "function symbol() external view returns (string)"];
|
|
54
78
|
export declare const MULTICALL3_ABI: readonly ["function aggregate3Value((address target, bool allowFailure, uint256 value, bytes callData)[] calls) external payable returns ((bool success, bytes returnData)[] returnData)"];
|
|
55
79
|
export declare const V3_ROUTER_ABI: readonly ["function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)", "function multicall(uint256 deadline, bytes[] data) external payable returns (bytes[] results)", "function unwrapWETH9(uint256 amountMinimum, address recipient) external payable", "function refundETH() external payable"];
|
|
@@ -10,7 +10,7 @@ export const XLAYER_RPC_URL = 'https://xlayerrpc.okx.com';
|
|
|
10
10
|
// 合约地址
|
|
11
11
|
// ========================================
|
|
12
12
|
/** 已部署的 UnifiedDelegate 合约地址 */
|
|
13
|
-
export const UNIFIED_DELEGATE_ADDRESS = '
|
|
13
|
+
export const UNIFIED_DELEGATE_ADDRESS = '0x33C577Fa6A701E30F35B4B0DE3A361bfF400F0Cc';
|
|
14
14
|
/** Multicall3 地址 (标准部署) */
|
|
15
15
|
export const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
|
|
16
16
|
/** WOKB 地址 */
|
|
@@ -58,6 +58,33 @@ export const DEFAULT_GAS_LIMIT = 2000000n;
|
|
|
58
58
|
export const DEFAULT_DEADLINE_MINUTES = 20;
|
|
59
59
|
export const DEFAULT_SLIPPAGE_BPS = 100; // 1%
|
|
60
60
|
// ========================================
|
|
61
|
+
// Gas 估算配置(动态计算)
|
|
62
|
+
// ========================================
|
|
63
|
+
export const GAS_ESTIMATE = {
|
|
64
|
+
/** 基础 gas(交易本身 + Multicall3 调用) */
|
|
65
|
+
BASE: 80000n,
|
|
66
|
+
/** 每个 EIP-7702 授权的 gas */
|
|
67
|
+
PER_AUTHORIZATION: 25000n,
|
|
68
|
+
/** 每个 Multicall 调用的基础 gas */
|
|
69
|
+
PER_CALL_BASE: 50000n,
|
|
70
|
+
/** 买入操作额外 gas(DEX swap) */
|
|
71
|
+
BUY_EXTRA: 150000n,
|
|
72
|
+
/** 卖出操作额外 gas(DEX swap + approve) */
|
|
73
|
+
SELL_EXTRA: 180000n,
|
|
74
|
+
/** 转账操作 gas */
|
|
75
|
+
TRANSFER: 30000n,
|
|
76
|
+
/** 创建代币额外 gas(普通创建) */
|
|
77
|
+
CREATE_TOKEN: 500000n,
|
|
78
|
+
/** 毕业操作额外 gas(流动性迁移到 DEX,约 800 万) */
|
|
79
|
+
GRADUATE: 8000000n,
|
|
80
|
+
/** 最小 gas limit */
|
|
81
|
+
MIN: 200000n,
|
|
82
|
+
/** 最大 gas limit */
|
|
83
|
+
MAX: 15000000n,
|
|
84
|
+
/** 安全系数(1.3 = 130%) */
|
|
85
|
+
SAFETY_MULTIPLIER: 1.3,
|
|
86
|
+
};
|
|
87
|
+
// ========================================
|
|
61
88
|
// 费率档位
|
|
62
89
|
// ========================================
|
|
63
90
|
export const V3_FEE_TIERS = {
|
|
@@ -75,6 +102,7 @@ export const UNIFIED_DELEGATE_ABI = [
|
|
|
75
102
|
// 转账功能
|
|
76
103
|
// ========================================
|
|
77
104
|
'function transferTo(address recipient) external payable',
|
|
105
|
+
'function transferAmount(address recipient, uint256 amount) external',
|
|
78
106
|
// ========================================
|
|
79
107
|
// V3 买卖功能 (PotatoSwap V3 / DYORSwap)
|
|
80
108
|
// ========================================
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
export * from './constants.js';
|
|
17
17
|
export * from './types.js';
|
|
18
|
-
export { bigintToBytes, addressToBytes, hexToBytes, trimLeadingZeros, getCachedProvider, clearProviderCache, createWallet, generateRandomWallet, generateRandomWallets, signAuthorization, signAuthorizationWithNonce, signAuthorizationsBatch, buildEIP7702Transaction, calculateProfitAmount, calculateProfitAmountByUserType, calculateProfitAmountByTxCount, getProfitRateBps, getProfitRateBpsDouble, getProfitRecipient, getDeadline, delay, type UserType, } from './utils.js';
|
|
18
|
+
export { bigintToBytes, addressToBytes, hexToBytes, trimLeadingZeros, getCachedProvider, clearProviderCache, createWallet, generateRandomWallet, generateRandomWallets, signAuthorization, signAuthorizationWithNonce, signAuthorizationsBatch, buildEIP7702Transaction, estimateGasLimit, estimateGasLimitSimple, calculateProfitAmount, calculateProfitAmountByUserType, calculateProfitAmountByTxCount, getProfitRateBps, getProfitRateBpsDouble, getProfitRecipient, getDeadline, delay, type UserType, type OperationType, } from './utils.js';
|
|
19
19
|
export { bundleBuy, } from './bundle-buy.js';
|
|
20
20
|
export { bundleSell, } from './bundle-sell.js';
|
|
21
21
|
export { bundleSwap, bundleBatchSwap, bundleMultiSwap, type BundleMultiSwapParams, type BundleMultiSwapResult, } from './bundle-swap.js';
|
|
@@ -24,7 +24,11 @@ export * from './types.js';
|
|
|
24
24
|
// ============================================================================
|
|
25
25
|
// 工具函数
|
|
26
26
|
// ============================================================================
|
|
27
|
-
export { bigintToBytes, addressToBytes, hexToBytes, trimLeadingZeros, getCachedProvider, clearProviderCache, createWallet, generateRandomWallet, generateRandomWallets, signAuthorization, signAuthorizationWithNonce, signAuthorizationsBatch, buildEIP7702Transaction,
|
|
27
|
+
export { bigintToBytes, addressToBytes, hexToBytes, trimLeadingZeros, getCachedProvider, clearProviderCache, createWallet, generateRandomWallet, generateRandomWallets, signAuthorization, signAuthorizationWithNonce, signAuthorizationsBatch, buildEIP7702Transaction,
|
|
28
|
+
// Gas 估算
|
|
29
|
+
estimateGasLimit, estimateGasLimitSimple,
|
|
30
|
+
// 利润计算
|
|
31
|
+
calculateProfitAmount, calculateProfitAmountByUserType, calculateProfitAmountByTxCount, getProfitRateBps, getProfitRateBpsDouble, getProfitRecipient, getDeadline, delay, } from './utils.js';
|
|
28
32
|
// ============================================================================
|
|
29
33
|
// 批量买入
|
|
30
34
|
// ============================================================================
|
|
@@ -97,9 +97,18 @@ export interface BundleSellResult {
|
|
|
97
97
|
txHash?: string;
|
|
98
98
|
/** 元数据 */
|
|
99
99
|
metadata: {
|
|
100
|
+
/** 总卖出代币数量 */
|
|
100
101
|
totalSellAmount: string;
|
|
102
|
+
/** 参与卖出的钱包数量 */
|
|
101
103
|
walletCount: number;
|
|
104
|
+
/** 预估卖出获得的 OKB 数量 */
|
|
105
|
+
estimatedOkbOut: string;
|
|
106
|
+
/** 利润金额(OKB) */
|
|
102
107
|
profitAmount: string;
|
|
108
|
+
/** 利润接收地址 */
|
|
109
|
+
profitRecipient: string;
|
|
110
|
+
/** 用户类型 */
|
|
111
|
+
userType: string;
|
|
103
112
|
};
|
|
104
113
|
}
|
|
105
114
|
export interface BundleApproveParams {
|
|
@@ -186,8 +195,15 @@ export interface BundleSwapResult {
|
|
|
186
195
|
sellerAddress: string;
|
|
187
196
|
buyerAddresses: string[];
|
|
188
197
|
sellAmount: string;
|
|
198
|
+
/** 预估卖出获得的 OKB 数量 */
|
|
199
|
+
estimatedOkbOut?: string;
|
|
189
200
|
estimatedBuyAmount: string;
|
|
201
|
+
/** 利润金额(OKB) */
|
|
190
202
|
profitAmount: string;
|
|
203
|
+
/** 利润接收地址 */
|
|
204
|
+
profitRecipient?: string;
|
|
205
|
+
/** 用户类型 */
|
|
206
|
+
userType?: string;
|
|
191
207
|
};
|
|
192
208
|
}
|
|
193
209
|
export interface MultiHopTransferParams {
|
|
@@ -362,6 +378,8 @@ export interface BundleCreateBuyParams extends CreateTokenParams {
|
|
|
362
378
|
buyerPrivateKeys: string[];
|
|
363
379
|
/** 每个买入钱包的金额 (OKB) */
|
|
364
380
|
buyAmounts: string[];
|
|
381
|
+
/** 用户类型(影响利润率) */
|
|
382
|
+
userType?: 'v0' | 'v1' | 'default';
|
|
365
383
|
/** 配置 */
|
|
366
384
|
config?: EIP7702Config;
|
|
367
385
|
}
|
|
@@ -391,6 +409,8 @@ export interface BundleCreateToDexParams extends CreateTokenParams {
|
|
|
391
409
|
dexBuyerPrivateKeys?: string[];
|
|
392
410
|
/** 外盘买入金额 (OKB) */
|
|
393
411
|
dexBuyAmounts?: string[];
|
|
412
|
+
/** 用户类型(影响利润率) */
|
|
413
|
+
userType?: 'v0' | 'v1' | 'default';
|
|
394
414
|
/** 配置 */
|
|
395
415
|
config?: EIP7702Config;
|
|
396
416
|
}
|
|
@@ -438,6 +458,8 @@ export interface BundleGraduateBuyParams {
|
|
|
438
458
|
dexAddresses?: string[];
|
|
439
459
|
/** 毕业金额(OKB) */
|
|
440
460
|
graduationAmount?: number;
|
|
461
|
+
/** 用户类型(影响利润率) */
|
|
462
|
+
userType?: 'v0' | 'v1' | 'default';
|
|
441
463
|
/** 配置 */
|
|
442
464
|
config?: EIP7702Config;
|
|
443
465
|
}
|
|
@@ -9,6 +9,24 @@ export declare function hexToBytes(hex: string): Uint8Array;
|
|
|
9
9
|
export declare function trimLeadingZeros(bytes: Uint8Array): Uint8Array;
|
|
10
10
|
export declare function getCachedProvider(rpcUrl?: string): JsonRpcProvider;
|
|
11
11
|
export declare function clearProviderCache(): void;
|
|
12
|
+
/**
|
|
13
|
+
* 操作类型(用于 gas 估算)
|
|
14
|
+
*/
|
|
15
|
+
export type OperationType = 'transfer' | 'buy' | 'sell' | 'swap' | 'create' | 'approve' | 'graduate' | 'unknown';
|
|
16
|
+
/**
|
|
17
|
+
* 动态估算 Gas Limit
|
|
18
|
+
*
|
|
19
|
+
* @param authorizationCount 授权数量
|
|
20
|
+
* @param calls Multicall 调用列表
|
|
21
|
+
* @param configGasLimit 配置的 gas limit(可选,优先使用)
|
|
22
|
+
* @returns 估算的 gas limit
|
|
23
|
+
*/
|
|
24
|
+
export declare function estimateGasLimit(authorizationCount: number, calls: Call3Value[], configGasLimit?: bigint): bigint;
|
|
25
|
+
/**
|
|
26
|
+
* 简化版 gas 估算(仅根据调用数量)
|
|
27
|
+
* 适用于无法分析 callData 的场景
|
|
28
|
+
*/
|
|
29
|
+
export declare function estimateGasLimitSimple(authorizationCount: number, callCount: number, operationType?: 'simple' | 'buy' | 'sell' | 'swap' | 'create' | 'graduate', configGasLimit?: bigint): bigint;
|
|
12
30
|
export declare function createWallet(privateKey: string, provider?: JsonRpcProvider): Wallet;
|
|
13
31
|
export declare function generateRandomWallet(): GeneratedWallet;
|
|
14
32
|
export declare function generateRandomWallets(count: number): GeneratedWallet[];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { ethers, Wallet, JsonRpcProvider } from 'ethers';
|
|
5
5
|
import * as rlp from '@ethereumjs/rlp';
|
|
6
|
-
import { XLAYER_CHAIN_ID, XLAYER_RPC_URL, MULTICALL3_ADDRESS,
|
|
6
|
+
import { XLAYER_CHAIN_ID, XLAYER_RPC_URL, MULTICALL3_ADDRESS, GAS_ESTIMATE, EIP7702_TX_TYPE, EIP7702_AUTH_PREFIX, MULTICALL3_ABI, PROFIT_CONFIG, DEFAULT_DEADLINE_MINUTES, } from './constants.js';
|
|
7
7
|
// ========================================
|
|
8
8
|
// 字节转换工具
|
|
9
9
|
// ========================================
|
|
@@ -49,6 +49,157 @@ export function getCachedProvider(rpcUrl = XLAYER_RPC_URL) {
|
|
|
49
49
|
export function clearProviderCache() {
|
|
50
50
|
providerCache.clear();
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* 分析 callData 推断操作类型
|
|
54
|
+
*/
|
|
55
|
+
function inferOperationType(callData) {
|
|
56
|
+
if (!callData || callData === '0x')
|
|
57
|
+
return 'transfer';
|
|
58
|
+
// 根据函数签名推断操作类型
|
|
59
|
+
const selector = callData.slice(0, 10).toLowerCase();
|
|
60
|
+
// 常见的函数选择器
|
|
61
|
+
const selectors = {
|
|
62
|
+
// 转账
|
|
63
|
+
'0xa9059cbb': 'transfer', // transfer(address,uint256)
|
|
64
|
+
// 买入相关
|
|
65
|
+
'0x7ff36ab5': 'buy', // swapExactETHForTokens
|
|
66
|
+
'0xb6f9de95': 'buy', // swapExactETHForTokensSupportingFeeOnTransferTokens
|
|
67
|
+
'0x414bf389': 'buy', // exactInputSingle (V3)
|
|
68
|
+
'0xc04b8d59': 'buy', // exactInput (V3)
|
|
69
|
+
// 卖出相关
|
|
70
|
+
'0x18cbafe5': 'sell', // swapExactTokensForETH
|
|
71
|
+
'0x791ac947': 'sell', // swapExactTokensForETHSupportingFeeOnTransferTokens
|
|
72
|
+
'0xdb3e2198': 'sell', // exactOutputSingle (V3)
|
|
73
|
+
// 授权
|
|
74
|
+
'0x095ea7b3': 'approve', // approve(address,uint256)
|
|
75
|
+
// 创建代币
|
|
76
|
+
'0x': 'create', // 需要更精确的匹配
|
|
77
|
+
};
|
|
78
|
+
// UnifiedDelegate 函数选择器(计算得到的 4 字节选择器)
|
|
79
|
+
const delegateSelectors = {
|
|
80
|
+
'0x3edd1128': 'transfer', // transferTo(address)
|
|
81
|
+
'0x2ada9a96': 'transfer', // transferAmount(address,uint256)
|
|
82
|
+
'0x2cdaa6d3': 'buy', // executeBuy(address,address,address,uint24,uint256)
|
|
83
|
+
'0x2e87f7de': 'buy', // executeBuyV2(address,address[],uint256)
|
|
84
|
+
'0x6fcda3a1': 'sell', // executeSell(address,address,address,uint24,uint256)
|
|
85
|
+
'0x0a3e9e95': 'sell', // executeSellV2(address,address[],uint256)
|
|
86
|
+
'0x72c18b75': 'sell', // executeSellAndTransfer(address,address,address,uint24,uint256,address)
|
|
87
|
+
'0x9db6b375': 'sell', // executeSellAndTransferV2(address,address[],uint256,address)
|
|
88
|
+
};
|
|
89
|
+
// 先检查标准选择器
|
|
90
|
+
if (selectors[selector]) {
|
|
91
|
+
return selectors[selector];
|
|
92
|
+
}
|
|
93
|
+
// 检查 UnifiedDelegate 选择器
|
|
94
|
+
if (delegateSelectors[selector]) {
|
|
95
|
+
return delegateSelectors[selector];
|
|
96
|
+
}
|
|
97
|
+
// 检查 callData 长度和模式
|
|
98
|
+
if (callData.length < 20) {
|
|
99
|
+
return 'transfer';
|
|
100
|
+
}
|
|
101
|
+
// 默认根据 callData 长度估算
|
|
102
|
+
if (callData.length > 500) {
|
|
103
|
+
return 'swap'; // 复杂调用可能是 swap
|
|
104
|
+
}
|
|
105
|
+
return 'unknown';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 根据操作类型获取额外 gas
|
|
109
|
+
*/
|
|
110
|
+
function getExtraGasForOperation(opType) {
|
|
111
|
+
switch (opType) {
|
|
112
|
+
case 'transfer':
|
|
113
|
+
return GAS_ESTIMATE.TRANSFER;
|
|
114
|
+
case 'buy':
|
|
115
|
+
return GAS_ESTIMATE.BUY_EXTRA;
|
|
116
|
+
case 'sell':
|
|
117
|
+
return GAS_ESTIMATE.SELL_EXTRA;
|
|
118
|
+
case 'swap':
|
|
119
|
+
return GAS_ESTIMATE.BUY_EXTRA + GAS_ESTIMATE.SELL_EXTRA;
|
|
120
|
+
case 'approve':
|
|
121
|
+
return 50000n;
|
|
122
|
+
case 'create':
|
|
123
|
+
return GAS_ESTIMATE.CREATE_TOKEN;
|
|
124
|
+
default:
|
|
125
|
+
return GAS_ESTIMATE.PER_CALL_BASE;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 动态估算 Gas Limit
|
|
130
|
+
*
|
|
131
|
+
* @param authorizationCount 授权数量
|
|
132
|
+
* @param calls Multicall 调用列表
|
|
133
|
+
* @param configGasLimit 配置的 gas limit(可选,优先使用)
|
|
134
|
+
* @returns 估算的 gas limit
|
|
135
|
+
*/
|
|
136
|
+
export function estimateGasLimit(authorizationCount, calls, configGasLimit) {
|
|
137
|
+
// 如果明确指定了 gasLimit,优先使用
|
|
138
|
+
if (configGasLimit && configGasLimit > 0n) {
|
|
139
|
+
return configGasLimit;
|
|
140
|
+
}
|
|
141
|
+
// 基础 gas
|
|
142
|
+
let estimated = GAS_ESTIMATE.BASE;
|
|
143
|
+
// 授权 gas
|
|
144
|
+
estimated += BigInt(authorizationCount) * GAS_ESTIMATE.PER_AUTHORIZATION;
|
|
145
|
+
// 每个调用的 gas
|
|
146
|
+
for (const call of calls) {
|
|
147
|
+
// 基础调用 gas
|
|
148
|
+
estimated += GAS_ESTIMATE.PER_CALL_BASE;
|
|
149
|
+
// 根据操作类型添加额外 gas
|
|
150
|
+
const opType = inferOperationType(call.callData);
|
|
151
|
+
estimated += getExtraGasForOperation(opType);
|
|
152
|
+
}
|
|
153
|
+
// 应用安全系数
|
|
154
|
+
const withSafety = BigInt(Math.ceil(Number(estimated) * GAS_ESTIMATE.SAFETY_MULTIPLIER));
|
|
155
|
+
// 限制在最小和最大值之间
|
|
156
|
+
if (withSafety < GAS_ESTIMATE.MIN) {
|
|
157
|
+
return GAS_ESTIMATE.MIN;
|
|
158
|
+
}
|
|
159
|
+
if (withSafety > GAS_ESTIMATE.MAX) {
|
|
160
|
+
return GAS_ESTIMATE.MAX;
|
|
161
|
+
}
|
|
162
|
+
return withSafety;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 简化版 gas 估算(仅根据调用数量)
|
|
166
|
+
* 适用于无法分析 callData 的场景
|
|
167
|
+
*/
|
|
168
|
+
export function estimateGasLimitSimple(authorizationCount, callCount, operationType = 'simple', configGasLimit) {
|
|
169
|
+
if (configGasLimit && configGasLimit > 0n) {
|
|
170
|
+
return configGasLimit;
|
|
171
|
+
}
|
|
172
|
+
// 毕业操作需要固定的高 gas(约 800 万)
|
|
173
|
+
if (operationType === 'graduate') {
|
|
174
|
+
return GAS_ESTIMATE.GRADUATE;
|
|
175
|
+
}
|
|
176
|
+
let estimated = GAS_ESTIMATE.BASE;
|
|
177
|
+
estimated += BigInt(authorizationCount) * GAS_ESTIMATE.PER_AUTHORIZATION;
|
|
178
|
+
estimated += BigInt(callCount) * GAS_ESTIMATE.PER_CALL_BASE;
|
|
179
|
+
// 根据操作类型添加额外 gas
|
|
180
|
+
switch (operationType) {
|
|
181
|
+
case 'buy':
|
|
182
|
+
estimated += BigInt(callCount) * GAS_ESTIMATE.BUY_EXTRA;
|
|
183
|
+
break;
|
|
184
|
+
case 'sell':
|
|
185
|
+
estimated += BigInt(callCount) * GAS_ESTIMATE.SELL_EXTRA;
|
|
186
|
+
break;
|
|
187
|
+
case 'swap':
|
|
188
|
+
estimated += BigInt(callCount) * (GAS_ESTIMATE.BUY_EXTRA + GAS_ESTIMATE.SELL_EXTRA);
|
|
189
|
+
break;
|
|
190
|
+
case 'create':
|
|
191
|
+
estimated += GAS_ESTIMATE.CREATE_TOKEN;
|
|
192
|
+
break;
|
|
193
|
+
default:
|
|
194
|
+
estimated += BigInt(callCount) * GAS_ESTIMATE.TRANSFER;
|
|
195
|
+
}
|
|
196
|
+
const withSafety = BigInt(Math.ceil(Number(estimated) * GAS_ESTIMATE.SAFETY_MULTIPLIER));
|
|
197
|
+
if (withSafety < GAS_ESTIMATE.MIN)
|
|
198
|
+
return GAS_ESTIMATE.MIN;
|
|
199
|
+
if (withSafety > GAS_ESTIMATE.MAX)
|
|
200
|
+
return GAS_ESTIMATE.MAX;
|
|
201
|
+
return withSafety;
|
|
202
|
+
}
|
|
52
203
|
// ========================================
|
|
53
204
|
// 钱包工具
|
|
54
205
|
// ========================================
|
|
@@ -152,7 +303,8 @@ export async function batchGetNonces(addresses, provider) {
|
|
|
152
303
|
export async function buildEIP7702Transaction(mainWallet, authorizations, calls, totalValue, config) {
|
|
153
304
|
const provider = getCachedProvider(config?.rpcUrl);
|
|
154
305
|
const chainId = config?.chainId ?? XLAYER_CHAIN_ID;
|
|
155
|
-
|
|
306
|
+
// ✅ 动态 Gas 估算:根据授权数量和调用复杂度自动计算
|
|
307
|
+
const gasLimit = estimateGasLimit(authorizations.length, calls, config?.gasLimit);
|
|
156
308
|
// ✅ 并行获取 nonce 和 fee data
|
|
157
309
|
const [nonce, feeData] = await Promise.all([
|
|
158
310
|
provider.getTransactionCount(mainWallet.address, 'pending'),
|
|
@@ -214,7 +366,8 @@ export async function buildEIP7702Transaction(mainWallet, authorizations, calls,
|
|
|
214
366
|
*/
|
|
215
367
|
export function buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, nonce, feeData, config) {
|
|
216
368
|
const chainId = config?.chainId ?? XLAYER_CHAIN_ID;
|
|
217
|
-
|
|
369
|
+
// ✅ 动态 Gas 估算:根据授权数量和调用复杂度自动计算
|
|
370
|
+
const gasLimit = estimateGasLimit(authorizations.length, calls, config?.gasLimit);
|
|
218
371
|
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas || ethers.parseUnits('1', 'gwei');
|
|
219
372
|
const maxFeePerGas = feeData.maxFeePerGas || ethers.parseUnits('50', 'gwei');
|
|
220
373
|
const multicall3 = new ethers.Interface(MULTICALL3_ABI);
|