@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 ADDED
@@ -0,0 +1,207 @@
1
+ Liquidity Depth CLI
2
+
3
+ - Compute Uniswap v3 market depth bands (±1/2/5/10% by default) using live on-chain state or subgraph.
4
+ - Estimate price impact for given USD trade sizes.
5
+ - Batch over many pools from CSV.
6
+ - Simulate Steer vault withdrawals on a fork and re-run the analysis “after” to see slippage effects.
7
+
8
+ Requirements
9
+
10
+ - Node.js 18+ (uses global fetch and BigInt)
11
+ - For on-chain mode: an Ethereum RPC URL in `--rpc` (or env `RPC_URL`) capable of eth_getProof. For Base, prefer `https://developer-access-mainnet.base.org`.
12
+
13
+ Install
14
+
15
+ - Node module consumers (after publish):
16
+ - `npm install liquidity-meter`
17
+ - ESM-only: import from `liquidity-meter` in Node 18+
18
+ - Local (from this repo):
19
+ - `npm install`
20
+ - `npm run generate && npm run build`
21
+ - `npm link` (optional; installs `liquidity-depth` in your PATH)
22
+
23
+ ESM Only
24
+
25
+ - This package ships ESM only. Use Node 18+ and `import` syntax.
26
+ - Exports:
27
+ - `import { analyzePool, fetchPoolSnapshotViem, computeMarketDepthBands, simulateVaultWithdraw } from 'liquidity-meter'`
28
+ - CLI subpath: `node ./node_modules/liquidity-meter/dist/cli/liquidity-depth.js --help`
29
+
30
+ Quick Start
31
+
32
+ - Single pool (on-chain via Tevm fork):
33
+ - `liquidity-depth --pool 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --rpc https://mainnet.infura.io/v3/<KEY>`
34
+ - Same with JSON output:
35
+ - `liquidity-depth --pool 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --rpc <URL> --json`
36
+ - Subgraph fallback:
37
+ - `liquidity-depth --pool 0x... --source subgraph`
38
+
39
+ - Single pool with withdraw simulation (Tevm fork):
40
+ - `liquidity-depth --pool 0xPOOL --vault 0xVAULT --owner 0xOWNER --withdraw-pct 25 --rpc https://developer-access-mainnet.base.org --usd-sizes 1000,5000,10000`
41
+
42
+ Tevm (On-Chain Fork)
43
+
44
+ - Default source is `tevm`. The tool forks at `latest` from `--rpc` and reads the pool directly.
45
+ - We suppress EVM logs in reports and mine blocks deterministically for post-action state.
46
+ - If you see “distance to target block exceeds maximum proof window,” use a provider with proof support or a more recent block (we default to `latest`).
47
+
48
+ Batch via CSV
49
+
50
+ - Run the CLI over many pools and write a per-pool report:
51
+ - `liquidity-depth --csv "/path/to/file.csv" --outdir ./reports --rpc <URL>`
52
+ - Reliability flags for large batches / flaky providers:
53
+ - `--throttle-ms N` add delay between rows and retries
54
+ - `--retries N` retry transient JSON-RPC failures (e.g., `-32000`, proof window, `429`)
55
+ - CSV header expectations:
56
+ - Pool address: prefer one of `pool`, `address`, `pool_address`, `id` (addresses embedded in URLs are auto-detected).
57
+ - If no pool address is present, provide `token0`, `token1`, and `fee`, and with `--rpc` the tool will resolve the pool using the Uniswap v3 factory.
58
+ - Optional per-row overrides: `rpc`, `source`, `percent`, `token0_usd`, `token1_usd`, `usd_sizes`, `assume_stable`, `prices`.
59
+ - Steer vault simulation columns (optional):
60
+ - `vault` (aliases: `vault_address`, `vault address`, `strategy`)
61
+ - `owner` (aliases: `owner_address`, `owner address`, `withdraw_owner`, `account`, `user`, `recipient`, `to`)
62
+ - `withdraw_pct` (aliases: `withdraw percent`, `withdraw_percent`, `withdrawPercentage`, `withdrawPercent`, `pct_withdraw`, `percent_withdraw`)
63
+ - `withdraw_shares` (aliases: `withdraw shares`, `withdrawShares`, `shares_to_withdraw`, `withdraw_share_amount`)
64
+ - Output:
65
+ - Writes one file per pool in `--outdir` (default `reports`), named `<pool>.txt` or `<pool>.json` when `--json` is set.
66
+
67
+ CSV example
68
+
69
+ ```
70
+ pool,vault,owner,withdraw_pct,usd_sizes
71
+ 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8,0xYourVault,0xYourOwner,25,"1000,5000,10000"
72
+ ```
73
+
74
+ Simulating Steer Vault Withdrawals (Before/After)
75
+
76
+ - Adds an “after” section by impersonating an owner on a Tevm fork, withdrawing shares from the Steer vault, mining, then re-running the analysis.
77
+ - Global flags:
78
+ - `--vault <VAULT_ADDRESS>`: Vault to withdraw from (per-row CSV overrides supported).
79
+ - `--owner <ADDRESS>`: Address whose shares are withdrawn (or env `OWNER`).
80
+ - `--withdraw-pct <N>`: Percent of owner’s current share balance to withdraw (e.g., 25).
81
+ - `--withdraw-shares <RAW>`: Raw share amount (overrides percent).
82
+ - The withdraw is simulated on the fork only. We top up owner gas, impersonate the owner, call `withdraw(shares,0,0,owner)`, then mine 1 block.
83
+ - Example (CSV with a `vault` column):
84
+ - `OWNER=0xYourOwner liquidity-depth --csv "/path/file.csv" --outdir ./reports --rpc <URL> --withdraw-pct 25`
85
+
86
+ Debug logging for withdraws
87
+
88
+ - Pass `--debug` to print a concise audit trail to stderr without polluting report files:
89
+ - Planned shares and percent, owner’s current shares and vault total supply.
90
+ - Owner and vault token balances before/after (formatted with decimals + symbol).
91
+ - Owner deltas received for token0/token1.
92
+ - Transaction hash on submit, then `[after]` changes to tick and in-range liquidity.
93
+ - A pool mismatch notice if `vault.pool()` differs from CLI `--pool`.
94
+
95
+ Text Output (Summary)
96
+
97
+ - Shows pool, token symbols/addresses, tick, fee, inferred or fetched USD prices, range of examined ticks, cumulative USD depth per bucket, headline ±2% depth, and optional price impact lines for USD sizes.
98
+ - If a withdrawal is simulated, an “— After Withdraw —” section prints the same summary after state changes.
99
+
100
+ JSON Output
101
+
102
+ - Without simulation: prints a single object with fields:
103
+ - `pool`, `tokens.token0/1`, `ethPriceUSD`, `tick`, `sqrtPriceX96`, `liquidity`, `feePips`, `range`, `percentBuckets`, `prices`, `buyCumToken1[]`, `sellCumToken0[]`, and metrics from `computeMarketDepthBands` plus optional `priceImpacts`.
104
+ - With simulation: prints `{ before: {...}, after: {...} }` with the same structure on each side. BigInts are stringified in arrays.
105
+
106
+ Options (Reference)
107
+
108
+ - `--pool` / `-p`: Uniswap v3 pool address.
109
+ - `--percent` / `-b`: Comma-separated percent buckets (default: `1,2,5,10`).
110
+ - `--source`: `tevm|subgraph|auto` (default: `tevm`).
111
+ - `--rpc` / `-r`: RPC URL for Tevm fork (or env `RPC_URL`).
112
+ - `--subgraph` / `-s`: Subgraph URL (defaults to Uniswap v3 mainnet).
113
+ - `--token0-usd` / `--token1-usd`: Manual USD prices.
114
+ - `--assume-stable 0|1`: Assume token index is $1 stable when USD not supplied/inferred.
115
+ - `--usd-sizes`: Comma-separated USD sizes for price impact (token1 buys, token0 sells).
116
+ - `--prices`: `reserve|dexscreener|llama|coingecko|auto` (default: `auto`; env `PRICES_SOURCE`).
117
+ - `--reserve-limit`: Limit for Reserve API (default: `100`; env `RESERVE_LIMIT`).
118
+ - `--throttle-ms`: Delay between CSV rows & retries (ms).
119
+ - `--retries`: Retries per row on transient RPC errors (default: `0`).
120
+ - `--csv`: CSV file with pool/vault rows.
121
+ - `--outdir`: Output directory (default: `reports`).
122
+ - `--json` / `-j`: Emit machine-readable JSON.
123
+ - `--debug`: Print pricing/inference debug logs to stderr.
124
+ - `--vault`, `--owner`, `--withdraw-pct`, `--withdraw-shares`: Steer vault withdraw simulation (see above).
125
+
126
+ Pricing Sources
127
+
128
+ - If manual USD prices aren’t provided, the tool tries (in `auto` mode): Reserve (if chainId known), Dexscreener (if chainId known), DeFiLlama, Coingecko.
129
+ - ChainId is resolved via the provided RPC. Coingecko can be rate-limited without an API key.
130
+
131
+ RPC Guidance (Proof Window)
132
+
133
+ - Tevm forks and then reads state using proofs from your RPC. Some providers only serve proofs for a recent block window.
134
+ - If you see: “distance to target block exceeds maximum proof window,” switch to a proof-capable provider or ensure you fork at `latest` (default).
135
+ - Recommended for Base: `https://developer-access-mainnet.base.org`.
136
+
137
+ Troubleshooting
138
+
139
+ - Missing `--rpc` in tevm mode: supply `--rpc` or set `RPC_URL`.
140
+ - CSV row errors write a file named `error-<pool>.txt` in `--outdir`.
141
+ - If owner has 0 vault shares at the forked block, the withdraw step is skipped.
142
+ - If a vault does not expose `pool()` or withdraw reverts, the run fails for that row only.
143
+ - Logs: Reports strip noisy EVM logs; use `--debug` for pricing diagnostics.
144
+ - zsh/newlines: keep command arguments on a single line (e.g., do not split `--rpc` URL across lines).
145
+
146
+ Examples
147
+
148
+ - Single pool on-chain:
149
+ - `liquidity-depth --pool 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --rpc https://mainnet.infura.io/v3/<KEY> --usd-sizes 1000,5000,10000`
150
+ - Batch CSV:
151
+ - `liquidity-depth --csv "/path/to/file.csv" --outdir ./reports --rpc https://developer-access-mainnet.base.org --usd-sizes 1000,5000,10000,25000,50000,100000`
152
+ - With backoff and retries:
153
+ - `liquidity-depth --csv "/path/to/file.csv" --outdir ./reports --rpc https://developer-access-mainnet.base.org --usd-sizes 1000,5000,10000,25000,50000,100000 --throttle-ms 500 --retries 2`
154
+ - Batch with withdraw simulation for a single owner across all rows:
155
+ - `OWNER=0xYourOwner liquidity-depth --csv "/path/to/file.csv" --outdir ./reports --rpc https://developer-access-mainnet.base.org --withdraw-pct 25 --usd-sizes 1000,5000,10000`
156
+
157
+ Programmatic API
158
+
159
+ - One-call analysis (before/after) with price inference and optional withdraw simulation:
160
+
161
+ ```
162
+ import { analyzePool } from 'liquidity-meter'
163
+
164
+ const res = await analyzePool({
165
+ poolAddress: '0x11e26bbd1a5547895a50fc39a2d4c0025dec0bda',
166
+ source: 'tevm', // or 'subgraph' | 'auto'
167
+ rpcUrl: 'https://developer-access-mainnet.base.org',
168
+ percentBuckets: [1, 2, 5, 10],
169
+ usdSizes: [1000, 5000, 10000, 25000],
170
+ prices: 'auto', // reserve | dexscreener | llama | coingecko | auto
171
+ withdraw: { // optional; TEVM only
172
+ vault: '0xYourVault',
173
+ owner: '0xYourOwner',
174
+ withdrawPct: 25, // or: withdrawShares: '1230000000000000000'
175
+ },
176
+ })
177
+
178
+ console.log(res.before.metrics.depthPlus2USD)
179
+ console.log(res.after?.metrics.depthPlus2USD)
180
+ ```
181
+
182
+ - Lower-level building blocks (for custom flows):
183
+
184
+ ```
185
+ import {
186
+ fetchPoolSnapshotViem, // TEVM on-chain snapshot
187
+ fetchPoolSnapshot as fetchFromSubgraph,
188
+ computeMarketDepthBands,
189
+ computePriceImpactsBySizes,
190
+ fetchTokenUSDPrices,
191
+ simulateVaultWithdraw, // TEVM withdraw sim
192
+ } from 'liquidity-meter'
193
+ ```
194
+
195
+ Notes & Limitations
196
+
197
+ - No transactions are sent to mainnet; all simulations run on a local fork.
198
+ - Certain deposit transaction types (e.g., EIP-7702/OP Stack deposits) may be filtered in forked blocks; this does not affect pool state reads.
199
+ - The tool does not model MEV or off-range liquidity changes between snapshots.
200
+
201
+ Templates
202
+
203
+ - This repo includes ready-to-edit CSV templates under `templates/`:
204
+ - `templates/pools_minimal.csv`
205
+ - `templates/pools_with_vault.csv`
206
+ - Copy one and fill in your addresses, then run (example):
207
+ - `liquidity-depth --csv ./templates/pools_with_vault.csv --outdir ./reports --rpc https://developer-access-mainnet.base.org`
@@ -0,0 +1,51 @@
1
+ import { createMemoryClient } from 'tevm';
2
+ import { Tick } from '../core/depth.js';
3
+ export type PoolSnapshotOnchain = {
4
+ sqrtPriceX96: bigint;
5
+ tick: number;
6
+ liquidity: bigint;
7
+ feePips: number;
8
+ ticks: Tick[];
9
+ token0: {
10
+ decimals: number;
11
+ usdPrice: number;
12
+ id?: string;
13
+ symbol?: string;
14
+ };
15
+ token1: {
16
+ decimals: number;
17
+ usdPrice: number;
18
+ id?: string;
19
+ symbol?: string;
20
+ };
21
+ meta: {
22
+ token0: {
23
+ decimals: number;
24
+ id: string;
25
+ symbol?: string;
26
+ };
27
+ token1: {
28
+ decimals: number;
29
+ id: string;
30
+ symbol?: string;
31
+ };
32
+ poolId: string;
33
+ range: {
34
+ start: number;
35
+ end: number;
36
+ };
37
+ farPercent: number;
38
+ tickSpacing: number;
39
+ };
40
+ };
41
+ export declare function fetchPoolSnapshotViem(params: {
42
+ poolAddress: string;
43
+ rpcUrl?: string;
44
+ percentBuckets?: number[];
45
+ client?: ReturnType<typeof createMemoryClient>;
46
+ }): Promise<PoolSnapshotOnchain>;
47
+ export declare function fetchPoolSnapshotCore(params: {
48
+ poolAddress: string;
49
+ rpcUrl: string;
50
+ percentBuckets?: number[];
51
+ }): Promise<PoolSnapshotOnchain>;
@@ -0,0 +1,158 @@
1
+ // On-chain fetcher for Uniswap v3 pools (TypeScript)
2
+ import { createMemoryClient, http } from 'tevm';
3
+ import { getAddress } from 'viem';
4
+ import { readContract } from '@wagmi/core';
5
+ import { configFromRpc } from '../wagmi/config.js';
6
+ import { erc20Abi, uniswapV3PoolAbi } from '../wagmi/generated.js';
7
+ // ABIs sourced from wagmi CLI generated file
8
+ function percentToTickDelta(pct) {
9
+ const r = 1 + pct / 100;
10
+ const delta = Math.log(r) / Math.log(1.0001);
11
+ return Math.max(1, Math.round(delta));
12
+ }
13
+ function floorDiv(a, b) {
14
+ const q = Math.trunc(a / b);
15
+ const r = a % b;
16
+ return (r !== 0 && ((r > 0) !== (b > 0))) ? q - 1 : q;
17
+ }
18
+ export async function fetchPoolSnapshotViem(params) {
19
+ const { poolAddress, rpcUrl, percentBuckets = [1, 2, 5, 10], client } = params;
20
+ let c = client;
21
+ if (!c) {
22
+ if (!rpcUrl)
23
+ throw new Error('rpcUrl required when client is not provided');
24
+ c = createMemoryClient({ fork: { transport: http(rpcUrl), blockTag: 'latest' }, loggingLevel: 'error' });
25
+ if (c.tevmReady)
26
+ await c.tevmReady();
27
+ }
28
+ const pool = getAddress(poolAddress);
29
+ const [slot0Raw, LRaw, feeRaw, spacingRaw, token0Addr, token1Addr] = await Promise.all([
30
+ c.readContract({ address: pool, abi: uniswapV3PoolAbi, functionName: 'slot0' }),
31
+ c.readContract({ address: pool, abi: uniswapV3PoolAbi, functionName: 'liquidity' }),
32
+ c.readContract({ address: pool, abi: uniswapV3PoolAbi, functionName: 'fee' }),
33
+ c.readContract({ address: pool, abi: uniswapV3PoolAbi, functionName: 'tickSpacing' }),
34
+ c.readContract({ address: pool, abi: uniswapV3PoolAbi, functionName: 'token0' }),
35
+ c.readContract({ address: pool, abi: uniswapV3PoolAbi, functionName: 'token1' }),
36
+ ]);
37
+ const [dec0, sym0, dec1, sym1] = await Promise.all([
38
+ c.readContract({ address: token0Addr, abi: erc20Abi, functionName: 'decimals' }),
39
+ c.readContract({ address: token0Addr, abi: erc20Abi, functionName: 'symbol' }),
40
+ c.readContract({ address: token1Addr, abi: erc20Abi, functionName: 'decimals' }),
41
+ c.readContract({ address: token1Addr, abi: erc20Abi, functionName: 'symbol' }),
42
+ ]);
43
+ const sqrtPriceX96 = slot0Raw[0];
44
+ const tick = Number(slot0Raw[1]);
45
+ const liquidity = LRaw;
46
+ const feePips = Number(feeRaw);
47
+ const tickSpacing = Number(spacingRaw);
48
+ const far = Math.max(...percentBuckets);
49
+ const delta = percentToTickDelta(far);
50
+ const start = tick - delta;
51
+ const end = tick + delta;
52
+ const compStart = floorDiv(start, tickSpacing);
53
+ const compEnd = floorDiv(end, tickSpacing);
54
+ const wordStart = floorDiv(compStart, 256);
55
+ const wordEnd = floorDiv(compEnd, 256);
56
+ const ticks = [];
57
+ for (let w = wordStart; w <= wordEnd; w++) {
58
+ const bitmap = await c.readContract({ address: pool, abi: uniswapV3PoolAbi, functionName: 'tickBitmap', args: [BigInt(w)] });
59
+ if (bitmap === 0n)
60
+ continue;
61
+ for (let bit = 0; bit < 256; bit++) {
62
+ if (((bitmap >> BigInt(bit)) & 1n) === 0n)
63
+ continue;
64
+ const compressed = w * 256 + bit;
65
+ const t = compressed * tickSpacing;
66
+ if (t < start || t > end)
67
+ continue;
68
+ const ret = await c.readContract({ address: pool, abi: uniswapV3PoolAbi, functionName: 'ticks', args: [t] });
69
+ const liquidityNet = ret[1];
70
+ ticks.push({ index: t, liquidityNet });
71
+ }
72
+ }
73
+ return {
74
+ sqrtPriceX96,
75
+ tick,
76
+ liquidity,
77
+ feePips,
78
+ ticks: ticks.sort((a, b) => a.index - b.index),
79
+ token0: { decimals: Number(dec0), usdPrice: 0, symbol: sym0, id: token0Addr },
80
+ token1: { decimals: Number(dec1), usdPrice: 0, symbol: sym1, id: token1Addr },
81
+ meta: {
82
+ token0: { decimals: Number(dec0), symbol: sym0, id: token0Addr },
83
+ token1: { decimals: Number(dec1), symbol: sym1, id: token1Addr },
84
+ poolId: pool,
85
+ range: { start, end },
86
+ farPercent: far,
87
+ tickSpacing,
88
+ },
89
+ };
90
+ }
91
+ // Fetch using wagmi/core actions (no TEVM), talking directly to RPC
92
+ export async function fetchPoolSnapshotCore(params) {
93
+ const { poolAddress, rpcUrl, percentBuckets = [1, 2, 5, 10] } = params;
94
+ const cfg = await configFromRpc(rpcUrl);
95
+ const chainId = cfg.chains[0].id;
96
+ const pool = getAddress(poolAddress);
97
+ const [slot0Raw, LRaw, feeRaw, spacingRaw, token0Addr, token1Addr] = await Promise.all([
98
+ readContract(cfg, { address: pool, abi: uniswapV3PoolAbi, functionName: 'slot0', chainId }),
99
+ readContract(cfg, { address: pool, abi: uniswapV3PoolAbi, functionName: 'liquidity', chainId }),
100
+ readContract(cfg, { address: pool, abi: uniswapV3PoolAbi, functionName: 'fee', chainId }),
101
+ readContract(cfg, { address: pool, abi: uniswapV3PoolAbi, functionName: 'tickSpacing', chainId }),
102
+ readContract(cfg, { address: pool, abi: uniswapV3PoolAbi, functionName: 'token0', chainId }),
103
+ readContract(cfg, { address: pool, abi: uniswapV3PoolAbi, functionName: 'token1', chainId }),
104
+ ]);
105
+ const [dec0, sym0, dec1, sym1] = await Promise.all([
106
+ readContract(cfg, { address: token0Addr, abi: erc20Abi, functionName: 'decimals', chainId }),
107
+ readContract(cfg, { address: token0Addr, abi: erc20Abi, functionName: 'symbol', chainId }),
108
+ readContract(cfg, { address: token1Addr, abi: erc20Abi, functionName: 'decimals', chainId }),
109
+ readContract(cfg, { address: token1Addr, abi: erc20Abi, functionName: 'symbol', chainId }),
110
+ ]);
111
+ const sqrtPriceX96 = slot0Raw[0];
112
+ const tick = Number(slot0Raw[1]);
113
+ const liquidity = LRaw;
114
+ const feePips = Number(feeRaw);
115
+ const tickSpacing = Number(spacingRaw);
116
+ const far = Math.max(...percentBuckets);
117
+ const delta = percentToTickDelta(far);
118
+ const start = tick - delta;
119
+ const end = tick + delta;
120
+ const compStart = floorDiv(start, tickSpacing);
121
+ const compEnd = floorDiv(end, tickSpacing);
122
+ const wordStart = floorDiv(compStart, 256);
123
+ const wordEnd = floorDiv(compEnd, 256);
124
+ const ticks = [];
125
+ for (let w = wordStart; w <= wordEnd; w++) {
126
+ const bitmap = (await readContract(cfg, { address: pool, abi: uniswapV3PoolAbi, functionName: 'tickBitmap', args: [BigInt(w)], chainId }));
127
+ if (bitmap === 0n)
128
+ continue;
129
+ for (let bit = 0; bit < 256; bit++) {
130
+ if (((bitmap >> BigInt(bit)) & 1n) === 0n)
131
+ continue;
132
+ const compressed = w * 256 + bit;
133
+ const t = compressed * tickSpacing;
134
+ if (t < start || t > end)
135
+ continue;
136
+ const ret = (await readContract(cfg, { address: pool, abi: uniswapV3PoolAbi, functionName: 'ticks', args: [t], chainId }));
137
+ const liquidityNet = ret[1];
138
+ ticks.push({ index: t, liquidityNet });
139
+ }
140
+ }
141
+ return {
142
+ sqrtPriceX96,
143
+ tick,
144
+ liquidity,
145
+ feePips,
146
+ ticks: ticks.sort((a, b) => a.index - b.index),
147
+ token0: { decimals: Number(dec0), usdPrice: 0, symbol: sym0, id: token0Addr },
148
+ token1: { decimals: Number(dec1), usdPrice: 0, symbol: sym1, id: token1Addr },
149
+ meta: {
150
+ token0: { decimals: Number(dec0), symbol: sym0, id: token0Addr },
151
+ token1: { decimals: Number(dec1), symbol: sym1, id: token1Addr },
152
+ poolId: pool,
153
+ range: { start, end },
154
+ farPercent: far,
155
+ tickSpacing,
156
+ },
157
+ };
158
+ }
@@ -0,0 +1,44 @@
1
+ import { Tick } from '../core/depth.js';
2
+ export type PoolSnapshot = {
3
+ sqrtPriceX96: bigint;
4
+ tick: number;
5
+ liquidity: bigint;
6
+ feePips: number;
7
+ ticks: Tick[];
8
+ token0: {
9
+ decimals: number;
10
+ usdPrice: number;
11
+ id?: string;
12
+ symbol?: string;
13
+ };
14
+ token1: {
15
+ decimals: number;
16
+ usdPrice: number;
17
+ id?: string;
18
+ symbol?: string;
19
+ };
20
+ meta: {
21
+ token0?: {
22
+ decimals: number;
23
+ id: string;
24
+ symbol?: string;
25
+ };
26
+ token1?: {
27
+ decimals: number;
28
+ id: string;
29
+ symbol?: string;
30
+ };
31
+ ethPriceUSD?: number;
32
+ poolId: string;
33
+ range: {
34
+ start: number;
35
+ end: number;
36
+ };
37
+ farPercent: number;
38
+ };
39
+ };
40
+ export declare function fetchPoolSnapshot(params: {
41
+ poolAddress: string;
42
+ percentBuckets?: number[];
43
+ subgraphUrl?: string;
44
+ }): Promise<PoolSnapshot>;
@@ -0,0 +1,105 @@
1
+ // Uniswap v3 subgraph client (TypeScript)
2
+ import axios from 'axios';
3
+ import { DepthInternals } from '../core/depth.js';
4
+ const DEFAULT_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3';
5
+ function percentToTickDelta(pct) {
6
+ const r = 1 + pct / 100;
7
+ const delta = Math.log(r) / Math.log(1.0001);
8
+ return Math.max(1, Math.round(delta));
9
+ }
10
+ async function graphqlFetch(url, query, variables) {
11
+ const res = await axios.post(url, { query, variables }, {
12
+ headers: { 'content-type': 'application/json' },
13
+ maxRedirects: 5,
14
+ timeout: 15000,
15
+ validateStatus: (s) => s >= 200 && s < 300,
16
+ });
17
+ const body = res.data;
18
+ if (body.errors && body.errors.length) {
19
+ const msg = body.errors.map((e) => e.message).join('; ');
20
+ throw new Error(`Subgraph error: ${msg}`);
21
+ }
22
+ return body.data;
23
+ }
24
+ export async function fetchPoolSnapshot(params) {
25
+ const { poolAddress, percentBuckets = [1, 2, 5, 10], subgraphUrl = DEFAULT_SUBGRAPH } = params;
26
+ const id = poolAddress.toLowerCase();
27
+ const coreQuery = `
28
+ query PoolCore($id: ID!) {
29
+ bundle(id: "1") { ethPriceUSD }
30
+ pool(id: $id) {
31
+ id
32
+ sqrtPrice
33
+ tick
34
+ liquidity
35
+ feeTier
36
+ token0 { id symbol decimals derivedETH }
37
+ token1 { id symbol decimals derivedETH }
38
+ }
39
+ }
40
+ `;
41
+ const core = await graphqlFetch(subgraphUrl, coreQuery, { id });
42
+ if (!core.pool)
43
+ throw new Error(`Pool not found in subgraph: ${id}`);
44
+ const ethPriceUSD = Number(core.bundle?.ethPriceUSD ?? 0);
45
+ const pool = core.pool;
46
+ const sqrtPriceX96 = BigInt(pool.sqrtPrice);
47
+ const tick = Number(pool.tick);
48
+ const liquidity = BigInt(pool.liquidity);
49
+ const feePips = Number(pool.feeTier);
50
+ const token0 = {
51
+ decimals: Number(pool.token0.decimals),
52
+ usdPrice: Number(pool.token0.derivedETH) * ethPriceUSD,
53
+ symbol: pool.token0.symbol,
54
+ id: pool.token0.id,
55
+ };
56
+ const token1 = {
57
+ decimals: Number(pool.token1.decimals),
58
+ usdPrice: Number(pool.token1.derivedETH) * ethPriceUSD,
59
+ symbol: pool.token1.symbol,
60
+ id: pool.token1.id,
61
+ };
62
+ const far = Math.max(...percentBuckets);
63
+ const delta = percentToTickDelta(far);
64
+ const start = Math.max(DepthInternals.MIN_TICK, tick - delta);
65
+ const end = Math.min(DepthInternals.MAX_TICK, tick + delta);
66
+ const ticks = [];
67
+ const pageSize = 1000;
68
+ let skip = 0;
69
+ while (true) {
70
+ const ticksQuery = `
71
+ query PoolTicks($id: ID!, $start: Int!, $end: Int!, $first: Int!, $skip: Int!) {
72
+ pool(id: $id) {
73
+ ticks(where: { tickIdx_gte: $start, tickIdx_lte: $end }, orderBy: tickIdx, orderDirection: asc, first: $first, skip: $skip) {
74
+ tickIdx
75
+ liquidityNet
76
+ }
77
+ }
78
+ }
79
+ `;
80
+ const data = await graphqlFetch(subgraphUrl, ticksQuery, { id, start, end, first: pageSize, skip });
81
+ const page = data.pool?.ticks ?? [];
82
+ for (const t of page)
83
+ ticks.push({ index: Number(t.tickIdx), liquidityNet: BigInt(t.liquidityNet) });
84
+ if (page.length < pageSize)
85
+ break;
86
+ skip += pageSize;
87
+ }
88
+ return {
89
+ sqrtPriceX96,
90
+ tick,
91
+ liquidity,
92
+ feePips,
93
+ ticks,
94
+ token0: { decimals: token0.decimals, usdPrice: token0.usdPrice, id: token0.id, symbol: token0.symbol },
95
+ token1: { decimals: token1.decimals, usdPrice: token1.usdPrice, id: token1.id, symbol: token1.symbol },
96
+ meta: {
97
+ token0: { decimals: token0.decimals, id: token0.id, symbol: token0.symbol },
98
+ token1: { decimals: token1.decimals, id: token1.id, symbol: token1.symbol },
99
+ ethPriceUSD,
100
+ poolId: id,
101
+ range: { start, end },
102
+ farPercent: far,
103
+ },
104
+ };
105
+ }
@@ -0,0 +1,14 @@
1
+ import { createMemoryClient } from 'tevm';
2
+ export type WithdrawParams = {
3
+ client: ReturnType<typeof createMemoryClient>;
4
+ vault: string;
5
+ owner: string;
6
+ withdrawPct?: number;
7
+ withdrawShares?: string | number | bigint;
8
+ debug?: boolean;
9
+ cliPool?: string;
10
+ viemChain?: any;
11
+ };
12
+ export declare function simulateVaultWithdraw(params: WithdrawParams): Promise<boolean>;
13
+ export declare function resolveViemChainFromClient(client: any): Promise<any>;
14
+ export declare function resolveTevmCommon(rpcUrl?: string): Promise<any>;