four-flap-meme-sdk 1.6.16 → 1.6.18

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.
@@ -19,6 +19,8 @@ export declare function buildDisperseFromSingleOwnerOpsWithProfit(params: {
19
19
  startNonce?: bigint;
20
20
  assumeDeployed?: boolean;
21
21
  maxTransfersPerUserOp?: number;
22
+ /** ✅ 多跳中间层:跳过利润扣除(只在最后一跳扣利润) */
23
+ skipProfit?: boolean;
22
24
  }): Promise<{
23
25
  ops: UserOperation[];
24
26
  sender: string;
@@ -41,6 +43,8 @@ export declare function buildTransfersWithProfit(params: {
41
43
  toAddresses: string[];
42
44
  amounts: string[];
43
45
  config?: Partial<XLayerConfig>;
46
+ /** ✅ 多跳中间层:跳过利润扣除(只在最后一跳扣利润) */
47
+ skipProfit?: boolean;
44
48
  }): Promise<{
45
49
  ops: UserOperation[];
46
50
  senders: string[];
@@ -78,9 +78,11 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
78
78
  });
79
79
  totalWei = values.reduce((a, b) => a + b, 0n);
80
80
  }
81
- const profitWei = calcProfitWei(totalWei, PROFIT_CONFIG.RATE_BPS);
81
+ // 多跳中间层:skipProfit=true 时不扣利润,全额转给目标
82
+ const skipProfit = params.skipProfit === true;
83
+ const profitWei = skipProfit ? 0n : calcProfitWei(totalWei, PROFIT_CONFIG.RATE_BPS);
82
84
  const distributableWei = totalWei > profitWei ? (totalWei - profitWei) : 0n;
83
- // 按比例缩放每个收款金额,使“总分发=总额-利润”;利润单独转给 recipient
85
+ // 按比例缩放每个收款金额,使"总分发=总额-利润";利润单独转给 recipient
84
86
  const scaled = [];
