four-flap-meme-sdk 1.5.27 → 1.5.29

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.
@@ -7,7 +7,7 @@
7
7
  * - 买卖一体化:买入 -> 授权 -> 卖出 -> 归集
8
8
  * - OKB 归集:将 sender 的 OKB 转回 owner
9
9
  */
10
- import { Wallet, Interface, Contract, Transaction, ethers } from 'ethers';
10
+ import { Wallet, Interface, Contract } from 'ethers';
11
11
  import { FLAP_PORTAL, ENTRYPOINT_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, } from './constants.js';
12
12
  import { AAAccountManager, encodeExecute } from './aa-account.js';
13
13
  import { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, parseOkb, formatOkb, } from './portal-ops.js';
@@ -204,237 +204,6 @@ export class BundleExecutor {
204
204
  return 18;
205
205
  }
206
206
  }
207
- /**
208
- * 构建买入 UserOp(已知 sender/nonce/initCode 的版本)
209
- */
210
- async buildBuyUserOpWithState(params) {
211
- const swapData = encodeBuyCall(params.tokenAddress, params.buyAmountWei, 0n);
212
- const callData = encodeExecute(this.portalAddress, params.buyAmountWei, swapData);
213
- if (!params.signOnly) {
214
- // 估算前确保 sender 有足够余额(用于模拟);paymaster 场景会自动跳过
215
- await this.aaManager.ensureSenderBalance(params.ownerWallet, params.sender, params.buyAmountWei + parseOkb('0.0003'), `${params.ownerName ?? 'owner'}/swap-buy-prefund-before-estimate`);
216
- }
217
- const gasPolicyRaw = this.config.gasPolicy ?? 'bundlerEstimate';
218
- const gasPolicy = params.signOnly && gasPolicyRaw === 'bundlerEstimate' ? 'fixed' : gasPolicyRaw;
219
- const { userOp, prefundWei } = gasPolicy === 'fixed'
220
- ? await this.aaManager.buildUserOpWithFixedGas({
221
- ownerWallet: params.ownerWallet,
222
- sender: params.sender,
223
- callData,
224
- nonce: params.nonce,
225
- initCode: params.initCode,
226
- deployed: params.initCode === '0x',
227
- fixedGas: {
228
- ...(this.config.fixedGas ?? {}),
229
- callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_BUY,
230
- },
231
- })
232
- : gasPolicy === 'localEstimate'
233
- ? await this.aaManager.buildUserOpWithLocalEstimate({
234
- ownerWallet: params.ownerWallet,
235
- sender: params.sender,
236
- callData,
237
- nonce: params.nonce,
238
- initCode: params.initCode,
239
- })
240
- : await this.aaManager.buildUserOpWithBundlerEstimate({
241
- ownerWallet: params.ownerWallet,
242
- sender: params.sender,
243
- callData,
244
- nonce: params.nonce,
245
- initCode: params.initCode,
246
- });
247
- if (!params.signOnly) {
248
- // 补足 prefund + 买入金额
249
- await this.aaManager.ensureSenderBalance(params.ownerWallet, params.sender, params.buyAmountWei + prefundWei + parseOkb('0.0002'), `${params.ownerName ?? 'owner'}/swap-buy-fund`);
250
- }
251
- const signed = await this.aaManager.signUserOp(userOp, params.ownerWallet);
252
- return { ...signed, prefundWei, ownerName: params.ownerName };
253
- }
254
- /**
255
- * 构建换手所需的 UserOps(approve? + sell + buy),并返回元数据
256
- */
257
- async buildSwapOps(params, opts) {
258
- const signOnly = opts?.signOnly ?? false;
259
- const { tokenAddress, sellerPrivateKey, buyerPrivateKey, sellAmount, sellPercent = 100, buyAmountOkb, slippageBps = 100, } = params;
260
- const provider = this.aaManager.getProvider();
261
- const sellerOwner = new Wallet(sellerPrivateKey, provider);
262
- const buyerOwner = new Wallet(buyerPrivateKey, provider);
263
- const [sellerAi, buyerAi] = await this.aaManager.getMultipleAccountInfo([sellerOwner.address, buyerOwner.address]);
264
- const sellerSender = sellerAi.sender;
265
- const buyerSender = buyerAi.sender;
266
- const nonceMap = new AANonceMap();
267
- nonceMap.init(sellerSender, sellerAi.nonce);
268
- nonceMap.init(buyerSender, buyerAi.nonce);
269
- // 计算卖出数量
270
- const decimals = await this.getErc20Decimals(tokenAddress);
271
- const sellerTokenBal = await this.portalQuery.getTokenBalance(tokenAddress, sellerSender);
272
- const sellAmountWei = (() => {
273
- if (sellAmount && String(sellAmount).trim().length > 0) {
274
- return ethers.parseUnits(String(sellAmount).trim(), decimals);
275
- }
276
- const pct = Math.max(0, Math.min(100, Math.floor(sellPercent)));
277
- return (sellerTokenBal * BigInt(pct)) / 100n;
278
- })();
279
- if (sellAmountWei <= 0n) {
280
- throw new Error('卖出数量为 0(请检查 sellAmount/sellPercent 或卖方余额)');
281
- }
282
- // 检查授权
283
- const allowance = await this.portalQuery.getAllowance(tokenAddress, sellerSender, this.portalAddress);
284
- const needApprove = allowance < sellAmountWei;
285
- // 计算买入 OKB 数量(默认用 previewSell(sellAmount) * (1 - slippage))
286
- const buyAmountWei = buyAmountOkb && String(buyAmountOkb).trim().length > 0
287
- ? parseOkb(String(buyAmountOkb).trim())
288
- : (() => {
289
- const bps = Math.max(0, Math.min(10000, Math.floor(slippageBps)));
290
- return 0n; // placeholder, filled below
291
- })();
292
- let finalBuyAmountWei = buyAmountWei;
293
- if (!(buyAmountOkb && String(buyAmountOkb).trim().length > 0)) {
294
- const quoted = await this.portalQuery.previewSell(tokenAddress, sellAmountWei);
295
- const bps = Math.max(0, Math.min(10000, Math.floor(slippageBps)));
296
- finalBuyAmountWei = (quoted * BigInt(10000 - bps)) / 10000n;
297
- }
298
- if (finalBuyAmountWei <= 0n) {
299
- throw new Error('买入 OKB 数量为 0(请检查 buyAmountOkb 或卖出预览输出)');
300
- }
301
- // initCode 处理:同一个 sender 的第一笔 op 才带 initCode
302
- const sellerInitCode0 = sellerAi.deployed ? '0x' : this.aaManager.generateInitCode(sellerOwner.address);
303
- const buyerInitCode0 = buyerAi.deployed ? '0x' : this.aaManager.generateInitCode(buyerOwner.address);
304
- let sellerInitCodeForNext = sellerInitCode0;
305
- let buyerInitCodeForNext = buyerInitCode0;
306
- const outOps = [];
307
- // 1) approve(可选)
308
- if (needApprove) {
309
- const approveNonce = nonceMap.next(sellerSender);
310
- const signedApprove = await this.buildApproveUserOp(sellerOwner, tokenAddress, this.portalAddress, sellerSender, approveNonce, sellerInitCodeForNext, 'seller', signOnly);
311
- outOps.push(signedApprove.userOp);
312
- sellerInitCodeForNext = '0x';
313
- }
314
- // 2) sell
315
- const sellNonce = nonceMap.next(sellerSender);
316
- const signedSell = await this.buildSellUserOp(sellerOwner, tokenAddress, sellAmountWei, sellerSender, sellNonce, sellerInitCodeForNext, needApprove, 'seller', signOnly);
317
- outOps.push(signedSell.userOp);
318
- sellerInitCodeForNext = '0x';
319
- // 3) buy(买方)
320
- const buyNonce = nonceMap.next(buyerSender);
321
- const signedBuy = await this.buildBuyUserOpWithState({
322
- ownerWallet: buyerOwner,
323
- sender: buyerSender,
324
- nonce: buyNonce,
325
- initCode: buyerInitCodeForNext,
326
- tokenAddress,
327
- buyAmountWei: finalBuyAmountWei,
328
- ownerName: 'buyer',
329
- signOnly,
330
- });
331
- outOps.push(signedBuy.userOp);
332
- buyerInitCodeForNext = '0x';
333
- return {
334
- ops: outOps,
335
- metadata: {
336
- sellerOwner: sellerOwner.address,
337
- sellerSender,
338
- buyerOwner: buyerOwner.address,
339
- buyerSender,
340
- sellAmountWei: sellAmountWei.toString(),
341
- buyAmountWei: finalBuyAmountWei.toString(),
342
- hasApprove: needApprove,
343
- },
344
- };
345
- }
346
- async buildBatchSwapOps(params, opts) {
347
- const signOnly = opts?.signOnly ?? false;
348
- const { tokenAddress, sellerPrivateKey, buyerPrivateKeys, buyAmountsOkb, sellAmount, sellPercent = 100, } = params;
349
- if (buyerPrivateKeys.length !== buyAmountsOkb.length) {
350
- throw new Error('buyerPrivateKeys 和 buyAmountsOkb 长度必须一致');
351
- }
352
- if (buyerPrivateKeys.length === 0) {
353
- throw new Error('至少需要 1 个 buyerPrivateKey');
354
- }
355
- const provider = this.aaManager.getProvider();
356
- const sellerOwner = new Wallet(sellerPrivateKey, provider);
357
- const buyerOwners = buyerPrivateKeys.map((pk) => new Wallet(pk, provider));
358
- const accountInfos = await this.aaManager.getMultipleAccountInfo([
359
- sellerOwner.address,
360
- ...buyerOwners.map((w) => w.address),
361
- ]);
362
- const sellerAi = accountInfos[0];
363
- const buyerAis = accountInfos.slice(1);
364
- const sellerSender = sellerAi.sender;
365
- const buyerSenders = buyerAis.map((ai) => ai.sender);
366
- const nonceMap = new AANonceMap();
367
- nonceMap.init(sellerSender, sellerAi.nonce);
368
- for (const ai of buyerAis)
369
- nonceMap.init(ai.sender, ai.nonce);
370
- // 计算卖出数量
371
- const decimals = await this.getErc20Decimals(tokenAddress);
372
- const sellerTokenBal = await this.portalQuery.getTokenBalance(tokenAddress, sellerSender);
373
- const sellAmountWei = (() => {
374
- if (sellAmount && String(sellAmount).trim().length > 0) {
375
- return ethers.parseUnits(String(sellAmount).trim(), decimals);
376
- }
377
- const pct = Math.max(0, Math.min(100, Math.floor(sellPercent)));
378
- return (sellerTokenBal * BigInt(pct)) / 100n;
379
- })();
380
- if (sellAmountWei <= 0n) {
381
- throw new Error('卖出数量为 0(请检查 sellAmount/sellPercent 或卖方余额)');
382
- }
383
- // 检查授权
384
- const allowance = await this.portalQuery.getAllowance(tokenAddress, sellerSender, this.portalAddress);
385
- const needApprove = allowance < sellAmountWei;
386
- // initCode:同一个 sender 的第一笔 op 才带 initCode
387
- let sellerInitCodeForNext = sellerAi.deployed ? '0x' : this.aaManager.generateInitCode(sellerOwner.address);
388
- const buyerInitCodes = buyerAis.map((ai, idx) => ai.deployed ? '0x' : this.aaManager.generateInitCode(buyerOwners[idx].address));
389
- const outOps = [];
390
- // 1) approve(可选)
391
- if (needApprove) {
392
- const approveNonce = nonceMap.next(sellerSender);
393
- const signedApprove = await this.buildApproveUserOp(sellerOwner, tokenAddress, this.portalAddress, sellerSender, approveNonce, sellerInitCodeForNext, 'seller', signOnly);
394
- outOps.push(signedApprove.userOp);
395
- sellerInitCodeForNext = '0x';
396
- }
397
- // 2) sell(一次)
398
- const sellNonce = nonceMap.next(sellerSender);
399
- const signedSell = await this.buildSellUserOp(sellerOwner, tokenAddress, sellAmountWei, sellerSender, sellNonce, sellerInitCodeForNext, needApprove, 'seller', signOnly);
400
- outOps.push(signedSell.userOp);
401
- sellerInitCodeForNext = '0x';
402
- // 3) buyers buy(多笔)
403
- const buyAmountsWei = buyAmountsOkb.map((v) => parseOkb(String(v)));
404
- const signedBuys = await mapWithConcurrency(buyerOwners, 4, async (w, i) => {
405
- const sender = buyerSenders[i];
406
- const initCode = buyerInitCodes[i];
407
- const nonce = nonceMap.next(sender);
408
- const amountWei = buyAmountsWei[i] ?? 0n;
409
- if (amountWei <= 0n) {
410
- throw new Error(`buyAmountsOkb[${i}] 无效(<=0)`);
411
- }
412
- return await this.buildBuyUserOpWithState({
413
- ownerWallet: w,
414
- sender,
415
- nonce,
416
- initCode,
417
- tokenAddress,
418
- buyAmountWei: amountWei,
419
- ownerName: `buyer${i + 1}`,
420
- signOnly,
421
- });
422
- });
423
- for (const s of signedBuys)
424
- outOps.push(s.userOp);
425
- return {
426
- ops: outOps,
427
- metadata: {
428
- sellerOwner: sellerOwner.address,
429
- sellerSender,
430
- buyerOwners: buyerOwners.map((w) => w.address),
431
- buyerSenders,
432
- sellAmountWei: sellAmountWei.toString(),
433
- buyAmountsWei: buyAmountsWei.map((x) => x.toString()),
434
- hasApprove: needApprove,
435
- },
436
- };
437
- }
438
207
  /**
439
208
  * 构建买入 UserOp
440
209
  */
