four-flap-meme-sdk 4.3.21 → 4.4.0

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.
Files changed (82) hide show
  1. package/dist/bundle/four-meme/approve-tokenmanager.js +46 -16
  2. package/dist/bundle/four-meme/core/create-token-quote.d.ts +5 -1
  3. package/dist/bundle/four-meme/core/create-token-quote.js +29 -4
  4. package/dist/bundle/four-meme/core/sell-flow.js +3 -3
  5. package/dist/bundle/four-meme/pancake-proxy/approve.js +47 -10
  6. package/dist/bundle/four-meme/private/sell.js +2 -10
  7. package/dist/bundle/four-meme/swap/bundle.js +4 -4
  8. package/dist/bundle/four-meme/swap-buy-first/multi-wallet.js +25 -8
  9. package/dist/bundle/four-meme/swap-buy-first/single.js +30 -9
  10. package/dist/bundle/four-meme/swap-sign-helpers.js +56 -3
  11. package/dist/bundle/four-meme/utils-sweep/erc20-no-hop.js +18 -7
  12. package/dist/bundle/four-meme/utils-sweep/with-hops.js +10 -4
  13. package/dist/bundle/internal.d.ts +5 -0
  14. package/dist/bundle/internal.js +36 -0
  15. package/dist/chains/bsc/pancake/bundle-buy-first-quote.js +24 -8
  16. package/dist/chains/bsc/pancake/bundle-buy-first-types.d.ts +2 -0
  17. package/dist/chains/bsc/pancake/bundle-buy-first-validate.d.ts +1 -1
  18. package/dist/chains/bsc/pancake/bundle-buy-first-validate.js +2 -2
  19. package/dist/chains/bsc/pancake/bundle-buy-first.js +1 -0
  20. package/dist/chains/bsc/pancake/bundle-swap-types.d.ts +2 -0
  21. package/dist/chains/bsc/pancake/quote.d.ts +7 -0
  22. package/dist/chains/bsc/pancake/quote.js +9 -1
  23. package/dist/chains/bsc/pancake/swap-batch.js +15 -16
  24. package/dist/chains/bsc/pancake/swap-build.d.ts +1 -1
  25. package/dist/chains/bsc/pancake/swap-build.js +15 -9
  26. package/dist/chains/bsc/pancake/swap-quick.js +4 -4
  27. package/dist/chains/bsc/pancake/swap-single.js +1 -0
  28. package/dist/chains/bsc/platforms/iro/token.d.ts +1 -0
  29. package/dist/chains/bsc/platforms/iro/token.js +36 -1
  30. package/dist/chains/eni/batch-router/transfer-sweep.js +10 -14
  31. package/dist/chains/eni/platforms/daoaas/graduation.js +60 -14
  32. package/dist/chains/eni/platforms/daoaas/meta.d.ts +0 -4
  33. package/dist/chains/eni/platforms/daoaas/meta.js +112 -27
  34. package/dist/chains/eni/platforms/daoaas/portal-direct-submit.js +11 -6
  35. package/dist/chains/eni/platforms/daoaas/portal-direct-tx.js +4 -2
  36. package/dist/chains/eni/platforms/daoaas/portal.d.ts +5 -0
  37. package/dist/chains/eni/platforms/daoaas/portal.js +63 -9
  38. package/dist/chains/eni/platforms/dswap/liquidity.js +144 -49
  39. package/dist/chains/eni/platforms/fair-launch/launcher.js +2 -10
  40. package/dist/chains/eni/platforms/fair-launch/pool.d.ts +1 -0
  41. package/dist/chains/eni/platforms/fair-launch/pool.js +16 -2
  42. package/dist/chains/eni/platforms/iro/token.d.ts +1 -0
  43. package/dist/chains/eni/platforms/iro/token.js +36 -1
  44. package/dist/chains/robinhood/constants.d.ts +2 -5
  45. package/dist/chains/robinhood/constants.js +8 -7
  46. package/dist/chains/robinhood/longetf/launch.js +4 -4
  47. package/dist/chains/robinhood/pons-v2/launch.js +51 -20
  48. package/dist/chains/xlayer/eip7702/bundle-approve.js +8 -8
  49. package/dist/chains/xlayer/eip7702/bundle-sell.js +3 -4
  50. package/dist/chains/xlayer/eip7702/bundle-swap-batch.js +4 -2
  51. package/dist/chains/xlayer/eip7702/bundle-swap-multi.js +3 -3
  52. package/dist/chains/xlayer/eip7702/multi-hop-transfer-sweep.js +4 -10
  53. package/dist/chains/xlayer/eip7702/volume-bundle.js +45 -24
  54. package/dist/chains/xlayer/eoa/eoa-wash-volume.js +4 -14
  55. package/dist/core/constants/gas.d.ts +0 -8
  56. package/dist/core/constants/gas.js +0 -9
  57. package/dist/core/quote/quote-helpers.d.ts +11 -0
  58. package/dist/core/quote/quote-helpers.js +121 -20
  59. package/dist/core/quote/quote-helpers.test.js +11 -1
  60. package/dist/domains/dex/direct-router-v2-buy.js +4 -2
  61. package/dist/domains/dex/direct-router-v2-sell.js +63 -12
  62. package/dist/domains/dex/direct-router-v3.js +3 -4
  63. package/dist/domains/dex/quote.js +15 -5
  64. package/dist/domains/flap/portal-bundle-merkle/pancake-proxy/approve.js +48 -8
  65. package/dist/domains/flap/portal-bundle-merkle/swap-batch.js +8 -10
  66. package/dist/domains/flap/portal-bundle-merkle/swap-buy-first-quote.js +24 -9
  67. package/dist/domains/flap/portal-bundle-merkle/swap-planning.js +6 -2
  68. package/dist/domains/flap/portal-bundle-merkle/utils/sweep.js +14 -7
  69. package/dist/domains/flap/reader.d.ts +6 -0
  70. package/dist/domains/flap/reader.js +77 -1
  71. package/dist/domains/four/contracts/tm-bundle-batch.js +2 -10
  72. package/dist/domains/four/contracts/tm-bundle-create.js +5 -2
  73. package/dist/domains/four/contracts/tm.d.ts +2 -0
  74. package/dist/domains/four/contracts/tm.js +59 -2
  75. package/dist/domains/four/tax-token.js +60 -28
  76. package/dist/domains/lp-inspect/inspect-tax.js +98 -12
  77. package/dist/domains/lp-inspect/inspect.js +57 -44
  78. package/dist/domains/lp-inspect/registry.js +33 -7
  79. package/dist/domains/openfour/registry.js +15 -5
  80. package/dist/utils/airdrop-sweep.js +37 -17
  81. package/dist/utils/swap-helpers.js +35 -7
  82. package/package.json +1 -1