85
87
  if (totalWei > 0n && distributableWei > 0n) {
86
88
  if (params.kind === 'native') {
@@ -268,11 +270,10 @@ export async function buildTransfersWithProfit(params) {
268
270
  initCodeUsed.add(k);
269
271
  return aaManager.generateInitCode(ownerAddress);
270
272
  };
271
- const unsigned = [];
272
- const opOwnerIndex = [];
273
+ // 第一步:计算每个钱包的归集金额和总利润
274
+ // 利润从每个钱包的归集金额中扣除,但只由第一个有效钱包统一转账给利润地址
275
+ const transferInfos = [];
273
276
  for (let i = 0; i < ownerWallets.length; i++) {
274
- const w = ownerWallets[i];
275
- const ai = infos[i];
276
277
  const sender = senders[i];
277
278
  const to = String(params.toAddresses[i] || '').trim();
278
279
  if (!/^0x[a-fA-F0-9]{40}$/.test(to))
@@ -292,36 +293,56 @@ export async function buildTransfersWithProfit(params) {
292
293
  }
293
294
  if (amountWei <= 0n)
294
295
  continue;
295
- const profitWei = calcProfitWei(amountWei, PROFIT_CONFIG.RATE_BPS);
296
+ // 多跳中间层:skipProfit=true 时不扣利润,全额转给目标
297
+ const skipProfit = params.skipProfit === true;
298
+ const profitWei = skipProfit ? 0n : calcProfitWei(amountWei, PROFIT_CONFIG.RATE_BPS);
296
299
  const toWei = amountWei - profitWei;
297
300
  totalProfitWei += profitWei;
298
- // 使用递增的 nonce,避免同一 sender 多次转账时冲突
299
- const initCode = getInitCode(sender, ai?.deployed ?? false, w.address);
300
- const nonce = getNextNonce(sender, BigInt(ai?.nonce ?? 0n));
301
- const callData = (() => {
302
- if (profitWei <= 0n) {
303
- if (params.kind === 'native')
304
- return encodeExecute(to, amountWei, '0x');
305
- const token = String(params.tokenAddress || '').trim();
306
- const transfer = erc20Iface.encodeFunctionData('transfer', [to, amountWei]);
307
- return encodeExecute(token, 0n, transfer);
301
+ transferInfos.push({ walletIndex: i, sender, to, amountWei, profitWei, toWei });
302
+ }
303
+ const unsigned = [];
304
+ const opOwnerIndex = [];
305
+ // 第二步:构建 UserOps
306
+ // - Native (OKB):只转 toWei,利润保留在 AA 钱包(可能需要用于后续 gas)
307
+ // - ERC20:全部转出,profitWei 给利润地址 + toWei 给目标地址
308
+ for (let idx = 0; idx < transferInfos.length; idx++) {
309
+ const info = transferInfos[idx];
310
+ const i = info.walletIndex;
311
+ const w = ownerWallets[i];
312
+ const ai = infos[i];
313
+ const initCode = getInitCode(info.sender, ai?.deployed ?? false, w.address);
314
+ const nonce = getNextNonce(info.sender, BigInt(ai?.nonce ?? 0n));
315
+ let callData;
316
+ let callGasLimit;
317
+ if (params.kind === 'native') {
318
+ // ✅ Native (OKB):只转 toWei,利润保留在 AA 钱包
319
+ callData = encodeExecute(info.to, info.toWei, '0x');
320
+ callGasLimit = 120000n;
321
+ }
322
+ else {
323
+ // ✅ ERC20:全部转出,不需要留余额
324
+ // executeBatch: 利润转给利润地址 + 归集金额转给目标地址
325
+ const token = String(params.tokenAddress || '').trim();
326
+ if (info.profitWei > 0n) {
327
+ const t1 = erc20Iface.encodeFunctionData('transfer', [PROFIT_CONFIG.RECIPIENT, info.profitWei]);
328
+ const t2 = erc20Iface.encodeFunctionData('transfer', [info.to, info.toWei]);
329
+ callData = encodeExecuteBatch([token, token], [0n, 0n], [t1, t2]);
330
+ callGasLimit = 260000n;
308
331
  }
309
- if (params.kind === 'native') {
310
- return encodeExecuteBatch([PROFIT_CONFIG.RECIPIENT, to], [profitWei, toWei], ['0x', '0x']);
332
+ else {
333
+ const transfer = erc20Iface.encodeFunctionData('transfer', [info.to, info.amountWei]);
334
+ callData = encodeExecute(token, 0n, transfer);
335
+ callGasLimit = 200000n;
311
336
  }
312
- const token = String(params.tokenAddress || '').trim();
313
- const t1 = erc20Iface.encodeFunctionData('transfer', [PROFIT_CONFIG.RECIPIENT, profitWei]);
314
- const t2 = erc20Iface.encodeFunctionData('transfer', [to, toWei]);
315
- return encodeExecuteBatch([token, token], [0n, 0n], [t1, t2]);
316
- })();
337
+ }
317
338
  const built = await aaManager.buildUserOpWithFixedGas({
318
339
  ownerWallet: w,
319
- sender,
340
+ sender: info.sender,
320
341
  callData,
321
342
  nonce,
322
343
  initCode,
323
- deployed: initCode === '0x', // ✅ 根据 initCode 判断是否已部署
324
- fixedGas: { callGasLimit: params.kind === 'native' ? 180000n : 260000n },
344
+ deployed: initCode === '0x',
345
+ fixedGas: { callGasLimit },
325
346
  });
326
347
  unsigned.push(built.userOp);
327
348
  opOwnerIndex.push(i);
@@ -92,19 +92,19 @@ export declare class BundleExecutor {
92
92
  *
93
93
  * 目标:对齐 BSC `flapBundleCreateToDex` 的逻辑,但适配 XLayer AA (EIP-4337)
94
94
  *
95
- * 流程:
96
- * 1. 准备阶段:Payer (Dev), Curve Buyers, Nonces, Profit Settings
97
- * 2. HandleOps #1:
98
- * - Op 1: `newTokenV4` (发币) - Payer 发起
99
- * - Ops 2..N: `swapExactInput` (内盘买入) - 多个 Curve Buyer 发起
100
- * - 注意:最后一笔内盘买入通常触发毕业,GasLimit 设为 8,000,000
101
- * 3. HandleOps #2 (可选):
102
- * - Ops 1..M: Approve + `swapExactETHForTokens` (外盘买入) - 多个 DEX Buyer 发起
103
- * 4. 利润提取 (可选):
104
- * - Payer EOA 发起 direct transfer
95
+ * ✅ 所有操作合并到一个 handleOps 中原子执行:
96
+ *
97
+ * handleOps([
98
+ * Op 1: `newTokenV4` (发币) - Payer 发起
99
+ * Ops 2..N: `swapExactInput` (内盘买入) - 多个 Curve Buyer 发起
100
+ * Ops N+1..M: `swapExactETHForTokens` (外盘买入,可选) - 多个 DEX Buyer 发起
101
+ * Op M+1: 利润转账 (可选) - Payer 发起
102
+ * ])
103
+ *
104
+ * 注意:最后一笔内盘买入通常触发毕业,GasLimit 设为 8,000,000
105
105
  *
106
106
  * @param params - 签名参数
107
- * @returns 签名后的交易列表及元数据
107
+ * @returns 签名后的交易列表及元数据(只有一个 handleOps 交易)
108
108
  */
109
109
  bundleCreateToDexSign(params: BundleCreateToDexSignParams): Promise<BundleCreateToDexSignResult>;
110
110
  /**
@@ -1492,19 +1492,19 @@ export class BundleExecutor {
1492
1492
  *
1493
1493
  * 目标:对齐 BSC `flapBundleCreateToDex` 的逻辑,但适配 XLayer AA (EIP-4337)
1494
1494
  *
1495
- * 流程:
1496
- * 1. 准备阶段:Payer (Dev), Curve Buyers, Nonces, Profit Settings
1497
- * 2. HandleOps #1:
1498
- * - Op 1: `newTokenV4` (发币) - Payer 发起
1499
- * - Ops 2..N: `swapExactInput` (内盘买入) - 多个 Curve Buyer 发起
1500
- * - 注意:最后一笔内盘买入通常触发毕业,GasLimit 设为 8,000,000
1501
- * 3. HandleOps #2 (可选):
1502
- * - Ops 1..M: Approve + `swapExactETHForTokens` (外盘买入) - 多个 DEX Buyer 发起
1503
- * 4. 利润提取 (可选):
1504
- * - Payer EOA 发起 direct transfer
1495
+ * ✅ 所有操作合并到一个 handleOps 中原子执行:
1496
+ *
1497
+ * handleOps([
1498
+ * Op 1: `newTokenV4` (发币) - Payer 发起
1499
+ * Ops 2..N: `swapExactInput` (内盘买入) - 多个 Curve Buyer 发起
1500
+ * Ops N+1..M: `swapExactETHForTokens` (外盘买入,可选) - 多个 DEX Buyer 发起
1501
+ * Op M+1: 利润转账 (可选) - Payer 发起
1502
+ * ])
1503
+ *
1504
+ * 注意:最后一笔内盘买入通常触发毕业,GasLimit 设为 8,000,000
1505
1505
  *
1506
1506
  * @param params - 签名参数
1507
- * @returns 签名后的交易列表及元数据
1507
+ * @returns 签名后的交易列表及元数据(只有一个 handleOps 交易)
1508
1508
  */
1509
1509
  async bundleCreateToDexSign(params) {
1510
1510
  const { tokenInfo, tokenAddress, payerPrivateKey, curveBuyerPrivateKeys, curveBuyAmounts, enableDexBuy = false, dexBuyerPrivateKeys = [], dexBuyAmounts = [], config = {} } = params;
@@ -1562,7 +1562,7 @@ export class BundleExecutor {
1562
1562
  // ✅ 预计算所有买入金额
1563
1563
  const curveBuyData = curveBuyerWallets.map((wallet, i) => {
1564
1564
  const buyWei = parseOkb(curveBuyAmounts[i]);
1565
- const gasLimit = 6500000n; // 统一给 650W,不再区分是否最后一笔
1565
+ const gasLimit = 8000000n; // 统一给 800W,不再区分是否最后一笔
1566
1566
  return { wallet, info: curveBuyerInfos[i], buyWei, gasLimit };
1567
1567
  });
1568
1568
  totalCurveBuyWei = curveBuyData.reduce((sum, d) => sum + d.buyWei, 0n);
@@ -1585,50 +1585,9 @@ export class BundleExecutor {
1585
1585
  const signedBuyOp = await aaManager.signUserOp(buyOpRes.userOp, wallet);
1586
1586
  return signedBuyOp.userOp;
1587
1587
  });
1588
- ops1.push(...signedCurveBuyOps);
1589
- // === 向内盘买家 AA 账户转入 OKB(prefund + 买入金额)===
1590
- // 修复 AA21 didn't pay prefund 错误
1591
- const feeData = await provider.getFeeData();
1592
- const gasPrice = feeData.gasPrice ?? 100000000n;
1593
- const chainId = (await provider.getNetwork()).chainId;
1594
- let currentNonce = params.payerStartNonce ?? (await provider.getTransactionCount(payerWallet.address, 'pending'));
1595
- const DEFAULT_PREFUND_ESTIMATE = parseOkb('0.002'); // 预估每个 UserOp 的 prefund
1596
- const BUFFER_WEI = parseOkb('0.0005'); // 缓冲金额
1597
- for (let i = 0; i < curveBuyerWallets.length; i++) {
1598
- const buyerSender = curveBuyerInfos[i].sender;
1599
- const buyWei = curveBuyData[i].buyWei;
1600
- // 计算需要转入的金额:买入金额 + prefund + 缓冲
1601
- const requiredAmount = buyWei + DEFAULT_PREFUND_ESTIMATE + BUFFER_WEI;
1602
- // 检查买家 AA 账户当前余额
1603
- const currentBalance = await provider.getBalance(buyerSender);
1604
- if (currentBalance < requiredAmount) {
1605
- const transferAmount = requiredAmount - currentBalance;
1606
- console.log(`[内盘买家${i + 1}] 向 AA 账户 ${buyerSender} 转入 ${formatOkb(transferAmount)} OKB`);
1607
- const fundTxRequest = {
1608
- to: buyerSender,
1609
- value: transferAmount,
1610
- nonce: currentNonce,
1611
- gasLimit: 21000n,
1612
- gasPrice,
1613
- chainId
1614
- };
1615
- const signedFundTx = await payerWallet.signTransaction(fundTxRequest);
1616
- signedTransactions.push(signedFundTx);
1617
- currentNonce++;
1618
- }
1619
- }
1620
- // 签名第一个 handleOps
1621
- const signedMainTx = await this.signHandleOpsTx({
1622
- ops: ops1,
1623
- payerWallet: payerWallet,
1624
- beneficiary: params.beneficiary ?? payerWallet.address,
1625
- nonce: currentNonce,
1626
- gasLimit: effConfig.gasLimit,
1627
- gasPrice: effConfig.minGasPriceGwei ? ethers.parseUnits(effConfig.minGasPriceGwei.toString(), 'gwei') : undefined
1628
- });
1629
- signedTransactions.push(signedMainTx);
1630
- currentNonce++;
1631
- // --- 4. 构建外盘买入 handleOps (如果启用) ---
1588
+ // ✅ 使用 allOps 收集所有 UserOps,最终合并到一个 handleOps 中
1589
+ const allOps = [...ops1, ...signedCurveBuyOps];
1590
+ // --- 4. 构建外盘买入 Ops (如果启用) ---
1632
1591
  if (enableDexBuy && dexBuyerPrivateKeys.length > 0) {
1633
1592
  const dexBuyerWallets = dexBuyerPrivateKeys.map(pk => createWallet(pk, config));
1634
1593
  const dexBuyerInfos = await mapWithConcurrency(dexBuyerWallets, 5, async (w) => {
@@ -1636,7 +1595,6 @@ export class BundleExecutor {
1636
1595
  nonceMap.init(info.sender, info.nonce);
1637
1596
  return info;
1638
1597
  });
1639
- const ops2 = [];
1640
1598
  const deadline = Math.floor(Date.now() / 1000) + 1200; // 20 min
1641
1599
  const dexType = (params.lpFeeProfile !== undefined && params.lpFeeProfile >= 0) ? 'V3' : 'V2';
1642
1600
  // ✅ 预计算所有外盘买入金额
@@ -1645,31 +1603,7 @@ export class BundleExecutor {
1645
1603
  return { wallet, info: dexBuyerInfos[i], buyWei };
1646
1604
  });
1647
1605
  totalDexBuyWei = dexBuyData.reduce((sum, d) => sum + d.buyWei, 0n);
1648
- // === 向外盘买家 AA 账户转入 OKB(prefund + 买入金额)===
1649
- for (let i = 0; i < dexBuyerWallets.length; i++) {
1650
- const buyerSender = dexBuyerInfos[i].sender;
1651
- const buyWei = dexBuyData[i].buyWei;
1652
- // 计算需要转入的金额:买入金额 + prefund + 缓冲
1653
- const requiredAmount = buyWei + DEFAULT_PREFUND_ESTIMATE + BUFFER_WEI;
1654
- // 检查买家 AA 账户当前余额
1655
- const dexBuyerBalance = await provider.getBalance(buyerSender);
1656
- if (dexBuyerBalance < requiredAmount) {
1657
- const transferAmount = requiredAmount - dexBuyerBalance;
1658
- console.log(`[外盘买家${i + 1}] 向 AA 账户 ${buyerSender} 转入 ${formatOkb(transferAmount)} OKB`);
1659
- const fundTxRequest = {
1660
- to: buyerSender,
1661
- value: transferAmount,
1662
- nonce: currentNonce,
1663
- gasLimit: 21000n,
1664
- gasPrice,
1665
- chainId
1666
- };
1667
- const signedFundTx = await payerWallet.signTransaction(fundTxRequest);
1668
- signedTransactions.push(signedFundTx);
1669
- currentNonce++;
1670
- }
1671
- }
1672
- // ✅ 并行构建和签名所有外盘买入 UserOps
1606
+ // 并行构建和签名所有外盘买入 UserOps(假设买家 AA 账户已预先充值)
1673
1607
  const signedDexBuyOps = await mapWithConcurrency(dexBuyData, 5, async (data) => {
1674
1608
  const { wallet, info, buyWei } = data;
1675
1609
  // ✅ 外盘买入:使用 DEX Router 进行交换
@@ -1698,26 +1632,16 @@ export class BundleExecutor {
1698
1632
  sender: info.sender,
1699
1633
  callData: dexBuyCallData,
1700
1634
  nonce: nonceMap.next(info.sender),
1701
- initCode: '0x', // HandleOps1 已部署
1702
- deployed: true,
1635
+ initCode: info.deployed ? '0x' : (await aaManager.generateInitCode(wallet.address)),
1636
+ deployed: info.deployed,
1703
1637
  fixedGas: { callGasLimit: 600000n }
1704
1638
  });
1705
1639
  const signedDexBuyOp = await aaManager.signUserOp(dexBuyOpRes.userOp, wallet);
1706
1640
  return signedDexBuyOp.userOp;
1707
1641
  });
1708
- ops2.push(...signedDexBuyOps);
1709
- const signedDexTx = await this.signHandleOpsTx({
1710
- ops: ops2,
1711
- payerWallet: payerWallet,
1712
- beneficiary: params.beneficiary ?? payerWallet.address,
1713
- nonce: currentNonce,
1714
- gasLimit: effConfig.gasLimit,
1715
- gasPrice: effConfig.minGasPriceGwei ? ethers.parseUnits(effConfig.minGasPriceGwei.toString(), 'gwei') : undefined
1716
- });
1717
- signedTransactions.push(signedDexTx);
1718
- currentNonce++;
1642
+ allOps.push(...signedDexBuyOps);
1719
1643
  }
1720
- // --- 5. 利润提取 (AA 内部 UserOp,不再使用 Tail Transaction) ---
1644
+ // --- 5. 利润提取 (AA 内部 UserOp,合并到同一个 handleOps) ---
1721
1645
  let totalProfitWei = 0n;
1722
1646
  if (profitSettings.extractProfit) {
1723
1647
  totalProfitWei += calculateProfitWei(totalCurveBuyWei, profitSettings.profitBps);
@@ -1725,36 +1649,37 @@ export class BundleExecutor {
1725
1649
  totalProfitWei += calculateProfitWei(totalDexBuyWei, profitSettings.profitBps);
1726
1650
  }
1727
1651
  if (totalProfitWei > 0n) {
1728
- // ✅ 使用 Payer AA 账户发起利润转账 UserOp(不再使用 Tail Transaction)
1652
+ // ✅ 使用 Payer AA 账户发起利润转账 UserOp
1729
1653
  const profitCallData = encodeExecute(profitSettings.profitRecipient, totalProfitWei, '0x');
1730
- const payerInitCode = payerAccount.deployed ? '0x' : aaManager.generateInitCode(payerWallet.address);
1731
- const { userOp: profitOp, prefundWei: profitPrefund } = await aaManager.buildUserOpWithFixedGas({
1654
+ const { userOp: profitOp } = await aaManager.buildUserOpWithFixedGas({
1732
1655
  ownerWallet: payerWallet,
1733
1656
  sender: payerAccount.sender,
1734
1657
  nonce: nonceMap.next(payerAccount.sender),
1735
- initCode: payerInitCode,
1658
+ initCode: '0x', // Payer 已在发币时部署
1736
1659
  callData: profitCallData,
1737
- deployed: payerAccount.deployed,
1660
+ deployed: true,
1738
1661
  fixedGas: {
1739
1662
  ...(effConfig.fixedGas ?? {}),
1740
1663
  callGasLimit: effConfig.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
1741
1664
  },
1742
1665
  });
1743
- // 确保 Payer AA 账户有足够余额支付利润转账
1744
- await aaManager.ensureSenderBalance(payerWallet, payerAccount.sender, totalProfitWei + profitPrefund + parseOkb('0.0001'), 'profit-transfer-fund');
1745
1666
  const signedProfitOp = await aaManager.signUserOp(profitOp, payerWallet);
1746
- // 签名利润 handleOps 交易
1747
- const signedProfitHandleOpsTx = await this.signHandleOpsTx({
1748
- ops: [signedProfitOp.userOp],
1749
- payerWallet: payerWallet,
1750
- beneficiary: params.beneficiary ?? payerWallet.address,
1751
- nonce: currentNonce,
1752
- gasLimit: effConfig.gasLimit,
1753
- gasPrice: effConfig.minGasPriceGwei ? ethers.parseUnits(effConfig.minGasPriceGwei.toString(), 'gwei') : undefined
1754
- });
1755
- signedTransactions.push(signedProfitHandleOpsTx);
1667
+ allOps.push(signedProfitOp.userOp);
1756
1668
  }
1757
1669
  }
1670
+ // ✅ 获取 Payer nonce(假设买家 AA 账户已预先充值)
1671
+ const currentNonce = params.payerStartNonce ?? (await provider.getTransactionCount(payerWallet.address, 'pending'));
1672
+ // ✅ 签名唯一的 handleOps(包含:发币 + 内盘买入 + 外盘买入 + 利润转账)
1673
+ const signedMainTx = await this.signHandleOpsTx({
1674
+ ops: allOps,
1675
+ payerWallet: payerWallet,
1676
+ beneficiary: params.beneficiary ?? payerWallet.address,
1677
+ nonce: currentNonce,
1678
+ gasLimit: effConfig.gasLimit,
1679
+ gasPrice: effConfig.minGasPriceGwei ? ethers.parseUnits(effConfig.minGasPriceGwei.toString(), 'gwei') : undefined
1680
+ });
1681
+ signedTransactions.push(signedMainTx);
1682
+ console.log(`[bundleCreateToDexSign] 合并到一个 handleOps: ${allOps.length} 个 UserOps(发币:1, 内盘:${curveBuyerWallets.length}, 外盘:${dexBuyerPrivateKeys.length}, 利润:${totalProfitWei > 0n ? 1 : 0})`);
1758
1683
  return {
1759
1684
  signedTransactions,
1760
1685
  tokenAddress,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.16",
3
+ "version": "1.6.18",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",