four-flap-meme-sdk 4.3.0 → 4.3.2
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/chains/bsc/pancake/bundle-buy-first-multi-wallet.js +7 -8
- package/dist/chains/robinhood/constants.d.ts +3 -0
- package/dist/core/constants/addresses.d.ts +6 -0
- package/dist/core/constants/addresses.js +6 -0
- package/dist/core/constants/profit.js +1 -0
- package/dist/core/constants/quote.d.ts +7 -1
- package/dist/core/constants/quote.js +9 -1
- package/dist/core/nonce/nonce-manager.d.ts +1 -0
- package/dist/core/nonce/nonce-manager.js +30 -16
- package/dist/core/types/holders-maker.d.ts +1 -1
- package/dist/core/types/lp-inspect.d.ts +1 -1
- package/dist/core/types/quote-helpers.d.ts +1 -1
- package/dist/domains/dex/direct-router-types.d.ts +5 -0
- package/dist/domains/dex/direct-router-types.js +5 -0
- package/dist/domains/dex/direct-router.d.ts +1 -1
- package/dist/domains/dex/index.d.ts +1 -1
- package/dist/domains/dex/router.d.ts +2 -2
- package/dist/domains/dex/router.js +7 -0
- package/dist/domains/dex/types.d.ts +5 -4
- package/dist/domains/holders/holders-maker.js +2 -2
- package/dist/domains/holders/internal/addresses.d.ts +2 -0
- package/dist/domains/holders/internal/addresses.js +2 -0
- package/dist/domains/holders/internal/buy-tx.d.ts +1 -1
- package/dist/domains/holders/internal/buy-tx.js +9 -4
- package/dist/domains/holders/internal/routing.js +8 -1
- package/dist/domains/lp-inspect/inspect-tax.d.ts +6 -0
- package/dist/domains/lp-inspect/inspect-tax.js +82 -0
- package/dist/domains/lp-inspect/inspect-tax.test.d.ts +1 -0
- package/dist/domains/lp-inspect/inspect-tax.test.js +63 -0
- package/dist/domains/lp-inspect/inspect.js +12 -34
- package/dist/domains/lp-inspect/lp-inspect-helpers.js +14 -0
- package/dist/exports/root-swap-dex-and-xlayer.d.ts +1 -1
- package/package.json +1 -1
|
@@ -57,7 +57,7 @@ export async function pancakeBundleBuyFirstMultiWallet(params) {
|
|
|
57
57
|
const deadline = BigInt(getDeadline());
|
|
58
58
|
const bribeAmount = config.bribeAmount && config.bribeAmount > 0 ? ethers.parseEther(String(config.bribeAmount)) : 0n;
|
|
59
59
|
const needBribeTx = bribeAmount > 0n;
|
|
60
|
-
//
|
|
60
|
+
// 报价 + Gas 并行;nonce 必须在报价之后再取,避免 quote 期间链上 nonce 已前进
|
|
61
61
|
// 预先计算所有唯一钱包地址(去重)
|
|
62
62
|
const allWallets = [...sellers, ...buyers];
|
|
63
63
|
const uniqueWallets = allWallets.filter((w, i) => {
|
|
@@ -65,7 +65,7 @@ export async function pancakeBundleBuyFirstMultiWallet(params) {
|
|
|
65
65
|
const firstIdx = allWallets.findIndex((x) => x.address.toLowerCase() === addr);
|
|
66
66
|
return firstIdx === i;
|
|
67
67
|
});
|
|
68
|
-
const [quoteResult, gasPrice
|
|
68
|
+
const [quoteResult, gasPrice] = await Promise.all([
|
|
69
69
|
quoteTokenOutput({
|
|
70
70
|
routeParams,
|
|
71
71
|
buyerFundsWei: totalFundsWei,
|
|
@@ -73,13 +73,7 @@ export async function pancakeBundleBuyFirstMultiWallet(params) {
|
|
|
73
73
|
v2RouterOverride: config.v2RouterAddress,
|
|
74
74
|
}),
|
|
75
75
|
getGasPrice(context.provider, config),
|
|
76
|
-
nonceManager.getNextNoncesForWallets(uniqueWallets),
|
|
77
76
|
]);
|
|
78
|
-
// 构建 nonces Map
|
|
79
|
-
const noncesMap = new Map();
|
|
80
|
-
uniqueWallets.forEach((wallet, i) => {
|
|
81
|
-
noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
|
|
82
|
-
});
|
|
83
77
|
// ✅ 将总金额平均分配给买方
|
|
84
78
|
const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
|
|
85
79
|
// ✅ 税币支持:买入时会被扣税,实际获得的代币 = 报价 * (1 - 税率)
|
|
@@ -102,6 +96,11 @@ export async function pancakeBundleBuyFirstMultiWallet(params) {
|
|
|
102
96
|
const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
|
|
103
97
|
profitAmount = await quoteRoundTripProfitToNative(context.provider, quoteToken, totalFundsWei, totalFundsWei, 'BSC', version, fee, config.v2RouterAddress);
|
|
104
98
|
}
|
|
99
|
+
const noncesArray = await nonceManager.getNextNoncesForWallets(uniqueWallets);
|
|
100
|
+
const noncesMap = new Map();
|
|
101
|
+
uniqueWallets.forEach((wallet, i) => {
|
|
102
|
+
noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
|
|
103
|
+
});
|
|
105
104
|
// ✅ 第三批并行:构建并签名所有交易
|
|
106
105
|
const allTransactions = [];
|
|
107
106
|
// 1. 贿赂交易(由主卖方支付)- 先处理以确定 nonce 偏移
|
|
@@ -7,6 +7,9 @@ export declare const ROBINHOOD_EXPLORER: "https://robinhoodchain.blockscout.com"
|
|
|
7
7
|
export declare const ROBINHOOD_ADDRESSES: {
|
|
8
8
|
readonly WETH: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73";
|
|
9
9
|
readonly Multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
10
|
+
readonly UniswapV3Factory: "0x1f7d7550B1b028f7571E69A784071F0205FD2EfA";
|
|
11
|
+
readonly UniswapV3Router: "0xCaf681a66D020601342297493863E78C959E5cb2";
|
|
12
|
+
readonly UniswapV3Quoter: "0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7";
|
|
10
13
|
readonly LongetfFactory: "0xd2ba46aeffec4bfddde62c2dd3e8c76c5c9aeedc";
|
|
11
14
|
readonly LongXyzLauncher: "0x22e99278308B393ea1260859B181AD7E78f5eeED";
|
|
12
15
|
readonly LongXyzHook: "0x4e3468951d49f2eea976ed0d6e75ffcb44a9a544";
|
|
@@ -141,6 +141,12 @@ export declare const ADDRESSES: {
|
|
|
141
141
|
readonly ROBINHOOD: {
|
|
142
142
|
readonly WETH: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73";
|
|
143
143
|
readonly Multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
144
|
+
/** Uniswap V3 Factory(链上无 Uniswap V2) */
|
|
145
|
+
readonly UniswapV3Factory: "0x1f7d7550B1b028f7571E69A784071F0205FD2EfA";
|
|
146
|
+
/** Uniswap SwapRouter02 */
|
|
147
|
+
readonly UniswapV3Router: "0xCaf681a66D020601342297493863E78C959E5cb2";
|
|
148
|
+
/** Uniswap QuoterV2 */
|
|
149
|
+
readonly UniswapV3Quoter: "0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7";
|
|
144
150
|
/** LONGETF factory — deployVault / register */
|
|
145
151
|
readonly LongetfFactory: "0xd2ba46aeffec4bfddde62c2dd3e8c76c5c9aeedc";
|
|
146
152
|
/** long.xyz Airlock launcher create() */
|
|
@@ -208,6 +208,12 @@ export const ADDRESSES = {
|
|
|
208
208
|
ROBINHOOD: {
|
|
209
209
|
WETH: '0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73',
|
|
210
210
|
Multicall3: '0xcA11bde05977b3631167028862bE2a173976CA11',
|
|
211
|
+
/** Uniswap V3 Factory(链上无 Uniswap V2) */
|
|
212
|
+
UniswapV3Factory: '0x1f7d7550B1b028f7571E69A784071F0205FD2EfA',
|
|
213
|
+
/** Uniswap SwapRouter02 */
|
|
214
|
+
UniswapV3Router: '0xCaf681a66D020601342297493863E78C959E5cb2',
|
|
215
|
+
/** Uniswap QuoterV2 */
|
|
216
|
+
UniswapV3Quoter: '0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7',
|
|
211
217
|
/** LONGETF factory — deployVault / register */
|
|
212
218
|
LongetfFactory: '0xd2ba46aeffec4bfddde62c2dd3e8c76c5c9aeedc',
|
|
213
219
|
/** long.xyz Airlock launcher create() */
|
|
@@ -126,6 +126,7 @@ export const TRANSFER_FEE_PER_ADDRESS = {
|
|
|
126
126
|
8453: 1000000000000000n, // Base: 0.001 ETH
|
|
127
127
|
2818: 1000000000000000n, // Morph: 0.001 ETH
|
|
128
128
|
173: 100000000000000000n, // ENI: 0.1 EGAS
|
|
129
|
+
4663: 1000000000000000n, // Robinhood Chain: 0.001 ETH
|
|
129
130
|
};
|
|
130
131
|
/**
|
|
131
132
|
* 获取每个地址的转账费用(原生代币 wei)
|
|
@@ -24,9 +24,15 @@ export declare const QUOTE_CONFIG: {
|
|
|
24
24
|
readonly wrappedNative: "0x6d1e851446f4d004ae2a72f9afed85e8829a205e";
|
|
25
25
|
readonly stableCoins: readonly ["0xDC1a8A35b0BaA3229b13f348ED708a2fd50b5e3a", "0xaFF944b96c1BAEA587159ec446280E468B32ee15"];
|
|
26
26
|
};
|
|
27
|
+
readonly ROBINHOOD: {
|
|
28
|
+
readonly v2Router: "";
|
|
29
|
+
readonly v3Quoter: "0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7";
|
|
30
|
+
readonly wrappedNative: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73";
|
|
31
|
+
readonly stableCoins: readonly [];
|
|
32
|
+
};
|
|
27
33
|
};
|
|
28
34
|
/** V3 报价尝试费率列表(与 `gas.V3_FEE_TIERS` 命名对象区分) */
|
|
29
|
-
export declare const QUOTE_V3_FEE_TIERS: readonly [100, 500, 2500, 10000];
|
|
35
|
+
export declare const QUOTE_V3_FEE_TIERS: readonly [100, 500, 2500, 3000, 10000];
|
|
30
36
|
export type QuoteConfigChain = keyof typeof QUOTE_CONFIG;
|
|
31
37
|
/** chainId → QUOTE_CONFIG 键;Flap 旧接口传数字 chainId,不能直接当 'BSC' 用 */
|
|
32
38
|
export declare function getQuoteChainKey(chainId: number): QuoteConfigChain | undefined;
|
|
@@ -33,9 +33,15 @@ export const QUOTE_CONFIG = {
|
|
|
33
33
|
wrappedNative: '0x6d1e851446f4d004ae2a72f9afed85e8829a205e',
|
|
34
34
|
stableCoins: ['0xDC1a8A35b0BaA3229b13f348ED708a2fd50b5e3a', '0xaFF944b96c1BAEA587159ec446280E468B32ee15'],
|
|
35
35
|
},
|
|
36
|
+
ROBINHOOD: {
|
|
37
|
+
v2Router: '',
|
|
38
|
+
v3Quoter: '0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7',
|
|
39
|
+
wrappedNative: '0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73',
|
|
40
|
+
stableCoins: [],
|
|
41
|
+
},
|
|
36
42
|
};
|
|
37
43
|
/** V3 报价尝试费率列表(与 `gas.V3_FEE_TIERS` 命名对象区分) */
|
|
38
|
-
export const QUOTE_V3_FEE_TIERS = [100, 500, 2500, 10000];
|
|
44
|
+
export const QUOTE_V3_FEE_TIERS = [100, 500, 2500, 3000, 10000];
|
|
39
45
|
/** chainId → QUOTE_CONFIG 键;Flap 旧接口传数字 chainId,不能直接当 'BSC' 用 */
|
|
40
46
|
export function getQuoteChainKey(chainId) {
|
|
41
47
|
if (chainId === CHAINS.BSC.chainId)
|
|
@@ -46,5 +52,7 @@ export function getQuoteChainKey(chainId) {
|
|
|
46
52
|
return 'XLAYER';
|
|
47
53
|
if (chainId === CHAINS.ENI.chainId)
|
|
48
54
|
return 'ENI';
|
|
55
|
+
if (chainId === CHAINS.ROBINHOOD.chainId)
|
|
56
|
+
return 'ROBINHOOD';
|
|
49
57
|
return undefined;
|
|
50
58
|
}
|
|
@@ -11,6 +11,7 @@ export declare class NonceManager {
|
|
|
11
11
|
constructor(provider: JsonRpcProvider);
|
|
12
12
|
private withAddressLock;
|
|
13
13
|
private getChainId;
|
|
14
|
+
private readOnchainNonce;
|
|
14
15
|
getNextNonce(wallet: Wallet | string): Promise<number>;
|
|
15
16
|
getNextNonceBatch(wallet: Wallet | string, count: number): Promise<number[]>;
|
|
16
17
|
clearTemp(): void;
|
|
@@ -77,6 +77,13 @@ export class NonceManager {
|
|
|
77
77
|
}
|
|
78
78
|
return this.chainIdPromise;
|
|
79
79
|
}
|
|
80
|
+
async readOnchainNonce(address) {
|
|
81
|
+
const [latest, pending] = await Promise.all([
|
|
82
|
+
this.provider.getTransactionCount(address, 'latest'),
|
|
83
|
+
this.provider.getTransactionCount(address, 'pending'),
|
|
84
|
+
]);
|
|
85
|
+
return Math.max(Number(latest) || 0, Number(pending) || 0);
|
|
86
|
+
}
|
|
80
87
|
async getNextNonce(wallet) {
|
|
81
88
|
const address = typeof wallet === 'string' ? wallet : wallet.address;
|
|
82
89
|
const key = address.toLowerCase();
|
|
@@ -90,7 +97,7 @@ export class NonceManager {
|
|
|
90
97
|
setGlobalNextNonce(chainId, key, cachedNonce + 1);
|
|
91
98
|
return cachedNonce;
|
|
92
99
|
}
|
|
93
|
-
const onchainNonce = await this.
|
|
100
|
+
const onchainNonce = await this.readOnchainNonce(address);
|
|
94
101
|
let effectiveNonce = onchainNonce;
|
|
95
102
|
if (flaky) {
|
|
96
103
|
const globalNext = getGlobalNextNonce(chainId, key);
|
|
@@ -118,7 +125,7 @@ export class NonceManager {
|
|
|
118
125
|
startNonce = cachedNonce;
|
|
119
126
|
}
|
|
120
127
|
else {
|
|
121
|
-
startNonce = await this.
|
|
128
|
+
startNonce = await this.readOnchainNonce(address);
|
|
122
129
|
}
|
|
123
130
|
if (flaky) {
|
|
124
131
|
const globalNext = getGlobalNextNonce(chainId, key);
|
|
@@ -170,23 +177,30 @@ export class NonceManager {
|
|
|
170
177
|
let queryResults;
|
|
171
178
|
const uniqueQueries = Array.from(needQueryByKey.values()).map((entries) => entries[0]);
|
|
172
179
|
try {
|
|
173
|
-
const batchRequests = uniqueQueries.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
const batchRequests = uniqueQueries.flatMap(({ address }, idx) => [
|
|
181
|
+
{
|
|
182
|
+
method: 'eth_getTransactionCount',
|
|
183
|
+
params: [address, 'latest'],
|
|
184
|
+
id: idx * 2 + 1,
|
|
185
|
+
jsonrpc: '2.0',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
method: 'eth_getTransactionCount',
|
|
189
|
+
params: [address, 'pending'],
|
|
190
|
+
id: idx * 2 + 2,
|
|
191
|
+
jsonrpc: '2.0',
|
|
192
|
+
},
|
|
193
|
+
]);
|
|
179
194
|
const rawResults = await asBatchSendProvider(this.provider)._send(batchRequests);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
195
|
+
const byId = new Map();
|
|
196
|
+
for (const row of rawResults) {
|
|
197
|
+
const id = Number(row.id);
|
|
198
|
+
byId.set(id, row.result ? parseInt(row.result, 16) : 0);
|
|
199
|
+
}
|
|
200
|
+
queryResults = uniqueQueries.map((_, idx) => Math.max(byId.get(idx * 2 + 1) || 0, byId.get(idx * 2 + 2) || 0));
|
|
186
201
|
}
|
|
187
202
|
catch {
|
|
188
|
-
|
|
189
|
-
queryResults = await Promise.all(queryPromises);
|
|
203
|
+
queryResults = await Promise.all(uniqueQueries.map(({ address }) => this.readOnchainNonce(address)));
|
|
190
204
|
}
|
|
191
205
|
for (let i = 0; i < uniqueQueries.length; i++) {
|
|
192
206
|
const { address } = uniqueQueries[i];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 刷持有人 - 类型定义
|
|
3
3
|
*/
|
|
4
4
|
import type { GeneratedWallet } from '../../utils/wallet.js';
|
|
5
|
-
export type HoldersMakerChain = 'BSC' | 'MONAD' | 'XLAYER' | 'ENI';
|
|
5
|
+
export type HoldersMakerChain = 'BSC' | 'MONAD' | 'XLAYER' | 'ENI' | 'ROBINHOOD';
|
|
6
6
|
export type HoldersMakerTradeType = 'four' | 'flap' | 'v2' | 'v3' | 'daoaas';
|
|
7
7
|
export type BaseTokenType = 'native' | 'usdt' | 'usdc';
|
|
8
8
|
export type HoldersMakerConfig = {
|
|
@@ -91,7 +91,7 @@ export interface LPInfo {
|
|
|
91
91
|
bestPools: BestPoolInfo[];
|
|
92
92
|
}
|
|
93
93
|
export interface InspectOptions {
|
|
94
|
-
chain: 'BSC' | 'MONAD' | 'XLAYER' | 'ENI';
|
|
94
|
+
chain: 'BSC' | 'MONAD' | 'XLAYER' | 'ENI' | 'ROBINHOOD';
|
|
95
95
|
rpcUrl: string;
|
|
96
96
|
flapChain?: FlapChain;
|
|
97
97
|
v3FeeTiers?: number[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsonRpcProvider } from 'ethers';
|
|
2
|
-
export type SupportedChain = 'BSC' | 'MONAD' | 'XLAYER' | 'ENI';
|
|
2
|
+
export type SupportedChain = 'BSC' | 'MONAD' | 'XLAYER' | 'ENI' | 'ROBINHOOD';
|
|
3
3
|
export interface QuoteParams {
|
|
4
4
|
provider: JsonRpcProvider;
|
|
5
5
|
tokenIn: string;
|
|
@@ -30,6 +30,10 @@ export declare const DIRECT_ROUTERS: {
|
|
|
30
30
|
readonly IROSWAP_V2: "0x93b1199fA96f6b9b1f993AA0226eB6076968a9BF";
|
|
31
31
|
readonly WEGAS: "0x6d1e851446f4d004ae2a72f9afed85e8829a205e";
|
|
32
32
|
};
|
|
33
|
+
readonly ROBINHOOD: {
|
|
34
|
+
readonly UNISWAP_V3: "0xCaf681a66D020601342297493863E78C959E5cb2";
|
|
35
|
+
readonly WETH: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73";
|
|
36
|
+
};
|
|
33
37
|
};
|
|
34
38
|
/** 链 ID 映射 */
|
|
35
39
|
export declare const CHAIN_IDS: {
|
|
@@ -37,6 +41,7 @@ export declare const CHAIN_IDS: {
|
|
|
37
41
|
readonly MONAD: 143;
|
|
38
42
|
readonly XLAYER: 196;
|
|
39
43
|
readonly ENI: 173;
|
|
44
|
+
readonly ROBINHOOD: 4663;
|
|
40
45
|
};
|
|
41
46
|
/** V2 Router ABI */
|
|
42
47
|
export declare const V2_ROUTER_ABI: string[];
|
|
@@ -33,6 +33,10 @@ export const DIRECT_ROUTERS = {
|
|
|
33
33
|
IROSWAP_V2: ADDRESSES.ENI.IroSwapV2Router,
|
|
34
34
|
WEGAS: ADDRESSES.ENI.WEGAS,
|
|
35
35
|
},
|
|
36
|
+
ROBINHOOD: {
|
|
37
|
+
UNISWAP_V3: ADDRESSES.ROBINHOOD.UniswapV3Router,
|
|
38
|
+
WETH: ADDRESSES.ROBINHOOD.WETH,
|
|
39
|
+
},
|
|
36
40
|
};
|
|
37
41
|
/** 链 ID 映射 */
|
|
38
42
|
export const CHAIN_IDS = {
|
|
@@ -40,6 +44,7 @@ export const CHAIN_IDS = {
|
|
|
40
44
|
MONAD: CHAINS.MONAD.chainId,
|
|
41
45
|
XLAYER: CHAINS.XLAYER.chainId,
|
|
42
46
|
ENI: CHAINS.ENI.chainId,
|
|
47
|
+
ROBINHOOD: CHAINS.ROBINHOOD.chainId,
|
|
43
48
|
};
|
|
44
49
|
/** V2 Router ABI */
|
|
45
50
|
export const V2_ROUTER_ABI = _V2_ROUTER_ABI;
|
|
@@ -10,4 +10,4 @@ export * from './direct-router-v2.js';
|
|
|
10
10
|
export * from './direct-router-v3.js';
|
|
11
11
|
export { getRouterAddress } from './router.js';
|
|
12
12
|
export { DIRECT_ROUTERS } from './direct-router-helpers.js';
|
|
13
|
-
export type { DirectV2BuyParams, DirectV2SellParams, DirectV3BuyParams, DirectV3SellParams, DirectRouterResult, DirectRouterSignConfig, DexKey, RouterVersion, } from './types.js';
|
|
13
|
+
export type { DirectV2BuyParams, DirectV2SellParams, DirectV3BuyParams, DirectV3SellParams, DirectRouterResult, DirectRouterSignConfig, DexKey, RouterVersion, DirectRouterChain, } from './types.js';
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 提供直接调用 DEX Router 的功能(不走代理合约)
|
|
5
5
|
*/
|
|
6
|
-
export { directV2BatchBuy, directV2BatchSell, directV3BatchBuy, directV3BatchSell, getRouterAddress, DIRECT_ROUTERS, type DirectV2BuyParams, type DirectV2SellParams, type DirectV3BuyParams, type DirectV3SellParams, type DirectRouterResult, type DirectRouterSignConfig, type DexKey, type RouterVersion, } from './direct-router.js';
|
|
6
|
+
export { directV2BatchBuy, directV2BatchSell, directV3BatchBuy, directV3BatchSell, getRouterAddress, DIRECT_ROUTERS, type DirectV2BuyParams, type DirectV2SellParams, type DirectV3BuyParams, type DirectV3SellParams, type DirectRouterResult, type DirectRouterSignConfig, type DexKey, type RouterVersion, type DirectRouterChain, } from './direct-router.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JsonRpcProvider } from 'ethers';
|
|
2
|
-
import type { DirectRouterSignConfig, DexKey, RouterVersion } from './types.js';
|
|
2
|
+
import type { DirectRouterSignConfig, DexKey, DirectRouterChain, RouterVersion } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* 截断小数位数,避免超过代币精度导致 parseUnits 报错
|
|
5
5
|
* 例如:truncateDecimals("21906.025000000000000000", 1) => "21906.0"
|
|
@@ -53,4 +53,4 @@ export declare function isDYORSwap(chain: string, routerAddress: string): boolea
|
|
|
53
53
|
/**
|
|
54
54
|
* 根据链和 DEX 获取 Router 地址
|
|
55
55
|
*/
|
|
56
|
-
export declare function getRouterAddress(chain:
|
|
56
|
+
export declare function getRouterAddress(chain: DirectRouterChain, dexKey: DexKey, version: RouterVersion): string;
|
|
@@ -34,6 +34,8 @@ export function getWrappedNative(chain) {
|
|
|
34
34
|
return DIRECT_ROUTERS.XLAYER.WOKB;
|
|
35
35
|
if (chainUpper === 'ENI')
|
|
36
36
|
return DIRECT_ROUTERS.ENI.WEGAS;
|
|
37
|
+
if (chainUpper === 'ROBINHOOD')
|
|
38
|
+
return DIRECT_ROUTERS.ROBINHOOD.WETH;
|
|
37
39
|
throw new Error(`Unsupported chain: ${chain}`);
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
@@ -189,5 +191,10 @@ export function getRouterAddress(chain, dexKey, version) {
|
|
|
189
191
|
}
|
|
190
192
|
return DIRECT_ROUTERS.ENI.DSWAP_V2;
|
|
191
193
|
}
|
|
194
|
+
if (chainUpper === 'ROBINHOOD') {
|
|
195
|
+
if (version === 'v3')
|
|
196
|
+
return DIRECT_ROUTERS.ROBINHOOD.UNISWAP_V3;
|
|
197
|
+
throw new Error(`Robinhood Chain 只有 Uniswap V3,不支持 ${dexKey}/${version}`);
|
|
198
|
+
}
|
|
192
199
|
throw new Error(`Unsupported chain/dex combination: ${chain}/${dexKey}`);
|
|
193
200
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GeneratedWallet } from '../../utils/wallet.js';
|
|
2
|
+
export type DirectRouterChain = 'BSC' | 'MONAD' | 'XLAYER' | 'ENI' | 'ROBINHOOD';
|
|
2
3
|
export interface DirectRouterSignConfig {
|
|
3
4
|
rpcUrl: string;
|
|
4
5
|
chainId?: number;
|
|
@@ -20,7 +21,7 @@ export interface DirectRouterSignConfig {
|
|
|
20
21
|
skipProfit?: boolean;
|
|
21
22
|
}
|
|
22
23
|
export interface DirectV2BuyParams {
|
|
23
|
-
chain:
|
|
24
|
+
chain: DirectRouterChain;
|
|
24
25
|
privateKeys: string[];
|
|
25
26
|
buyAmounts: string[];
|
|
26
27
|
tokenAddress: string;
|
|
@@ -31,7 +32,7 @@ export interface DirectV2BuyParams {
|
|
|
31
32
|
config: DirectRouterSignConfig;
|
|
32
33
|
}
|
|
33
34
|
export interface DirectV2SellParams {
|
|
34
|
-
chain:
|
|
35
|
+
chain: DirectRouterChain;
|
|
35
36
|
privateKeys: string[];
|
|
36
37
|
sellPercentages?: number[];
|
|
37
38
|
sellAmounts?: string[];
|
|
@@ -43,7 +44,7 @@ export interface DirectV2SellParams {
|
|
|
43
44
|
config: DirectRouterSignConfig;
|
|
44
45
|
}
|
|
45
46
|
export interface DirectV3BuyParams {
|
|
46
|
-
chain:
|
|
47
|
+
chain: DirectRouterChain;
|
|
47
48
|
privateKeys: string[];
|
|
48
49
|
buyAmounts: string[];
|
|
49
50
|
tokenAddress: string;
|
|
@@ -55,7 +56,7 @@ export interface DirectV3BuyParams {
|
|
|
55
56
|
config: DirectRouterSignConfig;
|
|
56
57
|
}
|
|
57
58
|
export interface DirectV3SellParams {
|
|
58
|
-
chain:
|
|
59
|
+
chain: DirectRouterChain;
|
|
59
60
|
privateKeys: string[];
|
|
60
61
|
sellPercentages?: number[];
|
|
61
62
|
sellAmounts?: string[];
|
|
@@ -25,7 +25,7 @@ export async function holdersMaker(params) {
|
|
|
25
25
|
const { rpcUrl, chain = 'BSC', chainId: configChainId, tradeType = 'flap', gasLimit = DEFAULT_GAS_LIMIT, gasPriceGwei = DEFAULT_GAS_PRICE_GWEI, bribeAmount = 0.000001, txType: configTxType = 0, disperseHopCount = 0, // ✅ 分发多跳数(默认0=直接转账)
|
|
26
26
|
} = config;
|
|
27
27
|
// ENI 链强制使用 EIP-1559 (type 2)
|
|
28
|
-
const txType = chain === 'ENI' ? 2 : configTxType;
|
|
28
|
+
const txType = chain === 'ENI' || chain === 'ROBINHOOD' ? 2 : configTxType;
|
|
29
29
|
const result = {
|
|
30
30
|
success: false,
|
|
31
31
|
newWallets: [],
|
|
@@ -172,7 +172,7 @@ export async function holdersMaker(params) {
|
|
|
172
172
|
buyTx = await buildV2BuyTxWithERC20(buyerWallet, tokenAddress, erc20TokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType, config.v2RouterAddress, chain);
|
|
173
173
|
break;
|
|
174
174
|
case 'v3':
|
|
175
|
-
buyTx = await buildV3BuyTxWithERC20(buyerWallet, tokenAddress, erc20TokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType, config.v3Fee);
|
|
175
|
+
buyTx = await buildV3BuyTxWithERC20(buyerWallet, tokenAddress, erc20TokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType, config.v3Fee, chain);
|
|
176
176
|
break;
|
|
177
177
|
default:
|
|
178
178
|
throw new Error(`ERC20 模式不支持交易类型: ${tradeType}`);
|
|
@@ -10,3 +10,5 @@ export declare const WOKB_ADDRESS: "0xe538905cf8410324e03a5a23c1c177a474d59b2b";
|
|
|
10
10
|
export declare const ENI_V2_ROUTER_ADDRESS: "0x97ed8be49d9a8b86247090aa41e908e76b8fcf22";
|
|
11
11
|
export declare const ENI_DAOAAS_PORTAL_ADDRESS: "0x0078341F4CD09D7E6D3125ef6eb4cB2F46CA8310";
|
|
12
12
|
export declare const WEGAS_ADDRESS: "0x6d1e851446f4d004ae2a72f9afed85e8829a205e";
|
|
13
|
+
export declare const ROBINHOOD_UNISWAP_V3_ROUTER_ADDRESS: "0xCaf681a66D020601342297493863E78C959E5cb2";
|
|
14
|
+
export declare const ROBINHOOD_WETH_ADDRESS: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73";
|
|
@@ -13,3 +13,5 @@ export const WOKB_ADDRESS = ADDRESSES.XLAYER.WOKB;
|
|
|
13
13
|
export const ENI_V2_ROUTER_ADDRESS = ADDRESSES.ENI.DswapV2Router;
|
|
14
14
|
export const ENI_DAOAAS_PORTAL_ADDRESS = ADDRESSES.ENI.DaoaasPortal;
|
|
15
15
|
export const WEGAS_ADDRESS = ADDRESSES.ENI.WEGAS;
|
|
16
|
+
export const ROBINHOOD_UNISWAP_V3_ROUTER_ADDRESS = ADDRESSES.ROBINHOOD.UniswapV3Router;
|
|
17
|
+
export const ROBINHOOD_WETH_ADDRESS = ADDRESSES.ROBINHOOD.WETH;
|
|
@@ -41,4 +41,4 @@ export declare function buildV2BuyTxWithERC20(wallet: Wallet, tokenAddress: stri
|
|
|
41
41
|
*
|
|
42
42
|
* ✅ 修复:使用 ethers.Interface 手动编码 calldata,避免 multicall 重载问题
|
|
43
43
|
*/
|
|
44
|
-
export declare function buildV3BuyTxWithERC20(wallet: Wallet, tokenAddress: string, baseTokenAddress: string, buyAmount: bigint, nonce: number, gasPrice: bigint, gasLimit: number, chainId: number, txType: 0 | 2, v3Fee?: number): Promise<string>;
|
|
44
|
+
export declare function buildV3BuyTxWithERC20(wallet: Wallet, tokenAddress: string, baseTokenAddress: string, buyAmount: bigint, nonce: number, gasPrice: bigint, gasLimit: number, chainId: number, txType: 0 | 2, v3Fee?: number, chain?: HoldersMakerChain): Promise<string>;
|
|
@@ -3,7 +3,7 @@ import { getDeadline } from '../../../utils/bundle-helpers.js';
|
|
|
3
3
|
import { ADDRESSES } from '../../../utils/constants.js';
|
|
4
4
|
import { FLAP_PORTAL_ADDRESSES } from '../../flap/constants.js';
|
|
5
5
|
import { V2_ROUTER_ABI, V3_ROUTER02_ABI, V3_ROUTER_LEGACY_ABI } from '../../../abis/common.js';
|
|
6
|
-
import { ENI_DAOAAS_PORTAL_ADDRESS, FOUR_TM2_ABI, PANCAKE_V3_ROUTER_ADDRESS, XLAYER_V3_ROUTER_ADDRESS, } from './addresses.js';
|
|
6
|
+
import { ENI_DAOAAS_PORTAL_ADDRESS, FOUR_TM2_ABI, PANCAKE_V3_ROUTER_ADDRESS, ROBINHOOD_UNISWAP_V3_ROUTER_ADDRESS, XLAYER_V3_ROUTER_ADDRESS, } from './addresses.js';
|
|
7
7
|
import { getRouterAddress, getWrappedNativeAddress } from './routing.js';
|
|
8
8
|
/**
|
|
9
9
|
* 构建 Flap 买入交易
|
|
@@ -141,7 +141,11 @@ export async function buildV3BuyTx(wallet, tokenAddress, buyAmount, nonce, gasPr
|
|
|
141
141
|
chain) {
|
|
142
142
|
const deadline = getDeadline();
|
|
143
143
|
const wrappedNative = getWrappedNativeAddress(chain);
|
|
144
|
-
const routerAddress = chain === '
|
|
144
|
+
const routerAddress = chain === 'ROBINHOOD'
|
|
145
|
+
? ROBINHOOD_UNISWAP_V3_ROUTER_ADDRESS
|
|
146
|
+
: chain === 'XLAYER'
|
|
147
|
+
? XLAYER_V3_ROUTER_ADDRESS
|
|
148
|
+
: PANCAKE_V3_ROUTER_ADDRESS;
|
|
145
149
|
// ✅ 根据链类型选择正确的 ABI 版本
|
|
146
150
|
// XLayer/Monad 使用旧版 Router(exactInputSingle 包含 deadline)
|
|
147
151
|
// BSC 使用新版 SwapRouter02(exactInputSingle 不含 deadline)
|
|
@@ -238,7 +242,7 @@ export async function buildV2BuyTxWithERC20(wallet, tokenAddress, baseTokenAddre
|
|
|
238
242
|
*
|
|
239
243
|
* ✅ 修复:使用 ethers.Interface 手动编码 calldata,避免 multicall 重载问题
|
|
240
244
|
*/
|
|
241
|
-
export async function buildV3BuyTxWithERC20(wallet, tokenAddress, baseTokenAddress, buyAmount, nonce, gasPrice, gasLimit, chainId, txType, v3Fee = 2500) {
|
|
245
|
+
export async function buildV3BuyTxWithERC20(wallet, tokenAddress, baseTokenAddress, buyAmount, nonce, gasPrice, gasLimit, chainId, txType, v3Fee = 2500, chain) {
|
|
242
246
|
const deadline = getDeadline();
|
|
243
247
|
const v3RouterIface = new ethers.Interface(V3_ROUTER02_ABI);
|
|
244
248
|
// 构建 exactInputSingle calldata
|
|
@@ -257,8 +261,9 @@ export async function buildV3BuyTxWithERC20(wallet, tokenAddress, baseTokenAddre
|
|
|
257
261
|
deadline,
|
|
258
262
|
[exactInputSingleData],
|
|
259
263
|
]);
|
|
264
|
+
const routerAddress = chain === 'ROBINHOOD' ? ROBINHOOD_UNISWAP_V3_ROUTER_ADDRESS : PANCAKE_V3_ROUTER_ADDRESS;
|
|
260
265
|
const tx = {
|
|
261
|
-
to:
|
|
266
|
+
to: routerAddress,
|
|
262
267
|
data: multicallData,
|
|
263
268
|
value: 0n,
|
|
264
269
|
nonce,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ADDRESSES } from '../../../utils/constants.js';
|
|
2
|
-
import { ENI_DAOAAS_PORTAL_ADDRESS, ENI_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ADDRESS, USDC_ADDRESS, USDT_ADDRESS, WBNB_ADDRESS, WEGAS_ADDRESS, WOKB_ADDRESS, XLAYER_V2_ROUTER_ADDRESS, XLAYER_V3_ROUTER_ADDRESS, } from './addresses.js';
|
|
2
|
+
import { ENI_DAOAAS_PORTAL_ADDRESS, ENI_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ADDRESS, USDC_ADDRESS, USDT_ADDRESS, WBNB_ADDRESS, WEGAS_ADDRESS, WOKB_ADDRESS, XLAYER_V2_ROUTER_ADDRESS, XLAYER_V3_ROUTER_ADDRESS, ROBINHOOD_UNISWAP_V3_ROUTER_ADDRESS, ROBINHOOD_WETH_ADDRESS, } from './addresses.js';
|
|
3
3
|
export function getBaseTokenAddress(baseToken, chain, customAddress) {
|
|
4
4
|
if (customAddress)
|
|
5
5
|
return customAddress;
|
|
@@ -26,6 +26,11 @@ export function getRouterAddress(tradeType, chain, v2RouterOverride) {
|
|
|
26
26
|
throw new Error(`ENI 链不支持交易类型: ${tradeType},仅支持 daoaas/v2`);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
if (chain === 'ROBINHOOD') {
|
|
30
|
+
if (tradeType === 'v3')
|
|
31
|
+
return ROBINHOOD_UNISWAP_V3_ROUTER_ADDRESS;
|
|
32
|
+
throw new Error(`Robinhood Chain 只支持 v3,不支持 ${tradeType}`);
|
|
33
|
+
}
|
|
29
34
|
const isXLayer = chain === 'XLAYER';
|
|
30
35
|
switch (tradeType) {
|
|
31
36
|
case 'v2':
|
|
@@ -41,5 +46,7 @@ export function getRouterAddress(tradeType, chain, v2RouterOverride) {
|
|
|
41
46
|
export function getWrappedNativeAddress(chain) {
|
|
42
47
|
if (chain === 'ENI')
|
|
43
48
|
return WEGAS_ADDRESS;
|
|
49
|
+
if (chain === 'ROBINHOOD')
|
|
50
|
+
return ROBINHOOD_WETH_ADDRESS;
|
|
44
51
|
return chain === 'XLAYER' ? WOKB_ADDRESS : WBNB_ADDRESS;
|
|
45
52
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type TokenStateV8 } from '../flap/portal.js';
|
|
2
|
+
export declare function isFlapInnerMarket(status: unknown): boolean;
|
|
3
|
+
/** 1–10 当百分数;≤10000 当 bps(Four feeRate 10000=100%)。 */
|
|
4
|
+
export declare function normalizeFeeToBps(raw: number): number | undefined;
|
|
5
|
+
export declare function flapV8TaxBps(st: TokenStateV8): number;
|
|
6
|
+
export declare function readBscOuterTaxBps(token: string, rpcUrl: string): Promise<number | undefined>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LP inspect 税率:Flap 真实税 / Four TaxToken / 外盘常见 view。
|
|
3
|
+
* 非 Flap 币 Portal 槽位常残留 100 bps,不能当 Pancake 税币税率。
|
|
4
|
+
*/
|
|
5
|
+
import { Contract } from 'ethers';
|
|
6
|
+
import { ZERO_ADDRESS } from '../../core/constants/index.js';
|
|
7
|
+
import { TokenStatus } from '../flap/portal.js';
|
|
8
|
+
import { getTaxTokenInfo, isTaxToken } from '../four/tax-token.js';
|
|
9
|
+
import { getProvider } from './inspect-helpers.js';
|
|
10
|
+
const GENERIC_TAX_FNS = [
|
|
11
|
+
'feeRate',
|
|
12
|
+
'buyTax',
|
|
13
|
+
'sellTax',
|
|
14
|
+
'buyFee',
|
|
15
|
+
'sellFee',
|
|
16
|
+
'taxFee',
|
|
17
|
+
'totalBuyFee',
|
|
18
|
+
'totalSellFee',
|
|
19
|
+
];
|
|
20
|
+
const GENERIC_TAX_ABI = GENERIC_TAX_FNS.map((name) => `function ${name}() view returns (uint256)`);
|
|
21
|
+
export function isFlapInnerMarket(status) {
|
|
22
|
+
const n = Number(status);
|
|
23
|
+
return n === TokenStatus.Tradable || n === TokenStatus.InDuel;
|
|
24
|
+
}
|
|
25
|
+
function isZeroAddress(value) {
|
|
26
|
+
const s = String(value || '').toLowerCase();
|
|
27
|
+
return !s || s === ZERO_ADDRESS.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
/** 1–10 当百分数;≤10000 当 bps(Four feeRate 10000=100%)。 */
|
|
30
|
+
export function normalizeFeeToBps(raw) {
|
|
31
|
+
if (!Number.isFinite(raw) || raw <= 0)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (raw <= 10)
|
|
34
|
+
return Math.round(raw * 100);
|
|
35
|
+
if (raw <= 10000)
|
|
36
|
+
return Math.round(raw);
|
|
37
|
+
if (raw > 1e14 && raw <= 1e18) {
|
|
38
|
+
const bps = Math.round((raw / 1e18) * 10000);
|
|
39
|
+
return bps > 0 && bps <= 10000 ? bps : undefined;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
export function flapV8TaxBps(st) {
|
|
44
|
+
const status = Number(st.status);
|
|
45
|
+
if (status === TokenStatus.Tradable || status === TokenStatus.InDuel) {
|
|
46
|
+
return Math.max(Number(st.buyTaxRate ?? 0n), Number(st.sellTaxRate ?? 0n));
|
|
47
|
+
}
|
|
48
|
+
if (status !== TokenStatus.DEX)
|
|
49
|
+
return 0;
|
|
50
|
+
if (isZeroAddress(st.pool) || isZeroAddress(st.quoteTokenAddress))
|
|
51
|
+
return 0;
|
|
52
|
+
return Math.max(Number(st.buyTaxRate ?? 0n), Number(st.sellTaxRate ?? 0n));
|
|
53
|
+
}
|
|
54
|
+
async function readGenericTokenTaxBps(token, provider) {
|
|
55
|
+
const tokenContract = new Contract(token, GENERIC_TAX_ABI, provider);
|
|
56
|
+
const reads = await Promise.allSettled(GENERIC_TAX_FNS.map(async (name) => {
|
|
57
|
+
const value = await tokenContract.getFunction(name).staticCall();
|
|
58
|
+
return normalizeFeeToBps(Number(value));
|
|
59
|
+
}));
|
|
60
|
+
let maxBps = 0;
|
|
61
|
+
for (const item of reads) {
|
|
62
|
+
if (item.status !== 'fulfilled' || typeof item.value !== 'number')
|
|
63
|
+
continue;
|
|
64
|
+
if (item.value > maxBps)
|
|
65
|
+
maxBps = item.value;
|
|
66
|
+
}
|
|
67
|
+
return maxBps > 0 ? maxBps : undefined;
|
|
68
|
+
}
|
|
69
|
+
export async function readBscOuterTaxBps(token, rpcUrl) {
|
|
70
|
+
try {
|
|
71
|
+
if (await isTaxToken(token, rpcUrl)) {
|
|
72
|
+
const fee = Number((await getTaxTokenInfo(token, rpcUrl)).config.feeRate);
|
|
73
|
+
const bps = normalizeFeeToBps(fee);
|
|
74
|
+
if (bps)
|
|
75
|
+
return bps;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* 非 Four TaxToken */
|
|
80
|
+
}
|
|
81
|
+
return readGenericTokenTaxBps(token, getProvider(rpcUrl));
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ZERO_ADDRESS } from '../../core/constants/index.js';
|
|
3
|
+
import { TokenStatus } from '../flap/portal.js';
|
|
4
|
+
import { flapV8TaxBps, isFlapInnerMarket, normalizeFeeToBps } from './inspect-tax.js';
|
|
5
|
+
function v8(partial) {
|
|
6
|
+
return {
|
|
7
|
+
reserve: 0n,
|
|
8
|
+
circulatingSupply: 0n,
|
|
9
|
+
price: 0n,
|
|
10
|
+
tokenVersion: 6,
|
|
11
|
+
r: 0n,
|
|
12
|
+
h: 0n,
|
|
13
|
+
k: 0n,
|
|
14
|
+
dexSupplyThresh: 0n,
|
|
15
|
+
quoteTokenAddress: ZERO_ADDRESS,
|
|
16
|
+
nativeToQuoteSwapEnabled: false,
|
|
17
|
+
extensionID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
18
|
+
buyTaxRate: 0n,
|
|
19
|
+
sellTaxRate: 0n,
|
|
20
|
+
taxRate: 0n,
|
|
21
|
+
pool: ZERO_ADDRESS,
|
|
22
|
+
progress: 0n,
|
|
23
|
+
lpFeeProfile: 0,
|
|
24
|
+
dexId: 0,
|
|
25
|
+
...partial,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
describe('normalizeFeeToBps', () => {
|
|
29
|
+
it('treats 1–10 as percent', () => {
|
|
30
|
+
expect(normalizeFeeToBps(5)).toBe(500);
|
|
31
|
+
expect(normalizeFeeToBps(1)).toBe(100);
|
|
32
|
+
});
|
|
33
|
+
it('keeps Four feeRate / bps as-is up to 10000', () => {
|
|
34
|
+
expect(normalizeFeeToBps(500)).toBe(500);
|
|
35
|
+
expect(normalizeFeeToBps(0)).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('flapV8TaxBps', () => {
|
|
39
|
+
it('reads inner flap tax', () => {
|
|
40
|
+
expect(isFlapInnerMarket(TokenStatus.Tradable)).toBe(true);
|
|
41
|
+
expect(flapV8TaxBps(v8({
|
|
42
|
+
status: TokenStatus.Tradable,
|
|
43
|
+
buyTaxRate: 100n,
|
|
44
|
+
sellTaxRate: 500n,
|
|
45
|
+
}))).toBe(500);
|
|
46
|
+
});
|
|
47
|
+
it('ignores DEX leftover 100 bps when pool/quote are zero', () => {
|
|
48
|
+
expect(flapV8TaxBps(v8({
|
|
49
|
+
status: TokenStatus.DEX,
|
|
50
|
+
buyTaxRate: 100n,
|
|
51
|
+
sellTaxRate: 100n,
|
|
52
|
+
}))).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
it('keeps graduated flap tax when pool and quote exist', () => {
|
|
55
|
+
expect(flapV8TaxBps(v8({
|
|
56
|
+
status: TokenStatus.DEX,
|
|
57
|
+
pool: '0x1111111111111111111111111111111111111111',
|
|
58
|
+
quoteTokenAddress: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
|
|
59
|
+
buyTaxRate: 100n,
|
|
60
|
+
sellTaxRate: 500n,
|
|
61
|
+
}))).toBe(500);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -7,30 +7,12 @@
|
|
|
7
7
|
import { Contract, formatEther } from 'ethers';
|
|
8
8
|
import { ADDRESSES, ZERO_ADDRESS } from '../../core/constants/index.js';
|
|
9
9
|
import { Helper3 } from '../four/contracts/helper3.js';
|
|
10
|
-
import { FlapPortal
|
|
11
|
-
import { getTaxTokenInfo, isTaxToken } from '../four/tax-token.js';
|
|
10
|
+
import { FlapPortal } from '../flap/portal.js';
|
|
12
11
|
import { DEFAULT_V3_FEE_TIERS } from './lp-inspect-helpers.js';
|
|
13
12
|
import { getChainConfig } from './registry.js';
|
|
14
13
|
import { determinePlatform, formatBalance, getProvider, getTokenDecimals, normalizeLiquidity, queryV2Pairs, queryV3Pools, } from './inspect-helpers.js';
|
|
15
|
-
|
|
16
|
-
// 常量(从公共模块导入)
|
|
17
|
-
// ============================================================================
|
|
14
|
+
import { flapV8TaxBps, isFlapInnerMarket, readBscOuterTaxBps } from './inspect-tax.js';
|
|
18
15
|
const MULTICALL3_ADDRESS = ADDRESSES.BSC.Multicall3;
|
|
19
|
-
/** 内盘可交易:1=Tradable,2=InDuel。0=无效(非 Flap 币常见)、3=已杀死、4=已毕业外盘,都不能当内盘提前返回。 */
|
|
20
|
-
function isFlapInnerMarket(status) {
|
|
21
|
-
const n = Number(status);
|
|
22
|
-
return n === TokenStatus.Tradable || n === TokenStatus.InDuel;
|
|
23
|
-
}
|
|
24
|
-
/** BSC V8 买卖税。只有真实 Flap 状态才读买卖税,避免非 Flap 币 slot 残留 100 bps。 */
|
|
25
|
-
function flapV8TaxBps(st) {
|
|
26
|
-
const status = Number(st.status);
|
|
27
|
-
if (status !== TokenStatus.Tradable &&
|
|
28
|
-
status !== TokenStatus.InDuel &&
|
|
29
|
-
status !== TokenStatus.DEX) {
|
|
30
|
-
return 0;
|
|
31
|
-
}
|
|
32
|
-
return Math.max(Number(st.buyTaxRate ?? 0n), Number(st.sellTaxRate ?? 0n));
|
|
33
|
-
}
|
|
34
16
|
// ============================================================================
|
|
35
17
|
// 主查询函数
|
|
36
18
|
// ============================================================================
|
|
@@ -207,17 +189,9 @@ export async function inspectTokenLP(token, opts) {
|
|
|
207
189
|
const offerTokens = formatBalance(info.offers, shouldFormat);
|
|
208
190
|
const lastPrice = formatBalance(info.lastPrice, shouldFormat);
|
|
209
191
|
result.four = { helper: ADDRESSES.BSC.TokenManagerHelper3, reserveNative, offerTokens, lastPrice };
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const taxRate = fee > 0 && fee <= 10 ? fee * 100 : fee;
|
|
214
|
-
if (taxRate > 0)
|
|
215
|
-
result.taxRate = taxRate;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
// 非 TaxToken 或读费率失败
|
|
220
|
-
}
|
|
192
|
+
const innerTax = await readBscOuterTaxBps(token, opts.rpcUrl);
|
|
193
|
+
if (innerTax)
|
|
194
|
+
result.taxRate = innerTax;
|
|
221
195
|
return result;
|
|
222
196
|
}
|
|
223
197
|
}
|
|
@@ -351,10 +325,14 @@ export async function inspectTokenLP(token, opts) {
|
|
|
351
325
|
// 确定平台类型
|
|
352
326
|
result.platform = determinePlatform(result.allPools);
|
|
353
327
|
// ========================================
|
|
354
|
-
// 4.
|
|
328
|
+
// 4. 外盘税率:合约 view 优先,Flap Portal 只认真实毕业盘
|
|
355
329
|
// ========================================
|
|
356
|
-
|
|
357
|
-
|
|
330
|
+
if (opts.chain === 'BSC' && !result.taxRate) {
|
|
331
|
+
const outerTax = await readBscOuterTaxBps(token, opts.rpcUrl);
|
|
332
|
+
if (outerTax)
|
|
333
|
+
result.taxRate = outerTax;
|
|
334
|
+
}
|
|
335
|
+
if (opts.flapChain && !result.flap && !result.taxRate && result.platform !== 'UNKNOWN') {
|
|
358
336
|
try {
|
|
359
337
|
const flap = new FlapPortal({ chain: opts.flapChain, rpcUrl: opts.rpcUrl });
|
|
360
338
|
let taxRate = 0;
|
|
@@ -97,6 +97,20 @@ export const CHAIN_DEX_CONFIGS = {
|
|
|
97
97
|
},
|
|
98
98
|
},
|
|
99
99
|
},
|
|
100
|
+
ROBINHOOD: {
|
|
101
|
+
wrappedNative: ADDRESSES.ROBINHOOD.WETH,
|
|
102
|
+
wrappedNativeSymbol: 'WETH',
|
|
103
|
+
stableCoins: [],
|
|
104
|
+
dexes: {
|
|
105
|
+
UNISWAP: {
|
|
106
|
+
name: 'Uniswap',
|
|
107
|
+
v3Factory: ADDRESSES.ROBINHOOD.UniswapV3Factory,
|
|
108
|
+
v3Router: ADDRESSES.ROBINHOOD.UniswapV3Router,
|
|
109
|
+
quoterV2: ADDRESSES.ROBINHOOD.UniswapV3Quoter,
|
|
110
|
+
enabled: true,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
100
114
|
};
|
|
101
115
|
// ============================================================================
|
|
102
116
|
// 动态配置 API
|
|
@@ -23,7 +23,7 @@ export { quoteV2, quoteV3, quote, getTokenToNativeQuote, getNativeToTokenQuote,
|
|
|
23
23
|
getWrappedNativeAddress, getStableCoins, } from '../core/quote/quote-helpers.js';
|
|
24
24
|
export { submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, submitDirectToRpcSmart as xlayerBundleSubmit, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult, } from '../bundle/submit.js';
|
|
25
25
|
export { submitBundleToEni, submitEniParallelThenProfit, type EniBundleSubmitConfig, type EniBundleSubmitResult, } from '../chains/eni/submit.js';
|
|
26
|
-
export { directV2BatchBuy, directV2BatchSell, directV3BatchBuy, directV3BatchSell, getRouterAddress, DIRECT_ROUTERS, type DirectV2BuyParams, type DirectV2SellParams, type DirectV3BuyParams, type DirectV3SellParams, type DirectRouterResult, type DirectRouterSignConfig, type DexKey, type RouterVersion, } from '../domains/dex/index.js';
|
|
26
|
+
export { directV2BatchBuy, directV2BatchSell, directV3BatchBuy, directV3BatchSell, getRouterAddress, DIRECT_ROUTERS, type DirectV2BuyParams, type DirectV2SellParams, type DirectV3BuyParams, type DirectV3SellParams, type DirectRouterResult, type DirectRouterSignConfig, type DexKey, type RouterVersion, type DirectRouterChain, } from '../domains/dex/index.js';
|
|
27
27
|
export * as XLayer from '../chains/xlayer/eoa/index.js';
|
|
28
28
|
export { createPortalQuery as xlayerCreatePortalQuery, PortalQuery as XLayerPortalQuery, encodeBuyCall as xlayerEncodeBuyCall, encodeSellCall as xlayerEncodeSellCall, encodeApproveCall as xlayerEncodeApproveCall, parseOkb as xlayerParseOkb, formatOkb as xlayerFormatOkb, XLAYER_CHAIN_ID, FLAP_PORTAL as XLAYER_FLAP_PORTAL, ENTRYPOINT_V06 as XLAYER_ENTRYPOINT, SIMPLE_ACCOUNT_FACTORY as XLAYER_FACTORY, PARTICLE_BUNDLER_URL as XLAYER_BUNDLER_URL, WOKB as XLAYER_WOKB, POTATOSWAP_V2_ROUTER as XLAYER_POTATOSWAP_ROUTER, signEoaWashBuyTransactions as xlayerSignEoaWashBuyTransactions, signEoaWashSellTransactions as xlayerSignEoaWashSellTransactions, signEoaWashVolumeTransactions as xlayerSignEoaWashVolumeTransactions, type EoaWashPoolType as XLayerEoaWashPoolType, type EoaWashVolumeParams as XLayerEoaWashVolumeParams, type EoaWashVolumeResult as XLayerEoaWashVolumeResult, xlayerQuickBatchSwapMerkle, xlayerCrossSwapMerkle, xlayerPureCrossSwapMerkle, type XLayerEoaSwapConfig, type XLayerQuickBatchSwapParams, type XLayerQuickBatchSwapResult, type XLayerCrossSwapParams, type XLayerCrossSwapResult, type XLayerPureCrossSwapParams, type XLayerPureCrossSwapResult, type BundleSwapParams as XLayerBundleSwapParams, type BundleSwapSignParams as XLayerBundleSwapSignParams, type BundleBatchSwapParams as XLayerBundleBatchSwapParams, type BundleBatchSwapSignParams as XLayerBundleBatchSwapSignParams, type BuyFirstParams as XLayerBuyFirstParams, type BuyFirstResult as XLayerBuyFirstResult, } from '../chains/xlayer/eoa/index.js';
|
|
29
29
|
export * as XLayerEIP7702 from '../chains/xlayer/eip7702/index.js';
|