@@ -1079,142 +848,6 @@ export class BundleExecutor {
1079
848
  const finalBalances = await this.portalQuery.getMultipleOkbBalances(senders);
1080
849
  return { buyResult, sellResult, withdrawResult, finalBalances };
1081
850
  }
1082
- /**
1083
- * ✅ 捆绑换手(AA):卖方卖出 → 买方买入,同一笔 handleOps 原子执行
1084
- */
1085
- async bundleSwap(params) {
1086
- const provider = this.aaManager.getProvider();
1087
- const payerWallet = new Wallet(params.sellerPrivateKey, provider);
1088
- const beneficiary = payerWallet.address;
1089
- const { ops, metadata } = await this.buildSwapOps(params, { signOnly: false });
1090
- const swapResult = await this.runHandleOps('swapBundle', ops, payerWallet, beneficiary);
1091
- if (!swapResult)
1092
- throw new Error('换手交易失败');
1093
- return { swapResult, metadata };
1094
- }
1095
- /**
1096
- * ✅ 捆绑换手(AA,仅签名):返回 raw signed tx 列表(像 BSC 捆绑一样交给后端提交)
1097
- * - signedTransactions[0] = handleOps tx(payer 签名)
1098
- * - 如果传入 routeAddress,则 signedTransactions[1] = tailTx(nonce+1)
1099
- */
1100
- async bundleSwapSign(params) {
1101
- const provider = this.aaManager.getProvider();
1102
- const payerPk = (params.payerPrivateKey ?? params.sellerPrivateKey);
1103
- const payerWallet = new Wallet(payerPk, provider);
1104
- const beneficiary = params.beneficiary ?? payerWallet.address;
1105
- const { ops, metadata } = await this.buildSwapOps(params, { signOnly: true });
1106
- // 先签 handleOps
1107
- const signedHandleOpsTx = await this.signHandleOpsTx({
1108
- ops,
1109
- payerWallet,
1110
- beneficiary,
1111
- nonce: params.payerStartNonce,
1112
- gasLimit: params.handleOpsGasLimit,
1113
- });
1114
- const signedTxs = [signedHandleOpsTx];
1115
- // 可选:追加 route 尾交易(nonce+1)
1116
- if (params.routeAddress) {
1117
- const tx = Transaction.from(signedHandleOpsTx);
1118
- const nonce = Number(tx.nonce);
1119
- const chainId = Number(tx.chainId || 0);
1120
- if (!Number.isFinite(nonce) || nonce < 0)
1121
- throw new Error('解析 handleOps nonce 失败');
1122
- if (!Number.isFinite(chainId) || chainId <= 0)
1123
- throw new Error('解析 handleOps chainId 失败');
1124
- const tailTx = await payerWallet.signTransaction({
1125
- to: params.routeAddress,
1126
- value: 0n,
1127
- data: '0x',
1128
- nonce: nonce + 1,
1129
- gasLimit: 21000n,
1130
- gasPrice: tx.gasPrice ?? undefined,
1131
- chainId,
1132
- type: 0,
1133
- });
1134
- signedTxs.push(tailTx);
1135
- }
1136
- return {
1137
- signedTransactions: signedTxs,
1138
- metadata: {
1139
- payer: payerWallet.address,
1140
- beneficiary,
1141
- sellerOwner: metadata.sellerOwner,
1142
- sellerSender: metadata.sellerSender,
1143
- buyerOwner: metadata.buyerOwner,
1144
- buyerSender: metadata.buyerSender,
1145
- sellAmountWei: metadata.sellAmountWei,
1146
- buyAmountWei: metadata.buyAmountWei,
1147
- hasApprove: metadata.hasApprove,
1148
- routeAddress: params.routeAddress,
1149
- },
1150
- };
1151
- }
1152
- /**
1153
- * ✅ 批量捆绑换手(AA):一卖多买,同一笔 handleOps 原子执行
1154
- */
1155
- async bundleBatchSwap(params) {
1156
- const provider = this.aaManager.getProvider();
1157
- const payerWallet = new Wallet(params.sellerPrivateKey, provider);
1158
- const beneficiary = payerWallet.address;
1159
- const { ops, metadata } = await this.buildBatchSwapOps(params, { signOnly: false });
1160
- const swapResult = await this.runHandleOps('batchSwapBundle', ops, payerWallet, beneficiary);
1161
- if (!swapResult)
1162
- throw new Error('批量换手交易失败');
1163
- return { swapResult, metadata };
1164
- }
1165
- /**
1166
- * ✅ 批量捆绑换手(AA,仅签名)
1167
- */
1168
- async bundleBatchSwapSign(params) {
1169
- const provider = this.aaManager.getProvider();
1170
- const payerPk = (params.payerPrivateKey ?? params.sellerPrivateKey);
1171
- const payerWallet = new Wallet(payerPk, provider);
1172
- const beneficiary = params.beneficiary ?? payerWallet.address;
1173
- const { ops, metadata } = await this.buildBatchSwapOps(params, { signOnly: true });
1174
- const signedHandleOpsTx = await this.signHandleOpsTx({
1175
- ops,
1176
- payerWallet,
1177
- beneficiary,
1178
- nonce: params.payerStartNonce,
1179
- gasLimit: params.handleOpsGasLimit,
1180
- });
1181
- const signedTxs = [signedHandleOpsTx];
1182
- if (params.routeAddress) {
1183
- const tx = Transaction.from(signedHandleOpsTx);
1184
- const nonce = Number(tx.nonce);
1185
- const chainId = Number(tx.chainId || 0);
1186
- if (!Number.isFinite(nonce) || nonce < 0)
1187
- throw new Error('解析 handleOps nonce 失败');
1188
- if (!Number.isFinite(chainId) || chainId <= 0)
1189
- throw new Error('解析 handleOps chainId 失败');
1190
- const tailTx = await payerWallet.signTransaction({
1191
- to: params.routeAddress,
1192
- value: 0n,
1193
- data: '0x',
1194
- nonce: nonce + 1,
1195
- gasLimit: 21000n,
1196
- gasPrice: tx.gasPrice ?? undefined,
1197
- chainId,
1198
- type: 0,
1199
- });
1200
- signedTxs.push(tailTx);
1201
- }
1202
- return {
1203
- signedTransactions: signedTxs,
1204
- metadata: {
1205
- payer: payerWallet.address,
1206
- beneficiary,
1207
- sellerOwner: metadata.sellerOwner,
1208
- sellerSender: metadata.sellerSender,
1209
- buyerOwners: metadata.buyerOwners,
1210
- buyerSenders: metadata.buyerSenders,
1211
- sellAmountWei: metadata.sellAmountWei,
1212
- buyAmountsWei: metadata.buyAmountsWei,
1213
- hasApprove: metadata.hasApprove,
1214
- routeAddress: params.routeAddress,
1215
- },
1216
- };
1217
- }
1218
851
  }
