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
@@ -2,7 +2,7 @@ import { ethers, Wallet } from 'ethers';
2
2
  import { getOptimizedGasPrice, NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../../utils/bundle-helpers.js';
3
3
  import { GAS_LIMITS, calculateTransferFee } from '../../../../core/constants/index.js';
4
4
  import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient } from '../config.js';
5
- import { batchGetBalances, calculateGasLimit, generateHopWallets, getErc20Decimals, isNativeTokenAddress, } from '../../../../bundle/internal.js';
5
+ import { batchGetBalances, batchGetTokenAndNativeBalances, calculateGasLimit, generateHopWallets, getErc20Decimals, isNativeTokenAddress, } from '../../../../bundle/internal.js';
6
6
  import { createSignProvider, resolveHopCountInput, resolveProvidedHops, } from '../../../../bundle/sign-context-helpers.js';
7
7
  /**
8
8
  * Flap Protocol: 归集(仅签名版本)
@@ -164,12 +164,13 @@ export async function flapSweepWithBundleMerkle(params) {
164
164
  }
165
165
  else {
166
166
  // ERC20 归集
167
- const [gasPrice, decimals, balances, nativeBalances] = await Promise.all([
167
+ const [gasPrice, decimals, paired] = await Promise.all([
168
168
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
169
169
  Promise.resolve(tokenDecimals ?? (await getErc20Decimals(provider, tokenAddress))),
170
- batchGetBalances(provider, addresses, tokenAddress),
171
- batchGetBalances(provider, addresses),
170
+ batchGetTokenAndNativeBalances(provider, addresses, tokenAddress, chainIdNum),
172
171
  ]);
172
+ const balances = paired.token;
173
+ const nativeBalances = paired.native;
173
174
  const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
174
175
  const profitTxGas = GAS_LIMITS.NATIVE_TRANSFER * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
175
176
  const sweepAmounts = [];
@@ -298,14 +299,20 @@ export async function flapSweepWithBundleMerkle(params) {
298
299
  }
299
300
  }
300
301
  const sourceAddresses = sourceWallets.map((w) => w.address);
301
- const [gasPrice, decimals, balances, nativeBalances] = await Promise.all([
302
+ const [gasPrice, decimals, paired] = await Promise.all([
302
303
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
303
304
  isNative
304
305
  ? Promise.resolve(18)
305
306
  : Promise.resolve(tokenDecimals ?? (await getErc20Decimals(provider, tokenAddress))),
306
- batchGetBalances(provider, sourceAddresses, tokenAddress),
307
- isNative ? Promise.resolve([]) : batchGetBalances(provider, sourceAddresses),
307
+ isNative || !tokenAddress
308
+ ? batchGetBalances(provider, sourceAddresses, tokenAddress, chainIdNum).then((token) => ({
309
+ token,
310
+ native: [],
311
+ }))
312
+ : batchGetTokenAndNativeBalances(provider, sourceAddresses, tokenAddress, chainIdNum),
308
313
  ]);
314
+ const balances = paired.token;
315
+ const nativeBalances = paired.native;
309
316
  const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
310
317
  const gasFeePerHop = finalGasLimit * gasPrice;
311
318
  const nonceManager = new NonceManager(provider);
@@ -10,6 +10,7 @@ export declare class FlapPortal {
10
10
  protected contract: Contract;
11
11
  private buyFeeRate;
12
12
  private sellFeeRate;
13
+ private chainId;
13
14
  constructor(cfg: PortalConfig);
14
15
  getTokenV2(token: string): Promise<TokenStateV2>;
15
16
  getTokenV3(token: string): Promise<TokenStateV3>;
@@ -26,6 +27,11 @@ export declare class FlapPortal {
26
27
  * 用 V7 读会把残留 taxRate(常见 100 bps)当成当前税。
27
28
  */
28
29
  getTokenV8(token: string): Promise<TokenStateV8>;
30
+ /**
31
+ * V8 / V7 / V5 一次 aggregate3,按可用版本取最高的一份。
32
+ * BSC 先看 V8;其它链先看 V7。
33
+ */
34
+ readPreferredTokenState(token: string, preferV8: boolean): Promise<TokenStateV5 | TokenStateV7 | TokenStateV8 | null>;
29
35
  getTokenV8Safe(token: string): Promise<TokenStateV8>;
