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
@@ -4,8 +4,8 @@
4
4
  * inspectTokenLP 主查询流程。V2/V3 批量查询、fallback、平台判断与格式化工具
5
5
  * 已抽取到 inspect-helpers.ts(内部 helper,不对外暴露)。
6
6
  */
7
- import { Contract, formatEther } from 'ethers';
8
- import { ADDRESSES, ZERO_ADDRESS } from '../../core/constants/index.js';
7
+ import { Contract, Interface, formatEther } from 'ethers';
8
+ import { ADDRESSES, CHAINS, ZERO_ADDRESS, getMulticall3Address } from '../../core/constants/index.js';
9
9
  import { Helper3 } from '../four/contracts/helper3.js';
10
10
  import { FlapPortal } from '../flap/portal.js';
11
11
  import { DEFAULT_V3_FEE_TIERS } from './lp-inspect-helpers.js';
@@ -13,6 +13,16 @@ import { getChainConfig } from './registry.js';
13
13
  import { determinePlatform, formatBalance, getProvider, getTokenDecimals, normalizeLiquidity, queryV2Pairs, queryV3Pools, } from './inspect-helpers.js';
14
14
  import { flapV8TaxBps, isFlapInnerMarket, readBscOuterTaxBps } from './inspect-tax.js';
15
15
  const MULTICALL3_ADDRESS = ADDRESSES.BSC.Multicall3;
16
+ const AGGREGATE3_ABI = [
17
+ 'function aggregate3((address target,bool allowFailure,bytes callData)[] calls) view returns ((bool success,bytes returnData)[])',
18
+ ];
19
+ async function readViews(provider, multicall3, calls) {
20
+ const multicall = new Contract(multicall3, AGGREGATE3_ABI, provider);
21
+ const aggregate3 = multicall.aggregate3;
22
+ if (!aggregate3)
23
+ throw new Error('aggregate3 missing');
24
+ return (await aggregate3.staticCall(calls.map((call) => ({ target: call.target, allowFailure: true, callData: call.callData }))));
25
+ }
16
26
  // ============================================================================
17
27
  // 主查询函数
18
28
  // ============================================================================
