four-flap-meme-sdk 1.5.26 → 1.5.27

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/index.d.ts CHANGED
@@ -53,4 +53,4 @@ export { submitDirectToRpc, submitDirectToRpcSequential, // ✅ 新增:顺序
53
53
  submitDirectToRpcParallel, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult } from './contracts/tm-bundle-merkle/submit.js';
54
54
  export { directV2BatchBuy, directV2BatchSell, directV3BatchBuy, directV3BatchSell, getRouterAddress, DIRECT_ROUTERS, type DirectV2BuyParams, type DirectV2SellParams, type DirectV3BuyParams, type DirectV3SellParams, type DirectRouterResult, type DirectRouterSignConfig, type DexKey, type RouterVersion, } from './dex/index.js';
55
55
  export * as XLayer from './xlayer/index.js';
56
- export { bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, createBundleExecutor as xlayerCreateBundleExecutor, BundleExecutor as XLayerBundleExecutor, makeVolume as xlayerMakeVolume, singleRoundVolume as xlayerSingleRoundVolume, createVolumeExecutor as xlayerCreateVolumeExecutor, VolumeExecutor as XLayerVolumeExecutor, createDexExecutor as xlayerCreateDexExecutor, createDexQuery as xlayerCreateDexQuery, quoteOkbToToken as xlayerQuoteOkbToToken, quoteTokenToOkb as xlayerQuoteTokenToOkb, DexExecutor as XLayerDexExecutor, DexQuery as XLayerDexQuery, createAAAccountManager as xlayerCreateAAAccountManager, predictSender as xlayerPredictSender, createWallet as xlayerCreateWallet, AAAccountManager as XLayerAAAccountManager, generateAAWallets as xlayerGenerateAAWallets, generateAAWalletsFromMnemonic as xlayerGenerateAAWalletsFromMnemonic, predictSendersFromPrivateKeys as xlayerPredictSendersFromPrivateKeys, createBundlerClient as xlayerCreateBundlerClient, BundlerClient as XLayerBundlerClient, createPortalQuery as xlayerCreatePortalQuery, PortalQuery as XLayerPortalQuery, encodeBuyCall as xlayerEncodeBuyCall, encodeSellCall as xlayerEncodeSellCall, encodeApproveCall as xlayerEncodeApproveCall, parseOkb as xlayerParseOkb, formatOkb as xlayerFormatOkb, XLAYER_CHAIN_ID, FLAP_PORTAL as XLAYER_FLAP_PORTAL, ENTRYPOINT_V06 as XLAYER_ENTRYPOINT, SIMPLE_ACCOUNT_FACTORY as XLAYER_FACTORY, PARTICLE_BUNDLER_URL as XLAYER_BUNDLER_URL, WOKB as XLAYER_WOKB, POTATOSWAP_V2_ROUTER as XLAYER_POTATOSWAP_ROUTER, type XLayerConfig, type BundleBuyParams as XLayerBundleBuyParams, type BundleSellParams as XLayerBundleSellParams, type BundleBuySellParams as XLayerBundleBuySellParams, type VolumeParams as XLayerVolumeParams, type BundleBuyResult as XLayerBundleBuyResult, type BundleSellResult as XLayerBundleSellResult, type BundleBuySellResult as XLayerBundleBuySellResult, type VolumeResult as XLayerVolumeResult, type HandleOpsResult as XLayerHandleOpsResult, type AAAccount as XLayerAAAccount, type UserOperation as XLayerUserOperation, type GeneratedAAWallet as XLayerGeneratedAAWallet, type GenerateAAWalletsParams as XLayerGenerateAAWalletsParams, type GenerateAAWalletsResult as XLayerGenerateAAWalletsResult, } from './xlayer/index.js';
56
+ export { bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, bundleSwap as xlayerBundleSwap, bundleSwapSign as xlayerBundleSwapSign, bundleBatchSwap as xlayerBundleBatchSwap, bundleBatchSwapSign as xlayerBundleBatchSwapSign, createBundleExecutor as xlayerCreateBundleExecutor, BundleExecutor as XLayerBundleExecutor, makeVolume as xlayerMakeVolume, singleRoundVolume as xlayerSingleRoundVolume, createVolumeExecutor as xlayerCreateVolumeExecutor, VolumeExecutor as XLayerVolumeExecutor, createDexExecutor as xlayerCreateDexExecutor, createDexQuery as xlayerCreateDexQuery, quoteOkbToToken as xlayerQuoteOkbToToken, quoteTokenToOkb as xlayerQuoteTokenToOkb, DexExecutor as XLayerDexExecutor, DexQuery as XLayerDexQuery, createAAAccountManager as xlayerCreateAAAccountManager, predictSender as xlayerPredictSender, createWallet as xlayerCreateWallet, AAAccountManager as XLayerAAAccountManager, generateAAWallets as xlayerGenerateAAWallets, generateAAWalletsFromMnemonic as xlayerGenerateAAWalletsFromMnemonic, predictSendersFromPrivateKeys as xlayerPredictSendersFromPrivateKeys, createBundlerClient as xlayerCreateBundlerClient, BundlerClient as XLayerBundlerClient, createPortalQuery as xlayerCreatePortalQuery, PortalQuery as XLayerPortalQuery, encodeBuyCall as xlayerEncodeBuyCall, encodeSellCall as xlayerEncodeSellCall, encodeApproveCall as xlayerEncodeApproveCall, parseOkb as xlayerParseOkb, formatOkb as xlayerFormatOkb, XLAYER_CHAIN_ID, FLAP_PORTAL as XLAYER_FLAP_PORTAL, ENTRYPOINT_V06 as XLAYER_ENTRYPOINT, SIMPLE_ACCOUNT_FACTORY as XLAYER_FACTORY, PARTICLE_BUNDLER_URL as XLAYER_BUNDLER_URL, WOKB as XLAYER_WOKB, POTATOSWAP_V2_ROUTER as XLAYER_POTATOSWAP_ROUTER, type XLayerConfig, type BundleBuyParams as XLayerBundleBuyParams, type BundleSellParams as XLayerBundleSellParams, type BundleBuySellParams as XLayerBundleBuySellParams, type BundleSwapParams as XLayerBundleSwapParams, type BundleSwapSignParams as XLayerBundleSwapSignParams, type BundleBatchSwapParams as XLayerBundleBatchSwapParams, type BundleBatchSwapSignParams as XLayerBundleBatchSwapSignParams, type VolumeParams as XLayerVolumeParams, type BundleBuyResult as XLayerBundleBuyResult, type BundleSellResult as XLayerBundleSellResult, type BundleBuySellResult as XLayerBundleBuySellResult, type BundleSwapResult as XLayerBundleSwapResult, type BundleSwapSignResult as XLayerBundleSwapSignResult, type BundleBatchSwapResult as XLayerBundleBatchSwapResult, type BundleBatchSwapSignResult as XLayerBundleBatchSwapSignResult, type VolumeResult as XLayerVolumeResult, type HandleOpsResult as XLayerHandleOpsResult, type AAAccount as XLayerAAAccount, type UserOperation as XLayerUserOperation, type GeneratedAAWallet as XLayerGeneratedAAWallet, type GenerateAAWalletsParams as XLayerGenerateAAWalletsParams, type GenerateAAWalletsResult as XLayerGenerateAAWalletsResult, } from './xlayer/index.js';
package/dist/index.js CHANGED
@@ -102,7 +102,7 @@ DIRECT_ROUTERS, } from './dex/index.js';
102
102
  export * as XLayer from './xlayer/index.js';