30
36
  /**
31
37
  * 计算距离毕业还需多少 OKB/ETH(含手续费)
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Flap Portal — 只读查询(reader)
3
3
  */
4
- import { Contract, JsonRpcProvider, formatEther, FixedNumber } from 'ethers';
4
+ import { Contract, Interface, JsonRpcProvider, formatEther, FixedNumber } from 'ethers';
5
+ import { MULTICALL3_ABI } from '../../abis/common.js';
6
+ import { CHAINS, getMulticall3Address } from '../../core/constants/index.js';
5
7
  import { CDPV2 } from './curve.js';
6
8
  import { FLAP_PORTAL_ADDRESSES, FLAP_DEFAULT_FEE_RATES } from './constants.js';
7
9
  // Portal ABI (使用 ethers 标准格式)
@@ -52,6 +54,7 @@ export class FlapPortal {
52
54
  contract;
53
55
  buyFeeRate;
54
56
  sellFeeRate;
57
+ chainId;
55
58
  constructor(cfg) {
56
59
  this.rpcUrl = cfg.rpcUrl;
57
60
  this.portal = cfg.addressOverride ?? FLAP_PORTAL_ADDRESSES[cfg.chain];
@@ -59,6 +62,7 @@ export class FlapPortal {
59
62
  const defaultFees = FLAP_DEFAULT_FEE_RATES[cfg.chain];
60
63
  this.buyFeeRate = cfg.buyFeeRate ?? defaultFees.buy;
61
64
  this.sellFeeRate = cfg.sellFeeRate ?? defaultFees.sell;
65
+ this.chainId = CHAINS[cfg.chain]?.chainId ?? CHAINS.BSC.chainId;
62
66
  // 使用 ethers v6 创建只读合约实例
63
67
  const provider = new JsonRpcProvider(this.rpcUrl);
64
68
  this.contract = new Contract(this.portal, PORTAL_ABI, provider);
@@ -181,6 +185,78 @@ export class FlapPortal {
181
185
  const raw = await this.contract.getTokenV8(token);
182
186
  return this.mapTokenStateV8(raw);
183
187
  }
188
+ /**
189
+ * V8 / V7 / V5 一次 aggregate3,按可用版本取最高的一份。
190
+ * BSC 先看 V8;其它链先看 V7。
191
+ */
192
+ async readPreferredTokenState(token, preferV8) {
193
+ const names = preferV8
194
+ ? ['getTokenV8Safe', 'getTokenV8', 'getTokenV7', 'getTokenV5']
195
+ : ['getTokenV7', 'getTokenV5'];
196
+ const portalIface = new Interface(PORTAL_ABI);
197
+ const multicall = new Contract(getMulticall3Address(this.chainId), MULTICALL3_ABI, this.contract.runner);
198
+ const aggregate3 = multicall.aggregate3;
199
+ if (!aggregate3)
200
+ return null;
201
+ const rows = (await aggregate3.staticCall(names.map((name) => ({
202
+ target: this.portal,
203
+ allowFailure: true,
204
+ callData: portalIface.encodeFunctionData(name, [token]),
205
+ }))));
206
+ for (let index = 0; index < names.length; index++) {
207
+ const row = rows[index];
208
+ const name = names[index];
209
+ if (!row?.success || !name || row.returnData === '0x')
210
+ continue;
211
+ try {
212
+ const decoded = portalIface.decodeFunctionResult(name, row.returnData);
213
+ const first = decoded[0];
214
+ const raw = (first && typeof first === 'object' && 'length' in first && first[1] !== undefined
215
+ ? first
216
+ : decoded);
217
+ if (name === 'getTokenV8Safe' || name === 'getTokenV8')
218
+ return this.mapTokenStateV8(raw);
219
+ if (name === 'getTokenV7') {
220
+ return {
221
+ status: raw[0],
222
+ reserve: raw[1],
223
+ circulatingSupply: raw[2],
224
+ price: raw[3],
225
+ tokenVersion: raw[4],
226
+ r: raw[5],
227
+ h: raw[6],
228
+ k: raw[7],
229
+ dexSupplyThresh: raw[8],
230
+ quoteTokenAddress: raw[9],
231
+ nativeToQuoteSwapEnabled: raw[10],
232
+ extensionID: raw[11],
233
+ taxRate: raw[12],
234
+ pool: raw[13],
235
+ progress: raw[14],
236
+ lpFeeProfile: Number(raw[15]),
237
+ };
238
+ }
239
+ return {
240
+ status: raw[0],
241
+ reserve: raw[1],
242
+ circulatingSupply: raw[2],
243
+ price: raw[3],
244
+ tokenVersion: raw[4],
245
+ r: raw[5],
246
+ h: raw[6],
247
+ k: raw[7],
248
+ dexSupplyThresh: raw[8],
249
+ quoteTokenAddress: raw[9],
250
+ nativeToQuoteSwapEnabled: raw[10],
251
+ extensionID: raw[11],
252
+ };
253
+ }
254
+ catch {
255
+ continue;
256
+ }
257
+ }
258
+ return null;
259
+ }
184
260
  async getTokenV8Safe(token) {
185
261
  try {
186
262
  const raw = await this.contract.getTokenV8Safe(token);
@@ -6,7 +6,7 @@ import { Club48Client, BUILDER_CONTROL_EOA, } from '../../../core/clients/club48
6
6
  import { ADDRESSES, CHAINS } from '../../../core/constants/index.js';
7
7
  import { GAS_LIMITS, getProfitRateBps, getProfitRecipient } from '../../../core/constants/index.js';
8
8
  import { buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../utils/bundle-helpers.js';
9
- import { trySell } from './tm.js';
9
+ import { trySellFundsBatch } from './tm.js';
10
10
  import { TM2_ABI, getErrorMessage, } from './tm-bundle-helpers.js';
11
11
  /**
12
12
  * four.meme: 批量购买
@@ -176,15 +176,7 @@ export async function batchSellWithBundle(params) {
176
176
  const wallets = privateKeys.map((pk) => new Wallet(pk, provider));
177
177
  const amountsWei = sellAmounts.map((a) => ethers.parseUnits(a, 18));
178
178
  // 并行预估所有卖出能获得的 BNB
179
- const estimatedBnbOuts = await Promise.all(amountsWei.map(async (amountWei) => {
180
- try {
181
- const result = await trySell('BSC', config.rpcUrl, tokenAddress, amountWei);
182
- return result.funds;
183
- }
184
- catch {
185
- return 0n;
186
- }
187
- }));
179
+ const estimatedBnbOuts = await trySellFundsBatch('BSC', config.rpcUrl, tokenAddress, amountsWei);
188
180
  // 找出收益最高的卖家(由他支付所有利润)
189
181
  let maxSellerIndex = 0;
190
182
  let maxBnbOut = estimatedBnbOuts[0];
@@ -10,6 +10,7 @@ import { FourClient, buildLoginMessage } from '../../../core/clients/four.js';
10
10
  import { consoleSdkLogger, getErrorMessageFromUnknown } from '../../../core/index.js';
11
11
  import { ERC20_ABI } from '../../../abis/common.js';
12
12
  import { computeFourCreateValueWei, isNativeRaisedToken } from '../raised-token.js';
13
+ import { readQuoteDecimalsAndAllowance } from '../../../bundle/four-meme/core/create-token-quote.js';
13
14
  import { TM2_ABI, getErrorMessage, waitForBundleWithProvider, } from './tm-bundle-helpers.js';
14
15
  const sdkLogger = consoleSdkLogger;
15
16
  export async function createTokenWithBundleBuy(params) {
@@ -128,9 +129,11 @@ export async function createTokenWithBundleBuy(params) {
128
129
  const preSaleStr = tokenInfo.preSale || '0';
129
130
  if (!isNativeRaisedToken(createRaised) && preSaleStr !== '0') {
130
131
  const quote = new ethers.Contract(createRaised.symbolAddress, ERC20_ABI, devWallet);
131
- const decimals = Number(await quote.decimals());
132
+ const provider = devWallet.provider;
133
+ if (!provider)
134
+ throw new Error('wallet has no provider');
135
+ const { decimals, allowance } = await readQuoteDecimalsAndAllowance(provider, createRaised.symbolAddress, devWallet.address, tmAddr);
132
136
  const amount = ethers.parseUnits(preSaleStr, decimals);
133
- const allowance = await quote.allowance(devWallet.address, tmAddr);
134
137
  if (allowance < amount) {
135
138
  const approveUnsigned = await quote.approve.populateTransaction(tmAddr, amount);
136
139
  const signedApprove = await devWallet.signTransaction({
@@ -33,6 +33,8 @@ export type TrySellResult = {
33
33
  fee: bigint;
34
34
  };
35
35
  export declare function trySell(chain: 'BSC' | 'BASE' | 'ARBITRUM_ONE', rpcUrl: string, token: string, amount: bigint, proxyAddressOverride?: string): Promise<TrySellResult>;
36
+ /** 多笔 Helper3.trySell 收成一次 aggregate3。单笔仍走 trySell。失败的金额记 0。 */
37
+ export declare function trySellFundsBatch(chain: 'BSC' | 'BASE' | 'ARBITRUM_ONE', rpcUrl: string, token: string, amounts: bigint[]): Promise<bigint[]>;
36
38
  export declare function buyTokenWithFunds(chain: 'BSC', rpcUrl: string, signerPrivateKey: string, token: string, funds: bigint, minAmount: bigint, to?: string, origin?: bigint): Promise<ContractTransactionReceipt | null>;
37
39
  export declare function sellToken(chain: 'BSC', rpcUrl: string, signerPrivateKey: string, token: string, amount: bigint, minFunds: bigint, origin?: bigint): Promise<ContractTransactionReceipt | null>;
38
40
  export declare function tradeBuy(chain: 'BSC' | 'BASE' | 'ARBITRUM_ONE', rpcUrl: string, token: string, params: {
@@ -1,7 +1,8 @@
1
- import { Contract, Wallet } from 'ethers';
1
+ import { Contract, Interface, Wallet } from 'ethers';
2
2
  import TM2Abi from '../../../abis/contracts/TokenManager2.json' with { type: 'json' };
3
3
  import Helper3Abi from '../../../abis/contracts/TokenManagerHelper3.json' with { type: 'json' };
4
- import { ADDRESSES, ZERO_ADDRESS } from '../../../core/constants/index.js';
4
+ import { ADDRESSES, CHAINS, ZERO_ADDRESS, getMulticall3Address } from '../../../core/constants/index.js';
5
+ import { MULTICALL3_ABI } from '../../../abis/common.js';
5
6
  import { createBscProvider } from '../../../utils/provider-factory.js';
6
7
  import { TM1 } from './tm1.js';
7
8
  import { TM2 } from './tm2.js';
@@ -97,6 +98,62 @@ export async function trySell(chain, rpcUrl, token, amount, proxyAddressOverride
97
98
  fee: r.fee,
98
99
  };
99
100
  }
101
+ const TRY_SELL_ABI = [
102
+ 'function trySell(address token, uint256 amount) view returns (address tokenManager, address quote, uint256 funds, uint256 fee)',
103
+ ];
104
+ /** 多笔 Helper3.trySell 收成一次 aggregate3。单笔仍走 trySell。失败的金额记 0。 */
105
+ export async function trySellFundsBatch(chain, rpcUrl, token, amounts) {
106
+ if (amounts.length === 0)
107
+ return [];
108
+ if (amounts.length === 1) {
109
+ try {
110
+ return [(await trySell(chain, rpcUrl, token, amounts[0])).funds];
111
+ }
112
+ catch {
113
+ return [0n];
114
+ }
115
+ }
116
+ const helper3Addresses = {
117
+ BSC: ADDRESSES.BSC.TokenManagerHelper3,
118
+ BASE: ADDRESSES.BASE.TokenManagerHelper3,
119
+ ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3,
120
+ };
121
+ const provider = createBscProvider(rpcUrl);
122
+ const iface = new Interface(TRY_SELL_ABI);
123
+ const multicall = new Contract(getMulticall3Address(CHAINS[chain].chainId), MULTICALL3_ABI, provider);
124
+ const calls = amounts.map((amount) => ({
125
+ target: helper3Addresses[chain],
126
+ allowFailure: true,
127
+ callData: iface.encodeFunctionData('trySell', [token, amount]),
128
+ }));
129
+ try {
130
+ const aggregate3 = multicall.aggregate3;
131
+ if (!aggregate3)
132
+ throw new Error('aggregate3 missing');
133
+ const rows = (await aggregate3.staticCall(calls));
134
+ return rows.map((row) => {
135
+ if (!row.success || row.returnData === '0x')
136
+ return 0n;
137
+ try {
138
+ const decoded = iface.decodeFunctionResult('trySell', row.returnData);
139
+ return BigInt(decoded.funds ?? decoded[2]);
140
+ }
141
+ catch {
142
+ return 0n;
143
+ }
144
+ });
145
+ }
146
+ catch {
147
+ return Promise.all(amounts.map(async (amount) => {
148
+ try {
149
+ return (await trySell(chain, rpcUrl, token, amount)).funds;
150
+ }
151
+ catch {
152
+ return 0n;
153
+ }
154
+ }));
155
+ }
156
+ }
100
157
  export async function buyTokenWithFunds(chain, rpcUrl, signerPrivateKey, token, funds, minAmount, to, origin = 0n) {
101
158
  const provider = createBscProvider(rpcUrl);
102
159
  const signer = new Wallet(signerPrivateKey, provider);
@@ -4,9 +4,10 @@
4
4
  * 提供 TaxToken 的识别、状态查询、奖励领取等功能。
5
5
  * 基于 Four.meme 官方文档: API-Contract-TaxToken.md
6
6
  */
7
- import { ethers, Contract, Wallet, JsonRpcProvider } from 'ethers';
7
+ import { ethers, Contract, Interface, Wallet, JsonRpcProvider } from 'ethers';
8
8
  import TaxTokenAbi from '../../abis/contracts/TaxToken.json' with { type: 'json' };
9
- import { ADDRESSES, CHAINS } from '../../core/constants/index.js';
9
+ import { MULTICALL3_ABI } from '../../abis/common.js';
10
+ import { ADDRESSES, CHAINS, getMulticall3Address } from '../../core/constants/index.js';
10
11
  // ============================================================================
11
12
  // 常量
12
13
  // ============================================================================
@@ -121,32 +122,63 @@ export async function getCreatorType(tokenAddress, rpcUrl) {
121
122
  */
122
123
  export async function getTaxTokenInfo(tokenAddress, rpcUrl) {
123
124
  const provider = new JsonRpcProvider(rpcUrl);
124
- const token = new Contract(tokenAddress, TAX_TOKEN_READ_ABI, provider);
125
- // 批量查询所有状态(利用 Promise.all 减少 RPC 调用轮次)
126
- const [feeRate, rateFounder, rateHolder, rateBurn, rateLiquidity, founder, quote, pair, mode, minDispatch, minShare, totalShares, feePerShare, feeAccumulated, feeDispatched, feeFounderVal, feeHolderVal, feeBurnVal, feeLiquidityVal, quoteFounderVal, quoteHolderVal, quoteClaimed,] = await Promise.all([
127
- token.feeRate(),
128
- token.rateFounder(),
129
- token.rateHolder(),
130
- token.rateBurn(),
131
- token.rateLiquidity(),
132
- token.founder(),
133
- token.quote(),
134
- token.pair(),
135
- token._mode(),
136
- token.minDispatch(),
137
- token.minShare(),
138
- token.totalShares(),
139
- token.feePerShare(),
140
- token.feeAccumulated(),
141
- token.feeDispatched(),
142
- token.feeFounder(),
143
- token.feeHolder(),
144
- token.feeBurn(),
145
- token.feeLiquidity(),
146
- token.quoteFounder(),
147
- token.quoteHolder(),
148
- token.quoteClaimed(),
149
- ]);
125
+ const viewNames = [
126
+ 'feeRate',
127
+ 'rateFounder',
128
+ 'rateHolder',
129
+ 'rateBurn',
130
+ 'rateLiquidity',
131
+ 'founder',
132
+ 'quote',
133
+ 'pair',
134
+ '_mode',
135
+ 'minDispatch',
136
+ 'minShare',
137
+ 'totalShares',
138
+ 'feePerShare',
139
+ 'feeAccumulated',
140
+ 'feeDispatched',
141
+ 'feeFounder',
142
+ 'feeHolder',
143
+ 'feeBurn',
144
+ 'feeLiquidity',
145
+ 'quoteFounder',
146
+ 'quoteHolder',
147
+ 'quoteClaimed',
148
+ ];
149
+ const tokenIface = new Interface(TAX_TOKEN_READ_ABI);
150
+ const multicall = new Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
151
+ const aggregate3 = multicall.aggregate3;
152
+ if (!aggregate3)
153
+ throw new Error('aggregate3 missing');
154
+ const rows = (await aggregate3.staticCall(viewNames.map((name) => ({
155
+ target: tokenAddress,
156
+ allowFailure: false,
157
+ callData: tokenIface.encodeFunctionData(name, []),
158
+ }))));
159
+ const read = (index) => tokenIface.decodeFunctionResult(viewNames[index], rows[index].returnData)[0];
160
+ const feeRate = read(0);
161
+ const rateFounder = read(1);
162
+ const rateHolder = read(2);
163
+ const rateBurn = read(3);
164
+ const rateLiquidity = read(4);
165
+ const founder = read(5);
166
+ const quote = read(6);
167
+ const pair = read(7);
168
+ const mode = read(8);
169
+ const minDispatch = read(9);
170
+ const minShare = read(10);
171
+ const totalShares = read(11);
172
+ const feePerShare = read(12);
173
+ const feeAccumulated = read(13);
174
+ const feeDispatched = read(14);
175
+ const feeFounderVal = read(15);
176
+ const feeHolderVal = read(16);
177
+ const feeBurnVal = read(17);
178
+ const feeLiquidityVal = read(18);
179
+ const quoteFounderVal = read(19);
180
+ const quoteHolderVal = read(20);
181
+ const quoteClaimed = read(21);
150
182
  return {
151
183
  config: {
152
184
  feeRate: BigInt(feeRate),
@@ -2,8 +2,9 @@
2
2
  * LP inspect 税率:Flap 真实税 / Four TaxToken / 外盘常见 view。
3
3
  * 非 Flap 币 Portal 槽位常残留 100 bps,不能当 Pancake 税币税率。
4
4
  */
5
- import { Contract } from 'ethers';
6
- import { ZERO_ADDRESS } from '../../core/constants/index.js';
5
+ import { Contract, Interface } from 'ethers';
6
+ import { ADDRESSES, CHAINS, ZERO_ADDRESS, getMulticall3Address } from '../../core/constants/index.js';
7
+ import { MULTICALL3_ABI } from '../../abis/common.js';
7
8
  import { TokenStatus } from '../flap/portal.js';
8
9
  import { getTaxTokenInfo, isTaxToken } from '../four/tax-token.js';
9
10
  import { getProvider } from './inspect-helpers.js';
@@ -51,24 +52,109 @@ export function flapV8TaxBps(st) {
51
52
  return 0;
52
53
  return Math.max(Number(st.buyTaxRate ?? 0n), Number(st.sellTaxRate ?? 0n));
53
54
  }
55
+ function maxBpsFromTaxReads(values) {
56
+ let maxBps = 0;
57
+ for (const value of values) {
58
+ if (typeof value !== 'number' || value === 100)
59
+ continue;
60
+ if (value > maxBps)
61
+ maxBps = value;
62
+ }
63
+ return maxBps > 0 ? maxBps : undefined;
64
+ }
54
65
  async function readGenericTokenTaxBps(token, provider) {
66
+ const tokenIface = new Interface(GENERIC_TAX_ABI);
67
+ const multicall = new Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
68
+ const aggregate3 = multicall.aggregate3;
69
+ if (aggregate3) {
70
+ try {
71
+ const rows = (await aggregate3.staticCall(GENERIC_TAX_FNS.map((name) => ({
72
+ target: token,
73
+ allowFailure: true,
74
+ callData: tokenIface.encodeFunctionData(name, []),
75
+ }))));
76
+ return maxBpsFromTaxReads(rows.map((row, index) => {
77
+ const name = GENERIC_TAX_FNS[index];
78
+ if (!name || !row.success || row.returnData === '0x')
79
+ return undefined;
80
+ try {
81
+ const value = tokenIface.decodeFunctionResult(name, row.returnData)[0];
82
+ return normalizeFeeToBps(Number(value));
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ }));
88
+ }
89
+ catch {
90
+ /* 回退逐个 view */
91
+ }
92
+ }
55
93
  const tokenContract = new Contract(token, GENERIC_TAX_ABI, provider);
56
94
  const reads = await Promise.allSettled(GENERIC_TAX_FNS.map(async (name) => {
57
95
  const value = await tokenContract.getFunction(name).staticCall();
58
96
  return normalizeFeeToBps(Number(value));
59
97
  }));
60
- let maxBps = 0;
61
- for (const item of reads) {
62
- if (item.status !== 'fulfilled' || typeof item.value !== 'number')
63
- continue;
64
- if (item.value === 100)
65
- continue;
66
- if (item.value > maxBps)
67
- maxBps = item.value;
98
+ return maxBpsFromTaxReads(reads.map((item) => (item.status === 'fulfilled' ? item.value : undefined)));
99
+ }
100
+ const TOKEN_INFO_ABI = ['function _tokenInfos(address) view returns (uint256 template)'];
101
+ /** Four 模板位和通用税率 view 一次读完。模板位是税币时只用 feeRate。 */
102
+ async function readOuterTaxBundle(token, provider) {
103
+ const tmIface = new Interface(TOKEN_INFO_ABI);
104
+ const tokenIface = new Interface(GENERIC_TAX_ABI);
105
+ const multicall = new Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
106
+ const aggregate3 = multicall.aggregate3;
107
+ if (!aggregate3)
108
+ throw new Error('aggregate3 missing');
109
+ const rows = (await aggregate3.staticCall([
110
+ {
111
+ target: ADDRESSES.BSC.TokenManagerOriginal,
112
+ allowFailure: true,
113
+ callData: tmIface.encodeFunctionData('_tokenInfos', [token]),
114
+ },
115
+ ...GENERIC_TAX_FNS.map((name) => ({
116
+ target: token,
117
+ allowFailure: true,
118
+ callData: tokenIface.encodeFunctionData(name, []),
119
+ })),
120
+ ]));
121
+ const templateRow = rows[0];
122
+ let isTax = false;
123
+ if (templateRow?.success && templateRow.returnData !== '0x') {
124
+ const template = tmIface.decodeFunctionResult('_tokenInfos', templateRow.returnData)[0];
125
+ isTax = Number((template >> 10n) & 0x3fn) === 5;
68
126
  }
69
- return maxBps > 0 ? maxBps : undefined;
127
+ const rawFees = GENERIC_TAX_FNS.map((name, index) => {
128
+ const row = rows[index + 1];
129
+ if (!name || !row?.success || row.returnData === '0x')
130
+ return undefined;
131
+ try {
132
+ return Number(tokenIface.decodeFunctionResult(name, row.returnData)[0]);
133
+ }
134
+ catch {
135
+ return undefined;
136
+ }
137
+ });
138
+ if (isTax) {
139
+ const fee = rawFees[0];
140
+ if (typeof fee === 'number' && Number.isFinite(fee) && fee > 0 && fee <= 10000)
141
+ return Math.round(fee);
142
+ if (typeof fee === 'number') {
143
+ const bps = normalizeFeeToBps(fee);
144
+ if (bps)
145
+ return bps;
146
+ }
147
+ }
148
+ return maxBpsFromTaxReads(rawFees.map((fee) => (typeof fee === 'number' ? normalizeFeeToBps(fee) : undefined)));
70
149
  }
71
150
  export async function readBscOuterTaxBps(token, rpcUrl) {
151
+ const provider = getProvider(rpcUrl);
152
+ try {
153
+ return await readOuterTaxBundle(token, provider);
154
+ }
155
+ catch {
156
+ /* 回退分开读 */
157
+ }
72
158
  try {
73
159
  if (await isTaxToken(token, rpcUrl)) {
74
160
  const fee = Number((await getTaxTokenInfo(token, rpcUrl)).config.feeRate);
@@ -82,5 +168,5 @@ export async function readBscOuterTaxBps(token, rpcUrl) {
82
168
  catch {
83
169
  /* 非 Four TaxToken */
84
170
  }
85
- return readGenericTokenTaxBps(token, getProvider(rpcUrl));
171
+ return readGenericTokenTaxBps(token, provider);
86
172
  }