@@ -93,6 +93,42 @@ export async function batchGetBalances(provider, addresses, tokenAddress, chainI
93
93
  return Promise.all(fallbackCalls);
94
94
  }
95
95
  }
96
+ /** 同一批地址的 ERC20 余额和原生币余额,一次 aggregate3。 */
97
+ export async function batchGetTokenAndNativeBalances(provider, addresses, tokenAddress, chainId) {
98
+ if (addresses.length === 0)
99
+ return { token: [], native: [] };
100
+ const resolvedChainId = chainId ?? Number((await provider.getNetwork()).chainId);
101
+ const multicall3Address = getMulticall3Address(resolvedChainId);
102
+ const erc20 = new ethers.Interface(['function balanceOf(address) view returns (uint256)']);
103
+ const nativeIface = new ethers.Interface(MULTICALL3_ABI);
104
+ const calls = [
105
+ ...addresses.map((addr) => ({
106
+ target: tokenAddress,
107
+ allowFailure: true,
108
+ callData: erc20.encodeFunctionData('balanceOf', [addr]),
109
+ })),
110
+ ...addresses.map((addr) => ({
111
+ target: multicall3Address,
112
+ allowFailure: true,
113
+ callData: nativeIface.encodeFunctionData('getEthBalance', [addr]),
114
+ })),
115
+ ];
116
+ try {
117
+ const multicall = new ethers.Contract(multicall3Address, MULTICALL3_ABI, provider);
118
+ const results = (await multicall.aggregate3(calls));
119
+ return {
120
+ token: mapAggregate3Results(results.slice(0, addresses.length), (returnData) => erc20.decodeFunctionResult('balanceOf', returnData)[0], 0n),
121
+ native: mapAggregate3Results(results.slice(addresses.length), (returnData) => nativeIface.decodeFunctionResult('getEthBalance', returnData)[0], 0n),
122
+ };
123
+ }
124
+ catch {
125
+ const [token, native] = await Promise.all([
126
+ batchGetBalances(provider, addresses, tokenAddress, resolvedChainId),
127
+ batchGetBalances(provider, addresses, undefined, resolvedChainId),
128
+ ]);
129
+ return { token, native };
130
+ }
131
+ }
96
132
  export async function estimateErc20TransferGas(provider, tokenAddress, from, to, amount, bufferPercent = 5) {
97
133
  try {
98
134
  const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * PancakeSwap Buy-First 报价/资金/上下文 helper(从 bundle-buy-first-helpers.ts 抽取)
3
3
  */
4
- import { ethers, Contract } from 'ethers';
4
+ import { ethers } from 'ethers';
5
5
  import { createSignProvider } from '../../../core/sign-context-helpers.js';
6
6
  import { CHAINS } from '../../../core/constants/index.js';
7
+ import { batchGetTokenAndNativeBalances } from '../../../bundle/internal.js';
7
8
  import { splitAmountByWeight as splitAmount } from '../../../core/amount/split-by-weight.js';
8
- import { quoteV2, quoteV3 } from '../../../core/quote/quote-helpers.js';
9
+ import { quoteV2, quoteV2ManyAmounts, quoteV3 } from '../../../core/quote/quote-helpers.js';
9
10
  import { applyPancakeSlippage, applyTaxBps, pairSameWalletSellAmounts, prefixSumAmounts, sequentialBuyAmountOutMins, sequentialPrefixOutputs, } from './quote.js';
10
- import { ERC20_BALANCE_OF_ABI } from './bundle-buy-first-constants.js';
11
11
  /**
12
12
  * 获取 Gas Limit
13
13
  */
@@ -35,13 +35,14 @@ export async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercent
35
35
  const reserveGasWei = ethers.parseEther((reserveGas ?? 0.0005).toString());
36
36
  // ✅ 根据是否使用原生代币获取不同的余额
37
37
  let buyerBalance;
38
+ let buyerNativeBalance = 0n;
38
39
  if (useNativeToken) {
39
40
  buyerBalance = await buyer.provider.getBalance(buyer.address);
40
41
  }
41
42
  else {
42
- // ERC20 代币余额
43
- const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
44
- buyerBalance = await erc20.balanceOf(buyer.address);
43
+ const paired = await batchGetTokenAndNativeBalances((provider || buyer.provider), [buyer.address], quoteToken, CHAINS.BSC.chainId);
44
+ buyerBalance = paired.token[0] ?? 0n;
45
+ buyerNativeBalance = paired.native[0] ?? 0n;
45
46
  }
46
47
  let buyerFundsWei;
47
48
  if (buyerFunds !== undefined) {
@@ -78,12 +79,17 @@ export async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercent
78
79
  throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(buyerFundsWei, quoteTokenDecimals)},实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
79
80
  }
80
81
  // ✅ ERC20 购买时,还需要检查买方是否有足够 BNB 支付 Gas
81
- const buyerBnbBalance = await buyer.provider.getBalance(buyer.address);
82
+ const buyerBnbBalance = buyerNativeBalance;
82
83
  if (buyerBnbBalance < reserveGasWei) {
83
84
  throw new Error(`买方 BNB 余额不足 (用于支付 Gas): 需要 ${ethers.formatEther(reserveGasWei)} BNB,实际 ${ethers.formatEther(buyerBnbBalance)} BNB`);
84
85
  }
85
86
  }