1219
852
  // ============================================================================
1220
853
  // 便捷函数
@@ -1246,31 +879,3 @@ export async function bundleBuySell(params) {
1246
879
  const executor = createBundleExecutor(params.config);
1247
880
  return executor.bundleBuySell(params);
1248
881
  }
1249
- /**
1250
- * 快速捆绑换手(链上执行)
1251
- */
1252
- export async function bundleSwap(params) {
1253
- const executor = createBundleExecutor(params.config);
1254
- return executor.bundleSwap(params);
1255
- }
1256
- /**
1257
- * 快速捆绑换手(仅签名,返回 raw signed tx)
1258
- */
1259
- export async function bundleSwapSign(params) {
1260
- const executor = createBundleExecutor(params.config);
1261
- return executor.bundleSwapSign(params);
1262
- }
1263
- /**
1264
- * 快速批量捆绑换手(链上执行)
1265
- */
1266
- export async function bundleBatchSwap(params) {
1267
- const executor = createBundleExecutor(params.config);
1268
- return executor.bundleBatchSwap(params);
1269
- }
1270
- /**
1271
- * 快速批量捆绑换手(仅签名)
1272
- */
1273
- export async function bundleBatchSwapSign(params) {
1274
- const executor = createBundleExecutor(params.config);
1275
- return executor.bundleBatchSwapSign(params);
1276
- }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * XLayer AA 外盘 (PotatoSwap/DEX) 捆绑换手实现
3
+ */
4
+ import { XLayerConfig, BundleSwapSignParams, BundleSwapSignResult, BundleBatchSwapSignParams, BundleBatchSwapSignResult } from './types.js';
5
+ /**
6
+ * XLayer AA 外盘换手执行器
7
+ */
8
+ export declare class AADexSwapExecutor {
9
+ private aaManager;
10
+ private dexQuery;
11
+ private bundleExecutor;
12
+ private config;
13
+ constructor(config?: XLayerConfig);
14
+ /**
15
+ * 获取 Router 地址
16
+ */
17
+ private getEffectiveRouter;
18
+ /**
19
+ * AA 外盘单钱包换手签名
20
+ */
21
+ bundleSwapSign(params: BundleSwapSignParams & {
22
+ skipApprovalCheck?: boolean;
23
+ }): Promise<BundleSwapSignResult>;
24
+ /**
25
+ * AA 外盘批量换手签名
26
+ */
27
+ bundleBatchSwapSign(params: BundleBatchSwapSignParams & {
28
+ skipApprovalCheck?: boolean;
29
+ }): Promise<BundleBatchSwapSignResult>;
30
+ }