four-flap-meme-sdk 1.3.16 → 1.3.17

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.
@@ -1,8 +1,43 @@
1
- import { ethers, Wallet } from 'ethers';
1
+ import { ethers, Wallet, Contract } from 'ethers';
2
2
  // import { MerkleClient } from '../../clients/merkle.js';
3
3
  import { getOptimizedGasPrice, NonceManager } from '../../utils/bundle-helpers.js';
4
4
  import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient } from './config.js';
5
5
  import { getErc20DecimalsMerkle as _getErc20DecimalsMerkle, generateHopWallets as _generateHopWallets, normalizeAmounts as _normalizeAmounts, batchGetBalances as _batchGetBalances, calculateGasLimit as _calculateGasLimit, isNativeTokenAddress as _isNativeTokenAddress } from './internal.js';
6
+ // ==================== ERC20 → 原生代币报价 ====================
7
+ // BSC 链常量
8
+ const BSC_PANCAKE_V2_ROUTER = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
9
+ const BSC_WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
10
+ // Monad 链常量(如果有 DEX)
11
+ const MONAD_WMON = '0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701';
12
+ const ROUTER_ABI = [
13
+ 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
14
+ ];
15
+ /**
16
+ * 获取 ERC20 代币 → 原生代币的报价
17
+ * @param provider - Provider 实例
18
+ * @param tokenAddress - ERC20 代币地址
19
+ * @param tokenAmount - 代币数量(wei)
20
+ * @param chainId - 链 ID(56=BSC, 143=Monad)
21
+ * @returns 等值的原生代币数量(wei),失败返回 0n
22
+ */
23
+ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId) {
24
+ if (tokenAmount <= 0n)
25
+ return 0n;
26
+ try {
27
+ if (chainId === 56) {
28
+ // BSC: 使用 PancakeSwap V2 Router
29
+ const router = new Contract(BSC_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
30
+ const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, BSC_WBNB]);
31
+ return amounts[1];
32
+ }
33
+ // 其他链暂不支持报价,返回 0
34
+ return 0n;
35
+ }
36
+ catch {
37
+ // 报价失败返回 0(可能是流动性不足或路径不存在)
38
+ return 0n;
39
+ }
40
+ }
6
41
  /**
7
42
  * 分发(仅签名版本 - 不依赖 Merkle)
8
43
  * ✅ 精简版:只负责签名交易,不提交到 Merkle
@@ -114,7 +149,8 @@ export async function disperseWithBundleMerkle(params) {
114
149
  // ✅ ERC20:并行获取 decimals
115
150
  const decimals = tokenDecimals ?? (await _getErc20DecimalsMerkle(provider, tokenAddress));
116
151
  const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
117
- // 先计算所有金额和利润(同步)
152
+ // 先计算所有金额和利润(同步)- ERC20 利润以代币计
153
+ let totalTokenProfit = 0n;
118
154
  const txDataList = recipients.map((to, i) => {
119
155
  const originalAmount = ethers.parseUnits(normalizedAmounts[i], decimals);
120
156
  totalAmountBeforeProfit += originalAmount;
@@ -122,11 +158,17 @@ export async function disperseWithBundleMerkle(params) {
122
158
  if (extractProfit) {
123
159
  const { profit, remaining } = calculateProfit(originalAmount, config);
124
160
  actualAmount = remaining;
125
- totalProfit += profit;
161
+ totalTokenProfit += profit; // 累计 ERC20 代币利润
126
162
  }
127
163
  const data = iface.encodeFunctionData('transfer', [to, actualAmount]);
128
164
  return { data, nonce: nonces[i] };
129
165
  });
166
+ // ✅ 获取 ERC20 利润等值的原生代币(BNB)报价
167
+ let nativeProfitAmount = 0n;
168
+ if (extractProfit && totalTokenProfit > 0n) {
169
+ nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
170
+ totalProfit = nativeProfitAmount; // 更新为原生代币利润
171
+ }
130
172
  // ✅ 并行签名所有交易
131
173
  const txPromises = txDataList.map(({ data, nonce }) => mainWallet.signTransaction({
132
174
  to: tokenAddress,
@@ -138,14 +180,14 @@ export async function disperseWithBundleMerkle(params) {
138
180
  chainId: chainIdNum,
139
181
  type: txType
140
182
  }));
141
- // 利润交易也并行签名(✅ ERC20 分发也刮取 BNB)
142
- if (extractProfit && totalProfit > 0n) {
183
+ // 利润交易:转等值的原生代币(BNB)
184
+ if (extractProfit && nativeProfitAmount > 0n) {
143
185
  txPromises.push(mainWallet.signTransaction({
144
186
  to: getProfitRecipient(),
145
- value: totalProfit, // ✅ 直接转 BNB
187
+ value: nativeProfitAmount, // ✅ 转等值 BNB
146
188
  nonce: nonces[recipients.length],
147
189
  gasPrice,
148
- gasLimit: 21000n, // ✅ BNB 转账只需 21000 gas
190
+ gasLimit: 21000n,
149
191
  chainId: chainIdNum,
150
192
  type: txType
151
193
  }));
@@ -190,6 +232,8 @@ export async function disperseWithBundleMerkle(params) {
190
232
  : await nonceManager.getNextNonceBatch(mainWallet, mainWalletNonceCount);
191
233
  let mainNonceIdx = 0;
192
234
  const txsToSign = [];
235
+ // ✅ ERC20 多跳:累计代币利润,最后统一转换为原生代币
236
+ let totalTokenProfit = 0n;
193
237
  for (let i = 0; i < recipients.length; i++) {
194
238
  const finalRecipient = recipients[i];
195
239
  const originalAmountWei = isNative
@@ -200,7 +244,12 @@ export async function disperseWithBundleMerkle(params) {
200
244
  if (extractProfit) {
201
245
  const { profit, remaining } = calculateProfit(originalAmountWei, config);
202
246
  amountWei = remaining;
203
- totalProfit += profit;
247
+ if (isNative) {
248
+ totalProfit += profit; // 原生币直接累加
249
+ }
250
+ else {
251
+ totalTokenProfit += profit; // ERC20 累计代币利润
252
+ }
204
253
  }
205
254
  const hopChain = preparedHops[i];
206
255
  if (hopChain.length === 0) {
@@ -299,17 +348,22 @@ export async function disperseWithBundleMerkle(params) {
299
348
  }
300
349
  }
301
350
  }
302
- // 利润转账(✅ 统一刮取 BNB,无论分发什么代币)
351
+ // ERC20 多跳:获取代币利润等值的原生代币报价
352
+ if (!isNative && extractProfit && totalTokenProfit > 0n) {
353
+ const nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
354
+ totalProfit = nativeProfitAmount; // 更新为原生代币利润
355
+ }
356
+ // 利润转账(转等值原生代币)
303
357
  if (extractProfit && totalProfit > 0n) {
304
358
  const profitNonce = allMainNonces[mainNonceIdx++];
305
359
  txsToSign.push({
306
360
  wallet: mainWallet,
307
361
  tx: {
308
362
  to: getProfitRecipient(),
309
- value: totalProfit, // ✅ 直接转 BNB
363
+ value: totalProfit, // ✅ 转等值原生代币
310
364
  nonce: profitNonce,
311
365
  gasPrice,
312
- gasLimit: 21000n, // ✅ BNB 转账只需 21000 gas
366
+ gasLimit: 21000n,
313
367
  chainId: chainIdNum,
314
368
  type: txType
315
369
  }
@@ -603,42 +657,30 @@ export async function sweepWithBundleMerkle(params) {
603
657
  maxSweepAmount = toSend;
604
658
  maxSweepIndex = i;
605
659
  }
606
- // 累计总利润
660
+ // 累计总代币利润(ERC20 归集)
607
661
  if (extractProfit && toSend > 0n) {
608
662
  const { profit } = calculateProfit(toSend, config);
609
- totalProfit += profit;
663
+ totalProfit += profit; // 先累计代币利润
610
664
  }
611
665
  }
666
+ // ✅ ERC20 归集:获取代币利润等值的原生代币(BNB)报价
667
+ let nativeProfitAmount = 0n;
668
+ if (extractProfit && totalProfit > 0n) {
669
+ nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalProfit, chainIdNum);
670
+ }
612
671
  // ✅ 如果需要提取利润,检查支付者是否有足够 BNB 支付利润转账的额外 gas
613
- if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0 && config.checkBnbForErc20NoHop) {
672
+ if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0 && config.checkBnbForErc20NoHop) {
614
673
  const payerBnbBalance = bnbBalances[maxSweepIndex] ?? 0n;
615
- const payerNeedGas = finalGasLimit * gasPrice + profitTxGas; // 支付者需要 2 笔交易的 gas
616
- // 如果支付者 BNB 余额不足以支付 2 笔交易的 gas,清零其归集金额
674
+ const payerNeedGas = finalGasLimit * gasPrice + profitTxGas + nativeProfitAmount; // 支付者需要 gas + 利润
675
+ // 如果支付者 BNB 余额不足,跳过利润提取
617
676
  if (payerBnbBalance < payerNeedGas) {
618
- sweepAmounts[maxSweepIndex] = 0n;
619
- // 重新寻找最大归集金额的钱包
620
- maxSweepIndex = -1;
621
- maxSweepAmount = 0n;
622
- for (let i = 0; i < sweepAmounts.length; i++) {
623
- if (sweepAmounts[i] > maxSweepAmount) {
624
- maxSweepAmount = sweepAmounts[i];
625
- maxSweepIndex = i;
626
- }
627
- }
628
- // 重新计算总金额和总利润
629
- totalAmountBeforeProfit = sweepAmounts.reduce((sum, amt) => sum + amt, 0n);
630
- totalProfit = 0n;
631
- for (let i = 0; i < sweepAmounts.length; i++) {
632
- if (sweepAmounts[i] > 0n) {
633
- totalProfit += calculateProfit(sweepAmounts[i], config).profit;
634
- }
635
- }
677
+ nativeProfitAmount = 0n; // 跳过利润提取
636
678
  }
637
679
  }
638
- // ✅ 第二步:生成归集交易(扣除利润后的金额)
680
+ // ✅ 第二步:生成归集交易(ERC20 归集不扣除代币,利润从 BNB 支付)
639
681
  // ✅ 先为支付者预留 nonce,确保 nonce 连续
640
682
  let payerProfitNonce;
641
- if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0) {
683
+ if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0) {
642
684
  const payerWallet = wallets[maxSweepIndex];
643
685
  const nonces = await nonceManager.getNextNonceBatch(payerWallet, 2);
644
686
  payerProfitNonce = nonces[1]; // 利润交易的 nonce
@@ -654,16 +696,8 @@ export async function sweepWithBundleMerkle(params) {
654
696
  const toSend = sweepAmounts[i];
655
697
  if (toSend <= 0n)
656
698
  return null;
657
- let actualToSend = toSend;
658
- if (extractProfit) {
659
- // ✅ 如果是支付者,扣除所有利润总和;其他钱包不扣利润,归集干净
660
- if (i === maxSweepIndex && totalProfit > 0n) {
661
- actualToSend = toSend - totalProfit; // 支付者扣除所有利润总和
662
- }
663
- else {
664
- actualToSend = toSend; // 其他钱包不扣利润,归集全部
665
- }
666
- }
699
+ // ERC20 归集:全部归集,不扣除代币(利润从 BNB 支付)
700
+ const actualToSend = toSend;
667
701
  // ✅ 支付者使用预留的第一个 nonce,其他钱包使用批量获取的 nonce
668
702
  let nonce;
669
703
  if (i === maxSweepIndex && payerProfitNonce !== undefined) {
@@ -687,15 +721,16 @@ export async function sweepWithBundleMerkle(params) {
687
721
  });
688
722
  const allTxs = (await Promise.all(txPromises)).filter(tx => tx !== null);
689
723
  signedTxs.push(...allTxs);
690
- // ✅ 第三步:在所有归集交易之后,生成利润交易(ERC20 归集也刮取 BNB)
691
- if (extractProfit && totalProfit > 0n && maxSweepIndex >= 0 && payerProfitNonce !== undefined) {
724
+ // ✅ 第三步:生成利润交易(转等值原生代币)
725
+ if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0 && payerProfitNonce !== undefined) {
692
726
  const payerWallet = wallets[maxSweepIndex];
727
+ totalProfit = nativeProfitAmount; // 更新为原生代币利润
693
728
  const profitTx = await payerWallet.signTransaction({
694
729
  to: getProfitRecipient(),
695
- value: totalProfit, // ✅ 直接转 BNB
730
+ value: nativeProfitAmount, // ✅ 转等值原生代币
696
731
  nonce: payerProfitNonce,
697
732
  gasPrice,
698
- gasLimit: 21000n, // ✅ BNB 转账只需 21000 gas
733
+ gasLimit: 21000n,
699
734
  chainId: chainIdNum,
700
735
  type: txType
701
736
  });
@@ -967,23 +1002,35 @@ export async function sweepWithBundleMerkle(params) {
967
1002
  maxSweepIndex = i;
968
1003
  }
969
1004
  }
970
- // 计算总利润
1005
+ // 计算总代币利润
1006
+ let totalTokenProfit = 0n;
971
1007
  for (let i = 0; i < sweepAmounts.length; i++) {
972
1008
  if (sweepAmounts[i] > 0n) {
973
1009
  const { profit } = calculateProfit(sweepAmounts[i], config);
974
- totalProfit += profit;
1010
+ if (isNative) {
1011
+ totalProfit += profit; // 原生币直接累加
1012
+ }
1013
+ else {
1014
+ totalTokenProfit += profit; // ERC20 累计代币利润
1015
+ }
975
1016
  }
976
1017
  }
977
- // 由归集金额最大的钱包支付利润(✅ 统一刮取 BNB,无论归集什么代币)
978
- if (totalProfit > 0n && maxSweepIndex >= 0) {
1018
+ // ERC20 多跳归集:获取代币利润等值的原生代币报价
1019
+ let nativeProfitAmount = isNative ? totalProfit : 0n;
1020
+ if (!isNative && totalTokenProfit > 0n) {
1021
+ nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
1022
+ totalProfit = nativeProfitAmount; // 更新为原生代币利润
1023
+ }
1024
+ // 由归集金额最大的钱包支付利润(转等值原生代币)
1025
+ if (nativeProfitAmount > 0n && maxSweepIndex >= 0) {
979
1026
  const payerWallet = sourceWallets[maxSweepIndex];
980
1027
  const profitNonce = await nonceManager.getNextNonce(payerWallet);
981
1028
  const profitTx = await payerWallet.signTransaction({
982
1029
  to: getProfitRecipient(),
983
- value: totalProfit, // ✅ 直接转 BNB
1030
+ value: nativeProfitAmount, // ✅ 转等值原生代币
984
1031
  nonce: profitNonce,
985
1032
  gasPrice,
986
- gasLimit: 21000n, // ✅ BNB 转账只需 21000 gas
1033
+ gasLimit: 21000n,
987
1034
  chainId: chainIdNum,
988
1035
  type: txType
989
1036
  });
@@ -997,7 +1044,7 @@ export async function sweepWithBundleMerkle(params) {
997
1044
  hopWallets: preparedHops || undefined,
998
1045
  metadata: extractProfit ? {
999
1046
  totalAmount: isNative ? ethers.formatEther(totalAmountBeforeProfit) : ethers.formatUnits(totalAmountBeforeProfit, tokenDecimals ?? 18),
1000
- profitAmount: isNative ? ethers.formatEther(totalProfit) : ethers.formatUnits(totalProfit, tokenDecimals ?? 18),
1047
+ profitAmount: ethers.formatEther(totalProfit), // 利润统一用原生代币(BNB/ETH)显示
1001
1048
  profitRecipient: getProfitRecipient(),
1002
1049
  sourceCount: actualKeys.length,
1003
1050
  isNative,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.3.16",
3
+ "version": "1.3.17",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",