four-flap-meme-sdk 1.7.62 → 1.7.64
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.
|
@@ -119,13 +119,14 @@ function encodeV2BuyCall(tokenAddress, recipient, deadline) {
|
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
121
|
* 构建利润转账调用
|
|
122
|
+
* ✅ 使用 transferAmount 从钱包余额转出,而不是 msg.value
|
|
122
123
|
*/
|
|
123
124
|
function buildProfitCall(walletAddress, profitAmount, profitRecipient, delegateInterface) {
|
|
124
125
|
return {
|
|
125
126
|
target: walletAddress,
|
|
126
127
|
allowFailure: false,
|
|
127
|
-
value:
|
|
128
|
-
callData: delegateInterface.encodeFunctionData('
|
|
128
|
+
value: 0n, // ✅ 不需要 msg.value,从钱包余额转出
|
|
129
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
129
130
|
};
|
|
130
131
|
}
|
|
131
132
|
// ========================================
|
|
@@ -262,8 +263,8 @@ export async function bundleCreateBuy(params) {
|
|
|
262
263
|
callData: buyBatchCall,
|
|
263
264
|
});
|
|
264
265
|
}
|
|
265
|
-
// ✅
|
|
266
|
-
const totalValue =
|
|
266
|
+
// ✅ 利润从开发者钱包余额转出,主交易只支付创建费用
|
|
267
|
+
const totalValue = params.quoteAmt ?? 0n;
|
|
267
268
|
// 构建签名交易
|
|
268
269
|
const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, nonces[0], feeData, config);
|
|
269
270
|
return {
|
|
@@ -417,8 +418,8 @@ export async function bundleCreateToDex(params) {
|
|
|
417
418
|
});
|
|
418
419
|
}
|
|
419
420
|
}
|
|
420
|
-
// ✅
|
|
421
|
-
const totalValue =
|
|
421
|
+
// ✅ 利润从 Payer 钱包余额转出,主交易只支付创建费用
|
|
422
|
+
const totalValue = params.quoteAmt ?? 0n;
|
|
422
423
|
// 构建签名交易(一键发射会触发毕业,使用高 gas limit)
|
|
423
424
|
const toDexConfig = {
|
|
424
425
|
...config,
|
|
@@ -616,8 +617,8 @@ export async function bundleGraduateBuy(params) {
|
|
|
616
617
|
});
|
|
617
618
|
}
|
|
618
619
|
}
|
|
619
|
-
// ✅
|
|
620
|
-
const totalValue =
|
|
620
|
+
// ✅ 利润从 Payer 钱包余额转出,主交易不需要支付额外费用
|
|
621
|
+
const totalValue = 0n;
|
|
621
622
|
// 构建签名交易(毕业操作强制使用高 gas limit)
|
|
622
623
|
const graduateConfig = {
|
|
623
624
|
...config,
|
|
@@ -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,89 +303,141 @@ 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
|
-
//
|
|
318
|
+
// 计算买入金额
|
|
319
319
|
const buyAmountAfterProfit = estimatedOkbOut > profitAmount
|
|
320
320
|
? estimatedOkbOut - profitAmount
|
|
321
321
|
: 0n;
|
|
322
|
+
const modeLabel = useFundUtilization ? '资金利用率' : '普通';
|
|
323
|
+
console.log(`[Bundle Swap] 模式: ${modeLabel}`);
|
|
322
324
|
console.log(`[Bundle Swap] 卖出数量: ${sellAmount} Token`);
|
|
323
325
|
console.log(`[Bundle Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
|
|
324
326
|
console.log(`[Bundle Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
|
|
325
|
-
console.log(`[Bundle Swap]
|
|
327
|
+
console.log(`[Bundle Swap] 买入金额: ${ethers.formatEther(buyAmountAfterProfit)} OKB`);
|
|
326
328
|
// ========================================
|
|
327
|
-
//
|
|
329
|
+
// 并行获取数据
|
|
328
330
|
// ========================================
|
|
329
331
|
const [nonces, feeData, buyerBalance] = await Promise.all([
|
|
330
|
-
// 并行获取 seller 和 buyer 的 nonce
|
|
331
332
|
batchGetNonces([sellerWallet.address, buyerWallet.address], provider),
|
|
332
|
-
// 获取 gas 费用数据
|
|
333
333
|
provider.getFeeData(),
|
|
334
|
-
// 获取买家的 OKB 余额(验证是否足够)
|
|
335
334
|
provider.getBalance(buyerWallet.address),
|
|
336
335
|
]);
|
|
337
336
|
const sellerNonce = nonces[0];
|
|
338
337
|
const buyerNonce = nonces[1];
|
|
339
|
-
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
338
|
+
// ========================================
|
|
339
|
+
// 验证余额
|
|
340
|
+
// ========================================
|
|
341
|
+
if (tradeType === 'FLAP' && buyAmountAfterProfit <= 0n) {
|
|
342
|
+
throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
|
|
343
|
+
}
|
|
344
|
+
// 普通模式:买家需要预存足够的 OKB
|
|
345
|
+
if (!useFundUtilization && tradeType === 'FLAP') {
|
|
345
346
|
if (buyerBalance < buyAmountAfterProfit) {
|
|
346
|
-
throw new Error(
|
|
347
|
+
throw new Error(`普通模式需要买家钱包有足够的 OKB,需要 ${ethers.formatEther(buyAmountAfterProfit)},当前 ${ethers.formatEther(buyerBalance)}`);
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
350
|
// ========================================
|
|
350
|
-
//
|
|
351
|
+
// 签署授权
|
|
351
352
|
// ========================================
|
|
352
|
-
const
|
|
353
|
+
const authPromises = [];
|
|
353
354
|
// 卖家授权(交易发起者,nonce +1)
|
|
354
|
-
|
|
355
|
+
authPromises.push(signAuthorization(sellerWallet, delegateAddress, BigInt(sellerNonce + 1)));
|
|
355
356
|
// 买家授权
|
|
356
|
-
|
|
357
|
-
//
|
|
358
|
-
const
|
|
359
|
-
|
|
357
|
+
authPromises.push(signAuthorization(buyerWallet, delegateAddress, BigInt(buyerNonce)));
|
|
358
|
+
// 中间钱包授权(资金利用率模式)
|
|
359
|
+
for (const hopWallet of hopWallets) {
|
|
360
|
+
authPromises.push(signAuthorization(hopWallet, delegateAddress, 0n));
|
|
361
|
+
}
|
|
362
|
+
const authorizations = await Promise.all(authPromises);
|
|
360
363
|
// ========================================
|
|
361
|
-
//
|
|
362
|
-
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
363
|
-
// 2. 买家用自己预存的 OKB 买入代币(金额 = 卖家卖出所得,不是全部余额)
|
|
364
|
-
// 3. 利润从买家的 OKB 中扣除
|
|
364
|
+
// 构建调用
|
|
365
365
|
// ========================================
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
+
// 卖家 → 第一个接收者
|
|
371
391
|
calls.push({
|
|
372
|
-
target:
|
|
392
|
+
target: sellerWallet.address,
|
|
373
393
|
allowFailure: false,
|
|
374
394
|
value: 0n,
|
|
375
|
-
callData: delegateInterface.encodeFunctionData('
|
|
395
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [firstRecipient]),
|
|
376
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
|
+
// ✅ 资金利用率模式下,所有 tradeType 都使用精确金额
|
|
409
|
+
const buyCall = buildBuyCallInternal(buyerWallet, buyAmountAfterProfit, // ✅ 使用卖出所得(扣除利润)
|
|
410
|
+
tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
411
|
+
calls.push(buyCall);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
// ========================================
|
|
415
|
+
// ✅ 普通模式
|
|
416
|
+
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
417
|
+
// 2. 买家用自己预存的 OKB 买入代币
|
|
418
|
+
// 3. 利润从买家的 OKB 中扣除
|
|
419
|
+
// ========================================
|
|
420
|
+
console.log(`[Bundle Swap] 普通模式`);
|
|
421
|
+
// 1. 卖家卖出(OKB 留在卖家账户)
|
|
422
|
+
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
423
|
+
calls.push(sellCall);
|
|
424
|
+
// 2. 利润刮取(从买家余额扣除)
|
|
425
|
+
if (profitAmount > 0n) {
|
|
426
|
+
calls.push({
|
|
427
|
+
target: buyerWallet.address,
|
|
428
|
+
allowFailure: false,
|
|
429
|
+
value: 0n,
|
|
430
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
// 3. 买家用自己的 OKB 买入(金额 = 卖家卖出所得,避免买卖资金不匹配)
|
|
434
|
+
const buyCall = buildBuyCallInternal(buyerWallet, buyAmountAfterProfit, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
435
|
+
calls.push(buyCall);
|
|
377
436
|
}
|
|
378
|
-
// 3. 买家用自己预存的 OKB 买入
|
|
379
|
-
// ✅ 关键修复:买入金额 = 卖家卖出所得(扣除利润),而不是买家全部余额
|
|
380
|
-
// FLAP 模式必须指定精确金额,V2/V3 使用 0n(合约自动使用全部余额)
|
|
381
|
-
const buyAmountForCall = tradeType === 'FLAP' ? buyAmountAfterProfit : 0n;
|
|
382
|
-
console.log(`[Bundle Swap] 实际买入调用金额: ${ethers.formatEther(buyAmountForCall)} OKB`);
|
|
383
|
-
const buyCall = buildBuyCallInternal(buyerWallet, buyAmountForCall, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
384
|
-
calls.push(buyCall);
|
|
385
437
|
// ========================================
|
|
386
|
-
//
|
|
438
|
+
// 构建交易
|
|
387
439
|
// ========================================
|
|
388
|
-
const signedTransaction = buildEIP7702TransactionSync(sellerWallet,
|
|
389
|
-
sellerNonce, feeData, config);
|
|
440
|
+
const signedTransaction = buildEIP7702TransactionSync(sellerWallet, authorizations, calls, 0n, sellerNonce, feeData, config);
|
|
390
441
|
return {
|
|
391
442
|
signedTransaction,
|
|
392
443
|
hopWallets: hopWalletInfos,
|
|
@@ -394,11 +445,12 @@ export async function bundleSwap(params) {
|
|
|
394
445
|
sellerAddress: sellerWallet.address,
|
|
395
446
|
buyerAddresses: [buyerWallet.address],
|
|
396
447
|
sellAmount: ethers.formatUnits(sellAmountWei, tokenDecimals),
|
|
397
|
-
estimatedOkbOut: ethers.formatEther(estimatedOkbOut),
|
|
398
|
-
estimatedBuyAmount:
|
|
399
|
-
profitAmount: ethers.formatEther(profitAmount),
|
|
448
|
+
estimatedOkbOut: ethers.formatEther(estimatedOkbOut),
|
|
449
|
+
estimatedBuyAmount: ethers.formatEther(buyAmountAfterProfit),
|
|
450
|
+
profitAmount: ethers.formatEther(profitAmount),
|
|
400
451
|
profitRecipient,
|
|
401
452
|
userType,
|
|
453
|
+
useFundUtilization,
|
|
402
454
|
},
|
|
403
455
|
};
|
|
404
456
|
}
|
|
@@ -419,141 +471,189 @@ export async function bundleSwap(params) {
|
|
|
419
471
|
* });
|
|
420
472
|
*/
|
|
421
473
|
export async function bundleBatchSwap(params) {
|
|
422
|
-
const { sellerPrivateKey, buyerPrivateKeys, buyerRatios, // ✅
|
|
423
|
-
tokenAddress, tokenDecimals = 18, sellAmount, hopCount = 2, // ✅ 设为 0 则不多跳
|
|
424
|
-
tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', // ✅ 用户类型(影响利润率)
|
|
474
|
+
const { sellerPrivateKey, buyerPrivateKeys, buyerRatios, tokenAddress, tokenDecimals = 18, sellAmount, hopCount = 0, tradeType = 'V3', routerAddress, fee = V3_FEE_TIERS.MEDIUM, userType = 'v0', useFundUtilization = false, // ✅ 默认普通模式
|
|
425
475
|
config, } = params;
|
|
426
476
|
const provider = getCachedProvider(config?.rpcUrl);
|
|
427
477
|
const delegateAddress = (config?.delegateAddress && config.delegateAddress.trim()) || UNIFIED_DELEGATE_ADDRESS;
|
|
428
478
|
const actualRouter = routerAddress ?? getDefaultRouter(tradeType);
|
|
479
|
+
const delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
|
|
480
|
+
const portalInterface = new ethers.Interface(FLAP_PORTAL_ABI);
|
|
429
481
|
// ========================================
|
|
430
|
-
//
|
|
482
|
+
// 创建钱包
|
|
431
483
|
// ========================================
|
|
432
484
|
const sellerWallet = createWallet(sellerPrivateKey, provider);
|
|
433
485
|
const buyerWallets = buyerPrivateKeys.map(pk => createWallet(pk, provider));
|
|
434
|
-
|
|
486
|
+
// 资金利用率模式才需要中间钱包
|
|
487
|
+
const actualHopCount = useFundUtilization ? hopCount : 0;
|
|
488
|
+
const hopWalletInfos = actualHopCount > 0 ? generateRandomWallets(actualHopCount) : [];
|
|
435
489
|
const hopWallets = hopWalletInfos.map(info => createWallet(info.privateKey, provider));
|
|
436
490
|
const sellAmountWei = ethers.parseUnits(truncateDecimals(sellAmount, tokenDecimals), tokenDecimals);
|
|
437
|
-
//
|
|
491
|
+
// 获取卖出报价
|
|
438
492
|
const estimatedOkbOut = await quoteSellOutput(sellAmountWei, tokenAddress, tradeType, fee, config?.rpcUrl);
|
|
439
|
-
//
|
|
493
|
+
// 计算利润和买入金额
|
|
440
494
|
const profitAmount = estimatedOkbOut > 0n
|
|
441
|
-
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true)
|
|
495
|
+
? calculateProfitAmountByUserType(estimatedOkbOut, userType, true)
|
|
442
496
|
: 0n;
|
|
443
497
|
const profitRecipient = getProfitRecipient(config);
|
|
444
|
-
// ✅ 计算总买入金额:基于卖家卖出得到的 OKB(不是买家全部余额)
|
|
445
498
|
const totalBuyAmountAfterProfit = estimatedOkbOut > profitAmount
|
|
446
499
|
? estimatedOkbOut - profitAmount
|
|
447
500
|
: 0n;
|
|
501
|
+
const modeLabel = useFundUtilization ? '资金利用率' : '普通';
|
|
502
|
+
console.log(`[Bundle Batch Swap] 模式: ${modeLabel}`);
|
|
448
503
|
console.log(`[Bundle Batch Swap] 卖出数量: ${sellAmount} Token`);
|
|
449
504
|
console.log(`[Bundle Batch Swap] 预估获得: ${ethers.formatEther(estimatedOkbOut)} OKB`);
|
|
450
505
|
console.log(`[Bundle Batch Swap] 利润: ${ethers.formatEther(profitAmount)} OKB (userType=${userType})`);
|
|
451
|
-
console.log(`[Bundle Batch Swap]
|
|
452
|
-
//
|
|
506
|
+
console.log(`[Bundle Batch Swap] 买入金额: ${ethers.formatEther(totalBuyAmountAfterProfit)} OKB`);
|
|
507
|
+
// 验证报价
|
|
453
508
|
if (tradeType === 'FLAP' && totalBuyAmountAfterProfit <= 0n) {
|
|
454
509
|
throw new Error('FLAP 内盘模式需要有效的报价,请检查代币地址或稍后重试');
|
|
455
510
|
}
|
|
456
511
|
// ========================================
|
|
457
|
-
//
|
|
512
|
+
// 并行获取数据
|
|
458
513
|
// ========================================
|
|
459
514
|
const addressesToQuery = [sellerWallet.address, ...buyerWallets.map(w => w.address)];
|
|
460
515
|
const [nonces, feeData, ...buyerBalances] = await Promise.all([
|
|
461
516
|
batchGetNonces(addressesToQuery, provider),
|
|
462
517
|
provider.getFeeData(),
|
|
463
|
-
// 获取所有买家的 OKB 余额(验证是否足够)
|
|
464
518
|
...buyerWallets.map(w => provider.getBalance(w.address)),
|
|
465
519
|
]);
|
|
466
520
|
const sellerNonce = nonces[0];
|
|
467
521
|
const buyerNonces = nonces.slice(1);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (tradeType === 'FLAP') {
|
|
522
|
+
// 普通模式验证:买家需要预存足够的 OKB
|
|
523
|
+
if (!useFundUtilization && tradeType === 'FLAP') {
|
|
471
524
|
const totalBuyerBalance = buyerBalances.reduce((sum, b) => sum + BigInt(b.toString()), 0n);
|
|
472
525
|
if (totalBuyerBalance < totalBuyAmountAfterProfit) {
|
|
473
|
-
throw new Error(
|
|
526
|
+
throw new Error(`普通模式需要买家钱包总余额足够,需要 ${ethers.formatEther(totalBuyAmountAfterProfit)},当前总计 ${ethers.formatEther(totalBuyerBalance)}`);
|
|
474
527
|
}
|
|
475
528
|
}
|
|
476
529
|
// ========================================
|
|
477
|
-
//
|
|
530
|
+
// 签署授权
|
|
478
531
|
// ========================================
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
532
|
+
const authPromises = [];
|
|
533
|
+
authPromises.push(signAuthorization(sellerWallet, delegateAddress, BigInt(sellerNonce + 1)));
|
|
534
|
+
for (let i = 0; i < buyerWallets.length; i++) {
|
|
535
|
+
authPromises.push(signAuthorization(buyerWallets[i], delegateAddress, BigInt(buyerNonces[i])));
|
|
536
|
+
}
|
|
537
|
+
// 中间钱包授权(资金利用率模式)
|
|
538
|
+
for (const hopWallet of hopWallets) {
|
|
539
|
+
authPromises.push(signAuthorization(hopWallet, delegateAddress, 0n));
|
|
540
|
+
}
|
|
541
|
+
const authorizations = await Promise.all(authPromises);
|
|
482
542
|
// ========================================
|
|
483
|
-
//
|
|
484
|
-
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
485
|
-
// 2. 所有买家用自己预存的 OKB 买入代币
|
|
486
|
-
// 3. 利润从第一个买家的 OKB 中扣除
|
|
543
|
+
// 构建调用
|
|
487
544
|
// ========================================
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (
|
|
545
|
+
const calls = [];
|
|
546
|
+
// ✅ 计算每个买家的买入金额
|
|
547
|
+
// 所有模式都按比例分配卖家卖出所得,避免买卖资金不匹配
|
|
548
|
+
let buyAmountsPerBuyer;
|
|
549
|
+
if (buyerWallets.length === 1) {
|
|
550
|
+
// 单买家:使用全部卖出所得
|
|
551
|
+
buyAmountsPerBuyer = [totalBuyAmountAfterProfit];
|
|
552
|
+
}
|
|
553
|
+
else if (buyerRatios && buyerRatios.length === buyerWallets.length) {
|
|
554
|
+
// 多买家 + 有比例:按前端传入的比例分配
|
|
555
|
+
let allocated = 0n;
|
|
556
|
+
buyAmountsPerBuyer = buyerRatios.map((ratio, i) => {
|
|
557
|
+
const amount = (totalBuyAmountAfterProfit * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
558
|
+
if (i === buyerRatios.length - 1) {
|
|
559
|
+
return totalBuyAmountAfterProfit - allocated;
|
|
560
|
+
}
|
|
561
|
+
allocated += amount;
|
|
562
|
+
return amount;
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
// 多买家无比例:均分
|
|
567
|
+
const avgAmount = totalBuyAmountAfterProfit / BigInt(buyerWallets.length);
|
|
568
|
+
const remainder = totalBuyAmountAfterProfit % BigInt(buyerWallets.length);
|
|
569
|
+
buyAmountsPerBuyer = buyerWallets.map((_, i) => i === buyerWallets.length - 1 ? avgAmount + remainder : avgAmount);
|
|
570
|
+
}
|
|
571
|
+
console.log(`[bundleBatchSwap] 买入金额分配:`, buyAmountsPerBuyer.map(a => ethers.formatEther(a)));
|
|
572
|
+
if (useFundUtilization) {
|
|
573
|
+
// ========================================
|
|
574
|
+
// ✅ 资金利用率模式(一卖多买)
|
|
575
|
+
// 1. 卖家卖出代币 → 获得 OKB
|
|
576
|
+
// 2. 利润从卖家 OKB 中扣除
|
|
577
|
+
// 3. OKB 多跳转账 → 分配给各买家
|
|
578
|
+
// 4. 各买家用收到的 OKB 买入
|
|
579
|
+
// ========================================
|
|
580
|
+
console.log(`[Bundle Batch Swap] 资金利用率模式,hopCount=${actualHopCount}`);
|
|
581
|
+
// 1. 卖家卖出
|
|
582
|
+
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
583
|
+
calls.push(sellCall);
|
|
584
|
+
// 2. 利润刮取(从卖家余额扣除)
|
|
585
|
+
if (profitAmount > 0n) {
|
|
586
|
+
calls.push({
|
|
587
|
+
target: sellerWallet.address,
|
|
588
|
+
allowFailure: false,
|
|
589
|
+
value: 0n,
|
|
590
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
// 3. OKB 多跳转账给第一个买家
|
|
594
|
+
const firstRecipient = hopWallets.length > 0 ? hopWallets[0].address : buyerWallets[0].address;
|
|
595
|
+
// 卖家 → 第一个接收者(转全部余额)
|
|
493
596
|
calls.push({
|
|
494
|
-
target:
|
|
597
|
+
target: sellerWallet.address,
|
|
495
598
|
allowFailure: false,
|
|
496
599
|
value: 0n,
|
|
497
|
-
callData: delegateInterface.encodeFunctionData('
|
|
600
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [firstRecipient]),
|
|
498
601
|
});
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
buyAmountsPerBuyer = [totalBuyAmountAfterProfit];
|
|
508
|
-
}
|
|
509
|
-
else if (buyerRatios && buyerRatios.length === buyerWallets.length) {
|
|
510
|
-
// 多买家 + 有比例:按前端传入的比例分配
|
|
511
|
-
let allocated = 0n;
|
|
512
|
-
buyAmountsPerBuyer = buyerRatios.map((ratio, i) => {
|
|
513
|
-
const amount = (totalBuyAmountAfterProfit * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
514
|
-
if (i === buyerRatios.length - 1) {
|
|
515
|
-
return totalBuyAmountAfterProfit - allocated; // 最后一个分配剩余,避免精度问题
|
|
516
|
-
}
|
|
517
|
-
allocated += amount;
|
|
518
|
-
return amount;
|
|
602
|
+
// 中间钱包之间转账
|
|
603
|
+
for (let i = 0; i < hopWallets.length; i++) {
|
|
604
|
+
const nextRecipient = i < hopWallets.length - 1 ? hopWallets[i + 1].address : buyerWallets[0].address;
|
|
605
|
+
calls.push({
|
|
606
|
+
target: hopWallets[i].address,
|
|
607
|
+
allowFailure: false,
|
|
608
|
+
value: 0n,
|
|
609
|
+
callData: delegateInterface.encodeFunctionData('transferTo', [nextRecipient]),
|
|
519
610
|
});
|
|
520
611
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
612
|
+
// 第一个买家分配给其他买家
|
|
613
|
+
for (let i = 1; i < buyerWallets.length; i++) {
|
|
614
|
+
calls.push({
|
|
615
|
+
target: buyerWallets[0].address,
|
|
616
|
+
allowFailure: false,
|
|
617
|
+
value: 0n,
|
|
618
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [buyerWallets[i].address, buyAmountsPerBuyer[i]]),
|
|
528
619
|
});
|
|
529
620
|
}
|
|
530
|
-
|
|
621
|
+
// 4. 所有买家买入
|
|
622
|
+
for (let i = 0; i < buyerWallets.length; i++) {
|
|
623
|
+
const buyCall = buildBuyCallInternal(buyerWallets[i], buyAmountsPerBuyer[i], tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
624
|
+
calls.push(buyCall);
|
|
625
|
+
}
|
|
531
626
|
}
|
|
532
627
|
else {
|
|
533
|
-
//
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
628
|
+
// ========================================
|
|
629
|
+
// ✅ 普通模式(一卖多买)
|
|
630
|
+
// 1. 卖家卖出代币 → OKB 留在卖家账户
|
|
631
|
+
// 2. 利润从第一个买家 OKB 中扣除
|
|
632
|
+
// 3. 各买家用自己预存的 OKB 买入
|
|
633
|
+
// ========================================
|
|
634
|
+
console.log(`[Bundle Batch Swap] 普通模式`);
|
|
635
|
+
// 1. 卖家卖出(OKB 留在卖家账户)
|
|
636
|
+
const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
637
|
+
calls.push(sellCall);
|
|
638
|
+
// 2. 利润刮取(从第一个买家余额扣除)
|
|
639
|
+
if (profitAmount > 0n) {
|
|
640
|
+
calls.push({
|
|
641
|
+
target: buyerWallets[0].address,
|
|
642
|
+
allowFailure: false,
|
|
643
|
+
value: 0n,
|
|
644
|
+
callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
// 3. 所有买家买入(使用自己预存的 OKB)
|
|
648
|
+
for (let i = 0; i < buyerWallets.length; i++) {
|
|
649
|
+
const buyCall = buildBuyCallInternal(buyerWallets[i], buyAmountsPerBuyer[i], tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
|
|
650
|
+
calls.push(buyCall);
|
|
651
|
+
}
|
|
550
652
|
}
|
|
551
|
-
const authorizations = await Promise.all(authPromises);
|
|
552
653
|
// ========================================
|
|
553
|
-
//
|
|
654
|
+
// 构建交易
|
|
554
655
|
// ========================================
|
|
555
|
-
const signedTransaction = buildEIP7702TransactionSync(sellerWallet, authorizations, calls, 0n,
|
|
556
|
-
sellerNonce, feeData, config);
|
|
656
|
+
const signedTransaction = buildEIP7702TransactionSync(sellerWallet, authorizations, calls, 0n, sellerNonce, feeData, config);
|
|
557
657
|
return {
|
|
558
658
|
signedTransaction,
|
|
559
659
|
hopWallets: hopWalletInfos,
|
|
@@ -561,11 +661,12 @@ export async function bundleBatchSwap(params) {
|
|
|
561
661
|
sellerAddress: sellerWallet.address,
|
|
562
662
|
buyerAddresses: buyerWallets.map(w => w.address),
|
|
563
663
|
sellAmount: ethers.formatUnits(sellAmountWei, tokenDecimals),
|
|
564
|
-
estimatedOkbOut: ethers.formatEther(estimatedOkbOut),
|
|
565
|
-
estimatedBuyAmount:
|
|
566
|
-
profitAmount: ethers.formatEther(profitAmount),
|
|
664
|
+
estimatedOkbOut: ethers.formatEther(estimatedOkbOut),
|
|
665
|
+
estimatedBuyAmount: ethers.formatEther(totalBuyAmountAfterProfit),
|
|
666
|
+
profitAmount: ethers.formatEther(profitAmount),
|
|
567
667
|
profitRecipient,
|
|
568
668
|
userType,
|
|
669
|
+
useFundUtilization,
|
|
569
670
|
},
|
|
570
671
|
};
|
|
571
672
|
}
|