four-flap-meme-sdk 1.7.61 → 1.7.63
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.
|
@@ -13,10 +13,22 @@ import type { BundleSwapParams as BaseBundleSwapParams, BundleBatchSwapParams as
|
|
|
13
13
|
export interface BundleSwapParams extends BaseBundleSwapParams {
|
|
14
14
|
/** 用户类型(影响利润率) */
|
|
15
15
|
userType?: UserType;
|
|
16
|
+
/**
|
|
17
|
+
* 是否使用资金利用率模式(默认 false)
|
|
18
|
+
* - false(普通模式): 卖家卖出 OKB 留在卖家账户,买家用自己预存的 OKB 买入
|
|
19
|
+
* - true(资金利用率模式): 卖家卖出的 OKB 通过多跳转账给买家,买家用收到的 OKB 买入
|
|
20
|
+
*/
|
|
21
|
+
useFundUtilization?: boolean;
|
|
16
22
|
}
|
|
17
23
|
export interface BundleBatchSwapParams extends BaseBundleBatchSwapParams {
|
|
18
24
|
/** 用户类型(影响利润率) */
|
|
19
25
|
userType?: UserType;
|
|
26
|
+
/**
|
|
27
|
+
* 是否使用资金利用率模式(默认 false)
|
|
28
|
+
* - false(普通模式): 卖家卖出 OKB 留在卖家账户,买家用自己预存的 OKB 买入
|
|
29
|
+
* - true(资金利用率模式): 卖家卖出的 OKB 通过多跳转账给买家,买家用收到的 OKB 买入
|
|
30
|
+
*/
|
|
31
|
+
useFundUtilization?: boolean;
|
|
20
32
|
}
|
|
21
33
|
import { type UserType } from './utils.js';
|
|
22
34
|
/**
|
|
@@ -291,8 +291,7 @@ tokenAddress, tradeType, routerAddress, fee) {
|
|
|
291
291
|
* });
|
|
292
292
|
*/
|
|
293
293
|
export async function bundleSwap(params) {
|
|
294
|
-
const { sellerPrivateKey, buyerPrivateKey, tokenAddress, tokenDecimals = 18, sellAmount, hopCount =
|
|
295
|
-
tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
294
|
+
const { sellerPrivateKey, buyerPrivateKey, tokenAddress, tokenDecimals = 18, sellAmount, hopCount = 0, tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', useFundUtilization = false, // ✅ 默认普通模式
|
|
296
295
|
config, } = params;
|
|
297
296
|
const provider = getCachedProvider(config?.rpcUrl);
|
|
298
297
|
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
@@ -304,84 +303,140 @@ export async function bundleSwap(params) {
|
|
|
304
303
|
// ========================================
|
|
305
304
|
const sellerWallet = createWallet(sellerPrivateKey, provider);
|
|
306
305
|
const buyerWallet = createWallet(buyerPrivateKey, provider);
|
|
307
|
-
//
|
|
308
|
-
const
|
|
306
|
+
// 资金利用率模式才需要中间钱包
|
|
307
|
+
const actualHopCount = useFundUtilization ? hopCount : 0;
|
|
308
|
+
const hopWalletInfos = actualHopCount > 0 ? generateRandomWallets(actualHopCount) : [];
|
|
309
309
|
const hopWallets = hopWalletInfos.map(info => createWallet(info.privateKey, provider));
|
|
310
310
|
const sellAmountWei = ethers.parseUnits(truncateDecimals(sellAmount, tokenDecimals), tokenDecimals);
|
|
311
|
-
//
|
|
311
|
+
// 获取卖出报价
|
|
312
312
|
const estimatedOkbOut = await quoteSellOutput(sellAmountWei, tokenAddress, tradeType, fee, config?.rpcUrl);
|
|
313
313
|
// 使用双边费率计算利润(换手 = 卖 + 买)
|
|
314
314
|
const profitAmount = estimatedOkbOut > 0n
|
|
315
|
-
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true)
|
|
315
|
+
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true)
|
|
316
316
|
: 0n;
|
|
317
317
|
const profitRecipient = getProfitRecipient(config);
|
|
318
|
+
// 计算买入金额
|
|
319
|
+
const buyAmountAfterProfit = estimatedOkbOut > profitAmount
|
|
320
|
+
? estimatedOkbOut - profitAmount
|
|
321
|
+
: 0n;
|
|
322
|
+
const modeLabel = useFundUtilization ? '资金利用率' : '普通';
|
|
323
|
+
console.log(`[Bundle Swap] 模式: ${modeLabel}`);
|
|
318
324
|
console.log(`[Bundle Swap] 卖出数量: ${sellAmount} Token`);
|
|
319
325
|
console.log(`[Bundle Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
|
|
320
326
|
console.log(`[Bundle Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
|
|
327
|
+
console.log(`[Bundle Swap] 买入金额: ${ethers.formatEther(buyAmountAfterProfit)} OKB`);
|
|
321
328
|
// ========================================
|
|
322
|
-
//
|
|
329
|
+
// 并行获取数据
|
|
323
330
|
// ========================================
|
|
324
331
|
const [nonces, feeData, buyerBalance] = await Promise.all([
|
|
325
|
-
// 并行获取 seller 和 buyer 的 nonce
|
|
326
332
|
batchGetNonces([sellerWallet.address, buyerWallet.address], provider),
|
|
327
|
-
// 获取 gas 费用数据
|
|
328
333
|
provider.getFeeData(),
|
|
329
|
-
// 获取买家的 OKB 余额(普通模式下买家用自己的钱)
|
|
330
334
|
provider.getBalance(buyerWallet.address),
|
|
331
335
|
]);
|
|
332
336
|
const sellerNonce = nonces[0];
|
|
333
337
|
const buyerNonce = nonces[1];
|
|
334
|
-
console.log(`[Bundle Swap] 买家 OKB 余额: ${ethers.formatEther(buyerBalance)} OKB`);
|
|
335
338
|
// ========================================
|
|
336
|
-
//
|
|
339
|
+
// 验证余额
|
|
337
340
|
// ========================================
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
341
|
+
if (tradeType === 'FLAP' && buyAmountAfterProfit <= 0n) {
|
|
342
|
+
throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
|
|
343
|
+
}
|
|
344
|
+
// 普通模式:买家需要预存足够的 OKB
|
|
345
|
+
if (!useFundUtilization && tradeType === 'FLAP') {
|
|
346
|
+
if (buyerBalance < buyAmountAfterProfit) {
|
|
347
|
+
throw new Error(`普通模式需要买家钱包有足够的 OKB,需要 ${ethers.formatEther(buyAmountAfterProfit)},当前 ${ethers.formatEther(buyerBalance)}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
345
350
|
// ========================================
|
|
346
|
-
//
|
|
351
|
+
// 签署授权
|
|
347
352
|
// ========================================
|
|
348
|
-
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
353
|
+
const authPromises = [];
|
|
354
|
+
// 卖家授权(交易发起者,nonce +1)
|
|
355
|
+
authPromises.push(signAuthorization(sellerWallet, delegateAddress, BigInt(sellerNonce + 1)));
|
|
356
|
+
// 买家授权
|
|
357
|
+
authPromises.push(signAuthorization(buyerWallet, delegateAddress, BigInt(buyerNonce)));
|
|
358
|
+
// 中间钱包授权(资金利用率模式)
|
|
359
|
+
for (const hopWallet of hopWallets) {
|
|
360
|
+
authPromises.push(signAuthorization(hopWallet, delegateAddress, 0n));
|
|
355
361
|
}
|
|
356
|
-
const
|
|
362
|
+
const authorizations = await Promise.all(authPromises);
|
|
357
363
|
// ========================================
|
|
358
|
-
//
|
|
359
|
-
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
360
|
-
// 2. 买家用自己预存的 OKB 买入代币
|
|
361
|
-
// 3. 利润从买家的 OKB 中扣除
|
|
364
|
+
// 构建调用
|
|
362
365
|
// ========================================
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
366
|
+
const calls = [];
|
|
367
|
+
if (useFundUtilization) {
|
|
368
|
+
// ========================================
|
|
369
|
+
// ✅ 资金利用率模式
|
|
370
|
+
// 1. 卖家卖出代币 → 获得 OKB
|
|
371
|
+
// 2. OKB 多跳转账 → 中间钱包1 → 中间钱包2 → ... → 买家
|
|
372
|
+
// 3. 利润从卖家 OKB 中扣除
|
|
373
|
+
// 4. 买家用收到的 OKB 买入
|
|
374
|
+
// ========================================
|
|
375
|
+
console.log(`[Bundle Swap] 资金利用率模式,hopCount=${actualHopCount}`);
|
|
376
|
+
// 1. 卖家卖出
|
|
377
|
+
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
378
|
+
calls.push(sellCall);
|
|
379
|
+
// 2. 利润刮取(从卖家余额扣除)
|
|
380
|
+
if (profitAmount > 0n) {
|
|
381
|
+
calls.push({
|
|
382
|
+
target: sellerWallet.address,
|
|
383
|
+
allowFailure: false,
|
|
384
|
+
value: 0n,
|
|
385
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
// 3. OKB 多跳转账(卖家 → hop1 → hop2 → ... → 买家)
|
|
389
|
+
const firstRecipient = hopWallets.length > 0 ? hopWallets[0].address : buyerWallet.address;
|
|
390
|
+
// 卖家 → 第一个接收者
|
|
368
391
|
calls.push({
|
|
369
|
-
target:
|
|
392
|
+
target: sellerWallet.address,
|
|
370
393
|
allowFailure: false,
|
|
371
394
|
value: 0n,
|
|
372
|
-
callData: delegateInterface.encodeFunctionData('
|
|
395
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [firstRecipient]),
|
|
373
396
|
});
|
|
397
|
+
// 中间钱包之间转账
|
|
398
|
+
for (let i = 0; i < hopWallets.length; i++) {
|
|
399
|
+
const nextRecipient = i < hopWallets.length - 1 ? hopWallets[i + 1].address : buyerWallet.address;
|
|
400
|
+
calls.push({
|
|
401
|
+
target: hopWallets[i].address,
|
|
402
|
+
allowFailure: false,
|
|
403
|
+
value: 0n,
|
|
404
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [nextRecipient]),
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
// 4. 买家买入(使用收到的全部 OKB)
|
|
408
|
+
const buyCall = buildBuyCallInternal(buyerWallet, tradeType === 'FLAP' ? buyAmountAfterProfit : 0n, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
409
|
+
calls.push(buyCall);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
// ========================================
|
|
413
|
+
// ✅ 普通模式
|
|
414
|
+
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
415
|
+
// 2. 买家用自己预存的 OKB 买入代币
|
|
416
|
+
// 3. 利润从买家的 OKB 中扣除
|
|
417
|
+
// ========================================
|
|
418
|
+
console.log(`[Bundle Swap] 普通模式`);
|
|
419
|
+
// 1. 卖家卖出(OKB 留在卖家账户)
|
|
420
|
+
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
421
|
+
calls.push(sellCall);
|
|
422
|
+
// 2. 利润刮取(从买家余额扣除)
|
|
423
|
+
if (profitAmount > 0n) {
|
|
424
|
+
calls.push({
|
|
425
|
+
target: buyerWallet.address,
|
|
426
|
+
allowFailure: false,
|
|
427
|
+
value: 0n,
|
|
428
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
// 3. 买家用自己的 OKB 买入
|
|
432
|
+
const buyAmountForCall = tradeType === 'FLAP' ? buyAmountAfterProfit : 0n;
|
|
433
|
+
const buyCall = buildBuyCallInternal(buyerWallet, buyAmountForCall, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
434
|
+
calls.push(buyCall);
|
|
374
435
|
}
|
|
375
|
-
// 3. 买家用自己预存的 OKB 买入
|
|
376
|
-
// ✅ FLAP 模式使用买家实际余额(扣除利润后),V2/V3 模式使用 0n(合约自动使用全部余额)
|
|
377
|
-
const buyAmountForCall = tradeType === 'FLAP' ? buyerAvailable : 0n;
|
|
378
|
-
const buyCall = buildBuyCallInternal(buyerWallet, buyAmountForCall, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
379
|
-
calls.push(buyCall);
|
|
380
436
|
// ========================================
|
|
381
|
-
//
|
|
437
|
+
// 构建交易
|
|
382
438
|
// ========================================
|
|
383
|
-
const signedTransaction = buildEIP7702TransactionSync(sellerWallet,
|
|
384
|
-
sellerNonce, feeData, config);
|
|
439
|
+
const signedTransaction = buildEIP7702TransactionSync(sellerWallet, authorizations, calls, 0n, sellerNonce, feeData, config);
|
|
385
440
|
return {
|
|
386
441
|
signedTransaction,
|
|
387
442
|
hopWallets: hopWalletInfos,
|
|
@@ -389,11 +444,12 @@ export async function bundleSwap(params) {
|
|
|
389
444
|
sellerAddress: sellerWallet.address,
|
|
390
445
|
buyerAddresses: [buyerWallet.address],
|
|
391
446
|
sellAmount: ethers.formatUnits(sellAmountWei, tokenDecimals),
|
|
392
|
-
estimatedOkbOut: ethers.formatEther(estimatedOkbOut),
|
|
393
|
-
estimatedBuyAmount:
|
|
394
|
-
profitAmount: ethers.formatEther(profitAmount),
|
|
447
|
+
estimatedOkbOut: ethers.formatEther(estimatedOkbOut),
|
|
448
|
+
estimatedBuyAmount: ethers.formatEther(buyAmountAfterProfit),
|
|
449
|
+
profitAmount: ethers.formatEther(profitAmount),
|
|
395
450
|
profitRecipient,
|
|
396
451
|
userType,
|
|
452
|
+
useFundUtilization,
|
|
397
453
|
},
|
|
398
454
|
};
|
|
399
455
|
}
|
|
@@ -414,109 +470,189 @@ export async function bundleSwap(params) {
|
|
|
414
470
|
* });
|
|
415
471
|
*/
|
|
416
472
|
export async function bundleBatchSwap(params) {
|
|
417
|
-
const { sellerPrivateKey, buyerPrivateKeys, buyerRatios, // ✅
|
|
418
|
-
tokenAddress, tokenDecimals = 18, sellAmount, hopCount = 2, // ✅ 设为 0 则不多跳
|
|
419
|
-
tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
473
|
+
const { sellerPrivateKey, buyerPrivateKeys, buyerRatios, tokenAddress, tokenDecimals = 18, sellAmount, hopCount = 0, tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', useFundUtilization = false, // ✅ 默认普通模式
|
|
420
474
|
config, } = params;
|
|
421
475
|
const provider = getCachedProvider(config?.rpcUrl);
|
|
422
476
|
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
423
477
|
const actualRouter = routerAddress ?? getDefaultRouter(tradeType);
|
|
478
|
+
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
479
|
+
const portalInterface = new ethers.Interface(FLAP_PORTAL_ABI);
|
|
424
480
|
// ========================================
|
|
425
|
-
//
|
|
481
|
+
// 创建钱包
|
|
426
482
|
// ========================================
|
|
427
483
|
const sellerWallet = createWallet(sellerPrivateKey, provider);
|
|
428
484
|
const buyerWallets = buyerPrivateKeys.map(pk => createWallet(pk, provider));
|
|
429
|
-
|
|
485
|
+
// 资金利用率模式才需要中间钱包
|
|
486
|
+
const actualHopCount = useFundUtilization ? hopCount : 0;
|
|
487
|
+
const hopWalletInfos = actualHopCount > 0 ? generateRandomWallets(actualHopCount) : [];
|
|
430
488
|
const hopWallets = hopWalletInfos.map(info => createWallet(info.privateKey, provider));
|
|
431
489
|
const sellAmountWei = ethers.parseUnits(truncateDecimals(sellAmount, tokenDecimals), tokenDecimals);
|
|
432
|
-
//
|
|
490
|
+
// 获取卖出报价
|
|
433
491
|
const estimatedOkbOut = await quoteSellOutput(sellAmountWei, tokenAddress, tradeType, fee, config?.rpcUrl);
|
|
434
|
-
//
|
|
492
|
+
// 计算利润和买入金额
|
|
435
493
|
const profitAmount = estimatedOkbOut > 0n
|
|
436
|
-
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true)
|
|
494
|
+
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true)
|
|
437
495
|
: 0n;
|
|
438
496
|
const profitRecipient = getProfitRecipient(config);
|
|
497
|
+
const totalBuyAmountAfterProfit = estimatedOkbOut > profitAmount
|
|
498
|
+
? estimatedOkbOut - profitAmount
|
|
499
|
+
: 0n;
|
|
500
|
+
const modeLabel = useFundUtilization ? '资金利用率' : '普通';
|
|
501
|
+
console.log(`[Bundle Batch Swap] 模式: ${modeLabel}`);
|
|
439
502
|
console.log(`[Bundle Batch Swap] 卖出数量: ${sellAmount} Token`);
|
|
440
503
|
console.log(`[Bundle Batch Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
|
|
441
504
|
console.log(`[Bundle Batch Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
|
|
442
|
-
|
|
443
|
-
|
|
505
|
+
console.log(`[Bundle Batch Swap] 买入金额: ${ethers.formatEther(totalBuyAmountAfterProfit)} OKB`);
|
|
506
|
+
// 验证报价
|
|
507
|
+
if (tradeType === 'FLAP' && totalBuyAmountAfterProfit <= 0n) {
|
|
444
508
|
throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
|
|
445
509
|
}
|
|
446
510
|
// ========================================
|
|
447
|
-
//
|
|
511
|
+
// 并行获取数据
|
|
448
512
|
// ========================================
|
|
449
513
|
const addressesToQuery = [sellerWallet.address, ...buyerWallets.map(w => w.address)];
|
|
450
514
|
const [nonces, feeData, ...buyerBalances] = await Promise.all([
|
|
451
515
|
batchGetNonces(addressesToQuery, provider),
|
|
452
516
|
provider.getFeeData(),
|
|
453
|
-
// 获取所有买家的 OKB 余额(普通模式下买家用自己的钱)
|
|
454
517
|
...buyerWallets.map(w => provider.getBalance(w.address)),
|
|
455
518
|
]);
|
|
456
519
|
const sellerNonce = nonces[0];
|
|
457
520
|
const buyerNonces = nonces.slice(1);
|
|
458
|
-
|
|
521
|
+
// 普通模式验证:买家需要预存足够的 OKB
|
|
522
|
+
if (!useFundUtilization && tradeType === 'FLAP') {
|
|
523
|
+
const totalBuyerBalance = buyerBalances.reduce((sum, b) => sum + BigInt(b.toString()), 0n);
|
|
524
|
+
if (totalBuyerBalance < totalBuyAmountAfterProfit) {
|
|
525
|
+
throw new Error(`普通模式需要买家钱包总余额足够,需要 ${ethers.formatEther(totalBuyAmountAfterProfit)},当前总计 ${ethers.formatEther(totalBuyerBalance)}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
459
528
|
// ========================================
|
|
460
|
-
//
|
|
529
|
+
// 签署授权
|
|
461
530
|
// ========================================
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
531
|
+
const authPromises = [];
|
|
532
|
+
authPromises.push(signAuthorization(sellerWallet, delegateAddress, BigInt(sellerNonce + 1)));
|
|
533
|
+
for (let i = 0; i < buyerWallets.length; i++) {
|
|
534
|
+
authPromises.push(signAuthorization(buyerWallets[i], delegateAddress, BigInt(buyerNonces[i])));
|
|
535
|
+
}
|
|
536
|
+
// 中间钱包授权(资金利用率模式)
|
|
537
|
+
for (const hopWallet of hopWallets) {
|
|
538
|
+
authPromises.push(signAuthorization(hopWallet, delegateAddress, 0n));
|
|
539
|
+
}
|
|
540
|
+
const authorizations = await Promise.all(authPromises);
|
|
465
541
|
// ========================================
|
|
466
|
-
//
|
|
467
|
-
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
468
|
-
// 2. 所有买家用自己预存的 OKB 买入代币
|
|
469
|
-
// 3. 利润从第一个买家的 OKB 中扣除
|
|
542
|
+
// 构建调用
|
|
470
543
|
// ========================================
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
calls.push(sellCall);
|
|
474
|
-
// 2. 利润刮取(从第一个买家扣除,在买入之前)
|
|
475
|
-
if (profitAmount > 0n) {
|
|
476
|
-
calls.push({
|
|
477
|
-
target: buyerWallets[0].address,
|
|
478
|
-
allowFailure: false,
|
|
479
|
-
value: 0n,
|
|
480
|
-
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
// ✅ 3. 计算每个买家的买入金额(普通模式:买家用自己的 OKB)
|
|
484
|
-
// FLAP 模式需要使用买家的实际余额
|
|
544
|
+
const calls = [];
|
|
545
|
+
// 计算每个买家的买入金额
|
|
485
546
|
let buyAmountsPerBuyer;
|
|
486
547
|
if (tradeType === 'FLAP') {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
548
|
+
if (buyerWallets.length === 1) {
|
|
549
|
+
buyAmountsPerBuyer = [totalBuyAmountAfterProfit];
|
|
550
|
+
}
|
|
551
|
+
else if (buyerRatios && buyerRatios.length === buyerWallets.length) {
|
|
552
|
+
let allocated = 0n;
|
|
553
|
+
buyAmountsPerBuyer = buyerRatios.map((ratio, i) => {
|
|
554
|
+
const amount = (totalBuyAmountAfterProfit * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
555
|
+
if (i === buyerRatios.length - 1) {
|
|
556
|
+
return totalBuyAmountAfterProfit - allocated;
|
|
557
|
+
}
|
|
558
|
+
allocated += amount;
|
|
559
|
+
return amount;
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
const avgAmount = totalBuyAmountAfterProfit / BigInt(buyerWallets.length);
|
|
564
|
+
const remainder = totalBuyAmountAfterProfit % BigInt(buyerWallets.length);
|
|
565
|
+
buyAmountsPerBuyer = buyerWallets.map((_, i) => i === buyerWallets.length - 1 ? avgAmount + remainder : avgAmount);
|
|
566
|
+
}
|
|
494
567
|
}
|
|
495
568
|
else {
|
|
496
|
-
// V2/V3 模式:使用 0n,合约自动使用全部余额
|
|
497
569
|
buyAmountsPerBuyer = buyerWallets.map(() => 0n);
|
|
498
570
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
571
|
+
if (useFundUtilization) {
|
|
572
|
+
// ========================================
|
|
573
|
+
// ✅ 资金利用率模式(一卖多买)
|
|
574
|
+
// 1. 卖家卖出代币 → 获得 OKB
|
|
575
|
+
// 2. 利润从卖家 OKB 中扣除
|
|
576
|
+
// 3. OKB 多跳转账 → 分配给各买家
|
|
577
|
+
// 4. 各买家用收到的 OKB 买入
|
|
578
|
+
// ========================================
|
|
579
|
+
console.log(`[Bundle Batch Swap] 资金利用率模式,hopCount=${actualHopCount}`);
|
|
580
|
+
// 1. 卖家卖出
|
|
581
|
+
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
582
|
+
calls.push(sellCall);
|
|
583
|
+
// 2. 利润刮取(从卖家余额扣除)
|
|
584
|
+
if (profitAmount > 0n) {
|
|
585
|
+
calls.push({
|
|
586
|
+
target: sellerWallet.address,
|
|
587
|
+
allowFailure: false,
|
|
588
|
+
value: 0n,
|
|
589
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
// 3. OKB 多跳转账给第一个买家
|
|
593
|
+
const firstRecipient = hopWallets.length > 0 ? hopWallets[0].address : buyerWallets[0].address;
|
|
594
|
+
// 卖家 → 第一个接收者(转全部余额)
|
|
595
|
+
calls.push({
|
|
596
|
+
target: sellerWallet.address,
|
|
597
|
+
allowFailure: false,
|
|
598
|
+
value: 0n,
|
|
599
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [firstRecipient]),
|
|
600
|
+
});
|
|
601
|
+
// 中间钱包之间转账
|
|
602
|
+
for (let i = 0; i < hopWallets.length; i++) {
|
|
603
|
+
const nextRecipient = i < hopWallets.length - 1 ? hopWallets[i + 1].address : buyerWallets[0].address;
|
|
604
|
+
calls.push({
|
|
605
|
+
target: hopWallets[i].address,
|
|
606
|
+
allowFailure: false,
|
|
607
|
+
value: 0n,
|
|
608
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [nextRecipient]),
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
// 第一个买家分配给其他买家
|
|
612
|
+
for (let i = 1; i < buyerWallets.length; i++) {
|
|
613
|
+
calls.push({
|
|
614
|
+
target: buyerWallets[0].address,
|
|
615
|
+
allowFailure: false,
|
|
616
|
+
value: 0n,
|
|
617
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [buyerWallets[i].address, buyAmountsPerBuyer[i]]),
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
// 4. 所有买家买入
|
|
621
|
+
for (let i = 0; i < buyerWallets.length; i++) {
|
|
622
|
+
const buyCall = buildBuyCallInternal(buyerWallets[i], buyAmountsPerBuyer[i], tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
623
|
+
calls.push(buyCall);
|
|
624
|
+
}
|
|
503
625
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
626
|
+
else {
|
|
627
|
+
// ========================================
|
|
628
|
+
// ✅ 普通模式(一卖多买)
|
|
629
|
+
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
630
|
+
// 2. 利润从第一个买家 OKB 中扣除
|
|
631
|
+
// 3. 各买家用自己预存的 OKB 买入
|
|
632
|
+
// ========================================
|
|
633
|
+
console.log(`[Bundle Batch Swap] 普通模式`);
|
|
634
|
+
// 1. 卖家卖出(OKB 留在卖家账户)
|
|
635
|
+
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
636
|
+
calls.push(sellCall);
|
|
637
|
+
// 2. 利润刮取(从第一个买家余额扣除)
|
|
638
|
+
if (profitAmount > 0n) {
|
|
639
|
+
calls.push({
|
|
640
|
+
target: buyerWallets[0].address,
|
|
641
|
+
allowFailure: false,
|
|
642
|
+
value: 0n,
|
|
643
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
// 3. 所有买家买入(使用自己预存的 OKB)
|
|
647
|
+
for (let i = 0; i < buyerWallets.length; i++) {
|
|
648
|
+
const buyCall = buildBuyCallInternal(buyerWallets[i], buyAmountsPerBuyer[i], tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
649
|
+
calls.push(buyCall);
|
|
650
|
+
}
|
|
513
651
|
}
|
|
514
|
-
const authorizations = await Promise.all(authPromises);
|
|
515
652
|
// ========================================
|
|
516
|
-
//
|
|
653
|
+
// 构建交易
|
|
517
654
|
// ========================================
|
|
518
|
-
const signedTransaction = buildEIP7702TransactionSync(sellerWallet, authorizations, calls, 0n,
|
|
519
|
-
sellerNonce, feeData, config);
|
|
655
|
+
const signedTransaction = buildEIP7702TransactionSync(sellerWallet, authorizations, calls, 0n, sellerNonce, feeData, config);
|
|
520
656
|
return {
|
|
521
657
|
signedTransaction,
|
|
522
658
|
hopWallets: hopWalletInfos,
|
|
@@ -524,11 +660,12 @@ export async function bundleBatchSwap(params) {
|
|
|
524
660
|
sellerAddress: sellerWallet.address,
|
|
525
661
|
buyerAddresses: buyerWallets.map(w => w.address),
|
|
526
662
|
sellAmount: ethers.formatUnits(sellAmountWei, tokenDecimals),
|
|
527
|
-
estimatedOkbOut: ethers.formatEther(estimatedOkbOut),
|
|
528
|
-
estimatedBuyAmount:
|
|
529
|
-
profitAmount: ethers.formatEther(profitAmount),
|
|
663
|
+
estimatedOkbOut: ethers.formatEther(estimatedOkbOut),
|
|
664
|
+
estimatedBuyAmount: ethers.formatEther(totalBuyAmountAfterProfit),
|
|
665
|
+
profitAmount: ethers.formatEther(profitAmount),
|
|
530
666
|
profitRecipient,
|
|
531
667
|
userType,
|
|
668
|
+
useFundUtilization,
|
|
532
669
|
},
|
|
533
670
|
};
|
|
534
671
|
}
|
|
@@ -21,8 +21,10 @@ export interface TransferResult {
|
|
|
21
21
|
metadata: Record<string, unknown>;
|
|
22
22
|
}
|
|
23
23
|
export interface DisperseParams {
|
|
24
|
-
/**
|
|
24
|
+
/** 资金来源钱包私钥(Token/OKB 从这个钱包转出) */
|
|
25
25
|
mainPrivateKey: string;
|
|
26
|
+
/** Payer 钱包私钥(可选,用于支付 gas;不传则使用 mainPrivateKey) */
|
|
27
|
+
payerPrivateKey?: string;
|
|
26
28
|
/** 接收者地址列表 */
|
|
27
29
|
recipients: string[];
|
|
28
30
|
/** 每个接收者的金额 */
|
|
@@ -101,16 +101,23 @@ async function quoteTokenToOkb(tokenAddress, tokenAmount, rpcUrl) {
|
|
|
101
101
|
* });
|
|
102
102
|
*/
|
|
103
103
|
export async function disperse(params) {
|
|
104
|
-
const { mainPrivateKey, recipients, amounts, tokenAddress, tokenDecimals = 18, hopCount = 0, userType, config, } = params;
|
|
104
|
+
const { mainPrivateKey, payerPrivateKey, recipients, amounts, tokenAddress, tokenDecimals = 18, hopCount = 0, userType, config, } = params;
|
|
105
105
|
if (recipients.length !== amounts.length) {
|
|
106
106
|
throw new Error('recipients 和 amounts 长度必须相同');
|
|
107
107
|
}
|
|
108
108
|
const provider = getCachedProvider(config?.rpcUrl);
|
|
109
109
|
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
110
110
|
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
111
|
+
// ✅ mainWallet: 资金来源(Token/OKB 从这里转出)
|
|
111
112
|
const mainWallet = createWallet(mainPrivateKey, provider);
|
|
112
113
|
const isNative = !tokenAddress;
|
|
113
114
|
const profitRecipient = getProfitRecipient(config);
|
|
115
|
+
// ✅ 支持单独的 Payer 钱包(用于支付 gas)
|
|
116
|
+
const hasSeparatePayer = payerPrivateKey && payerPrivateKey !== mainPrivateKey;
|
|
117
|
+
const payerWallet = hasSeparatePayer ? createWallet(payerPrivateKey, provider) : null;
|
|
118
|
+
const txSender = payerWallet ?? mainWallet; // 交易发起者
|
|
119
|
+
console.log(`[disperse] 资金钱包: ${mainWallet.address}`);
|
|
120
|
+
console.log(`[disperse] Gas Payer: ${txSender.address}${hasSeparatePayer ? ' (独立 Payer)' : ''}`);
|
|
114
121
|
// ✅ 为每个接收者生成独立的中间钱包链
|
|
115
122
|
// 每个接收者有自己的 hopCount 个中间钱包
|
|
116
123
|
const allHopWalletInfos = [];
|
|
@@ -145,23 +152,28 @@ export async function disperse(params) {
|
|
|
145
152
|
// ========================================
|
|
146
153
|
// 并行获取数据
|
|
147
154
|
// ========================================
|
|
148
|
-
const
|
|
149
|
-
|
|
155
|
+
const addressesToGetNonces = payerWallet
|
|
156
|
+
? [payerWallet.address, mainWallet.address]
|
|
157
|
+
: [mainWallet.address];
|
|
158
|
+
const [allNoncesResult, feeData] = await Promise.all([
|
|
159
|
+
batchGetNonces(addressesToGetNonces, provider),
|
|
150
160
|
provider.getFeeData(),
|
|
151
161
|
]);
|
|
162
|
+
const payerNonce = payerWallet ? allNoncesResult[0] : allNoncesResult[0];
|
|
163
|
+
const mainNonce = payerWallet ? allNoncesResult[1] : allNoncesResult[0];
|
|
152
164
|
// ========================================
|
|
153
165
|
// 同步签署授权(已有 nonce)
|
|
154
166
|
// ========================================
|
|
155
|
-
//
|
|
156
|
-
const allWallets = [mainWallet];
|
|
157
|
-
const allNonces = [mainNonce];
|
|
167
|
+
// ✅ 如果有 Payer,需要为 Payer 和 mainWallet 都生成授权
|
|
168
|
+
const allWallets = payerWallet ? [payerWallet, mainWallet] : [mainWallet];
|
|
169
|
+
const allNonces = payerWallet ? [payerNonce, mainNonce] : [mainNonce];
|
|
158
170
|
for (const hopWallets of allHopWallets) {
|
|
159
171
|
for (const hopWallet of hopWallets) {
|
|
160
172
|
allWallets.push(hopWallet);
|
|
161
173
|
allNonces.push(0);
|
|
162
174
|
}
|
|
163
175
|
}
|
|
164
|
-
const authorizations = signAuthorizationsWithNonces(allWallets, allNonces, delegateAddress, 0 // mainWallet
|
|
176
|
+
const authorizations = signAuthorizationsWithNonces(allWallets, allNonces, delegateAddress, 0 // txSender(Payer 或 mainWallet)是交易发起者
|
|
165
177
|
);
|
|
166
178
|
// ========================================
|
|
167
179
|
// 同步构建调用
|
|
@@ -176,12 +188,24 @@ export async function disperse(params) {
|
|
|
176
188
|
// 直接分发:主钱包 → 接收者
|
|
177
189
|
// ========================================
|
|
178
190
|
if (isNative) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
191
|
+
// ✅ Payer 模式:使用 transferAmount(从 mainWallet 余额转出)
|
|
192
|
+
// ✅ 非 Payer 模式:使用 transferTo(从 msg.value 转出)
|
|
193
|
+
if (payerWallet) {
|
|
194
|
+
calls.push({
|
|
195
|
+
target: mainWallet.address,
|
|
196
|
+
allowFailure: false,
|
|
197
|
+
value: 0n,
|
|
198
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [recipient, amtWei]),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
calls.push({
|
|
203
|
+
target: mainWallet.address,
|
|
204
|
+
allowFailure: false,
|
|
205
|
+
value: amtWei,
|
|
206
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [recipient]),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
185
209
|
}
|
|
186
210
|
else {
|
|
187
211
|
calls.push({
|
|
@@ -204,13 +228,24 @@ export async function disperse(params) {
|
|
|
204
228
|
const lastHop = hopWallets[hopWallets.length - 1];
|
|
205
229
|
if (isNative) {
|
|
206
230
|
// 主钱包 → 第一个中间钱包
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
231
|
+
// ✅ Payer 模式:使用 transferAmount(从 mainWallet 余额转出)
|
|
232
|
+
if (payerWallet) {
|
|
233
|
+
calls.push({
|
|
234
|
+
target: mainWallet.address,
|
|
235
|
+
allowFailure: false,
|
|
236
|
+
value: 0n,
|
|
237
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [firstHop.address, amtWei]),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
calls.push({
|
|
242
|
+
target: mainWallet.address,
|
|
243
|
+
allowFailure: false,
|
|
244
|
+
value: amtWei,
|
|
245
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [firstHop.address]),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// 中间钱包之间转账(使用全部余额)
|
|
214
249
|
for (let j = 0; j < hopWallets.length - 1; j++) {
|
|
215
250
|
calls.push({
|
|
216
251
|
target: hopWallets[j].address,
|
|
@@ -219,7 +254,7 @@ export async function disperse(params) {
|
|
|
219
254
|
callData: delegateInterface.encodeFunctionData('transferTo', [hopWallets[j + 1].address]),
|
|
220
255
|
});
|
|
221
256
|
}
|
|
222
|
-
// 最后一个中间钱包 →
|
|
257
|
+
// 最后一个中间钱包 → 接收者(使用全部余额)
|
|
223
258
|
calls.push({
|
|
224
259
|
target: lastHop.address,
|
|
225
260
|
allowFailure: false,
|
|
@@ -280,15 +315,20 @@ export async function disperse(params) {
|
|
|
280
315
|
// ========================================
|
|
281
316
|
// 构建交易
|
|
282
317
|
// ========================================
|
|
283
|
-
|
|
284
|
-
|
|
318
|
+
// ✅ Payer 模式下 value 为 0(资金从 mainWallet 转出,不是 Payer)
|
|
319
|
+
// 无 Payer 时,原生币分发需要传递 value(资金从 txSender 转出)
|
|
320
|
+
const totalValue = (isNative && !payerWallet) ? totalAmount : 0n;
|
|
321
|
+
const signedTransaction = buildEIP7702TransactionSync(txSender, // ✅ 使用 txSender(Payer 或 mainWallet)发起交易
|
|
322
|
+
authorizations, calls, totalValue, payerNonce, // ✅ 使用 txSender 的 nonce
|
|
323
|
+
feeData, config);
|
|
285
324
|
// ✅ 返回所有中间钱包(扁平化)
|
|
286
325
|
const flatHopWallets = allHopWalletInfos.flat();
|
|
287
326
|
return {
|
|
288
327
|
signedTransaction,
|
|
289
328
|
hopWallets: flatHopWallets.length > 0 ? flatHopWallets : undefined,
|
|
290
329
|
metadata: {
|
|
291
|
-
senderAddress: mainWallet.address,
|
|
330
|
+
senderAddress: mainWallet.address, // 资金来源
|
|
331
|
+
payerAddress: txSender.address, // Gas 支付者
|
|
292
332
|
recipientCount: recipients.length,
|
|
293
333
|
totalAmount: isNative ? ethers.formatEther(totalAmount) : ethers.formatUnits(totalAmount, tokenDecimals),
|
|
294
334
|
profitAmount: ethers.formatEther(profitAmountOkb), // ✅ 以 OKB 计
|