103
103
  export {
104
104
  // 捆绑交易
105
- bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, createBundleExecutor as xlayerCreateBundleExecutor, BundleExecutor as XLayerBundleExecutor,
105
+ bundleBuy as xlayerBundleBuy, bundleSell as xlayerBundleSell, bundleBuySell as xlayerBundleBuySell, bundleSwap as xlayerBundleSwap, bundleSwapSign as xlayerBundleSwapSign, bundleBatchSwap as xlayerBundleBatchSwap, bundleBatchSwapSign as xlayerBundleBatchSwapSign, createBundleExecutor as xlayerCreateBundleExecutor, BundleExecutor as XLayerBundleExecutor,
106
106
  // 刷量
107
107
  makeVolume as xlayerMakeVolume, singleRoundVolume as xlayerSingleRoundVolume, createVolumeExecutor as xlayerCreateVolumeExecutor, VolumeExecutor as XLayerVolumeExecutor,
108
108
  // DEX 交易
@@ -7,7 +7,7 @@
7
7
  * - 买卖一体化:买入 -> 授权 -> 卖出 -> 归集
8
8
  * - OKB 归集:将 sender 的 OKB 转回 owner
9
9
  */
10
- import type { XLayerConfig, BundleBuyParams, BundleBuyResult, BundleSellParams, BundleSellResult, BundleBuySellParams, BundleBuySellResult } from './types.js';
10
+ import type { XLayerConfig, BundleBuyParams, BundleBuyResult, BundleSellParams, BundleSellResult, BundleBuySellParams, BundleBuySellResult, BundleSwapParams, BundleSwapResult, BundleSwapSignParams, BundleSwapSignResult, BundleBatchSwapParams, BundleBatchSwapResult, BundleBatchSwapSignParams, BundleBatchSwapSignResult } from './types.js';
11
11
  import { AAAccountManager } from './aa-account.js';
12
12
  import { PortalQuery } from './portal-ops.js';
13
13
  /**
@@ -36,6 +36,21 @@ export declare class BundleExecutor {
36
36
  * 执行 handleOps 并解析结果
37
37
  */
38
38
  private runHandleOps;
39
+ /**
40
+ * 构造并签名 handleOps 交易(raw signed tx)
41
+ * - 用于“像 BSC 一样:前端/后端提交 raw tx bundle”的场景
42
+ */
43
+ private signHandleOpsTx;
44
+ private getErc20Decimals;
45
+ /**
46
+ * 构建买入 UserOp(已知 sender/nonce/initCode 的版本)
47
+ */
48
+ private buildBuyUserOpWithState;
49
+ /**
50
+ * 构建换手所需的 UserOps(approve? + sell + buy),并返回元数据
51
+ */
52
+ private buildSwapOps;
53
+ private buildBatchSwapOps;
39
54
  /**
40
55
  * 构建买入 UserOp
41
56
  */
@@ -79,6 +94,24 @@ export declare class BundleExecutor {
79
94
  * 完整流程:买入 -> 授权 -> 卖出 -> 归集
80
95
  */
81
96
  bundleBuySell(params: BundleBuySellParams): Promise<BundleBuySellResult>;
97
+ /**
98
+ * ✅ 捆绑换手(AA):卖方卖出 → 买方买入,同一笔 handleOps 原子执行
99
+ */
100
+ bundleSwap(params: BundleSwapParams): Promise<BundleSwapResult>;
101
+ /**
102
+ * ✅ 捆绑换手(AA,仅签名):返回 raw signed tx 列表(像 BSC 捆绑一样交给后端提交)
103
+ * - signedTransactions[0] = handleOps tx(payer 签名)
104
+ * - 如果传入 routeAddress,则 signedTransactions[1] = tailTx(nonce+1)
105
+ */
106
+ bundleSwapSign(params: BundleSwapSignParams): Promise<BundleSwapSignResult>;
107
+ /**
108
+ * ✅ 批量捆绑换手(AA):一卖多买,同一笔 handleOps 原子执行
109
+ */
110
+ bundleBatchSwap(params: BundleBatchSwapParams): Promise<BundleBatchSwapResult>;
111
+ /**
112
+ * ✅ 批量捆绑换手(AA,仅签名)
113
+ */
114
+ bundleBatchSwapSign(params: BundleBatchSwapSignParams): Promise<BundleBatchSwapSignResult>;
82
115
  }
83
116
  /**
84
117
  * 创建捆绑交易执行器
@@ -96,3 +129,19 @@ export declare function bundleSell(params: BundleSellParams): Promise<BundleSell
96
129
  * 快速捆绑买卖
97
130
  */
98
131
  export declare function bundleBuySell(params: BundleBuySellParams): Promise<BundleBuySellResult>;
132
+ /**
133
+ * 快速捆绑换手(链上执行)
134
+ */
135
+ export declare function bundleSwap(params: BundleSwapParams): Promise<BundleSwapResult>;
136
+ /**
137
+ * 快速捆绑换手(仅签名,返回 raw signed tx)
138
+ */
139
+ export declare function bundleSwapSign(params: BundleSwapSignParams): Promise<BundleSwapSignResult>;
140
+ /**
141
+ * 快速批量捆绑换手(链上执行)
142
+ */
143
+ export declare function bundleBatchSwap(params: BundleBatchSwapParams): Promise<BundleBatchSwapResult>;
144
+ /**
145
+ * 快速批量捆绑换手(仅签名)
146
+ */
147
+ export declare function bundleBatchSwapSign(params: BundleBatchSwapSignParams): Promise<BundleBatchSwapSignResult>;
@@ -7,7 +7,7 @@
7
7
  * - 买卖一体化:买入 -> 授权 -> 卖出 -> 归集
8
8
  * - OKB 归集:将 sender 的 OKB 转回 owner
9
9
  */
10
- import { Wallet, Interface, Contract } from 'ethers';
10
+ import { Wallet, Interface, Contract, Transaction, ethers } 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';
@@ -153,6 +153,288 @@ export class BundleExecutor {
153
153
  userOpEvents,
154
154
  };
155
155
  }
