four-flap-meme-sdk 1.5.51 → 1.5.53
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/account-fetcher.d.ts +27 -0
- package/dist/xlayer/account-fetcher.js +27 -0
- package/dist/xlayer/bundle.js +87 -59
- package/dist/xlayer/constants.d.ts +4 -4
- package/dist/xlayer/constants.js +4 -10
- package/dist/xlayer/dex-buy-first.d.ts +2 -6
- package/dist/xlayer/dex-buy-first.js +127 -178
- package/dist/xlayer/dex-helpers.d.ts +20 -0
- package/dist/xlayer/dex-helpers.js +67 -0
- package/dist/xlayer/dex.d.ts +4 -0
- package/dist/xlayer/dex.js +8 -0
- package/dist/xlayer/router-manager.d.ts +38 -0
- package/dist/xlayer/router-manager.js +69 -0
- package/dist/xlayer/types.d.ts +11 -1
- package/dist/xlayer/userOp-builder.d.ts +51 -0
- package/dist/xlayer/userOp-builder.js +97 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA 账户信息批量获取器
|
|
3
|
+
*/
|
|
4
|
+
import type { Wallet } from 'ethers';
|
|
5
|
+
import type { AAAccountManager } from './aa-account.js';
|
|
6
|
+
import type { AAAccount } from './types.js';
|
|
7
|
+
export interface AccountInfoBatch {
|
|
8
|
+
buyerAis: AAAccount[];
|
|
9
|
+
sellerAis: AAAccount[];
|
|
10
|
+
sellerSenders: string[];
|
|
11
|
+
sellerTokenBalances: Map<string, bigint>;
|
|
12
|
+
sellerAllowances: Map<string, bigint>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* ✅ 优化 1: 并行获取所有账户信息
|
|
16
|
+
*/
|
|
17
|
+
export declare function fetchAccountInfoBatch(params: {
|
|
18
|
+
buyers: Wallet[];
|
|
19
|
+
sellers: Wallet[];
|
|
20
|
+
tokenAddress: string;
|
|
21
|
+
effectiveRouter: string;
|
|
22
|
+
aaManager: AAAccountManager;
|
|
23
|
+
portalQuery: {
|
|
24
|
+
getMultipleTokenBalances: (token: string, addresses: string[]) => Promise<Map<string, bigint>>;
|
|
25
|
+
getMultipleAllowances: (token: string, owners: string[], spender: string) => Promise<Map<string, bigint>>;
|
|
26
|
+
};
|
|
27
|
+
}): Promise<AccountInfoBatch>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA 账户信息批量获取器
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* ✅ 优化 1: 并行获取所有账户信息
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchAccountInfoBatch(params) {
|
|
8
|
+
const { buyers, sellers, tokenAddress, effectiveRouter, aaManager, portalQuery } = params;
|
|
9
|
+
// ✅ 并行获取买方和卖方的账户信息
|
|
10
|
+
const [buyerAis, sellerAis] = await Promise.all([
|
|
11
|
+
aaManager.getMultipleAccountInfo(buyers.map((w) => w.address)),
|
|
12
|
+
aaManager.getMultipleAccountInfo(sellers.map((w) => w.address))
|
|
13
|
+
]);
|
|
14
|
+
const sellerSenders = sellerAis.map((ai) => ai.sender);
|
|
15
|
+
// ✅ 并行获取卖方的代币余额和授权额度
|
|
16
|
+
const [sellerTokenBalances, sellerAllowances] = await Promise.all([
|
|
17
|
+
portalQuery.getMultipleTokenBalances(tokenAddress, sellerSenders),
|
|
18
|
+
portalQuery.getMultipleAllowances(tokenAddress, sellerSenders, effectiveRouter)
|
|
19
|
+
]);
|
|
20
|
+
return {
|
|
21
|
+
buyerAis,
|
|
22
|
+
sellerAis,
|
|
23
|
+
sellerSenders,
|
|
24
|
+
sellerTokenBalances,
|
|
25
|
+
sellerAllowances
|
|
26
|
+
};
|
|
27
|
+
}
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -648,7 +648,7 @@ export class BundleExecutor {
|
|
|
648
648
|
// TODO: 如果需要,可以在这里检查并转账 ERC20 代币到 sender
|
|
649
649
|
}
|
|
650
650
|
});
|
|
651
|
-
// ✅ 构建买入 callData:如果使用 ERC20
|
|
651
|
+
// ✅ 构建买入 callData:如果使用 ERC20 代币,需要先 approve
|
|
652
652
|
const buyCallDatas = buyWeis.map((buyWei) => {
|
|
653
653
|
// 使用 portal 的 swapExactInput,支持 inputToken 参数
|
|
654
654
|
const portalIface = new Interface([
|
|
@@ -663,8 +663,16 @@ export class BundleExecutor {
|
|
|
663
663
|
permitData: '0x',
|
|
664
664
|
},
|
|
665
665
|
]);
|
|
666
|
-
//
|
|
667
|
-
|
|
666
|
+
// ✅ ERC20 代币需要先 approve,使用 executeBatch 将 approve + swap 合并为一个 UserOp
|
|
667
|
+
if (useNativeToken) {
|
|
668
|
+
// 原生代币:直接 swap
|
|
669
|
+
return encodeExecute(this.portalAddress, buyWei, swapData);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
// ERC20 代币:approve + swap 批量调用
|
|
673
|
+
const approveData = encodeApproveCall(this.portalAddress, buyWei);
|
|
674
|
+
return encodeExecuteBatch([inputToken, this.portalAddress], [0n, 0n], [approveData, swapData]);
|
|
675
|
+
}
|
|
668
676
|
});
|
|
669
677
|
const initCodes = accountInfos.map((ai, i) => ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address));
|
|
670
678
|
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
@@ -977,7 +985,7 @@ export class BundleExecutor {
|
|
|
977
985
|
// TODO: 如果需要,可以在这里检查并转账 ERC20 代币到 sender
|
|
978
986
|
}
|
|
979
987
|
});
|
|
980
|
-
// ✅ 构建买入 callData:如果使用 ERC20
|
|
988
|
+
// ✅ 构建买入 callData:如果使用 ERC20 代币,需要先 approve
|
|
981
989
|
const buyCallDatas = buyWeis.map((buyWei) => {
|
|
982
990
|
// 使用 portal 的 swapExactInput,支持 inputToken 参数
|
|
983
991
|
const portalIface = new Interface([
|
|
@@ -992,8 +1000,16 @@ export class BundleExecutor {
|
|
|
992
1000
|
permitData: '0x',
|
|
993
1001
|
},
|
|
994
1002
|
]);
|
|
995
|
-
//
|
|
996
|
-
|
|
1003
|
+
// ✅ ERC20 代币需要先 approve,使用 executeBatch 将 approve + swap 合并为一个 UserOp
|
|
1004
|
+
if (useNativeToken) {
|
|
1005
|
+
// 原生代币:直接 swap
|
|
1006
|
+
return encodeExecute(this.portalAddress, buyWei, swapData);
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
// ERC20 代币:approve + swap 批量调用
|
|
1010
|
+
const approveData = encodeApproveCall(this.portalAddress, buyWei);
|
|
1011
|
+
return encodeExecuteBatch([inputToken, this.portalAddress], [0n, 0n], [approveData, swapData]);
|
|
1012
|
+
}
|
|
997
1013
|
});
|
|
998
1014
|
const initCodes = accountInfos.map((ai, i) => (ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address)));
|
|
999
1015
|
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
@@ -1252,12 +1268,15 @@ export class BundleExecutor {
|
|
|
1252
1268
|
const inputToken = params.quoteToken && params.quoteToken !== ZERO_ADDRESS ? params.quoteToken : ZERO_ADDRESS;
|
|
1253
1269
|
const useNativeToken = inputToken === ZERO_ADDRESS;
|
|
1254
1270
|
const quoteDecimals = 18;
|
|
1255
|
-
let totalBuyWei = 0n;
|
|
1256
|
-
let totalBuyProfitWei = 0n;
|
|
1257
|
-
const buyerSenders = [];
|
|
1258
1271
|
const portalIface = new Interface(PORTAL_ABI);
|
|
1259
|
-
|
|
1260
|
-
|
|
1272
|
+
// ✅ 批量获取所有买家的账户信息(并行优化)
|
|
1273
|
+
const buyerAccountInfos = await this.aaManager.getMultipleAccountInfo(buyerWallets.map(w => w.address));
|
|
1274
|
+
const buyerSenders = buyerAccountInfos.map(ai => ai.sender);
|
|
1275
|
+
for (const ai of buyerAccountInfos) {
|
|
1276
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
1277
|
+
}
|
|
1278
|
+
// ✅ 预计算所有买家的金额和利润
|
|
1279
|
+
const buyerData = buyerWallets.map((buyer, i) => {
|
|
1261
1280
|
const amountStr = buyAmounts[i] || '0';
|
|
1262
1281
|
const originalWei = useNativeToken ? parseOkb(amountStr) : ethers.parseUnits(amountStr, quoteDecimals);
|
|
1263
1282
|
let buyWei = originalWei;
|
|
@@ -1267,11 +1286,13 @@ export class BundleExecutor {
|
|
|
1267
1286
|
buyWei = res.remaining;
|
|
1268
1287
|
profitWei = res.profit;
|
|
1269
1288
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1289
|
+
return { buyer, buyWei, profitWei, accountInfo: buyerAccountInfos[i] };
|
|
1290
|
+
});
|
|
1291
|
+
const totalBuyWei = buyerData.reduce((sum, d) => sum + d.buyWei, 0n);
|
|
1292
|
+
const totalBuyProfitWei = buyerData.reduce((sum, d) => sum + d.profitWei, 0n);
|
|
1293
|
+
// ✅ 并行构建和签名所有买家的 UserOps
|
|
1294
|
+
const signedBuyOps = await mapWithConcurrency(buyerData, 5, async (data) => {
|
|
1295
|
+
const { buyer, buyWei, accountInfo } = data;
|
|
1275
1296
|
const swapData = portalIface.encodeFunctionData('swapExactInput', [
|
|
1276
1297
|
{
|
|
1277
1298
|
inputToken,
|
|
@@ -1281,17 +1302,28 @@ export class BundleExecutor {
|
|
|
1281
1302
|
permitData: '0x',
|
|
1282
1303
|
},
|
|
1283
1304
|
]);
|
|
1305
|
+
// ✅ ERC20 代币需要先 approve,使用 executeBatch 将 approve + swap 合并为一个 UserOp
|
|
1306
|
+
let buyCallData;
|
|
1307
|
+
if (useNativeToken) {
|
|
1308
|
+
buyCallData = encodeExecute(this.portalAddress, buyWei, swapData);
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
const approveData = encodeApproveCall(this.portalAddress, buyWei);
|
|
1312
|
+
buyCallData = encodeExecuteBatch([inputToken, this.portalAddress], [0n, 0n], [approveData, swapData]);
|
|
1313
|
+
}
|
|
1284
1314
|
const buyOp = await this.aaManager.buildUserOpWithFixedGas({
|
|
1285
1315
|
ownerWallet: buyer,
|
|
1286
|
-
sender:
|
|
1287
|
-
callData:
|
|
1288
|
-
nonce: nonceMap.next(
|
|
1289
|
-
initCode:
|
|
1290
|
-
deployed:
|
|
1316
|
+
sender: accountInfo.sender,
|
|
1317
|
+
callData: buyCallData,
|
|
1318
|
+
nonce: nonceMap.next(accountInfo.sender),
|
|
1319
|
+
initCode: accountInfo.deployed ? '0x' : this.aaManager.generateInitCode(buyer.address),
|
|
1320
|
+
deployed: accountInfo.deployed
|
|
1291
1321
|
});
|
|
1292
1322
|
const signedBuyOp = await this.aaManager.signUserOp(buyOp.userOp, buyer);
|
|
1293
|
-
|
|
1294
|
-
}
|
|
1323
|
+
return signedBuyOp.userOp;
|
|
1324
|
+
});
|
|
1325
|
+
// 将所有签名的买入 UserOps 添加到操作列表
|
|
1326
|
+
ops.push(...signedBuyOps);
|
|
1295
1327
|
// 利润由尾笔交易处理(不再在买入阶段追加利润转账 UserOp)
|
|
1296
1328
|
// === 计算利润金额 ===
|
|
1297
1329
|
let nativeProfitAmount = 0n;
|
|
@@ -1425,17 +1457,20 @@ export class BundleExecutor {
|
|
|
1425
1457
|
});
|
|
1426
1458
|
const signedCreateOp = await aaManager.signUserOp(createOpRes.userOp, payerWallet);
|
|
1427
1459
|
ops1.push(signedCreateOp.userOp);
|
|
1428
|
-
// --- 3. 构建内盘买入 Ops
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
const info = curveBuyerInfos[i];
|
|
1460
|
+
// --- 3. 构建内盘买入 Ops(并行优化)---
|
|
1461
|
+
// ✅ 预计算所有买入金额
|
|
1462
|
+
const curveBuyData = curveBuyerWallets.map((wallet, i) => {
|
|
1432
1463
|
const buyWei = parseOkb(curveBuyAmounts[i]);
|
|
1433
|
-
|
|
1464
|
+
const isLast = i === curveBuyerWallets.length - 1;
|
|
1465
|
+
const gasLimit = isLast ? 8000000n : 800000n; // 最后一笔触发毕业,给大一点
|
|
1466
|
+
return { wallet, info: curveBuyerInfos[i], buyWei, gasLimit };
|
|
1467
|
+
});
|
|
1468
|
+
totalCurveBuyWei = curveBuyData.reduce((sum, d) => sum + d.buyWei, 0n);
|
|
1469
|
+
// ✅ 并行构建和签名所有内盘买入 UserOps
|
|
1470
|
+
const signedCurveBuyOps = await mapWithConcurrency(curveBuyData, 5, async (data) => {
|
|
1471
|
+
const { wallet, info, buyWei, gasLimit } = data;
|
|
1434
1472
|
const buyData = encodeBuyCall(tokenAddress, buyWei, 0n);
|
|
1435
1473
|
const buyCallData = encodeExecute(FLAP_PORTAL, buyWei, buyData);
|
|
1436
|
-
// 最后一笔买入通常触发毕业,GasLimit 给大一点 (8M)
|
|
1437
|
-
const isLast = i === curveBuyerWallets.length - 1;
|
|
1438
|
-
const gasLimit = isLast ? 8000000n : 800000n;
|
|
1439
1474
|
const buyOpRes = await aaManager.buildUserOpWithFixedGas({
|
|
1440
1475
|
ownerWallet: wallet,
|
|
1441
1476
|
sender: info.sender,
|
|
@@ -1446,8 +1481,9 @@ export class BundleExecutor {
|
|
|
1446
1481
|
fixedGas: { callGasLimit: gasLimit }
|
|
1447
1482
|
});
|
|
1448
1483
|
const signedBuyOp = await aaManager.signUserOp(buyOpRes.userOp, wallet);
|
|
1449
|
-
|
|
1450
|
-
}
|
|
1484
|
+
return signedBuyOp.userOp;
|
|
1485
|
+
});
|
|
1486
|
+
ops1.push(...signedCurveBuyOps);
|
|
1451
1487
|
// 签名第一个 handleOps
|
|
1452
1488
|
const startNonce = params.payerStartNonce ?? (await provider.getTransactionCount(payerWallet.address, 'pending'));
|
|
1453
1489
|
const signedMainTx = await this.signHandleOpsTx({
|
|
@@ -1467,40 +1503,32 @@ export class BundleExecutor {
|
|
|
1467
1503
|
});
|
|
1468
1504
|
const ops2 = [];
|
|
1469
1505
|
const deadline = Math.floor(Date.now() / 1000) + 1200; // 20 min
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
const info = dexBuyerInfos[i];
|
|
1506
|
+
// ✅ 预计算所有外盘买入金额
|
|
1507
|
+
const dexBuyData = dexBuyerWallets.map((wallet, i) => {
|
|
1473
1508
|
const buyWei = parseOkb(dexBuyAmounts[i]);
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1509
|
+
return { wallet, info: dexBuyerInfos[i], buyWei };
|
|
1510
|
+
});
|
|
1511
|
+
totalDexBuyWei = dexBuyData.reduce((sum, d) => sum + d.buyWei, 0n);
|
|
1512
|
+
// ✅ 并行构建和签名所有外盘买入 UserOps
|
|
1513
|
+
const signedDexBuyOps = await mapWithConcurrency(dexBuyData, 5, async (data) => {
|
|
1514
|
+
const { wallet, info, buyWei } = data;
|
|
1515
|
+
// AA 模式外盘:使用 executeBatch 将 Approve + Swap 合并为一个 UserOp
|
|
1477
1516
|
const approveData = encodeApproveCall(POTATOSWAP_V2_ROUTER);
|
|
1478
|
-
const approveCallData = encodeExecute(tokenAddress, 0n, approveData);
|
|
1479
|
-
const approveOpRes = await aaManager.buildUserOpWithFixedGas({
|
|
1480
|
-
ownerWallet: wallet,
|
|
1481
|
-
sender: info.sender,
|
|
1482
|
-
callData: approveCallData,
|
|
1483
|
-
nonce: nonceMap.next(info.sender),
|
|
1484
|
-
initCode: '0x', // HandleOps1 已部署
|
|
1485
|
-
deployed: true,
|
|
1486
|
-
fixedGas: { callGasLimit: 100000n }
|
|
1487
|
-
});
|
|
1488
|
-
const signedApprove = await aaManager.signUserOp(approveOpRes.userOp, wallet);
|
|
1489
|
-
ops2.push(signedApprove.userOp);
|
|
1490
|
-
// 2. Swap (PotatoSwap V2)
|
|
1491
1517
|
const swapData = encodeSwapExactETHForTokensSupportingFee(0n, [WOKB, tokenAddress], info.sender, deadline);
|
|
1492
|
-
const
|
|
1493
|
-
const
|
|
1518
|
+
const batchCallData = encodeExecuteBatch([tokenAddress, POTATOSWAP_V2_ROUTER], [0n, buyWei], [approveData, swapData]);
|
|
1519
|
+
const dexBuyOpRes = await aaManager.buildUserOpWithFixedGas({
|
|
1494
1520
|
ownerWallet: wallet,
|
|
1495
1521
|
sender: info.sender,
|
|
1496
|
-
callData:
|
|
1522
|
+
callData: batchCallData,
|
|
1497
1523
|
nonce: nonceMap.next(info.sender),
|
|
1524
|
+
initCode: '0x', // HandleOps1 已部署
|
|
1498
1525
|
deployed: true,
|
|
1499
|
-
fixedGas: { callGasLimit:
|
|
1526
|
+
fixedGas: { callGasLimit: 600000n }
|
|
1500
1527
|
});
|
|
1501
|
-
const
|
|
1502
|
-
|
|
1503
|
-
}
|
|
1528
|
+
const signedDexBuyOp = await aaManager.signUserOp(dexBuyOpRes.userOp, wallet);
|
|
1529
|
+
return signedDexBuyOp.userOp;
|
|
1530
|
+
});
|
|
1531
|
+
ops2.push(...signedDexBuyOps);
|
|
1504
1532
|
const signedDexTx = await this.signHandleOpsTx({
|
|
1505
1533
|
ops: ops2,
|
|
1506
1534
|
payerWallet: payerWallet,
|
|
@@ -24,6 +24,10 @@ export declare const FLAP_PORTAL = "0xb30D8c4216E1f21F27444D2FfAee3ad577808678";
|
|
|
24
24
|
export declare const FLAP_TOKEN_IMPL = "0x12Dc83157Bf1cfCB8Db5952b3ba5bb56Cc38f8C9";
|
|
25
25
|
/** WOKB 原生包装代币 */
|
|
26
26
|
export declare const WOKB = "0xe538905cf8410324e03a5a23c1c177a474d59b2b";
|
|
27
|
+
/** ✅ USDT 代币地址(XLayer,6 位精度) */
|
|
28
|
+
export declare const USDT = "0x1e4a5963abfd975d8c9021ce480b42188849d41d";
|
|
29
|
+
/** ✅ 零地址(表示原生代币) */
|
|
30
|
+
export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
27
31
|
/** Multicall3 合约 */
|
|
28
32
|
export declare const MULTICALL3 = "0xca11bde05977b3631167028862be2a173976ca11";
|
|
29
33
|
/** PotatoSwap V2 Router */
|
|
@@ -34,14 +38,10 @@ export declare const POTATOSWAP_SWAP_ROUTER02 = "0xB45D0149249488333E3F3f9F35980
|
|
|
34
38
|
export declare const POTATOSWAP_V3_ROUTER = "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41";
|
|
35
39
|
/** PotatoSwap V3 Factory */
|
|
36
40
|
export declare const POTATOSWAP_V3_FACTORY = "0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5";
|
|
37
|
-
/** USDT (6位精度) */
|
|
38
|
-
export declare const USDT = "0x1e4a5963abfd975d8c9021ce480b42188849d41d";
|
|
39
41
|
/** USDC (6位精度) */
|
|
40
42
|
export declare const USDC = "0x74b7f16337b8972027f6196a17a631ac6de26d22";
|
|
41
43
|
/** USD₮0 / USDT0 (6位精度) - Flap xLayer 支持的稳定币计价 */
|
|
42
44
|
export declare const USDT0 = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
|
|
43
|
-
/** 零地址(表示原生代币 OKB) */
|
|
44
|
-
export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
45
45
|
/** 默认 Gas Price 兜底值(0.1 gwei) */
|
|
46
46
|
export declare const DEFAULT_GAS_PRICE = 100000000n;
|
|
47
47
|
/** 默认 Salt(用于 AA 账户派生) */
|
package/dist/xlayer/constants.js
CHANGED
|
@@ -36,6 +36,10 @@ export const FLAP_TOKEN_IMPL = '0x12Dc83157Bf1cfCB8Db5952b3ba5bb56Cc38f8C9';
|
|
|
36
36
|
// ============================================================================
|
|
37
37
|
/** WOKB 原生包装代币 */
|
|
38
38
|
export const WOKB = '0xe538905cf8410324e03a5a23c1c177a474d59b2b';
|
|
39
|
+
/** ✅ USDT 代币地址(XLayer,6 位精度) */
|
|
40
|
+
export const USDT = '0x1e4a5963abfd975d8c9021ce480b42188849d41d';
|
|
41
|
+
/** ✅ 零地址(表示原生代币) */
|
|
42
|
+
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
39
43
|
/** Multicall3 合约 */
|
|
40
44
|
export const MULTICALL3 = '0xca11bde05977b3631167028862be2a173976ca11';
|
|
41
45
|
/** PotatoSwap V2 Router */
|
|
@@ -46,21 +50,11 @@ export const POTATOSWAP_SWAP_ROUTER02 = '0xB45D0149249488333E3F3f9F359807F4b810C
|
|
|
46
50
|
export const POTATOSWAP_V3_ROUTER = '0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41';
|
|
47
51
|
/** PotatoSwap V3 Factory */
|
|
48
52
|
export const POTATOSWAP_V3_FACTORY = '0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5';
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// 稳定币地址
|
|
51
|
-
// ============================================================================
|
|
52
|
-
/** USDT (6位精度) */
|
|
53
|
-
export const USDT = '0x1e4a5963abfd975d8c9021ce480b42188849d41d';
|
|
54
53
|
/** USDC (6位精度) */
|
|
55
54
|
export const USDC = '0x74b7f16337b8972027f6196a17a631ac6de26d22';
|
|
56
55
|
/** USD₮0 / USDT0 (6位精度) - Flap xLayer 支持的稳定币计价 */
|
|
57
56
|
export const USDT0 = '0x779ded0c9e1022225f8e0630b35a9b54be713736';
|
|
58
57
|
// ============================================================================
|
|
59
|
-
// 零地址
|
|
60
|
-
// ============================================================================
|
|
61
|
-
/** 零地址(表示原生代币 OKB) */
|
|
62
|
-
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
63
|
-
// ============================================================================
|
|
64
58
|
// Gas 配置
|
|
65
59
|
// ============================================================================
|
|
66
60
|
/** 默认 Gas Price 兜底值(0.1 gwei) */
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* XLayer AA Buy-First(外盘 / PotatoSwap V2)
|
|
3
|
-
*
|
|
4
|
-
* 对齐 BSC buy-first 思路:
|
|
5
|
-
* - 买方先买入(OKB -> token)
|
|
6
|
-
* - 卖方再卖出等值 token(token -> OKB,卖方需要预持仓)
|
|
7
|
-
* - ✅ 利润:在卖出后归集阶段刮取
|
|
3
|
+
* ✅ 优化版本:并行化 RPC、批量签名、合并 approve+sell
|
|
8
4
|
*/
|
|
9
5
|
import type { XLayerConfig } from './types.js';
|
|
10
6
|
import type { BuyFirstParams, BuyFirstResult } from './types.js';
|
|
@@ -14,11 +10,11 @@ export declare class AADexBuyFirstExecutor {
|
|
|
14
10
|
private dexQuery;
|
|
15
11
|
private config;
|
|
16
12
|
constructor(config?: XLayerConfig);
|
|
17
|
-
private getEffectiveRouter;
|
|
18
13
|
private runHandleOps;
|
|
19
14
|
private pickWallets;
|
|
20
15
|
private buildWithdrawUserOp;
|
|
21
16
|
private safeQuoteBuy;
|
|
17
|
+
private initNonceAndInitCode;
|
|
22
18
|
execute(params: BuyFirstParams): Promise<BuyFirstResult>;
|
|
23
19
|
}
|
|
24
20
|
export declare function createAADexBuyFirstExecutor(config?: XLayerConfig): AADexBuyFirstExecutor;
|
|
@@ -1,72 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* XLayer AA Buy-First(外盘 / PotatoSwap V2)
|
|
3
|
-
*
|
|
4
|
-
* 对齐 BSC buy-first 思路:
|
|
5
|
-
* - 买方先买入(OKB -> token)
|
|
6
|
-
* - 卖方再卖出等值 token(token -> OKB,卖方需要预持仓)
|
|
7
|
-
* - ✅ 利润:在卖出后归集阶段刮取
|
|
3
|
+
* ✅ 优化版本:并行化 RPC、批量签名、合并 approve+sell
|
|
8
4
|
*/
|
|
9
5
|
import { Contract, Interface, Wallet, ethers } from 'ethers';
|
|
10
6
|
import { AANonceMap } from './types.js';
|
|
11
|
-
import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, WOKB, } from './constants.js';
|
|
7
|
+
import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, WOKB, ZERO_ADDRESS, } from './constants.js';
|
|
12
8
|
import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
13
|
-
import { DexQuery
|
|
14
|
-
import { PortalQuery,
|
|
9
|
+
import { DexQuery } from './dex.js';
|
|
10
|
+
import { PortalQuery, parseOkb } from './portal-ops.js';
|
|
15
11
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
16
|
-
import {
|
|
17
|
-
|
|
12
|
+
import { getDexDeadline, resolveProfitSettings, calculateProfitWei, splitAmount, getEffectiveRouter } from './dex-helpers.js';
|
|
13
|
+
import { fetchAccountInfoBatch } from './account-fetcher.js';
|
|
14
|
+
import { buildBuyUserOps, buildSellUserOps } from './userOp-builder.js';
|
|
18
15
|
const DEFAULT_CALL_GAS_LIMIT_BUY = 450000n;
|
|
19
16
|
const DEFAULT_CALL_GAS_LIMIT_SELL = 450000n;
|
|
20
17
|
const DEFAULT_CALL_GAS_LIMIT_APPROVE = 200000n;
|
|
21
18
|
const DEFAULT_CALL_GAS_LIMIT_WITHDRAW = 120000n;
|
|
22
|
-
function getDexDeadline(minutes = 20) {
|
|
23
|
-
return Math.floor(Date.now() / 1000) + minutes * 60;
|
|
24
|
-
}
|
|
25
|
-
function resolveProfitSettings(config) {
|
|
26
|
-
const extractProfit = config?.extractProfit !== false; // 默认 true
|
|
27
|
-
const profitBpsRaw = config?.profitBps ?? PROFIT_CONFIG.RATE_BPS_SWAP;
|
|
28
|
-
const profitBps = Math.max(0, Math.min(10000, Number(profitBpsRaw)));
|
|
29
|
-
const profitRecipient = config?.profitRecipient ?? PROFIT_CONFIG.RECIPIENT;
|
|
30
|
-
return { extractProfit, profitBps, profitRecipient };
|
|
31
|
-
}
|
|
32
|
-
function calculateProfitWei(amountWei, profitBps) {
|
|
33
|
-
if (amountWei <= 0n)
|
|
34
|
-
return 0n;
|
|
35
|
-
if (!Number.isFinite(profitBps) || profitBps <= 0)
|
|
36
|
-
return 0n;
|
|
37
|
-
return (amountWei * BigInt(profitBps)) / 10000n;
|
|
38
|
-
}
|
|
39
|
-
function shuffle(arr) {
|
|
40
|
-
const a = arr.slice();
|
|
41
|
-
for (let i = a.length - 1; i > 0; i--) {
|
|
42
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
43
|
-
[a[i], a[j]] = [a[j], a[i]];
|
|
44
|
-
}
|
|
45
|
-
return a;
|
|
46
|
-
}
|
|
47
|
-
function splitAmount(totalAmount, count) {
|
|
48
|
-
if (count <= 0)
|
|
49
|
-
throw new Error('拆分份数必须大于 0');
|
|
50
|
-
if (count === 1)
|
|
51
|
-
return [totalAmount];
|
|
52
|
-
if (totalAmount <= 0n)
|
|
53
|
-
return new Array(count).fill(0n);
|
|
54
|
-
const weights = [];
|
|
55
|
-
for (let i = 0; i < count; i++) {
|
|
56
|
-
const w = BigInt(500000 + Math.floor(Math.random() * 1000000)); // [0.5,1.5)
|
|
57
|
-
weights.push(w);
|
|
58
|
-
}
|
|
59
|
-
const totalWeight = weights.reduce((a, b) => a + b, 0n);
|
|
60
|
-
const amounts = [];
|
|
61
|
-
let allocated = 0n;
|
|
62
|
-
for (let i = 0; i < count - 1; i++) {
|
|
63
|
-
const amt = (totalAmount * weights[i]) / totalWeight;
|
|
64
|
-
amounts.push(amt);
|
|
65
|
-
allocated += amt;
|
|
66
|
-
}
|
|
67
|
-
amounts.push(totalAmount - allocated);
|
|
68
|
-
return shuffle(amounts);
|
|
69
|
-
}
|
|
70
19
|
export class AADexBuyFirstExecutor {
|
|
71
20
|
constructor(config = {}) {
|
|
72
21
|
this.config = config;
|
|
@@ -74,14 +23,6 @@ export class AADexBuyFirstExecutor {
|
|
|
74
23
|
this.portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
|
|
75
24
|
this.dexQuery = new DexQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId });
|
|
76
25
|
}
|
|
77
|
-
getEffectiveRouter(params) {
|
|
78
|
-
if (params.routerAddress)
|
|
79
|
-
return params.routerAddress;
|
|
80
|
-
if (params.dexKey === 'DYORSWAP') {
|
|
81
|
-
return '0xfb001fbbace32f09cb6d3c449b935183de53ee96';
|
|
82
|
-
}
|
|
83
|
-
return POTATOSWAP_V2_ROUTER;
|
|
84
|
-
}
|
|
85
26
|
async runHandleOps(label, ops, bundlerSigner, beneficiary) {
|
|
86
27
|
const provider = this.aaManager.getProvider();
|
|
87
28
|
const entryPointAddress = this.aaManager.getEntryPointAddress();
|
|
@@ -153,9 +94,8 @@ export class AADexBuyFirstExecutor {
|
|
|
153
94
|
: 0n;
|
|
154
95
|
if (withdrawAmount <= 0n)
|
|
155
96
|
return null;
|
|
156
|
-
const { extractProfit, profitBps
|
|
97
|
+
const { extractProfit, profitBps } = resolveProfitSettings(effConfig);
|
|
157
98
|
const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
|
|
158
|
-
const toOwnerWei = withdrawAmount - profitWei;
|
|
159
99
|
const callData = encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
160
100
|
const { userOp } = gasPolicy === 'fixed'
|
|
161
101
|
? await this.aaManager.buildUserOpWithFixedGas({
|
|
@@ -189,12 +129,49 @@ export class AADexBuyFirstExecutor {
|
|
|
189
129
|
return 0n;
|
|
190
130
|
}
|
|
191
131
|
}
|
|
132
|
+
initNonceAndInitCode(params) {
|
|
133
|
+
const { buyers, sellers, buyerAis, sellerAis } = params;
|
|
134
|
+
const nonceMap = new AANonceMap();
|
|
135
|
+
// ✅ 使用 Set 去重,避免重复初始化
|
|
136
|
+
const uniqueSenders = new Set();
|
|
137
|
+
for (const ai of [...buyerAis, ...sellerAis]) {
|
|
138
|
+
const senderLower = ai.sender.toLowerCase();
|
|
139
|
+
if (!uniqueSenders.has(senderLower)) {
|
|
140
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
141
|
+
uniqueSenders.add(senderLower);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const initCodeBySenderLower = new Map();
|
|
145
|
+
const initIfNeeded = (sender, deployed, ownerAddress) => {
|
|
146
|
+
const k = sender.toLowerCase();
|
|
147
|
+
if (initCodeBySenderLower.has(k))
|
|
148
|
+
return;
|
|
149
|
+
initCodeBySenderLower.set(k, deployed ? '0x' : this.aaManager.generateInitCode(ownerAddress));
|
|
150
|
+
};
|
|
151
|
+
const consumeInitCode = (sender) => {
|
|
152
|
+
const k = sender.toLowerCase();
|
|
153
|
+
const cur = initCodeBySenderLower.get(k);
|
|
154
|
+
if (cur === undefined)
|
|
155
|
+
throw new Error(`initCode not initialized for sender: ${sender}`);
|
|
156
|
+
if (cur !== '0x')
|
|
157
|
+
initCodeBySenderLower.set(k, '0x');
|
|
158
|
+
return cur;
|
|
159
|
+
};
|
|
160
|
+
for (let i = 0; i < buyers.length; i++)
|
|
161
|
+
initIfNeeded(buyerAis[i].sender, buyerAis[i].deployed, buyers[i].address);
|
|
162
|
+
for (let i = 0; i < sellers.length; i++)
|
|
163
|
+
initIfNeeded(sellerAis[i].sender, sellerAis[i].deployed, sellers[i].address);
|
|
164
|
+
return { nonceMap, consumeInitCode };
|
|
165
|
+
}
|
|
192
166
|
async execute(params) {
|
|
193
|
-
const { tradeType = 'V2',
|
|
167
|
+
const { tradeType = 'V2', routerVersion = 'V2', // ✅ 新增
|
|
168
|
+
dexKey, routerAddress, deadlineMinutes = 20, tokenAddress, buyerPrivateKeys, sellerPrivateKeys, buyerFunds, buyCount, sellCount, slippageBps = 0, withdrawToOwner = true, withdrawReserve = DEFAULT_WITHDRAW_RESERVE, config, quoteToken, // ✅ 新增
|
|
169
|
+
quoteTokenDecimals = 18, // ✅ 新增
|
|
170
|
+
} = params;
|
|
194
171
|
if (tradeType !== 'V2') {
|
|
195
172
|
throw new Error('AADexBuyFirstExecutor 仅支持 tradeType=V2(外盘)');
|
|
196
173
|
}
|
|
197
|
-
const effectiveRouter =
|
|
174
|
+
const effectiveRouter = getEffectiveRouter({ dexKey, routerAddress, routerVersion }, POTATOSWAP_V2_ROUTER);
|
|
198
175
|
const deadline = getDexDeadline(deadlineMinutes);
|
|
199
176
|
const buyers = this.pickWallets(buyerPrivateKeys, buyCount);
|
|
200
177
|
const sellers = this.pickWallets(sellerPrivateKeys, sellCount);
|
|
@@ -204,49 +181,60 @@ export class AADexBuyFirstExecutor {
|
|
|
204
181
|
throw new Error('sellCount=0 或 sellerPrivateKeys 为空');
|
|
205
182
|
const effConfig = { ...(this.config ?? {}), ...(config ?? {}) };
|
|
206
183
|
const profitSettings = resolveProfitSettings(effConfig);
|
|
207
|
-
const
|
|
184
|
+
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
185
|
+
// ✅ 解析买入金额(根据 quoteToken 精度)
|
|
186
|
+
const totalBuyWei = useNativeToken
|
|
187
|
+
? parseOkb(String(buyerFunds))
|
|
188
|
+
: ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
|
|
208
189
|
if (totalBuyWei <= 0n)
|
|
209
190
|
throw new Error('buyerFunds 需要 > 0');
|
|
210
191
|
const buyAmountsWei = splitAmount(totalBuyWei, buyers.length);
|
|
211
|
-
|
|
192
|
+
// ✅ 并行报价(支持 OKB 或 USDT)
|
|
193
|
+
const quotedPerBuy = await mapWithConcurrency(buyAmountsWei, 6, async (amt) => {
|
|
194
|
+
if (useNativeToken) {
|
|
195
|
+
return await this.safeQuoteBuy(tokenAddress, amt);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// USDT → Token 报价
|
|
199
|
+
try {
|
|
200
|
+
return await this.dexQuery.quoteTokenToToken(quoteToken, tokenAddress, amt);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return 0n;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
212
207
|
const quotedTotalTokenOut = quotedPerBuy.reduce((a, b) => a + (b ?? 0n), 0n);
|
|
213
208
|
const estimatedTokenOutWei = (quotedTotalTokenOut * BigInt(10000 - Math.max(0, Math.min(10000, slippageBps)))) / 10000n;
|
|
214
209
|
if (estimatedTokenOutWei <= 0n) {
|
|
215
210
|
throw new Error('买入报价为 0,无法规划 sellAmount(可能无流动性)');
|
|
216
211
|
}
|
|
217
212
|
const sellAmountsWei = splitAmount(estimatedTokenOutWei, sellers.length);
|
|
218
|
-
//
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
for (let i = 0; i < buyers.length; i++)
|
|
244
|
-
initIfNeeded(buyerAis[i].sender, buyerAis[i].deployed, buyers[i].address);
|
|
245
|
-
for (let i = 0; i < sellers.length; i++)
|
|
246
|
-
initIfNeeded(sellerAis[i].sender, sellerAis[i].deployed, sellers[i].address);
|
|
247
|
-
// 卖方预持仓检查
|
|
248
|
-
const sellerSenders = sellerAis.map((ai) => ai.sender);
|
|
249
|
-
const sellerTokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, sellerSenders);
|
|
213
|
+
// ✅ 优化 1: 并行获取所有账户信息
|
|
214
|
+
const accountInfo = await fetchAccountInfoBatch({
|
|
215
|
+
buyers,
|
|
216
|
+
sellers,
|
|
217
|
+
tokenAddress,
|
|
218
|
+
effectiveRouter,
|
|
219
|
+
aaManager: this.aaManager,
|
|
220
|
+
portalQuery: this.portalQuery
|
|
221
|
+
});
|
|
222
|
+
const { buyerAis, sellerAis, sellerSenders, sellerTokenBalances, sellerAllowances } = accountInfo;
|
|
223
|
+
// ✅ 验证买方余额(OKB 或 USDT)
|
|
224
|
+
if (!useNativeToken) {
|
|
225
|
+
const buyerSenders = buyerAis.map(ai => ai.sender);
|
|
226
|
+
const buyerQuoteBalances = await this.portalQuery.getMultipleTokenBalances(quoteToken, buyerSenders);
|
|
227
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
228
|
+
const sender = buyerSenders[i];
|
|
229
|
+
const needBuy = buyAmountsWei[i] ?? 0n;
|
|
230
|
+
const bal = buyerQuoteBalances.get(sender) ?? 0n;
|
|
231
|
+
if (needBuy > bal) {
|
|
232
|
+
const tokenName = quoteTokenDecimals === 6 ? 'USDT' : 'ERC20';
|
|
233
|
+
throw new Error(`买方 ${tokenName} 余额不足: sender=${sender} need=${needBuy.toString()} bal=${bal.toString()}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// 验证卖方余额
|
|
250
238
|
for (let i = 0; i < sellers.length; i++) {
|
|
251
239
|
const sender = sellerSenders[i];
|
|
252
240
|
const needSell = sellAmountsWei[i] ?? 0n;
|
|
@@ -255,80 +243,41 @@ export class AADexBuyFirstExecutor {
|
|
|
255
243
|
throw new Error(`卖方预持仓不足: sender=${sender} need=${needSell.toString()} bal=${bal.toString()}`);
|
|
256
244
|
}
|
|
257
245
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (sellWei <= 0n)
|
|
294
|
-
continue;
|
|
295
|
-
const allowance = sellerAllowances.get(sender) ?? 0n;
|
|
296
|
-
if (allowance < sellWei) {
|
|
297
|
-
const approveCallData = encodeExecute(tokenAddress, 0n, encodeApproveCall(effectiveRouter, ethers.MaxUint256));
|
|
298
|
-
const { userOp: approveOp, prefundWei } = await this.aaManager.buildUserOpWithFixedGas({
|
|
299
|
-
ownerWallet: w,
|
|
300
|
-
sender,
|
|
301
|
-
nonce: nonceMap.next(sender),
|
|
302
|
-
initCode: consumeInitCode(sender),
|
|
303
|
-
callData: approveCallData,
|
|
304
|
-
deployed: ai.deployed,
|
|
305
|
-
fixedGas: {
|
|
306
|
-
...(effConfig.fixedGas ?? {}),
|
|
307
|
-
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_APPROVE,
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
await this.aaManager.ensureSenderBalance(w, sender, prefundWei + parseOkb('0.0001'), `buyFirstDex/seller${i + 1}/approve-fund`);
|
|
311
|
-
const signedApprove = await this.aaManager.signUserOp(approveOp, w);
|
|
312
|
-
ops.push(signedApprove.userOp);
|
|
313
|
-
}
|
|
314
|
-
const sellSwapData = encodeSwapExactTokensForETHSupportingFee(sellWei, 0n, [tokenAddress, WOKB], sender, deadline);
|
|
315
|
-
const sellCallData = encodeExecute(effectiveRouter, 0n, sellSwapData);
|
|
316
|
-
const { userOp: sellOp, prefundWei: sellPrefund } = await this.aaManager.buildUserOpWithFixedGas({
|
|
317
|
-
ownerWallet: w,
|
|
318
|
-
sender,
|
|
319
|
-
nonce: nonceMap.next(sender),
|
|
320
|
-
initCode: consumeInitCode(sender),
|
|
321
|
-
callData: sellCallData,
|
|
322
|
-
deployed: ai.deployed,
|
|
323
|
-
fixedGas: {
|
|
324
|
-
...(effConfig.fixedGas ?? {}),
|
|
325
|
-
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL,
|
|
326
|
-
},
|
|
327
|
-
});
|
|
328
|
-
await this.aaManager.ensureSenderBalance(w, sender, sellPrefund + parseOkb('0.0001'), `buyFirstDex/seller${i + 1}/sell-fund`);
|
|
329
|
-
const signedSell = await this.aaManager.signUserOp(sellOp, w);
|
|
330
|
-
ops.push(signedSell.userOp);
|
|
331
|
-
}
|
|
246
|
+
const { nonceMap, consumeInitCode } = this.initNonceAndInitCode({ buyers, sellers, buyerAis, sellerAis });
|
|
247
|
+
// ✅ 优化 2 + 3: 并行构建买入和卖出 UserOp(卖出合并 approve+sell)
|
|
248
|
+
const [buyOps, sellOps] = await Promise.all([
|
|
249
|
+
buildBuyUserOps({
|
|
250
|
+
buyers,
|
|
251
|
+
buyerAis,
|
|
252
|
+
buyAmountsWei,
|
|
253
|
+
tokenAddress,
|
|
254
|
+
effectiveRouter,
|
|
255
|
+
deadline,
|
|
256
|
+
wokbAddress: WOKB,
|
|
257
|
+
nonceMap,
|
|
258
|
+
consumeInitCode,
|
|
259
|
+
aaManager: this.aaManager,
|
|
260
|
+
config: effConfig,
|
|
261
|
+
defaultCallGasLimit: DEFAULT_CALL_GAS_LIMIT_BUY
|
|
262
|
+
}),
|
|
263
|
+
buildSellUserOps({
|
|
264
|
+
sellers,
|
|
265
|
+
sellerAis,
|
|
266
|
+
sellAmountsWei,
|
|
267
|
+
tokenAddress,
|
|
268
|
+
effectiveRouter,
|
|
269
|
+
deadline,
|
|
270
|
+
wokbAddress: WOKB,
|
|
271
|
+
sellerAllowances,
|
|
272
|
+
nonceMap,
|
|
273
|
+
consumeInitCode,
|
|
274
|
+
aaManager: this.aaManager,
|
|
275
|
+
config: effConfig,
|
|
276
|
+
defaultApproveGasLimit: DEFAULT_CALL_GAS_LIMIT_APPROVE,
|
|
277
|
+
defaultSellGasLimit: DEFAULT_CALL_GAS_LIMIT_SELL
|
|
278
|
+
})
|
|
279
|
+
]);
|
|
280
|
+
const ops = [...buyOps, ...sellOps];
|
|
332
281
|
if (ops.length === 0)
|
|
333
282
|
throw new Error('本轮没有生成任何 UserOp');
|
|
334
283
|
const payerWallet = buyers[0];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA DEX 工具函数
|
|
3
|
+
*/
|
|
4
|
+
export declare function getDexDeadline(minutes?: number): number;
|
|
5
|
+
export declare function calculateProfitWei(amountWei: bigint, profitBps: number): bigint;
|
|
6
|
+
export declare function resolveProfitSettings(config?: {
|
|
7
|
+
extractProfit?: boolean;
|
|
8
|
+
profitBps?: number;
|
|
9
|
+
profitRecipient?: string;
|
|
10
|
+
}): {
|
|
11
|
+
extractProfit: boolean;
|
|
12
|
+
profitBps: number;
|
|
13
|
+
profitRecipient: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function splitAmount(totalAmount: bigint, count: number): bigint[];
|
|
16
|
+
export declare function getEffectiveRouter(params: {
|
|
17
|
+
dexKey?: string;
|
|
18
|
+
routerAddress?: string;
|
|
19
|
+
routerVersion?: 'V2' | 'V3';
|
|
20
|
+
}, defaultRouter: string): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA DEX 工具函数
|
|
3
|
+
*/
|
|
4
|
+
import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
5
|
+
export function getDexDeadline(minutes = 20) {
|
|
6
|
+
return Math.floor(Date.now() / 1000) + minutes * 60;
|
|
7
|
+
}
|
|
8
|
+
export function calculateProfitWei(amountWei, profitBps) {
|
|
9
|
+
if (amountWei <= 0n)
|
|
10
|
+
return 0n;
|
|
11
|
+
if (!Number.isFinite(profitBps) || profitBps <= 0)
|
|
12
|
+
return 0n;
|
|
13
|
+
return (amountWei * BigInt(profitBps)) / 10000n;
|
|
14
|
+
}
|
|
15
|
+
export function resolveProfitSettings(config) {
|
|
16
|
+
const extractProfit = config?.extractProfit !== false;
|
|
17
|
+
const profitBpsRaw = config?.profitBps ?? PROFIT_CONFIG.RATE_BPS_SWAP;
|
|
18
|
+
const profitBps = Math.max(0, Math.min(10000, Number(profitBpsRaw)));
|
|
19
|
+
const profitRecipient = config?.profitRecipient ?? PROFIT_CONFIG.RECIPIENT;
|
|
20
|
+
return { extractProfit, profitBps, profitRecipient };
|
|
21
|
+
}
|
|
22
|
+
function shuffle(arr) {
|
|
23
|
+
const a = arr.slice();
|
|
24
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
25
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
26
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
27
|
+
}
|
|
28
|
+
return a;
|
|
29
|
+
}
|
|
30
|
+
export function splitAmount(totalAmount, count) {
|
|
31
|
+
if (count <= 0)
|
|
32
|
+
throw new Error('拆分份数必须大于 0');
|
|
33
|
+
if (count === 1)
|
|
34
|
+
return [totalAmount];
|
|
35
|
+
if (totalAmount <= 0n)
|
|
36
|
+
return new Array(count).fill(0n);
|
|
37
|
+
const weights = [];
|
|
38
|
+
for (let i = 0; i < count; i++) {
|
|
39
|
+
const w = BigInt(500000 + Math.floor(Math.random() * 1000000));
|
|
40
|
+
weights.push(w);
|
|
41
|
+
}
|
|
42
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0n);
|
|
43
|
+
const amounts = [];
|
|
44
|
+
let allocated = 0n;
|
|
45
|
+
for (let i = 0; i < count - 1; i++) {
|
|
46
|
+
const amt = (totalAmount * weights[i]) / totalWeight;
|
|
47
|
+
amounts.push(amt);
|
|
48
|
+
allocated += amt;
|
|
49
|
+
}
|
|
50
|
+
amounts.push(totalAmount - allocated);
|
|
51
|
+
return shuffle(amounts);
|
|
52
|
+
}
|
|
53
|
+
export function getEffectiveRouter(params, defaultRouter) {
|
|
54
|
+
// 优先使用显式指定的地址
|
|
55
|
+
if (params.routerAddress)
|
|
56
|
+
return params.routerAddress;
|
|
57
|
+
// 根据 dexKey 选择
|
|
58
|
+
if (params.dexKey === 'DYORSWAP') {
|
|
59
|
+
return '0xfb001fbbace32f09cb6d3c449b935183de53ee96';
|
|
60
|
+
}
|
|
61
|
+
// 根据 routerVersion 选择
|
|
62
|
+
if (params.routerVersion === 'V3') {
|
|
63
|
+
return '0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41'; // POTATOSWAP_V3_ROUTER
|
|
64
|
+
}
|
|
65
|
+
// 默认使用传入的 defaultRouter(通常是 V2)
|
|
66
|
+
return defaultRouter;
|
|
67
|
+
}
|
package/dist/xlayer/dex.d.ts
CHANGED
|
@@ -64,6 +64,10 @@ export declare class DexQuery {
|
|
|
64
64
|
* 获取 Token -> OKB 的预期输出
|
|
65
65
|
*/
|
|
66
66
|
quoteTokenToOkb(tokenAmount: bigint, tokenAddress: string): Promise<bigint>;
|
|
67
|
+
/**
|
|
68
|
+
* ✅ 获取 Token → Token 的预期输出(支持 USDT → Token)
|
|
69
|
+
*/
|
|
70
|
+
quoteTokenToToken(inputToken: string, outputToken: string, inputAmount: bigint): Promise<bigint>;
|
|
67
71
|
/**
|
|
68
72
|
* 获取 WOKB 地址
|
|
69
73
|
*/
|
package/dist/xlayer/dex.js
CHANGED
|
@@ -129,6 +129,14 @@ export class DexQuery {
|
|
|
129
129
|
const amounts = await this.getAmountsOut(tokenAmount, path);
|
|
130
130
|
return amounts[amounts.length - 1];
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* ✅ 获取 Token → Token 的预期输出(支持 USDT → Token)
|
|
134
|
+
*/
|
|
135
|
+
async quoteTokenToToken(inputToken, outputToken, inputAmount) {
|
|
136
|
+
const path = [inputToken, outputToken];
|
|
137
|
+
const amounts = await this.getAmountsOut(inputAmount, path);
|
|
138
|
+
return amounts[amounts.length - 1];
|
|
139
|
+
}
|
|
132
140
|
/**
|
|
133
141
|
* 获取 WOKB 地址
|
|
134
142
|
*/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer Router 管理器
|
|
3
|
+
* 统一管理 V2/V3 Router 地址和授权目标选择逻辑
|
|
4
|
+
*/
|
|
5
|
+
import type { RouterVersion, ApprovalTarget } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* ✅ 获取 Router 地址
|
|
8
|
+
* @param params.routerVersion - Router 版本(V2 或 V3)
|
|
9
|
+
* @param params.dexKey - DEX 标识(如 DYORSWAP)
|
|
10
|
+
* @param params.routerAddress - 显式指定的 Router 地址(优先级最高)
|
|
11
|
+
* @returns Router 地址
|
|
12
|
+
*/
|
|
13
|
+
export declare function getRouterAddress(params: {
|
|
14
|
+
routerVersion?: RouterVersion;
|
|
15
|
+
dexKey?: string;
|
|
16
|
+
routerAddress?: string;
|
|
17
|
+
}): string;
|
|
18
|
+
/**
|
|
19
|
+
* ✅ 获取授权目标地址
|
|
20
|
+
* @param target - 授权目标类型
|
|
21
|
+
* @returns 目标合约地址
|
|
22
|
+
*/
|
|
23
|
+
export declare function getApprovalTargetAddress(target: ApprovalTarget): string;
|
|
24
|
+
/**
|
|
25
|
+
* ✅ 获取所有授权目标地址
|
|
26
|
+
* @param targets - 授权目标类型数组
|
|
27
|
+
* @returns 目标信息数组
|
|
28
|
+
*/
|
|
29
|
+
export declare function getAllApprovalTargets(targets: ApprovalTarget[]): Array<{
|
|
30
|
+
target: ApprovalTarget;
|
|
31
|
+
address: string;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* ✅ 根据交易类型推断授权目标
|
|
35
|
+
* @param tradeType - 交易类型(FLAP/V2/V3)
|
|
36
|
+
* @returns 授权目标类型
|
|
37
|
+
*/
|
|
38
|
+
export declare function inferApprovalTarget(tradeType: string): ApprovalTarget;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer Router 管理器
|
|
3
|
+
* 统一管理 V2/V3 Router 地址和授权目标选择逻辑
|
|
4
|
+
*/
|
|
5
|
+
import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, FLAP_PORTAL, } from './constants.js';
|
|
6
|
+
/**
|
|
7
|
+
* ✅ 获取 Router 地址
|
|
8
|
+
* @param params.routerVersion - Router 版本(V2 或 V3)
|
|
9
|
+
* @param params.dexKey - DEX 标识(如 DYORSWAP)
|
|
10
|
+
* @param params.routerAddress - 显式指定的 Router 地址(优先级最高)
|
|
11
|
+
* @returns Router 地址
|
|
12
|
+
*/
|
|
13
|
+
export function getRouterAddress(params) {
|
|
14
|
+
// 1. 优先使用显式指定的地址
|
|
15
|
+
if (params.routerAddress)
|
|
16
|
+
return params.routerAddress;
|
|
17
|
+
// 2. 根据 dexKey 选择
|
|
18
|
+
if (params.dexKey === 'DYORSWAP') {
|
|
19
|
+
return '0xfb001fbbace32f09cb6d3c449b935183de53ee96';
|
|
20
|
+
}
|
|
21
|
+
// 3. 根据 routerVersion 选择
|
|
22
|
+
const version = params.routerVersion ?? 'V2';
|
|
23
|
+
if (version === 'V3') {
|
|
24
|
+
return POTATOSWAP_V3_ROUTER;
|
|
25
|
+
}
|
|
26
|
+
// 4. 默认 V2
|
|
27
|
+
return POTATOSWAP_V2_ROUTER;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* ✅ 获取授权目标地址
|
|
31
|
+
* @param target - 授权目标类型
|
|
32
|
+
* @returns 目标合约地址
|
|
33
|
+
*/
|
|
34
|
+
export function getApprovalTargetAddress(target) {
|
|
35
|
+
switch (target) {
|
|
36
|
+
case 'FLAP_PORTAL':
|
|
37
|
+
return FLAP_PORTAL;
|
|
38
|
+
case 'V2_ROUTER':
|
|
39
|
+
return POTATOSWAP_V2_ROUTER;
|
|
40
|
+
case 'V3_ROUTER':
|
|
41
|
+
return POTATOSWAP_V3_ROUTER;
|
|
42
|
+
default:
|
|
43
|
+
throw new Error(`Unknown approval target: ${target}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* ✅ 获取所有授权目标地址
|
|
48
|
+
* @param targets - 授权目标类型数组
|
|
49
|
+
* @returns 目标信息数组
|
|
50
|
+
*/
|
|
51
|
+
export function getAllApprovalTargets(targets) {
|
|
52
|
+
return targets.map(target => ({
|
|
53
|
+
target,
|
|
54
|
+
address: getApprovalTargetAddress(target)
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* ✅ 根据交易类型推断授权目标
|
|
59
|
+
* @param tradeType - 交易类型(FLAP/V2/V3)
|
|
60
|
+
* @returns 授权目标类型
|
|
61
|
+
*/
|
|
62
|
+
export function inferApprovalTarget(tradeType) {
|
|
63
|
+
const type = String(tradeType || '').toUpperCase();
|
|
64
|
+
if (type === 'V2')
|
|
65
|
+
return 'V2_ROUTER';
|
|
66
|
+
if (type === 'V3')
|
|
67
|
+
return 'V3_ROUTER';
|
|
68
|
+
return 'FLAP_PORTAL'; // 默认内盘
|
|
69
|
+
}
|
package/dist/xlayer/types.d.ts
CHANGED
|
@@ -661,8 +661,14 @@ export interface VolumeResult {
|
|
|
661
661
|
totalVolume: bigint;
|
|
662
662
|
}
|
|
663
663
|
export type BuyFirstTradeType = 'FLAP' | 'V2';
|
|
664
|
+
/** ✅ Router 版本类型 */
|
|
665
|
+
export type RouterVersion = 'V2' | 'V3';
|
|
666
|
+
/** ✅ 授权目标类型 */
|
|
667
|
+
export type ApprovalTarget = 'FLAP_PORTAL' | 'V2_ROUTER' | 'V3_ROUTER';
|
|
664
668
|
export interface BuyFirstParams {
|
|
665
669
|
tradeType?: BuyFirstTradeType;
|
|
670
|
+
/** ✅ 新增:Router 版本(V2 或 V3,默认 V2) */
|
|
671
|
+
routerVersion?: RouterVersion;
|
|
666
672
|
/** DEX 标识(仅 V2 时使用,用于选择 Router) */
|
|
667
673
|
dexKey?: string;
|
|
668
674
|
/** 显式指定 Router 地址(仅 V2 时使用) */
|
|
@@ -672,10 +678,14 @@ export interface BuyFirstParams {
|
|
|
672
678
|
tokenAddress: string;
|
|
673
679
|
buyerPrivateKeys: string[];
|
|
674
680
|
sellerPrivateKeys: string[];
|
|
675
|
-
/** 本轮总买入资金(OKB) */
|
|
681
|
+
/** 本轮总买入资金(OKB 或 quoteToken) */
|
|
676
682
|
buyerFunds: string;
|
|
677
683
|
buyCount?: number;
|
|
678
684
|
sellCount?: number;
|
|
685
|
+
/** ✅ 新增:买入使用的代币地址(零地址或不传表示使用 OKB,非零地址表示使用 ERC20 如 USDT) */
|
|
686
|
+
quoteToken?: string;
|
|
687
|
+
/** ✅ 新增:quoteToken 的精度(默认 18,USDT 在 XLayer 上是 6 位) */
|
|
688
|
+
quoteTokenDecimals?: number;
|
|
679
689
|
/** 报价滑点(仅用于估算 sellAmount),默认 0 */
|
|
680
690
|
slippageBps?: number;
|
|
681
691
|
withdrawToOwner?: boolean;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA UserOp 批量构建器
|
|
3
|
+
*/
|
|
4
|
+
import type { Wallet } from 'ethers';
|
|
5
|
+
import type { AAAccountManager } from './aa-account.js';
|
|
6
|
+
import type { AANonceMap } from './types.js';
|
|
7
|
+
import type { UserOperation, XLayerConfig } from './types.js';
|
|
8
|
+
export interface BuildBuyOpsParams {
|
|
9
|
+
buyers: Wallet[];
|
|
10
|
+
buyerAis: Array<{
|
|
11
|
+
sender: string;
|
|
12
|
+
deployed: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
buyAmountsWei: bigint[];
|
|
15
|
+
tokenAddress: string;
|
|
16
|
+
effectiveRouter: string;
|
|
17
|
+
deadline: number;
|
|
18
|
+
wokbAddress: string;
|
|
19
|
+
nonceMap: AANonceMap;
|
|
20
|
+
consumeInitCode: (sender: string) => string;
|
|
21
|
+
aaManager: AAAccountManager;
|
|
22
|
+
config: XLayerConfig;
|
|
23
|
+
defaultCallGasLimit: bigint;
|
|
24
|
+
}
|
|
25
|
+
export interface BuildSellOpsParams {
|
|
26
|
+
sellers: Wallet[];
|
|
27
|
+
sellerAis: Array<{
|
|
28
|
+
sender: string;
|
|
29
|
+
deployed: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
sellAmountsWei: bigint[];
|
|
32
|
+
tokenAddress: string;
|
|
33
|
+
effectiveRouter: string;
|
|
34
|
+
deadline: number;
|
|
35
|
+
wokbAddress: string;
|
|
36
|
+
sellerAllowances: Map<string, bigint>;
|
|
37
|
+
nonceMap: AANonceMap;
|
|
38
|
+
consumeInitCode: (sender: string) => string;
|
|
39
|
+
aaManager: AAAccountManager;
|
|
40
|
+
config: XLayerConfig;
|
|
41
|
+
defaultApproveGasLimit: bigint;
|
|
42
|
+
defaultSellGasLimit: bigint;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* ✅ 优化 1: 并行构建所有买入 UserOp
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildBuyUserOps(params: BuildBuyOpsParams): Promise<UserOperation[]>;
|
|
48
|
+
/**
|
|
49
|
+
* ✅ 优化 3: 合并 Approve 和 Sell
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildSellUserOps(params: BuildSellOpsParams): Promise<UserOperation[]>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XLayer AA UserOp 批量构建器
|
|
3
|
+
*/
|
|
4
|
+
import { ethers } from 'ethers';
|
|
5
|
+
import { encodeExecute, encodeExecuteBatch } from './aa-account.js';
|
|
6
|
+
import { encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee } from './dex.js';
|
|
7
|
+
import { encodeApproveCall, parseOkb } from './portal-ops.js';
|
|
8
|
+
/**
|
|
9
|
+
* ✅ 优化 1: 并行构建所有买入 UserOp
|
|
10
|
+
*/
|
|
11
|
+
export async function buildBuyUserOps(params) {
|
|
12
|
+
const { buyers, buyerAis, buyAmountsWei, tokenAddress, effectiveRouter, deadline, wokbAddress, nonceMap, consumeInitCode, aaManager, config, defaultCallGasLimit } = params;
|
|
13
|
+
const buyOpPromises = buyers.map(async (w, i) => {
|
|
14
|
+
const ai = buyerAis[i];
|
|
15
|
+
const sender = ai.sender;
|
|
16
|
+
const buyWei = buyAmountsWei[i] ?? 0n;
|
|
17
|
+
if (buyWei <= 0n)
|
|
18
|
+
return null;
|
|
19
|
+
const swapData = encodeSwapExactETHForTokensSupportingFee(0n, [wokbAddress, tokenAddress], sender, deadline);
|
|
20
|
+
const callData = encodeExecute(effectiveRouter, buyWei, swapData);
|
|
21
|
+
const { userOp, prefundWei } = await aaManager.buildUserOpWithFixedGas({
|
|
22
|
+
ownerWallet: w,
|
|
23
|
+
sender,
|
|
24
|
+
nonce: nonceMap.next(sender),
|
|
25
|
+
initCode: consumeInitCode(sender),
|
|
26
|
+
callData,
|
|
27
|
+
deployed: ai.deployed,
|
|
28
|
+
fixedGas: {
|
|
29
|
+
...(config.fixedGas ?? {}),
|
|
30
|
+
callGasLimit: config.fixedGas?.callGasLimit ?? defaultCallGasLimit,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
await aaManager.ensureSenderBalance(w, sender, buyWei + prefundWei + parseOkb('0.0001'), `buyFirstDex/buyer${i + 1}/fund`);
|
|
34
|
+
const signed = await aaManager.signUserOp(userOp, w);
|
|
35
|
+
return signed.userOp;
|
|
36
|
+
});
|
|
37
|
+
const ops = await Promise.all(buyOpPromises);
|
|
38
|
+
return ops.filter((op) => op !== null);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* ✅ 优化 3: 合并 Approve 和 Sell
|
|
42
|
+
*/
|
|
43
|
+
export async function buildSellUserOps(params) {
|
|
44
|
+
const { sellers, sellerAis, sellAmountsWei, tokenAddress, effectiveRouter, deadline, wokbAddress, sellerAllowances, nonceMap, consumeInitCode, aaManager, config, defaultApproveGasLimit, defaultSellGasLimit } = params;
|
|
45
|
+
const sellOpPromises = sellers.map(async (w, i) => {
|
|
46
|
+
const ai = sellerAis[i];
|
|
47
|
+
const sender = ai.sender;
|
|
48
|
+
const sellWei = sellAmountsWei[i] ?? 0n;
|
|
49
|
+
if (sellWei <= 0n)
|
|
50
|
+
return null;
|
|
51
|
+
const allowance = sellerAllowances.get(sender) ?? 0n;
|
|
52
|
+
const needApprove = allowance < sellWei;
|
|
53
|
+
if (needApprove) {
|
|
54
|
+
// ✅ 合并 approve + sell 到一个 UserOp
|
|
55
|
+
const approveData = encodeApproveCall(effectiveRouter, ethers.MaxUint256);
|
|
56
|
+
const sellSwapData = encodeSwapExactTokensForETHSupportingFee(sellWei, 0n, [tokenAddress, wokbAddress], sender, deadline);
|
|
57
|
+
const batchCallData = encodeExecuteBatch([tokenAddress, effectiveRouter], [0n, 0n], [approveData, sellSwapData]);
|
|
58
|
+
const { userOp, prefundWei } = await aaManager.buildUserOpWithFixedGas({
|
|
59
|
+
ownerWallet: w,
|
|
60
|
+
sender,
|
|
61
|
+
nonce: nonceMap.next(sender),
|
|
62
|
+
initCode: consumeInitCode(sender),
|
|
63
|
+
callData: batchCallData,
|
|
64
|
+
deployed: ai.deployed,
|
|
65
|
+
fixedGas: {
|
|
66
|
+
...(config.fixedGas ?? {}),
|
|
67
|
+
callGasLimit: config.fixedGas?.callGasLimit ?? (defaultApproveGasLimit + defaultSellGasLimit),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
await aaManager.ensureSenderBalance(w, sender, prefundWei + parseOkb('0.0001'), `buyFirstDex/seller${i + 1}/batch-fund`);
|
|
71
|
+
const signed = await aaManager.signUserOp(userOp, w);
|
|
72
|
+
return signed.userOp;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// 只需要 sell
|
|
76
|
+
const sellSwapData = encodeSwapExactTokensForETHSupportingFee(sellWei, 0n, [tokenAddress, wokbAddress], sender, deadline);
|
|
77
|
+
const sellCallData = encodeExecute(effectiveRouter, 0n, sellSwapData);
|
|
78
|
+
const { userOp, prefundWei } = await aaManager.buildUserOpWithFixedGas({
|
|
79
|
+
ownerWallet: w,
|
|
80
|
+
sender,
|
|
81
|
+
nonce: nonceMap.next(sender),
|
|
82
|
+
initCode: consumeInitCode(sender),
|
|
83
|
+
callData: sellCallData,
|
|
84
|
+
deployed: ai.deployed,
|
|
85
|
+
fixedGas: {
|
|
86
|
+
...(config.fixedGas ?? {}),
|
|
87
|
+
callGasLimit: config.fixedGas?.callGasLimit ?? defaultSellGasLimit,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
await aaManager.ensureSenderBalance(w, sender, prefundWei + parseOkb('0.0001'), `buyFirstDex/seller${i + 1}/sell-fund`);
|
|
91
|
+
const signed = await aaManager.signUserOp(userOp, w);
|
|
92
|
+
return signed.userOp;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const ops = await Promise.all(sellOpPromises);
|
|
96
|
+
return ops.filter((op) => op !== null);
|
|
97
|
+
}
|