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