156
+ /**
157
+ * 构造并签名 handleOps 交易(raw signed tx)
158
+ * - 用于“像 BSC 一样:前端/后端提交 raw tx bundle”的场景
159
+ */
160
+ async signHandleOpsTx(params) {
161
+ const provider = this.aaManager.getProvider();
162
+ const entryPointAddress = this.aaManager.getEntryPointAddress();
163
+ const epIface = new Interface(ENTRYPOINT_ABI);
164
+ const data = epIface.encodeFunctionData('handleOps', [params.ops, params.beneficiary]);
165
+ const feeData = await provider.getFeeData();
166
+ const gasPrice = params.gasPrice ?? feeData.gasPrice ?? 100000000n;
167
+ const nonce = params.nonce ?? await provider.getTransactionCount(params.payerWallet.address, 'pending');
168
+ let gasLimit = params.gasLimit;
169
+ if (!gasLimit) {
170
+ try {
171
+ const est = await provider.estimateGas({
172
+ from: params.payerWallet.address,
173
+ to: entryPointAddress,
174
+ data,
175
+ value: 0n,
176
+ });
177
+ gasLimit = BigInt(Math.ceil(Number(est) * 1.2));
178
+ }
179
+ catch {
180
+ // 兜底:handleOps 可能很大,使用相对保守的 gasLimit
181
+ gasLimit = 3500000n;
182
+ }
183
+ }
184
+ return await params.payerWallet.signTransaction({
185
+ to: entryPointAddress,
186
+ data,
187
+ value: 0n,
188
+ nonce,
189
+ gasLimit,
190
+ gasPrice,
191
+ chainId: this.config.chainId ?? 196,
192
+ type: 0,
193
+ });
194
+ }
195
+ async getErc20Decimals(tokenAddress) {
196
+ const provider = this.aaManager.getProvider();
197
+ const token = new Contract(tokenAddress, ['function decimals() view returns (uint8)'], provider);
198
+ try {
199
+ const d = await token.decimals();
200
+ const n = Number(d);
201
+ return Number.isFinite(n) ? n : 18;
202
+ }
203
+ catch {
204
+ return 18;
205
+ }
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
+ }
156
438
  /**
157
439
  * 构建买入 UserOp
158
440
  */