@@ -40,13 +50,20 @@ export async function inspectTokenLP(token, opts) {
40
50
  let tokenDecimals;
41
51
  let totalSupplyRaw;
42
52
  try {
43
- const tokenContract = new Contract(token, ['function decimals() view returns (uint8)', 'function totalSupply() view returns (uint256)'], provider);
44
- const [decimals, supply] = await Promise.all([
45
- tokenContract.decimals().catch(() => 18),
46
- tokenContract.totalSupply().catch(() => undefined),
53
+ const tokenIface = new Interface([
54
+ 'function decimals() view returns (uint8)',
55
+ 'function totalSupply() view returns (uint256)',
56
+ ]);
57
+ const rows = await readViews(provider, multicall3, [
58
+ { target: token, callData: tokenIface.encodeFunctionData('decimals', []) },
59
+ { target: token, callData: tokenIface.encodeFunctionData('totalSupply', []) },
47
60
  ]);
61
+ const decimals = rows[0]?.success ? tokenIface.decodeFunctionResult('decimals', rows[0].returnData)[0] : 18;
62
+ const supply = rows[1]?.success
63
+ ? tokenIface.decodeFunctionResult('totalSupply', rows[1].returnData)[0]
64
+ : undefined;
48
65
  tokenDecimals = Number(decimals);
49
- totalSupplyRaw = supply ? BigInt(supply) : undefined;
66
+ totalSupplyRaw = supply;
50
67
  result.decimals = tokenDecimals;
51
68
  if (totalSupplyRaw !== undefined) {
52
69
  result.totalSupplyRaw = totalSupplyRaw;
@@ -65,25 +82,13 @@ export async function inspectTokenLP(token, opts) {
65
82
  let st = null;
66
83
  let taxRate;
67
84
  const useBscV8 = opts.flapChain === 'BSC';
68
- if (useBscV8) {
69
- try {
70
- const stV8 = await flap.getTokenV8Safe(token);
71
- st = stV8;
72
- taxRate = flapV8TaxBps(stV8);
73
- }
74
- catch {
75
- // V8 不可用时只借 V7/V5 读盘面,不用 V7 残留 taxRate(常见 100 bps)
76
- }
77
- }
78
- if (!st) {
79
- try {
80
- const stV7 = await flap.getTokenV7(token);
81
- st = stV7;
82
- if (!useBscV8)
83
- taxRate = Number(stV7.taxRate ?? 0n);
84
- }
85
- catch {
86
- st = await flap.getTokenV5(token);
85
+ const preferred = await flap.readPreferredTokenState(token, useBscV8);
86
+ if (preferred) {
87
+ st = preferred;
88
+ if (useBscV8 && 'buyTaxRate' in preferred)
89
+ taxRate = flapV8TaxBps(preferred);
90
+ else if (!useBscV8 && 'taxRate' in preferred && !('buyTaxRate' in preferred)) {
91
+ taxRate = Number(preferred.taxRate ?? 0n);
87
92
  }
88
93
  }
89
94
  if (st && isFlapInnerMarket(st.status)) {
@@ -125,22 +130,29 @@ export async function inspectTokenLP(token, opts) {
125
130
  if (opts.chain === 'ENI') {
126
131
  try {
127
132
  const portalAddress = ADDRESSES.ENI.DaoaasPortal;
128
- const portalAbi = [
133
+ const portalIface = new Interface([
129
134
  'function getTokenState(address token) view returns (uint256)',
130
135
  'function getPrice(address token) view returns (uint256)',
131
136
  'function getOKAmountBySale(address token, uint256 tokenAmount) view returns (uint256)',
132
- ];
133
- const portal = new Contract(portalAddress, portalAbi, provider);
134
- const status = Number(await portal.getTokenState(token));
137
+ ]);
138
+ const tokenIface = new Interface([
139
+ 'function totalSupply() view returns (uint256)',
140
+ 'function balanceOf(address) view returns (uint256)',
141
+ ]);
142
+ const portal = new Contract(portalAddress, portalIface, provider);
143
+ const eniRows = await readViews(provider, getMulticall3Address(CHAINS.ENI.chainId), [
144
+ { target: portalAddress, callData: portalIface.encodeFunctionData('getTokenState', [token]) },
145
+ { target: portalAddress, callData: portalIface.encodeFunctionData('getPrice', [token]) },
146
+ { target: token, callData: tokenIface.encodeFunctionData('totalSupply', []) },
147
+ { target: token, callData: tokenIface.encodeFunctionData('balanceOf', [portalAddress]) },
148
+ ]);
149
+ const status = Number(portalIface.decodeFunctionResult('getTokenState', eniRows[0].returnData)[0]);
135
150
  // status: 0=不存在, 1=交易中(内盘), 2=已毕业(外盘)
136
151
  if (status === 1) {
137
152
  result.platform = 'DAOAAS';
138
- const price = (await portal.getPrice(token));
139
- const tokenContract = new Contract(token, ['function totalSupply() view returns (uint256)', 'function balanceOf(address) view returns (uint256)'], provider);
140
- const [totalSupply, portalTokenBalance] = await Promise.all([
141
- tokenContract.totalSupply(),
142
- tokenContract.balanceOf(portalAddress),
143
- ]);
153
+ const price = portalIface.decodeFunctionResult('getPrice', eniRows[1].returnData)[0];
154
+ const totalSupply = tokenIface.decodeFunctionResult('totalSupply', eniRows[2].returnData)[0];
155
+ const portalTokenBalance = tokenIface.decodeFunctionResult('balanceOf', eniRows[3].returnData)[0];
144
156
  const circulatingSupply = totalSupply - portalTokenBalance;
145
157
  let reserveNative = '0';
146
158
  try {
@@ -339,16 +351,17 @@ export async function inspectTokenLP(token, opts) {
339
351
  try {
340
352
  const flap = new FlapPortal({ chain: opts.flapChain, rpcUrl: opts.rpcUrl });
341
353
  let taxRate = 0;
354
+ const preferred = await flap.readPreferredTokenState(token, opts.flapChain === 'BSC');
342
355
  if (opts.flapChain === 'BSC') {
343
- const stV8 = await flap.getTokenV8Safe(token);
344
- taxRate = flapV8TaxBps(stV8);
345
- // V8 已把「无池残留 100」收成 0;毕业盘真实 1%(100 bps)要留下
346
- if (taxRate > 0)
347
- result.taxRate = taxRate;
356
+ if (preferred && 'buyTaxRate' in preferred) {
357
+ taxRate = flapV8TaxBps(preferred);
358
+ // V8 已把「无池残留 100」收成 0;毕业盘真实 1%(100 bps)要留下
359
+ if (taxRate > 0)
360
+ result.taxRate = taxRate;
361
+ }
348
362
  }
349
- else {
350
- const stV7 = await flap.getTokenV7(token);
351
- taxRate = Number(stV7.taxRate ?? 0n);
363
+ else if (preferred && 'taxRate' in preferred && !('buyTaxRate' in preferred)) {
364
+ taxRate = Number(preferred.taxRate ?? 0n);
352
365
  if (taxRate > 0 && taxRate !== 100)
353
366
  result.taxRate = taxRate;
354
367
  }
@@ -3,7 +3,9 @@
3
3
  *
4
4
  * DEX/稳定币注册、链配置获取、报价代币列表等注册表相关 API。
5
5
  */
6
- import { Contract } from 'ethers';
6
+ import { Contract, Interface } from 'ethers';
7
+ import { MULTICALL3_ABI } from '../../abis/common.js';
8
+ import { getMulticall3Address } from '../../core/constants/addresses.js';
7
9
  import { createProvider } from '../../utils/provider-factory.js';
8
10
  import { ADDRESSES } from '../../core/constants/index.js';
9
11
  import { getErrorMessageFromUnknown } from '../../core/index.js';
@@ -22,12 +24,36 @@ export async function getFactoryFromRouter(routerAddress, rpcUrl, routerType = '
22
24
  }
23
25
  else {
24
26
  // V3 SwapRouter02 可能同时有 V2 和 V3 Factory
25
- const router = new Contract(routerAddress, V3_ROUTER_ABI, provider);
26
- // ✅ 并行获取 V3 和 V2 Factory
27
- const [v3Factory, v2Factory] = await Promise.all([
28
- router.factory().catch(() => undefined),
29
- router.factoryV2().catch(() => undefined),
30
- ]);
27
+ const routerIface = new Interface(V3_ROUTER_ABI);
28
+ const multicall = new Contract(getMulticall3Address(56), MULTICALL3_ABI, provider);
29
+ const aggregate3 = multicall.aggregate3;
30
+ let v3Factory;
31
+ let v2Factory;
32
+ if (aggregate3) {
33
+ const rows = (await aggregate3.staticCall([
34
+ {
35
+ target: routerAddress,
36
+ allowFailure: true,
37
+ callData: routerIface.encodeFunctionData('factory', []),
38
+ },
39
+ {
40
+ target: routerAddress,
41
+ allowFailure: true,
42
+ callData: routerIface.encodeFunctionData('factoryV2', []),
43
+ },
44
+ ]));
45
+ if (rows[0]?.success && rows[0].returnData !== '0x') {
46
+ v3Factory = routerIface.decodeFunctionResult('factory', rows[0].returnData)[0];
47
+ }
48
+ if (rows[1]?.success && rows[1].returnData !== '0x') {
49
+ v2Factory = routerIface.decodeFunctionResult('factoryV2', rows[1].returnData)[0];
50
+ }
51
+ }
52
+ else {
53
+ const router = new Contract(routerAddress, V3_ROUTER_ABI, provider);
54
+ v3Factory = await router.factory().catch(() => undefined);
55
+ v2Factory = await router.factoryV2().catch(() => undefined);
56
+ }
31
57
  if (v3Factory)
32
58
  result.v3Factory = v3Factory;
33
59
  if (v2Factory)
@@ -1,5 +1,6 @@
1
- import { Contract } from 'ethers';
2
- import { ADDRESSES } from '../../core/constants/index.js';
1
+ import { Contract, Interface } from 'ethers';
2
+ import { MULTICALL3_ABI } from '../../abis/common.js';
3
+ import { ADDRESSES, CHAINS, getMulticall3Address } from '../../core/constants/index.js';
3
4
  import { OPENFOUR_CORE_ABI, OPENFOUR_REGISTRY_ABI, OPENFOUR_TOOLS_ABI } from '../../abis/openfour.js';
4
5
  function mapParam(raw) {
5
6
  return {
@@ -23,11 +24,20 @@ function mapSchema(raw) {
23
24
  }
24
25
  export async function loadOpenFourAddresses(provider, registryAddress) {
25
26
  const registry = registryAddress || ADDRESSES.BSC.OpenFourRegistry;
26
- const reg = new Contract(registry, OPENFOUR_REGISTRY_ABI, provider);
27
- const [core, tools] = await Promise.all([reg.openFourCore(), reg.openFourTool()]);
27
+ const registryIface = new Interface(OPENFOUR_REGISTRY_ABI);
28
+ const multicall = new Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
29
+ const aggregate3 = multicall.aggregate3;
30
+ if (!aggregate3)
31
+ throw new Error('aggregate3 missing');
32
+ const rows = (await aggregate3.staticCall([
33
+ { target: registry, allowFailure: false, callData: registryIface.encodeFunctionData('openFourCore', []) },
34
+ { target: registry, allowFailure: false, callData: registryIface.encodeFunctionData('openFourTool', []) },
35
+ ]));
36
+ const core = registryIface.decodeFunctionResult('openFourCore', rows[0].returnData)[0];
37
+ const tools = registryIface.decodeFunctionResult('openFourTool', rows[1].returnData)[0];
28
38
  const coreC = new Contract(core, OPENFOUR_CORE_ABI, provider);
29
39
  const wrappedNative = (await coreC.wrappedNative());
30
- return { registry, core: core, tools: tools, wrappedNative };
40
+ return { registry, core, tools, wrappedNative };
31
41
  }
32
42
  export async function loadOpenFourPresetSchemas(provider, presetId, toolsAddress) {
33
43
  const toolsAddr = toolsAddress ?? (await loadOpenFourAddresses(provider)).tools;
@@ -1,7 +1,9 @@
1
- import { ethers, JsonRpcProvider } from 'ethers';
2
- import { GAS_LIMITS, isEniChain } from '../core/constants/index.js';
1
+ import { ethers, Interface, JsonRpcProvider } from 'ethers';
2
+ import { MULTICALL3_ABI } from '../abis/common.js';
3
+ import { GAS_LIMITS, getMulticall3Address, isEniChain } from '../core/constants/index.js';
3
4
  import { buildGasFields } from '../core/gas/bundle-gas.js';
4
5
  import { getErc20Decimals } from '../core/erc20/decimals.js';
6
+ import { batchGetBalances } from '../bundle/internal.js';
5
7
  function getTxTypeForChain(chainId) {
6
8
  return isEniChain(chainId) ? 2 : 0;
7
9
  }
@@ -103,7 +105,7 @@ export async function sweepWithBundle(params) {
103
105
  const gasCost = nativeGasLimit * gasPrice;
104
106
  // 并行获取所有余额和 nonce
105
107
  const [balances, nonces] = await Promise.all([
106
- Promise.all(wallets.map((w) => provider.getBalance(w.address).catch(() => 0n))),
108
+ batchGetBalances(provider, wallets.map((w) => w.address), undefined, chainId),
107
109
  Promise.all(wallets.map((w) => provider.getTransactionCount(w.address, 'pending'))),
108
110
  ]);
109
111
  // 计算每个钱包的发送金额
@@ -140,22 +142,40 @@ export async function sweepWithBundle(params) {
140
142
  'function transfer(address,uint256) returns (bool)',
141
143
  ]);
142
144
  // ✅ 并行获取 decimals、所有代币余额和 nonce
143
- const [decimals, balanceResults, nonces] = await Promise.all([
144
- getErc20Decimals(provider, tokenAddress),
145
- Promise.all(wallets.map(async (w) => {
146
- try {
147
- const balData = iface.encodeFunctionData('balanceOf', [w.address]);
148
- const balRaw = await provider.call({ to: tokenAddress, data: balData });
149
- const decoded = iface.decodeFunctionResult('balanceOf', balRaw);
150
- const bal = decoded[0];
151
- return bal;
152
- }
153
- catch {
154
- return 0n;
155
- }
156
- })),
145
+ const owners = wallets.map((w) => w.address);
146
+ const sweepIface = new Interface([
147
+ 'function decimals() view returns (uint8)',
148
+ 'function balanceOf(address) view returns (uint256)',
149
+ ]);
150
+ const [viewRows, nonces] = await Promise.all([
151
+ (async () => {
152
+ const multicall = new ethers.Contract(getMulticall3Address(chainId), MULTICALL3_ABI, provider);
153
+ const aggregate3 = multicall.aggregate3;
154
+ if (!aggregate3)
155
+ throw new Error('aggregate3 missing');
156
+ return (await aggregate3.staticCall([
157
+ { target: tokenAddress, allowFailure: false, callData: sweepIface.encodeFunctionData('decimals', []) },
158
+ ...owners.map((owner) => ({
159
+ target: tokenAddress,
160
+ allowFailure: true,
161
+ callData: sweepIface.encodeFunctionData('balanceOf', [owner]),
162
+ })),
163
+ ]));
164
+ })(),
157
165
  Promise.all(wallets.map((w) => provider.getTransactionCount(w.address, 'pending'))),
158
166
  ]);
167
+ const decimals = Number(sweepIface.decodeFunctionResult('decimals', viewRows[0].returnData)[0]);
168
+ const balanceResults = owners.map((_, index) => {
169
+ const row = viewRows[index + 1];
170
+ if (!row?.success || row.returnData === '0x')
171
+ return 0n;
172
+ try {
173
+ return sweepIface.decodeFunctionResult('balanceOf', row.returnData)[0];
174
+ }
175
+ catch {
176
+ return 0n;
177
+ }
178
+ });
159
179
  // 计算每个钱包的发送金额
160
180
  const sendAmounts = balanceResults.map((bal) => {
161
181
  if (ratio !== undefined) {
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * 换手交易通用工具函数
3
3
  */
4
- import { ethers, Contract } from 'ethers';
4
+ import { ethers, Contract, Interface } from 'ethers';
5
5
  import { batchCheckAllowances } from '../core/erc20/index.js';
6
- import { ERC20_ABI } from '../abis/common.js';
6
+ import { ERC20_ABI, MULTICALL3_ABI } from '../abis/common.js';
7
+ import { getMulticall3Address } from '../core/constants/index.js';
7
8
  import { NonceManager, getOptimizedGasPrice } from './bundle-helpers.js';
8
9
  const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
9
10
  /**
@@ -83,6 +84,37 @@ export async function ensureTokenApproval(provider, merkle, wallet, tokenAddress
83
84
  throw new Error(`授权失败: ${results[0]?.error || '未知错误'}`);
84
85
  }
85
86
  }
87
+ async function readDecimalsAndBalance(provider, tokenAddress, walletAddress) {
88
+ try {
89
+ const chainId = Number((await provider.getNetwork()).chainId);
90
+ const iface = new Interface([
91
+ 'function decimals() view returns (uint8)',
92
+ 'function balanceOf(address) view returns (uint256)',
93
+ ]);
94
+ const multicall = new Contract(getMulticall3Address(chainId), MULTICALL3_ABI, provider);
95
+ const aggregate3 = multicall.aggregate3;
96
+ if (!aggregate3)
97
+ throw new Error('aggregate3 missing');
98
+ const rows = (await aggregate3.staticCall([
99
+ { target: tokenAddress, allowFailure: false, callData: iface.encodeFunctionData('decimals', []) },
100
+ {
101
+ target: tokenAddress,
102
+ allowFailure: false,
103
+ callData: iface.encodeFunctionData('balanceOf', [walletAddress]),
104
+ },
105
+ ]));
106
+ return [
107
+ Number(iface.decodeFunctionResult('decimals', rows[0].returnData)[0]),
108
+ iface.decodeFunctionResult('balanceOf', rows[1].returnData)[0],
109
+ ];
110
+ }
111
+ catch {
112
+ return Promise.all([
113
+ getTokenDecimals(provider, tokenAddress),
114
+ getTokenBalance(provider, tokenAddress, walletAddress),
115
+ ]);
116
+ }
117
+ }
86
118
  /**
87
119
  * 获取代币余额
88
120
  */
@@ -140,11 +172,7 @@ export async function calculateSellAmount(provider, tokenAddress, walletAddress,
140
172
  if (sellPercentage <= 0 || sellPercentage > 100) {
141
173
  throw new Error('sellPercentage 必须在 0-100 之间');
142
174
  }
143
- // ✅ 并行获取 decimals 和 balance
144
- const [decimals, balance] = await Promise.all([
145
- getTokenDecimals(provider, tokenAddress),
146
- getTokenBalance(provider, tokenAddress, walletAddress),
147
- ]);
175
+ const [decimals, balance] = await readDecimalsAndBalance(provider, tokenAddress, walletAddress);
148
176
  const amount = (balance * BigInt(Math.floor(sellPercentage * 100))) / 10000n;
149
177
  return { amount, decimals };
150
178
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "4.3.21",
3
+ "version": "4.4.0",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",