@clonegod/ttd-base-common 1.1.3 → 1.1.5
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/appconfig/base_env_args.d.ts +1 -0
- package/dist/appconfig/base_env_args.js +1 -0
- package/dist/common/constants.js +1 -1
- package/dist/quote/get_base_token_price.d.ts +1 -2
- package/dist/quote/get_base_token_price.js +33 -11
- package/dist/quote/tick/tick_lens_loaders.d.ts +15 -0
- package/dist/quote/tick/tick_lens_loaders.js +87 -1
- package/package.json +2 -2
|
@@ -37,6 +37,7 @@ export declare class BaseEnvArgs extends EnvArgs {
|
|
|
37
37
|
tick_cache_neighboring_words: number;
|
|
38
38
|
tick_cache_ttl: number;
|
|
39
39
|
tick_cache_min_update_interval: number;
|
|
40
|
+
standard_wss_enabled: boolean;
|
|
40
41
|
flashblocks_enabled: boolean;
|
|
41
42
|
flashblocks_quicknode_wss: string;
|
|
42
43
|
flashblocks_dedup_ttl_ms: number;
|
|
@@ -38,6 +38,7 @@ const constants_1 = require("../common/constants");
|
|
|
38
38
|
min_quote_interval_ms: { env: 'MIN_QUOTE_INTERVAL_MS', type: 'number', default: 10000, desc: '最小询价间隔(ms)' },
|
|
39
39
|
use_pricefeed_for_orderbook: { env: 'USE_PRICEFEED_FOR_ORDERBOOK', type: 'boolean', default: false, desc: 'PriceFeed (v3 source) 报价是否推 orderbook' },
|
|
40
40
|
trade_parse_fetch_block_time: { env: 'TRADE_PARSE_FETCH_BLOCK_TIME', type: 'boolean', default: false, desc: '解析交易 receipt 时是否额外 RPC 查询 block 时间戳' },
|
|
41
|
+
standard_wss_enabled: { env: 'STANDARD_WSS_ENABLED', type: 'boolean', default: false, desc: '标准 WSS confirmed logs 订阅开关;默认关闭(newFlashblocks 已替代)' },
|
|
41
42
|
flashblocks_enabled: { env: 'FLASHBLOCKS_ENABLED', type: 'boolean', default: true, desc: 'Flashblocks pending logs 主路径开关' },
|
|
42
43
|
flashblocks_quicknode_wss: { env: 'FLASHBLOCKS_QUICKNODE_WSS', type: 'string', default: '', desc: 'QuickNode Flashblocks WSS endpoint(FLASHBLOCKS_ENABLED=true 时必填;不提供默认值,避免误用 demo endpoint)' },
|
|
43
44
|
flashblocks_dedup_ttl_ms: { env: 'FLASHBLOCKS_DEDUP_TTL_MS', type: 'number', default: 30000, desc: '事件去重 TTL:Flashblocks + 标准 WSS 同 tx 重复推送的去重窗口(ms)' },
|
package/dist/common/constants.js
CHANGED
|
@@ -41,7 +41,7 @@ exports.SlipstreamV3PoolABI = [
|
|
|
41
41
|
"function token0() external view returns (address)",
|
|
42
42
|
"function token1() external view returns (address)",
|
|
43
43
|
"function fee() external view returns (uint24)",
|
|
44
|
-
"function ticks(int24 tick) external view returns (uint128 liquidityGross, int128 liquidityNet, int56 tickCumulativeOutside, uint160 secondsPerLiquidityOutsideX128, uint32 secondsOutside, bool initialized)",
|
|
44
|
+
"function ticks(int24 tick) external view returns (uint128 liquidityGross, int128 liquidityNet, uint128 stakedLiquidityNet, uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128, uint256 rewardGrowthOutsideX128, int56 tickCumulativeOutside, uint160 secondsPerLiquidityOutsideX128, uint32 secondsOutside, bool initialized)",
|
|
45
45
|
"function tickBitmap(int16 wordPosition) external view returns (uint256)",
|
|
46
46
|
"event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint128 protocolFeesToken0, uint128 protocolFeesToken1)",
|
|
47
47
|
"event Mint(address sender, address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1)",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { FormattedTokenPrice } from '@clonegod/ttd-core';
|
|
2
|
-
type PriceSource = '
|
|
2
|
+
export type PriceSource = 'cache_only' | 'cache_first' | 'force_fetch';
|
|
3
3
|
export declare function get_base_token_price_info(addresses: string[], opts?: {
|
|
4
4
|
source?: PriceSource;
|
|
5
5
|
}): Promise<Map<string, FormattedTokenPrice>>;
|
|
6
|
-
export {};
|
|
@@ -4,8 +4,9 @@ exports.get_base_token_price_info = get_base_token_price_info;
|
|
|
4
4
|
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
5
|
const DEFI_LLAMA_BASE_CHAIN = 'base';
|
|
6
6
|
const GECKO_BASE_NETWORK = 'base';
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const EXTERNAL_BATCH_SIZE = 10;
|
|
8
|
+
const EXTERNAL_BATCH_DELAY_MS = 1000;
|
|
9
|
+
const CACHE_BATCH_SIZE = 100;
|
|
9
10
|
async function fetchFromDefiLlama(addresses) {
|
|
10
11
|
const result = new Map();
|
|
11
12
|
if (addresses.length === 0)
|
|
@@ -54,36 +55,57 @@ async function fetchFromGeckoTerminal(addresses) {
|
|
|
54
55
|
}
|
|
55
56
|
return result;
|
|
56
57
|
}
|
|
58
|
+
const CACHE_CHANNEL = {
|
|
59
|
+
name: 'CachedPrice',
|
|
60
|
+
fn: ttd_core_1.fetchPriceFromCache,
|
|
61
|
+
batchSize: CACHE_BATCH_SIZE,
|
|
62
|
+
batchDelayMs: 0,
|
|
63
|
+
};
|
|
64
|
+
const EXTERNAL_CHANNELS = [
|
|
65
|
+
{ name: 'DefiLlama', fn: fetchFromDefiLlama, batchSize: EXTERNAL_BATCH_SIZE, batchDelayMs: EXTERNAL_BATCH_DELAY_MS },
|
|
66
|
+
{ name: 'GeckoTerminal', fn: fetchFromGeckoTerminal, batchSize: EXTERNAL_BATCH_SIZE, batchDelayMs: EXTERNAL_BATCH_DELAY_MS },
|
|
67
|
+
];
|
|
57
68
|
async function get_base_token_price_info(addresses, opts) {
|
|
69
|
+
const source = opts?.source ?? 'cache_only';
|
|
58
70
|
addresses = Array.from(new Set(addresses.map(a => a.toLowerCase())));
|
|
59
71
|
const result = new Map();
|
|
60
72
|
if (addresses.length === 0)
|
|
61
73
|
return result;
|
|
62
|
-
const channels = [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
];
|
|
74
|
+
const channels = source === 'cache_only' ? [CACHE_CHANNEL] :
|
|
75
|
+
source === 'cache_first' ? [CACHE_CHANNEL, ...EXTERNAL_CHANNELS] :
|
|
76
|
+
[...EXTERNAL_CHANNELS];
|
|
77
|
+
const externalHits = [];
|
|
66
78
|
let remaining = [...addresses];
|
|
67
79
|
for (const ch of channels) {
|
|
68
80
|
if (remaining.length === 0)
|
|
69
81
|
break;
|
|
70
|
-
const batches = (0, ttd_core_1.chunkArray)(remaining,
|
|
82
|
+
const batches = (0, ttd_core_1.chunkArray)(remaining, ch.batchSize);
|
|
71
83
|
(0, ttd_core_1.log_debug)(`[get_base_token_price] ${ch.name}: ${remaining.length} addrs in ${batches.length} batches`);
|
|
72
84
|
for (let i = 0; i < batches.length; i++) {
|
|
73
85
|
try {
|
|
74
86
|
const hit = await ch.fn(batches[i]);
|
|
75
|
-
for (const [k, v] of hit)
|
|
87
|
+
for (const [k, v] of hit) {
|
|
76
88
|
result.set(k, v);
|
|
89
|
+
if (ch.name !== 'CachedPrice') {
|
|
90
|
+
externalHits.push({ address: k, price: v.price, source: ch.name });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
77
93
|
}
|
|
78
94
|
catch (e) {
|
|
79
95
|
(0, ttd_core_1.log_warn)(`[get_base_token_price] ${ch.name} batch ${i + 1} error: ${e?.message ?? e}`);
|
|
80
96
|
}
|
|
81
|
-
if (i < batches.length - 1)
|
|
82
|
-
await (0, ttd_core_1.sleep)(
|
|
97
|
+
if (i < batches.length - 1 && ch.batchDelayMs > 0)
|
|
98
|
+
await (0, ttd_core_1.sleep)(ch.batchDelayMs);
|
|
83
99
|
}
|
|
84
100
|
remaining = remaining.filter(a => !result.has(a));
|
|
85
101
|
}
|
|
86
|
-
if (
|
|
102
|
+
if (externalHits.length > 0) {
|
|
103
|
+
await (0, ttd_core_1.cache_new_market_price_batch)(externalHits);
|
|
104
|
+
}
|
|
105
|
+
if (source === 'cache_only' && remaining.length > 0) {
|
|
106
|
+
(0, ttd_core_1.warnPriceCacheMiss)(remaining);
|
|
107
|
+
}
|
|
108
|
+
if (source === 'force_fetch' && result.size === 0) {
|
|
87
109
|
throw new Error(`Unable to fetch price for any token: ${addresses.join(', ')}`);
|
|
88
110
|
}
|
|
89
111
|
return result;
|
|
@@ -22,3 +22,18 @@ export declare class MulticallTickLensLoader implements TickLensLoader {
|
|
|
22
22
|
loadBitmapWords(poolAddress: string, bitmapIndexes: number[]): Promise<Map<number, TickInfo>>;
|
|
23
23
|
private loadBitmapWordsIndividually;
|
|
24
24
|
}
|
|
25
|
+
export declare class SlipstreamTickLensLoader implements TickLensLoader {
|
|
26
|
+
private ethersLib;
|
|
27
|
+
private provider;
|
|
28
|
+
private multicallAddress;
|
|
29
|
+
private poolIface;
|
|
30
|
+
private multicallContract;
|
|
31
|
+
private tickSpacingCache;
|
|
32
|
+
constructor(params: {
|
|
33
|
+
ethersLib: any;
|
|
34
|
+
provider: any;
|
|
35
|
+
multicallAddress?: string;
|
|
36
|
+
});
|
|
37
|
+
loadBitmapWords(poolAddress: string, bitmapIndexes: number[]): Promise<Map<number, TickInfo>>;
|
|
38
|
+
private getTickSpacing;
|
|
39
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MulticallTickLensLoader = exports.BASE_TICK_LENS_ADDRESSES = exports.MULTICALL3_ADDRESS = void 0;
|
|
3
|
+
exports.SlipstreamTickLensLoader = exports.MulticallTickLensLoader = exports.BASE_TICK_LENS_ADDRESSES = exports.MULTICALL3_ADDRESS = void 0;
|
|
4
4
|
const ttd_core_1 = require("@clonegod/ttd-core");
|
|
5
|
+
const constants_1 = require("../../common/constants");
|
|
5
6
|
exports.MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
|
|
6
7
|
exports.BASE_TICK_LENS_ADDRESSES = {
|
|
7
8
|
UNISWAP_V3: '0x0CdeE061c75D43c82520eD998C23ac2991c9ac6d',
|
|
@@ -156,3 +157,88 @@ class MulticallTickLensLoader {
|
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
159
|
exports.MulticallTickLensLoader = MulticallTickLensLoader;
|
|
160
|
+
class SlipstreamTickLensLoader {
|
|
161
|
+
constructor(params) {
|
|
162
|
+
this.tickSpacingCache = new Map();
|
|
163
|
+
this.ethersLib = params.ethersLib;
|
|
164
|
+
this.provider = params.provider;
|
|
165
|
+
this.multicallAddress = params.multicallAddress || exports.MULTICALL3_ADDRESS;
|
|
166
|
+
const InterfaceClass = params.ethersLib.Interface || params.ethersLib.utils?.Interface;
|
|
167
|
+
if (!InterfaceClass) {
|
|
168
|
+
throw new Error('ethersLib.Interface not found, ensure ethers v5 or v6 is passed');
|
|
169
|
+
}
|
|
170
|
+
this.poolIface = new InterfaceClass(constants_1.SlipstreamV3PoolABI);
|
|
171
|
+
this.multicallContract = new params.ethersLib.Contract(this.multicallAddress, MULTICALL3_ABI, this.provider);
|
|
172
|
+
}
|
|
173
|
+
async loadBitmapWords(poolAddress, bitmapIndexes) {
|
|
174
|
+
const result = new Map();
|
|
175
|
+
if (bitmapIndexes.length === 0)
|
|
176
|
+
return result;
|
|
177
|
+
try {
|
|
178
|
+
const tickSpacing = await this.getTickSpacing(poolAddress);
|
|
179
|
+
const bitmapCalls = bitmapIndexes.map(idx => ({
|
|
180
|
+
target: poolAddress,
|
|
181
|
+
callData: this.poolIface.encodeFunctionData('tickBitmap', [idx]),
|
|
182
|
+
}));
|
|
183
|
+
const callMethod = this.multicallContract.aggregate.staticCall || this.multicallContract.callStatic?.aggregate;
|
|
184
|
+
const [, bitmapReturnData] = await callMethod(bitmapCalls);
|
|
185
|
+
const populatedTicks = [];
|
|
186
|
+
for (let i = 0; i < bitmapReturnData.length; i++) {
|
|
187
|
+
try {
|
|
188
|
+
const decoded = this.poolIface.decodeFunctionResult('tickBitmap', bitmapReturnData[i]);
|
|
189
|
+
const bitmap = typeof decoded[0] === 'bigint' ? decoded[0] : BigInt(decoded[0].toString());
|
|
190
|
+
if (bitmap === BigInt(0))
|
|
191
|
+
continue;
|
|
192
|
+
const wordPos = bitmapIndexes[i];
|
|
193
|
+
for (let bit = 0; bit < 256; bit++) {
|
|
194
|
+
if ((bitmap & (BigInt(1) << BigInt(bit))) !== BigInt(0)) {
|
|
195
|
+
const compressed = wordPos * 256 + bit;
|
|
196
|
+
populatedTicks.push(compressed * tickSpacing);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
(0, ttd_core_1.log_warn)(`[SlipstreamTickLens] decode tickBitmap idx=${bitmapIndexes[i]} failed: ${e.message}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (populatedTicks.length === 0) {
|
|
205
|
+
(0, ttd_core_1.log_debug)(`[SlipstreamTickLens] no populated ticks in ${bitmapIndexes.length} bitmap words for ${poolAddress}`);
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
const tickCalls = populatedTicks.map(t => ({
|
|
209
|
+
target: poolAddress,
|
|
210
|
+
callData: this.poolIface.encodeFunctionData('ticks', [t]),
|
|
211
|
+
}));
|
|
212
|
+
const [, tickReturnData] = await callMethod(tickCalls);
|
|
213
|
+
for (let i = 0; i < tickReturnData.length; i++) {
|
|
214
|
+
try {
|
|
215
|
+
const decoded = this.poolIface.decodeFunctionResult('ticks', tickReturnData[i]);
|
|
216
|
+
const liquidityGross = typeof decoded[0] === 'bigint' ? decoded[0] : BigInt(decoded[0].toString());
|
|
217
|
+
const liquidityNet = typeof decoded[1] === 'bigint' ? decoded[1] : BigInt(decoded[1].toString());
|
|
218
|
+
if (liquidityGross === BigInt(0) && liquidityNet === BigInt(0))
|
|
219
|
+
continue;
|
|
220
|
+
result.set(populatedTicks[i], { liquidityNet, liquidityGross });
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
(0, ttd_core_1.log_warn)(`[SlipstreamTickLens] decode ticks(${populatedTicks[i]}) failed: ${e.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
(0, ttd_core_1.log_debug)(`[SlipstreamTickLens] Loaded ${result.size}/${populatedTicks.length} ticks from ${bitmapIndexes.length} bitmap words (2 RPC calls)`);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
(0, ttd_core_1.log_warn)(`[SlipstreamTickLens] multicall failed for ${poolAddress}: ${error.message}`);
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
async getTickSpacing(poolAddress) {
|
|
234
|
+
const cached = this.tickSpacingCache.get(poolAddress);
|
|
235
|
+
if (cached !== undefined)
|
|
236
|
+
return cached;
|
|
237
|
+
const poolContract = new this.ethersLib.Contract(poolAddress, constants_1.SlipstreamV3PoolABI, this.provider);
|
|
238
|
+
const ts = await poolContract.tickSpacing();
|
|
239
|
+
const value = Number(ts);
|
|
240
|
+
this.tickSpacingCache.set(poolAddress, value);
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
exports.SlipstreamTickLensLoader = SlipstreamTickLensLoader;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clonegod/ttd-base-common",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Base common library",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"push": "npm run build && npm publish"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@clonegod/ttd-core": "3.1.
|
|
17
|
+
"@clonegod/ttd-core": "3.1.76",
|
|
18
18
|
"axios": "1.15.0",
|
|
19
19
|
"dotenv": "^16.4.7",
|
|
20
20
|
"ethers": "^5.8.0"
|