@@ -200,11 +482,14 @@ export class BundleExecutor {
200
482
  /**
201
483
  * 构建授权 UserOp
202
484
  */
203
- async buildApproveUserOp(ownerWallet, tokenAddress, spender, sender, nonce, initCode, ownerName) {
485
+ async buildApproveUserOp(ownerWallet, tokenAddress, spender, sender, nonce, initCode, ownerName, signOnly = false) {
204
486
  const approveData = encodeApproveCall(spender);
205
487
  const callData = encodeExecute(tokenAddress, 0n, approveData);
206
- await this.aaManager.ensureSenderBalance(ownerWallet, sender, parseOkb('0.0002'), `${ownerName ?? 'owner'}/approve-prefund`);
207
- const gasPolicy = this.config.gasPolicy ?? 'bundlerEstimate';
488
+ if (!signOnly) {
489
+ await this.aaManager.ensureSenderBalance(ownerWallet, sender, parseOkb('0.0002'), `${ownerName ?? 'owner'}/approve-prefund`);
490
+ }
491
+ const gasPolicyRaw = this.config.gasPolicy ?? 'bundlerEstimate';
492
+ const gasPolicy = signOnly && gasPolicyRaw === 'bundlerEstimate' ? 'fixed' : gasPolicyRaw;
208
493
  const { userOp, prefundWei } = gasPolicy === 'fixed'
209
494
  ? await this.aaManager.buildUserOpWithFixedGas({
210
495
  ownerWallet,
@@ -218,25 +503,38 @@ export class BundleExecutor {
218
503
  callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_APPROVE,
219
504
  },
220
505
  })
221
- : await this.aaManager.buildUserOpWithLocalEstimate({
222
- ownerWallet,
223
- sender,
224
- callData,
225
- nonce,
226
- initCode,
227
- });
228
- await this.aaManager.ensureSenderBalance(ownerWallet, sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/approve-fund`);
506
+ : gasPolicy === 'localEstimate'
507
+ ? await this.aaManager.buildUserOpWithLocalEstimate({
508
+ ownerWallet,
509
+ sender,
510
+ callData,
511
+ nonce,
512
+ initCode,
513
+ })
514
+ : await this.aaManager.buildUserOpWithBundlerEstimate({
515
+ ownerWallet,
516
+ sender,
517
+ callData,
518
+ nonce,
519
+ initCode,
520
+ });
521
+ if (!signOnly) {
522
+ await this.aaManager.ensureSenderBalance(ownerWallet, sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/approve-fund`);
523
+ }
229
524
  const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
230
525
  return { ...signed, prefundWei, ownerName };
231
526
  }
232
527
  /**
233
528
  * 构建卖出 UserOp
234
529
  */
235
- async buildSellUserOp(ownerWallet, tokenAddress, sellAmount, sender, nonce, initCode, needApprove, ownerName) {
530
+ async buildSellUserOp(ownerWallet, tokenAddress, sellAmount, sender, nonce, initCode, needApprove, ownerName, signOnly = false) {
236
531
  const sellData = encodeSellCall(tokenAddress, sellAmount, 0n);
237
532
  const callData = encodeExecute(this.portalAddress, 0n, sellData);
238
- await this.aaManager.ensureSenderBalance(ownerWallet, sender, parseOkb('0.0003'), `${ownerName ?? 'owner'}/sell-prefund`);
239
- const gasPolicy = this.config.gasPolicy ?? 'bundlerEstimate';
533
+ if (!signOnly) {
534
+ await this.aaManager.ensureSenderBalance(ownerWallet, sender, parseOkb('0.0003'), `${ownerName ?? 'owner'}/sell-prefund`);
535
+ }
536
+ const gasPolicyRaw = this.config.gasPolicy ?? 'bundlerEstimate';
537
+ const gasPolicy = signOnly && gasPolicyRaw === 'bundlerEstimate' ? 'fixed' : gasPolicyRaw;
240
538
  // 如果需要 approve(还未执行),estimateGas 可能 revert;因此默认就用固定 callGasLimit
241
539
  const { userOp, prefundWei } = gasPolicy === 'fixed'
242
540
  ? await this.aaManager.buildUserOpWithFixedGas({
@@ -251,23 +549,33 @@ export class BundleExecutor {
251
549
  callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL,
252
550
  },
253
551
  })
254
- : needApprove
255
- ? await this.aaManager.buildUserOpWithLocalEstimate({
256
- ownerWallet,
257
- sender,
258
- callData,
259
- nonce,
260
- initCode,
261
- callGasLimit: DEFAULT_CALL_GAS_LIMIT_SELL,
262
- })
263
- : await this.aaManager.buildUserOpWithLocalEstimate({
552
+ : gasPolicy === 'localEstimate'
553
+ ? (needApprove
554
+ ? await this.aaManager.buildUserOpWithLocalEstimate({
555
+ ownerWallet,
556
+ sender,
557
+ callData,
558
+ nonce,
559
+ initCode,
560
+ callGasLimit: DEFAULT_CALL_GAS_LIMIT_SELL,
561
+ })
562
+ : await this.aaManager.buildUserOpWithLocalEstimate({
563
+ ownerWallet,
564
+ sender,
565
+ callData,
566
+ nonce,
567
+ initCode,
568
+ }))
569
+ : await this.aaManager.buildUserOpWithBundlerEstimate({
264
570
  ownerWallet,
265
571
  sender,
266
572
  callData,
267
573
  nonce,
268
574
  initCode,
269
575
  });
270
- await this.aaManager.ensureSenderBalance(ownerWallet, sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/sell-fund`);
576
+ if (!signOnly) {
577
+ await this.aaManager.ensureSenderBalance(ownerWallet, sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/sell-fund`);
578
+ }
271
579
  const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
272
580
  return { ...signed, prefundWei, ownerName };
273
581
  }
@@ -300,8 +608,11 @@ export class BundleExecutor {
300
608
  }
301
609
  // 先估算 prefund(使用空调用)
302
610
  const tempCallData = encodeExecute(params.ownerWallet.address, 0n, '0x');
303
- await this.aaManager.ensureSenderBalance(params.ownerWallet, params.sender, parseOkb('0.0002'), `${params.ownerName ?? 'owner'}/withdraw-prefund`);
304
- const gasPolicy = this.config.gasPolicy ?? 'bundlerEstimate';
611
+ if (!params.signOnly) {
612
+ await this.aaManager.ensureSenderBalance(params.ownerWallet, params.sender, parseOkb('0.0002'), `${params.ownerName ?? 'owner'}/withdraw-prefund`);
613
+ }
614
+ const gasPolicyRaw = this.config.gasPolicy ?? 'bundlerEstimate';
615
+ const gasPolicy = params.signOnly && gasPolicyRaw === 'bundlerEstimate' ? 'fixed' : gasPolicyRaw;
305
616
  const { prefundWei } = gasPolicy === 'fixed'
306
617
  ? await this.aaManager.buildUserOpWithFixedGas({
307
618
  ownerWallet: params.ownerWallet,
@@ -315,13 +626,21 @@ export class BundleExecutor {
315
626
  callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
316
627
  },
317
628
  })
318
- : await this.aaManager.buildUserOpWithLocalEstimate({
319
- ownerWallet: params.ownerWallet,
320
- sender: params.sender,
321
- callData: tempCallData,
322
- nonce: params.nonce,
323
- initCode: params.initCode,
324
- });
629
+ : gasPolicy === 'localEstimate'
630
+ ? await this.aaManager.buildUserOpWithLocalEstimate({
631
+ ownerWallet: params.ownerWallet,
632
+ sender: params.sender,
633
+ callData: tempCallData,
634
+ nonce: params.nonce,
635
+ initCode: params.initCode,
636
+ })
637
+ : await this.aaManager.buildUserOpWithBundlerEstimate({
638
+ ownerWallet: params.ownerWallet,
639
+ sender: params.sender,
640
+ callData: tempCallData,
641
+ nonce: params.nonce,
642
+ initCode: params.initCode,
643
+ });
325
644
  // 计算可归集金额(用已知余额近似;fund 发生时余额会变大,属于可接受的保守近似)
326
645
  const withdrawAmount = senderBalance > prefundWei + params.reserveWei
327
646
  ? senderBalance - prefundWei - params.reserveWei
@@ -344,13 +663,21 @@ export class BundleExecutor {
344
663
  callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
345
664
  },
346
665
  })
347
- : await this.aaManager.buildUserOpWithLocalEstimate({
348
- ownerWallet: params.ownerWallet,
349
- sender: params.sender,
350
- callData,
351
- nonce: params.nonce,
352
- initCode: params.initCode,
353
- });
666
+ : gasPolicy === 'localEstimate'
667
+ ? await this.aaManager.buildUserOpWithLocalEstimate({
668
+ ownerWallet: params.ownerWallet,
669
+ sender: params.sender,
670
+ callData,
671
+ nonce: params.nonce,
672
+ initCode: params.initCode,
673
+ })
674
+ : await this.aaManager.buildUserOpWithBundlerEstimate({
675
+ ownerWallet: params.ownerWallet,
676
+ sender: params.sender,
677
+ callData,
678
+ nonce: params.nonce,
679
+ initCode: params.initCode,
680
+ });
354
681
  console.log(`\n[${params.ownerName ?? 'owner'}] withdraw: ${formatOkb(withdrawAmount)} OKB`);
355
682
  const signed = await this.aaManager.signUserOp(userOp, params.ownerWallet);
356
683
  return { ...signed, prefundWei, ownerName: params.ownerName };
@@ -752,6 +1079,142 @@ export class BundleExecutor {
752
1079
  const finalBalances = await this.portalQuery.getMultipleOkbBalances(senders);
753
1080
  return { buyResult, sellResult, withdrawResult, finalBalances };
754
1081
  }
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
+ }
755
1218
  }