86
- return { buyerFundsWei, buyerBalance, reserveGas: reserveGasWei };
87
+ return {
88
+ buyerFundsWei,
89
+ buyerBalance,
90
+ reserveGas: reserveGasWei,
91
+ nativeBalance: useNativeToken ? undefined : buyerNativeBalance,
92
+ };
87
93
  }
88
94
  /**
89
95
  * ✅ 使用 quote-helpers 统一报价
@@ -144,6 +150,16 @@ export function reverseBuyRouteParams(routeParams) {
144
150
  }
145
151
  export async function quotePrefixSequentialOuts(params) {
146
152
  const prefixes = prefixSumAmounts(params.amountsWei);
153
+ if (params.routeParams.routeType === 'v2') {
154
+ const path = params.routeParams.v2Path;
155
+ const batched = await quoteV2ManyAmounts(params.provider, path[0], path[path.length - 1], prefixes, 'BSC', params.v2RouterOverride);
156
+ if (batched) {
157
+ if (prefixes.some((sum, index) => sum > 0n && (batched[index] ?? 0n) <= 0n)) {
158
+ throw new Error('V2 报价失败');
159
+ }
160
+ return sequentialPrefixOutputs(batched);
161
+ }
162
+ }
147
163
  const prefixQuoted = await Promise.all(prefixes.map(async (sum) => {
148
164
  if (sum <= 0n)
149
165
  return 0n;
@@ -105,6 +105,7 @@ export type BuyerFundsResult = {
105
105
  buyerFundsWei: bigint;
106
106
  buyerBalance: bigint;
107
107
  reserveGas: bigint;
108
+ nativeBalance?: bigint;
108
109
  };
109
110
  export type QuoteTokenResult = {
110
111
  quotedTokenOut: bigint;
@@ -170,6 +171,7 @@ export type BalanceValidationParams = {
170
171
  quoteTokenDecimals?: number;
171
172
  provider?: ethers.JsonRpcProvider;
172
173
  buyerAddress?: string;
174
+ nativeBalance?: bigint;
173
175
  };
174
176
  export interface MultiWalletParams {
175
177
  buyerPrivateKeys: string[];
@@ -1,2 +1,2 @@
1
1
  import type { BalanceValidationParams } from './bundle-buy-first-types.js';
2
- export declare function validateFinalBalances({ sameAddress, buyerFundsWei, buyerBalance, reserveGas, gasLimit, gasPrice, useNativeToken, quoteTokenDecimals, provider, buyerAddress, }: BalanceValidationParams): Promise<void>;
2
+ export declare function validateFinalBalances({ sameAddress, buyerFundsWei, buyerBalance, reserveGas, gasLimit, gasPrice, useNativeToken, quoteTokenDecimals, provider, buyerAddress, nativeBalance, }: BalanceValidationParams): Promise<void>;
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { ethers } from 'ethers';
5
5
  import { FLAT_FEE } from './bundle-buy-first-constants.js';
6
- export async function validateFinalBalances({ sameAddress, buyerFundsWei, buyerBalance, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress, }) {
6
+ export async function validateFinalBalances({ sameAddress, buyerFundsWei, buyerBalance, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress, nativeBalance, }) {
7
7
  const gasCost = gasLimit * gasPrice;
8
8
  if (sameAddress) {
9
9
  // 同一地址:需要足够的余额支付两笔交易
@@ -21,7 +21,7 @@ export async function validateFinalBalances({ sameAddress, buyerFundsWei, buyerB
21
21
  }
22
22
  // 检查 BNB Gas
23
23
  if (provider && buyerAddress) {
24
- const bnbBalance = await provider.getBalance(buyerAddress);
24
+ const bnbBalance = nativeBalance ?? (await provider.getBalance(buyerAddress));
25
25
  const requiredGas = gasCost * 2n;
26
26
  if (bnbBalance < requiredGas) {
27
27
  throw new Error(`账户 BNB 余额不足 (用于支付 Gas):\n - 需要: ${ethers.formatEther(requiredGas)} BNB\n - 实际: ${ethers.formatEther(bnbBalance)} BNB`);
@@ -148,6 +148,7 @@ export async function pancakeBundleBuyFirstMerkle(params) {
148
148
  quoteTokenDecimals,
149
149
  provider: context.provider,
150
150
  buyerAddress: buyer.address,
151
+ nativeBalance: buyerFundsInfo.nativeBalance,
151
152
  });
152
153
  // ✅ 组装顺序:贿赂 → 买入(多笔) → 卖出(多笔)(与 Flap 一致)
153
154
  const allTransactions = [];
@@ -85,6 +85,7 @@ export type BalanceValidationParams = {
85
85
  quoteTokenDecimals?: number;
86
86
  provider?: ethers.JsonRpcProvider;
87
87
  buyerAddress?: string;
88
+ nativeBalance?: bigint;
88
89
  };
89
90
  export interface PancakeSwapSignConfig {
90
91
  rpcUrl: string;
@@ -179,6 +180,7 @@ export type BuyerBudget = {
179
180
  requiredBalance: bigint;
180
181
  buyAmountBNB: bigint;
181
182
  useNativeToken: boolean;
183
+ nativeBalance?: bigint;
182
184
  };
183
185
  export type NoncePlan = {
184
186
  sellerNonce: number;
@@ -41,6 +41,13 @@ export declare function isSameWalletBuySell(buyers: Array<{
41
41
  * quotedTotal 必须是卖出应得的计价币数量,不能传买入花费。
42
42
  */
