four-flap-meme-sdk 1.9.41 → 1.9.43

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.
@@ -260,11 +260,11 @@ export async function directSellForSubmit(params) {
260
260
  };
261
261
  }
262
262
  export async function directBatchBuyForSubmit(params) {
263
- const { rpcUrl = ENI_RPC_URL, token, privateKeys, amounts, slippageBps = 100, gasPrice } = params;
264
- if (privateKeys.length !== amounts.length) {
265
- throw new Error(`privateKeys.length (${privateKeys.length}) !== amounts.length (${amounts.length})`);
263
+ const { rpcUrl = ENI_RPC_URL, token, privateKeys: rawKeys, amounts: rawAmounts, slippageBps = 100, gasPrice } = params;
264
+ if (rawKeys.length !== rawAmounts.length) {
265
+ throw new Error(`privateKeys.length (${rawKeys.length}) !== amounts.length (${rawAmounts.length})`);
266
266
  }
267
- if (privateKeys.length === 0) {
267
+ if (rawKeys.length === 0) {
268
268
  throw new Error('privateKeys 不能为空');
269
269
  }
270
270
  const provider = new JsonRpcProvider(rpcUrl, ENI_CHAIN_ID, { staticNetwork: true });
@@ -272,6 +272,34 @@ export async function directBatchBuyForSubmit(params) {
272
272
  const feeData = await provider.getFeeData();
273
273
  const maxFeePerGas = gasPrice ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? 1000000000n;
274
274
  const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
275
+ const swapGasLimit = 300000n;
276
+ const profitTxGasCost = NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
277
+ const totalFlowRaw = rawAmounts.reduce((sum, amount) => sum + amount, 0n);
278
+ const profitAmountFull = calculateProfitAmount(totalFlowRaw, NORMAL_PROFIT_BPS) + profitTxGasCost;
279
+ const maxPayerIdx = pickMaxAmountIndex(rawAmounts);
280
+ const wallets = rawKeys.map(pk => new Wallet(pk, provider));
281
+ const balances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
282
+ let privateKeys = rawKeys;
283
+ let amounts = rawAmounts;
284
+ const validIndices = [];
285
+ for (let i = 0; i < rawKeys.length; i++) {
286
+ let required = rawAmounts[i] + swapGasLimit * maxFeePerGas;
287
+ if (i === maxPayerIdx)
288
+ required += profitAmountFull + NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
289
+ if (balances[i] < required) {
290
+ console.warn(`[DAOAAS BatchBuy] 钱包 ${wallets[i].address.slice(0, 10)} 余额不足: 余额=${balances[i]}, 需要=${required},跳过`);
291
+ continue;
292
+ }
293
+ validIndices.push(i);
294
+ }
295
+ if (validIndices.length === 0) {
296
+ throw new Error('所有钱包余额不足(含 swap + gas),无法执行 DAOAAS 买入');
297
+ }
298
+ if (validIndices.length < rawKeys.length) {
299
+ privateKeys = validIndices.map(i => rawKeys[i]);
300
+ amounts = validIndices.map(i => rawAmounts[i]);
301
+ console.log(`[DAOAAS BatchBuy] 余额预检: ${rawKeys.length - validIndices.length} 个钱包被跳过, 剩余 ${validIndices.length} 个`);
302
+ }
275
303
  const transactions = [];
276
304
  const totalFlow = amounts.reduce((sum, amount) => sum + amount, 0n);
277
305
  const isBatch = privateKeys.length > 1;
@@ -293,11 +321,10 @@ export async function directBatchBuyForSubmit(params) {
293
321
  type: 2,
294
322
  maxFeePerGas,
295
323
  maxPriorityFeePerGas,
296
- gasLimit: 300000n,
324
+ gasLimit: swapGasLimit,
297
325
  });
298
326
  transactions.push({ hash: '', signedTx, from: wallet.address, nonce });
299
327
  }
300
- const profitTxGasCost = NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
301
328
  const profitAmount = calculateProfitAmount(totalFlow, NORMAL_PROFIT_BPS) + profitTxGasCost;
302
329
  const payerIndex = pickMaxAmountIndex(amounts);
303
330
  const profitTx = await buildRevenueTail({
@@ -311,7 +338,7 @@ export async function directBatchBuyForSubmit(params) {
311
338
  return {
312
339
  transactions,
313
340
  signedTransactions: [...transactions.map(tx => tx.signedTx), profitTx.signedTx],
314
- totalGasCost: 300000n * maxFeePerGas * BigInt(transactions.length) + NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas,
341
+ totalGasCost: swapGasLimit * maxFeePerGas * BigInt(transactions.length) + NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas,
315
342
  metadata: formatSubmitMetadata(totalFlow, profitAmount),
316
343
  };
317
344
  }
@@ -328,11 +355,25 @@ export async function directBatchSellForSubmit(params) {
328
355
  const feeData = await provider.getFeeData();
329
356
  const maxFeePerGas = gasPrice ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? 1000000000n;
330
357
  const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
358
+ const swapGasLimit = 300000n;
359
+ const swapGasCost = swapGasLimit * maxFeePerGas;
360
+ const profitGasCost = NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
361
+ const wallets = privateKeys.map(pk => new Wallet(pk, provider));
362
+ const balances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
363
+ const maxPayerIdx = pickMaxAmountIndex(tokenAmounts);
364
+ for (let i = 0; i < wallets.length; i++) {
365
+ let required = swapGasCost;
366
+ if (i === maxPayerIdx)
367
+ required += profitGasCost;
368
+ if (balances[i] < required) {
369
+ console.warn(`[DAOAAS BatchSell] 钱包 ${wallets[i].address.slice(0, 10)} 原生余额不足 gas: 余额=${balances[i]}, 需要=${required},可能导致交易失败`);
370
+ }
371
+ }
331
372
  const transactions = [];
332
373
  const estimatedOuts = [];
333
374
  const isBatch = privateKeys.length > 1;
334
375
  for (let i = 0; i < privateKeys.length; i++) {
335
- const wallet = new Wallet(privateKeys[i], provider);
376
+ const wallet = wallets[i];
336
377
  const preview = await query.previewSell(token, tokenAmounts[i]);
337
378
  estimatedOuts.push(preview);
338
379
  const effectiveSlippage = isBatch
@@ -349,7 +390,7 @@ export async function directBatchSellForSubmit(params) {
349
390
  type: 2,
350
391
  maxFeePerGas,
351
392
  maxPriorityFeePerGas,
352
- gasLimit: 300000n,
393
+ gasLimit: swapGasLimit,
353
394
  });
354
395
  transactions.push({ hash: '', signedTx, from: wallet.address, nonce });
355
396
  }
@@ -535,16 +535,19 @@ export async function directV2BatchBuy(params) {
535
535
  const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
536
536
  const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
537
537
  const baseProfitWei = calculateProfitAmount(totalFlowWei);
538
- // ✅ 方案 B:并行获取 nonces、gasPriceERC20 报价
539
- const [nonces, gasPrice, nativeProfitWei] = await Promise.all([
538
+ // ✅ 方案 B:并行获取 nonces、gasPriceERC20 报价 以及(非原生代币时)授权额度
539
+ const quoteTokenContract = (!useNative && quoteToken) ? new Contract(quoteToken, ERC20_ABI, provider) : null;
540
+ const [nonces, gasPrice, nativeProfitWei, buyAllowances] = await Promise.all([
540
541
  startNonces && startNonces.length === wallets.length
541
542
  ? Promise.resolve(startNonces)
542
543
  : new NonceManager(provider).getNextNoncesForWallets(wallets),
543
544
  getGasPrice(provider, config),
544
- // ERC20 报价提前并行获取(V2 买入用 V2 报价)
545
545
  (!useNative && baseProfitWei > 0n && quoteToken)
546
546
  ? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v2')
547
- : Promise.resolve(baseProfitWei)
547
+ : Promise.resolve(baseProfitWei),
548
+ quoteTokenContract
549
+ ? Promise.all(wallets.map(w => quoteTokenContract.allowance(w.address, routerAddress).catch(() => 0n)))
550
+ : Promise.resolve(wallets.map(() => ethers.MaxUint256)),
548
551
  ]);
549
552
  // 确定最终利润金额(ENI 链额外补偿利润转账的 gas 成本)
550
553
  let profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
@@ -554,6 +557,75 @@ export async function directV2BatchBuy(params) {
554
557
  const gasLimit = getGasLimit(config, 250000);
555
558
  const txType = config.txType ?? 0;
556
559
  const deadline = getDeadline();
560
+ // ENI 链原生代币买入:链上余额预检,跳过余额不足的钱包
561
+ let activeIndices = null;
562
+ if (useNative && chain.toUpperCase() === 'ENI') {
563
+ const eniBalances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
564
+ const maxFlowIdx = findMaxFlowIndex(flowAmounts);
565
+ const profitGasCost = GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
566
+ const validIdx = [];
567
+ for (let i = 0; i < wallets.length; i++) {
568
+ let required = flowAmounts[i] + BigInt(gasLimit) * gasPrice;
569
+ if (i === maxFlowIdx && profitWei > 0n) {
570
+ required += profitWei + profitGasCost;
571
+ }
572
+ if (eniBalances[i] < required) {
573
+ console.warn(`[V2 BatchBuy ENI] 钱包 ${wallets[i].address.slice(0, 10)} 余额不足: 余额=${eniBalances[i]}, 需要=${required},跳过`);
574
+ continue;
575
+ }
576
+ validIdx.push(i);
577
+ }
578
+ if (validIdx.length === 0) {
579
+ throw new Error('[V2 BatchBuy ENI] 所有钱包余额不足(含 swap + gas + 利润费),无法执行');
580
+ }
581
+ if (validIdx.length < wallets.length) {
582
+ console.log(`[V2 BatchBuy ENI] 余额预检: ${wallets.length - validIdx.length} 个钱包被跳过, 剩余 ${validIdx.length} 个`);
583
+ activeIndices = validIdx;
584
+ }
585
+ }
586
+ // 如果有钱包被跳过,重建数组(仅 ENI)
587
+ let activeWallets = wallets;
588
+ let activeFlowAmounts = flowAmounts;
589
+ let activeNonces = nonces;
590
+ let activeBuyAllowances = buyAllowances;
591
+ if (activeIndices) {
592
+ activeWallets = activeIndices.map(i => wallets[i]);
593
+ activeFlowAmounts = activeIndices.map(i => flowAmounts[i]);
594
+ activeNonces = activeIndices.map(i => nonces[i]);
595
+ activeBuyAllowances = activeIndices.map(i => buyAllowances[i]);
596
+ }
597
+ const activeTotalFlowWei = activeFlowAmounts.reduce((sum, amt) => sum + amt, 0n);
598
+ const activeBaseProfitWei = calculateProfitAmount(activeTotalFlowWei);
599
+ let activeProfitWei = activeBaseProfitWei > 0n ? activeBaseProfitWei : 0n;
600
+ if (activeProfitWei > 0n && chain.toUpperCase() === 'ENI') {
601
+ activeProfitWei += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
602
+ }
603
+ if (!activeIndices) {
604
+ activeProfitWei = profitWei;
605
+ }
606
+ // ✅ 自动授权:ERC20 报价代币买入时,检查钱包对 Router 的授权额度,不足则插入 approve
607
+ const buyApproveTxs = [];
608
+ if (!useNative && quoteTokenContract) {
609
+ for (let i = 0; i < activeWallets.length; i++) {
610
+ if (activeFlowAmounts[i] <= 0n)
611
+ continue;
612
+ if (activeBuyAllowances[i] < activeFlowAmounts[i]) {
613
+ console.log(`🔓 [V2 Buy] 钱包 ${i} 报价代币授权不足 (${activeBuyAllowances[i]} < ${activeFlowAmounts[i]}), 自动 approve`);
614
+ const approveData = quoteTokenContract.interface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]);
615
+ const approveTx = await activeWallets[i].signTransaction({
616
+ to: quoteToken,
617
+ data: approveData,
618
+ value: 0n,
619
+ nonce: activeNonces[i],
620
+ gasLimit: 60000n,
621
+ ...buildGasFields(txType, gasPrice),
622
+ chainId,
623
+ });
624
+ buyApproveTxs.push({ walletIndex: i, tx: approveTx });
625
+ activeNonces[i]++;
626
+ }
627
+ }
628
+ }
557
629
  // 构建路径
558
630
  const inputToken = useNative ? wrappedNative : quoteToken;
559
631
  const path = [inputToken, tokenAddress];
@@ -584,28 +656,28 @@ export async function directV2BatchBuy(params) {
584
656
  }
585
657
  };
586
658
  // 选择金额最大的钱包支付贿赂和利润
587
- const maxFlowIndex = findMaxFlowIndex(flowAmounts);
659
+ const maxFlowIndex = findMaxFlowIndex(activeFlowAmounts);
588
660
  // 计算贿赂金额(仅 BSC 链)
589
661
  const bribeWei = getBribeAmount(config, chain);
590
- const hasBribe = bribeWei > 0n && wallets.length > 0;
591
- const hasProfit = profitWei > 0n;
662
+ const hasBribe = bribeWei > 0n && activeWallets.length > 0;
663
+ const hasProfit = activeProfitWei > 0n;
592
664
  // 计算 nonce 偏移
593
- const nonceOffsets = wallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
665
+ const nonceOffsets = activeWallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
594
666
  // ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
595
667
  const signPromises = [];
596
668
  // 贿赂交易
597
669
  if (hasBribe) {
598
- signPromises.push(buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType)
670
+ signPromises.push(buildBribeTransaction(activeWallets[maxFlowIndex], bribeWei, activeNonces[maxFlowIndex], gasPrice, chainId, txType)
599
671
  .then(tx => ({ type: 'bribe', index: 0, tx })));
600
672
  }
601
673
  // 主交易(并行签名)
602
- wallets.forEach((wallet, i) => {
603
- const { txData, txValue } = buildTxData(wallet, flowAmounts[i]);
674
+ activeWallets.forEach((wallet, i) => {
675
+ const { txData, txValue } = buildTxData(wallet, activeFlowAmounts[i]);
604
676
  signPromises.push(wallet.signTransaction({
605
677
  to: routerAddress,
606
678
  data: txData,
607
679
  value: txValue,
608
- nonce: nonces[i] + nonceOffsets[i],
680
+ nonce: activeNonces[i] + nonceOffsets[i],
609
681
  gasLimit,
610
682
  ...buildGasFields(txType, gasPrice),
611
683
  chainId,
@@ -613,34 +685,35 @@ export async function directV2BatchBuy(params) {
613
685
  });
614
686
  // ✅ 并行执行所有签名
615
687
  const signedResults = await Promise.all(signPromises);
616
- // 按类型分组并按顺序组装:贿赂 → 交易
688
+ // 按类型分组并按顺序组装:approve贿赂 → 交易
689
+ const approveSignedTxs = buyApproveTxs.sort((a, b) => a.walletIndex - b.walletIndex).map(a => a.tx);
617
690
  const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
618
691
  const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
619
- const signedTxs = [...bribeTxs, ...swapTxs];
692
+ const signedTxs = [...approveSignedTxs, ...bribeTxs, ...swapTxs];
620
693
  // ✅ 检查是否使用分布式利润模式
621
694
  const profitMode = config.profitMode || 'single';
622
695
  const skipProfit = config.skipProfit === true;
623
- console.log('🔧 [SDK directV2BatchBuy] profitMode:', profitMode, 'skipProfit:', skipProfit, 'wallets:', wallets.length);
696
+ console.log('🔧 [SDK directV2BatchBuy] profitMode:', profitMode, 'skipProfit:', skipProfit, 'wallets:', activeWallets.length);
624
697
  // 利润多跳转账
625
698
  let profitHopWallets;
626
699
  if (hasProfit && !skipProfit) {
627
700
  if (profitMode === 'distributed') {
628
701
  // ✅ 分布式模式:每个钱包独立扣除自己的利润
629
702
  const MAX_DISTRIBUTED_WALLETS = 12;
630
- if (wallets.length > MAX_DISTRIBUTED_WALLETS) {
631
- throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${wallets.length} 个`);
703
+ if (activeWallets.length > MAX_DISTRIBUTED_WALLETS) {
704
+ throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${activeWallets.length} 个`);
632
705
  }
633
706
  profitHopWallets = [];
634
707
  // 计算每个钱包的利润(按比例分配)
635
- for (let i = 0; i < wallets.length; i++) {
636
- const walletProfit = totalFlowWei > 0n
637
- ? (profitWei * flowAmounts[i]) / totalFlowWei
708
+ for (let i = 0; i < activeWallets.length; i++) {
709
+ const walletProfit = activeTotalFlowWei > 0n
710
+ ? (activeProfitWei * activeFlowAmounts[i]) / activeTotalFlowWei
638
711
  : 0n;
639
712
  if (walletProfit > 0n) {
640
- const walletProfitNonce = nonces[i] + nonceOffsets[i] + 1;
713
+ const walletProfitNonce = activeNonces[i] + nonceOffsets[i] + 1;
641
714
  const profitResult = await buildProfitHopTransactions({
642
715
  provider,
643
- payerWallet: wallets[i],
716
+ payerWallet: activeWallets[i],
644
717
  profitAmount: walletProfit,
645
718
  profitRecipient: PROFIT_CONFIG.RECIPIENT,
646
719
  hopCount: PROFIT_HOP_COUNT,
@@ -658,8 +731,8 @@ export async function directV2BatchBuy(params) {
658
731
  }
659
732
  else {
660
733
  // ✅ 单一模式(默认):从金额最大的钱包统一扣除
661
- const profitNonce = nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
662
- const profitResult = await buildProfitTransactionWithHops(provider, wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType);
734
+ const profitNonce = activeNonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
735
+ const profitResult = await buildProfitTransactionWithHops(provider, activeWallets[maxFlowIndex], activeProfitWei, profitNonce, gasPrice, chainId, txType);
663
736
  signedTxs.push(...profitResult.signedTransactions);
664
737
  profitHopWallets = profitResult.hopWallets;
665
738
  }
@@ -668,10 +741,10 @@ export async function directV2BatchBuy(params) {
668
741
  signedTransactions: signedTxs,
669
742
  profitHopWallets,
670
743
  metadata: {
671
- profitMode, // ✅ 返回使用的利润模式
672
- profitAmount: ethers.formatEther(profitWei),
744
+ profitMode,
745
+ profitAmount: ethers.formatEther(activeProfitWei),
673
746
  profitRecipient: PROFIT_CONFIG.RECIPIENT,
674
- totalFlow: ethers.formatUnits(totalFlowWei, quoteTokenDecimals),
747
+ totalFlow: ethers.formatUnits(activeTotalFlowWei, quoteTokenDecimals),
675
748
  },
676
749
  };
677
750
  }
@@ -704,22 +777,42 @@ export async function directV2BatchSell(params) {
704
777
  const gasLimit = getGasLimit(config, 300000);
705
778
  const txType = config.txType ?? 0;
706
779
  const deadline = getDeadline();
707
- // 计算卖出数量(同步操作,无 RPC
780
+ // 计算卖出数量(同步操作,无 RPC),并安全兜底不超过链上实际余额
708
781
  const sellAmountsWei = [];
709
782
  for (let i = 0; i < wallets.length; i++) {
783
+ let amount;
710
784
  if (sellAmounts && sellAmounts[i]) {
711
785
  const truncatedAmount = truncateDecimals(sellAmounts[i], tokenDecimals);
712
- sellAmountsWei.push(ethers.parseUnits(truncatedAmount, tokenDecimals));
786
+ amount = ethers.parseUnits(truncatedAmount, tokenDecimals);
713
787
  }
714
788
  else if (sellPercentages && sellPercentages[i]) {
715
789
  const pct = Math.min(100, Math.max(0, sellPercentages[i]));
716
- sellAmountsWei.push((balances[i] * BigInt(pct)) / 100n);
790
+ amount = (balances[i] * BigInt(pct)) / 100n;
717
791
  }
718
792
  else {
719
- sellAmountsWei.push(balances[i]);
793
+ amount = balances[i];
720
794
  }
795
+ if (amount > balances[i]) {
796
+ console.log(`⚠️ [V2 Sell] 钱包 ${i} 卖出量(${amount}) > 链上余额(${balances[i]}),已自动调整为实际余额`);
797
+ amount = balances[i];
798
+ }
799
+ sellAmountsWei.push(amount);
721
800
  }
722
801
  const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
802
+ // ENI 链卖出:检查 maxPayer 的原生余额是否足够覆盖 gas(swap gas + profit hop gas)
803
+ if (chain.toUpperCase() === 'ENI') {
804
+ const maxSellIdx = findMaxFlowIndex(sellAmountsWei);
805
+ const nativeBalances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
806
+ for (let i = 0; i < wallets.length; i++) {
807
+ let requiredGas = BigInt(gasLimit) * gasPrice;
808
+ if (i === maxSellIdx) {
809
+ requiredGas += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
810
+ }
811
+ if (nativeBalances[i] < requiredGas) {
812
+ console.warn(`[V2 BatchSell ENI] 钱包 ${wallets[i].address.slice(0, 10)} 原生余额不足 gas: 余额=${nativeBalances[i]}, 需要=${requiredGas},交易可能失败`);
813
+ }
814
+ }
815
+ }
723
816
  // ✅ 自动授权:检查每个钱包对 Router 的授权额度,不足则插入 approve 交易
724
817
  const approveTxs = [];
725
818
  for (let i = 0; i < wallets.length; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.9.41",
3
+ "version": "1.9.43",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",