756
1219
  // ============================================================================
757
1220
  // 便捷函数
@@ -783,3 +1246,31 @@ export async function bundleBuySell(params) {
783
1246
  const executor = createBundleExecutor(params.config);
784
1247
  return executor.bundleBuySell(params);
785
1248
  }
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
+ }
@@ -61,13 +61,17 @@ export * from './types.js';
61
61
  export { BundlerClient, createBundlerClient, type BundlerConfig, type BundlerReceipt, } from './bundler.js';
62
62
  export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, type GeneratedAAWallet, type GenerateAAWalletsParams, type GenerateAAWalletsResult, } from './aa-account.js';
63
63
  export { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, createPortalQuery, applySlippage, formatOkb, parseOkb, formatTokenAmount, parseTokenAmount, type PortalQueryConfig, } from './portal-ops.js';
64
- export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, } from './bundle.js';
64
+ export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, bundleSwap, bundleSwapSign, bundleBatchSwap, bundleBatchSwapSign, } from './bundle.js';
65
65
  export { VolumeExecutor, createVolumeExecutor, makeVolume, singleRoundVolume, } from './volume.js';
66
66
  export { DexQuery, DexExecutor, createDexQuery, createDexExecutor, quoteOkbToToken, quoteTokenToOkb, encodeSwapExactETHForTokens, encodeSwapExactTokensForETH, encodeSwapExactTokensForTokens, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactTokensForTokensSupportingFee, type DexConfig, } from './dex.js';
