four-flap-meme-sdk 1.6.44 → 1.6.47
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.
|
@@ -557,12 +557,42 @@ export class AADexSwapExecutor {
|
|
|
557
557
|
return 0n;
|
|
558
558
|
}
|
|
559
559
|
})();
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
560
|
+
// ✅ 关键修复:提前计算 buyerPrefund,用于资金充足性检查
|
|
561
|
+
// 在 capitalMode 下,分发金额 = 买入金额 + buyerPrefund + 利润
|
|
562
|
+
// 必须确保卖出所得 >= 分发金额,否则交易会失败
|
|
563
|
+
const feeDataEarly = await this.aaManager.getFeeData();
|
|
564
|
+
const gasPriceEarly = feeDataEarly.gasPrice ?? feeDataEarly.maxFeePerGas ?? 5000000000n;
|
|
565
|
+
const estimateBuyerPrefundEarly = (deployed) => {
|
|
566
|
+
const callGas = DEX_BUY_CALL_GAS_LIMIT;
|
|
567
|
+
const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
|
|
568
|
+
const preVerifyGas = PRE_VERIFICATION_GAS;
|
|
569
|
+
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
570
|
+
return (totalGas * gasPriceEarly * PREFUND_BUFFER_PERCENT) / 100n;
|
|
571
|
+
};
|
|
572
|
+
// ✅ 计算所有 buyer 需要的 prefund 总和(仅原生代币模式 + capitalMode 需要)
|
|
573
|
+
const totalBuyerPrefundEarly = (useNativeToken && capitalMode)
|
|
574
|
+
? buyerAis.reduce((sum, ai) => sum + estimateBuyerPrefundEarly(ai.deployed), 0n)
|
|
575
|
+
: 0n;
|
|
576
|
+
// ✅ 计算利润(考虑 buyerPrefund 后的可用利润空间)
|
|
563
577
|
const profitWeiRaw = extractProfit ? calculateProfitWei(quotedSellOutWei, profitBps) : 0n;
|
|
564
|
-
|
|
565
|
-
const
|
|
578
|
+
// 利润上限 = 卖出所得 - 买入金额 - buyerPrefund
|
|
579
|
+
const profitCapWithPrefund = quotedSellOutWei > (totalBuyWei + totalBuyerPrefundEarly)
|
|
580
|
+
? (quotedSellOutWei - totalBuyWei - totalBuyerPrefundEarly)
|
|
581
|
+
: 0n;
|
|
582
|
+
const profitWei = profitWeiRaw > profitCapWithPrefund ? profitCapWithPrefund : profitWeiRaw;
|
|
583
|
+
// ✅ 资金充足性检查:卖出所得 >= 买入金额 + buyerPrefund + 利润
|
|
584
|
+
const totalDistributeNeeded = totalBuyWei + totalBuyerPrefundEarly + profitWei;
|
|
585
|
+
if (quotedSellOutWei > 0n && totalDistributeNeeded > quotedSellOutWei) {
|
|
586
|
+
console.error('[AA DEX 批量换手] ❌ 资金不足:', {
|
|
587
|
+
quotedSellOutWei: ethers.formatEther(quotedSellOutWei),
|
|
588
|
+
totalBuyWei: ethers.formatEther(totalBuyWei),
|
|
589
|
+
totalBuyerPrefund: ethers.formatEther(totalBuyerPrefundEarly),
|
|
590
|
+
profitWei: ethers.formatEther(profitWei),
|
|
591
|
+
totalNeeded: ethers.formatEther(totalDistributeNeeded),
|
|
592
|
+
shortfall: ethers.formatEther(totalDistributeNeeded - quotedSellOutWei),
|
|
593
|
+
});
|
|
594
|
+
throw new Error(`AA 批量换手:资金不足!卖出预估 ${ethers.formatEther(quotedSellOutWei)} OKB,但分发需要 ${ethers.formatEther(totalDistributeNeeded)} OKB(买入 ${ethers.formatEther(totalBuyWei)} + prefund ${ethers.formatEther(totalBuyerPrefundEarly)} + 利润 ${ethers.formatEther(profitWei)}),缺口 ${ethers.formatEther(totalDistributeNeeded - quotedSellOutWei)} OKB`);
|
|
595
|
+
}
|
|
566
596
|
// ✅ 利润在分发阶段通过 AA 内部刮取
|
|
567
597
|
// ✅ 资金分发逻辑(仅 capitalMode=true 时执行,对应 BSC flapQuickBatchSwapMerkle)
|
|
568
598
|
// capitalMode=false 时跳过分发,买方用自己的 OKB(对应 BSC flapBatchSwapMerkle)
|
|
@@ -625,31 +655,44 @@ export class AADexSwapExecutor {
|
|
|
625
655
|
const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
|
|
626
656
|
const totalBuyerPrefund = buyerPrefunds.reduce((a, b) => a + b, 0n);
|
|
627
657
|
console.log(`[AA DEX 直接分发] 买方数量: ${buyerSenders.length}, 总 prefund 预估: ${ethers.formatEther(totalBuyerPrefund)} OKB`);
|
|
628
|
-
// ✅
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
658
|
+
// ✅ 直接分发模式:
|
|
659
|
+
// - 原生代币模式:分发金额 = 买入金额 + 买方 prefund
|
|
660
|
+
// - ERC20 模式:只分发 ERC20 代币,prefund 需要 buyer 自己有 OKB
|
|
661
|
+
const items = buyerSenders.map((to, i) => {
|
|
662
|
+
const buyAmount = buyAmountsWei[i] ?? 0n;
|
|
663
|
+
const prefund = buyerPrefunds[i] ?? 0n;
|
|
664
|
+
return {
|
|
665
|
+
to,
|
|
666
|
+
buyAmount, // ERC20 代币金额
|
|
667
|
+
prefund, // OKB prefund(仅原生代币模式使用)
|
|
668
|
+
totalValue: buyAmount + prefund // 原生代币模式的总金额
|
|
669
|
+
};
|
|
670
|
+
}).filter(x => x.buyAmount > 0n);
|
|
671
|
+
// 添加利润接收者到分发列表(只用于原生代币模式的金额计算)
|
|
672
|
+
const profitItem = (extractProfit && profitWei > 0n)
|
|
673
|
+
? { to: profitRecipient, buyAmount: 0n, prefund: 0n, totalValue: profitWei }
|
|
674
|
+
: null;
|
|
637
675
|
const chunks = chunkArray(items, maxPerOp);
|
|
638
676
|
for (const ch of chunks) {
|
|
639
677
|
let callData;
|
|
640
678
|
if (useNativeToken) {
|
|
641
|
-
// ✅ 原生代币模式:使用 Multicall3
|
|
679
|
+
// ✅ 原生代币模式:使用 Multicall3 批量转账(包含 prefund)
|
|
680
|
+
const allItems = profitItem && ch === chunks[0] ? [...ch, profitItem] : ch;
|
|
642
681
|
const { totalValue, data } = encodeNativeDisperseViaMulticall3({
|
|
643
|
-
to:
|
|
644
|
-
values:
|
|
682
|
+
to: allItems.map(x => x.to),
|
|
683
|
+
values: allItems.map(x => x.totalValue),
|
|
645
684
|
});
|
|
646
685
|
callData = encodeExecute(MULTICALL3, totalValue, data);
|
|
647
686
|
}
|
|
648
687
|
else {
|
|
649
|
-
// ✅ ERC20
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
688
|
+
// ✅ ERC20 模式:只转账 ERC20 代币(不包含 OKB prefund)
|
|
689
|
+
// 注意:buyer 需要自己有足够的 OKB 来支付 prefund,否则交易会失败
|
|
690
|
+
const allItems = profitItem && ch === chunks[0]
|
|
691
|
+
? [...ch, { ...profitItem, buyAmount: profitWei }]
|
|
692
|
+
: ch;
|
|
693
|
+
const targets = allItems.map(() => quoteToken);
|
|
694
|
+
const values = allItems.map(() => 0n);
|
|
695
|
+
const datas = allItems.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.buyAmount]));
|
|
653
696
|
callData = encodeExecuteBatch(targets, values, datas);
|
|
654
697
|
}
|
|
655
698
|
const signedDisperse = await this.aaManager.buildUserOpWithState({
|
|
@@ -744,6 +787,24 @@ export class AADexSwapExecutor {
|
|
|
744
787
|
allGeneratedHopWallets.push(chainHops);
|
|
745
788
|
}
|
|
746
789
|
console.log(`[AA DEX 多跳] ✅ 使用预充值 hop 钱包: ${totalHopsNeeded} 个 (${buyerSenders.length} 条链 × ${effectiveHopCount} 跳)`);
|
|
790
|
+
// ✅ 关键修复:检查 buyer 钱包是否有足够的 OKB 支付 prefund
|
|
791
|
+
// ERC-4337 的 handleOps 会先验证所有 UserOps(需要 prefund),然后才执行
|
|
792
|
+
// 所以 buyer 需要在交易前就有足够的 OKB,不能依赖 hop 链在 execution 阶段转入的资金
|
|
793
|
+
const buyerBalances = await Promise.all(buyerSenders.map(s => provider.getBalance(s)));
|
|
794
|
+
const insufficientBuyers = [];
|
|
795
|
+
for (let i = 0; i < buyerSenders.length; i++) {
|
|
796
|
+
const buyerAi = buyerAis[i];
|
|
797
|
+
const buyerPrefundNeeded = estimatePrefund(DEX_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
|
|
798
|
+
if (buyerBalances[i] < buyerPrefundNeeded) {
|
|
799
|
+
insufficientBuyers.push(`${buyerSenders[i]} (余额: ${ethers.formatEther(buyerBalances[i])} OKB, 需要: ${ethers.formatEther(buyerPrefundNeeded)} OKB, 缺口: ${ethers.formatEther(buyerPrefundNeeded - buyerBalances[i])} OKB)`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (insufficientBuyers.length > 0) {
|
|
803
|
+
console.error('[AA DEX 多跳] ❌ 以下 buyer 钱包 OKB 余额不足以支付 prefund:', insufficientBuyers);
|
|
804
|
+
throw new Error(`Buyer 钱包 OKB 余额不足!在 ERC-4337 的 handleOps 中,所有 UserOps 在 Validation 阶段就需要支付 prefund,此时 hop 链还没有执行。` +
|
|
805
|
+
`请先给 buyer 钱包充值 OKB。不足的钱包: ${insufficientBuyers.length} 个`);
|
|
806
|
+
}
|
|
807
|
+
console.log('[AA DEX 多跳] ✅ Buyer 钱包 prefund 检查通过');
|
|
747
808
|
}
|
|
748
809
|
else {
|
|
749
810
|
// Paymaster 模式:生成新钱包(不需要预充值,prefund=0)
|
|
@@ -368,12 +368,42 @@ export class AAPortalSwapExecutor {
|
|
|
368
368
|
catch {
|
|
369
369
|
quotedSellOutWei = await this.portalQuery.quoteExactInput(tokenAddress, ZERO_ADDRESS, sellAmountWei);
|
|
370
370
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
371
|
+
// ✅ 关键修复:提前计算 buyerPrefund,用于资金充足性检查
|
|
372
|
+
// 在 capitalMode 下,分发金额 = 买入金额 + buyerPrefund + 利润
|
|
373
|
+
// 必须确保卖出所得 >= 分发金额,否则交易会失败
|
|
374
|
+
const feeDataEarly = await this.aaManager.getFeeData();
|
|
375
|
+
const gasPriceEarly = feeDataEarly.gasPrice ?? feeDataEarly.maxFeePerGas ?? 5000000000n;
|
|
376
|
+
const estimateBuyerPrefundEarly = (deployed) => {
|
|
377
|
+
const callGas = PORTAL_BUY_CALL_GAS_LIMIT;
|
|
378
|
+
const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
|
|
379
|
+
const preVerifyGas = PRE_VERIFICATION_GAS;
|
|
380
|
+
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
381
|
+
return (totalGas * gasPriceEarly * PREFUND_BUFFER_PERCENT) / 100n;
|
|
382
|
+
};
|
|
383
|
+
// ✅ 计算所有 buyer 需要的 prefund 总和(仅原生代币模式 + capitalMode 需要)
|
|
384
|
+
const totalBuyerPrefundEarly = (useNativeToken && capitalMode)
|
|
385
|
+
? buyerAis.reduce((sum, ai) => sum + estimateBuyerPrefundEarly(ai.deployed), 0n)
|
|
386
|
+
: 0n;
|
|
387
|
+
// ✅ 计算利润(考虑 buyerPrefund 后的可用利润空间)
|
|
374
388
|
const profitWeiRaw = extractProfit ? calculateProfitWei(quotedSellOutWei, profitBps) : 0n;
|
|
375
|
-
|
|
376
|
-
const
|
|
389
|
+
// 利润上限 = 卖出所得 - 买入金额 - buyerPrefund
|
|
390
|
+
const profitCapWithPrefund = quotedSellOutWei > (totalBuyWei + totalBuyerPrefundEarly)
|
|
391
|
+
? (quotedSellOutWei - totalBuyWei - totalBuyerPrefundEarly)
|
|
392
|
+
: 0n;
|
|
393
|
+
const profitWei = profitWeiRaw > profitCapWithPrefund ? profitCapWithPrefund : profitWeiRaw;
|
|
394
|
+
// ✅ 资金充足性检查:卖出所得 >= 买入金额 + buyerPrefund + 利润
|
|
395
|
+
const totalDistributeNeeded = totalBuyWei + totalBuyerPrefundEarly + profitWei;
|
|
396
|
+
if (quotedSellOutWei > 0n && totalDistributeNeeded > quotedSellOutWei) {
|
|
397
|
+
console.error('[AA Portal 批量换手] ❌ 资金不足:', {
|
|
398
|
+
quotedSellOutWei: ethers.formatEther(quotedSellOutWei),
|
|
399
|
+
totalBuyWei: ethers.formatEther(totalBuyWei),
|
|
400
|
+
totalBuyerPrefund: ethers.formatEther(totalBuyerPrefundEarly),
|
|
401
|
+
profitWei: ethers.formatEther(profitWei),
|
|
402
|
+
totalNeeded: ethers.formatEther(totalDistributeNeeded),
|
|
403
|
+
shortfall: ethers.formatEther(totalDistributeNeeded - quotedSellOutWei),
|
|
404
|
+
});
|
|
405
|
+
throw new Error(`AA 批量换手:资金不足!卖出预估 ${ethers.formatEther(quotedSellOutWei)} OKB,但分发需要 ${ethers.formatEther(totalDistributeNeeded)} OKB(买入 ${ethers.formatEther(totalBuyWei)} + prefund ${ethers.formatEther(totalBuyerPrefundEarly)} + 利润 ${ethers.formatEther(profitWei)}),缺口 ${ethers.formatEther(totalDistributeNeeded - quotedSellOutWei)} OKB`);
|
|
406
|
+
}
|
|
377
407
|
// ✅ 利润在分发阶段通过 AA 内部刮取
|
|
378
408
|
// ✅ 资金分发逻辑(仅 capitalMode=true 时执行,对应 BSC flapQuickBatchSwapMerkle)
|
|
379
409
|
// capitalMode=false 时跳过分发,买方用自己的 OKB(对应 BSC flapBatchSwapMerkle)
|
|
@@ -434,29 +464,44 @@ export class AAPortalSwapExecutor {
|
|
|
434
464
|
const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
|
|
435
465
|
const totalBuyerPrefund = buyerPrefunds.reduce((a, b) => a + b, 0n);
|
|
436
466
|
console.log(`[AA Portal 直接分发] 买方数量: ${buyerSenders.length}, 总 prefund 预估: ${ethers.formatEther(totalBuyerPrefund)} OKB`);
|
|
437
|
-
// ✅
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
467
|
+
// ✅ 直接分发模式:
|
|
468
|
+
// - 原生代币模式:分发金额 = 买入金额 + 买方 prefund
|
|
469
|
+
// - ERC20 模式:只分发 ERC20 代币,prefund 需要 buyer 自己有 OKB
|
|
470
|
+
const items = buyerSenders.map((to, i) => {
|
|
471
|
+
const buyAmount = buyAmountsWei[i] ?? 0n;
|
|
472
|
+
const prefund = buyerPrefunds[i] ?? 0n;
|
|
473
|
+
return {
|
|
474
|
+
to,
|
|
475
|
+
buyAmount, // ERC20 代币金额
|
|
476
|
+
prefund, // OKB prefund(仅原生代币模式使用)
|
|
477
|
+
totalValue: buyAmount + prefund // 原生代币模式的总金额
|
|
478
|
+
};
|
|
479
|
+
}).filter(x => x.buyAmount > 0n);
|
|
480
|
+
// 添加利润接收者到分发列表(只用于原生代币模式的金额计算)
|
|
481
|
+
const profitItem = (extractProfit && profitWei > 0n)
|
|
482
|
+
? { to: profitRecipient, buyAmount: 0n, prefund: 0n, totalValue: profitWei }
|
|
483
|
+
: null;
|
|
445
484
|
const chunks = chunkArray(items, maxPerOp);
|
|
446
485
|
for (const ch of chunks) {
|
|
447
486
|
let callData;
|
|
448
487
|
if (useNativeToken) {
|
|
488
|
+
// ✅ 原生代币模式:使用 Multicall3 批量转账(包含 prefund)
|
|
489
|
+
const allItems = profitItem && ch === chunks[0] ? [...ch, profitItem] : ch;
|
|
449
490
|
const { totalValue, data } = encodeNativeDisperseViaMulticall3({
|
|
450
|
-
to:
|
|
451
|
-
values:
|
|
491
|
+
to: allItems.map(x => x.to),
|
|
492
|
+
values: allItems.map(x => x.totalValue),
|
|
452
493
|
});
|
|
453
494
|
callData = encodeExecute(MULTICALL3, totalValue, data);
|
|
454
495
|
}
|
|
455
496
|
else {
|
|
456
|
-
// ERC20
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
497
|
+
// ✅ ERC20 模式:只转账 ERC20 代币(不包含 OKB prefund)
|
|
498
|
+
// 注意:buyer 需要自己有足够的 OKB 来支付 prefund,否则交易会失败
|
|
499
|
+
const allItems = profitItem && ch === chunks[0]
|
|
500
|
+
? [...ch, { ...profitItem, buyAmount: profitWei }]
|
|
501
|
+
: ch;
|
|
502
|
+
const targets = allItems.map(() => quoteToken);
|
|
503
|
+
const values = allItems.map(() => 0n);
|
|
504
|
+
const datas = allItems.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.buyAmount]));
|
|
460
505
|
callData = encodeExecuteBatch(targets, values, datas);
|
|
461
506
|
}
|
|
462
507
|
const signedDisperse = await this.aaManager.buildUserOpWithState({
|
|
@@ -552,6 +597,24 @@ export class AAPortalSwapExecutor {
|
|
|
552
597
|
allGeneratedHopWallets.push(chainHops);
|
|
553
598
|
}
|
|
554
599
|
console.log(`[AA Portal 多跳] ✅ 使用预充值 hop 钱包: ${totalHopsNeeded} 个`);
|
|
600
|
+
// ✅ 关键修复:检查 buyer 钱包是否有足够的 OKB 支付 prefund
|
|
601
|
+
// ERC-4337 的 handleOps 会先验证所有 UserOps(需要 prefund),然后才执行
|
|
602
|
+
// 所以 buyer 需要在交易前就有足够的 OKB,不能依赖 hop 链在 execution 阶段转入的资金
|
|
603
|
+
const buyerBalances = await Promise.all(buyerSenders.map(s => provider.getBalance(s)));
|
|
604
|
+
const insufficientBuyers = [];
|
|
605
|
+
for (let i = 0; i < buyerSenders.length; i++) {
|
|
606
|
+
const buyerAi = buyerAis[i];
|
|
607
|
+
const buyerPrefundNeeded = estimatePrefund(PORTAL_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
|
|
608
|
+
if (buyerBalances[i] < buyerPrefundNeeded) {
|
|
609
|
+
insufficientBuyers.push(`${buyerSenders[i]} (余额: ${ethers.formatEther(buyerBalances[i])} OKB, 需要: ${ethers.formatEther(buyerPrefundNeeded)} OKB, 缺口: ${ethers.formatEther(buyerPrefundNeeded - buyerBalances[i])} OKB)`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (insufficientBuyers.length > 0) {
|
|
613
|
+
console.error('[AA Portal 多跳] ❌ 以下 buyer 钱包 OKB 余额不足以支付 prefund:', insufficientBuyers);
|
|
614
|
+
throw new Error(`Buyer 钱包 OKB 余额不足!在 ERC-4337 的 handleOps 中,所有 UserOps 在 Validation 阶段就需要支付 prefund,此时 hop 链还没有执行。` +
|
|
615
|
+
`请先给 buyer 钱包充值 OKB。不足的钱包: ${insufficientBuyers.length} 个`);
|
|
616
|
+
}
|
|
617
|
+
console.log('[AA Portal 多跳] ✅ Buyer 钱包 prefund 检查通过');
|
|
555
618
|
}
|
|
556
619
|
else {
|
|
557
620
|
// Paymaster 模式:生成新钱包
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "four-flap-meme-sdk",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.47",
|
|
4
4
|
"description": "SDK for Flap bonding curve and four.meme TokenManager",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,4 +36,4 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"typescript": "^5.6.3"
|
|
38
38
|
}
|
|
39
|
-
}
|
|
39
|
+
}
|