43
43
  export declare function resolveBuyFirstSellMins(sameWallet: boolean, quotedTotal: bigint, sellAmounts: bigint[], slippageBps?: number): bigint[];
44
+ /** 多笔买入报价。V2 已选定路径时一次 multicall;V3 仍逐笔 eth_call。 */
45
+ export declare function quoteBuyOutputs(params: {
46
+ routeParams: RouteParams;
47
+ buyAmountsWei: readonly bigint[];
48
+ provider: ethers.JsonRpcProvider;
49
+ v2RouterAddress?: string;
50
+ }): Promise<bigint[]>;
44
51
  export declare function quoteBuyOutput(params: {
45
52
  routeParams: RouteParams;
46
53
  buyAmountWei: bigint;
@@ -1,5 +1,5 @@
1
1
  import { ADDRESSES, applySlippageBps, getProfitRateBps as getSharedProfitRateBps, calculateBundleSwapRoundTripProfit } from '../../../core/constants/index.js';
2
- import { quoteV2, quoteV3 } from '../../../core/quote/quote-helpers.js';
2
+ import { QUOTE_CONFIG, quoteV2, quoteV2SamePathAmounts, quoteV3 } from '../../../core/quote/quote-helpers.js';
3
3
  import { WBNB_ADDRESS, ZERO_ADDRESS, } from './bundle-swap-types.js';
4
4
  function normalizeAddr(addr) {
5
5
  return String(addr || '').trim().toLowerCase();
@@ -102,6 +102,14 @@ export function resolveBuyFirstSellMins(sameWallet, quotedTotal, sellAmounts, sl
102
102
  return sellAmounts.map(() => 0n);
103
103
  return splitQuotedMins(quotedTotal, sellAmounts, slippageBps);
104
104
  }
105
+ /** 多笔买入报价。V2 已选定路径时一次 multicall;V3 仍逐笔 eth_call。 */
106
+ export async function quoteBuyOutputs(params) {
107
+ const { routeParams, buyAmountsWei, provider, v2RouterAddress } = params;
108
+ if (routeParams.routeType === 'v2' && routeParams.v2Path.length >= 2) {
109
+ return quoteV2SamePathAmounts(provider, 'BSC', v2RouterAddress || QUOTE_CONFIG.BSC.v2Router, [...routeParams.v2Path].reverse(), buyAmountsWei);
110
+ }
111
+ return Promise.all(buyAmountsWei.map((buyAmountWei) => quoteBuyOutput({ routeParams, buyAmountWei, provider, v2RouterAddress })));
112
+ }
105
113
  export async function quoteBuyOutput(params) {
106
114
  const { routeParams, buyAmountWei, provider, v2RouterAddress } = params;
107
115
  if (buyAmountWei <= 0n)
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * swap-batch - 批量换手(一卖多买)
3
3
  */
4
- import { getProfitRecipient, BLOCKRAZOR_BUILDER_EOA } from '../../../core/constants/index.js';
4
+ import { getProfitRecipient, BLOCKRAZOR_BUILDER_EOA, CHAINS } from '../../../core/constants/index.js';
5
+ import { batchGetBalances } from '../../../bundle/internal.js';
5
6
  import { getGasLimit } from '../../../core/gas/bundle-gas.js';
6
7
  import { ethers, Contract, Wallet } from 'ethers';
7
8
  import { calculateSellAmount } from '../../../utils/swap-helpers.js';
8
9
  import { NonceManager, getDeadline, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
9
- import { BRIBE_GAS_LIMIT, ERC20_BALANCE_OF_ABI, FLAT_FEE, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, WBNB_ADDRESS, calculateProfitAmount, createPancakeContext, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, quoteSellOutput, quoteBuyOutput, applyPancakeSlippage, resolvePancakeQuoteMode, taxedSellQuoteInput, sequentialBuyAmountOutMins, } from './bundle-swap-helpers.js';
10
+ import { BRIBE_GAS_LIMIT, FLAT_FEE, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, WBNB_ADDRESS, calculateProfitAmount, createPancakeContext, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, quoteSellOutput, quoteBuyOutputs, applyPancakeSlippage, resolvePancakeQuoteMode, taxedSellQuoteInput, sequentialBuyAmountOutMins, } from './bundle-swap-helpers.js';
10
11
  /**
11
12
  * PancakeSwap 批量换手(一卖多买)
12
13
  *
@@ -76,32 +77,30 @@ export async function pancakeBatchSwapMerkle(params) {
76
77
  }
77
78
  // ✅ 并行验证所有买方余额
78
79
  const reserveGas = ethers.parseEther((config.reserveGasBNB ?? 0.0005).toString());
79
- const erc20Contract = useNativeToken ? null : new Contract(effectiveQuoteToken, ERC20_BALANCE_OF_ABI, context.provider);
80
- await Promise.all(buyers.map(async (buyer, i) => {
80
+ const buyerAddresses = buyers.map((buyer) => buyer.address);
81
+ const buyerBalances = await batchGetBalances(context.provider, buyerAddresses, useNativeToken ? undefined : effectiveQuoteToken, CHAINS.BSC.chainId);
82
+ buyerBalances.forEach((buyerBalance, i) => {
81
83
  const buyAmount = buyAmountsWei[i];
82
84
  if (useNativeToken) {
83
- const buyerBalance = await buyer.provider.getBalance(buyer.address);
84
85
  const required = buyAmount + FLAT_FEE + reserveGas;
85
86
  if (buyerBalance < required) {
86
87
  throw new Error(`买方 ${i + 1} 余额不足: 需要 ${ethers.formatEther(required)}, 实际 ${ethers.formatEther(buyerBalance)}`);
87
88
  }
89
+ return;
88
90
  }
89
- else {
90
- const buyerBalance = await erc20Contract.balanceOf(buyer.address);
91
- const required = buyAmount + FLAT_FEE;
92
- if (buyerBalance < required) {
93
- throw new Error(`买方 ${i + 1} 代币余额不足: 需要 ${ethers.formatUnits(required, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
94
- }
91
+ const required = buyAmount + FLAT_FEE;
92
+ if (buyerBalance < required) {
93
+ throw new Error(`买方 ${i + 1} 代币余额不足: 需要 ${ethers.formatUnits(required, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
95
94
  }
96
- }));
95
+ });
97
96
  const slippageBps = config.slippageBps ?? 100;
98
97
  const sellMinOut = applyPancakeSlippage(estimatedBNBOut, slippageBps);
99
- const buyQuotedOuts = await Promise.all(buyAmountsWei.map((amt) => quoteBuyOutput({
98
+ const buyQuotedOuts = await quoteBuyOutputs({
100
99
  routeParams,
101
- buyAmountWei: amt,
100
+ buyAmountsWei,
102
101
  provider: context.provider,
103
- v2RouterAddress: config.v2RouterAddress,
104
- })));
102
+ v2RouterAddress: config.v2RouterAddress || PANCAKE_V2_ROUTER_ADDRESS,
103
+ });
105
104
  const buyMinOuts = sequentialBuyAmountOutMins(buyQuotedOuts, taxRateBps, slippageBps);
106
105
  const deadline = getDeadline();
107
106
  const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
@@ -15,5 +15,5 @@ export declare function buildNoncePlanFromNonces(startNonces: number[], sameAddr
15
15
  * 贿赂和利润由卖方发送
16
16
  */
17
17
  export declare function planNonces({ seller, buyer, sameAddress, approvalExists, profitNeeded, needBribeTx, nonceManager, }: PlanNonceParams): Promise<NoncePlan>;
18
- export declare function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken, quoteTokenDecimals, provider, buyerAddress, }: BalanceValidationParams): Promise<void>;
18
+ export declare function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken, quoteTokenDecimals, provider, buyerAddress, nativeBalance, }: BalanceValidationParams): Promise<void>;
19
19
  export declare function countTruthy(values: Array<string | number | bigint | boolean | null | undefined>): number;
@@ -1,8 +1,9 @@
1
1
  import { ethers, Contract } from 'ethers';
2
2
  import { createSignProvider } from '../../../core/sign-context-helpers.js';
3
3
  import { CHAINS } from '../../../core/constants/index.js';
4
+ import { batchGetTokenAndNativeBalances } from '../../../bundle/internal.js';
4
5
  import { getDeadline } from '../../../utils/bundle-helpers.js';
5
- import { ERC20_BALANCE_OF_ABI, FLAT_FEE, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, } from './bundle-swap-types.js';
6
+ import { FLAT_FEE, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, } from './bundle-swap-types.js';
6
7
  export function createPancakeContext(config) {
7
8
  const chainId = config.chainId ?? CHAINS.BSC.chainId;
8
9
  const provider = createSignProvider(config.rpcUrl, chainId);
@@ -95,19 +96,24 @@ export async function calculateBuyerBudget({ buyer, quotedBNBOut, reserveGasBNB,
95
96
  return { buyerBalance, reserveGas, requiredBalance, buyAmountBNB, useNativeToken };
96
97
  }
97
98
  else {
98
- // ERC20 代币余额
99
- const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
100
- buyerBalance = await erc20.balanceOf(buyer.address);
99
+ const paired = await batchGetTokenAndNativeBalances((provider || buyer.provider), [buyer.address], quoteToken, CHAINS.BSC.chainId);
100
+ buyerBalance = paired.token[0] ?? 0n;
101
+ const buyerBnbBalance = paired.native[0] ?? 0n;
101
102
  const requiredBalance = buyAmountBNB + FLAT_FEE; // ERC20 不需要预留 Gas 在代币余额中
102
103
  if (buyerBalance < requiredBalance) {
103
104
  throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(requiredBalance, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
104
105
  }
105
- // ✅ ERC20 购买时,还需要检查买方是否有足够 BNB 支付 Gas
106
- const buyerBnbBalance = await buyer.provider.getBalance(buyer.address);
107
106
  if (buyerBnbBalance < reserveGas) {
108
107
  throw new Error(`买方 BNB 余额不足 (用于支付 Gas): 需要 ${ethers.formatEther(reserveGas)} BNB, 实际 ${ethers.formatEther(buyerBnbBalance)} BNB`);
109
108
  }
110
- return { buyerBalance, reserveGas, requiredBalance, buyAmountBNB, useNativeToken };
109
+ return {
110
+ buyerBalance,
111
+ reserveGas,
112
+ requiredBalance,
113
+ buyAmountBNB,
114
+ useNativeToken,
115
+ nativeBalance: buyerBnbBalance,
116
+ };
111
117
  }
112
118
  }
113
119
  /**
@@ -176,7 +182,7 @@ export async function planNonces({ seller, buyer, sameAddress, approvalExists, p
176
182
  ]);
177
183
  return { sellerNonce, buyerNonce };
178
184
  }
179
- export async function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress, }) {
185
+ export async function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress, nativeBalance, }) {
180
186
  const gasCost = gasLimit * gasPrice;
181
187
  if (sameAddress) {
182
188
  // 同一地址:需要足够的余额支付两笔交易
@@ -194,7 +200,7 @@ export async function validateFinalBalances({ sameAddress, buyerBalance, buyAmou
194
200
  }
195
201
  // 检查 BNB Gas
196
202
  if (provider && buyerAddress) {
197
- const bnbBalance = await provider.getBalance(buyerAddress);
203
+ const bnbBalance = nativeBalance ?? (await provider.getBalance(buyerAddress));
198
204
  const requiredGas = gasCost * 2n;
199
205
  if (bnbBalance < requiredGas) {
200
206
  throw new Error(`账户 BNB 余额不足 (用于支付 Gas):\n - 需要: ${ethers.formatEther(requiredGas)} BNB\n - 实际: ${ethers.formatEther(bnbBalance)} BNB`);
@@ -7,7 +7,7 @@ import { ethers, Contract, Wallet } from 'ethers';
7
7
  import { calculateSellAmount } from '../../../utils/swap-helpers.js';
8
8
  import { NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
9
9
  import { ERC20_ABI } from '../../../abis/common.js';
10
- import { BRIBE_GAS_LIMIT, FLAT_FEE, NATIVE_TRANSFER_GAS_LIMIT, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, WBNB_ADDRESS, buildBNBHopChainForERC20, buildERC20HopChain, buildNativeHopChain, calculateProfitAmount, createPancakeContext, generateDisperseHopPaths, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, quoteSellOutput, quoteBuyOutput, applyPancakeSlippage, sequentialBuyAmountOutMins, taxedSellQuoteInput, resolvePancakeQuoteMode, } from './bundle-swap-helpers.js';
10
+ import { BRIBE_GAS_LIMIT, FLAT_FEE, NATIVE_TRANSFER_GAS_LIMIT, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, WBNB_ADDRESS, buildBNBHopChainForERC20, buildERC20HopChain, buildNativeHopChain, calculateProfitAmount, createPancakeContext, generateDisperseHopPaths, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, quoteSellOutput, quoteBuyOutputs, applyPancakeSlippage, sequentialBuyAmountOutMins, taxedSellQuoteInput, resolvePancakeQuoteMode, } from './bundle-swap-helpers.js';
11
11
  /**
12
12
  * PancakeSwap 快捷批量换手(资金自动流转)
13
13
  *
@@ -189,12 +189,12 @@ export async function pancakeQuickBatchSwapMerkle(params) {
189
189
  }
190
190
  const slippageBps = config.slippageBps ?? 100;
191
191
  const sellMinOut = applyPancakeSlippage(estimatedOutput, slippageBps);
192
- const buyQuotedOuts = await Promise.all(transferAmountsWei.map((amt) => quoteBuyOutput({
192
+ const buyQuotedOuts = await quoteBuyOutputs({
193
193
  routeParams,
194
- buyAmountWei: amt,
194
+ buyAmountsWei: transferAmountsWei,
195
195
  provider: context.provider,
196
196
  v2RouterAddress: config.v2RouterAddress,
197
- })));
197
+ });
198
198
  const buyMinOuts = sequentialBuyAmountOutMins(buyQuotedOuts, taxRateBps, slippageBps);
199
199
  // ==================== 规划 Nonce ====================
200
200
  // ✅ 如果前端传入了 startNonces,直接使用(性能优化)
@@ -160,6 +160,7 @@ export async function pancakeBundleSwapMerkle(params) {
160
160
  quoteTokenDecimals,
161
161
  provider: context.provider,
162
162
  buyerAddress: buyer.address,
163
+ nativeBalance: buyerBudget.nativeBalance,
163
164
  });
164
165
  // ✅ 组装顺序:贿赂 → 卖出 → 买入
165
166
  const signedTransactions = [];
@@ -11,6 +11,7 @@ import type { IroSubscribeParams, IroPledgeParams, IroTxResult, IroTokenInfo, Ir
11
11
  export declare class IroTokenQuery {
12
12
  private provider;
13
13
  private token;
14
+ private chainId;
14
15
  readonly tokenAddress: string;
15
16
  constructor(tokenAddress: string, rpcUrl?: string, chainId?: number);
16
17
  name(): Promise<string>;
@@ -7,6 +7,8 @@
7
7
  * - 查询: price, creator, isOpen, getTokenPrice 等
8
8
  */
9
9
  import { Contract, Interface, Wallet, JsonRpcProvider } from 'ethers';
10
+ import { MULTICALL3_ABI } from '../../../../abis/common.js';
11
+ import { getMulticall3Address } from '../../../../core/constants/index.js';
10
12
  import { BSC_CHAIN_ID, BSC_RPC_URL } from '../../constants.js';
11
13
  import { IRO_TOKEN_ABI, SUBSCRIBE_GAS_LIMIT } from './constants.js';
12
14
  import { buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../../utils/bundle-helpers.js';
@@ -15,13 +17,28 @@ const tokenIface = new Interface(IRO_TOKEN_ABI);
15
17
  // ============================================================================
16
18
  // 查询
17
19
  // ============================================================================
20
+ const IRO_INFO_VIEWS = ['name', 'symbol', 'decimals', 'totalSupply', 'POOL', 'isOpen', 'price', 'creator', 'TKA'];
21
+ async function readIroInfoViews(provider, tokenAddress, chainId) {
22
+ const multicall = new Contract(getMulticall3Address(chainId), MULTICALL3_ABI, provider);
23
+ const aggregate3 = multicall.aggregate3;
24
+ if (!aggregate3)
25
+ throw new Error('aggregate3 missing');
26
+ const rows = (await aggregate3.staticCall(IRO_INFO_VIEWS.map((name) => ({
27
+ target: tokenAddress,
28
+ allowFailure: false,
29
+ callData: tokenIface.encodeFunctionData(name, []),
30
+ }))));
31
+ return IRO_INFO_VIEWS.map((name, index) => tokenIface.decodeFunctionResult(name, rows[index].returnData)[0]);
32
+ }
18
33
  export class IroTokenQuery {
19
34
  provider;
20
35
  token;
36
+ chainId;
21
37
  tokenAddress;
22
38
  constructor(tokenAddress, rpcUrl, chainId) {
23
39
  this.tokenAddress = tokenAddress;
24
- this.provider = new JsonRpcProvider(rpcUrl ?? BSC_RPC_URL, chainId ?? BSC_CHAIN_ID, { staticNetwork: true });
40
+ this.chainId = chainId ?? BSC_CHAIN_ID;
41
+ this.provider = new JsonRpcProvider(rpcUrl ?? BSC_RPC_URL, this.chainId, { staticNetwork: true });
25
42
  this.token = new Contract(tokenAddress, IRO_TOKEN_ABI, this.provider);
26
43
  }
27
44
  async name() {
@@ -93,6 +110,24 @@ export class IroTokenQuery {
93
110
  }
94
111
  /** 获取完整代币信息 */
95
112
  async getTokenInfo() {
113
+ try {
114
+ const row = await readIroInfoViews(this.provider, this.tokenAddress, this.chainId);
115
+ return {
116
+ name: String(row[0]),
117
+ symbol: String(row[1]),
118
+ decimals: Number(row[2]),
119
+ totalSupply: row[3],
120
+ tokenAddress: this.tokenAddress,
121
+ poolAddress: String(row[4]),
122
+ isOpen: Boolean(row[5]),
123
+ price: row[6],
124
+ creator: String(row[7]),
125
+ tka: String(row[8]),
126
+ };
127
+ }
128
+ catch {
129
+ /* 回退逐个 view */
130
+ }
96
131
  const [name, symbol, decimals, totalSupply, poolAddress, isOpen, priceVal, creatorAddr, tkaAddr] = await Promise.all([
97
132
  this.name(),
98
133
  this.symbol(),
@@ -5,11 +5,11 @@
5
5
  * - batchSweepEgas: 批量归集 EGAS (各钱包独立签名)
6
6
  * - eniSweepForSubmit: 前端统一入口 (EGAS / ERC20 归集)
7
7
  */
8
- import { Wallet, Contract, ethers } from 'ethers';
8
+ import { Wallet, ethers } from 'ethers';
9
9
  import { ENI_CHAIN_ID, ENI_RPC_URL } from '../constants.js';
10
10
  import { createEniProvider } from '../../../utils/provider-factory.js';
11
- import { ERC20_ABI } from '../../../abis/common.js';
12
11
  import { getProfitRecipient, calculateTransferFee } from '../../../core/constants/profit.js';
12
+ import { batchGetBalances, batchGetTokenAndNativeBalances } from '../../../bundle/internal.js';
13
13
  import { GAS_LIMITS } from '../../../core/constants/gas.js';
14
14
  import { erc20Iface, DEFAULT_GAS_PER_SWEEP, GAS_NATIVE_TRANSFER } from './transfer-shared.js';
15
15
  export async function batchSweepToken(params) {
@@ -76,10 +76,7 @@ export async function eniSweepForSubmit(params) {
76
76
  const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
77
77
  const gasForTransfer = GAS_NATIVE_TRANSFER * maxFeePerGas;
78
78
  if (isNative) {
79
- const balances = await Promise.all(sourcePrivateKeys.map(async (key) => {
80
- const wallet = new Wallet(key, provider);
81
- return provider.getBalance(wallet.address);
82
- }));
79
+ const balances = await batchGetBalances(provider, sourcePrivateKeys.map((key) => new Wallet(key).address), undefined, ENI_CHAIN_ID);
83
80
  const totalHopGasReserve = hopCount > 0 ? gasForTransfer * BigInt(hopCount) : 0n;
84
81
  const validKeys = [];
85
82
  const validAmounts = [];
@@ -131,17 +128,16 @@ export async function eniSweepForSubmit(params) {
131
128
  return { signedTransactions: signedTxs };
132
129
  }
133
130
  // ===== ERC20 归集 =====
134
- const erc20 = new Contract(tokenAddress, ERC20_ABI, provider);
135
131
  const erc20TransferGasCost = erc20GasLimit * maxFeePerGas;
136
132
  const hopGasCostPerWallet = hopCount > 0 ? gasForTransfer * BigInt(hopCount) + erc20GasLimit * maxFeePerGas * BigInt(hopCount) : 0n;
137
133
  const minNativeRequired = erc20TransferGasCost + hopGasCostPerWallet;
138
- const walletsData = await Promise.all(sourcePrivateKeys.map(async (key) => {
139
- const wallet = new Wallet(key, provider);
140
- const [tokenBal, nativeBal] = await Promise.all([
141
- erc20.balanceOf(wallet.address),
142
- provider.getBalance(wallet.address),
143
- ]);
144
- return { tokenBal, nativeBal };
134
+ const sourceAddresses = sourcePrivateKeys.map((key) => new Wallet(key).address);
135
+ const paired = await batchGetTokenAndNativeBalances(provider, sourceAddresses, tokenAddress, ENI_CHAIN_ID);
136
+ const tokenBals = paired.token;
137
+ const nativeBals = paired.native;
138
+ const walletsData = sourceAddresses.map((_, i) => ({
139
+ tokenBal: tokenBals[i] ?? 0n,
140
+ nativeBal: nativeBals[i] ?? 0n,
145
141
  }));
146
142
  const validKeys = [];
147
143
  const validAmounts = [];
@@ -8,6 +8,9 @@
8
8
  *
9
9
  * LPFairLauncher 创建的代币不经过 bonding curve,没有毕业阶段
10
10
  */
11
+ import { Contract, Interface, JsonRpcProvider } from 'ethers';
12
+ import { DAOAAS_PORTAL, DAOAAS_PORTAL_ABI, ENI_CHAIN_ID, ENI_MULTICALL3, ENI_RPC_URL } from '../../constants.js';
13
+ import { MULTICALL3_ABI } from '../../../../abis/common.js';
11
14
  import { DaoaasPortalQuery } from './portal.js';
12
15
  import { DaoaasTokenStatus } from './types.js';
13
16
  /**
@@ -28,20 +31,63 @@ export async function getGraduationInfo(token, config) {
28
31
  * 批量查询毕业状态
29
32
  */
30
33
  export async function batchGetGraduationInfo(tokens, config) {
31
- const results = [];
32
- for (const token of tokens) {
33
- try {
34
- results.push(await getGraduationInfo(token, config));
35
- }
36
- catch {
37
- results.push({
38
- token,
39
- status: DaoaasTokenStatus.NotCreated,
40
- isGraduated: false,
41
- isTrading: false,
42
- price: 0n,
43
- });
34
+ if (tokens.length === 0)
35
+ return [];
36
+ const rpcUrl = config?.rpcUrl ?? ENI_RPC_URL;
37
+ const portal = config?.portal ?? DAOAAS_PORTAL;
38
+ const multicall3 = config?.multicall3 ?? ENI_MULTICALL3;
39
+ const provider = new JsonRpcProvider(rpcUrl, ENI_CHAIN_ID, { staticNetwork: true });
40
+ const portalIface = new Interface(DAOAAS_PORTAL_ABI);
41
+ const empty = (token) => ({
42
+ token,
43
+ status: DaoaasTokenStatus.NotCreated,
44
+ isGraduated: false,
45
+ isTrading: false,
46
+ price: 0n,
47
+ });
48
+ try {
49
+ const multicall = new Contract(multicall3, MULTICALL3_ABI, provider);
50
+ const aggregate3 = multicall.aggregate3;
51
+ if (!aggregate3)
52
+ throw new Error('aggregate3 missing');
53
+ const calls = tokens.flatMap((token) => [
54
+ { target: portal, allowFailure: true, callData: portalIface.encodeFunctionData('getTokenState', [token]) },
55
+ { target: portal, allowFailure: true, callData: portalIface.encodeFunctionData('getPrice', [token]) },
56
+ ]);
57
+ const rows = (await aggregate3.staticCall(calls));
58
+ return tokens.map((token, index) => {
59
+ const stateRow = rows[index * 2];
60
+ const priceRow = rows[index * 2 + 1];
61
+ if (!stateRow?.success || stateRow.returnData === '0x')
62
+ return empty(token);
63
+ try {
64
+ const status = Number(portalIface.decodeFunctionResult('getTokenState', stateRow.returnData)[0]);
65
+ const price = priceRow?.success && priceRow.returnData !== '0x'
66
+ ? portalIface.decodeFunctionResult('getPrice', priceRow.returnData)[0]
67
+ : 0n;
68
+ return {
69
+ token,
70
+ status,
71
+ isGraduated: status === DaoaasTokenStatus.Graduated,
72
+ isTrading: status === DaoaasTokenStatus.Trading,
73
+ price,
74
+ };
75
+ }
76
+ catch {
77
+ return empty(token);
78
+ }
79
+ });
80
+ }
81
+ catch {
82
+ const results = [];
83
+ for (const token of tokens) {
84
+ try {
85
+ results.push(await getGraduationInfo(token, config));
86
+ }
87
+ catch {
88
+ results.push(empty(token));
89
+ }
44
90
  }
91
+ return results;
45
92
  }
46
- return results;
47
93
  }
@@ -25,8 +25,4 @@ export declare function getTokenMeta(token: string, rpcUrl?: string): Promise<Da
25
25
  * FairLaunch 代币的 logo 和 metadata 存储在 LaunchParams 中
26
26
  */
27
27
  export declare function getFairLaunchTokenMeta(token: string, rpcUrl?: string): Promise<DaoaasTokenMeta>;
28
- /**
29
- * 批量获取代币元数据(自动识别 Portal 或 FairLaunch 来源)
30
- * 对标 BSC: getFlapMetasByAddresses
31
- */
32
28
  export declare function getTokenMetasBatch(tokens: string[], rpcUrl?: string): Promise<DaoaasTokenMeta[]>;