four-flap-meme-sdk 1.6.95 → 1.6.97

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.
@@ -255,6 +255,8 @@ export declare function submitDirectToRpc(signedTransactions: string[], config:
255
255
  *
256
256
  * 每笔交易广播后会等待上链确认,确保后续交易能正确执行
257
257
  *
258
+ * ⚠️ 多跳场景:如果某笔交易失败,会立即停止(因为后续交易肯定也会失败)
259
+ *
258
260
  * @param signedTransactions 签名后的交易数组
259
261
  * @param config 直接广播配置
260
262
  * @returns 广播结果
@@ -397,6 +397,8 @@ export async function submitDirectToRpc(signedTransactions, config) {
397
397
  *
398
398
  * 每笔交易广播后会等待上链确认,确保后续交易能正确执行
399
399
  *
400
+ * ⚠️ 多跳场景:如果某笔交易失败,会立即停止(因为后续交易肯定也会失败)
401
+ *
400
402
  * @param signedTransactions 签名后的交易数组
401
403
  * @param config 直接广播配置
402
404
  * @returns 广播结果
@@ -427,7 +429,8 @@ export async function submitDirectToRpcSequential(signedTransactions, config) {
427
429
  }
428
430
  const chainId = config.chainId ?? 143;
429
431
  const chainName = config.chainName ?? 'MONAD';
430
- const confirmationTimeout = config.confirmationTimeout ?? 60000; // 默认60秒超时
432
+ // XLayer 链默认 120 秒超时(网络可能较慢)
433
+ const confirmationTimeout = config.confirmationTimeout ?? (chainId === 196 ? 120000 : 60000);
431
434
  const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
432
435
  chainId,
433
436
  name: chainName
@@ -435,16 +438,19 @@ export async function submitDirectToRpcSequential(signedTransactions, config) {
435
438
  const results = [];
436
439
  const txHashes = [];
437
440
  const errors = [];
441
+ console.log(`📤 [${chainName}] 开始顺序广播 ${totalTransactions} 笔交易(等待每笔确认)`);
438
442
  // 顺序广播并等待确认
439
443
  for (let i = 0; i < signedTransactions.length; i++) {
440
444
  const signedTx = signedTransactions[i];
441
445
  try {
442
446
  // 广播交易
443
447
  const txResponse = await provider.broadcastTransaction(signedTx);
448
+ console.log(`📡 [${chainName}] 交易 ${i + 1}/${totalTransactions} 广播成功: ${txResponse.hash},等待确认...`);
444
449
  // ✅ 等待交易确认
445
450
  const receipt = await provider.waitForTransaction(txResponse.hash, 1, // 等待1个确认
446
451
  confirmationTimeout);
447
452
  if (receipt && receipt.status === 1) {
453
+ console.log(`✅ [${chainName}] 交易 ${i + 1}/${totalTransactions} 已确认: ${txResponse.hash}`);
448
454
  results.push({
449
455
  index: i,
450
456
  success: true,
@@ -454,6 +460,7 @@ export async function submitDirectToRpcSequential(signedTransactions, config) {
454
460
  }
455
461
  else {
456
462
  const errorMsg = `交易执行失败(status=${receipt?.status})`;
463
+ console.error(`❌ [${chainName}] 交易 ${i + 1}/${totalTransactions} 上链失败: ${errorMsg}`);
457
464
  results.push({
458
465
  index: i,
459
466
  success: false,
@@ -461,32 +468,44 @@ export async function submitDirectToRpcSequential(signedTransactions, config) {
461
468
  error: errorMsg
462
469
  });
463
470
  errors.push(`交易 ${i + 1}: ${errorMsg}`);
464
- // ✅ 多跳场景:如果某笔失败,后续交易可能也会失败,继续尝试但标记
471
+ // ✅ 多跳场景:如果某笔失败,后续交易肯定也会失败,立即停止
472
+ console.warn(`⚠️ [${chainName}] 多跳模式检测到失败,停止后续 ${totalTransactions - i - 1} 笔交易`);
473
+ break;
465
474
  }
466
475
  }
467
476
  catch (error) {
468
477
  const errorMessage = error?.message || String(error);
478
+ console.error(`❌ [${chainName}] 交易 ${i + 1}/${totalTransactions} 广播/确认失败:`, errorMessage);
469
479
  // ✅ 兼容:nonce too low / already known 等情况下,交易可能已经被接收
470
480
  const recovered = await recoverTxHashIfAlreadySeen(provider, signedTx, errorMessage);
471
481
  if (recovered) {
482
+ console.log(`🔄 [${chainName}] 交易 ${i + 1} 可能已广播,尝试等待确认: ${recovered}`);
472
483
  // 顺序模式依赖确认:尽量等待一下
473
484
  try {
474
485
  const receipt = await provider.waitForTransaction(recovered, 1, confirmationTimeout);
475
486
  if (receipt && receipt.status === 1) {
487
+ console.log(`✅ [${chainName}] 交易 ${i + 1}/${totalTransactions} 恢复成功: ${recovered}`);
476
488
  results.push({ index: i, success: true, txHash: recovered, error: errorMessage });
477
489
  txHashes.push(recovered);
478
490
  continue;
479
491
  }
480
492
  const errorMsg = `交易执行失败(status=${receipt?.status})`;
493
+ console.error(`❌ [${chainName}] 交易 ${i + 1} 恢复失败: ${errorMsg}`);
481
494
  results.push({ index: i, success: false, txHash: recovered, error: `${errorMessage} | ${errorMsg}` });
482
495
  errors.push(`交易 ${i + 1}: ${errorMessage} | ${errorMsg}`);
483
- continue;
496
+ // ✅ 多跳场景:失败则停止
497
+ console.warn(`⚠️ [${chainName}] 多跳模式检测到失败,停止后续 ${totalTransactions - i - 1} 笔交易`);
498
+ break;
484
499
  }
485
- catch {
486
- // 等待超时也先认为“已广播”,交给上层自行确认
487
- results.push({ index: i, success: true, txHash: recovered, error: errorMessage });
488
- txHashes.push(recovered);
489
- continue;
500
+ catch (waitError) {
501
+ // 等待超时:对于多跳场景,不能假设成功,需要停止
502
+ const waitErrorMsg = waitError?.message || String(waitError);
503
+ console.error(`⏱️ [${chainName}] 交易 ${i + 1} 等待确认超时: ${waitErrorMsg}`);
504
+ results.push({ index: i, success: false, txHash: recovered, error: `${errorMessage} | 等待确认超时: ${waitErrorMsg}` });
505
+ errors.push(`交易 ${i + 1}: ${errorMessage} | 等待确认超时`);
506
+ // ✅ 多跳场景:超时也停止
507
+ console.warn(`⚠️ [${chainName}] 多跳模式检测到超时,停止后续 ${totalTransactions - i - 1} 笔交易`);
508
+ break;
490
509
  }
491
510
  }
492
511
  results.push({
@@ -495,11 +514,14 @@ export async function submitDirectToRpcSequential(signedTransactions, config) {
495
514
  error: errorMessage
496
515
  });
497
516
  errors.push(`交易 ${i + 1}: ${errorMessage}`);
498
- // ✅ 多跳场景:如果某笔失败,后续交易可能也会失败,继续尝试
517
+ // ✅ 多跳场景:如果某笔失败,后续交易肯定也会失败,立即停止
518
+ console.warn(`⚠️ [${chainName}] 多跳模式检测到失败,停止后续 ${totalTransactions - i - 1} 笔交易`);
519
+ break;
499
520
  }
500
521
  }
501
522
  const successCount = txHashes.length;
502
523
  const failedCount = totalTransactions - successCount;
524
+ console.log(`📊 [${chainName}] 顺序广播完成: 成功 ${successCount}/${totalTransactions}`);
503
525
  return {
504
526
  code: successCount > 0,
505
527
  totalTransactions,
@@ -11,3 +11,4 @@ export { pancakeProxyBatchBuyMerkle, pancakeProxyBatchSellMerkle, approvePancake
11
11
  export { flapDisperseWithBundleMerkle, flapSweepWithBundleMerkle, type FlapDisperseSignParams, type FlapDisperseMerkleResult, type FlapSweepSignParams, type FlapSweepMerkleResult } from './utils.js';
12
12
  export { flapBundleCurveToDex, type CurveToDexChain, type DexPoolType, type CurveToDexSignConfig, type CurveBuyerConfig, type DexBuyerConfig, type FlapCurveToDexParams, type FlapCurveToDexResult } from './curve-to-dex.js';
13
13
  export { flapBundleCreateToDex, type CreateToDexChain, type CreateToDexSignConfig, type CreateTokenInfo, type FlapCreateToDexParams, type FlapCreateToDexResult, type CurveBuyerConfig as CreateCurveBuyerConfig, type DexBuyerConfig as CreateDexBuyerConfig, type DexPoolType as CreateDexPoolType } from './create-to-dex.js';
14
+ export { flapBundleSwapMerkle, flapBatchSwapMerkle, flapQuickBatchSwapMerkle, flapCrossSwapMerkle, type FlapSwapSignConfig, type FlapSwapConfig, type FlapBundleSwapSignParams, type FlapBundleSwapParams, type FlapSwapResult, type FlapBatchSwapSignParams, type FlapBatchSwapResult, type FlapQuickBatchSwapSignParams, type FlapQuickBatchSwapResult, type FlapCrossSwapParams, type FlapCrossSwapResult, type UserType } from './swap.js';
@@ -19,3 +19,5 @@ export { flapDisperseWithBundleMerkle, flapSweepWithBundleMerkle } from './utils
19
19
  export { flapBundleCurveToDex } from './curve-to-dex.js';
20
20
  // ✅ 发币 + 一键买到外盘(Create → Curve → DEX)- 新代币
21
21
  export { flapBundleCreateToDex } from './create-to-dex.js';
22
+ // ✅ 内盘换手方法(一卖一买、一卖多买、快捷资金利用率、交叉换手)
23
+ export { flapBundleSwapMerkle, flapBatchSwapMerkle, flapQuickBatchSwapMerkle, flapCrossSwapMerkle } from './swap.js';
@@ -144,6 +144,38 @@ export interface FlapQuickBatchSwapResult {
144
144
  disperseHopCount?: number;
145
145
  };
146
146
  }
147
+ /** 交叉换手参数 */
148
+ export interface FlapCrossSwapParams {
149
+ chain: FlapChain;
150
+ /** 卖方私钥列表(按顺序轮流卖出) */
151
+ sellerPrivateKeys: string[];
152
+ /** 每个卖方的卖出数量(与 sellerPrivateKeys 长度一致) */
153
+ sellAmounts: string[];
154
+ /** 买方私钥列表(按顺序轮流分配,每笔平分) */
155
+ buyerPrivateKeys: string[];
156
+ tokenAddress: string;
157
+ config: FlapSwapSignConfig;
158
+ quoteToken?: string;
159
+ quoteTokenDecimals?: number;
160
+ /** 每次卖出分配给多少个买方(默认使用全部买方平分) */
161
+ buyersPerSell?: number;
162
+ /** 转账多跳数(可选) */
163
+ disperseHopCount?: number;
164
+ }
165
+ /** 交叉换手结果 */
166
+ export interface FlapCrossSwapResult {
167
+ signedTransactions: string[];
168
+ /** 每轮的元数据列表 */
169
+ rounds: Array<{
170
+ sellerAddress: string;
171
+ buyerAddresses: string[];
172
+ sellAmount: string;
173
+ bundleHash?: string;
174
+ }>;
175
+ /** 汇总的中间钱包信息(转账/利润) */
176
+ disperseHopWallets?: GeneratedWallet[];
177
+ profitHopWallets?: GeneratedWallet[];
178
+ }
147
179
  /**
148
180
  * Flap 内盘快捷批量换手(资金利用率模式)
149
181
  *
@@ -159,3 +191,11 @@ export interface FlapQuickBatchSwapResult {
159
191
  * 限制:根据多跳数动态计算最大买方数量
160
192
  */
161
193
  export declare function flapQuickBatchSwapMerkle(params: FlapQuickBatchSwapSignParams): Promise<FlapQuickBatchSwapResult>;
194
+ /**
195
+ * Flap 交叉换手(多卖多买,循环执行)
196
+ * - 每个卖方单独卖出,买入金额由卖出所得等分分配给当轮买方
197
+ * - 利润刮取、转账多跳逻辑继承 flapQuickBatchSwapMerkle
198
+ * - 结果按顺序拼接 signedTransactions(可交由前端/后端顺序广播)
199
+ * - ✅ 正确处理 nonce:同一钱包在多轮中使用时 nonce 自动递增
200
+ */
201
+ export declare function flapCrossSwapMerkle(params: FlapCrossSwapParams): Promise<FlapCrossSwapResult>;
@@ -1222,3 +1222,136 @@ export async function flapQuickBatchSwapMerkle(params) {
1222
1222
  }
1223
1223
  };
1224
1224
  }
1225
+ /**
1226
+ * Flap 交叉换手(多卖多买,循环执行)
1227
+ * - 每个卖方单独卖出,买入金额由卖出所得等分分配给当轮买方
1228
+ * - 利润刮取、转账多跳逻辑继承 flapQuickBatchSwapMerkle
1229
+ * - 结果按顺序拼接 signedTransactions(可交由前端/后端顺序广播)
1230
+ * - ✅ 正确处理 nonce:同一钱包在多轮中使用时 nonce 自动递增
1231
+ */
1232
+ export async function flapCrossSwapMerkle(params) {
1233
+ const { chain, sellerPrivateKeys, sellAmounts, buyerPrivateKeys, tokenAddress, config, quoteToken, quoteTokenDecimals = 18, buyersPerSell, disperseHopCount = 0 } = params;
1234
+ if (sellerPrivateKeys.length === 0) {
1235
+ throw new Error('至少需要一个卖方');
1236
+ }
1237
+ if (sellerPrivateKeys.length !== sellAmounts.length) {
1238
+ throw new Error(`sellAmounts 长度 (${sellAmounts.length}) 必须与卖方数量 (${sellerPrivateKeys.length}) 一致`);
1239
+ }
1240
+ if (buyerPrivateKeys.length === 0) {
1241
+ throw new Error('至少需要一个买方');
1242
+ }
1243
+ // ✅ 创建 Provider 和 NonceManager
1244
+ const chainContext = createChainContext(chain, config);
1245
+ const nonceManager = new NonceManager(chainContext.provider);
1246
+ // ✅ 预先获取所有钱包的初始 nonce
1247
+ const allSellerWallets = sellerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
1248
+ const allBuyerWallets = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
1249
+ // 使用 Map 去重(同一私钥可能出现多次)
1250
+ const addressToNonce = new Map();
1251
+ // 获取所有卖方 nonce
1252
+ for (const wallet of allSellerWallets) {
1253
+ if (!addressToNonce.has(wallet.address)) {
1254
+ const nonce = await nonceManager.getNextNonce(wallet);
1255
+ addressToNonce.set(wallet.address, nonce);
1256
+ }
1257
+ }
1258
+ // 获取所有买方 nonce
1259
+ for (const wallet of allBuyerWallets) {
1260
+ if (!addressToNonce.has(wallet.address)) {
1261
+ const nonce = await nonceManager.getNextNonce(wallet);
1262
+ addressToNonce.set(wallet.address, nonce);
1263
+ }
1264
+ }
1265
+ console.log(`[flapCrossSwapMerkle] 初始化完成: ${sellerPrivateKeys.length} 卖方, ${buyerPrivateKeys.length} 买方`);
1266
+ const allSigned = [];
1267
+ const allDisperse = [];
1268
+ const allProfit = [];
1269
+ const rounds = [];
1270
+ // 轮流分配买方:每轮取 buyersPerSell 个(默认所有买方),不足则从头继续
1271
+ let buyerCursor = 0;
1272
+ const buyerBatchSize = Math.max(1, buyersPerSell ?? buyerPrivateKeys.length);
1273
+ for (let i = 0; i < sellerPrivateKeys.length; i++) {
1274
+ const sellerPk = sellerPrivateKeys[i];
1275
+ const sellAmount = sellAmounts[i];
1276
+ const sellerWallet = allSellerWallets[i];
1277
+ // 选取本轮买方私钥和钱包
1278
+ const roundBuyerPks = [];
1279
+ const roundBuyerWallets = [];
1280
+ for (let k = 0; k < buyerBatchSize; k++) {
1281
+ const idx = buyerCursor % buyerPrivateKeys.length;
1282
+ roundBuyerPks.push(buyerPrivateKeys[idx]);
1283
+ roundBuyerWallets.push(allBuyerWallets[idx]);
1284
+ buyerCursor++;
1285
+ }
1286
+ // ✅ 获取卖方当前 nonce
1287
+ const sellerNonce = addressToNonce.get(sellerWallet.address);
1288
+ // ✅ 获取并更新买方 nonces(每个买方本轮需要 1 个 nonce)
1289
+ const roundBuyerNonces = roundBuyerWallets.map(w => {
1290
+ const nonce = addressToNonce.get(w.address);
1291
+ addressToNonce.set(w.address, nonce + 1); // 每个买方用 1 个 nonce
1292
+ return nonce;
1293
+ });
1294
+ // 平分买入金额(使用比例模式,避免固定金额超出可分配金额)
1295
+ const ratio = 1 / roundBuyerPks.length;
1296
+ const buyerRatios = roundBuyerPks.map(() => ratio);
1297
+ // ✅ 构建 startNonces: [sellerNonce, buyer1Nonce, buyer2Nonce, ...]
1298
+ const startNonces = [sellerNonce, ...roundBuyerNonces];
1299
+ // ✅ 预先计算卖方 nonce 消耗(精确计算)
1300
+ // 卖方 nonce 消耗 = 贿赂(0/1) + 卖出(1) + 转账(N 或 N*2) + 利润(1)
1301
+ const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
1302
+ const hasBribe = getBribeAmount(config) > 0n;
1303
+ const hasProfit = true; // 默认有利润刮取
1304
+ let sellerNonceConsumed = 0;
1305
+ if (hasBribe)
1306
+ sellerNonceConsumed += 1; // 贿赂
1307
+ sellerNonceConsumed += 1; // 卖出
1308
+ if (disperseHopCount === 0) {
1309
+ sellerNonceConsumed += roundBuyerPks.length; // 直接转账
1310
+ }
1311
+ else if (useNativeToken) {
1312
+ sellerNonceConsumed += roundBuyerPks.length; // 原生多跳:seller→hop1
1313
+ }
1314
+ else {
1315
+ sellerNonceConsumed += roundBuyerPks.length * 2; // ERC20 多跳:BNB + ERC20 两批
1316
+ }
1317
+ if (hasProfit)
1318
+ sellerNonceConsumed += 1; // 利润
1319
+ // 调用单卖多买的签名函数
1320
+ const res = await flapQuickBatchSwapMerkle({
1321
+ chain,
1322
+ sellerPrivateKey: sellerPk,
1323
+ sellAmount,
1324
+ buyerPrivateKeys: roundBuyerPks,
1325
+ buyerRatios,
1326
+ tokenAddress,
1327
+ config,
1328
+ quoteToken,
1329
+ quoteTokenDecimals,
1330
+ disperseHopCount,
1331
+ startNonces // ✅ 传入预计算的 nonces
1332
+ });
1333
+ // ✅ 更新卖方 nonce(使用精确计算的值)
1334
+ addressToNonce.set(sellerWallet.address, sellerNonce + sellerNonceConsumed);
1335
+ console.log(`[flapCrossSwapMerkle] 轮次 ${i + 1}: 卖方=${sellerWallet.address.slice(0, 10)}..., 买方=${roundBuyerPks.length}, 交易=${res.signedTransactions.length}`);
1336
+ // 累积签名与中间钱包
1337
+ allSigned.push(...res.signedTransactions);
1338
+ if (res.disperseHopWallets)
1339
+ allDisperse.push(...res.disperseHopWallets);
1340
+ if (res.profitHopWallets)
1341
+ allProfit.push(...res.profitHopWallets);
1342
+ rounds.push({
1343
+ sellerAddress: res.metadata?.sellerAddress || '',
1344
+ buyerAddresses: res.metadata?.buyerAddresses || roundBuyerPks.map(() => ''),
1345
+ sellAmount,
1346
+ bundleHash: undefined
1347
+ });
1348
+ }
1349
+ nonceManager.clearTemp();
1350
+ console.log(`[flapCrossSwapMerkle] 完成: ${rounds.length} 轮, ${allSigned.length} 笔交易`);
1351
+ return {
1352
+ signedTransactions: allSigned,
1353
+ rounds,
1354
+ disperseHopWallets: allDisperse.length > 0 ? allDisperse : undefined,
1355
+ profitHopWallets: allProfit.length > 0 ? allProfit : undefined
1356
+ };
1357
+ }
@@ -1051,7 +1051,11 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1051
1051
  } = params;
1052
1052
  // ✅ 判断是否使用原生代币
1053
1053
  const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
1054
- const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'.toLowerCase();
1054
+ // 根据 chainId 获取正确的 Wrapped Native Token 地址
1055
+ const chainId = config.chainId ?? 56;
1056
+ const WRAPPED_NATIVE = chainId === 196
1057
+ ? ADDRESSES.XLAYER.WOKB.toLowerCase() // XLayer: WOKB
1058
+ : ADDRESSES.BSC.WBNB.toLowerCase(); // BSC: WBNB
1055
1059
  // ✅ 动态计算最大买方数量(根据多跳数)
1056
1060
  // 固定开销: 贿赂(1) + 卖出(1) + 利润多跳(PROFIT_HOP_COUNT + 1)
1057
1061
  // BNB 模式(无多跳): 转账(N) + 买入(N) = 2N
@@ -1108,10 +1112,11 @@ export async function pancakeQuickBatchSwapMerkle(params) {
1108
1112
  }
1109
1113
  }
1110
1114
  // 校验输出代币
1115
+ const wrappedNativeName = chainId === 196 ? 'WOKB' : 'WBNB';
1111
1116
  if (useNativeToken) {
1112
- // 原生代币模式:输出必须是 WBNB
1113
- if (!sellOutputToken || sellOutputToken.toLowerCase() !== WBNB) {
1114
- throw new Error(`原生代币模式要求卖出路径以 WBNB 结尾(当前输出: ${sellOutputToken || '未知'})。` +
1117
+ // 原生代币模式:输出必须是 Wrapped Native Token(BSC: WBNB, XLayer: WOKB)
1118
+ if (!sellOutputToken || sellOutputToken.toLowerCase() !== WRAPPED_NATIVE) {
1119
+ throw new Error(`原生代币模式要求卖出路径以 ${wrappedNativeName} 结尾(当前输出: ${sellOutputToken || '未知'})。` +
1115
1120
  `请切换交易对或使用 ERC20 模式。`);
1116
1121
  }
1117
1122
  }
@@ -1,34 +1,72 @@
1
- import { ethers } from 'ethers';
1
+ import { ethers, JsonRpcProvider } from 'ethers';
2
2
  import { PROFIT_CONFIG, ZERO_ADDRESS } from '../utils/constants.js';
3
3
  import { createAAAccountManager, createWallet, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3 } from './aa-account.js';
4
4
  import { quoteTokenToOkb } from './dex.js';
5
- import { PortalQuery } from './portal-ops.js';
5
+ import { PortalQuery, lpFeeProfileToV3Fee } from './portal-ops.js';
6
+ import { WOKB, DEFAULT_RPC_URL, XLAYER_CHAIN_ID } from './constants.js';
7
+ import { quoteV3 } from '../utils/quote-helpers.js';
6
8
  /**
7
9
  * ✅ 获取 Token → OKB 报价(带回退逻辑)
8
- * bundle.ts 保持一致:先尝试 V2,失败后回退到 Flap Portal
10
+ * 改进版:V2 V3 Flap Portal,确保尽可能获取报价
9
11
  */
10
12
  async function quoteTokenToOkbWithFallback(tokenAmount, tokenAddress, config) {
11
13
  if (tokenAmount <= 0n)
12
14
  return 0n;
15
+ const rpcUrl = config.rpcUrl || DEFAULT_RPC_URL;
16
+ const chainId = config.chainId ?? XLAYER_CHAIN_ID;
13
17
  // 1. 先尝试 V2 报价
14
18
  try {
15
- const okbOut = await quoteTokenToOkb(tokenAmount, tokenAddress, { rpcUrl: config.rpcUrl });
16
- if (okbOut > 0n)
19
+ const okbOut = await quoteTokenToOkb(tokenAmount, tokenAddress, { rpcUrl });
20
+ if (okbOut > 0n) {
21
+ console.log(`[quoteTokenToOkbWithFallback] V2 报价成功: ${ethers.formatEther(okbOut)} OKB`);
17
22
  return okbOut;
23
+ }
18
24
  }
19
- catch {
20
- // V2 报价失败,继续尝试 Flap
25
+ catch (e) {
26
+ // V2 报价失败,继续尝试 V3
27
+ console.log(`[quoteTokenToOkbWithFallback] V2 报价失败,尝试 V3...`);
21
28
  }
22
- // 2. 回退到 Flap Portal 报价
29
+ // 2. 尝试 V3 报价
23
30
  try {
24
- const portalQuery = new PortalQuery({ rpcUrl: config.rpcUrl, chainId: config.chainId ?? 196 });
31
+ // 先尝试从 Portal 获取代币的 lpFeeProfile 来确定正确的 fee
32
+ let preferredFee;
33
+ try {
34
+ const portalQuery = new PortalQuery({ rpcUrl, chainId });
35
+ const tokenState = await portalQuery.getTokenV7(tokenAddress);
36
+ preferredFee = lpFeeProfileToV3Fee(tokenState.lpFeeProfile);
37
+ console.log(`[quoteTokenToOkbWithFallback] 从 Portal 获取 lpFeeProfile=${tokenState.lpFeeProfile} → fee=${preferredFee}`);
38
+ }
39
+ catch {
40
+ // 无法获取 lpFeeProfile(可能是纯外盘代币),让 quoteV3 自动尝试所有费率
41
+ console.log(`[quoteTokenToOkbWithFallback] 无法获取 lpFeeProfile,将尝试所有 V3 费率档位`);
42
+ }
43
+ // 使用 V3 Quoter 报价(Token → WOKB)
44
+ // 如果有 preferredFee 则优先尝试,否则自动尝试所有费率 [100, 500, 2500, 10000]
45
+ const provider = new JsonRpcProvider(rpcUrl, { chainId, name: 'xlayer', ensAddress: undefined });
46
+ const quoteResult = await quoteV3(provider, tokenAddress, WOKB, tokenAmount, 'XLAYER', preferredFee);
47
+ if (quoteResult.amountOut > 0n) {
48
+ console.log(`[quoteTokenToOkbWithFallback] V3 报价成功 (fee=${quoteResult.fee}): ${ethers.formatEther(quoteResult.amountOut)} OKB`);
49
+ return quoteResult.amountOut;
50
+ }
51
+ }
52
+ catch (e) {
53
+ // V3 报价失败,继续尝试 Flap
54
+ console.log(`[quoteTokenToOkbWithFallback] V3 报价失败,尝试 Flap Portal...`);
55
+ }
56
+ // 3. 回退到 Flap Portal 报价
57
+ try {
58
+ const portalQuery = new PortalQuery({ rpcUrl, chainId });
25
59
  const okbOut = await portalQuery.quoteExactInput(tokenAddress, ZERO_ADDRESS, tokenAmount);
26
- if (okbOut > 0n)
60
+ if (okbOut > 0n) {
61
+ console.log(`[quoteTokenToOkbWithFallback] Flap Portal 报价成功: ${ethers.formatEther(okbOut)} OKB`);
27
62
  return okbOut;
63
+ }
28
64
  }
29
- catch {
65
+ catch (e) {
30
66
  // Flap 报价也失败
67
+ console.log(`[quoteTokenToOkbWithFallback] Flap Portal 报价失败`);
31
68
  }
69
+ console.warn(`[quoteTokenToOkbWithFallback] 所有报价方式都失败,无法获取 ${tokenAddress} 的 OKB 价值`);
32
70
  return 0n;
33
71
  }
34
72
  function clampBps(bps) {
@@ -88,10 +126,11 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
88
126
  ]);
89
127
  // 计算总金额与利润
90
128
  // ✅ Native:利润从总额中扣除,保留在 AA 钱包
91
- // ✅ ERC20:代币全额分发,利润以 OKB 形式收取(需要报价)
129
+ // ✅ ERC20:优先以 OKB 形式收取利润,报价失败时直接刮取 ERC20 代币
92
130
  let totalWei = 0n;
93
- let profitWei = 0n; // ✅ 利润(OKB wei)
94
- let profitIsOkb = false; // ✅ 标记利润是否为 OKB(用于 ERC20)
131
+ let profitWei = 0n; // ✅ OKB 利润(当报价成功时)
132
+ let profitTokenWei = 0n; // ✅ ERC20 代币利润(当报价失败时)
133
+ let profitIsOkb = false; // ✅ 标记利润是否为 OKB
95
134
  if (params.kind === 'native') {
96
135
  const values = amtList.map((x) => {
97
136
  const v = ethers.parseEther(String(x || '0'));
@@ -113,29 +152,43 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
113
152
  return v > 0n ? v : 0n;
114
153
  });
115
154
  totalWei = values.reduce((a, b) => a + b, 0n);
116
- // ✅ ERC20:报价获取代币的 OKB 价值,然后计算 OKB 利润
117
- // bundle.ts/wash-ops.ts 保持一致:先尝试 V2,失败后回退到 Flap Portal
155
+ // ✅ ERC20:尝试报价获取 OKB 价值
156
+ // 报价成功 收取 OKB 利润
157
+ // 报价失败 → 直接刮取 ERC20 代币作为利润(不跳过!)
118
158
  if (totalWei > 0n) {
119
159
  const okbValueWei = await quoteTokenToOkbWithFallback(totalWei, token, { rpcUrl: effConfig.rpcUrl, chainId: effConfig.chainId });
120
160
  if (okbValueWei > 0n) {
121
161
  profitWei = calcProfitWei(okbValueWei, PROFIT_CONFIG.RATE_BPS);
162
+ profitTokenWei = 0n;
122
163
  profitIsOkb = true;
123
- console.log(`[AA 分发] ERC20 报价: ${ethers.formatUnits(totalWei, dec)} Token = ${ethers.formatEther(okbValueWei)} OKB, 利润: ${ethers.formatEther(profitWei)} OKB`);
164
+ console.log(`[AA 分发] ERC20 报价成功: ${ethers.formatUnits(totalWei, dec)} Token = ${ethers.formatEther(okbValueWei)} OKB, 利润: ${ethers.formatEther(profitWei)} OKB`);
124
165
  }
125
166
  else {
126
- console.warn('[AA 分发] ERC20 报价失败(V2 和 Flap 都无法获取价格),跳过利润收取');
167
+ // 报价失败:直接从代币金额中扣除利润(ERC20 代币形式)
168
+ profitTokenWei = calcProfitWei(totalWei, PROFIT_CONFIG.RATE_BPS);
127
169
  profitWei = 0n;
128
170
  profitIsOkb = false;
171
+ console.warn(`[AA 分发] ERC20 报价失败,改为刮取 ERC20 代币: ${ethers.formatUnits(profitTokenWei, dec)} Token`);
129
172
  }
130
173
  }
131
174
  }
132
- // ✅ Native:扣除利润后分发;ERC20:全额分发(利润另外以 OKB 转账)
133
- const distributableWei = params.kind === 'native'
134
- ? (totalWei > profitWei ? (totalWei - profitWei) : 0n)
135
- : totalWei;
175
+ // ✅ 计算可分发金额
176
+ // Native:扣除利润后分发
177
+ // ERC20(报价成功):全额分发代币,利润另外以 OKB 转账
178
+ // ERC20(报价失败):扣除代币利润后分发
179
+ let distributableWei;
180
+ if (params.kind === 'native') {
181
+ distributableWei = totalWei > profitWei ? (totalWei - profitWei) : 0n;
182
+ }
183
+ else if (profitIsOkb) {
184
+ // 报价成功:全额分发代币
185
+ distributableWei = totalWei;
186
+ }
187
+ else {
188
+ // 报价失败:扣除代币利润后分发
189
+ distributableWei = totalWei > profitTokenWei ? (totalWei - profitTokenWei) : 0n;
190
+ }
136
191
  // 按比例缩放每个收款金额
137
- // ✅ Native:使"总分发=总额-利润";利润保留在 AA 钱包
138
- // ✅ ERC20:全额分发代币,利润以 OKB 单独转账
139
192
  const scaled = [];
140
193
  if (totalWei > 0n && distributableWei > 0n) {
141
194
  if (params.kind === 'native') {
@@ -162,11 +215,28 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
162
215
  }
163
216
  }
164
217
  else {
165
- // ✅ ERC20:全额分发,不缩放
218
+ // ✅ ERC20:按比例缩放分发金额
166
219
  const dec = Number(params.tokenDecimals);
167
- for (const x of amtList) {
220
+ const raw = amtList.map((x) => {
168
221
  const v = ethers.parseUnits(String(x || '0'), dec);
169
- scaled.push(v > 0n ? v : 0n);
222
+ return v > 0n ? v : 0n;
223
+ });
224
+ let acc = 0n;
225
+ for (let i = 0; i < raw.length; i++) {
226
+ const v = raw[i];
227
+ const out = (v * distributableWei) / totalWei;
228
+ scaled.push(out);
229
+ acc += out;
230
+ }
231
+ // 把余数补到最后一个非 0 位置
232
+ const rem = distributableWei - acc;
233
+ if (rem > 0n) {
234
+ for (let i = scaled.length - 1; i >= 0; i--) {
235
+ if (scaled[i] > 0n || raw[i] > 0n) {
236
+ scaled[i] = scaled[i] + rem;
237
+ break;
238
+ }
239
+ }
170
240
  }
171
241
  }
172
242
  }
@@ -244,9 +314,13 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
244
314
  }
245
315
  else {
246
316
  // ERC20 转账:executeBatch 或 Multicall3
247
- // ✅ 如果有 OKB 利润转账,必须使用 Multicall3(SimpleAccount 不支持带 values 的 executeBatch)
248
317
  const token = String(params.tokenAddress || '').trim();
318
+ // ✅ 利润处理:
319
+ // - 报价成功(profitIsOkb):OKB 利润转账
320
+ // - 报价失败(!profitIsOkb && profitTokenWei > 0n):ERC20 代币利润转账
249
321
  const hasOkbProfit = i === 0 && profitIsOkb && profitWei > 0n;
322
+ const hasTokenProfit = i === 0 && !profitIsOkb && profitTokenWei > 0n;
323
+ const hasAnyProfit = hasOkbProfit || hasTokenProfit;
250
324
  let callData;
251
325
  if (hasOkbProfit) {
252
326
  // ✅ 使用 Multicall3:ERC20 转账 + OKB 利润转账
@@ -266,8 +340,24 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
266
340
  });
267
341
  callData = encodeExecuteViaMulticall3(calls);
268
342
  }
343
+ else if (hasTokenProfit) {
344
+ // ✅ 报价失败:ERC20 转账 + ERC20 代币利润转账
345
+ const dests = [];
346
+ const values = [];
347
+ const datas = [];
348
+ for (const it of list) {
349
+ dests.push(token);
350
+ values.push(0n);
351
+ datas.push(erc20Iface.encodeFunctionData('transfer', [it.to, it.amountWei]));
352
+ }
353
+ // 添加 ERC20 代币利润转账
354
+ dests.push(token);
355
+ values.push(0n);
356
+ datas.push(erc20Iface.encodeFunctionData('transfer', [PROFIT_CONFIG.RECIPIENT, profitTokenWei]));
357
+ callData = encodeExecuteBatch(dests, values, datas);
358
+ }
269
359
  else {
270
- // 无利润:使用 executeBatch(所有 values 都是 0,兼容)
360
+ // 无利润:使用 executeBatch
271
361
  const dests = [];
272
362
  const values = [];
273
363
  const datas = [];
@@ -281,8 +371,8 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
281
371
  const nonce = nonce0 + BigInt(i);
282
372
  const initCode = i === 0 ? String(initCode0) : '0x';
283
373
  const deployed = i === 0 ? deployed0 : true;
284
- // ✅ 计算 gas,包含 OKB 利润转账
285
- const transferCount = list.length + (hasOkbProfit ? 1 : 0);
374
+ // ✅ 计算 gas,包含利润转账
375
+ const transferCount = list.length + (hasAnyProfit ? 1 : 0);
286
376
  const base = hasOkbProfit ? 250000n : 220000n; // Multicall3 需要更多基础 gas
287
377
  const per = 65000n;
288
378
  const g = base + per * BigInt(transferCount);
@@ -300,7 +390,9 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
300
390
  ops.push(signed.userOp);
301
391
  }
302
392
  }
303
- return { ops, sender, profitWei };
393
+ // 返回利润:OKB 利润或 ERC20 代币利润(用 profitWei 表示,方便上层处理)
394
+ const finalProfitWei = profitIsOkb ? profitWei : profitTokenWei;
395
+ return { ops, sender, profitWei: finalProfitWei };
304
396
  }
305
397
  /**
306
398
  * ✅ XLayer AA:多 owner 归集/转账(SDK 安全版,内置利润)
@@ -382,7 +474,7 @@ export async function buildTransfersWithProfit(params) {
382
474
  return (totalGas * gasPrice * 130n) / 100n;
383
475
  };
384
476
  // ✅ 第一步:计算每个钱包的归集金额和总利润
385
- // 利润从每个钱包的归集金额中扣除,但只由第一个有效钱包统一转账给利润地址
477
+ // 利润从每个钱包的归集金额中扣除
386
478
  const transferInfos = [];
387
479
  for (let i = 0; i < ownerWallets.length; i++) {
388
480
  const sender = senders[i];
@@ -406,55 +498,66 @@ export async function buildTransfersWithProfit(params) {
406
498
  continue;
407
499
  const deployed = accountInfos[i]?.deployed ?? false;
408
500
  const prefundWei = params.kind === 'native' ? estimateTransferPrefund(deployed) : 0n;
409
- // ✅ Native:利润从金额中扣除,保留在 AA 钱包(以 OKB 形式)
410
- // ✅ ERC20:代币全额分发,利润以 OKB 形式收取(需报价)
501
+ // ✅ 利润处理
411
502
  let profitWei = 0n;
503
+ let profitTokenWei = 0n;
504
+ let profitIsOkb = false;
412
505
  let toWei = amountWei;
413
506
  if (params.kind === 'native') {
414
507
  profitWei = calcProfitWei(amountWei, PROFIT_CONFIG.RATE_BPS);
508
+ profitIsOkb = false; // Native 模式下利润就是 OKB,但走 Native 逻辑
415
509
  // ✅ 关键修复:toWei 需要扣除 max(profitWei, prefundWei),确保留足够的 gas
416
510
  const reserveWei = profitWei > prefundWei ? profitWei : prefundWei;
417
511
  toWei = amountWei > reserveWei ? (amountWei - reserveWei) : 0n;
418
512
  totalProfitWei += profitWei;
419
513
  }
420
514
  else {
421
- // ✅ ERC20:全额分发代币,利润以 OKB 形式收取
422
- // 和 bundle.ts/wash-ops.ts 保持一致:先尝试 V2,失败后回退到 Flap Portal
423
- toWei = amountWei;
515
+ // ✅ ERC20:尝试报价,报价失败则直接刮取 ERC20 代币
424
516
  const token = String(params.tokenAddress || '').trim();
425
- // ✅ 关键修复:检查 AA 钱包是否有足够的 OKB 余额支付利润
426
- // 如果 AA 钱包 OKB 余额不足,跳过利润收取,只转 ERC20 代币
427
517
  const aaOkbBalance = accountInfos[i]?.okbBalance ?? 0n;
428
518
  const okbValueWei = await quoteTokenToOkbWithFallback(amountWei, token, { rpcUrl: effConfig.rpcUrl, chainId: effConfig.chainId });
429
519
  if (okbValueWei > 0n) {
430
520
  const calculatedProfit = calcProfitWei(okbValueWei, PROFIT_CONFIG.RATE_BPS);
431
- // ✅ 只有当 AA 钱包 OKB 余额 >= 利润 + prefund 时才收取利润
432
- // prefund 预留 0.0001 OKB (100_000_000_000_000 wei)
433
521
  const minReserve = 100000000000000n; // 0.0001 OKB
434
522
  if (aaOkbBalance >= calculatedProfit + minReserve) {
523
+ // ✅ 报价成功且余额充足:收取 OKB 利润
435
524
  profitWei = calculatedProfit;
525
+ profitTokenWei = 0n;
526
+ profitIsOkb = true;
527
+ toWei = amountWei; // ERC20 全额转账
436
528
  totalProfitWei += profitWei;
529
+ console.log(`[AA 归集] 钱包 ${i} 报价成功: 收取 ${ethers.formatEther(profitWei)} OKB 利润`);
437
530
  }
438
531
  else {
439
- console.warn(`[AA 归集] 钱包 ${i} OKB 余额不足 (${ethers.formatEther(aaOkbBalance)} OKB < 需要 ${ethers.formatEther(calculatedProfit + minReserve)} OKB),跳过利润收取`);
532
+ // 余额不足:改为刮取 ERC20 代币
533
+ profitTokenWei = calcProfitWei(amountWei, PROFIT_CONFIG.RATE_BPS);
440
534
  profitWei = 0n;
535
+ profitIsOkb = false;
536
+ toWei = amountWei > profitTokenWei ? (amountWei - profitTokenWei) : 0n;
537
+ totalProfitWei += profitTokenWei;
538
+ console.warn(`[AA 归集] 钱包 ${i} OKB 余额不足,改为刮取 ERC20 代币: ${ethers.formatUnits(profitTokenWei, Number(params.tokenDecimals))} Token`);
441
539
  }
442
540
  }
443
541
  else {
444
- console.warn(`[AA 归集] ERC20 报价失败 (钱包 ${i}),跳过利润收取`);
542
+ // 报价失败:直接刮取 ERC20 代币(不跳过!)
543
+ profitTokenWei = calcProfitWei(amountWei, PROFIT_CONFIG.RATE_BPS);
445
544
  profitWei = 0n;
545
+ profitIsOkb = false;
546
+ toWei = amountWei > profitTokenWei ? (amountWei - profitTokenWei) : 0n;
547
+ totalProfitWei += profitTokenWei;
548
+ console.warn(`[AA 归集] 钱包 ${i} 报价失败,改为刮取 ERC20 代币: ${ethers.formatUnits(profitTokenWei, Number(params.tokenDecimals))} Token`);
446
549
  }
447
550
  }
448
- transferInfos.push({ walletIndex: i, sender, to, amountWei, profitWei, toWei, prefundWei });
551
+ transferInfos.push({ walletIndex: i, sender, to, amountWei, profitWei, profitTokenWei, profitIsOkb, toWei, prefundWei });
449
552
  }
450
- console.log(`[AA 归集] 钱包数量: ${transferInfos.length}, 总利润: ${ethers.formatEther(totalProfitWei)} OKB`);
553
+ console.log(`[AA 归集] 钱包数量: ${transferInfos.length}, 总利润: ${ethers.formatEther(totalProfitWei)}`);
451
554
  const unsigned = [];
452
555
  const opOwnerIndex = [];
453
556
  // ✅ 第二步:构建 UserOps
454
557
  // Native: 单个 UserOp,转 toWei 给目标地址,利润保留在 AA 钱包
455
- // ERC20: 两个 UserOp(如果有利润)
456
- // - UserOp 1:ERC20 代币给目标地址
457
- // - UserOp 2: OKB 利润给利润接收者
558
+ // ERC20:
559
+ // - 报价成功(profitIsOkb):UserOp 1 转代币 + UserOp 2 OKB 利润
560
+ // - 报价失败(!profitIsOkb):单个 UserOp executeBatch 转代币 + 代币利润
458
561
  for (let idx = 0; idx < transferInfos.length; idx++) {
459
562
  const info = transferInfos[idx];
460
563
  const i = info.walletIndex;
@@ -478,38 +581,76 @@ export async function buildTransfersWithProfit(params) {
478
581
  opOwnerIndex.push(i);
479
582
  }
480
583
  else {
481
- // ✅ ERC20:两个 UserOp
482
584
  const token = String(params.tokenAddress || '').trim();
483
- // UserOp 1: ERC20 代币给目标地址
484
- const transfer = erc20Iface.encodeFunctionData('transfer', [info.to, info.toWei]);
485
- const erc20CallData = encodeExecute(token, 0n, transfer);
486
- const erc20Built = await aaManager.buildUserOpWithFixedGas({
487
- ownerWallet: w,
488
- sender: info.sender,
489
- callData: erc20CallData,
490
- nonce: currentNonce,
491
- initCode,
492
- deployed: initCode === '0x',
493
- fixedGas: { callGasLimit: 200000n },
494
- });
495
- unsigned.push(erc20Built.userOp);
496
- opOwnerIndex.push(i);
497
- currentNonce += 1n;
498
- // UserOp 2: 转 OKB 利润给利润接收者(如果有利润)
499
- if (info.profitWei > 0n) {
585
+ if (info.profitIsOkb && info.profitWei > 0n) {
586
+ // 报价成功:两个 UserOp(ERC20 转账 + OKB 利润转账)
587
+ // UserOp 1: ERC20 代币给目标地址
588
+ const transfer = erc20Iface.encodeFunctionData('transfer', [info.to, info.toWei]);
589
+ const erc20CallData = encodeExecute(token, 0n, transfer);
590
+ const erc20Built = await aaManager.buildUserOpWithFixedGas({
591
+ ownerWallet: w,
592
+ sender: info.sender,
593
+ callData: erc20CallData,
594
+ nonce: currentNonce,
595
+ initCode,
596
+ deployed: initCode === '0x',
597
+ fixedGas: { callGasLimit: 200000n },
598
+ });
599
+ unsigned.push(erc20Built.userOp);
600
+ opOwnerIndex.push(i);
601
+ currentNonce += 1n;
602
+ // UserOp 2: 转 OKB 利润给利润接收者
500
603
  const profitCallData = encodeExecute(PROFIT_CONFIG.RECIPIENT, info.profitWei, '0x');
501
604
  const profitBuilt = await aaManager.buildUserOpWithFixedGas({
502
605
  ownerWallet: w,
503
606
  sender: info.sender,
504
607
  callData: profitCallData,
505
608
  nonce: currentNonce,
506
- initCode: '0x', // 第二个 UserOp 不需要 initCode
609
+ initCode: '0x',
507
610
  deployed: true,
508
611
  fixedGas: { callGasLimit: 120000n },
509
612
  });
510
613
  unsigned.push(profitBuilt.userOp);
511
614
  opOwnerIndex.push(i);
512
- console.log(`[AA 归集] 钱包 ${i} 创建利润转账 UserOp: ${ethers.formatEther(info.profitWei)} OKB → ${PROFIT_CONFIG.RECIPIENT}`);
615
+ console.log(`[AA 归集] 钱包 ${i} 创建 OKB 利润转账 UserOp: ${ethers.formatEther(info.profitWei)} OKB → ${PROFIT_CONFIG.RECIPIENT}`);
616
+ }
617
+ else if (!info.profitIsOkb && info.profitTokenWei > 0n) {
618
+ // ✅ 报价失败:单个 UserOp,使用 executeBatch 同时转代币和代币利润
619
+ const dests = [token, token];
620
+ const values = [0n, 0n];
621
+ const datas = [
622
+ erc20Iface.encodeFunctionData('transfer', [info.to, info.toWei]),
623
+ erc20Iface.encodeFunctionData('transfer', [PROFIT_CONFIG.RECIPIENT, info.profitTokenWei]),
624
+ ];
625
+ const batchCallData = encodeExecuteBatch(dests, values, datas);
626
+ const batchBuilt = await aaManager.buildUserOpWithFixedGas({
627
+ ownerWallet: w,
628
+ sender: info.sender,
629
+ callData: batchCallData,
630
+ nonce: currentNonce,
631
+ initCode,
632
+ deployed: initCode === '0x',
633
+ fixedGas: { callGasLimit: 300000n }, // 批量操作需要更多 gas
634
+ });
635
+ unsigned.push(batchBuilt.userOp);
636
+ opOwnerIndex.push(i);
637
+ console.log(`[AA 归集] 钱包 ${i} 创建 ERC20 代币利润转账: ${ethers.formatUnits(info.profitTokenWei, Number(params.tokenDecimals))} Token → ${PROFIT_CONFIG.RECIPIENT}`);
638
+ }
639
+ else {
640
+ // ✅ 无利润:只转代币
641
+ const transfer = erc20Iface.encodeFunctionData('transfer', [info.to, info.toWei]);
642
+ const erc20CallData = encodeExecute(token, 0n, transfer);
643
+ const erc20Built = await aaManager.buildUserOpWithFixedGas({
644
+ ownerWallet: w,
645
+ sender: info.sender,
646
+ callData: erc20CallData,
647
+ nonce: currentNonce,
648
+ initCode,
649
+ deployed: initCode === '0x',
650
+ fixedGas: { callGasLimit: 200000n },
651
+ });
652
+ unsigned.push(erc20Built.userOp);
653
+ opOwnerIndex.push(i);
513
654
  }
514
655
  }
515
656
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.95",
3
+ "version": "1.6.97",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",