four-flap-meme-sdk 1.7.59 → 1.7.60
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.
|
@@ -330,15 +330,11 @@ export async function bundleSwap(params) {
|
|
|
330
330
|
const sellerNonce = nonces[0];
|
|
331
331
|
const buyerNonce = nonces[1];
|
|
332
332
|
// ========================================
|
|
333
|
-
//
|
|
333
|
+
// 同步签署授权(只需要卖家和买家,不需要 hop wallets)
|
|
334
334
|
// ========================================
|
|
335
335
|
const authorizations = [];
|
|
336
336
|
// 卖家授权(交易发起者,nonce +1)
|
|
337
337
|
authorizations.push(signAuthorization(sellerWallet, delegateAddress, BigInt(sellerNonce + 1)));
|
|
338
|
-
// 中间钱包授权(新生成的钱包,nonce 都是 0)
|
|
339
|
-
for (const hopWallet of hopWallets) {
|
|
340
|
-
authorizations.push(signAuthorization(hopWallet, delegateAddress, 0n));
|
|
341
|
-
}
|
|
342
338
|
// 买家授权
|
|
343
339
|
authorizations.push(signAuthorization(buyerWallet, delegateAddress, BigInt(buyerNonce)));
|
|
344
340
|
// 等待所有授权签名完成
|
|
@@ -354,40 +350,26 @@ export async function bundleSwap(params) {
|
|
|
354
350
|
throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
|
|
355
351
|
}
|
|
356
352
|
const calls = [];
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
// 1.
|
|
353
|
+
// ========================================
|
|
354
|
+
// ✅ 修复:普通换手模式逻辑
|
|
355
|
+
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
356
|
+
// 2. 买家用自己预存的 OKB 买入代币
|
|
357
|
+
// 3. 利润从买家的 OKB 中扣除
|
|
358
|
+
// ========================================
|
|
359
|
+
// 1. 卖家卖出(OKB 留在卖家账户,不转给买家)
|
|
360
360
|
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
361
361
|
calls.push(sellCall);
|
|
362
|
-
// 2.
|
|
362
|
+
// 2. 利润刮取(从买家余额扣除,在买入之前)
|
|
363
363
|
if (profitAmount > 0n) {
|
|
364
364
|
calls.push({
|
|
365
|
-
target:
|
|
365
|
+
target: buyerWallet.address, // ✅ 改为从买家扣利润
|
|
366
366
|
allowFailure: false,
|
|
367
367
|
value: 0n,
|
|
368
368
|
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
369
369
|
});
|
|
370
370
|
}
|
|
371
|
-
// 3.
|
|
372
|
-
|
|
373
|
-
target: sellerWallet.address,
|
|
374
|
-
allowFailure: false,
|
|
375
|
-
value: 0n,
|
|
376
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [firstRecipient]),
|
|
377
|
-
});
|
|
378
|
-
// 4. 中间钱包之间的转账(如果有)
|
|
379
|
-
for (let i = 0; i < hopWallets.length; i++) {
|
|
380
|
-
const nextRecipient = i < hopWallets.length - 1 ? hopWallets[i + 1].address : buyerWallet.address;
|
|
381
|
-
calls.push({
|
|
382
|
-
target: hopWallets[i].address,
|
|
383
|
-
allowFailure: false,
|
|
384
|
-
value: 0n,
|
|
385
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [nextRecipient]),
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
// 5. 买家买入
|
|
389
|
-
// ✅ FLAP 模式必须使用实际金额(Portal 需要 inputAmount 和 msg.value 匹配)
|
|
390
|
-
// ✅ V2/V3 模式使用 0n(让合约使用钱包全部余额,更可靠)
|
|
371
|
+
// 3. 买家用自己预存的 OKB 买入
|
|
372
|
+
// ✅ FLAP 模式必须使用实际金额,V2/V3 模式使用 0n(钱包全部余额)
|
|
391
373
|
const buyAmountForCall = tradeType === 'FLAP' ? buyAmount : 0n;
|
|
392
374
|
const buyCall = buildBuyCallInternal(buyerWallet, buyAmountForCall, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
393
375
|
calls.push(buyCall);
|
|
@@ -473,102 +455,55 @@ export async function bundleBatchSwap(params) {
|
|
|
473
455
|
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
474
456
|
const portalInterface = new ethers.Interface(FLAP_PORTAL_ABI);
|
|
475
457
|
const calls = [];
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
//
|
|
458
|
+
// ========================================
|
|
459
|
+
// ✅ 修复:普通换手模式逻辑(一卖多买)
|
|
460
|
+
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
461
|
+
// 2. 所有买家用自己预存的 OKB 买入代币
|
|
462
|
+
// 3. 利润从第一个买家的 OKB 中扣除
|
|
463
|
+
// ========================================
|
|
464
|
+
// 1. 卖家卖出(OKB 留在卖家账户,不转给买家)
|
|
480
465
|
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
481
466
|
calls.push(sellCall);
|
|
482
|
-
//
|
|
467
|
+
// 2. 利润刮取(从第一个买家扣除,在买入之前)
|
|
483
468
|
if (profitAmount > 0n) {
|
|
484
469
|
calls.push({
|
|
485
|
-
target:
|
|
470
|
+
target: buyerWallets[0].address, // ✅ 改为从第一个买家扣利润
|
|
486
471
|
allowFailure: false,
|
|
487
472
|
value: 0n,
|
|
488
473
|
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
489
474
|
});
|
|
490
475
|
}
|
|
491
|
-
//
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
allowFailure: false,
|
|
495
|
-
value: 0n,
|
|
496
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [firstRecipient]),
|
|
497
|
-
});
|
|
498
|
-
// 中间钱包转账(如果有)
|
|
499
|
-
for (let i = 0; i < hopWallets.length - 1; i++) {
|
|
500
|
-
calls.push({
|
|
501
|
-
target: hopWallets[i].address,
|
|
502
|
-
allowFailure: false,
|
|
503
|
-
value: 0n,
|
|
504
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [hopWallets[i + 1].address]),
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
// 最后一个中间钱包转给第一个买家
|
|
508
|
-
if (hopWallets.length > 0) {
|
|
509
|
-
calls.push({
|
|
510
|
-
target: hopWallets[hopWallets.length - 1].address,
|
|
511
|
-
allowFailure: false,
|
|
512
|
-
value: 0n,
|
|
513
|
-
callData: delegateInterface.encodeFunctionData('transferTo', [buyerWallets[0].address]),
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
// ✅ 2. 资金分配计算(多买家时需要分发)
|
|
517
|
-
// 注意:如果报价失败 estimatedOkbOut=0,使用 0n 让买家用全部收到的余额买入
|
|
518
|
-
const okbForBuyers = estimatedOkbOut > profitAmount ? estimatedOkbOut - profitAmount : 0n;
|
|
476
|
+
// ✅ 3. 计算每个买家的买入金额
|
|
477
|
+
// 买家使用自己预存的 OKB,按比例买入对应价值的代币
|
|
478
|
+
// 如果前端传入了 buyerRatios,按比例计算;否则均分
|
|
519
479
|
let buyAmountsPerBuyer;
|
|
520
480
|
if (buyerWallets.length === 1) {
|
|
521
|
-
// 单买家:使用 0n
|
|
481
|
+
// 单买家:使用 0n 表示用全部余额买入
|
|
522
482
|
buyAmountsPerBuyer = [0n];
|
|
523
483
|
}
|
|
524
|
-
else if (buyerRatios && buyerRatios.length === buyerWallets.length
|
|
525
|
-
// 多买家 +
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
else if (okbForBuyers > 0n) {
|
|
531
|
-
// 多买家 + 有报价:均分
|
|
532
|
-
const avgAmount = okbForBuyers / BigInt(buyerWallets.length);
|
|
533
|
-
buyAmountsPerBuyer = buyerWallets.map(() => avgAmount);
|
|
484
|
+
else if (buyerRatios && buyerRatios.length === buyerWallets.length) {
|
|
485
|
+
// 多买家 + 有比例:每个买家使用 0n(用自己全部余额买入)
|
|
486
|
+
// 前端应该根据比例给每个买家预存对应金额的 OKB
|
|
487
|
+
buyAmountsPerBuyer = buyerWallets.map(() => 0n);
|
|
488
|
+
console.log(`[bundleBatchSwap] 按比例买入模式,买家将使用自己预存的 OKB 买入`);
|
|
534
489
|
}
|
|
535
490
|
else {
|
|
536
|
-
//
|
|
491
|
+
// 多买家无比例:每个买家使用全部余额买入
|
|
537
492
|
buyAmountsPerBuyer = buyerWallets.map(() => 0n);
|
|
538
|
-
console.warn(`[bundleBatchSwap] 报价失败,买家将使用全部余额买入`);
|
|
539
493
|
}
|
|
540
|
-
//
|
|
541
|
-
if (buyerWallets.length > 1 && okbForBuyers > 0n) {
|
|
542
|
-
for (let i = 1; i < buyerWallets.length; i++) {
|
|
543
|
-
if (buyAmountsPerBuyer[i] > 0n) {
|
|
544
|
-
calls.push({
|
|
545
|
-
target: buyerWallets[0].address, // 第一个买家分发
|
|
546
|
-
allowFailure: false,
|
|
547
|
-
value: 0n,
|
|
548
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [buyerWallets[i].address, buyAmountsPerBuyer[i]]),
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
// ✅ 4. 所有买家买入
|
|
554
|
-
// FLAP 模式:必须使用实际金额(Portal 需要 inputAmount 和 msg.value 匹配)
|
|
555
|
-
// V2/V3 模式:使用 0n 让合约用钱包全部余额买入(更可靠)
|
|
494
|
+
// 4. 所有买家买入(使用自己预存的 OKB)
|
|
556
495
|
for (let i = 0; i < buyerWallets.length; i++) {
|
|
557
|
-
// FLAP
|
|
496
|
+
// FLAP 模式使用分配的金额(0n = 用全部余额),V2/V3 模式也使用 0n
|
|
558
497
|
const buyAmountForCall = tradeType === 'FLAP' ? buyAmountsPerBuyer[i] : 0n;
|
|
559
498
|
const buyCall = buildBuyCallInternal(buyerWallets[i], buyAmountForCall, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
560
499
|
calls.push(buyCall);
|
|
561
500
|
}
|
|
562
501
|
// ========================================
|
|
563
|
-
//
|
|
502
|
+
// 同步签署授权(只需要卖家和买家,不需要 hop wallets)
|
|
564
503
|
// ========================================
|
|
565
504
|
const authPromises = [];
|
|
566
505
|
// 卖家授权(交易发起者,nonce +1)
|
|
567
506
|
authPromises.push(signAuthorization(sellerWallet, delegateAddress, BigInt(sellerNonce + 1)));
|
|
568
|
-
// 中间钱包授权(nonce = 0)
|
|
569
|
-
for (const hopWallet of hopWallets) {
|
|
570
|
-
authPromises.push(signAuthorization(hopWallet, delegateAddress, 0n));
|
|
571
|
-
}
|
|
572
507
|
// 买家授权
|
|
573
508
|
for (let i = 0; i < buyerWallets.length; i++) {
|
|
574
509
|
authPromises.push(signAuthorization(buyerWallets[i], delegateAddress, BigInt(buyerNonces[i])));
|
|
@@ -13,9 +13,57 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 只生成签名,由调用方决定如何提交
|
|
15
15
|
*/
|
|
16
|
-
import { ethers, Contract } from 'ethers';
|
|
16
|
+
import { ethers, Contract, JsonRpcProvider } from 'ethers';
|
|
17
17
|
import { getCachedProvider, createWallet, signAuthorizationsWithNonces, batchGetNonces, buildEIP7702TransactionSync, generateRandomWallets, calculateProfitAmountByUserType, getProfitRecipient, } from './utils.js';
|
|
18
|
-
import { UNIFIED_DELEGATE_ADDRESS, UNIFIED_DELEGATE_ABI, ERC20_ABI, } from './constants.js';
|
|
18
|
+
import { UNIFIED_DELEGATE_ADDRESS, UNIFIED_DELEGATE_ABI, ERC20_ABI, FLAP_PORTAL_ADDRESS, FLAP_PORTAL_ABI, WOKB_ADDRESS, XLAYER_RPC_URL, XLAYER_CHAIN_ID, } from './constants.js';
|
|
19
|
+
import { quoteV2 } from '../../utils/quote-helpers.js';
|
|
20
|
+
// ========================================
|
|
21
|
+
// ERC20 → OKB 报价(用于利润计算)
|
|
22
|
+
// ========================================
|
|
23
|
+
/**
|
|
24
|
+
* 获取 ERC20 代币卖出能得到多少 OKB(用于利润计算)
|
|
25
|
+
*/
|
|
26
|
+
async function quoteTokenToOkb(tokenAddress, tokenAmount, rpcUrl) {
|
|
27
|
+
if (tokenAmount <= 0n)
|
|
28
|
+
return 0n;
|
|
29
|
+
const provider = new JsonRpcProvider(rpcUrl || XLAYER_RPC_URL, { chainId: XLAYER_CHAIN_ID, name: 'xlayer', ensAddress: undefined });
|
|
30
|
+
try {
|
|
31
|
+
// 1. 优先使用 FLAP Portal quoteExactInput
|
|
32
|
+
const portalContract = new Contract(FLAP_PORTAL_ADDRESS, FLAP_PORTAL_ABI, provider);
|
|
33
|
+
try {
|
|
34
|
+
const okbOut = await portalContract.quoteExactInput.staticCall({
|
|
35
|
+
inputToken: tokenAddress,
|
|
36
|
+
outputToken: ethers.ZeroAddress,
|
|
37
|
+
inputAmount: tokenAmount,
|
|
38
|
+
});
|
|
39
|
+
console.log(`[quoteTokenToOkb] FLAP quoteExactInput 成功: ${ethers.formatEther(okbOut)} OKB`);
|
|
40
|
+
return BigInt(okbOut.toString());
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// 继续尝试其他方法
|
|
44
|
+
}
|
|
45
|
+
// 2. 回退到 previewSell
|
|
46
|
+
try {
|
|
47
|
+
const okbOut = await portalContract.previewSell(tokenAddress, tokenAmount);
|
|
48
|
+
console.log(`[quoteTokenToOkb] FLAP previewSell 成功: ${ethers.formatEther(okbOut)} OKB`);
|
|
49
|
+
return BigInt(okbOut.toString());
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// 继续尝试其他方法
|
|
53
|
+
}
|
|
54
|
+
// 3. 回退到 V2 Router
|
|
55
|
+
const v2Result = await quoteV2(provider, tokenAddress, WOKB_ADDRESS, tokenAmount, 'XLAYER');
|
|
56
|
+
if (v2Result.amountOut > 0n) {
|
|
57
|
+
console.log(`[quoteTokenToOkb] V2 报价成功: ${ethers.formatEther(v2Result.amountOut)} OKB`);
|
|
58
|
+
return v2Result.amountOut;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.warn(`[quoteTokenToOkb] 报价失败:`, error);
|
|
63
|
+
}
|
|
64
|
+
console.warn(`[quoteTokenToOkb] 无法获取报价,利润计算可能不准确`);
|
|
65
|
+
return 0n;
|
|
66
|
+
}
|
|
19
67
|
/**
|
|
20
68
|
* 分散资金 - 一对多分发
|
|
21
69
|
*
|
|
@@ -81,11 +129,19 @@ export async function disperse(params) {
|
|
|
81
129
|
// 解析金额
|
|
82
130
|
const amountsWei = amounts.map(amt => isNative ? ethers.parseEther(amt) : ethers.parseUnits(amt, tokenDecimals));
|
|
83
131
|
const totalAmount = amountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
84
|
-
// ✅ 计算利润(ERC20
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
132
|
+
// ✅ 计算利润(ERC20 分发时获取 OKB 报价,以 OKB 形式刮取)
|
|
133
|
+
let profitAmountOkb = 0n;
|
|
134
|
+
if (!isNative && tokenAddress) {
|
|
135
|
+
// 获取 ERC20 代币对应的 OKB 报价
|
|
136
|
+
const okbValueEstimate = await quoteTokenToOkb(tokenAddress, totalAmount, config?.rpcUrl);
|
|
137
|
+
if (okbValueEstimate > 0n) {
|
|
138
|
+
profitAmountOkb = calculateProfitAmountByUserType(okbValueEstimate, userType, false); // 单边模式
|
|
139
|
+
console.log(`[disperse] ERC20 利润刮取: totalToken=${ethers.formatUnits(totalAmount, tokenDecimals)}, okbValue=${ethers.formatEther(okbValueEstimate)}, profitOkb=${ethers.formatEther(profitAmountOkb)}`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.warn(`[disperse] 无法获取 ERC20 报价,跳过利润刮取`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
89
145
|
// ========================================
|
|
90
146
|
// 并行获取数据
|
|
91
147
|
// ========================================
|
|
@@ -210,20 +266,16 @@ export async function disperse(params) {
|
|
|
210
266
|
}
|
|
211
267
|
}
|
|
212
268
|
// ========================================
|
|
213
|
-
// 添加利润转账调用(ERC20
|
|
269
|
+
// 添加利润转账调用(ERC20 分发时,以 OKB 形式刮取)
|
|
214
270
|
// ========================================
|
|
215
|
-
if (
|
|
271
|
+
if (profitAmountOkb > 0n && !isNative) {
|
|
216
272
|
calls.push({
|
|
217
273
|
target: mainWallet.address,
|
|
218
274
|
allowFailure: false,
|
|
219
275
|
value: 0n,
|
|
220
|
-
callData: delegateInterface.encodeFunctionData('
|
|
221
|
-
tokenAddress,
|
|
222
|
-
profitRecipient,
|
|
223
|
-
profitAmount,
|
|
224
|
-
]),
|
|
276
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
225
277
|
});
|
|
226
|
-
console.log(`[disperse] 已添加利润转账调用: ${ethers.
|
|
278
|
+
console.log(`[disperse] 已添加利润转账调用: ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
227
279
|
}
|
|
228
280
|
// ========================================
|
|
229
281
|
// 构建交易
|
|
@@ -239,7 +291,7 @@ export async function disperse(params) {
|
|
|
239
291
|
senderAddress: mainWallet.address,
|
|
240
292
|
recipientCount: recipients.length,
|
|
241
293
|
totalAmount: isNative ? ethers.formatEther(totalAmount) : ethers.formatUnits(totalAmount, tokenDecimals),
|
|
242
|
-
profitAmount:
|
|
294
|
+
profitAmount: ethers.formatEther(profitAmountOkb), // ✅ 以 OKB 计
|
|
243
295
|
isNative,
|
|
244
296
|
tokenAddress: tokenAddress ?? null,
|
|
245
297
|
hopCount,
|
|
@@ -344,12 +396,20 @@ export async function sweep(params) {
|
|
|
344
396
|
return (afterReserve * percentBigInt) / 100n;
|
|
345
397
|
});
|
|
346
398
|
const totalAmount = transferAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
347
|
-
// ✅ 计算利润(ERC20
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
399
|
+
// ✅ 计算利润(ERC20 归集时获取 OKB 报价,以 OKB 形式刮取)
|
|
400
|
+
let profitAmountOkb = 0n;
|
|
401
|
+
if (!isNative && tokenAddress) {
|
|
402
|
+
// 获取 ERC20 代币对应的 OKB 报价
|
|
403
|
+
const okbValueEstimate = await quoteTokenToOkb(tokenAddress, totalAmount, config?.rpcUrl);
|
|
404
|
+
if (okbValueEstimate > 0n) {
|
|
405
|
+
profitAmountOkb = calculateProfitAmountByUserType(okbValueEstimate, userType, false); // 单边模式
|
|
406
|
+
console.log(`[sweep] ERC20 利润刮取: totalToken=${ethers.formatUnits(totalAmount, tokenDecimals)}, okbValue=${ethers.formatEther(okbValueEstimate)}, profitOkb=${ethers.formatEther(profitAmountOkb)}`);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
console.warn(`[sweep] 无法获取 ERC20 报价,跳过利润刮取`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// ✅ 找出归集金额最大的钱包(用于支付 OKB 利润)
|
|
353
413
|
let maxSweepIndex = -1;
|
|
354
414
|
let maxSweepAmount = 0n;
|
|
355
415
|
for (let i = 0; i < transferAmounts.length; i++) {
|
|
@@ -384,22 +444,18 @@ export async function sweep(params) {
|
|
|
384
444
|
});
|
|
385
445
|
}
|
|
386
446
|
else {
|
|
387
|
-
// ✅ ERC20
|
|
388
|
-
if (
|
|
389
|
-
// 1.
|
|
447
|
+
// ✅ ERC20 归集:最大归集钱包负责转 OKB 利润
|
|
448
|
+
if (profitAmountOkb > 0n && i === maxSweepIndex) {
|
|
449
|
+
// 1. 先转 OKB 利润
|
|
390
450
|
calls.push({
|
|
391
451
|
target: sourceWallet.address,
|
|
392
452
|
allowFailure: false,
|
|
393
453
|
value: 0n,
|
|
394
|
-
callData: delegateInterface.encodeFunctionData('
|
|
395
|
-
tokenAddress,
|
|
396
|
-
profitRecipient,
|
|
397
|
-
profitAmount,
|
|
398
|
-
]),
|
|
454
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
399
455
|
});
|
|
400
|
-
console.log(`[sweep] 钱包 ${i} 转利润: ${ethers.
|
|
456
|
+
console.log(`[sweep] 钱包 ${i} 转利润: ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
401
457
|
}
|
|
402
|
-
// 2.
|
|
458
|
+
// 2. 再归集 ERC20 代币到目标地址
|
|
403
459
|
calls.push({
|
|
404
460
|
target: sourceWallet.address,
|
|
405
461
|
allowFailure: false,
|
|
@@ -446,21 +502,17 @@ export async function sweep(params) {
|
|
|
446
502
|
}
|
|
447
503
|
else {
|
|
448
504
|
// ERC20 多跳
|
|
449
|
-
// ✅
|
|
450
|
-
if (
|
|
505
|
+
// ✅ 最大归集钱包负责转 OKB 利润(在归集前)
|
|
506
|
+
if (profitAmountOkb > 0n && i === maxSweepIndex) {
|
|
451
507
|
calls.push({
|
|
452
508
|
target: sourceWallet.address,
|
|
453
509
|
allowFailure: false,
|
|
454
510
|
value: 0n,
|
|
455
|
-
callData: delegateInterface.encodeFunctionData('
|
|
456
|
-
tokenAddress,
|
|
457
|
-
profitRecipient,
|
|
458
|
-
profitAmount,
|
|
459
|
-
]),
|
|
511
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
460
512
|
});
|
|
461
|
-
console.log(`[sweep] 钱包 ${i} 转利润(多跳): ${ethers.
|
|
513
|
+
console.log(`[sweep] 钱包 ${i} 转利润(多跳): ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
462
514
|
}
|
|
463
|
-
// 源钱包 →
|
|
515
|
+
// 源钱包 → 第一个中间钱包(ERC20 代币)
|
|
464
516
|
calls.push({
|
|
465
517
|
target: sourceWallet.address,
|
|
466
518
|
allowFailure: false,
|
|
@@ -531,7 +583,7 @@ export async function sweep(params) {
|
|
|
531
583
|
targetAddress,
|
|
532
584
|
sourceCount: sourceWallets.length,
|
|
533
585
|
totalAmount: isNative ? ethers.formatEther(totalAmount) : ethers.formatUnits(totalAmount, tokenDecimals),
|
|
534
|
-
profitAmount:
|
|
586
|
+
profitAmount: ethers.formatEther(profitAmountOkb), // ✅ 以 OKB 计
|
|
535
587
|
isNative,
|
|
536
588
|
tokenAddress: tokenAddress ?? null,
|
|
537
589
|
hopCount,
|
|
@@ -626,12 +678,20 @@ export async function pairwiseTransfer(params) {
|
|
|
626
678
|
// 解析金额为 Wei
|
|
627
679
|
const amountsWei = normalizedAmounts.map(amt => isNative ? ethers.parseEther(amt) : ethers.parseUnits(amt, tokenDecimals));
|
|
628
680
|
const totalAmount = amountsWei.reduce((sum, amt) => sum + amt, 0n);
|
|
629
|
-
// ✅ 计算利润(ERC20
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
681
|
+
// ✅ 计算利润(ERC20 多对多转账时获取 OKB 报价,以 OKB 形式刮取)
|
|
682
|
+
let profitAmountOkb = 0n;
|
|
683
|
+
if (!isNative && tokenAddress) {
|
|
684
|
+
// 获取 ERC20 代币对应的 OKB 报价
|
|
685
|
+
const okbValueEstimate = await quoteTokenToOkb(tokenAddress, totalAmount, config?.rpcUrl);
|
|
686
|
+
if (okbValueEstimate > 0n) {
|
|
687
|
+
profitAmountOkb = calculateProfitAmountByUserType(okbValueEstimate, userType, false); // 单边模式
|
|
688
|
+
console.log(`[pairwiseTransfer] ERC20 利润刮取: totalToken=${ethers.formatUnits(totalAmount, tokenDecimals)}, okbValue=${ethers.formatEther(okbValueEstimate)}, profitOkb=${ethers.formatEther(profitAmountOkb)}`);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
console.warn(`[pairwiseTransfer] 无法获取 ERC20 报价,跳过利润刮取`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// ✅ 找出转账金额最大的发送者(用于支付 OKB 利润)
|
|
635
695
|
let maxTransferIndex = 0;
|
|
636
696
|
let maxTransferAmount = amountsWei[0];
|
|
637
697
|
for (let i = 1; i < amountsWei.length; i++) {
|
|
@@ -679,20 +739,17 @@ export async function pairwiseTransfer(params) {
|
|
|
679
739
|
});
|
|
680
740
|
}
|
|
681
741
|
else {
|
|
682
|
-
// ✅ ERC20
|
|
683
|
-
if (
|
|
742
|
+
// ✅ ERC20 多对多:最大转账钱包负责转 OKB 利润
|
|
743
|
+
if (profitAmountOkb > 0n && i === maxTransferIndex) {
|
|
684
744
|
calls.push({
|
|
685
745
|
target: senderWallet.address,
|
|
686
746
|
allowFailure: false,
|
|
687
747
|
value: 0n,
|
|
688
|
-
callData: delegateInterface.encodeFunctionData('
|
|
689
|
-
tokenAddress,
|
|
690
|
-
profitRecipient,
|
|
691
|
-
profitAmount,
|
|
692
|
-
]),
|
|
748
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
693
749
|
});
|
|
694
|
-
console.log(`[pairwiseTransfer] 发送者 ${i} 转利润: ${ethers.
|
|
750
|
+
console.log(`[pairwiseTransfer] 发送者 ${i} 转利润: ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
695
751
|
}
|
|
752
|
+
// ERC20 代币转账
|
|
696
753
|
calls.push({
|
|
697
754
|
target: senderWallet.address,
|
|
698
755
|
allowFailure: false,
|
|
@@ -739,21 +796,17 @@ export async function pairwiseTransfer(params) {
|
|
|
739
796
|
}
|
|
740
797
|
else {
|
|
741
798
|
// ERC20 多跳
|
|
742
|
-
// ✅
|
|
743
|
-
if (
|
|
799
|
+
// ✅ 最大转账钱包负责转 OKB 利润(在转账前)
|
|
800
|
+
if (profitAmountOkb > 0n && i === maxTransferIndex) {
|
|
744
801
|
calls.push({
|
|
745
802
|
target: senderWallet.address,
|
|
746
803
|
allowFailure: false,
|
|
747
804
|
value: 0n,
|
|
748
|
-
callData: delegateInterface.encodeFunctionData('
|
|
749
|
-
tokenAddress,
|
|
750
|
-
profitRecipient,
|
|
751
|
-
profitAmount,
|
|
752
|
-
]),
|
|
805
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmountOkb]),
|
|
753
806
|
});
|
|
754
|
-
console.log(`[pairwiseTransfer] 发送者 ${i} 转利润(多跳): ${ethers.
|
|
807
|
+
console.log(`[pairwiseTransfer] 发送者 ${i} 转利润(多跳): ${ethers.formatEther(profitAmountOkb)} OKB → ${profitRecipient}`);
|
|
755
808
|
}
|
|
756
|
-
// sender →
|
|
809
|
+
// sender → 第一个中间钱包(ERC20 代币)
|
|
757
810
|
calls.push({
|
|
758
811
|
target: senderWallet.address,
|
|
759
812
|
allowFailure: false,
|
|
@@ -819,7 +872,7 @@ export async function pairwiseTransfer(params) {
|
|
|
819
872
|
metadata: {
|
|
820
873
|
pairCount,
|
|
821
874
|
totalAmount: isNative ? ethers.formatEther(totalAmount) : ethers.formatUnits(totalAmount, tokenDecimals),
|
|
822
|
-
profitAmount:
|
|
875
|
+
profitAmount: ethers.formatEther(profitAmountOkb), // ✅ 以 OKB 计
|
|
823
876
|
isNative,
|
|
824
877
|
tokenAddress: tokenAddress ?? null,
|
|
825
878
|
hopCount,
|