@steerprotocol/liquidity-meter 1.0.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/README.md +207 -0
- package/dist/adapters/onchain.d.ts +51 -0
- package/dist/adapters/onchain.js +158 -0
- package/dist/adapters/uniswapv3-subgraph.d.ts +44 -0
- package/dist/adapters/uniswapv3-subgraph.js +105 -0
- package/dist/adapters/withdraw.d.ts +14 -0
- package/dist/adapters/withdraw.js +150 -0
- package/dist/api.d.ts +72 -0
- package/dist/api.js +180 -0
- package/dist/cli/liquidity-depth.d.ts +2 -0
- package/dist/cli/liquidity-depth.js +1160 -0
- package/dist/core/depth.d.ts +48 -0
- package/dist/core/depth.js +314 -0
- package/dist/handlers/cron.d.ts +27 -0
- package/dist/handlers/cron.js +68 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/prices.d.ts +15 -0
- package/dist/prices.js +205 -0
- package/dist/wagmi/config.d.ts +2106 -0
- package/dist/wagmi/config.js +24 -0
- package/dist/wagmi/generated.d.ts +2019 -0
- package/dist/wagmi/generated.js +346 -0
- package/package.json +47 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type Tick = {
|
|
2
|
+
index: number;
|
|
3
|
+
liquidityNet: bigint;
|
|
4
|
+
};
|
|
5
|
+
export type TokenInfoUSD = {
|
|
6
|
+
decimals: number;
|
|
7
|
+
usdPrice: number;
|
|
8
|
+
};
|
|
9
|
+
export type MarketDepthInput = {
|
|
10
|
+
sqrtPriceX96: bigint;
|
|
11
|
+
tick: number;
|
|
12
|
+
liquidity: bigint;
|
|
13
|
+
feePips?: number;
|
|
14
|
+
ticks: Tick[];
|
|
15
|
+
token0: TokenInfoUSD;
|
|
16
|
+
token1: TokenInfoUSD;
|
|
17
|
+
percentBuckets?: number[];
|
|
18
|
+
};
|
|
19
|
+
export type MarketDepthOutput = {
|
|
20
|
+
buyCumUSD: number[];
|
|
21
|
+
sellCumUSD: number[];
|
|
22
|
+
buyBandsUSD: number[];
|
|
23
|
+
sellBandsUSD: number[];
|
|
24
|
+
depthPlus2USD: number;
|
|
25
|
+
depthMinus2USD: number;
|
|
26
|
+
buyCumToken1: bigint[];
|
|
27
|
+
sellCumToken0: bigint[];
|
|
28
|
+
};
|
|
29
|
+
export type PriceImpactInput = {
|
|
30
|
+
sqrtPriceX96: bigint;
|
|
31
|
+
tick: number;
|
|
32
|
+
liquidity: bigint;
|
|
33
|
+
feePips?: number;
|
|
34
|
+
ticks: Tick[];
|
|
35
|
+
buySizesToken1: bigint[];
|
|
36
|
+
sellSizesToken0: bigint[];
|
|
37
|
+
};
|
|
38
|
+
export type PriceImpactOutput = {
|
|
39
|
+
buyPct: number[];
|
|
40
|
+
sellPct: number[];
|
|
41
|
+
};
|
|
42
|
+
export declare const DepthInternals: {
|
|
43
|
+
readonly MIN_TICK: -887272;
|
|
44
|
+
readonly MAX_TICK: 887272;
|
|
45
|
+
};
|
|
46
|
+
export declare function getSqrtRatioAtTick(tick: number): bigint;
|
|
47
|
+
export declare function computeMarketDepthBands(input: MarketDepthInput): MarketDepthOutput;
|
|
48
|
+
export declare function computePriceImpactsBySizes(input: PriceImpactInput): PriceImpactOutput;
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Uniswap v3 market depth and price impact core (TypeScript)
|
|
3
|
+
- Pure functions, no network/IO
|
|
4
|
+
- BigInt math using Q64.96 fixed-point
|
|
5
|
+
*/
|
|
6
|
+
// ---------------- Fixed-point + constants ----------------
|
|
7
|
+
const Q32 = 1n << 32n;
|
|
8
|
+
const Q96 = 1n << 96n;
|
|
9
|
+
const MAX_UINT256 = (1n << 256n) - 1n;
|
|
10
|
+
export const DepthInternals = {
|
|
11
|
+
MIN_TICK: -887272,
|
|
12
|
+
MAX_TICK: 887272,
|
|
13
|
+
};
|
|
14
|
+
const MIN_SQRT_RATIO = 4295128739n;
|
|
15
|
+
const MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342n;
|
|
16
|
+
function mulDiv(a, b, d) {
|
|
17
|
+
return (a * b) / d;
|
|
18
|
+
}
|
|
19
|
+
function mulDivRoundingUp(a, b, d) {
|
|
20
|
+
const prod = a * b;
|
|
21
|
+
const q = prod / d;
|
|
22
|
+
return prod % d === 0n ? q : q + 1n;
|
|
23
|
+
}
|
|
24
|
+
// ---------------- TickMath (v3 exact) ----------------
|
|
25
|
+
// Returns Q64.96 sqrt price at the LOWER boundary of `tick`.
|
|
26
|
+
export function getSqrtRatioAtTick(tick) {
|
|
27
|
+
if (tick < DepthInternals.MIN_TICK || tick > DepthInternals.MAX_TICK) {
|
|
28
|
+
throw new Error('TICK_OUT_OF_RANGE');
|
|
29
|
+
}
|
|
30
|
+
let absTick = tick < 0 ? -tick : tick;
|
|
31
|
+
// 1.0001^(2^i) constants in Q128.128
|
|
32
|
+
let ratio = (absTick & 0x1) !== 0
|
|
33
|
+
? 0xfffcb933bd6fad37aa2d162d1a594001n
|
|
34
|
+
: 0x100000000000000000000000000000000n;
|
|
35
|
+
if ((absTick & 0x2) !== 0)
|
|
36
|
+
ratio = (ratio * 0xfff97272373d413259a46990580e213an) >> 128n;
|
|
37
|
+
if ((absTick & 0x4) !== 0)
|
|
38
|
+
ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdccn) >> 128n;
|
|
39
|
+
if ((absTick & 0x8) !== 0)
|
|
40
|
+
ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0n) >> 128n;
|
|
41
|
+
if ((absTick & 0x10) !== 0)
|
|
42
|
+
ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644n) >> 128n;
|
|
43
|
+
if ((absTick & 0x20) !== 0)
|
|
44
|
+
ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0n) >> 128n;
|
|
45
|
+
if ((absTick & 0x40) !== 0)
|
|
46
|
+
ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861n) >> 128n;
|
|
47
|
+
if ((absTick & 0x80) !== 0)
|
|
48
|
+
ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053n) >> 128n;
|
|
49
|
+
if ((absTick & 0x100) !== 0)
|
|
50
|
+
ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4n) >> 128n;
|
|
51
|
+
if ((absTick & 0x200) !== 0)
|
|
52
|
+
ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54n) >> 128n;
|
|
53
|
+
if ((absTick & 0x400) !== 0)
|
|
54
|
+
ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3n) >> 128n;
|
|
55
|
+
if ((absTick & 0x800) !== 0)
|
|
56
|
+
ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9n) >> 128n;
|
|
57
|
+
if ((absTick & 0x1000) !== 0)
|
|
58
|
+
ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825n) >> 128n;
|
|
59
|
+
if ((absTick & 0x2000) !== 0)
|
|
60
|
+
ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5n) >> 128n;
|
|
61
|
+
if ((absTick & 0x4000) !== 0)
|
|
62
|
+
ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7n) >> 128n;
|
|
63
|
+
if ((absTick & 0x8000) !== 0)
|
|
64
|
+
ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6n) >> 128n;
|
|
65
|
+
if ((absTick & 0x10000) !== 0)
|
|
66
|
+
ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9n) >> 128n;
|
|
67
|
+
if ((absTick & 0x20000) !== 0)
|
|
68
|
+
ratio = (ratio * 0x5d6af8dedb81196699c329225ee604n) >> 128n;
|
|
69
|
+
if ((absTick & 0x40000) !== 0)
|
|
70
|
+
ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98n) >> 128n;
|
|
71
|
+
if ((absTick & 0x80000) !== 0)
|
|
72
|
+
ratio = (ratio * 0x48a170391f7dc42444e8fa2n) >> 128n;
|
|
73
|
+
if (tick > 0)
|
|
74
|
+
ratio = MAX_UINT256 / ratio;
|
|
75
|
+
const sqrtPriceX96 = (ratio >> 32n) + (ratio % Q32 === 0n ? 0n : 1n);
|
|
76
|
+
return sqrtPriceX96;
|
|
77
|
+
}
|
|
78
|
+
// ---------------- Amount delta helpers ----------------
|
|
79
|
+
function amount1DeltaUp(sqrtA, sqrtB, liquidity, roundUp = true) {
|
|
80
|
+
if (sqrtB < sqrtA)
|
|
81
|
+
[sqrtA, sqrtB] = [sqrtB, sqrtA];
|
|
82
|
+
const num = liquidity * (sqrtB - sqrtA);
|
|
83
|
+
return roundUp ? mulDivRoundingUp(num, 1n, Q96) : mulDiv(num, 1n, Q96);
|
|
84
|
+
}
|
|
85
|
+
function amount0DeltaDown(sqrtA, sqrtB, liquidity, roundUp = true) {
|
|
86
|
+
if (sqrtB < sqrtA)
|
|
87
|
+
[sqrtA, sqrtB] = [sqrtB, sqrtA];
|
|
88
|
+
const num = liquidity * (sqrtB - sqrtA) * Q96;
|
|
89
|
+
const den = sqrtB * sqrtA;
|
|
90
|
+
return roundUp ? mulDivRoundingUp(num, 1n, den) : mulDiv(num, 1n, den);
|
|
91
|
+
}
|
|
92
|
+
function grossUpForFee(amountIn, feePips) {
|
|
93
|
+
if (!feePips)
|
|
94
|
+
return amountIn;
|
|
95
|
+
const feeDen = 1000000n;
|
|
96
|
+
const den = feeDen - BigInt(feePips);
|
|
97
|
+
return mulDivRoundingUp(amountIn, feeDen, den);
|
|
98
|
+
}
|
|
99
|
+
// ---------------- Utilities ----------------
|
|
100
|
+
function toUSDFromRaw(amount, decimals, usd) {
|
|
101
|
+
if (!usd || usd <= 0)
|
|
102
|
+
return 0;
|
|
103
|
+
const scale = 10n ** BigInt(Math.max(0, decimals));
|
|
104
|
+
const usdMicros = BigInt(Math.round(usd * 1e6));
|
|
105
|
+
const micros = (amount * usdMicros) / scale;
|
|
106
|
+
return Number(micros) / 1e6;
|
|
107
|
+
}
|
|
108
|
+
function clamp(val, lo, hi) {
|
|
109
|
+
return Math.max(lo, Math.min(hi, val));
|
|
110
|
+
}
|
|
111
|
+
function firstTickIndexGreaterThan(ticks, t) {
|
|
112
|
+
let lo = 0, hi = ticks.length;
|
|
113
|
+
while (lo < hi) {
|
|
114
|
+
const mid = (lo + hi) >> 1;
|
|
115
|
+
if (ticks[mid].index <= t)
|
|
116
|
+
lo = mid + 1;
|
|
117
|
+
else
|
|
118
|
+
hi = mid;
|
|
119
|
+
}
|
|
120
|
+
return lo;
|
|
121
|
+
}
|
|
122
|
+
function lastTickIndexAtOrBelow(ticks, t) {
|
|
123
|
+
return firstTickIndexGreaterThan(ticks, t) - 1;
|
|
124
|
+
}
|
|
125
|
+
function percentToTickDelta(pct) {
|
|
126
|
+
const r = 1 + pct / 100;
|
|
127
|
+
const delta = Math.log(r) / Math.log(1.0001);
|
|
128
|
+
return Math.max(1, Math.round(delta));
|
|
129
|
+
}
|
|
130
|
+
// ---------------- Integrations ----------------
|
|
131
|
+
function integrateUp(sqrtStart, currentTick, Lstart, targetTick, feePips, ticks) {
|
|
132
|
+
let s = sqrtStart;
|
|
133
|
+
let L = Lstart;
|
|
134
|
+
let tick = currentTick;
|
|
135
|
+
let i = firstTickIndexGreaterThan(ticks, tick);
|
|
136
|
+
const targetSqrt = getSqrtRatioAtTick(clamp(targetTick, DepthInternals.MIN_TICK, DepthInternals.MAX_TICK));
|
|
137
|
+
let acc = 0n;
|
|
138
|
+
while (s < targetSqrt) {
|
|
139
|
+
const nextTick = i < 0 || i >= ticks.length ? null : ticks[i];
|
|
140
|
+
const sqrtAtNext = nextTick ? getSqrtRatioAtTick(nextTick.index) : MAX_SQRT_RATIO;
|
|
141
|
+
const bound = sqrtAtNext < targetSqrt ? sqrtAtNext : targetSqrt;
|
|
142
|
+
const dy = amount1DeltaUp(s, bound, L, true);
|
|
143
|
+
const dyGross = grossUpForFee(dy, feePips);
|
|
144
|
+
acc += dyGross;
|
|
145
|
+
s = bound;
|
|
146
|
+
if (s === sqrtAtNext && nextTick) {
|
|
147
|
+
L = L + nextTick.liquidityNet;
|
|
148
|
+
tick = nextTick.index;
|
|
149
|
+
i += 1;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { token1In: acc, finalSqrt: s, finalTick: tick, finalL: L };
|
|
156
|
+
}
|
|
157
|
+
function integrateDown(sqrtStart, currentTick, Lstart, targetTick, feePips, ticks) {
|
|
158
|
+
let s = sqrtStart;
|
|
159
|
+
let L = Lstart;
|
|
160
|
+
let tick = currentTick;
|
|
161
|
+
let j = lastTickIndexAtOrBelow(ticks, tick);
|
|
162
|
+
const targetSqrt = getSqrtRatioAtTick(clamp(targetTick, DepthInternals.MIN_TICK, DepthInternals.MAX_TICK));
|
|
163
|
+
let acc = 0n;
|
|
164
|
+
while (s > targetSqrt) {
|
|
165
|
+
const prevTick = j >= 0 ? ticks[j] : null;
|
|
166
|
+
const sqrtAtPrev = prevTick ? getSqrtRatioAtTick(prevTick.index) : MIN_SQRT_RATIO;
|
|
167
|
+
const bound = sqrtAtPrev > targetSqrt ? sqrtAtPrev : targetSqrt;
|
|
168
|
+
const dx = amount0DeltaDown(bound, s, L, true);
|
|
169
|
+
const dxGross = grossUpForFee(dx, feePips);
|
|
170
|
+
acc += dxGross;
|
|
171
|
+
s = bound;
|
|
172
|
+
if (s === sqrtAtPrev && prevTick) {
|
|
173
|
+
L = L - prevTick.liquidityNet;
|
|
174
|
+
tick = prevTick.index - 1;
|
|
175
|
+
j -= 1;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { token0In: acc, finalSqrt: s, finalTick: tick, finalL: L };
|
|
182
|
+
}
|
|
183
|
+
// ---------------- Public API ----------------
|
|
184
|
+
export function computeMarketDepthBands(input) {
|
|
185
|
+
const { sqrtPriceX96, tick, liquidity, feePips = 0, ticks, token0, token1, percentBuckets = [1, 2, 5, 10], } = input;
|
|
186
|
+
const sortedTicks = [...ticks].sort((a, b) => a.index - b.index);
|
|
187
|
+
const upTargets = percentBuckets.map((p) => clamp(tick + percentToTickDelta(p), DepthInternals.MIN_TICK, DepthInternals.MAX_TICK));
|
|
188
|
+
const downTargets = percentBuckets.map((p) => clamp(tick - percentToTickDelta(p), DepthInternals.MIN_TICK, DepthInternals.MAX_TICK));
|
|
189
|
+
const buyCumToken1 = [];
|
|
190
|
+
const sellCumToken0 = [];
|
|
191
|
+
let sU = sqrtPriceX96, tU = tick, LU = liquidity;
|
|
192
|
+
for (const tgt of upTargets) {
|
|
193
|
+
const { token1In, finalSqrt, finalTick, finalL } = integrateUp(sU, tU, LU, tgt, feePips, sortedTicks);
|
|
194
|
+
const cum = (buyCumToken1.length ? buyCumToken1[buyCumToken1.length - 1] : 0n) + token1In;
|
|
195
|
+
buyCumToken1.push(cum);
|
|
196
|
+
sU = finalSqrt;
|
|
197
|
+
tU = finalTick;
|
|
198
|
+
LU = finalL;
|
|
199
|
+
}
|
|
200
|
+
let sD = sqrtPriceX96, tD = tick, LD = liquidity;
|
|
201
|
+
for (const tgt of downTargets) {
|
|
202
|
+
const { token0In, finalSqrt, finalTick, finalL } = integrateDown(sD, tD, LD, tgt, feePips, sortedTicks);
|
|
203
|
+
const cum = (sellCumToken0.length ? sellCumToken0[sellCumToken0.length - 1] : 0n) + token0In;
|
|
204
|
+
sellCumToken0.push(cum);
|
|
205
|
+
sD = finalSqrt;
|
|
206
|
+
tD = finalTick;
|
|
207
|
+
LD = finalL;
|
|
208
|
+
}
|
|
209
|
+
const buyCumUSD = buyCumToken1.map((a) => toUSDFromRaw(a, token1.decimals, token1.usdPrice));
|
|
210
|
+
const sellCumUSD = sellCumToken0.map((a) => toUSDFromRaw(a, token0.decimals, token0.usdPrice));
|
|
211
|
+
const toBands = (cum) => [cum[0], cum[1] - cum[0], cum[2] - cum[1], cum[3] - cum[2]];
|
|
212
|
+
const buyBandsUSD = toBands(buyCumUSD);
|
|
213
|
+
const sellBandsUSD = toBands(sellCumUSD);
|
|
214
|
+
const idx2 = percentBuckets.findIndex((p) => p === 2);
|
|
215
|
+
const depthPlus2USD = idx2 >= 0 ? buyCumUSD[idx2] : NaN;
|
|
216
|
+
const depthMinus2USD = idx2 >= 0 ? sellCumUSD[idx2] : NaN;
|
|
217
|
+
return { buyCumUSD, sellCumUSD, buyBandsUSD, sellBandsUSD, depthPlus2USD, depthMinus2USD, buyCumToken1, sellCumToken0 };
|
|
218
|
+
}
|
|
219
|
+
// ---------------- Price impact by size ----------------
|
|
220
|
+
function integrateUpByAmount(sqrtStart, currentTick, Lstart, budgetGross, // token1 gross in
|
|
221
|
+
feePips, ticks) {
|
|
222
|
+
let s = sqrtStart;
|
|
223
|
+
let L = Lstart;
|
|
224
|
+
let tick = currentTick;
|
|
225
|
+
let i = firstTickIndexGreaterThan(ticks, tick);
|
|
226
|
+
const feeDen = 1000000n;
|
|
227
|
+
const den = feeDen - BigInt(feePips || 0);
|
|
228
|
+
while (budgetGross > 0n && s < MAX_SQRT_RATIO) {
|
|
229
|
+
const nextTick = i < 0 || i >= ticks.length ? null : ticks[i];
|
|
230
|
+
const sqrtAtNext = nextTick ? getSqrtRatioAtTick(nextTick.index) : MAX_SQRT_RATIO;
|
|
231
|
+
const dyNetToNext = amount1DeltaUp(s, sqrtAtNext, L, true);
|
|
232
|
+
const dyGrossToNext = grossUpForFee(dyNetToNext, feePips || 0);
|
|
233
|
+
if (budgetGross < dyGrossToNext) {
|
|
234
|
+
const netIn = mulDiv(budgetGross, den, feeDen);
|
|
235
|
+
const deltaS = mulDivRoundingUp(netIn, Q96, L);
|
|
236
|
+
s = s + deltaS;
|
|
237
|
+
budgetGross = 0n;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
budgetGross -= dyGrossToNext;
|
|
242
|
+
s = sqrtAtNext;
|
|
243
|
+
if (nextTick) {
|
|
244
|
+
L = L + nextTick.liquidityNet;
|
|
245
|
+
tick = nextTick.index;
|
|
246
|
+
i += 1;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return { finalSqrt: s, finalTick: tick, finalL: L };
|
|
251
|
+
}
|
|
252
|
+
function integrateDownByAmount(sqrtStart, currentTick, Lstart, budgetGross, // token0 gross in
|
|
253
|
+
feePips, ticks) {
|
|
254
|
+
let s = sqrtStart;
|
|
255
|
+
let L = Lstart;
|
|
256
|
+
let tick = currentTick;
|
|
257
|
+
let j = lastTickIndexAtOrBelow(ticks, tick);
|
|
258
|
+
const feeDen = 1000000n;
|
|
259
|
+
const den = feeDen - BigInt(feePips || 0);
|
|
260
|
+
while (budgetGross > 0n && s > MIN_SQRT_RATIO) {
|
|
261
|
+
const prevTick = j >= 0 ? ticks[j] : null;
|
|
262
|
+
const sqrtAtPrev = prevTick ? getSqrtRatioAtTick(prevTick.index) : MIN_SQRT_RATIO;
|
|
263
|
+
const dxNetToPrev = amount0DeltaDown(sqrtAtPrev, s, L, true);
|
|
264
|
+
const dxGrossToPrev = grossUpForFee(dxNetToPrev, feePips || 0);
|
|
265
|
+
if (budgetGross < dxGrossToPrev) {
|
|
266
|
+
const netIn = mulDiv(budgetGross, den, feeDen);
|
|
267
|
+
const LQ = L * Q96;
|
|
268
|
+
const num = LQ * s;
|
|
269
|
+
const den2 = LQ + netIn * s;
|
|
270
|
+
const bound = den2 === 0n ? MIN_SQRT_RATIO : mulDiv(num, 1n, den2);
|
|
271
|
+
s = bound;
|
|
272
|
+
budgetGross = 0n;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
budgetGross -= dxGrossToPrev;
|
|
277
|
+
s = sqrtAtPrev;
|
|
278
|
+
if (prevTick) {
|
|
279
|
+
L = L - prevTick.liquidityNet;
|
|
280
|
+
tick = prevTick.index - 1;
|
|
281
|
+
j -= 1;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return { finalSqrt: s, finalTick: tick, finalL: L };
|
|
286
|
+
}
|
|
287
|
+
function priceImpactPercent(startSqrt, endSqrt) {
|
|
288
|
+
const a = startSqrt;
|
|
289
|
+
const b = endSqrt;
|
|
290
|
+
const scale = 1000000n;
|
|
291
|
+
const numRaw = (b - a) * (b + a) * 100n * scale;
|
|
292
|
+
const den = a * a;
|
|
293
|
+
let val;
|
|
294
|
+
if (numRaw >= 0n)
|
|
295
|
+
val = (numRaw + den / 2n) / den;
|
|
296
|
+
else
|
|
297
|
+
val = -((-numRaw + den / 2n) / den);
|
|
298
|
+
return Number(val) / Number(scale);
|
|
299
|
+
}
|
|
300
|
+
export function computePriceImpactsBySizes(input) {
|
|
301
|
+
const { sqrtPriceX96, tick, liquidity, feePips = 0, ticks, buySizesToken1, sellSizesToken0 } = input;
|
|
302
|
+
const sortedTicks = [...ticks].sort((a, b) => a.index - b.index);
|
|
303
|
+
const buyPct = [];
|
|
304
|
+
for (const size of buySizesToken1) {
|
|
305
|
+
const { finalSqrt } = integrateUpByAmount(sqrtPriceX96, tick, liquidity, size, feePips, sortedTicks);
|
|
306
|
+
buyPct.push(priceImpactPercent(sqrtPriceX96, finalSqrt));
|
|
307
|
+
}
|
|
308
|
+
const sellPct = [];
|
|
309
|
+
for (const size of sellSizesToken0) {
|
|
310
|
+
const { finalSqrt } = integrateDownByAmount(sqrtPriceX96, tick, liquidity, size, feePips, sortedTicks);
|
|
311
|
+
sellPct.push(priceImpactPercent(sqrtPriceX96, finalSqrt));
|
|
312
|
+
}
|
|
313
|
+
return { buyPct, sellPct };
|
|
314
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type MarketDepthOutput } from '../core/depth.js';
|
|
2
|
+
export type DataSource = 'subgraph' | 'onchain' | 'auto';
|
|
3
|
+
export type PriceSource = 'reserve' | 'dexscreener' | 'llama' | 'coingecko' | 'auto';
|
|
4
|
+
export type LiquidityDepthJobConfig = {
|
|
5
|
+
pools: string[];
|
|
6
|
+
percentBuckets?: number[];
|
|
7
|
+
dataSource?: DataSource;
|
|
8
|
+
subgraphUrl?: string;
|
|
9
|
+
rpcUrl?: string;
|
|
10
|
+
priceSource?: PriceSource;
|
|
11
|
+
reserveLimit?: number;
|
|
12
|
+
};
|
|
13
|
+
export type LiquidityDepthJobResult = {
|
|
14
|
+
pool: string;
|
|
15
|
+
meta?: any;
|
|
16
|
+
usdPrices: {
|
|
17
|
+
token0USD?: number;
|
|
18
|
+
token1USD?: number;
|
|
19
|
+
source0?: string;
|
|
20
|
+
source1?: string;
|
|
21
|
+
};
|
|
22
|
+
depth: MarketDepthOutput;
|
|
23
|
+
};
|
|
24
|
+
export declare function runLiquidityDepthJob(cfg: LiquidityDepthJobConfig): Promise<{
|
|
25
|
+
timestamp: number;
|
|
26
|
+
results: LiquidityDepthJobResult[];
|
|
27
|
+
}>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { computeMarketDepthBands } from '../core/depth.js';
|
|
2
|
+
import { fetchPoolSnapshot as fetchFromSubgraph } from '../adapters/uniswapv3-subgraph.js';
|
|
3
|
+
import { fetchPoolSnapshotViem as fetchOnchain } from '../adapters/onchain.js';
|
|
4
|
+
import { fetchTokenUSDPrices } from '../prices.js';
|
|
5
|
+
function pickSource({ dataSource, rpcUrl }) {
|
|
6
|
+
if (dataSource === 'subgraph' || dataSource === 'onchain')
|
|
7
|
+
return dataSource;
|
|
8
|
+
if (rpcUrl)
|
|
9
|
+
return 'onchain';
|
|
10
|
+
return 'subgraph';
|
|
11
|
+
}
|
|
12
|
+
export async function runLiquidityDepthJob(cfg) {
|
|
13
|
+
const percentBuckets = (cfg.percentBuckets && cfg.percentBuckets.length) ? cfg.percentBuckets : [1, 2, 5, 10];
|
|
14
|
+
const results = [];
|
|
15
|
+
const when = Math.floor(Date.now() / 1000);
|
|
16
|
+
for (const pool of cfg.pools) {
|
|
17
|
+
const source = pickSource({ dataSource: cfg.dataSource, rpcUrl: cfg.rpcUrl });
|
|
18
|
+
let snap;
|
|
19
|
+
if (source === 'onchain') {
|
|
20
|
+
if (!cfg.rpcUrl)
|
|
21
|
+
throw new Error('rpcUrl required for onchain data source');
|
|
22
|
+
snap = await fetchOnchain({ poolAddress: pool, rpcUrl: cfg.rpcUrl, percentBuckets });
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
snap = await fetchFromSubgraph({ poolAddress: pool, percentBuckets, subgraphUrl: cfg.subgraphUrl });
|
|
26
|
+
}
|
|
27
|
+
// Resolve USD prices, prefer snapshot-provided values if any
|
|
28
|
+
let token0USD = Number(snap.token0?.usdPrice || 0);
|
|
29
|
+
let token1USD = Number(snap.token1?.usdPrice || 0);
|
|
30
|
+
let src0;
|
|
31
|
+
let src1;
|
|
32
|
+
if (!token0USD || !token1USD) {
|
|
33
|
+
const id0 = snap.meta?.token0?.id || snap.token0?.id;
|
|
34
|
+
const id1 = snap.meta?.token1?.id || snap.token1?.id;
|
|
35
|
+
if (id0 && id1) {
|
|
36
|
+
const { byAddress } = await fetchTokenUSDPrices({ addresses: [String(id0), String(id1)], source: cfg.priceSource ?? 'auto', rpcUrl: cfg.rpcUrl, debug: false, reserveLimit: cfg.reserveLimit ?? 100 });
|
|
37
|
+
const p0 = byAddress[String(id0).toLowerCase()];
|
|
38
|
+
const p1 = byAddress[String(id1).toLowerCase()];
|
|
39
|
+
if (!token0USD && p0) {
|
|
40
|
+
token0USD = p0.price;
|
|
41
|
+
src0 = p0.source;
|
|
42
|
+
}
|
|
43
|
+
if (!token1USD && p1) {
|
|
44
|
+
token1USD = p1.price;
|
|
45
|
+
src1 = p1.source;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Compute depth
|
|
50
|
+
const depth = computeMarketDepthBands({
|
|
51
|
+
sqrtPriceX96: snap.sqrtPriceX96,
|
|
52
|
+
tick: snap.tick,
|
|
53
|
+
liquidity: snap.liquidity,
|
|
54
|
+
feePips: snap.feePips,
|
|
55
|
+
ticks: snap.ticks,
|
|
56
|
+
token0: { decimals: snap.token0.decimals, usdPrice: token0USD || 0 },
|
|
57
|
+
token1: { decimals: snap.token1.decimals, usdPrice: token1USD || 0 },
|
|
58
|
+
percentBuckets,
|
|
59
|
+
});
|
|
60
|
+
results.push({
|
|
61
|
+
pool,
|
|
62
|
+
meta: snap.meta,
|
|
63
|
+
usdPrices: { token0USD, token1USD, source0: src0, source1: src1 },
|
|
64
|
+
depth,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { timestamp: when, results };
|
|
68
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/prices.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function fetchPricesLlama(addresses: string[]): Promise<Record<string, number>>;
|
|
2
|
+
export declare function fetchPricesCoingecko(addresses: string[], apiKey?: string): Promise<Record<string, number>>;
|
|
3
|
+
export declare function fetchTokenUSDPrices(opts: {
|
|
4
|
+
addresses: string[];
|
|
5
|
+
source?: 'reserve' | 'dexscreener' | 'llama' | 'coingecko' | 'auto';
|
|
6
|
+
rpcUrl?: string;
|
|
7
|
+
chainId?: number;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
reserveLimit?: number;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
byAddress: Record<string, {
|
|
12
|
+
price: number;
|
|
13
|
+
source: string;
|
|
14
|
+
}>;
|
|
15
|
+
}>;
|