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,23 +4,35 @@
4
4
  * 对标 BSC: getFlapMetaByAddress / getFlapMetasByAddresses
5
5
  * 提供代币 Logo、社交链接、描述等元信息查询
6
6
  */
7
- import { Contract, JsonRpcProvider } from 'ethers';
7
+ import { Contract, Interface, JsonRpcProvider } from 'ethers';
8
+ import { MULTICALL3_ABI } from '../../../../abis/common.js';
9
+ import { getMulticall3Address } from '../../../../core/constants/index.js';
8
10
  import { ENI_CHAIN_ID, ENI_RPC_URL, LP_FAIR_LAUNCHER_ABI } from '../../constants.js';
9
11
  import { LP_FAIR_LAUNCHER_ADDRESS } from '../fair-launch/constants.js';
10
- import { ERC20_ABI } from '../../../../abis/common.js';
11
12
  /**
12
13
  * 从 ERC20 合约获取 DAOaaS Portal 代币基本元数据
13
14
  * Portal 的 getTokenState 仅返回状态码,name/symbol 从 ERC20 接口获取
14
15
  */
15
16
  export async function getTokenMeta(token, rpcUrl = ENI_RPC_URL) {
16
17
  const provider = new JsonRpcProvider(rpcUrl, ENI_CHAIN_ID, { staticNetwork: true });
17
- const tokenContract = new Contract(token, ERC20_ABI, provider);
18
- let name = '', symbol = '';
18
+ const erc20Iface = new Interface(['function name() view returns (string)', 'function symbol() view returns (string)']);
19
+ let name = '';
20
+ let symbol = '';
19
21
  try {
20
- [name, symbol] = await Promise.all([
21
- tokenContract.name(),
22
- tokenContract.symbol(),
23
- ]);
22
+ const multicall = new Contract(getMulticall3Address(ENI_CHAIN_ID), MULTICALL3_ABI, provider);
23
+ const aggregate3 = multicall.aggregate3;
24
+ if (!aggregate3)
25
+ throw new Error('aggregate3 missing');
26
+ const rows = (await aggregate3.staticCall([
27
+ { target: token, allowFailure: true, callData: erc20Iface.encodeFunctionData('name', []) },
28
+ { target: token, allowFailure: true, callData: erc20Iface.encodeFunctionData('symbol', []) },
29
+ ]));
30
+ if (rows[0]?.success && rows[0].returnData !== '0x') {
31
+ name = String(erc20Iface.decodeFunctionResult('name', rows[0].returnData)[0] ?? '');
32
+ }
33
+ if (rows[1]?.success && rows[1].returnData !== '0x') {
34
+ symbol = String(erc20Iface.decodeFunctionResult('symbol', rows[1].returnData)[0] ?? '');
35
+ }
24
36
  }
25
37
  catch {
26
38
  /* ignore */
@@ -70,28 +82,101 @@ export async function getFairLaunchTokenMeta(token, rpcUrl = ENI_RPC_URL) {
70
82
  * 批量获取代币元数据(自动识别 Portal 或 FairLaunch 来源)
71
83
  * 对标 BSC: getFlapMetasByAddresses
72
84
  */
85
+ function emptyTokenMeta(token) {
86
+ return {
87
+ token,
88
+ name: '',
89
+ symbol: '',
90
+ logo: '',
91
+ description: '',
92
+ website: '',
93
+ twitter: '',
94
+ telegram: '',
95
+ discord: '',
96
+ };
97
+ }
98
+ function metaFromFairLaunch(token, info) {
99
+ let meta = {};
100
+ try {
101
+ if (info.metadata && info.metadata !== '{}' && info.metadata !== '') {
102
+ meta = JSON.parse(info.metadata);
103
+ }
104
+ }
105
+ catch {
106
+ /* metadata may not be valid JSON */
107
+ }
108
+ return {
109
+ token: info.token ?? token,
110
+ name: info.name ?? '',
111
+ symbol: info.symbol ?? '',
112
+ logo: info.logo ?? '',
113
+ description: meta.description ?? '',
114
+ website: meta.website ?? meta.web ?? '',
115
+ twitter: meta.twitter ?? meta.x ?? '',
116
+ telegram: meta.telegram ?? meta.tg ?? '',
117
+ discord: meta.discord ?? '',
118
+ };
119
+ }
73
120
  export async function getTokenMetasBatch(tokens, rpcUrl = ENI_RPC_URL) {
74
- return Promise.all(tokens.map(async (token) => {
121
+ if (tokens.length === 0)
122
+ return [];
123
+ const provider = new JsonRpcProvider(rpcUrl, ENI_CHAIN_ID, { staticNetwork: true });
124
+ const fairIface = new Interface(LP_FAIR_LAUNCHER_ABI);
125
+ const erc20Iface = new Interface(['function name() view returns (string)', 'function symbol() view returns (string)']);
126
+ const multicall = new Contract(getMulticall3Address(ENI_CHAIN_ID), MULTICALL3_ABI, provider);
127
+ const aggregate3 = multicall.aggregate3;
128
+ if (!aggregate3) {
129
+ return Promise.all(tokens.map((token) => getTokenMeta(token, rpcUrl).catch(() => emptyTokenMeta(token))));
130
+ }
131
+ const fairRows = (await aggregate3.staticCall(tokens.map((token) => ({
132
+ target: LP_FAIR_LAUNCHER_ADDRESS,
133
+ allowFailure: true,
134
+ callData: fairIface.encodeFunctionData('getTokenInfoByToken', [token]),
135
+ }))));
136
+ const missed = [];
137
+ const out = tokens.map((token, index) => {
138
+ const row = fairRows[index];
139
+ if (!row?.success || row.returnData === '0x') {
140
+ missed.push(index);
141
+ return emptyTokenMeta(token);
142
+ }
75
143
  try {
76
- return await getFairLaunchTokenMeta(token, rpcUrl);
144
+ const info = fairIface.decodeFunctionResult('getTokenInfoByToken', row.returnData)[0];
145
+ if (!info?.name && !info?.symbol) {
146
+ missed.push(index);
147
+ return emptyTokenMeta(token);
148
+ }
149
+ return metaFromFairLaunch(token, info);
77
150
  }
78
151
  catch {
79
- try {
80
- return await getTokenMeta(token, rpcUrl);
81
- }
82
- catch {
83
- return {
84
- token,
85
- name: '',
86
- symbol: '',
87
- logo: '',
88
- description: '',
89
- website: '',
90
- twitter: '',
91
- telegram: '',
92
- discord: '',
93
- };
94
- }
152
+ missed.push(index);
153
+ return emptyTokenMeta(token);
95
154
  }
96
- }));
155
+ });
156
+ if (missed.length === 0)
157
+ return out;
158
+ const ercRows = (await aggregate3.staticCall(missed.flatMap((index) => [
159
+ {
160
+ target: tokens[index],
161
+ allowFailure: true,
162
+ callData: erc20Iface.encodeFunctionData('name', []),
163
+ },
164
+ {
165
+ target: tokens[index],
166
+ allowFailure: true,
167
+ callData: erc20Iface.encodeFunctionData('symbol', []),
168
+ },
169
+ ])));
170
+ missed.forEach((tokenIndex, index) => {
171
+ const nameRow = ercRows[index * 2];
172
+ const symbolRow = ercRows[index * 2 + 1];
173
+ const name = nameRow?.success && nameRow.returnData !== '0x'
174
+ ? String(erc20Iface.decodeFunctionResult('name', nameRow.returnData)[0] ?? '')
175
+ : '';
176
+ const symbol = symbolRow?.success && symbolRow.returnData !== '0x'
177
+ ? String(erc20Iface.decodeFunctionResult('symbol', symbolRow.returnData)[0] ?? '')
178
+ : '';
179
+ out[tokenIndex] = { ...emptyTokenMeta(tokens[tokenIndex]), name, symbol };
180
+ });
181
+ return out;
97
182
  }
@@ -7,6 +7,7 @@
7
7
  import { Wallet, JsonRpcProvider } from 'ethers';
8
8
  import { ENI_CHAIN_ID, ENI_RPC_URL, DAOAAS_PORTAL, NATIVE_TRANSFER_GAS_LIMIT } from '../../constants.js';
9
9
  import { encodeBuyCall, encodeSellCall, applySlippage, DaoaasPortalQuery } from './portal.js';
10
+ import { batchGetBalances } from '../../../../bundle/internal.js';
10
11
  import { getProfitRecipient, getProfitRateBps, calculateProfitAmount, calculateBundleSwapRoundTripProfit, } from '../../../../core/constants/profit.js';
11
12
  import { directBuy, directSell, directBatchBuy, directBatchSell } from './portal-direct-tx.js';
12
13
  import { buildProfitTransfer } from './portal-direct-profit.js';
@@ -126,7 +127,7 @@ export async function directBatchBuyForSubmit(params) {
126
127
  const profitAmountFull = calculateProfitAmount(totalFlowRaw, normalProfitBps()) + profitTxGasCost;
127
128
  const maxPayerIdx = pickMaxAmountIndex(rawAmounts);
128
129
  const wallets = rawKeys.map((pk) => new Wallet(pk, provider));
129
- const balances = await Promise.all(wallets.map((w) => provider.getBalance(w.address)));
130
+ const balances = await batchGetBalances(provider, wallets.map((w) => w.address), undefined, ENI_CHAIN_ID);
130
131
  let privateKeys = rawKeys;
131
132
  let amounts = rawAmounts;
132
133
  const validIndices = [];
@@ -148,9 +149,10 @@ export async function directBatchBuyForSubmit(params) {
148
149
  }
149
150
  const transactions = [];
150
151
  const totalFlow = amounts.reduce((sum, amount) => sum + amount, 0n);
152
+ const previews = await query.previewBuys(token, amounts);
151
153
  for (let i = 0; i < privateKeys.length; i++) {
152
154
  const wallet = new Wallet(privateKeys[i], provider);
153
- const preview = await query.previewBuy(token, amounts[i]);
155
+ const preview = previews[i];
154
156
  const minOut = applySlippage(preview, slippageBps);
155
157
  const calldata = encodeBuyCall(token, minOut);
156
158
  const nonce = await wallet.getNonce('pending');
@@ -201,9 +203,10 @@ export async function directBatchSellForSubmit(params) {
201
203
  const wallets = privateKeys.map((pk) => new Wallet(pk, provider));
202
204
  const transactions = [];
203
205
  const estimatedOuts = [];
206
+ const previews = await query.previewSells(token, tokenAmounts);
204
207
  for (let i = 0; i < privateKeys.length; i++) {
205
208
  const wallet = wallets[i];
206
- const preview = await query.previewSell(token, tokenAmounts[i]);
209
+ const preview = previews[i];
207
210
  estimatedOuts.push(preview);
208
211
  const minOut = applySlippage(preview, slippageBps);
209
212
  const calldata = encodeSellCall(token, tokenAmounts[i], minOut);
@@ -263,13 +266,15 @@ export async function directQuickSwapForSubmit(params) {
263
266
  const primarySellerAddress = primarySellerWallet.address.toLowerCase();
264
267
  // 2a) 归集:其他 seller 将 EGAS 转到 seller[0](多卖家场景)
265
268
  if (params.sellerKeys.length > 1) {
266
- for (let i = 1; i < params.sellerKeys.length; i++) {
267
- const otherWallet = new Wallet(params.sellerKeys[i], provider);
269
+ const otherWallets = params.sellerKeys.slice(1).map((key) => new Wallet(key, provider));
270
+ const otherBalances = await batchGetBalances(provider, otherWallets.map((w) => w.address), undefined, ENI_CHAIN_ID);
271
+ for (let i = 0; i < otherWallets.length; i++) {
272
+ const otherWallet = otherWallets[i];
268
273
  const otherSellTxs = sellResult.transactions.filter((tx) => tx.from.toLowerCase() === otherWallet.address.toLowerCase());
269
274
  const otherNextNonce = otherSellTxs.length > 0
270
275
  ? Math.max(...otherSellTxs.map((tx) => tx.nonce)) + 1
271
276
  : await otherWallet.getNonce('pending');
272
- const otherBalance = await provider.getBalance(otherWallet.address);
277
+ const otherBalance = otherBalances[i] ?? 0n;
273
278
  const reserveGas = NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
274
279
  const transferable = otherBalance - reserveGas;
275
280
  if (transferable <= 0n)
@@ -74,9 +74,10 @@ export async function directBatchBuy(params) {
74
74
  const maxFeePerGas = gasPrice ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? 1000000000n;
75
75
  const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
76
76
  const transactions = [];
77
+ const previews = await query.previewBuys(token, amounts);
77
78
  for (let i = 0; i < privateKeys.length; i++) {
78
79
  const wallet = new Wallet(privateKeys[i], provider);
79
- const preview = await query.previewBuy(token, amounts[i]);
80
+ const preview = previews[i];
80
81
  const minOut = applySlippage(preview, slippageBps);
81
82
  const calldata = encodeBuyCall(token, minOut);
82
83
  const nonce = await wallet.getNonce('pending');
@@ -110,9 +111,10 @@ export async function directBatchSell(params) {
110
111
  const maxFeePerGas = gasPrice ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? 1000000000n;
111
112
  const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
112
113
  const transactions = [];
114
+ const previews = await query.previewSells(token, tokenAmounts);
113
115
  for (let i = 0; i < privateKeys.length; i++) {
114
116
  const wallet = new Wallet(privateKeys[i], provider);
115
- const preview = await query.previewSell(token, tokenAmounts[i]);
117
+ const preview = previews[i];
116
118
  const minOut = applySlippage(preview, slippageBps);
117
119
  const calldata = encodeSellCall(token, tokenAmounts[i], minOut);
118
120
  const nonce = await wallet.getNonce('pending');
@@ -33,6 +33,11 @@ export declare class DaoaasPortalQuery {
33
33
  previewBuy(token: string, egasAmount: bigint): Promise<bigint>;
34
34
  /** 预估卖出获得的 EGAS 数量 */
35
35
  previewSell(token: string, tokenAmount: bigint): Promise<bigint>;
36
+ /** 多笔买入预估,一次 aggregate3 */
37
+ previewBuys(token: string, egasAmounts: bigint[]): Promise<bigint[]>;
38
+ /** 多笔卖出预估,一次 aggregate3 */
39
+ previewSells(token: string, tokenAmounts: bigint[]): Promise<bigint[]>;
40
+ private previewMany;
36
41
  /** 获取代币完整状态(链上 getTokenState 仅返回 uint256 状态码,其余数据分别查询) */
37
42
  getTokenState(token: string): Promise<DaoaasTokenState>;
38
43
  /** 批量获取钱包 EGAS 余额 */
@@ -57,17 +57,71 @@ export class DaoaasPortalQuery {
57
57
  async previewSell(token, tokenAmount) {
58
58
  return this.portal.getOKAmountBySale(token, tokenAmount);
59
59
  }
60
+ /** 多笔买入预估,一次 aggregate3 */
61
+ async previewBuys(token, egasAmounts) {
62
+ return this.previewMany('getTokenAmountByPurchase', token, egasAmounts);
63
+ }
64
+ /** 多笔卖出预估,一次 aggregate3 */
65
+ async previewSells(token, tokenAmounts) {
66
+ return this.previewMany('getOKAmountBySale', token, tokenAmounts);
67
+ }
68
+ async previewMany(fn, token, amounts) {
69
+ if (amounts.length === 0)
70
+ return [];
71
+ const one = (amount) => fn === 'getTokenAmountByPurchase' ? this.previewBuy(token, amount) : this.previewSell(token, amount);
72
+ if (amounts.length === 1)
73
+ return [await one(amounts[0])];
74
+ try {
75
+ const mc = new Contract(this.multicall3Address, MULTICALL3_ABI, this.provider);
76
+ const aggregate3 = mc.aggregate3;
77
+ if (!aggregate3)
78
+ throw new Error('aggregate3 missing');
79
+ const rows = (await aggregate3.staticCall(amounts.map((amount) => ({
80
+ target: this.portalAddress,
81
+ allowFailure: false,
82
+ callData: portalIface.encodeFunctionData(fn, [token, amount]),
83
+ }))));
84
+ return rows.map((row) => portalIface.decodeFunctionResult(fn, row.returnData)[0]);
85
+ }
86
+ catch {
87
+ const out = [];
88
+ for (const amount of amounts)
89
+ out.push(await one(amount));
90
+ return out;
91
+ }
92
+ }
60
93
  /** 获取代币完整状态(链上 getTokenState 仅返回 uint256 状态码,其余数据分别查询) */
61
94
  async getTokenState(token) {
62
- const status = Number(await this.portal.getTokenState(token));
63
- const price = (await this.portal.getPrice(token));
64
- const tokenContract = new Contract(token, ERC20_ABI, this.provider);
65
- const [totalSupply, portalBalance, name, symbol] = await Promise.all([
66
- tokenContract.totalSupply(),
67
- tokenContract.balanceOf(this.portalAddress),
68
- tokenContract.name(),
69
- tokenContract.symbol(),
70
- ]);
95
+ const mc = new Contract(this.multicall3Address, MULTICALL3_ABI, this.provider);
96
+ const aggregate3 = mc.aggregate3;
97
+ if (!aggregate3)
98
+ throw new Error('aggregate3 missing');
99
+ const rows = (await aggregate3.staticCall([
100
+ {
101
+ target: this.portalAddress,
102
+ allowFailure: false,
103
+ callData: portalIface.encodeFunctionData('getTokenState', [token]),
104
+ },
105
+ {
106
+ target: this.portalAddress,
107
+ allowFailure: false,
108
+ callData: portalIface.encodeFunctionData('getPrice', [token]),
109
+ },
110
+ { target: token, allowFailure: false, callData: erc20Iface.encodeFunctionData('totalSupply', []) },
111
+ {
112
+ target: token,
113
+ allowFailure: false,
114
+ callData: erc20Iface.encodeFunctionData('balanceOf', [this.portalAddress]),
115
+ },
116
+ { target: token, allowFailure: false, callData: erc20Iface.encodeFunctionData('name', []) },
117
+ { target: token, allowFailure: false, callData: erc20Iface.encodeFunctionData('symbol', []) },
118
+ ]));
119
+ const status = Number(portalIface.decodeFunctionResult('getTokenState', rows[0].returnData)[0]);
120
+ const price = portalIface.decodeFunctionResult('getPrice', rows[1].returnData)[0];
121
+ const totalSupply = erc20Iface.decodeFunctionResult('totalSupply', rows[2].returnData)[0];
122
+ const portalBalance = erc20Iface.decodeFunctionResult('balanceOf', rows[3].returnData)[0];
123
+ const name = String(erc20Iface.decodeFunctionResult('name', rows[4].returnData)[0] ?? '');
124
+ const symbol = String(erc20Iface.decodeFunctionResult('symbol', rows[5].returnData)[0] ?? '');
71
125
  const circulatingSupply = totalSupply - portalBalance;
72
126
  return { token, name, symbol, totalSupply, circulatingSupply, price, status };
73
127
  }
@@ -9,7 +9,8 @@
9
9
  */
10
10
  import { Wallet, JsonRpcProvider, Contract, ethers, Interface } from 'ethers';
11
11
  import { ENI_CHAIN_ID, ENI_RPC_URL, WEGAS, NATIVE_TRANSFER_GAS_LIMIT, DSWAP_V2_ROUTER, DSWAP_V2_FACTORY, DSWAP_V2_ROUTER_ABI, DSWAP_V2_FACTORY_ABI, DSWAP_V2_PAIR_ABI, } from '../../constants.js';
12
- import { ERC20_ABI } from '../../../../abis/common.js';
12
+ import { ERC20_ABI, MULTICALL3_ABI } from '../../../../abis/common.js';
13
+ import { getMulticall3Address } from '../../../../core/constants/index.js';
13
14
  import { getProfitRecipient, getProfitRateBps, calculateProfitAmount } from '../../../../core/constants/profit.js';
14
15
  const DEFAULT_DEADLINE_MINUTES = 20;
15
16
  const DEFAULT_SLIPPAGE_PCT = 5;
@@ -50,42 +51,102 @@ export async function getPairAddress(params) {
50
51
  const pair = await factory.getPair(params.tokenA, params.tokenB);
51
52
  return pair;
52
53
  }
53
- export async function getLPPairInfo(params) {
54
- const provider = getProvider(params.rpcUrl);
55
- const factory = new Contract(DSWAP_V2_FACTORY, DSWAP_V2_FACTORY_ABI, provider);
56
- const pairAddr = await factory.getPair(params.token, WEGAS);
57
- if (!pairAddr || pairAddr === ethers.ZeroAddress) {
58
- return null;
59
- }
60
- const pair = new Contract(pairAddr, DSWAP_V2_PAIR_ABI, provider);
61
- const [token0, token1, reserves, totalSupply] = await Promise.all([
62
- pair.token0(),
63
- pair.token1(),
64
- pair.getReserves(),
65
- pair.totalSupply(),
66
- ]);
67
- let userLPBalance = 0n;
68
- if (params.userAddress) {
69
- userLPBalance = (await pair.balanceOf(params.userAddress));
70
- }
54
+ const factoryIface = new Interface(DSWAP_V2_FACTORY_ABI);
55
+ async function aggregate3(provider, calls) {
56
+ const multicall = new Contract(getMulticall3Address(ENI_CHAIN_ID), MULTICALL3_ABI, provider);
57
+ const fn = multicall.aggregate3;
58
+ if (!fn)
59
+ throw new Error('aggregate3 missing');
60
+ return (await fn.staticCall(calls));
61
+ }
62
+ function toLpPairInfo(pairAddress, token0, token1, reserve0, reserve1, totalSupply, userLPBalance) {
71
63
  const userShare = totalSupply > 0n ? Number((userLPBalance * 10000n) / totalSupply) / 10000 : 0;
72
- const userToken0Amount = totalSupply > 0n ? (reserves[0] * userLPBalance) / totalSupply : 0n;
73
- const userToken1Amount = totalSupply > 0n ? (reserves[1] * userLPBalance) / totalSupply : 0n;
74
64
  return {
75
- pairAddress: pairAddr,
65
+ pairAddress,
76
66
  token0,
77
67
  token1,
78
- reserve0: reserves[0],
79
- reserve1: reserves[1],
68
+ reserve0,
69
+ reserve1,
80
70
  totalSupply,
81
71
  userLPBalance,
82
72
  userShare,
83
- userToken0Amount,
84
- userToken1Amount,
73
+ userToken0Amount: totalSupply > 0n ? (reserve0 * userLPBalance) / totalSupply : 0n,
74
+ userToken1Amount: totalSupply > 0n ? (reserve1 * userLPBalance) / totalSupply : 0n,
85
75
  };
86
76
  }
77
+ async function readPairInfos(provider, pairAddresses, userAddress) {
78
+ if (pairAddresses.length === 0)
79
+ return [];
80
+ const stride = userAddress ? 5 : 4;
81
+ const calls = pairAddresses.flatMap((pairAddress) => {
82
+ const base = [
83
+ { target: pairAddress, allowFailure: false, callData: pairIface.encodeFunctionData('token0', []) },
84
+ { target: pairAddress, allowFailure: false, callData: pairIface.encodeFunctionData('token1', []) },
85
+ { target: pairAddress, allowFailure: false, callData: pairIface.encodeFunctionData('getReserves', []) },
86
+ { target: pairAddress, allowFailure: false, callData: pairIface.encodeFunctionData('totalSupply', []) },
87
+ ];
88
+ if (!userAddress)
89
+ return base;
90
+ return [
91
+ ...base,
92
+ {
93
+ target: pairAddress,
94
+ allowFailure: false,
95
+ callData: pairIface.encodeFunctionData('balanceOf', [userAddress]),
96
+ },
97
+ ];
98
+ });
99
+ const rows = await aggregate3(provider, calls);
100
+ return pairAddresses.map((pairAddress, index) => {
101
+ const at = index * stride;
102
+ const token0 = pairIface.decodeFunctionResult('token0', rows[at].returnData)[0];
103
+ const token1 = pairIface.decodeFunctionResult('token1', rows[at + 1].returnData)[0];
104
+ const reserves = pairIface.decodeFunctionResult('getReserves', rows[at + 2].returnData);
105
+ const totalSupply = pairIface.decodeFunctionResult('totalSupply', rows[at + 3].returnData)[0];
106
+ const userLPBalance = userAddress
107
+ ? pairIface.decodeFunctionResult('balanceOf', rows[at + 4].returnData)[0]
108
+ : 0n;
109
+ return toLpPairInfo(pairAddress, token0, token1, reserves[0], reserves[1], totalSupply, userLPBalance);
110
+ });
111
+ }
112
+ export async function getLPPairInfo(params) {
113
+ const provider = getProvider(params.rpcUrl);
114
+ const factory = new Contract(DSWAP_V2_FACTORY, DSWAP_V2_FACTORY_ABI, provider);
115
+ const pairAddr = await factory.getPair(params.token, WEGAS);
116
+ if (!pairAddr || pairAddr === ethers.ZeroAddress)
117
+ return null;
118
+ const [info] = await readPairInfos(provider, [pairAddr], params.userAddress);
119
+ return info ?? null;
120
+ }
87
121
  export async function batchGetLPPairInfo(params) {
88
- return Promise.all(params.tokens.map((token) => getLPPairInfo({ rpcUrl: params.rpcUrl, token, userAddress: params.userAddress })));
122
+ if (params.tokens.length === 0)
123
+ return [];
124
+ try {
125
+ const provider = getProvider(params.rpcUrl);
126
+ const pairRows = await aggregate3(provider, params.tokens.map((token) => ({
127
+ target: DSWAP_V2_FACTORY,
128
+ allowFailure: true,
129
+ callData: factoryIface.encodeFunctionData('getPair', [token, WEGAS]),
130
+ })));
131
+ const found = [];
132
+ pairRows.forEach((row, index) => {
133
+ if (!row.success || row.returnData === '0x')
134
+ return;
135
+ const pairAddress = factoryIface.decodeFunctionResult('getPair', row.returnData)[0];
136
+ if (!pairAddress || pairAddress === ethers.ZeroAddress)
137
+ return;
138
+ found.push({ index, pairAddress });
139
+ });
140
+ const infos = await readPairInfos(provider, found.map((item) => item.pairAddress), params.userAddress);
141
+ const out = params.tokens.map(() => null);
142
+ found.forEach((item, index) => {
143
+ out[item.index] = infos[index] ?? null;
144
+ });
145
+ return out;
146
+ }
147
+ catch {
148
+ return Promise.all(params.tokens.map((token) => getLPPairInfo({ rpcUrl: params.rpcUrl, token, userAddress: params.userAddress })));
149
+ }
89
150
  }
90
151
  // ============================================================================
91
152
  // 创建/添加 流动性 — EGAS + Token
@@ -155,6 +216,28 @@ export async function addLiquidityETHForSubmit(params) {
155
216
  // ============================================================================
156
217
  // 移除流动性 — EGAS + Token
157
218
  // ============================================================================
219
+ async function readRemovePair(provider, pairAddress, walletAddress) {
220
+ const rows = await aggregate3(provider, [
221
+ { target: pairAddress, allowFailure: false, callData: pairIface.encodeFunctionData('token0', []) },
222
+ { target: pairAddress, allowFailure: false, callData: pairIface.encodeFunctionData('getReserves', []) },
223
+ { target: pairAddress, allowFailure: false, callData: pairIface.encodeFunctionData('totalSupply', []) },
224
+ { target: pairAddress, allowFailure: false, callData: pairIface.encodeFunctionData('balanceOf', [walletAddress]) },
225
+ {
226
+ target: pairAddress,
227
+ allowFailure: false,
228
+ callData: pairIface.encodeFunctionData('allowance', [walletAddress, DSWAP_V2_ROUTER]),
229
+ },
230
+ ]);
231
+ const reserves = pairIface.decodeFunctionResult('getReserves', rows[1].returnData);
232
+ return {
233
+ token0: pairIface.decodeFunctionResult('token0', rows[0].returnData)[0],
234
+ reserve0: reserves[0],
235
+ reserve1: reserves[1],
236
+ totalSupply: pairIface.decodeFunctionResult('totalSupply', rows[2].returnData)[0],
237
+ balance: pairIface.decodeFunctionResult('balanceOf', rows[3].returnData)[0],
238
+ allowance: pairIface.decodeFunctionResult('allowance', rows[4].returnData)[0],
239
+ };
240
+ }
158
241
  export async function removeLiquidityETHForSubmit(params) {
159
242
  const { rpcUrl, privateKey, token } = params;
160
243
  const slippage = params.slippagePct ?? DEFAULT_SLIPPAGE_PCT;
@@ -169,28 +252,25 @@ export async function removeLiquidityETHForSubmit(params) {
169
252
  if (!pairAddr || pairAddr === ethers.ZeroAddress) {
170
253
  throw new Error('LP pair not found');
171
254
  }
172
- const pair = new Contract(pairAddr, DSWAP_V2_PAIR_ABI, provider);
255
+ const pairState = await readRemovePair(provider, pairAddr, wallet.address);
173
256
  let liquidity = params.liquidity;
174
257
  if (liquidity === 0n) {
175
- liquidity = (await pair.balanceOf(wallet.address));
258
+ liquidity = pairState.balance;
176
259
  if (liquidity === 0n)
177
260
  throw new Error('No LP tokens to remove');
178
261
  }
179
- const [token0, reserves, totalSupply] = await Promise.all([
180
- pair.token0(),
181
- pair.getReserves(),
182
- pair.totalSupply(),
183
- ]);
262
+ const token0 = pairState.token0;
263
+ const totalSupply = pairState.totalSupply;
184
264
  const isToken0 = token.toLowerCase() === token0.toLowerCase();
185
- const reserveToken = isToken0 ? reserves[0] : reserves[1];
186
- const reserveETH = isToken0 ? reserves[1] : reserves[0];
265
+ const reserveToken = isToken0 ? pairState.reserve0 : pairState.reserve1;
266
+ const reserveETH = isToken0 ? pairState.reserve1 : pairState.reserve0;
187
267
  const amountTokenExpected = (reserveToken * liquidity) / totalSupply;
188
268
  const amountETHExpected = (reserveETH * liquidity) / totalSupply;
189
269
  const amountTokenMin = applySlippage(amountTokenExpected, slippage);
190
270
  const amountETHMin = applySlippage(amountETHExpected, slippage);
191
271
  let currentNonce = await wallet.getNonce();
192
272
  const signedTxs = [];
193
- const lpAllowance = await pair.allowance(wallet.address, DSWAP_V2_ROUTER);
273
+ const lpAllowance = pairState.allowance;
194
274
  if (lpAllowance < liquidity) {
195
275
  const approveTx = await wallet.signTransaction({
196
276
  to: pairAddr,
@@ -252,12 +332,29 @@ export async function addLiquidityForSubmit(params) {
252
332
  const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
253
333
  let currentNonce = await wallet.getNonce();
254
334
  const signedTxs = [];
255
- for (const [token, amount] of [
335
+ const tokenAmounts = [
256
336
  [tokenA, amountADesired],
257
337
  [tokenB, amountBDesired],
258
- ]) {
259
- const erc20 = new Contract(token, ERC20_ABI, provider);
260
- const allowance = await erc20.allowance(wallet.address, DSWAP_V2_ROUTER);
338
+ ];
339
+ let allowances;
340
+ try {
341
+ const rows = await aggregate3(provider, tokenAmounts.map(([token]) => ({
342
+ target: token,
343
+ allowFailure: false,
344
+ callData: erc20Iface.encodeFunctionData('allowance', [wallet.address, DSWAP_V2_ROUTER]),
345
+ })));
346
+ allowances = rows.map((row) => erc20Iface.decodeFunctionResult('allowance', row.returnData)[0]);
347
+ }
348
+ catch {
349
+ allowances = [];
350
+ for (const [token] of tokenAmounts) {
351
+ const erc20 = new Contract(token, ERC20_ABI, provider);
352
+ allowances.push(await erc20.allowance(wallet.address, DSWAP_V2_ROUTER));
353
+ }
354
+ }
355
+ for (let index = 0; index < tokenAmounts.length; index++) {
356
+ const [token, amount] = tokenAmounts[index];
357
+ const allowance = allowances[index];
261
358
  if (allowance < amount) {
262
359
  const approveTx = await wallet.signTransaction({
263
360
  to: token,
@@ -315,18 +412,16 @@ export async function removeLiquidityForSubmit(params) {
315
412
  if (!pairAddr || pairAddr === ethers.ZeroAddress) {
316
413
  throw new Error('LP pair not found');
317
414
  }
318
- const pair = new Contract(pairAddr, DSWAP_V2_PAIR_ABI, provider);
415
+ const pairState = await readRemovePair(provider, pairAddr, wallet.address);
319
416
  let liquidity = params.liquidity;
320
417
  if (liquidity === 0n) {
321
- liquidity = (await pair.balanceOf(wallet.address));
418
+ liquidity = pairState.balance;
322
419
  if (liquidity === 0n)
323
420
  throw new Error('No LP tokens to remove');
324
421
  }
325
- const [token0, reserves, totalSupply] = await Promise.all([
326
- pair.token0(),
327
- pair.getReserves(),
328
- pair.totalSupply(),
329
- ]);
422
+ const token0 = pairState.token0;
423
+ const totalSupply = pairState.totalSupply;
424
+ const reserves = [pairState.reserve0, pairState.reserve1];
330
425
  const isAToken0 = tokenA.toLowerCase() === token0.toLowerCase();
331
426
  const reserveA = isAToken0 ? reserves[0] : reserves[1];
332
427
  const reserveB = isAToken0 ? reserves[1] : reserves[0];
@@ -334,7 +429,7 @@ export async function removeLiquidityForSubmit(params) {
334
429
  const amountBMin = applySlippage((reserveB * liquidity) / totalSupply, slippage);
335
430
  let currentNonce = await wallet.getNonce();
336
431
  const signedTxs = [];
337
- const lpAllowance = await pair.allowance(wallet.address, DSWAP_V2_ROUTER);
432
+ const lpAllowance = pairState.allowance;
338
433
  if (lpAllowance < liquidity) {
339
434
  const approveTx = await wallet.signTransaction({
340
435
  to: pairAddr,
@@ -50,16 +50,8 @@ export class FairLauncherQuery {
50
50
  const start = total;
51
51
  const end = start - BigInt(count) + 1n;
52
52
  const startIdx = end < 1n ? 1n : end;
53
- const tokens = [];
54
- for (let i = start; i >= startIdx; i--) {
55
- try {
56
- tokens.push(await this.getTokenInfoByIndex(i));
57
- }
58
- catch {
59
- break;
60
- }
61
- }
62
- return tokens;
53
+ const tokens = await this.getTokenInfoBatch(startIdx, start + 1n);
54
+ return tokens.reverse();
63
55
  }
64
56
  /**
65
57
  * 批量查询代币 (分页, 对标文档 getTokenInfoByIndexAllBatch)
@@ -55,6 +55,7 @@ export declare class FairPoolQuery {
55
55
  maxCount(): Promise<bigint>;
56
56
  userCount(): Promise<bigint>;
57
57
  /** 检查当前是否在 claim 窗口内 */
58
+ private readPoolUints;
58
59
  isClaimOpen(): Promise<boolean>;
59
60
  /** 检查是否还有 claim 名额 */
60
61
  hasClaimSlots(): Promise<boolean>;