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 = 2, // 设为 0 则不多跳,直接 seller buyer
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
- // 生成中间钱包(hopCount = 0 时不生成)
308
- const hopWalletInfos = hopCount > 0 ? generateRandomWallets(hopCount) : [];
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
- // ✅ 修复:获取卖出报价,基于预估的 OKB 输出计算利润
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) // isDoubleMode = 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
- // 同步签署授权(只需要卖家和买家,不需要 hop wallets)
339
+ // 验证余额
337
340
  // ========================================
338
- const authorizations = [];
339
- // 卖家授权(交易发起者,nonce +1)
340
- authorizations.push(signAuthorization(sellerWallet, delegateAddress, BigInt(sellerNonce + 1)));
341
- // 买家授权
342
- authorizations.push(signAuthorization(buyerWallet, delegateAddress, BigInt(buyerNonce)));
343
- // 等待所有授权签名完成
344
- const resolvedAuthorizations = await Promise.all(authorizations);
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
- // 计算买入金额(普通模式:买家用自己的 OKB)
349
- // FLAP 模式需要使用买家的实际余额,扣除利润后的金额
350
- const buyerAvailable = buyerBalance > profitAmount ? buyerBalance - profitAmount : 0n;
351
- console.log(`[Bundle Swap] 买家可用金额(扣除利润后): ${ethers.formatEther(buyerAvailable)} OKB`);
352
- // FLAP 模式需要买家有足够的 OKB
353
- if (tradeType === 'FLAP' && buyerAvailable <= 0n) {
354
- throw new Error('FLAP 内盘模式需要买家钱包有足够的 OKB');
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 calls = [];
362
+ const authorizations = await Promise.all(authPromises);
357
363
  // ========================================
358
- // ✅ 普通换手模式逻辑
359
- // 1. 卖家卖出代币 → OKB 留在卖家账户
360
- // 2. 买家用自己预存的 OKB 买入代币
361
- // 3. 利润从买家的 OKB 中扣除
364
+ // 构建调用
362
365
  // ========================================
363
- // 1. 卖家卖出(OKB 留在卖家账户,不转给买家)
364
- const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
365
- calls.push(sellCall);
366
- // 2. 利润刮取(从买家余额扣除,在买入之前)
367
- if (profitAmount > 0n) {
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: buyerWallet.address,
392
+ target: sellerWallet.address,
370
393
  allowFailure: false,
371
394
  value: 0n,
372
- callData: delegateInterface.encodeFunctionData('transferAmount', [profitRecipient, profitAmount]),
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, resolvedAuthorizations, calls, 0n, // 修复:利润使用 transferAmount 从卖家余额转账,不需要通过 msg.value 传递
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), // ✅ 预估卖出所得 OKB
393
- estimatedBuyAmount: '0',
394
- profitAmount: ethers.formatEther(profitAmount), // ✅ 利润是 OKB
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
- const hopWalletInfos = hopCount > 0 ? generateRandomWallets(hopCount) : [];
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
- // ✅ 修复:获取卖出报价,基于预估的 OKB 输出计算利润
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) // isDoubleMode = 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
- // FLAP 模式需要有效的报价(不能使用 0n 作为买入金额)
443
- if (tradeType === 'FLAP' && estimatedOkbOut <= 0n) {
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
- console.log(`[Bundle Batch Swap] 买家余额:`, buyerBalances.map(b => ethers.formatEther(b)));
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 delegateInterface = new ethers.Interface(UNIFIED_DELEGATE_ABI);
463
- const portalInterface = new ethers.Interface(FLAP_PORTAL_ABI);
464
- const calls = [];
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
- // 1. 卖家卖出(OKB 留在卖家账户,不转给买家)
472
- const sellCall = buildSellCallWithAmountInternal(sellerWallet, sellAmountWei, tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
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
- // FLAP 模式:使用买家的实际 OKB 余额(第一个买家扣除利润)
488
- buyAmountsPerBuyer = buyerBalances.map((balance, i) => {
489
- const b = BigInt(balance.toString());
490
- // 第一个买家需要扣除利润
491
- return i === 0 ? (b > profitAmount ? b - profitAmount : 0n) : b;
492
- });
493
- console.log(`[bundleBatchSwap] FLAP 模式,买家买入金额:`, buyAmountsPerBuyer.map(a => ethers.formatEther(a)));
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
- // 4. 所有买家买入(使用自己预存的 OKB)
500
- for (let i = 0; i < buyerWallets.length; i++) {
501
- const buyCall = buildBuyCallInternal(buyerWallets[i], buyAmountsPerBuyer[i], tokenAddress, tradeType, actualRouter, fee, delegateInterface, portalInterface);
502
- calls.push(buyCall);
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
- // 同步签署授权(只需要卖家和买家,不需要 hop wallets)
506
- // ========================================
507
- const authPromises = [];
508
- // 卖家授权(交易发起者,nonce +1)
509
- authPromises.push(signAuthorization(sellerWallet, delegateAddress, BigInt(sellerNonce + 1)));
510
- // 买家授权
511
- for (let i = 0; i < buyerWallets.length; i++) {
512
- authPromises.push(signAuthorization(buyerWallets[i], delegateAddress, BigInt(buyerNonces[i])));
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, // 利润使用 transferAmount 从卖家余额转账,不需要通过 msg.value 传递
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), // ✅ 预估卖出所得 OKB
528
- estimatedBuyAmount: '0',
529
- profitAmount: ethers.formatEther(profitAmount), // ✅ 利润是 OKB
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
- /** 主钱包私钥(发起者,支付 gas) */
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 [mainNonce, feeData] = await Promise.all([
149
- provider.getTransactionCount(mainWallet.address, 'pending'),
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
- calls.push({
180
- target: mainWallet.address,
181
- allowFailure: false,
182
- value: amtWei,
183
- callData: delegateInterface.encodeFunctionData('transferTo', [recipient]),
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
- calls.push({
208
- target: mainWallet.address,
209
- allowFailure: false,
210
- value: amtWei,
211
- callData: delegateInterface.encodeFunctionData('transferTo', [firstHop.address]),
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
- const totalValue = isNative ? totalAmount : 0n;
284
- const signedTransaction = buildEIP7702TransactionSync(mainWallet, authorizations, calls, totalValue, mainNonce, feeData, config);
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 计
@@ -204,6 +204,8 @@ export interface BundleSwapResult {
204
204
  profitRecipient?: string;
205
205
  /** 用户类型 */
206
206
  userType?: string;
207
+ /** 是否使用资金利用率模式 */
208
+ useFundUtilization?: boolean;
207
209
  };
208
210
  }
209
211
  export interface MultiHopTransferParams {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.7.61",
3
+ "version": "1.7.63",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",