@clonegod/ttd-base-common 1.1.2 → 1.1.4
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/common/constants.d.ts +1 -0
- package/dist/common/constants.js +15 -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 +1 -1
package/dist/common/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UniswapV3RouterABI = exports.UniswapV3PoolABI = exports.EVENT_SIGNATURES = exports.EVENT_NAMES = exports.WETH_ADDRESS = exports.NATIVE_ETH_ADDRESS = exports.BASE_CHAIN_ID = void 0;
|
|
3
|
+
exports.UniswapV3RouterABI = exports.SlipstreamV3PoolABI = exports.UniswapV3PoolABI = exports.EVENT_SIGNATURES = exports.EVENT_NAMES = exports.WETH_ADDRESS = exports.NATIVE_ETH_ADDRESS = exports.BASE_CHAIN_ID = void 0;
|
|
4
4
|
exports.BASE_CHAIN_ID = 8453;
|
|
5
5
|
exports.NATIVE_ETH_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
6
6
|
exports.WETH_ADDRESS = '0x4200000000000000000000000000000000000006';
|
|
@@ -33,6 +33,20 @@ exports.UniswapV3PoolABI = [
|
|
|
33
33
|
"event Mint(address sender, address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1)",
|
|
34
34
|
"event Burn(address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1)"
|
|
35
35
|
];
|
|
36
|
+
exports.SlipstreamV3PoolABI = [
|
|
37
|
+
"function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, bool unlocked)",
|
|
38
|
+
"function liquidity() external view returns (uint128)",
|
|
39
|
+
"function tickSpacing() external view returns (int24)",
|
|
40
|
+
"function factory() external view returns (address)",
|
|
41
|
+
"function token0() external view returns (address)",
|
|
42
|
+
"function token1() external view returns (address)",
|
|
43
|
+
"function fee() external view returns (uint24)",
|
|
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
|
+
"function tickBitmap(int16 wordPosition) external view returns (uint256)",
|
|
46
|
+
"event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint128 protocolFeesToken0, uint128 protocolFeesToken1)",
|
|
47
|
+
"event Mint(address sender, address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1)",
|
|
48
|
+
"event Burn(address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1)"
|
|
49
|
+
];
|
|
36
50
|
exports.UniswapV3RouterABI = [
|
|
37
51
|
'function exactInputSingle(tuple(address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)',
|
|
38
52
|
'function exactInput(tuple(bytes path, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum)) external payable returns (uint256 amountOut)',
|
|
@@ -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;
|