67
67
  export declare const xlayer: {
68
68
  bundleBuy: (params: import("./types.js").BundleBuyParams) => Promise<import("./types.js").BundleBuyResult>;
69
69
  bundleSell: (params: import("./types.js").BundleSellParams) => Promise<import("./types.js").BundleSellResult>;
70
70
  bundleBuySell: (params: import("./types.js").BundleBuySellParams) => Promise<import("./types.js").BundleBuySellResult>;
71
+ bundleSwap: (params: import("./types.js").BundleSwapParams) => Promise<import("./types.js").BundleSwapResult>;
72
+ bundleSwapSign: (params: import("./types.js").BundleSwapSignParams) => Promise<import("./types.js").BundleSwapSignResult>;
73
+ bundleBatchSwap: (params: import("./types.js").BundleBatchSwapParams) => Promise<import("./types.js").BundleBatchSwapResult>;
74
+ bundleBatchSwapSign: (params: import("./types.js").BundleBatchSwapSignParams) => Promise<import("./types.js").BundleBatchSwapSignResult>;
71
75
  makeVolume: (params: import("./types.js").VolumeParams) => Promise<import("./types.js").VolumeResult>;
72
76
  quoteOkbToToken: (okbAmount: bigint, tokenAddress: string, config?: import("./dex.js").DexConfig | undefined) => Promise<bigint>;
73
77
  quoteTokenToOkb: (tokenAmount: bigint, tokenAddress: string, config?: import("./dex.js").DexConfig | undefined) => Promise<bigint>;
@@ -81,7 +81,7 @@ export { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, P
81
81
  // ============================================================================
82
82
  // 捆绑交易
83
83
  // ============================================================================
84
- export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, } from './bundle.js';
84
+ export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, bundleSwap, bundleSwapSign, bundleBatchSwap, bundleBatchSwapSign, } from './bundle.js';
85
85
  // ============================================================================
