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.
- package/dist/bundle/four-meme/approve-tokenmanager.js +46 -16
- package/dist/bundle/four-meme/core/create-token-quote.d.ts +5 -1
- package/dist/bundle/four-meme/core/create-token-quote.js +29 -4
- package/dist/bundle/four-meme/core/sell-flow.js +3 -3
- package/dist/bundle/four-meme/pancake-proxy/approve.js +47 -10
- package/dist/bundle/four-meme/private/sell.js +2 -10
- package/dist/bundle/four-meme/swap/bundle.js +4 -4
- package/dist/bundle/four-meme/swap-buy-first/multi-wallet.js +25 -8
- package/dist/bundle/four-meme/swap-buy-first/single.js +30 -9
- package/dist/bundle/four-meme/swap-sign-helpers.js +56 -3
- package/dist/bundle/four-meme/utils-sweep/erc20-no-hop.js +18 -7
- package/dist/bundle/four-meme/utils-sweep/with-hops.js +10 -4
- package/dist/bundle/internal.d.ts +5 -0
- package/dist/bundle/internal.js +36 -0
- package/dist/chains/bsc/pancake/bundle-buy-first-quote.js +24 -8
- package/dist/chains/bsc/pancake/bundle-buy-first-types.d.ts +2 -0
- package/dist/chains/bsc/pancake/bundle-buy-first-validate.d.ts +1 -1
- package/dist/chains/bsc/pancake/bundle-buy-first-validate.js +2 -2
- package/dist/chains/bsc/pancake/bundle-buy-first.js +1 -0
- package/dist/chains/bsc/pancake/bundle-swap-types.d.ts +2 -0
- package/dist/chains/bsc/pancake/quote.d.ts +7 -0
- package/dist/chains/bsc/pancake/quote.js +9 -1
- package/dist/chains/bsc/pancake/swap-batch.js +15 -16
- package/dist/chains/bsc/pancake/swap-build.d.ts +1 -1
- package/dist/chains/bsc/pancake/swap-build.js +15 -9
- package/dist/chains/bsc/pancake/swap-quick.js +4 -4
- package/dist/chains/bsc/pancake/swap-single.js +1 -0
- package/dist/chains/bsc/platforms/iro/token.d.ts +1 -0
- package/dist/chains/bsc/platforms/iro/token.js +36 -1
- package/dist/chains/eni/batch-router/transfer-sweep.js +10 -14
- package/dist/chains/eni/platforms/daoaas/graduation.js +60 -14
- package/dist/chains/eni/platforms/daoaas/meta.d.ts +0 -4
- package/dist/chains/eni/platforms/daoaas/meta.js +112 -27
- package/dist/chains/eni/platforms/daoaas/portal-direct-submit.js +11 -6
- package/dist/chains/eni/platforms/daoaas/portal-direct-tx.js +4 -2
- package/dist/chains/eni/platforms/daoaas/portal.d.ts +5 -0
- package/dist/chains/eni/platforms/daoaas/portal.js +63 -9
- package/dist/chains/eni/platforms/dswap/liquidity.js +144 -49
- package/dist/chains/eni/platforms/fair-launch/launcher.js +2 -10
- package/dist/chains/eni/platforms/fair-launch/pool.d.ts +1 -0
- package/dist/chains/eni/platforms/fair-launch/pool.js +16 -2
- package/dist/chains/eni/platforms/iro/token.d.ts +1 -0
- package/dist/chains/eni/platforms/iro/token.js +36 -1
- package/dist/chains/robinhood/constants.d.ts +2 -5
- package/dist/chains/robinhood/constants.js +8 -7
- package/dist/chains/robinhood/longetf/launch.js +4 -4
- package/dist/chains/robinhood/pons-v2/launch.js +51 -20
- package/dist/chains/xlayer/eip7702/bundle-approve.js +8 -8
- package/dist/chains/xlayer/eip7702/bundle-sell.js +3 -4
- package/dist/chains/xlayer/eip7702/bundle-swap-batch.js +4 -2
- package/dist/chains/xlayer/eip7702/bundle-swap-multi.js +3 -3
- package/dist/chains/xlayer/eip7702/multi-hop-transfer-sweep.js +4 -10
- package/dist/chains/xlayer/eip7702/volume-bundle.js +45 -24
- package/dist/chains/xlayer/eoa/eoa-wash-volume.js +4 -14
- package/dist/core/constants/gas.d.ts +0 -8
- package/dist/core/constants/gas.js +0 -9
- package/dist/core/quote/quote-helpers.d.ts +11 -0
- package/dist/core/quote/quote-helpers.js +121 -20
- package/dist/core/quote/quote-helpers.test.js +11 -1
- package/dist/domains/dex/direct-router-v2-buy.js +4 -2
- package/dist/domains/dex/direct-router-v2-sell.js +63 -12
- package/dist/domains/dex/direct-router-v3.js +3 -4
- package/dist/domains/dex/quote.js +15 -5
- package/dist/domains/flap/portal-bundle-merkle/pancake-proxy/approve.js +48 -8
- package/dist/domains/flap/portal-bundle-merkle/swap-batch.js +8 -10
- package/dist/domains/flap/portal-bundle-merkle/swap-buy-first-quote.js +24 -9
- package/dist/domains/flap/portal-bundle-merkle/swap-planning.js +6 -2
- package/dist/domains/flap/portal-bundle-merkle/utils/sweep.js +14 -7
- package/dist/domains/flap/reader.d.ts +6 -0
- package/dist/domains/flap/reader.js +77 -1
- package/dist/domains/four/contracts/tm-bundle-batch.js +2 -10
- package/dist/domains/four/contracts/tm-bundle-create.js +5 -2
- package/dist/domains/four/contracts/tm.d.ts +2 -0
- package/dist/domains/four/contracts/tm.js +59 -2
- package/dist/domains/four/tax-token.js +60 -28
- package/dist/domains/lp-inspect/inspect-tax.js +98 -12
- package/dist/domains/lp-inspect/inspect.js +57 -44
- package/dist/domains/lp-inspect/registry.js +33 -7
- package/dist/domains/openfour/registry.js +15 -5
- package/dist/utils/airdrop-sweep.js +37 -17
- package/dist/utils/swap-helpers.js +35 -7
- 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
|
|
18
|
-
let name = ''
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
267
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
65
|
+
pairAddress,
|
|
76
66
|
token0,
|
|
77
67
|
token1,
|
|
78
|
-
reserve0
|
|
79
|
-
reserve1
|
|
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
|
-
|
|
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
|
|
255
|
+
const pairState = await readRemovePair(provider, pairAddr, wallet.address);
|
|
173
256
|
let liquidity = params.liquidity;
|
|
174
257
|
if (liquidity === 0n) {
|
|
175
|
-
liquidity =
|
|
258
|
+
liquidity = pairState.balance;
|
|
176
259
|
if (liquidity === 0n)
|
|
177
260
|
throw new Error('No LP tokens to remove');
|
|
178
261
|
}
|
|
179
|
-
const
|
|
180
|
-
|
|
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 ?
|
|
186
|
-
const reserveETH = isToken0 ?
|
|
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 =
|
|
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
|
-
|
|
335
|
+
const tokenAmounts = [
|
|
256
336
|
[tokenA, amountADesired],
|
|
257
337
|
[tokenB, amountBDesired],
|
|
258
|
-
]
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
415
|
+
const pairState = await readRemovePair(provider, pairAddr, wallet.address);
|
|
319
416
|
let liquidity = params.liquidity;
|
|
320
417
|
if (liquidity === 0n) {
|
|
321
|
-
liquidity =
|
|
418
|
+
liquidity = pairState.balance;
|
|
322
419
|
if (liquidity === 0n)
|
|
323
420
|
throw new Error('No LP tokens to remove');
|
|
324
421
|
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
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 =
|
|
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
|
-
|
|
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)
|