four-flap-meme-sdk 1.6.30 → 1.6.32
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/aa-account.d.ts +20 -0
- package/dist/xlayer/aa-account.js +38 -1
- package/dist/xlayer/aa-transfer-profit.js +13 -9
- package/dist/xlayer/dex-bundle-swap.js +138 -166
- package/dist/xlayer/dex-bundle.js +6 -3
- package/dist/xlayer/dex-buy-first.js +6 -3
- package/dist/xlayer/index.d.ts +1 -1
- package/dist/xlayer/index.js +1 -1
- package/dist/xlayer/portal-bundle-swap.js +140 -168
- package/dist/xlayer/portal-buy-first.js +6 -3
- package/package.json +1 -1
|
@@ -218,8 +218,28 @@ export declare function computeUserOpHashV06(userOp: UserOperation, entryPoint:
|
|
|
218
218
|
export declare function encodeExecute(dest: string, value: bigint, data: string): string;
|
|
219
219
|
/**
|
|
220
220
|
* 编码 SimpleAccount.executeBatch 调用
|
|
221
|
+
*
|
|
222
|
+
* ✅ 兼容性修复:
|
|
223
|
+
* - 如果所有 values 都是 0,使用 executeBatch(address[],bytes[])(选择器 0x18dfb3c7)
|
|
224
|
+
* - 如果有非零 value,使用 executeBatch(address[],uint256[],bytes[])(选择器 0x47e1da2a)
|
|
225
|
+
*
|
|
226
|
+
* 注意:某些 AA 账户实现只支持无 values 的版本,此时需要用 Multicall3 或多次 execute
|
|
221
227
|
*/
|
|
222
228
|
export declare function encodeExecuteBatch(dests: string[], values: bigint[], datas: string[]): string;
|
|
229
|
+
/**
|
|
230
|
+
* ✅ 使用 Multicall3 编码批量转账(兼容所有 AA 实现)
|
|
231
|
+
*
|
|
232
|
+
* 当 AA 账户不支持 executeBatch(address[],uint256[],bytes[]) 时,
|
|
233
|
+
* 可以用 Multicall3 打包多个调用,再通过单个 execute 执行
|
|
234
|
+
*
|
|
235
|
+
* @param calls 调用列表 [{target, value, data}, ...]
|
|
236
|
+
* @returns execute(multicall3, totalValue, aggregate3ValueData)
|
|
237
|
+
*/
|
|
238
|
+
export declare function encodeExecuteViaMulticall3(calls: Array<{
|
|
239
|
+
target: string;
|
|
240
|
+
value: bigint;
|
|
241
|
+
data: string;
|
|
242
|
+
}>): string;
|
|
223
243
|
/**
|
|
224
244
|
* 创建 AA 账户管理器
|
|
225
245
|
*/
|
|
@@ -800,12 +800,49 @@ export function encodeExecute(dest, value, data) {
|
|
|
800
800
|
}
|
|
801
801
|
/**
|
|
802
802
|
* 编码 SimpleAccount.executeBatch 调用
|
|
803
|
+
*
|
|
804
|
+
* ✅ 兼容性修复:
|
|
805
|
+
* - 如果所有 values 都是 0,使用 executeBatch(address[],bytes[])(选择器 0x18dfb3c7)
|
|
806
|
+
* - 如果有非零 value,使用 executeBatch(address[],uint256[],bytes[])(选择器 0x47e1da2a)
|
|
807
|
+
*
|
|
808
|
+
* 注意:某些 AA 账户实现只支持无 values 的版本,此时需要用 Multicall3 或多次 execute
|
|
803
809
|
*/
|
|
804
810
|
export function encodeExecuteBatch(dests, values, datas) {
|
|
805
811
|
const iface = new Interface(SIMPLE_ACCOUNT_ABI);
|
|
806
|
-
//
|
|
812
|
+
// ✅ 检查是否所有 values 都是 0
|
|
813
|
+
const allZeroValues = values.every(v => v === 0n);
|
|
814
|
+
if (allZeroValues) {
|
|
815
|
+
// 使用无 values 的签名(更多 AA 实现支持)
|
|
816
|
+
return iface.encodeFunctionData('executeBatch(address[],bytes[])', [dests, datas]);
|
|
817
|
+
}
|
|
818
|
+
// 有非零 value,使用带 values 的签名
|
|
819
|
+
// ⚠️ 注意:某些 AA 账户实现不支持此签名,可能需要用 encodeExecuteBatchWithMulticall3
|
|
807
820
|
return iface.encodeFunctionData('executeBatch(address[],uint256[],bytes[])', [dests, values, datas]);
|
|
808
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* ✅ 使用 Multicall3 编码批量转账(兼容所有 AA 实现)
|
|
824
|
+
*
|
|
825
|
+
* 当 AA 账户不支持 executeBatch(address[],uint256[],bytes[]) 时,
|
|
826
|
+
* 可以用 Multicall3 打包多个调用,再通过单个 execute 执行
|
|
827
|
+
*
|
|
828
|
+
* @param calls 调用列表 [{target, value, data}, ...]
|
|
829
|
+
* @returns execute(multicall3, totalValue, aggregate3ValueData)
|
|
830
|
+
*/
|
|
831
|
+
export function encodeExecuteViaMulticall3(calls) {
|
|
832
|
+
const multicall3Iface = new Interface([
|
|
833
|
+
'function aggregate3Value((address target, bool allowFailure, uint256 value, bytes callData)[] calls) payable returns ((bool success, bytes returnData)[] returnData)'
|
|
834
|
+
]);
|
|
835
|
+
const multicallCalls = calls.map(c => ({
|
|
836
|
+
target: c.target,
|
|
837
|
+
allowFailure: false,
|
|
838
|
+
value: c.value,
|
|
839
|
+
callData: c.data,
|
|
840
|
+
}));
|
|
841
|
+
const totalValue = calls.reduce((sum, c) => sum + c.value, 0n);
|
|
842
|
+
const multicallData = multicall3Iface.encodeFunctionData('aggregate3Value', [multicallCalls]);
|
|
843
|
+
// 通过 execute 调用 Multicall3
|
|
844
|
+
return encodeExecute(MULTICALL3, totalValue, multicallData);
|
|
845
|
+
}
|
|
809
846
|
/**
|
|
810
847
|
* 创建 AA 账户管理器
|
|
811
848
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
2
|
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
3
|
-
import { createAAAccountManager, createWallet, encodeExecute, encodeExecuteBatch } from './aa-account.js';
|
|
3
|
+
import { createAAAccountManager, createWallet, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3 } from './aa-account.js';
|
|
4
4
|
function clampBps(bps) {
|
|
5
5
|
if (!Number.isFinite(bps))
|
|
6
6
|
return 0;
|
|
@@ -170,18 +170,22 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
|
|
|
170
170
|
const ops = [];
|
|
171
171
|
for (let i = 0; i < chunks.length; i++) {
|
|
172
172
|
const list = chunks[i];
|
|
173
|
-
// ✅
|
|
173
|
+
// ✅ 兼容性修复:使用 Multicall3 进行 native 批量转账
|
|
174
|
+
// 原因:某些 AA 账户实现不支持 executeBatch(address[],uint256[],bytes[])
|
|
175
|
+
// 使用 Multicall3 可以兼容所有 AA 实现
|
|
174
176
|
if (params.kind === 'native') {
|
|
175
|
-
// Native
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
// Native 转账:通过 Multicall3 批量调用
|
|
178
|
+
const calls = list.map((it) => ({
|
|
179
|
+
target: it.to,
|
|
180
|
+
value: it.amountWei,
|
|
181
|
+
data: '0x',
|
|
182
|
+
}));
|
|
183
|
+
const callData = encodeExecuteViaMulticall3(calls);
|
|
180
184
|
const nonce = nonce0 + BigInt(i);
|
|
181
185
|
const initCode = i === 0 ? String(initCode0) : '0x';
|
|
182
186
|
const deployed = i === 0 ? deployed0 : true;
|
|
183
|
-
const base =
|
|
184
|
-
const per =
|
|
187
|
+
const base = 200000n; // Multicall3 基础 gas
|
|
188
|
+
const per = 50000n; // 每个转账的 gas
|
|
185
189
|
const g = base + per * BigInt(list.length);
|
|
186
190
|
const callGasLimit = g > 4500000n ? 4500000n : g;
|
|
187
191
|
const built = await fnFixed.call(aaManager, {
|
|
@@ -3,8 +3,12 @@
|
|
|
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';
|
|
7
|
-
|
|
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)
|
|
11
|
+
import { AAAccountManager, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3 } 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';
|
|
10
14
|
import { BundleExecutor } from './bundle.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,152 @@ 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 同时刮取利润(使用 Multicall3 兼容所有 AA 实现)
|
|
700
|
+
callData0 = encodeExecuteViaMulticall3([
|
|
701
|
+
{ target: hop0.sender, value: chainTotalAmount, data: '0x' },
|
|
702
|
+
{ target: profitRecipient, value: profitWei, data: '0x' },
|
|
703
|
+
]);
|
|
704
|
+
profitHandled = true;
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
callData0 = encodeExecute(hop0.sender, chainTotalAmount, '0x');
|
|
708
|
+
}
|
|
708
709
|
}
|
|
709
710
|
else {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
711
|
+
if (!profitHandled && extractProfit && profitWei > 0n) {
|
|
712
|
+
const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
|
|
713
|
+
const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
|
|
714
|
+
callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
|
|
715
|
+
profitHandled = true;
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
|
|
719
|
+
callData0 = encodeExecute(quoteToken, 0n, transferData);
|
|
720
|
+
}
|
|
719
721
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
722
|
+
const signedSellerToHop0 = await this.aaManager.buildUserOpWithState({
|
|
723
|
+
ownerWallet: sellerOwner,
|
|
724
|
+
sender: sellerAi.sender,
|
|
725
|
+
nonce: nonceMap.next(sellerAi.sender),
|
|
726
|
+
initCode: consumeInitCode(sellerAi.sender),
|
|
727
|
+
callData: callData0,
|
|
728
|
+
signOnly: true,
|
|
729
|
+
});
|
|
730
|
+
outOps.push(signedSellerToHop0.userOp);
|
|
731
|
+
// 2) hop[j] → hop[j+1] (中间跳)
|
|
732
|
+
let remainingPrefund = totalHopPrefund;
|
|
733
|
+
for (let j = 0; j < chainHops.length - 1; j++) {
|
|
734
|
+
const currentHop = chainHops[j];
|
|
735
|
+
const nextHop = chainHops[j + 1];
|
|
736
|
+
// 当前 hop 扣除自己的 prefund 后转给下一个
|
|
737
|
+
remainingPrefund -= hopPrefunds[j];
|
|
738
|
+
const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
|
|
739
|
+
let callData;
|
|
740
|
+
if (useNativeToken) {
|
|
741
|
+
callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [nextHop.sender, buyAmount]);
|
|
745
|
+
callData = encodeExecute(quoteToken, 0n, transferData);
|
|
746
|
+
}
|
|
747
|
+
const signedHop = await this.aaManager.buildUserOpWithState({
|
|
748
|
+
ownerWallet: currentHop.wallet,
|
|
749
|
+
sender: currentHop.sender,
|
|
750
|
+
nonce: nonceMap.next(currentHop.sender),
|
|
751
|
+
initCode: currentHop.initCode,
|
|
752
|
+
callData,
|
|
753
|
+
signOnly: true,
|
|
754
|
+
});
|
|
755
|
+
outOps.push(signedHop.userOp);
|
|
723
756
|
}
|
|
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;
|
|
757
|
+
// 3) lastHop → buyer
|
|
758
|
+
const lastHop = chainHops[chainHops.length - 1];
|
|
759
|
+
const amountToBuyer = buyAmount + buyerPrefund;
|
|
760
|
+
let callDataLast;
|
|
747
761
|
if (useNativeToken) {
|
|
748
|
-
|
|
762
|
+
callDataLast = encodeExecute(buyerSender, amountToBuyer, '0x');
|
|
749
763
|
}
|
|
750
764
|
else {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
callData = encodeExecute(quoteToken, 0n, transferData);
|
|
765
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [buyerSender, buyAmount]);
|
|
766
|
+
callDataLast = encodeExecute(quoteToken, 0n, transferData);
|
|
754
767
|
}
|
|
755
|
-
const
|
|
756
|
-
ownerWallet:
|
|
757
|
-
sender,
|
|
758
|
-
nonce: nonceMap.next(sender),
|
|
759
|
-
initCode:
|
|
760
|
-
callData,
|
|
768
|
+
const signedLastHop = await this.aaManager.buildUserOpWithState({
|
|
769
|
+
ownerWallet: lastHop.wallet,
|
|
770
|
+
sender: lastHop.sender,
|
|
771
|
+
nonce: nonceMap.next(lastHop.sender),
|
|
772
|
+
initCode: lastHop.initCode,
|
|
773
|
+
callData: callDataLast,
|
|
761
774
|
signOnly: true,
|
|
762
775
|
});
|
|
763
|
-
outOps.push(
|
|
764
|
-
// ✅ 更新已使用的 prefund(当前 hop 的 prefund 已被扣除)
|
|
765
|
-
prefixPrefundUsed += hopPrefunds[j + 1] ?? 0n;
|
|
776
|
+
outOps.push(signedLastHop.userOp);
|
|
766
777
|
}
|
|
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),
|
|
778
|
+
// ✅ 保存所有动态生成的多跳钱包信息,用于后续导出
|
|
779
|
+
const allHopWalletsFlat = [];
|
|
780
|
+
for (const chainHops of allGeneratedHopWallets) {
|
|
781
|
+
for (const hop of chainHops) {
|
|
782
|
+
allHopWalletsFlat.push({
|
|
783
|
+
address: hop.sender,
|
|
784
|
+
privateKey: hop.wallet.privateKey,
|
|
793
785
|
});
|
|
794
|
-
callData = encodeExecute(MULTICALL3, totalValue, data);
|
|
795
786
|
}
|
|
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
787
|
}
|
|
812
|
-
|
|
813
|
-
this._generatedHopWallets = generatedHopWallets.map((w, i) => ({
|
|
814
|
-
address: hopSenders[i],
|
|
815
|
-
privateKey: w.privateKey,
|
|
816
|
-
}));
|
|
788
|
+
this._generatedHopWallets = allHopWalletsFlat;
|
|
817
789
|
}
|
|
818
790
|
}
|
|
819
791
|
else if (!capitalMode && extractProfit && profitWei > 0n) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Wallet, Contract, Interface, ethers } from 'ethers';
|
|
10
10
|
import { ENTRYPOINT_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, WOKB, } from './constants.js';
|
|
11
|
-
import { AAAccountManager, encodeExecute,
|
|
11
|
+
import { AAAccountManager, encodeExecute, encodeExecuteViaMulticall3 } from './aa-account.js';
|
|
12
12
|
import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, } from './dex.js';
|
|
13
13
|
import { PortalQuery, encodeApproveCall, parseOkb, formatOkb, lpFeeProfileToV3Fee } from './portal-ops.js';
|
|
14
14
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
@@ -153,9 +153,12 @@ export class DexBundleExecutor {
|
|
|
153
153
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
|
|
154
154
|
const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
|
|
155
155
|
const toOwnerWei = withdrawAmount - profitWei;
|
|
156
|
-
// ✅ AA 内部刮取利润:使用
|
|
156
|
+
// ✅ AA 内部刮取利润:使用 Multicall3 同时转账给利润接收者和 owner(兼容所有 AA 实现)
|
|
157
157
|
const callData = extractProfit && profitWei > 0n
|
|
158
|
-
?
|
|
158
|
+
? encodeExecuteViaMulticall3([
|
|
159
|
+
{ target: profitRecipient, value: profitWei, data: '0x' },
|
|
160
|
+
{ target: params.ownerWallet.address, value: toOwnerWei, data: '0x' },
|
|
161
|
+
])
|
|
159
162
|
: encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
160
163
|
const { userOp } = gasPolicy === 'fixed'
|
|
161
164
|
? await this.aaManager.buildUserOpWithFixedGas({
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Contract, Interface, Wallet, ethers } from 'ethers';
|
|
6
6
|
import { AANonceMap } from './types.js';
|
|
7
7
|
import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, WOKB, ZERO_ADDRESS, } from './constants.js';
|
|
8
|
-
import { AAAccountManager, encodeExecute,
|
|
8
|
+
import { AAAccountManager, encodeExecute, encodeExecuteViaMulticall3 } from './aa-account.js';
|
|
9
9
|
import { DexQuery } from './dex.js';
|
|
10
10
|
import { PortalQuery, parseOkb } from './portal-ops.js';
|
|
11
11
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
@@ -97,9 +97,12 @@ export class AADexBuyFirstExecutor {
|
|
|
97
97
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
|
|
98
98
|
const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
|
|
99
99
|
const toOwnerWei = withdrawAmount - profitWei;
|
|
100
|
-
// ✅ AA 内部刮取利润:使用
|
|
100
|
+
// ✅ AA 内部刮取利润:使用 Multicall3 同时转账给利润接收者和 owner(兼容所有 AA 实现)
|
|
101
101
|
const callData = extractProfit && profitWei > 0n
|
|
102
|
-
?
|
|
102
|
+
? encodeExecuteViaMulticall3([
|
|
103
|
+
{ target: profitRecipient, value: profitWei, data: '0x' },
|
|
104
|
+
{ target: params.ownerWallet.address, value: toOwnerWei, data: '0x' },
|
|
105
|
+
])
|
|
103
106
|
: encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
104
107
|
const { userOp } = gasPolicy === 'fixed'
|
|
105
108
|
? await this.aaManager.buildUserOpWithFixedGas({
|
package/dist/xlayer/index.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export * from './constants.js';
|
|
|
60
60
|
export * from './types.js';
|
|
61
61
|
import type { BundleSwapSignParams, BundleSwapSignResult, BundleBatchSwapSignParams, BundleBatchSwapSignResult } from './types.js';
|
|
62
62
|
export { BundlerClient, createBundlerClient, type BundlerConfig, type BundlerReceipt, } from './bundler.js';
|
|
63
|
-
export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, type GeneratedAAWallet, type GenerateAAWalletsParams, type GenerateAAWalletsResult, } from './aa-account.js';
|
|
63
|
+
export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3, generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, type GeneratedAAWallet, type GenerateAAWalletsParams, type GenerateAAWalletsResult, } from './aa-account.js';
|
|
64
64
|
export { encodeBuyCall, encodeBuyCallV3, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, createPortalQuery, applySlippage, formatOkb, parseOkb, formatTokenAmount, parseTokenAmount, type PortalQueryConfig, } from './portal-ops.js';
|
|
65
65
|
export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, bundleCreateBuySign, bundleCreateToDexSign, bundleGraduateBuy, bundlePreApprove, checkApprovalStatus, } from './bundle.js';
|
|
66
66
|
export { DexBundleExecutor, createDexBundleExecutor, dexBundleBuySell, type DexBundleConfig, type DexBundleBuySellParams, } from './dex-bundle.js';
|
package/dist/xlayer/index.js
CHANGED
|
@@ -71,7 +71,7 @@ export { BundlerClient, createBundlerClient, } from './bundler.js';
|
|
|
71
71
|
// ============================================================================
|
|
72
72
|
// AA 账户管理
|
|
73
73
|
// ============================================================================
|
|
74
|
-
export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch,
|
|
74
|
+
export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3,
|
|
75
75
|
// 批量生成钱包工具
|
|
76
76
|
generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, } from './aa-account.js';
|
|
77
77
|
// ============================================================================
|
|
@@ -3,8 +3,12 @@
|
|
|
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';
|
|
7
|
-
|
|
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)
|
|
11
|
+
import { AAAccountManager, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3 } 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';
|
|
10
14
|
import { PROFIT_CONFIG } from '../utils/constants.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,154 @@ 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 同时刮取利润(使用 Multicall3 兼容所有 AA 实现)
|
|
509
|
+
callData0 = encodeExecuteViaMulticall3([
|
|
510
|
+
{ target: hop0.sender, value: chainTotalAmount, data: '0x' },
|
|
511
|
+
{ target: profitRecipient, value: profitWei, data: '0x' },
|
|
512
|
+
]);
|
|
513
|
+
profitHandled = true;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
callData0 = encodeExecute(hop0.sender, chainTotalAmount, '0x');
|
|
517
|
+
}
|
|
518
518
|
}
|
|
519
519
|
else {
|
|
520
|
-
|
|
520
|
+
if (!profitHandled && extractProfit && profitWei > 0n) {
|
|
521
|
+
const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
|
|
522
|
+
const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
|
|
523
|
+
callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
|
|
524
|
+
profitHandled = true;
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
|
|
528
|
+
callData0 = encodeExecute(quoteToken, 0n, transferData);
|
|
529
|
+
}
|
|
521
530
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
callData0
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
531
|
+
const signedSellerToHop0 = await this.aaManager.buildUserOpWithState({
|
|
532
|
+
ownerWallet: sellerOwner,
|
|
533
|
+
sender: sellerAi.sender,
|
|
534
|
+
nonce: nonceMap.next(sellerAi.sender),
|
|
535
|
+
initCode: consumeInitCode(sellerAi.sender),
|
|
536
|
+
callData: callData0,
|
|
537
|
+
signOnly: true,
|
|
538
|
+
});
|
|
539
|
+
outOps.push(signedSellerToHop0.userOp);
|
|
540
|
+
// 2) hop[j] → hop[j+1] (中间跳)
|
|
541
|
+
let remainingPrefund = totalHopPrefund;
|
|
542
|
+
for (let j = 0; j < chainHops.length - 1; j++) {
|
|
543
|
+
const currentHop = chainHops[j];
|
|
544
|
+
const nextHop = chainHops[j + 1];
|
|
545
|
+
// 当前 hop 扣除自己的 prefund 后转给下一个
|
|
546
|
+
remainingPrefund -= hopPrefunds[j];
|
|
547
|
+
const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
|
|
548
|
+
let callData;
|
|
549
|
+
if (useNativeToken) {
|
|
550
|
+
callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [nextHop.sender, buyAmount]);
|
|
554
|
+
callData = encodeExecute(quoteToken, 0n, transferData);
|
|
555
|
+
}
|
|
556
|
+
const signedHop = await this.aaManager.buildUserOpWithState({
|
|
557
|
+
ownerWallet: currentHop.wallet,
|
|
558
|
+
sender: currentHop.sender,
|
|
559
|
+
nonce: nonceMap.next(currentHop.sender),
|
|
560
|
+
initCode: currentHop.initCode,
|
|
561
|
+
callData,
|
|
562
|
+
signOnly: true,
|
|
563
|
+
});
|
|
564
|
+
outOps.push(signedHop.userOp);
|
|
532
565
|
}
|
|
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;
|
|
566
|
+
// 3) lastHop → buyer
|
|
567
|
+
const lastHop = chainHops[chainHops.length - 1];
|
|
568
|
+
const amountToBuyer = buyAmount + buyerPrefund;
|
|
569
|
+
let callDataLast;
|
|
556
570
|
if (useNativeToken) {
|
|
557
|
-
|
|
571
|
+
callDataLast = encodeExecute(buyerSender, amountToBuyer, '0x');
|
|
558
572
|
}
|
|
559
573
|
else {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
callData = encodeExecute(quoteToken, 0n, transferData);
|
|
574
|
+
const transferData = erc20Iface.encodeFunctionData('transfer', [buyerSender, buyAmount]);
|
|
575
|
+
callDataLast = encodeExecute(quoteToken, 0n, transferData);
|
|
563
576
|
}
|
|
564
|
-
const
|
|
565
|
-
ownerWallet:
|
|
566
|
-
sender,
|
|
567
|
-
nonce: nonceMap.next(sender),
|
|
568
|
-
initCode:
|
|
569
|
-
callData,
|
|
577
|
+
const signedLastHop = await this.aaManager.buildUserOpWithState({
|
|
578
|
+
ownerWallet: lastHop.wallet,
|
|
579
|
+
sender: lastHop.sender,
|
|
580
|
+
nonce: nonceMap.next(lastHop.sender),
|
|
581
|
+
initCode: lastHop.initCode,
|
|
582
|
+
callData: callDataLast,
|
|
570
583
|
signOnly: true,
|
|
571
584
|
});
|
|
572
|
-
outOps.push(
|
|
573
|
-
// ✅ 更新已使用的 prefund(当前 hop 的 prefund 已被扣除)
|
|
574
|
-
prefixPrefundUsed += hopPrefunds[j + 1] ?? 0n;
|
|
585
|
+
outOps.push(signedLastHop.userOp);
|
|
575
586
|
}
|
|
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),
|
|
587
|
+
// ✅ 保存所有动态生成的多跳钱包信息,用于后续导出
|
|
588
|
+
const allHopWalletsFlat = [];
|
|
589
|
+
for (const chainHops of allGeneratedHopWallets) {
|
|
590
|
+
for (const hop of chainHops) {
|
|
591
|
+
allHopWalletsFlat.push({
|
|
592
|
+
address: hop.sender,
|
|
593
|
+
privateKey: hop.wallet.privateKey,
|
|
602
594
|
});
|
|
603
|
-
callData = encodeExecute(MULTICALL3, totalValue, data);
|
|
604
595
|
}
|
|
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
596
|
}
|
|
621
|
-
|
|
622
|
-
this._generatedHopWallets = generatedHopWallets.map((w, i) => ({
|
|
623
|
-
address: hopSenders[i],
|
|
624
|
-
privateKey: w.privateKey,
|
|
625
|
-
}));
|
|
597
|
+
this._generatedHopWallets = allHopWalletsFlat;
|
|
626
598
|
}
|
|
627
599
|
}
|
|
628
600
|
else if (!capitalMode && extractProfit && profitWei > 0n) {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { Contract, Interface, Wallet, ethers } from 'ethers';
|
|
14
14
|
import { AANonceMap } from './types.js';
|
|
15
15
|
import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, FLAP_PORTAL, } from './constants.js';
|
|
16
|
-
import { AAAccountManager, encodeExecute,
|
|
16
|
+
import { AAAccountManager, encodeExecute, encodeExecuteViaMulticall3 } from './aa-account.js';
|
|
17
17
|
import { PortalQuery, encodeApproveCall, encodeBuyCall, encodeSellCall, parseOkb } from './portal-ops.js';
|
|
18
18
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
19
19
|
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
@@ -168,9 +168,12 @@ export class AAPortalBuyFirstExecutor {
|
|
|
168
168
|
const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
|
|
169
169
|
const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
|
|
170
170
|
const toOwnerWei = withdrawAmount - profitWei;
|
|
171
|
-
// ✅ AA 内部刮取利润:使用
|
|
171
|
+
// ✅ AA 内部刮取利润:使用 Multicall3 同时转账给利润接收者和 owner(兼容所有 AA 实现)
|
|
172
172
|
const callData = extractProfit && profitWei > 0n
|
|
173
|
-
?
|
|
173
|
+
? encodeExecuteViaMulticall3([
|
|
174
|
+
{ target: profitRecipient, value: profitWei, data: '0x' },
|
|
175
|
+
{ target: params.ownerWallet.address, value: toOwnerWei, data: '0x' },
|
|
176
|
+
])
|
|
174
177
|
: encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
175
178
|
const { userOp } = gasPolicy === 'fixed'
|
|
176
179
|
? await this.aaManager.buildUserOpWithFixedGas({
|