86
86
  // 刷量/做市
87
87
  // ============================================================================
@@ -112,6 +112,22 @@ export const xlayer = {
112
112
  const { bundleBuySell } = await import('./bundle.js');
113
113
  return bundleBuySell(...args);
114
114
  },
115
+ bundleSwap: async (...args) => {
116
+ const { bundleSwap } = await import('./bundle.js');
117
+ return bundleSwap(...args);
118
+ },
119
+ bundleSwapSign: async (...args) => {
120
+ const { bundleSwapSign } = await import('./bundle.js');
121
+ return bundleSwapSign(...args);
122
+ },
123
+ bundleBatchSwap: async (...args) => {
124
+ const { bundleBatchSwap } = await import('./bundle.js');
125
+ return bundleBatchSwap(...args);
126
+ },
127
+ bundleBatchSwapSign: async (...args) => {
128
+ const { bundleBatchSwapSign } = await import('./bundle.js');
129
+ return bundleBatchSwapSign(...args);
130
+ },
115
131
  // 刷量
116
132
  makeVolume: async (...args) => {
117
133
  const { makeVolume } = await import('./volume.js');
@@ -189,6 +189,142 @@ export interface BundleBuySellParams {
189
189
  /** 配置覆盖 */
190
190
  config?: Partial<XLayerConfig>;
191
191
  }
192
+ /**
193
+ * 捆绑换手参数(AA 模式,单卖单买)
194
+ * - 卖方 AA 先卖出
195
+ * - 买方 AA 再买入
196
+ * - 两个 UserOp(以及可选 approve)放入同一笔 handleOps
197
+ */
198
+ export interface BundleSwapParams {
199
+ /** 代币地址 */
200
+ tokenAddress: string;
201
+ /** 卖方 Owner 私钥 */
202
+ sellerPrivateKey: string;
203
+ /** 买方 Owner 私钥 */
204
+ buyerPrivateKey: string;
205
+ /** 卖出数量(人类可读字符串,按代币 decimals 解析);优先于 sellPercent */
206
+ sellAmount?: string;
207
+ /** 卖出比例(0-100,默认 100) */
208
+ sellPercent?: number;
209
+ /**
210
+ * 买入 OKB 数量(字符串)。
211
+ * - 不传则默认使用 previewSell(sellAmount) 的结果,并应用 slippageBps
212
+ */
213
+ buyAmountOkb?: string;
214
+ /** 预估卖出滑点(基点),默认 100 = 1%(用于 buyAmountOkb 未传入时) */
215
+ slippageBps?: number;
216
+ /** 配置覆盖 */
217
+ config?: Partial<XLayerConfig>;
218
+ }
219
+ /**
220
+ * 捆绑换手执行结果(链上执行 handleOps)
221
+ */
222
+ export interface BundleSwapResult {
223
+ /** handleOps 交易结果 */
224
+ swapResult: HandleOpsResult;
225
+ /** 元数据(便于前端显示) */
226
+ metadata: {
227
+ sellerOwner: string;
228
+ sellerSender: string;
229
+ buyerOwner: string;
230
+ buyerSender: string;
231
+ sellAmountWei: string;
232
+ buyAmountWei: string;
233
+ hasApprove: boolean;
234
+ };
235
+ }
236
+ /**
237
+ * 捆绑换手签名参数(仅签名 handleOps 交易;可选追加 route 尾交易)
238
+ * - 返回 raw signed tx,可按“BSC 捆绑”模式交给后端/Builder 提交
239
+ */
240
+ export interface BundleSwapSignParams extends BundleSwapParams {
241
+ /** Payer 私钥(用于签 handleOps 交易);默认 sellerPrivateKey */
242
+ payerPrivateKey?: string;
243
+ /** beneficiary(默认 payer 地址) */
244
+ beneficiary?: string;
245
+ /** 可选:指定 payer 起始 nonce(不传则用 pending nonce) */
246
+ payerStartNonce?: number;
247
+ /** 可选:覆盖 handleOps gasLimit(不传则自动 estimate,失败回退) */
248
+ handleOpsGasLimit?: bigint;
249
+ /**
250
+ * 可选:routeAddress(如果你的后端要求“route 尾交易”,则传入)
251
+ * - SDK 会自动构造 nonce+1 的空转账 tailTx
252
+ */
253
+ routeAddress?: string;
254
+ }
255
+ /**
256
+ * 捆绑换手签名结果
257
+ */
258
+ export interface BundleSwapSignResult {
259
+ /** raw signed tx 列表(handleOps 在前;如果有 routeAddress 则 tailTx 在后) */
260
+ signedTransactions: string[];
261
+ /** 元数据(便于前端显示) */
262
+ metadata: {
263
+ payer: string;
264
+ beneficiary: string;
265
+ sellerOwner: string;
266
+ sellerSender: string;
267
+ buyerOwner: string;
268
+ buyerSender: string;
269
+ sellAmountWei: string;
270
+ buyAmountWei: string;
271
+ hasApprove: boolean;
272
+ routeAddress?: string;
273
+ };
274
+ }
275
+ /**
276
+ * 批量捆绑换手参数(一卖多买)
277
+ */
278
+ export interface BundleBatchSwapParams {
279
+ /** 代币地址 */
280
+ tokenAddress: string;
281
+ /** 卖方 Owner 私钥 */
282
+ sellerPrivateKey: string;
283
+ /** 买方 Owner 私钥列表 */
284
+ buyerPrivateKeys: string[];
285
+ /** 每个买方的买入 OKB 数量(字符串,长度必须与 buyerPrivateKeys 一致) */
286
+ buyAmountsOkb: string[];
287
+ /** 卖出数量(人类可读字符串,按代币 decimals 解析);优先于 sellPercent */
288
+ sellAmount?: string;
289
+ /** 卖出比例(0-100,默认 100) */
290
+ sellPercent?: number;
291
+ /** 配置覆盖 */
292
+ config?: Partial<XLayerConfig>;
293
+ }
294
+ export interface BundleBatchSwapResult {
295
+ swapResult: HandleOpsResult;
296
+ metadata: {
297
+ sellerOwner: string;
298
+ sellerSender: string;
299
+ buyerOwners: string[];
300
+ buyerSenders: string[];
301
+ sellAmountWei: string;
302
+ buyAmountsWei: string[];
303
+ hasApprove: boolean;
304
+ };
305
+ }
306
+ export interface BundleBatchSwapSignParams extends BundleBatchSwapParams {
307
+ payerPrivateKey?: string;
308
+ beneficiary?: string;
309
+ payerStartNonce?: number;
310
+ handleOpsGasLimit?: bigint;
311
+ routeAddress?: string;
312
+ }
313
+ export interface BundleBatchSwapSignResult {
314
+ signedTransactions: string[];
315
+ metadata: {
316
+ payer: string;
317
+ beneficiary: string;
318
+ sellerOwner: string;
319
+ sellerSender: string;
320
+ buyerOwners: string[];
321
+ buyerSenders: string[];
322
+ sellAmountWei: string;
323
+ buyAmountsWei: string[];
324
+ hasApprove: boolean;
325
+ routeAddress?: string;
326
+ };
327
+ }
192
328
  /**
193
329
  * 刷量参数
194
330
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.5.26",
3
+ "version": "1.5.27",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",