four-flap-meme-sdk 1.6.30 → 1.6.31
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.
- package/dist/xlayer/dex-bundle-swap.js +134 -165
- package/dist/xlayer/portal-bundle-swap.js +136 -167
- package/package.json +1 -1
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Wallet, ethers } from 'ethers';
|
|
5
5
|
import { AANonceMap, } from './types.js';
|
|
6
|
-
import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_V3_FACTORY, WOKB, MULTICALL3, } from './constants.js';
|
|
6
|
+
import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_V3_FACTORY, WOKB, MULTICALL3, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, } from './constants.js';
|
|
7
|
+
// 多跳 prefund 估算用的 gas 常量(可配置)
|
|
8
|
+
const HOP_CALL_GAS_LIMIT = 150000n; // hop 转账操作
|
|
9
|
+
const DEX_BUY_CALL_GAS_LIMIT = 650000n; // DEX V3 买入操作
|
|
10
|
+
const PREFUND_BUFFER_PERCENT = 120n; // prefund buffer 百分比 (120 = 1.2x)
|
|
7
11
|
import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
|
|
8
12
|
import { encodeApproveCall, lpFeeProfileToV3Fee, } from './portal-ops.js';
|
|
9
13
|
import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, encodeSwapExactTokensForTokensSupportingFee, } from './dex.js';
|
|
@@ -586,13 +590,12 @@ export class AADexSwapExecutor {
|
|
|
586
590
|
const feeData = await this.aaManager.getFeeData();
|
|
587
591
|
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n; // 5 gwei fallback
|
|
588
592
|
const estimateBuyerPrefund = (deployed) => {
|
|
589
|
-
// 买入操作的 gas
|
|
590
|
-
const callGas =
|
|
591
|
-
const verifyGas = deployed ?
|
|
592
|
-
const preVerifyGas =
|
|
593
|
+
// 买入操作的 gas(使用常量)
|
|
594
|
+
const callGas = DEX_BUY_CALL_GAS_LIMIT;
|
|
595
|
+
const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
|
|
596
|
+
const preVerifyGas = PRE_VERIFICATION_GAS;
|
|
593
597
|
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
594
|
-
|
|
595
|
-
return (totalGas * gasPrice * 120n) / 100n;
|
|
598
|
+
return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
|
|
596
599
|
};
|
|
597
600
|
// 计算每个买方需要的 prefund
|
|
598
601
|
const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
|
|
@@ -637,183 +640,149 @@ export class AADexSwapExecutor {
|
|
|
637
640
|
}
|
|
638
641
|
}
|
|
639
642
|
else {
|
|
640
|
-
// ✅
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const generatedHopWallets = [];
|
|
644
|
-
for (let i = 0; i < hopCount; i++) {
|
|
645
|
-
const randomWallet = ethers.Wallet.createRandom();
|
|
646
|
-
generatedHopWallets.push(new ethers.Wallet(randomWallet.privateKey));
|
|
647
|
-
}
|
|
648
|
-
// 预测这些 hop 钱包的 AA sender 地址
|
|
649
|
-
const hopAiPromises = generatedHopWallets.map(async (w) => {
|
|
650
|
-
const sender = await this.aaManager.predictSenderAddress(w.address);
|
|
651
|
-
const code = await provider.getCode(sender);
|
|
652
|
-
const deployed = code && code !== '0x';
|
|
653
|
-
const initCode = deployed ? '0x' : this.aaManager.generateInitCode(w.address);
|
|
654
|
-
return { sender, deployed, initCode, ownerAddress: w.address };
|
|
655
|
-
});
|
|
656
|
-
const hopAis = await Promise.all(hopAiPromises);
|
|
657
|
-
const hopSenders = hopAis.map(ai => ai.sender);
|
|
658
|
-
const hopOwners = generatedHopWallets;
|
|
659
|
-
// ✅ 关键:为动态生成的 hop 钱包初始化 nonce(新账户从 0 开始)
|
|
660
|
-
for (const hopAi of hopAis) {
|
|
661
|
-
nonceMap.init(hopAi.sender, 0n);
|
|
662
|
-
}
|
|
663
|
-
// ✅ 关键修复:计算每个 hop 钱包执行 UserOp 需要的 prefund(gas 费用)
|
|
664
|
-
// EntryPoint 在 validation 阶段会扣除 prefund,此时 hop 钱包还没收到 seller 的转账
|
|
665
|
-
// 因此需要在 seller 转账时额外包含 hop 的 gas 费用
|
|
643
|
+
// ✅ 多跳模式(BSC 模式):每个买方有独立的多跳链
|
|
644
|
+
// 填写 3 跳 + 2 个买方 = 2 条独立的多跳链,每条 3 个 hop 钱包 = 6 个动态生成的 hop 钱包
|
|
645
|
+
console.log('[AA DEX 批量换手] 进入多跳模式 (hopCount > 0):', { hopCount, buyerCount: buyerSenders.length });
|
|
666
646
|
const feeData = await this.aaManager.getFeeData();
|
|
667
|
-
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
|
|
668
|
-
//
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const callGas = 150000n;
|
|
673
|
-
const verifyGas = deployed ? 250000n : 800000n;
|
|
674
|
-
const preVerifyGas = 21000n;
|
|
647
|
+
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
|
|
648
|
+
// 预估 prefund 的函数(使用全局常量)
|
|
649
|
+
const estimatePrefund = (callGas, deployed) => {
|
|
650
|
+
const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
|
|
651
|
+
const preVerifyGas = PRE_VERIFICATION_GAS;
|
|
675
652
|
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
676
|
-
|
|
677
|
-
return (totalGas * gasPrice * 120n) / 100n;
|
|
653
|
+
return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
|
|
678
654
|
};
|
|
679
|
-
//
|
|
680
|
-
|
|
681
|
-
const
|
|
682
|
-
for (let
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const prefund = estimateHopPrefund(hopDeployed);
|
|
695
|
-
hopPrefunds.push(prefund);
|
|
696
|
-
totalHopPrefund += prefund;
|
|
655
|
+
// ✅ 为每个买方生成独立的多跳链
|
|
656
|
+
// hopChains[buyerIdx] = [hop0, hop1, ..., hop(H-1)] 每条链有 hopCount 个 hop 钱包
|
|
657
|
+
const allGeneratedHopWallets = [];
|
|
658
|
+
for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
|
|
659
|
+
const chainHops = [];
|
|
660
|
+
for (let h = 0; h < hopCount; h++) {
|
|
661
|
+
const randomWallet = ethers.Wallet.createRandom();
|
|
662
|
+
const wallet = new ethers.Wallet(randomWallet.privateKey);
|
|
663
|
+
const sender = await this.aaManager.predictSenderAddress(wallet.address);
|
|
664
|
+
const code = await provider.getCode(sender);
|
|
665
|
+
const deployed = code !== null && code !== '0x';
|
|
666
|
+
const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
|
|
667
|
+
chainHops.push({ wallet, sender, deployed, initCode });
|
|
668
|
+
// 初始化 nonce
|
|
669
|
+
nonceMap.init(sender, 0n);
|
|
697
670
|
}
|
|
671
|
+
allGeneratedHopWallets.push(chainHops);
|
|
698
672
|
}
|
|
699
|
-
console.log(`[AA DEX 多跳] hop
|
|
700
|
-
|
|
701
|
-
let
|
|
702
|
-
// ✅
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
673
|
+
console.log(`[AA DEX 多跳] 总 hop 钱包数: ${buyerSenders.length * hopCount} (${buyerSenders.length} 条链 × ${hopCount} 跳)`);
|
|
674
|
+
// ✅ 利润刮取(只在第一笔 seller UserOp 中执行)
|
|
675
|
+
let profitHandled = false;
|
|
676
|
+
// ✅ 构建每条多跳链的 UserOps
|
|
677
|
+
for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
|
|
678
|
+
const chainHops = allGeneratedHopWallets[buyerIdx];
|
|
679
|
+
const buyerSender = buyerSenders[buyerIdx];
|
|
680
|
+
const buyerAi = buyerAis[buyerIdx];
|
|
681
|
+
const buyAmount = buyAmountsWei[buyerIdx] ?? 0n;
|
|
682
|
+
if (buyAmount <= 0n)
|
|
683
|
+
continue;
|
|
684
|
+
// 计算这条链上所有 hop 的 prefund(使用常量)
|
|
685
|
+
const hopPrefunds = chainHops.map((hop, idx) => {
|
|
686
|
+
// 最后一个 hop 转账给 buyer,中间 hop 转账给下一个 hop
|
|
687
|
+
return estimatePrefund(HOP_CALL_GAS_LIMIT, hop.deployed);
|
|
688
|
+
});
|
|
689
|
+
const totalHopPrefund = hopPrefunds.reduce((a, b) => a + b, 0n);
|
|
690
|
+
// 计算 buyer 的 prefund(用于买入,使用常量)
|
|
691
|
+
const buyerPrefund = estimatePrefund(DEX_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
|
|
692
|
+
// 这条链的总金额 = 买入金额 + 所有 hop prefund + buyer prefund
|
|
693
|
+
const chainTotalAmount = buyAmount + totalHopPrefund + buyerPrefund;
|
|
694
|
+
// 1) seller → hop0
|
|
695
|
+
const hop0 = chainHops[0];
|
|
696
|
+
let callData0;
|
|
697
|
+
if (useNativeToken) {
|
|
698
|
+
if (!profitHandled && extractProfit && profitWei > 0n) {
|
|
699
|
+
// 第一笔 seller UserOp 同时刮取利润
|
|
700
|
+
callData0 = encodeExecuteBatch([hop0.sender, profitRecipient], [chainTotalAmount, profitWei], ['0x', '0x']);
|
|
701
|
+
profitHandled = true;
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
callData0 = encodeExecute(hop0.sender, chainTotalAmount, '0x');
|
|
705
|
+
}
|
|
708
706
|
}
|
|
709
707
|
else {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
708
|
+
if (!profitHandled && extractProfit && profitWei > 0n) {
|
|
709
|
+
const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
|
|
710
|
+
const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
|
|
711
|
+
callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
|
|
712
|
+
profitHandled = true;
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
|
|
716
|
+
callData0 = encodeExecute(quoteToken, 0n, transferData);
|
|
717
|
+
}
|
|
719
718
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
719
|
+
const signedSellerToHop0 = await this.aaManager.buildUserOpWithState({
|
|
720
|
+
ownerWallet: sellerOwner,
|
|
721
|
+
sender: sellerAi.sender,
|
|
722
|
+
nonce: nonceMap.next(sellerAi.sender),
|
|
723
|
+
initCode: consumeInitCode(sellerAi.sender),
|
|
724
|
+
callData: callData0,
|
|
725
|
+
signOnly: true,
|
|
726
|
+
});
|
|
727
|
+
outOps.push(signedSellerToHop0.userOp);
|
|
728
|
+
// 2) hop[j] → hop[j+1] (中间跳)
|
|
729
|
+
let remainingPrefund = totalHopPrefund;
|
|
730
|
+
for (let j = 0; j < chainHops.length - 1; j++) {
|
|
731
|
+
const currentHop = chainHops[j];
|
|
732
|
+
const nextHop = chainHops[j + 1];
|
|
733
|
+
// 当前 hop 扣除自己的 prefund 后转给下一个
|
|
734
|
+
remainingPrefund -= hopPrefunds[j];
|
|
735
|
+
const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
|
|
736
|
+
let callData;
|
|
737
|
+
if (useNativeToken) {
|
|
738
|
+
callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [nextHop.sender, buyAmount]);
|
|
742
|
+
callData = encodeExecute(quoteToken, 0n, transferData);
|
|
743
|
+
}
|
|
744
|
+
const signedHop = await this.aaManager.buildUserOpWithState({
|
|
745
|
+
ownerWallet: currentHop.wallet,
|
|
746
|
+
sender: currentHop.sender,
|
|
747
|
+
nonce: nonceMap.next(currentHop.sender),
|
|
748
|
+
initCode: currentHop.initCode,
|
|
749
|
+
callData,
|
|
750
|
+
signOnly: true,
|
|
751
|
+
});
|
|
752
|
+
outOps.push(signedHop.userOp);
|
|
723
753
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
nonce: nonceMap.next(sellerAi.sender),
|
|
729
|
-
initCode: consumeInitCode(sellerAi.sender),
|
|
730
|
-
callData: callData0,
|
|
731
|
-
signOnly: true,
|
|
732
|
-
});
|
|
733
|
-
outOps.push(signedToHop0.userOp);
|
|
734
|
-
// 2) hop j -> hop j+1
|
|
735
|
-
// ✅ 修复:动态生成的 hop 钱包只做资金中转,不保留任何金额
|
|
736
|
-
let prefixPrefundUsed = hopPrefunds[0] ?? 0n; // hop0 自己用掉的 prefund
|
|
737
|
-
for (let j = 0; j < hopSenders.length - 1; j++) {
|
|
738
|
-
const sender = hopSenders[j];
|
|
739
|
-
const next = hopSenders[j + 1];
|
|
740
|
-
const hopAi = hopAis[j];
|
|
741
|
-
// 剩余需要转给后续 hop 的金额 = totalBuyWei + (剩余 hop 的 prefund)
|
|
742
|
-
const remainingPrefund = totalHopPrefund - prefixPrefundUsed;
|
|
743
|
-
const amountToTransfer = totalBuyWei + remainingPrefund;
|
|
744
|
-
if (amountToTransfer <= 0n)
|
|
745
|
-
break;
|
|
746
|
-
let callData;
|
|
754
|
+
// 3) lastHop → buyer
|
|
755
|
+
const lastHop = chainHops[chainHops.length - 1];
|
|
756
|
+
const amountToBuyer = buyAmount + buyerPrefund;
|
|
757
|
+
let callDataLast;
|
|
747
758
|
if (useNativeToken) {
|
|
748
|
-
|
|
759
|
+
callDataLast = encodeExecute(buyerSender, amountToBuyer, '0x');
|
|
749
760
|
}
|
|
750
761
|
else {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
callData = encodeExecute(quoteToken, 0n, transferData);
|
|
762
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [buyerSender, buyAmount]);
|
|
763
|
+
callDataLast = encodeExecute(quoteToken, 0n, transferData);
|
|
754
764
|
}
|
|
755
|
-
const
|
|
756
|
-
ownerWallet:
|
|
757
|
-
sender,
|
|
758
|
-
nonce: nonceMap.next(sender),
|
|
759
|
-
initCode:
|
|
760
|
-
callData,
|
|
765
|
+
const signedLastHop = await this.aaManager.buildUserOpWithState({
|
|
766
|
+
ownerWallet: lastHop.wallet,
|
|
767
|
+
sender: lastHop.sender,
|
|
768
|
+
nonce: nonceMap.next(lastHop.sender),
|
|
769
|
+
initCode: lastHop.initCode,
|
|
770
|
+
callData: callDataLast,
|
|
761
771
|
signOnly: true,
|
|
762
772
|
});
|
|
763
|
-
outOps.push(
|
|
764
|
-
// ✅ 更新已使用的 prefund(当前 hop 的 prefund 已被扣除)
|
|
765
|
-
prefixPrefundUsed += hopPrefunds[j + 1] ?? 0n;
|
|
773
|
+
outOps.push(signedLastHop.userOp);
|
|
766
774
|
}
|
|
767
|
-
//
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const callGas = 650000n; // DEX V3 买入
|
|
775
|
-
const verifyGas = deployed ? 250000n : 800000n;
|
|
776
|
-
const preVerifyGas = 60000n;
|
|
777
|
-
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
778
|
-
return (totalGas * gasPrice * 120n) / 100n;
|
|
779
|
-
};
|
|
780
|
-
const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
|
|
781
|
-
// ✅ 分发金额 = 买入金额 + 买方 prefund
|
|
782
|
-
const allItems = buyerSenders.map((to, i) => ({
|
|
783
|
-
to,
|
|
784
|
-
value: (buyAmountsWei[i] ?? 0n) + (buyerPrefunds[i] ?? 0n)
|
|
785
|
-
})).filter(x => x.value > 0n);
|
|
786
|
-
const restChunks = chunkArray(allItems, maxPerOp);
|
|
787
|
-
for (const ch of restChunks) {
|
|
788
|
-
let callData;
|
|
789
|
-
if (useNativeToken) {
|
|
790
|
-
const { totalValue, data } = encodeNativeDisperseViaMulticall3({
|
|
791
|
-
to: ch.map(x => x.to),
|
|
792
|
-
values: ch.map(x => x.value),
|
|
775
|
+
// ✅ 保存所有动态生成的多跳钱包信息,用于后续导出
|
|
776
|
+
const allHopWalletsFlat = [];
|
|
777
|
+
for (const chainHops of allGeneratedHopWallets) {
|
|
778
|
+
for (const hop of chainHops) {
|
|
779
|
+
allHopWalletsFlat.push({
|
|
780
|
+
address: hop.sender,
|
|
781
|
+
privateKey: hop.wallet.privateKey,
|
|
793
782
|
});
|
|
794
|
-
callData = encodeExecute(MULTICALL3, totalValue, data);
|
|
795
783
|
}
|
|
796
|
-
else {
|
|
797
|
-
const targets = ch.map(() => quoteToken);
|
|
798
|
-
const values = ch.map(() => 0n);
|
|
799
|
-
const datas = ch.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.value]));
|
|
800
|
-
callData = encodeExecuteBatch(targets, values, datas);
|
|
801
|
-
}
|
|
802
|
-
const signedDisperse = await this.aaManager.buildUserOpWithState({
|
|
803
|
-
ownerWallet: lastHopOwner,
|
|
804
|
-
sender: lastHopSender,
|
|
805
|
-
nonce: nonceMap.next(lastHopSender),
|
|
806
|
-
initCode: lastHopAi.initCode || '0x', // ✅ 使用动态生成的 hop 钱包的 initCode
|
|
807
|
-
callData,
|
|
808
|
-
signOnly: true,
|
|
809
|
-
});
|
|
810
|
-
outOps.push(signedDisperse.userOp);
|
|
811
784
|
}
|
|
812
|
-
|
|
813
|
-
this._generatedHopWallets = generatedHopWallets.map((w, i) => ({
|
|
814
|
-
address: hopSenders[i],
|
|
815
|
-
privateKey: w.privateKey,
|
|
816
|
-
}));
|
|
785
|
+
this._generatedHopWallets = allHopWalletsFlat;
|
|
817
786
|
}
|
|
818
787
|
}
|
|
819
788
|
else if (!capitalMode && extractProfit && profitWei > 0n) {
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Wallet, ethers } from 'ethers';
|
|
5
5
|
import { AANonceMap, } from './types.js';
|
|
6
|
-
import { FLAP_PORTAL, ZERO_ADDRESS, MULTICALL3, } from './constants.js';
|
|
6
|
+
import { FLAP_PORTAL, ZERO_ADDRESS, MULTICALL3, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, } from './constants.js';
|
|
7
|
+
// 多跳 prefund 估算用的 gas 常量(可配置)
|
|
8
|
+
const HOP_CALL_GAS_LIMIT = 150000n; // hop 转账操作
|
|
9
|
+
const PORTAL_BUY_CALL_GAS_LIMIT = 450000n; // Portal 买入操作
|
|
10
|
+
const PREFUND_BUFFER_PERCENT = 120n; // prefund buffer 百分比 (120 = 1.2x)
|
|
7
11
|
import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
|
|
8
12
|
import { encodeBuyCall, encodeSellCall, encodeBuyCallWithQuote, encodeSellCallWithQuote, encodeApproveCall, PortalQuery, parseOkb, } from './portal-ops.js';
|
|
9
13
|
import { BundleExecutor } from './bundle.js';
|
|
@@ -395,13 +399,12 @@ export class AAPortalSwapExecutor {
|
|
|
395
399
|
const feeData = await this.aaManager.getFeeData();
|
|
396
400
|
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n; // 5 gwei fallback
|
|
397
401
|
const estimateBuyerPrefund = (deployed) => {
|
|
398
|
-
// 买入操作的 gas
|
|
399
|
-
const callGas =
|
|
400
|
-
const verifyGas = deployed ?
|
|
401
|
-
const preVerifyGas =
|
|
402
|
+
// 买入操作的 gas(使用常量)
|
|
403
|
+
const callGas = PORTAL_BUY_CALL_GAS_LIMIT;
|
|
404
|
+
const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
|
|
405
|
+
const preVerifyGas = PRE_VERIFICATION_GAS;
|
|
402
406
|
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
403
|
-
|
|
404
|
-
return (totalGas * gasPrice * 120n) / 100n;
|
|
407
|
+
return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
|
|
405
408
|
};
|
|
406
409
|
// 计算每个买方需要的 prefund
|
|
407
410
|
const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
|
|
@@ -444,185 +447,151 @@ export class AAPortalSwapExecutor {
|
|
|
444
447
|
}
|
|
445
448
|
}
|
|
446
449
|
else {
|
|
447
|
-
// ✅
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const generatedHopWallets = [];
|
|
451
|
-
for (let i = 0; i < hopCount; i++) {
|
|
452
|
-
const randomWallet = ethers.Wallet.createRandom();
|
|
453
|
-
generatedHopWallets.push(new ethers.Wallet(randomWallet.privateKey));
|
|
454
|
-
}
|
|
455
|
-
// 预测这些 hop 钱包的 AA sender 地址
|
|
456
|
-
const provider = this.aaManager.getProvider();
|
|
457
|
-
const hopAiPromises = generatedHopWallets.map(async (w) => {
|
|
458
|
-
const sender = await this.aaManager.predictSenderAddress(w.address);
|
|
459
|
-
const code = await provider.getCode(sender);
|
|
460
|
-
const deployed = code && code !== '0x';
|
|
461
|
-
const initCode = deployed ? '0x' : this.aaManager.generateInitCode(w.address);
|
|
462
|
-
return { sender, deployed, initCode, ownerAddress: w.address };
|
|
463
|
-
});
|
|
464
|
-
const hopAis = await Promise.all(hopAiPromises);
|
|
465
|
-
const hopSenders = hopAis.map(ai => ai.sender);
|
|
466
|
-
const hopOwners = generatedHopWallets;
|
|
467
|
-
// ✅ 关键:为动态生成的 hop 钱包初始化 nonce(新账户从 0 开始)
|
|
468
|
-
for (const hopAi of hopAis) {
|
|
469
|
-
nonceMap.init(hopAi.sender, 0n);
|
|
470
|
-
}
|
|
471
|
-
// ✅ 关键修复:计算每个 hop 钱包执行 UserOp 需要的 prefund(gas 费用)
|
|
472
|
-
// EntryPoint 在 validation 阶段会扣除 prefund,此时 hop 钱包还没收到 seller 的转账
|
|
473
|
-
// 因此需要在 seller 转账时额外包含 hop 的 gas 费用
|
|
450
|
+
// ✅ 多跳模式(BSC 模式):每个买方有独立的多跳链
|
|
451
|
+
// 填写 3 跳 + 2 个买方 = 2 条独立的多跳链,每条 3 个 hop 钱包 = 6 个动态生成的 hop 钱包
|
|
452
|
+
console.log('[AA Portal 批量换手] 进入多跳模式 (hopCount > 0):', { hopCount, buyerCount: buyerSenders.length });
|
|
474
453
|
const feeData = await this.aaManager.getFeeData();
|
|
475
|
-
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
|
|
476
|
-
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
const verifyGas = deployed ? 250000n : 800000n;
|
|
482
|
-
const preVerifyGas = 21000n;
|
|
454
|
+
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
|
|
455
|
+
const provider = this.aaManager.getProvider();
|
|
456
|
+
// 预估 prefund 的函数(使用全局常量)
|
|
457
|
+
const estimatePrefund = (callGas, deployed) => {
|
|
458
|
+
const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
|
|
459
|
+
const preVerifyGas = PRE_VERIFICATION_GAS;
|
|
483
460
|
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
484
|
-
|
|
485
|
-
return (totalGas * gasPrice * 120n) / 100n;
|
|
461
|
+
return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
|
|
486
462
|
};
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
for (let
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
totalHopPrefund += prefund;
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
const prefund = estimateHopPrefund(hopDeployed);
|
|
506
|
-
hopPrefunds.push(prefund);
|
|
507
|
-
totalHopPrefund += prefund;
|
|
463
|
+
// ✅ 为每个买方生成独立的多跳链
|
|
464
|
+
// hopChains[buyerIdx] = [hop0, hop1, ..., hop(H-1)] 每条链有 hopCount 个 hop 钱包
|
|
465
|
+
const allGeneratedHopWallets = [];
|
|
466
|
+
for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
|
|
467
|
+
const chainHops = [];
|
|
468
|
+
for (let h = 0; h < hopCount; h++) {
|
|
469
|
+
const randomWallet = ethers.Wallet.createRandom();
|
|
470
|
+
const wallet = new ethers.Wallet(randomWallet.privateKey);
|
|
471
|
+
const sender = await this.aaManager.predictSenderAddress(wallet.address);
|
|
472
|
+
const code = await provider.getCode(sender);
|
|
473
|
+
const deployed = code !== null && code !== '0x';
|
|
474
|
+
const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
|
|
475
|
+
chainHops.push({ wallet, sender, deployed, initCode });
|
|
476
|
+
// 初始化 nonce
|
|
477
|
+
nonceMap.init(sender, 0n);
|
|
508
478
|
}
|
|
479
|
+
allGeneratedHopWallets.push(chainHops);
|
|
509
480
|
}
|
|
510
|
-
console.log(`[AA 多跳] hop
|
|
511
|
-
|
|
512
|
-
let
|
|
513
|
-
// ✅
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
481
|
+
console.log(`[AA 多跳] 总 hop 钱包数: ${buyerSenders.length * hopCount} (${buyerSenders.length} 条链 × ${hopCount} 跳)`);
|
|
482
|
+
// ✅ 利润刮取(只在第一笔 seller UserOp 中执行)
|
|
483
|
+
let profitHandled = false;
|
|
484
|
+
// ✅ 构建每条多跳链的 UserOps
|
|
485
|
+
for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
|
|
486
|
+
const chainHops = allGeneratedHopWallets[buyerIdx];
|
|
487
|
+
const buyerSender = buyerSenders[buyerIdx];
|
|
488
|
+
const buyerAi = buyerAis[buyerIdx];
|
|
489
|
+
const buyAmount = buyAmountsWei[buyerIdx] ?? 0n;
|
|
490
|
+
if (buyAmount <= 0n)
|
|
491
|
+
continue;
|
|
492
|
+
// 计算这条链上所有 hop 的 prefund(使用常量)
|
|
493
|
+
const hopPrefunds = chainHops.map((hop, idx) => {
|
|
494
|
+
const isLastHop = idx === chainHops.length - 1;
|
|
495
|
+
// 最后一个 hop 转账给 buyer,中间 hop 转账给下一个 hop
|
|
496
|
+
return estimatePrefund(HOP_CALL_GAS_LIMIT, hop.deployed);
|
|
497
|
+
});
|
|
498
|
+
const totalHopPrefund = hopPrefunds.reduce((a, b) => a + b, 0n);
|
|
499
|
+
// 计算 buyer 的 prefund(用于买入,使用常量)
|
|
500
|
+
const buyerPrefund = estimatePrefund(PORTAL_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
|
|
501
|
+
// 这条链的总金额 = 买入金额 + 所有 hop prefund + buyer prefund
|
|
502
|
+
const chainTotalAmount = buyAmount + totalHopPrefund + buyerPrefund;
|
|
503
|
+
// 1) seller → hop0
|
|
504
|
+
const hop0 = chainHops[0];
|
|
505
|
+
let callData0;
|
|
506
|
+
if (useNativeToken) {
|
|
507
|
+
if (!profitHandled && extractProfit && profitWei > 0n) {
|
|
508
|
+
// 第一笔 seller UserOp 同时刮取利润
|
|
509
|
+
callData0 = encodeExecuteBatch([hop0.sender, profitRecipient], [chainTotalAmount, profitWei], ['0x', '0x']);
|
|
510
|
+
profitHandled = true;
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
callData0 = encodeExecute(hop0.sender, chainTotalAmount, '0x');
|
|
514
|
+
}
|
|
518
515
|
}
|
|
519
516
|
else {
|
|
520
|
-
|
|
517
|
+
if (!profitHandled && extractProfit && profitWei > 0n) {
|
|
518
|
+
const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
|
|
519
|
+
const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
|
|
520
|
+
callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
|
|
521
|
+
profitHandled = true;
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
|
|
525
|
+
callData0 = encodeExecute(quoteToken, 0n, transferData);
|
|
526
|
+
}
|
|
521
527
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
callData0
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
528
|
+
const signedSellerToHop0 = await this.aaManager.buildUserOpWithState({
|
|
529
|
+
ownerWallet: sellerOwner,
|
|
530
|
+
sender: sellerAi.sender,
|
|
531
|
+
nonce: nonceMap.next(sellerAi.sender),
|
|
532
|
+
initCode: consumeInitCode(sellerAi.sender),
|
|
533
|
+
callData: callData0,
|
|
534
|
+
signOnly: true,
|
|
535
|
+
});
|
|
536
|
+
outOps.push(signedSellerToHop0.userOp);
|
|
537
|
+
// 2) hop[j] → hop[j+1] (中间跳)
|
|
538
|
+
let remainingPrefund = totalHopPrefund;
|
|
539
|
+
for (let j = 0; j < chainHops.length - 1; j++) {
|
|
540
|
+
const currentHop = chainHops[j];
|
|
541
|
+
const nextHop = chainHops[j + 1];
|
|
542
|
+
// 当前 hop 扣除自己的 prefund 后转给下一个
|
|
543
|
+
remainingPrefund -= hopPrefunds[j];
|
|
544
|
+
const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
|
|
545
|
+
let callData;
|
|
546
|
+
if (useNativeToken) {
|
|
547
|
+
callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [nextHop.sender, buyAmount]);
|
|
551
|
+
callData = encodeExecute(quoteToken, 0n, transferData);
|
|
552
|
+
}
|
|
553
|
+
const signedHop = await this.aaManager.buildUserOpWithState({
|
|
554
|
+
ownerWallet: currentHop.wallet,
|
|
555
|
+
sender: currentHop.sender,
|
|
556
|
+
nonce: nonceMap.next(currentHop.sender),
|
|
557
|
+
initCode: currentHop.initCode,
|
|
558
|
+
callData,
|
|
559
|
+
signOnly: true,
|
|
560
|
+
});
|
|
561
|
+
outOps.push(signedHop.userOp);
|
|
532
562
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
nonce: nonceMap.next(sellerAi.sender),
|
|
538
|
-
initCode: consumeInitCode(sellerAi.sender),
|
|
539
|
-
callData: callData0,
|
|
540
|
-
signOnly: true,
|
|
541
|
-
});
|
|
542
|
-
outOps.push(signedToHop0.userOp);
|
|
543
|
-
// 2) hop j -> hop j+1
|
|
544
|
-
// ✅ 修复:动态生成的 hop 钱包只做资金中转,不保留任何金额
|
|
545
|
-
let prefixPrefundUsed = hopPrefunds[0] ?? 0n; // hop0 自己用掉的 prefund
|
|
546
|
-
for (let j = 0; j < hopSenders.length - 1; j++) {
|
|
547
|
-
const sender = hopSenders[j];
|
|
548
|
-
const next = hopSenders[j + 1];
|
|
549
|
-
const hopAi = hopAis[j];
|
|
550
|
-
// 剩余需要转给后续 hop 的金额 = totalBuyWei + (剩余 hop 的 prefund)
|
|
551
|
-
const remainingPrefund = totalHopPrefund - prefixPrefundUsed;
|
|
552
|
-
const amountToTransfer = totalBuyWei + remainingPrefund;
|
|
553
|
-
if (amountToTransfer <= 0n)
|
|
554
|
-
break;
|
|
555
|
-
let callData;
|
|
563
|
+
// 3) lastHop → buyer
|
|
564
|
+
const lastHop = chainHops[chainHops.length - 1];
|
|
565
|
+
const amountToBuyer = buyAmount + buyerPrefund;
|
|
566
|
+
let callDataLast;
|
|
556
567
|
if (useNativeToken) {
|
|
557
|
-
|
|
568
|
+
callDataLast = encodeExecute(buyerSender, amountToBuyer, '0x');
|
|
558
569
|
}
|
|
559
570
|
else {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
callData = encodeExecute(quoteToken, 0n, transferData);
|
|
571
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [buyerSender, buyAmount]);
|
|
572
|
+
callDataLast = encodeExecute(quoteToken, 0n, transferData);
|
|
563
573
|
}
|
|
564
|
-
const
|
|
565
|
-
ownerWallet:
|
|
566
|
-
sender,
|
|
567
|
-
nonce: nonceMap.next(sender),
|
|
568
|
-
initCode:
|
|
569
|
-
callData,
|
|
574
|
+
const signedLastHop = await this.aaManager.buildUserOpWithState({
|
|
575
|
+
ownerWallet: lastHop.wallet,
|
|
576
|
+
sender: lastHop.sender,
|
|
577
|
+
nonce: nonceMap.next(lastHop.sender),
|
|
578
|
+
initCode: lastHop.initCode,
|
|
579
|
+
callData: callDataLast,
|
|
570
580
|
signOnly: true,
|
|
571
581
|
});
|
|
572
|
-
outOps.push(
|
|
573
|
-
// ✅ 更新已使用的 prefund(当前 hop 的 prefund 已被扣除)
|
|
574
|
-
prefixPrefundUsed += hopPrefunds[j + 1] ?? 0n;
|
|
582
|
+
outOps.push(signedLastHop.userOp);
|
|
575
583
|
}
|
|
576
|
-
//
|
|
577
|
-
const
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const callGas = 450000n; // Portal 买入
|
|
584
|
-
const verifyGas = deployed ? 250000n : 800000n;
|
|
585
|
-
const preVerifyGas = 60000n;
|
|
586
|
-
const totalGas = callGas + verifyGas + preVerifyGas;
|
|
587
|
-
return (totalGas * gasPrice * 120n) / 100n;
|
|
588
|
-
};
|
|
589
|
-
const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
|
|
590
|
-
// ✅ 分发金额 = 买入金额 + 买方 prefund
|
|
591
|
-
const allItems = buyerSenders.map((to, i) => ({
|
|
592
|
-
to,
|
|
593
|
-
value: (buyAmountsWei[i] ?? 0n) + (buyerPrefunds[i] ?? 0n)
|
|
594
|
-
})).filter(x => x.value > 0n);
|
|
595
|
-
const restChunks = chunkArray(allItems, maxPerOp);
|
|
596
|
-
for (const ch of restChunks) {
|
|
597
|
-
let callData;
|
|
598
|
-
if (useNativeToken) {
|
|
599
|
-
const { totalValue, data } = encodeNativeDisperseViaMulticall3({
|
|
600
|
-
to: ch.map(x => x.to),
|
|
601
|
-
values: ch.map(x => x.value),
|
|
584
|
+
// ✅ 保存所有动态生成的多跳钱包信息,用于后续导出
|
|
585
|
+
const allHopWalletsFlat = [];
|
|
586
|
+
for (const chainHops of allGeneratedHopWallets) {
|
|
587
|
+
for (const hop of chainHops) {
|
|
588
|
+
allHopWalletsFlat.push({
|
|
589
|
+
address: hop.sender,
|
|
590
|
+
privateKey: hop.wallet.privateKey,
|
|
602
591
|
});
|
|
603
|
-
callData = encodeExecute(MULTICALL3, totalValue, data);
|
|
604
592
|
}
|
|
605
|
-
else {
|
|
606
|
-
const targets = ch.map(() => quoteToken);
|
|
607
|
-
const values = ch.map(() => 0n);
|
|
608
|
-
const datas = ch.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.value]));
|
|
609
|
-
callData = encodeExecuteBatch(targets, values, datas);
|
|
610
|
-
}
|
|
611
|
-
const signedDisperse = await this.aaManager.buildUserOpWithState({
|
|
612
|
-
ownerWallet: lastHopOwner,
|
|
613
|
-
sender: lastHopSender,
|
|
614
|
-
nonce: nonceMap.next(lastHopSender),
|
|
615
|
-
initCode: lastHopAi.initCode || '0x', // ✅ 使用动态生成的 hop 钱包的 initCode
|
|
616
|
-
callData,
|
|
617
|
-
signOnly: true,
|
|
618
|
-
});
|
|
619
|
-
outOps.push(signedDisperse.userOp);
|
|
620
593
|
}
|
|
621
|
-
|
|
622
|
-
this._generatedHopWallets = generatedHopWallets.map((w, i) => ({
|
|
623
|
-
address: hopSenders[i],
|
|
624
|
-
privateKey: w.privateKey,
|
|
625
|
-
}));
|
|
594
|
+
this._generatedHopWallets = allHopWalletsFlat;
|
|
626
595
|
}
|
|
627
596
|
}
|
|
628
597
|
else if (!capitalMode && extractProfit && profitWei > 0n) {
|