four-flap-meme-sdk 1.5.37 → 1.5.39
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/bundle.d.ts +46 -1
- package/dist/xlayer/bundle.js +264 -55
- package/dist/xlayer/examples/bundle-buy-sell.js +0 -3
- package/dist/xlayer/index.d.ts +3 -1
- package/dist/xlayer/index.js +12 -1
- package/dist/xlayer/types.d.ts +50 -4
- package/package.json +1 -1
package/dist/xlayer/bundle.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - 买卖一体化:买入 -> 授权 -> 卖出 -> 归集
|
|
8
8
|
* - OKB 归集:将 sender 的 OKB 转回 owner
|
|
9
9
|
*/
|
|
10
|
-
import type { XLayerConfig, BundleBuyParams, BundleBuyResult, BundleSellParams, BundleSellResult, BundleBuySellParams, BundleBuySellResult, BundleCreateBuyParams, BundleCreateBuyResult, BundleCreateBuySignParams, BundleCreateBuySignResult } from './types.js';
|
|
10
|
+
import type { XLayerConfig, BundleBuyParams, BundleBuyResult, BundleSellParams, BundleSellResult, BundleBuySellParams, BundleBuySellResult, BundleCreateBuyParams, BundleCreateBuyResult, BundleCreateBuySignParams, BundleCreateBuySignResult, BundlePreApproveParams, BundlePreApproveResult, ApprovalStatusResult } from './types.js';
|
|
11
11
|
import { AAAccountManager } from './aa-account.js';
|
|
12
12
|
import { PortalQuery } from './portal-ops.js';
|
|
13
13
|
/**
|
|
@@ -95,6 +95,39 @@ export declare class BundleExecutor {
|
|
|
95
95
|
* 捆绑发射代币 + 购买(执行版本)
|
|
96
96
|
*/
|
|
97
97
|
bundleCreateBuy(params: BundleCreateBuyParams): Promise<BundleCreateBuyResult>;
|
|
98
|
+
/**
|
|
99
|
+
* 检查多个 AA 账户的授权状态
|
|
100
|
+
*
|
|
101
|
+
* 用于判断是否需要执行预授权,或查看当前授权进度
|
|
102
|
+
*
|
|
103
|
+
* @param tokenAddress - 代币地址
|
|
104
|
+
* @param privateKeys - Owner 私钥列表
|
|
105
|
+
* @param spender - 授权目标(默认 FLAP_PORTAL)
|
|
106
|
+
* @returns 每个地址的授权状态
|
|
107
|
+
*/
|
|
108
|
+
checkApprovalStatus(tokenAddress: string, privateKeys: string[], spender?: string): Promise<ApprovalStatusResult[]>;
|
|
109
|
+
/**
|
|
110
|
+
* 批量预授权
|
|
111
|
+
*
|
|
112
|
+
* 提前授权代币给 Portal/Router,避免交易时等待授权确认
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* // 预授权给 Portal(用于内盘交易)
|
|
117
|
+
* const result = await executor.bundlePreApprove({
|
|
118
|
+
* tokenAddress: '0x...',
|
|
119
|
+
* privateKeys: ['0x...', '0x...'],
|
|
120
|
+
* });
|
|
121
|
+
*
|
|
122
|
+
* // 预授权给外盘 Router
|
|
123
|
+
* const result = await executor.bundlePreApprove({
|
|
124
|
+
* tokenAddress: '0x...',
|
|
125
|
+
* privateKeys: ['0x...'],
|
|
126
|
+
* spender: POTATOSWAP_V2_ROUTER,
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
bundlePreApprove(params: BundlePreApproveParams): Promise<BundlePreApproveResult>;
|
|
98
131
|
}
|
|
99
132
|
/**
|
|
100
133
|
* 创建捆绑交易执行器
|
|
@@ -120,3 +153,15 @@ export declare function bundleCreateBuy(params: BundleCreateBuyParams): Promise<
|
|
|
120
153
|
* 快速捆绑发射 + 购买(仅签名)
|
|
121
154
|
*/
|
|
122
155
|
export declare function bundleCreateBuySign(params: BundleCreateBuySignParams): Promise<BundleCreateBuySignResult>;
|
|
156
|
+
/**
|
|
157
|
+
* 快速预授权
|
|
158
|
+
*
|
|
159
|
+
* 一键授权多个 AA 账户的代币,避免交易时等待授权确认
|
|
160
|
+
*/
|
|
161
|
+
export declare function bundlePreApprove(params: BundlePreApproveParams): Promise<BundlePreApproveResult>;
|
|
162
|
+
/**
|
|
163
|
+
* 检查授权状态
|
|
164
|
+
*
|
|
165
|
+
* 批量查询多个 AA 账户的代币授权状态
|
|
166
|
+
*/
|
|
167
|
+
export declare function checkApprovalStatus(tokenAddress: string, privateKeys: string[], spender?: string, config?: XLayerConfig): Promise<ApprovalStatusResult[]>;
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - OKB 归集:将 sender 的 OKB 转回 owner
|
|
9
9
|
*/
|
|
10
10
|
import { Wallet, Interface, Contract, ethers } from 'ethers';
|
|
11
|
-
import { FLAP_PORTAL, ENTRYPOINT_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, } from './constants.js';
|
|
11
|
+
import { FLAP_PORTAL, ENTRYPOINT_ABI, PORTAL_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, } from './constants.js';
|
|
12
12
|
import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
|
|
13
13
|
import { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, encodeCreateCallV2, encodeCreateCallV3, encodeCreateCallV4, PortalQuery, parseOkb, formatOkb, } from './portal-ops.js';
|
|
14
14
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
@@ -765,40 +765,9 @@ export class BundleExecutor {
|
|
|
765
765
|
for (const ai of accountInfos)
|
|
766
766
|
nonceMap.init(ai.sender, ai.nonce);
|
|
767
767
|
const tokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, senders);
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const didApprove = new Array(wallets.length).fill(false);
|
|
772
|
-
const touched = new Array(wallets.length).fill(false); // 任意阶段使用过 UserOp(用于决定 initCode=0x)
|
|
773
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
774
|
-
const sender = senders[i];
|
|
775
|
-
const balance = tokenBalances.get(sender) ?? 0n;
|
|
776
|
-
const allowance = allowances.get(sender) ?? 0n;
|
|
777
|
-
if (balance === 0n)
|
|
778
|
-
continue;
|
|
779
|
-
if (allowance >= balance)
|
|
780
|
-
continue;
|
|
781
|
-
const ai = accountInfos[i];
|
|
782
|
-
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
783
|
-
approveItems.push({ i, sender, nonce: nonceMap.next(sender), initCode });
|
|
784
|
-
didApprove[i] = true;
|
|
785
|
-
touched[i] = true;
|
|
786
|
-
}
|
|
787
|
-
const approveOps = [];
|
|
788
|
-
if (approveItems.length > 0) {
|
|
789
|
-
const signedApproves = await mapWithConcurrency(approveItems, 4, async (it) => {
|
|
790
|
-
const i = it.i;
|
|
791
|
-
const signed = await this.buildApproveUserOp(wallets[i], tokenAddress, this.portalAddress, it.sender, it.nonce, it.initCode, `owner${i + 1}`);
|
|
792
|
-
return { i, userOp: signed.userOp };
|
|
793
|
-
});
|
|
794
|
-
for (const r of signedApproves)
|
|
795
|
-
approveOps.push(r.userOp);
|
|
796
|
-
}
|
|
797
|
-
let approveResult;
|
|
798
|
-
if (approveOps.length > 0) {
|
|
799
|
-
approveResult = await this.runHandleOps('approveBundle', approveOps, bundlerSigner, beneficiary) ?? undefined;
|
|
800
|
-
}
|
|
801
|
-
// 2. 卖出
|
|
768
|
+
// ✅ 不再内联授权,假设已通过 bundlePreApprove 预先授权
|
|
769
|
+
const touched = new Array(wallets.length).fill(false);
|
|
770
|
+
// 直接卖出
|
|
802
771
|
const sellOps = [];
|
|
803
772
|
const sellItems = [];
|
|
804
773
|
for (let i = 0; i < wallets.length; i++) {
|
|
@@ -811,16 +780,15 @@ export class BundleExecutor {
|
|
|
811
780
|
continue;
|
|
812
781
|
const ai = accountInfos[i];
|
|
813
782
|
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
814
|
-
// approve 已单独打包并等待确认,因此这里不需要用“needApprove=真”去走保守 callGasLimit
|
|
815
|
-
const needApprove = false;
|
|
816
783
|
const nonce = nonceMap.next(sender);
|
|
817
|
-
sellItems.push({ i, sender, nonce, initCode
|
|
784
|
+
sellItems.push({ i, sender, nonce, initCode, sellAmount });
|
|
818
785
|
touched[i] = true;
|
|
819
786
|
}
|
|
820
787
|
if (sellItems.length > 0) {
|
|
821
788
|
const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
|
|
822
789
|
const i = it.i;
|
|
823
|
-
const signed = await this.buildSellUserOp(wallets[i], tokenAddress, it.sellAmount, it.sender, it.nonce, it.initCode,
|
|
790
|
+
const signed = await this.buildSellUserOp(wallets[i], tokenAddress, it.sellAmount, it.sender, it.nonce, it.initCode, false, // needApprove=false,假设已预先授权
|
|
791
|
+
`owner${i + 1}`);
|
|
824
792
|
return signed.userOp;
|
|
825
793
|
});
|
|
826
794
|
sellOps.push(...signedSells);
|
|
@@ -878,7 +846,6 @@ export class BundleExecutor {
|
|
|
878
846
|
}
|
|
879
847
|
}
|
|
880
848
|
return {
|
|
881
|
-
approveResult,
|
|
882
849
|
sellResult,
|
|
883
850
|
withdrawResult,
|
|
884
851
|
profit: withdrawToOwner
|
|
@@ -1058,8 +1025,7 @@ export class BundleExecutor {
|
|
|
1058
1025
|
}
|
|
1059
1026
|
// 获取买入后的代币余额
|
|
1060
1027
|
const tokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, senders);
|
|
1061
|
-
|
|
1062
|
-
// 2. 授权 + 卖出(可以合并到同一笔 handleOps)
|
|
1028
|
+
// 2. 直接卖出(✅ 不再内联授权,假设已通过 bundlePreApprove 预先授权)
|
|
1063
1029
|
const sellOps = [];
|
|
1064
1030
|
const sellPerWallet = await mapWithConcurrency(wallets, 4, async (w, i) => {
|
|
1065
1031
|
const sender = senders[i];
|
|
@@ -1068,20 +1034,14 @@ export class BundleExecutor {
|
|
|
1068
1034
|
console.log(`[owner${i + 1}] 没买到代币,跳过卖出`);
|
|
1069
1035
|
return [];
|
|
1070
1036
|
}
|
|
1071
|
-
const allowance = allowances.get(sender) ?? 0n;
|
|
1072
|
-
const needApprove = allowance < balance;
|
|
1073
1037
|
const initCode = '0x';
|
|
1074
1038
|
const out = [];
|
|
1075
|
-
if (needApprove) {
|
|
1076
|
-
const nonce = nonceMap.next(sender);
|
|
1077
|
-
const approveOp = await this.buildApproveUserOp(w, tokenAddress, this.portalAddress, sender, nonce, initCode, `owner${i + 1}`);
|
|
1078
|
-
out.push(approveOp.userOp);
|
|
1079
|
-
}
|
|
1080
1039
|
const sellAmount = (balance * BigInt(sellPercent)) / 100n;
|
|
1081
1040
|
if (sellAmount === 0n)
|
|
1082
1041
|
return out;
|
|
1083
1042
|
const sellNonce = nonceMap.next(sender);
|
|
1084
|
-
const sellOp = await this.buildSellUserOp(w, tokenAddress, sellAmount, sender, sellNonce, initCode,
|
|
1043
|
+
const sellOp = await this.buildSellUserOp(w, tokenAddress, sellAmount, sender, sellNonce, initCode, false, // needApprove=false,假设已预先授权
|
|
1044
|
+
`owner${i + 1}`);
|
|
1085
1045
|
out.push(sellOp.userOp);
|
|
1086
1046
|
return out;
|
|
1087
1047
|
});
|
|
@@ -1247,21 +1207,47 @@ export class BundleExecutor {
|
|
|
1247
1207
|
const signedDevOp = await this.aaManager.signUserOp(devCreateOp.userOp, devWallet);
|
|
1248
1208
|
ops.push(signedDevOp.userOp);
|
|
1249
1209
|
// 2b. Buyers 买入
|
|
1210
|
+
const profitSettings = resolveProfitSettings(effConfig);
|
|
1211
|
+
const inputToken = params.quoteToken && params.quoteToken !== ZERO_ADDRESS ? params.quoteToken : ZERO_ADDRESS;
|
|
1212
|
+
const useNativeToken = inputToken === ZERO_ADDRESS;
|
|
1213
|
+
// BundleCreateBuyParams 目前没有 quoteTokenDecimals,默认用 18
|
|
1214
|
+
const quoteDecimals = 18;
|
|
1250
1215
|
let totalBuyWei = 0n;
|
|
1216
|
+
let totalBuyProfitWei = 0n; // inputToken 本位利润
|
|
1251
1217
|
const buyerSenders = [];
|
|
1218
|
+
const profitWeis = [];
|
|
1219
|
+
const portalIface = new Interface(PORTAL_ABI);
|
|
1252
1220
|
for (let i = 0; i < buyerWallets.length; i++) {
|
|
1253
1221
|
const buyer = buyerWallets[i];
|
|
1254
|
-
const
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1222
|
+
const amountStr = buyAmounts[i] || '0';
|
|
1223
|
+
const originalWei = useNativeToken ? parseOkb(amountStr) : ethers.parseUnits(amountStr, quoteDecimals);
|
|
1224
|
+
let buyWei = originalWei;
|
|
1225
|
+
let profitWei = 0n;
|
|
1226
|
+
if (profitSettings.extractProfit) {
|
|
1227
|
+
const res = calculateProfit(originalWei, profitSettings.profitBps);
|
|
1228
|
+
buyWei = res.remaining;
|
|
1229
|
+
profitWei = res.profit;
|
|
1230
|
+
}
|
|
1231
|
+
totalBuyWei += buyWei;
|
|
1232
|
+
totalBuyProfitWei += profitWei;
|
|
1233
|
+
profitWeis.push(profitWei);
|
|
1257
1234
|
const buyerAccount = await this.aaManager.getAccountInfo(buyer.address);
|
|
1258
1235
|
buyerSenders.push(buyerAccount.sender);
|
|
1259
1236
|
nonceMap.init(buyerAccount.sender, buyerAccount.nonce);
|
|
1260
|
-
|
|
1237
|
+
// 买入:使用扣除利润后的金额
|
|
1238
|
+
const swapData = portalIface.encodeFunctionData('swapExactInput', [
|
|
1239
|
+
{
|
|
1240
|
+
inputToken,
|
|
1241
|
+
outputToken: tokenAddress,
|
|
1242
|
+
inputAmount: buyWei,
|
|
1243
|
+
minOutputAmount: 0n,
|
|
1244
|
+
permitData: '0x',
|
|
1245
|
+
},
|
|
1246
|
+
]);
|
|
1261
1247
|
const buyOp = await this.aaManager.buildUserOpWithFixedGas({
|
|
1262
1248
|
ownerWallet: buyer,
|
|
1263
1249
|
sender: buyerAccount.sender,
|
|
1264
|
-
callData: encodeExecute(this.portalAddress,
|
|
1250
|
+
callData: encodeExecute(this.portalAddress, useNativeToken ? buyWei : 0n, swapData),
|
|
1265
1251
|
nonce: nonceMap.next(buyerAccount.sender),
|
|
1266
1252
|
initCode: buyerAccount.deployed ? '0x' : this.aaManager.generateInitCode(buyer.address),
|
|
1267
1253
|
deployed: buyerAccount.deployed
|
|
@@ -1269,6 +1255,49 @@ export class BundleExecutor {
|
|
|
1269
1255
|
const signedBuyOp = await this.aaManager.signUserOp(buyOp.userOp, buyer);
|
|
1270
1256
|
ops.push(signedBuyOp.userOp);
|
|
1271
1257
|
}
|
|
1258
|
+
// ✅ 刮取利润:折算并转账(SignOnly 模式也需要包含此 UserOp)
|
|
1259
|
+
if (profitSettings.extractProfit && totalBuyProfitWei > 0n) {
|
|
1260
|
+
// 1. 获取 OKB 报价(SignOnly 模式也需要这个报价来决定最终转账金额)
|
|
1261
|
+
let nativeProfitAmount = totalBuyProfitWei;
|
|
1262
|
+
if (!useNativeToken) {
|
|
1263
|
+
try {
|
|
1264
|
+
const dq = new DexQuery(effConfig);
|
|
1265
|
+
nativeProfitAmount = await dq.quoteTokenToOkb(totalBuyProfitWei, inputToken);
|
|
1266
|
+
}
|
|
1267
|
+
catch {
|
|
1268
|
+
try {
|
|
1269
|
+
const pq = this.portalQuery;
|
|
1270
|
+
nativeProfitAmount = await pq.quoteExactInput(inputToken, ZERO_ADDRESS, totalBuyProfitWei);
|
|
1271
|
+
}
|
|
1272
|
+
catch {
|
|
1273
|
+
nativeProfitAmount = 0n;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (nativeProfitAmount > 0n) {
|
|
1278
|
+
console.log(`[利润提取-签名] 总利润: ${useNativeToken ? formatOkb(nativeProfitAmount) : `${formatOkb(totalBuyProfitWei)} (ERC20) -> ${formatOkb(nativeProfitAmount)} (OKB)`} -> ${profitSettings.profitRecipient}`);
|
|
1279
|
+
// 找到利润贡献最大的买家作为利润转账的 sender
|
|
1280
|
+
const maxProfitIndex = profitWeis.reduce((maxIdx, p, idx) => p > (profitWeis[maxIdx] ?? 0n) ? idx : maxIdx, 0);
|
|
1281
|
+
const pOwner = buyerWallets[maxProfitIndex];
|
|
1282
|
+
const pSender = buyerSenders[maxProfitIndex];
|
|
1283
|
+
// 利润转账:直接转 OKB(对齐 bundleBuy 语义)
|
|
1284
|
+
const profitCallData = encodeExecute(profitSettings.profitRecipient, nativeProfitAmount, '0x');
|
|
1285
|
+
const profitOp = await this.aaManager.buildUserOpWithFixedGas({
|
|
1286
|
+
ownerWallet: pOwner,
|
|
1287
|
+
sender: pSender,
|
|
1288
|
+
callData: profitCallData,
|
|
1289
|
+
nonce: nonceMap.next(pSender),
|
|
1290
|
+
initCode: '0x', // 之前买入阶段已处理 initCode
|
|
1291
|
+
deployed: true,
|
|
1292
|
+
fixedGas: {
|
|
1293
|
+
...(effConfig.fixedGas ?? {}),
|
|
1294
|
+
callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
|
|
1295
|
+
}
|
|
1296
|
+
});
|
|
1297
|
+
const signedProfitOp = await this.aaManager.signUserOp(profitOp.userOp, pOwner);
|
|
1298
|
+
ops.push(signedProfitOp.userOp);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1272
1301
|
// 3. 签名 handleOps 交易
|
|
1273
1302
|
const signedTx = await this.signHandleOpsTx({
|
|
1274
1303
|
ops,
|
|
@@ -1329,6 +1358,168 @@ export class BundleExecutor {
|
|
|
1329
1358
|
tokenAddress: signResult.tokenAddress
|
|
1330
1359
|
};
|
|
1331
1360
|
}
|
|
1361
|
+
// ============================================================================
|
|
1362
|
+
// 预授权 API(一键授权,提升交易速度)
|
|
1363
|
+
// ============================================================================
|
|
1364
|
+
/**
|
|
1365
|
+
* 检查多个 AA 账户的授权状态
|
|
1366
|
+
*
|
|
1367
|
+
* 用于判断是否需要执行预授权,或查看当前授权进度
|
|
1368
|
+
*
|
|
1369
|
+
* @param tokenAddress - 代币地址
|
|
1370
|
+
* @param privateKeys - Owner 私钥列表
|
|
1371
|
+
* @param spender - 授权目标(默认 FLAP_PORTAL)
|
|
1372
|
+
* @returns 每个地址的授权状态
|
|
1373
|
+
*/
|
|
1374
|
+
async checkApprovalStatus(tokenAddress, privateKeys, spender = FLAP_PORTAL) {
|
|
1375
|
+
const sharedProvider = this.aaManager.getProvider();
|
|
1376
|
+
const wallets = privateKeys.map((pk) => new Wallet(pk, sharedProvider));
|
|
1377
|
+
// 批量获取账户信息
|
|
1378
|
+
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
1379
|
+
const senders = accountInfos.map((ai) => ai.sender);
|
|
1380
|
+
// 批量获取代币余额和授权额度
|
|
1381
|
+
const [tokenBalances, allowances] = await Promise.all([
|
|
1382
|
+
this.portalQuery.getMultipleTokenBalances(tokenAddress, senders),
|
|
1383
|
+
this.portalQuery.getMultipleAllowances(tokenAddress, senders, spender),
|
|
1384
|
+
]);
|
|
1385
|
+
// 组装结果
|
|
1386
|
+
return wallets.map((wallet, i) => {
|
|
1387
|
+
const sender = senders[i];
|
|
1388
|
+
const tokenBalance = tokenBalances.get(sender) ?? 0n;
|
|
1389
|
+
const currentAllowance = allowances.get(sender) ?? 0n;
|
|
1390
|
+
// 如果授权额度 >= 代币余额,视为已授权(足够完成卖出)
|
|
1391
|
+
const isApproved = currentAllowance >= tokenBalance;
|
|
1392
|
+
return {
|
|
1393
|
+
owner: wallet.address,
|
|
1394
|
+
sender,
|
|
1395
|
+
isApproved,
|
|
1396
|
+
currentAllowance,
|
|
1397
|
+
tokenBalance,
|
|
1398
|
+
};
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* 批量预授权
|
|
1403
|
+
*
|
|
1404
|
+
* 提前授权代币给 Portal/Router,避免交易时等待授权确认
|
|
1405
|
+
*
|
|
1406
|
+
* @example
|
|
1407
|
+
* ```typescript
|
|
1408
|
+
* // 预授权给 Portal(用于内盘交易)
|
|
1409
|
+
* const result = await executor.bundlePreApprove({
|
|
1410
|
+
* tokenAddress: '0x...',
|
|
1411
|
+
* privateKeys: ['0x...', '0x...'],
|
|
1412
|
+
* });
|
|
1413
|
+
*
|
|
1414
|
+
* // 预授权给外盘 Router
|
|
1415
|
+
* const result = await executor.bundlePreApprove({
|
|
1416
|
+
* tokenAddress: '0x...',
|
|
1417
|
+
* privateKeys: ['0x...'],
|
|
1418
|
+
* spender: POTATOSWAP_V2_ROUTER,
|
|
1419
|
+
* });
|
|
1420
|
+
* ```
|
|
1421
|
+
*/
|
|
1422
|
+
async bundlePreApprove(params) {
|
|
1423
|
+
const { tokenAddress, privateKeys, spender = FLAP_PORTAL, signOnly = false, config } = params;
|
|
1424
|
+
const sharedProvider = this.aaManager.getProvider();
|
|
1425
|
+
const wallets = privateKeys.map((pk) => new Wallet(pk, sharedProvider));
|
|
1426
|
+
const bundlerSigner = wallets[0];
|
|
1427
|
+
const beneficiary = bundlerSigner.address;
|
|
1428
|
+
console.log('=== XLayer Bundle Pre-Approve ===');
|
|
1429
|
+
console.log('token:', tokenAddress);
|
|
1430
|
+
console.log('spender:', spender);
|
|
1431
|
+
console.log('owners:', wallets.length);
|
|
1432
|
+
console.log('signOnly:', signOnly);
|
|
1433
|
+
// 1. 批量获取账户信息
|
|
1434
|
+
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
1435
|
+
const senders = accountInfos.map((ai) => ai.sender);
|
|
1436
|
+
const nonceMap = new AANonceMap();
|
|
1437
|
+
for (const ai of accountInfos)
|
|
1438
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
1439
|
+
// 2. 批量获取代币余额和授权额度
|
|
1440
|
+
const [tokenBalances, allowances] = await Promise.all([
|
|
1441
|
+
this.portalQuery.getMultipleTokenBalances(tokenAddress, senders),
|
|
1442
|
+
this.portalQuery.getMultipleAllowances(tokenAddress, senders, spender),
|
|
1443
|
+
]);
|
|
1444
|
+
// 3. 过滤需要授权的账户
|
|
1445
|
+
const approveItems = [];
|
|
1446
|
+
const details = [];
|
|
1447
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
1448
|
+
const sender = senders[i];
|
|
1449
|
+
const tokenBalance = tokenBalances.get(sender) ?? 0n;
|
|
1450
|
+
const allowance = allowances.get(sender) ?? 0n;
|
|
1451
|
+
const isApproved = allowance >= tokenBalance || tokenBalance === 0n;
|
|
1452
|
+
details.push({
|
|
1453
|
+
owner: wallets[i].address,
|
|
1454
|
+
sender,
|
|
1455
|
+
isApproved,
|
|
1456
|
+
currentAllowance: allowance,
|
|
1457
|
+
tokenBalance,
|
|
1458
|
+
});
|
|
1459
|
+
// 如果有余额且授权不足,需要授权
|
|
1460
|
+
if (tokenBalance > 0n && allowance < tokenBalance) {
|
|
1461
|
+
const ai = accountInfos[i];
|
|
1462
|
+
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
1463
|
+
approveItems.push({ i, sender, nonce: nonceMap.next(sender), initCode });
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
const alreadyApprovedCount = details.filter((d) => d.isApproved).length;
|
|
1467
|
+
// 如果全部已授权,直接返回
|
|
1468
|
+
if (approveItems.length === 0) {
|
|
1469
|
+
console.log('所有地址已授权,无需操作');
|
|
1470
|
+
return {
|
|
1471
|
+
approvedCount: 0,
|
|
1472
|
+
alreadyApprovedCount,
|
|
1473
|
+
details,
|
|
1474
|
+
signedTransactions: signOnly ? [] : undefined,
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
console.log(`需要授权: ${approveItems.length}/${wallets.length}`);
|
|
1478
|
+
// 4. 构建授权 UserOps(signOnly 模式跳过 prefund)
|
|
1479
|
+
const approveOps = [];
|
|
1480
|
+
const signedApproves = await mapWithConcurrency(approveItems, 4, async (it) => {
|
|
1481
|
+
const i = it.i;
|
|
1482
|
+
const signed = await this.buildApproveUserOp(wallets[i], tokenAddress, spender, it.sender, it.nonce, it.initCode, `owner${i + 1}`, signOnly // signOnly=true 时跳过 ensureSenderBalance
|
|
1483
|
+
);
|
|
1484
|
+
return signed.userOp;
|
|
1485
|
+
});
|
|
1486
|
+
approveOps.push(...signedApproves);
|
|
1487
|
+
// ============ signOnly 模式:签名 handleOps 交易,返回 raw tx ============
|
|
1488
|
+
if (signOnly) {
|
|
1489
|
+
console.log('signOnly 模式:签名 handleOps 交易...');
|
|
1490
|
+
const signedTx = await this.signHandleOpsTx({
|
|
1491
|
+
ops: approveOps,
|
|
1492
|
+
payerWallet: bundlerSigner,
|
|
1493
|
+
beneficiary,
|
|
1494
|
+
});
|
|
1495
|
+
console.log('签名完成,返回 raw tx');
|
|
1496
|
+
return {
|
|
1497
|
+
approvedCount: approveItems.length,
|
|
1498
|
+
alreadyApprovedCount,
|
|
1499
|
+
details,
|
|
1500
|
+
signedTransactions: [signedTx],
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
// ============ 非 signOnly 模式:执行 handleOps ============
|
|
1504
|
+
const approveResult = await this.runHandleOps('preApproveBundle', approveOps, bundlerSigner, beneficiary);
|
|
1505
|
+
if (!approveResult) {
|
|
1506
|
+
throw new Error('预授权交易失败');
|
|
1507
|
+
}
|
|
1508
|
+
// 更新 details 中授权成功的状态
|
|
1509
|
+
for (const it of approveItems) {
|
|
1510
|
+
const detail = details.find((d) => d.sender === it.sender);
|
|
1511
|
+
if (detail) {
|
|
1512
|
+
detail.isApproved = true;
|
|
1513
|
+
detail.currentAllowance = ethers.MaxUint256;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
approvedCount: approveItems.length,
|
|
1518
|
+
alreadyApprovedCount,
|
|
1519
|
+
approveResult,
|
|
1520
|
+
details,
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1332
1523
|
}
|
|
1333
1524
|
// ============================================================================
|
|
1334
1525
|
// 便捷函数
|
|
@@ -1374,3 +1565,21 @@ export async function bundleCreateBuySign(params) {
|
|
|
1374
1565
|
const executor = createBundleExecutor(params.config);
|
|
1375
1566
|
return executor.bundleCreateBuySign(params);
|
|
1376
1567
|
}
|
|
1568
|
+
/**
|
|
1569
|
+
* 快速预授权
|
|
1570
|
+
*
|
|
1571
|
+
* 一键授权多个 AA 账户的代币,避免交易时等待授权确认
|
|
1572
|
+
*/
|
|
1573
|
+
export async function bundlePreApprove(params) {
|
|
1574
|
+
const executor = createBundleExecutor(params.config);
|
|
1575
|
+
return executor.bundlePreApprove(params);
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* 检查授权状态
|
|
1579
|
+
*
|
|
1580
|
+
* 批量查询多个 AA 账户的代币授权状态
|
|
1581
|
+
*/
|
|
1582
|
+
export async function checkApprovalStatus(tokenAddress, privateKeys, spender, config) {
|
|
1583
|
+
const executor = createBundleExecutor(config);
|
|
1584
|
+
return executor.checkApprovalStatus(tokenAddress, privateKeys, spender);
|
|
1585
|
+
}
|
|
@@ -80,9 +80,6 @@ async function example3_bundleSell() {
|
|
|
80
80
|
withdrawReserve: '0.00005', // 保留少量在 sender
|
|
81
81
|
config,
|
|
82
82
|
});
|
|
83
|
-
if (result.approveResult) {
|
|
84
|
-
console.log('授权交易哈希:', result.approveResult.txHash);
|
|
85
|
-
}
|
|
86
83
|
console.log('卖出交易哈希:', result.sellResult.txHash);
|
|
87
84
|
if (result.withdrawResult) {
|
|
88
85
|
console.log('归集交易哈希:', result.withdrawResult.txHash);
|
package/dist/xlayer/index.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ import type { BundleSwapSignParams, BundleSwapSignResult, BundleBatchSwapSignPar
|
|
|
62
62
|
export { BundlerClient, createBundlerClient, type BundlerConfig, type BundlerReceipt, } from './bundler.js';
|
|
63
63
|
export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, type GeneratedAAWallet, type GenerateAAWalletsParams, type GenerateAAWalletsResult, } from './aa-account.js';
|
|
64
64
|
export { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, createPortalQuery, applySlippage, formatOkb, parseOkb, formatTokenAmount, parseTokenAmount, type PortalQueryConfig, } from './portal-ops.js';
|
|
65
|
-
export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, bundleCreateBuy, bundleCreateBuySign, } from './bundle.js';
|
|
65
|
+
export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, bundleCreateBuy, bundleCreateBuySign, bundlePreApprove, checkApprovalStatus, } from './bundle.js';
|
|
66
66
|
export { DexBundleExecutor, createDexBundleExecutor, dexBundleBuySell, type DexBundleConfig, type DexBundleBuySellParams, } from './dex-bundle.js';
|
|
67
67
|
export { AAPortalSwapExecutor, } from './portal-bundle-swap.js';
|
|
68
68
|
export { AADexSwapExecutor, } from './dex-bundle-swap.js';
|
|
@@ -96,6 +96,8 @@ export declare const xlayer: {
|
|
|
96
96
|
bundleBatchSwapSign: (params: BundleBatchSwapSignParams & {
|
|
97
97
|
skipApprovalCheck?: boolean;
|
|
98
98
|
}) => Promise<BundleBatchSwapSignResult>;
|
|
99
|
+
bundlePreApprove: (params: import("./types.js").BundlePreApproveParams) => Promise<import("./types.js").BundlePreApproveResult>;
|
|
100
|
+
checkApprovalStatus: (tokenAddress: string, privateKeys: string[], spender?: string | undefined, config?: import("./types.js").XLayerConfig | undefined) => Promise<import("./types.js").ApprovalStatusResult[]>;
|
|
99
101
|
makeVolume: (params: import("./types.js").VolumeParams) => Promise<import("./types.js").VolumeResult>;
|
|
100
102
|
quoteOkbToToken: (okbAmount: bigint, tokenAddress: string, config?: import("./dex.js").DexConfig | undefined) => Promise<bigint>;
|
|
101
103
|
quoteTokenToOkb: (tokenAmount: bigint, tokenAddress: string, config?: import("./dex.js").DexConfig | undefined) => Promise<bigint>;
|
package/dist/xlayer/index.js
CHANGED
|
@@ -81,7 +81,9 @@ export { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, P
|
|
|
81
81
|
// ============================================================================
|
|
82
82
|
// 捆绑交易
|
|
83
83
|
// ============================================================================
|
|
84
|
-
export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, bundleCreateBuy, bundleCreateBuySign,
|
|
84
|
+
export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, bundleCreateBuy, bundleCreateBuySign,
|
|
85
|
+
// 预授权(一键授权)
|
|
86
|
+
bundlePreApprove, checkApprovalStatus, } from './bundle.js';
|
|
85
87
|
// ============================================================================
|
|
86
88
|
// 外盘捆绑交易(DEX Bundle)
|
|
87
89
|
// ============================================================================
|
|
@@ -166,6 +168,15 @@ export const xlayer = {
|
|
|
166
168
|
// 捆绑换手(AA):仅签名(raw signed tx)
|
|
167
169
|
bundleSwapSign: async (...args) => bundleSwapSign(...args),
|
|
168
170
|
bundleBatchSwapSign: async (...args) => bundleBatchSwapSign(...args),
|
|
171
|
+
// 预授权(一键授权)
|
|
172
|
+
bundlePreApprove: async (...args) => {
|
|
173
|
+
const { bundlePreApprove } = await import('./bundle.js');
|
|
174
|
+
return bundlePreApprove(...args);
|
|
175
|
+
},
|
|
176
|
+
checkApprovalStatus: async (...args) => {
|
|
177
|
+
const { checkApprovalStatus } = await import('./bundle.js');
|
|
178
|
+
return checkApprovalStatus(...args);
|
|
179
|
+
},
|
|
169
180
|
// 刷量
|
|
170
181
|
makeVolume: async (...args) => {
|
|
171
182
|
const { makeVolume } = await import('./volume.js');
|
package/dist/xlayer/types.d.ts
CHANGED
|
@@ -565,8 +565,6 @@ export interface BundleBuyResult {
|
|
|
565
565
|
* 捆绑卖出结果
|
|
566
566
|
*/
|
|
567
567
|
export interface BundleSellResult {
|
|
568
|
-
/** 授权交易结果(如果需要) */
|
|
569
|
-
approveResult?: HandleOpsResult;
|
|
570
568
|
/** 卖出交易结果 */
|
|
571
569
|
sellResult: HandleOpsResult;
|
|
572
570
|
/** 归集交易结果(如果 withdrawToOwner) */
|
|
@@ -588,8 +586,6 @@ export interface BundleSellResult {
|
|
|
588
586
|
export interface BundleBuySellResult {
|
|
589
587
|
/** 买入阶段结果 */
|
|
590
588
|
buyResult: HandleOpsResult;
|
|
591
|
-
/** 授权阶段结果(如果需要) */
|
|
592
|
-
approveResult?: HandleOpsResult;
|
|
593
589
|
/** 卖出阶段结果 */
|
|
594
590
|
sellResult: HandleOpsResult;
|
|
595
591
|
/** 归集阶段结果(如果 withdrawToOwner) */
|
|
@@ -699,6 +695,56 @@ export interface DexSwapResult {
|
|
|
699
695
|
outputAmount: bigint;
|
|
700
696
|
gasUsed: bigint;
|
|
701
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* 预授权参数
|
|
700
|
+
* 用于提前授权代币,避免在交易时等待授权确认
|
|
701
|
+
*/
|
|
702
|
+
export interface BundlePreApproveParams {
|
|
703
|
+
/** 代币地址 */
|
|
704
|
+
tokenAddress: string;
|
|
705
|
+
/** Owner 私钥列表 */
|
|
706
|
+
privateKeys: string[];
|
|
707
|
+
/** 授权目标(默认 FLAP_PORTAL,可传 Router 地址用于外盘交易) */
|
|
708
|
+
spender?: string;
|
|
709
|
+
/**
|
|
710
|
+
* 是否仅签名(不执行)
|
|
711
|
+
* - true: 返回签名后的 handleOps 交易,由前端发送到服务端广播
|
|
712
|
+
* - false(默认): 直接执行 handleOps
|
|
713
|
+
*/
|
|
714
|
+
signOnly?: boolean;
|
|
715
|
+
/** 配置覆盖 */
|
|
716
|
+
config?: Partial<XLayerConfig>;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* 预授权结果
|
|
720
|
+
*/
|
|
721
|
+
export interface BundlePreApproveResult {
|
|
722
|
+
/** 发送授权交易的数量 */
|
|
723
|
+
approvedCount: number;
|
|
724
|
+
/** 已经授权无需操作的数量 */
|
|
725
|
+
alreadyApprovedCount: number;
|
|
726
|
+
/** 授权交易结果(signOnly=false 时有值) */
|
|
727
|
+
approveResult?: HandleOpsResult;
|
|
728
|
+
/** 每个地址的授权状态详情 */
|
|
729
|
+
details: ApprovalStatusResult[];
|
|
730
|
+
/** signOnly=true 时:签名后的 handleOps 交易(raw signed tx) */
|
|
731
|
+
signedTransactions?: string[];
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* 授权状态检查结果
|
|
735
|
+
*/
|
|
736
|
+
export interface ApprovalStatusResult {
|
|
737
|
+
/** Owner EOA 地址 */
|
|
738
|
+
owner: string;
|
|
739
|
+
/** AA Sender 地址 */
|
|
740
|
+
sender: string;
|
|
741
|
+
/** 是否已授权(授权额度 >= 代币余额) */
|
|
742
|
+
isApproved: boolean;
|
|
743
|
+
/** 当前授权额度 */
|
|
744
|
+
currentAllowance: bigint;
|
|
745
|
+
/** 当前代币余额 */
|
|
746
|
+
tokenBalance: bigint;
|
|
747
|
+
}
|
|
702
748
|
/**
|
|
703
749
|
* 深度 Partial 类型
|
|
704
750
|
*/
|