@steerprotocol/liquidity-meter 1.0.0 → 2.0.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/README.md +27 -56
- package/bin/liquidity-depth.js +795 -0
- package/package.json +42 -32
- package/src/api.js +568 -0
- package/src/depth.js +492 -0
- package/src/index.js +6 -0
- package/src/prices.js +188 -0
- package/src/uniswapv3-subgraph.js +141 -0
- package/src/viem-onchain.js +506 -0
- package/src/withdraw.js +333 -0
- package/templates/README.md +37 -0
- package/templates/pools_minimal.csv +3 -0
- package/templates/pools_with_vault.csv +3 -0
- package/dist/adapters/onchain.d.ts +0 -51
- package/dist/adapters/onchain.js +0 -158
- package/dist/adapters/uniswapv3-subgraph.d.ts +0 -44
- package/dist/adapters/uniswapv3-subgraph.js +0 -105
- package/dist/adapters/withdraw.d.ts +0 -14
- package/dist/adapters/withdraw.js +0 -150
- package/dist/api.d.ts +0 -72
- package/dist/api.js +0 -180
- package/dist/cli/liquidity-depth.d.ts +0 -2
- package/dist/cli/liquidity-depth.js +0 -1160
- package/dist/core/depth.d.ts +0 -48
- package/dist/core/depth.js +0 -314
- package/dist/handlers/cron.d.ts +0 -27
- package/dist/handlers/cron.js +0 -68
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -7
- package/dist/prices.d.ts +0 -15
- package/dist/prices.js +0 -205
- package/dist/wagmi/config.d.ts +0 -2106
- package/dist/wagmi/config.js +0 -24
- package/dist/wagmi/generated.d.ts +0 -2019
- package/dist/wagmi/generated.js +0 -346
package/src/withdraw.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPublicClient,
|
|
3
|
+
encodeFunctionData,
|
|
4
|
+
getAddress,
|
|
5
|
+
http as viemHttp,
|
|
6
|
+
parseAbi,
|
|
7
|
+
parseEther,
|
|
8
|
+
} from 'viem';
|
|
9
|
+
import { sendTransaction as viemSendTransaction } from 'viem/actions';
|
|
10
|
+
|
|
11
|
+
const ERC20_ABI = parseAbi([
|
|
12
|
+
'function balanceOf(address) view returns (uint256)',
|
|
13
|
+
'function decimals() view returns (uint8)',
|
|
14
|
+
'function symbol() view returns (string)',
|
|
15
|
+
'function totalSupply() view returns (uint256)',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const VAULT_ABI = parseAbi([
|
|
19
|
+
'function pool() view returns (address)',
|
|
20
|
+
'function token0() view returns (address)',
|
|
21
|
+
'function token1() view returns (address)',
|
|
22
|
+
'function decimals() view returns (uint8)',
|
|
23
|
+
'function withdraw(uint256 shares,uint256 amount0Min,uint256 amount1Min,address to) returns (uint256 amount0,uint256 amount1)',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function formatUnits(value, decimals) {
|
|
27
|
+
try {
|
|
28
|
+
let normalized = BigInt(value);
|
|
29
|
+
const negative = normalized < 0n;
|
|
30
|
+
if (negative) normalized = -normalized;
|
|
31
|
+
const stringified = normalized
|
|
32
|
+
.toString()
|
|
33
|
+
.padStart(Number(decimals) + 1, '0');
|
|
34
|
+
const integer = stringified.slice(0, stringified.length - Number(decimals));
|
|
35
|
+
const fraction = stringified
|
|
36
|
+
.slice(stringified.length - Number(decimals))
|
|
37
|
+
.replace(/0+$/, '')
|
|
38
|
+
.slice(0, 8);
|
|
39
|
+
return `${negative ? '-' : ''}${integer}${fraction ? `.${fraction}` : ''}`;
|
|
40
|
+
} catch (_) {
|
|
41
|
+
return String(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function formatToken(value, decimals, symbol) {
|
|
46
|
+
return `${formatUnits(value, decimals)} ${symbol || ''}`.trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function createForkClient({ rpcUrl, blockTag = 'latest' }) {
|
|
50
|
+
const { createMemoryClient, http } = await import('tevm');
|
|
51
|
+
const client = createMemoryClient({
|
|
52
|
+
fork: { transport: http(rpcUrl), blockTag },
|
|
53
|
+
loggingLevel: 'error',
|
|
54
|
+
});
|
|
55
|
+
if (client.tevmReady) await client.tevmReady();
|
|
56
|
+
return client;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function resolveTevmCommon(rpcUrl) {
|
|
60
|
+
try {
|
|
61
|
+
const tevmCommon = await import('tevm/common');
|
|
62
|
+
const client = createPublicClient({ transport: viemHttp(rpcUrl) });
|
|
63
|
+
const chainId = await client.getChainId();
|
|
64
|
+
for (const value of Object.values(tevmCommon)) {
|
|
65
|
+
if (value && typeof value === 'object' && 'id' in value && value.id === chainId) {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (_) {}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function resolveViemChain(rpcUrl) {
|
|
74
|
+
try {
|
|
75
|
+
const viemChains = await import('viem/chains');
|
|
76
|
+
const client = createPublicClient({ transport: viemHttp(rpcUrl) });
|
|
77
|
+
const chainId = await client.getChainId();
|
|
78
|
+
for (const value of Object.values(viemChains)) {
|
|
79
|
+
if (value && typeof value === 'object' && 'id' in value && value.id === chainId) {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch (_) {}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function resolveViemChainFromClient(client) {
|
|
88
|
+
try {
|
|
89
|
+
const viemChains = await import('viem/chains');
|
|
90
|
+
const chainId = await client.getChainId();
|
|
91
|
+
for (const value of Object.values(viemChains)) {
|
|
92
|
+
if (value && typeof value === 'object' && 'id' in value && value.id === chainId) {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (_) {}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function simulateVaultWithdraw({
|
|
101
|
+
client,
|
|
102
|
+
vault,
|
|
103
|
+
owner,
|
|
104
|
+
withdrawPct,
|
|
105
|
+
withdrawShares,
|
|
106
|
+
debug = false,
|
|
107
|
+
cliPool,
|
|
108
|
+
viemChain,
|
|
109
|
+
sendTransaction = viemSendTransaction,
|
|
110
|
+
}) {
|
|
111
|
+
if (!client) throw new Error('simulateVaultWithdraw requires tevm client');
|
|
112
|
+
if (!vault) throw new Error('Missing --vault');
|
|
113
|
+
if (!owner) throw new Error('Missing --owner for withdraw simulation');
|
|
114
|
+
|
|
115
|
+
const vaultAddress = getAddress(vault);
|
|
116
|
+
const ownerAddress = getAddress(owner);
|
|
117
|
+
|
|
118
|
+
let poolAddress;
|
|
119
|
+
try {
|
|
120
|
+
poolAddress = await client.readContract({
|
|
121
|
+
address: vaultAddress,
|
|
122
|
+
abi: VAULT_ABI,
|
|
123
|
+
functionName: 'pool',
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
throw new Error(`Failed to read vault.pool(): ${error?.message || error}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let vaultDecimals = 18;
|
|
130
|
+
try {
|
|
131
|
+
vaultDecimals = Number(
|
|
132
|
+
await client.readContract({
|
|
133
|
+
address: vaultAddress,
|
|
134
|
+
abi: VAULT_ABI,
|
|
135
|
+
functionName: 'decimals',
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
} catch (_) {}
|
|
139
|
+
|
|
140
|
+
const token0Address = await client.readContract({
|
|
141
|
+
address: vaultAddress,
|
|
142
|
+
abi: VAULT_ABI,
|
|
143
|
+
functionName: 'token0',
|
|
144
|
+
});
|
|
145
|
+
const token1Address = await client.readContract({
|
|
146
|
+
address: vaultAddress,
|
|
147
|
+
abi: VAULT_ABI,
|
|
148
|
+
functionName: 'token1',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const [token0Decimals, token1Decimals] = await Promise.all([
|
|
152
|
+
client.readContract({
|
|
153
|
+
address: token0Address,
|
|
154
|
+
abi: ERC20_ABI,
|
|
155
|
+
functionName: 'decimals',
|
|
156
|
+
}),
|
|
157
|
+
client.readContract({
|
|
158
|
+
address: token1Address,
|
|
159
|
+
abi: ERC20_ABI,
|
|
160
|
+
functionName: 'decimals',
|
|
161
|
+
}),
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
let [token0Symbol, token1Symbol] = ['T0', 'T1'];
|
|
165
|
+
try {
|
|
166
|
+
token0Symbol = await client.readContract({
|
|
167
|
+
address: token0Address,
|
|
168
|
+
abi: ERC20_ABI,
|
|
169
|
+
functionName: 'symbol',
|
|
170
|
+
});
|
|
171
|
+
} catch (_) {}
|
|
172
|
+
try {
|
|
173
|
+
token1Symbol = await client.readContract({
|
|
174
|
+
address: token1Address,
|
|
175
|
+
abi: ERC20_ABI,
|
|
176
|
+
functionName: 'symbol',
|
|
177
|
+
});
|
|
178
|
+
} catch (_) {}
|
|
179
|
+
|
|
180
|
+
let shares = null;
|
|
181
|
+
if (withdrawShares != null) {
|
|
182
|
+
try {
|
|
183
|
+
shares = BigInt(String(withdrawShares));
|
|
184
|
+
} catch (_) {
|
|
185
|
+
throw new Error('--withdraw-shares must be a BigInt-compatible value');
|
|
186
|
+
}
|
|
187
|
+
} else if (Number.isFinite(withdrawPct)) {
|
|
188
|
+
const balance = await client.readContract({
|
|
189
|
+
address: vaultAddress,
|
|
190
|
+
abi: ERC20_ABI,
|
|
191
|
+
functionName: 'balanceOf',
|
|
192
|
+
args: [ownerAddress],
|
|
193
|
+
});
|
|
194
|
+
const pct = BigInt(Math.max(0, Math.min(100, Math.floor(Number(withdrawPct)))));
|
|
195
|
+
shares = (BigInt(balance) * pct) / 100n;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const ownerShares = await client.readContract({
|
|
199
|
+
address: vaultAddress,
|
|
200
|
+
abi: ERC20_ABI,
|
|
201
|
+
functionName: 'balanceOf',
|
|
202
|
+
args: [ownerAddress],
|
|
203
|
+
});
|
|
204
|
+
const totalShares = await client.readContract({
|
|
205
|
+
address: vaultAddress,
|
|
206
|
+
abi: ERC20_ABI,
|
|
207
|
+
functionName: 'totalSupply',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (debug) {
|
|
211
|
+
const poolWarning =
|
|
212
|
+
cliPool && cliPool.toLowerCase() !== poolAddress.toLowerCase()
|
|
213
|
+
? ` (CLI pool=${cliPool})`
|
|
214
|
+
: '';
|
|
215
|
+
console.error(
|
|
216
|
+
`[withdraw] vault=${vaultAddress} pool=${poolAddress}${poolWarning}`
|
|
217
|
+
);
|
|
218
|
+
console.error(
|
|
219
|
+
`[withdraw] owner=${ownerAddress} ownerShares=${formatToken(ownerShares, vaultDecimals, 'shares')} totalSupply=${formatToken(totalShares, vaultDecimals, 'shares')}`
|
|
220
|
+
);
|
|
221
|
+
console.error(
|
|
222
|
+
`[withdraw] planned shares=${formatToken(shares ?? 0n, vaultDecimals, 'shares')} (${Number(withdrawPct ?? 0)}%)`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!shares || shares <= 0n) {
|
|
227
|
+
if (debug) console.error('[withdraw] no shares to withdraw; skipping');
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
await client.setBalance({ address: ownerAddress, value: parseEther('0.05') });
|
|
233
|
+
} catch (_) {}
|
|
234
|
+
try {
|
|
235
|
+
await client.impersonateAccount({ address: ownerAddress });
|
|
236
|
+
} catch (_) {}
|
|
237
|
+
|
|
238
|
+
const [owner0Before, owner1Before, vault0Before, vault1Before] = await Promise.all([
|
|
239
|
+
client.readContract({
|
|
240
|
+
address: token0Address,
|
|
241
|
+
abi: ERC20_ABI,
|
|
242
|
+
functionName: 'balanceOf',
|
|
243
|
+
args: [ownerAddress],
|
|
244
|
+
}),
|
|
245
|
+
client.readContract({
|
|
246
|
+
address: token1Address,
|
|
247
|
+
abi: ERC20_ABI,
|
|
248
|
+
functionName: 'balanceOf',
|
|
249
|
+
args: [ownerAddress],
|
|
250
|
+
}),
|
|
251
|
+
client.readContract({
|
|
252
|
+
address: token0Address,
|
|
253
|
+
abi: ERC20_ABI,
|
|
254
|
+
functionName: 'balanceOf',
|
|
255
|
+
args: [vaultAddress],
|
|
256
|
+
}),
|
|
257
|
+
client.readContract({
|
|
258
|
+
address: token1Address,
|
|
259
|
+
abi: ERC20_ABI,
|
|
260
|
+
functionName: 'balanceOf',
|
|
261
|
+
args: [vaultAddress],
|
|
262
|
+
}),
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
if (debug) {
|
|
266
|
+
console.error(
|
|
267
|
+
`[withdraw] owner balances before: ${formatToken(owner0Before, token0Decimals, token0Symbol)}, ${formatToken(owner1Before, token1Decimals, token1Symbol)}`
|
|
268
|
+
);
|
|
269
|
+
console.error(
|
|
270
|
+
`[withdraw] vault balances before: ${formatToken(vault0Before, token0Decimals, token0Symbol)}, ${formatToken(vault1Before, token1Decimals, token1Symbol)}`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const data = encodeWithdrawData(shares, ownerAddress);
|
|
275
|
+
const chain = viemChain || (await resolveViemChainFromClient(client));
|
|
276
|
+
const hash = await sendTransaction(client, {
|
|
277
|
+
account: ownerAddress,
|
|
278
|
+
to: vaultAddress,
|
|
279
|
+
data,
|
|
280
|
+
chain,
|
|
281
|
+
});
|
|
282
|
+
if (debug) console.error(`[withdraw] tx sent: ${hash}`);
|
|
283
|
+
await client.mine({ blocks: 1 });
|
|
284
|
+
|
|
285
|
+
const [owner0After, owner1After, vault0After, vault1After] = await Promise.all([
|
|
286
|
+
client.readContract({
|
|
287
|
+
address: token0Address,
|
|
288
|
+
abi: ERC20_ABI,
|
|
289
|
+
functionName: 'balanceOf',
|
|
290
|
+
args: [ownerAddress],
|
|
291
|
+
}),
|
|
292
|
+
client.readContract({
|
|
293
|
+
address: token1Address,
|
|
294
|
+
abi: ERC20_ABI,
|
|
295
|
+
functionName: 'balanceOf',
|
|
296
|
+
args: [ownerAddress],
|
|
297
|
+
}),
|
|
298
|
+
client.readContract({
|
|
299
|
+
address: token0Address,
|
|
300
|
+
abi: ERC20_ABI,
|
|
301
|
+
functionName: 'balanceOf',
|
|
302
|
+
args: [vaultAddress],
|
|
303
|
+
}),
|
|
304
|
+
client.readContract({
|
|
305
|
+
address: token1Address,
|
|
306
|
+
abi: ERC20_ABI,
|
|
307
|
+
functionName: 'balanceOf',
|
|
308
|
+
args: [vaultAddress],
|
|
309
|
+
}),
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
if (debug) {
|
|
313
|
+
console.error(
|
|
314
|
+
`[withdraw] owner balances after : ${formatToken(owner0After, token0Decimals, token0Symbol)}, ${formatToken(owner1After, token1Decimals, token1Symbol)}`
|
|
315
|
+
);
|
|
316
|
+
console.error(
|
|
317
|
+
`[withdraw] vault balances after : ${formatToken(vault0After, token0Decimals, token0Symbol)}, ${formatToken(vault1After, token1Decimals, token1Symbol)}`
|
|
318
|
+
);
|
|
319
|
+
console.error(
|
|
320
|
+
`[withdraw] owner deltas: ${formatToken(BigInt(owner0After) - BigInt(owner0Before), token0Decimals, token0Symbol)}, ${formatToken(BigInt(owner1After) - BigInt(owner1Before), token1Decimals, token1Symbol)}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function encodeWithdrawData(shares, ownerAddress) {
|
|
328
|
+
return encodeFunctionData({
|
|
329
|
+
abi: VAULT_ABI,
|
|
330
|
+
functionName: 'withdraw',
|
|
331
|
+
args: [shares, 0n, 0n, ownerAddress],
|
|
332
|
+
});
|
|
333
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Templates
|
|
2
|
+
|
|
3
|
+
This folder contains ready-to-edit templates to help you get started quickly.
|
|
4
|
+
|
|
5
|
+
Pick one of the CSVs below, copy it somewhere (or edit in place), fill in your addresses and options, then run the CLI with `--csv`.
|
|
6
|
+
|
|
7
|
+
Files
|
|
8
|
+
|
|
9
|
+
- pools_minimal.csv
|
|
10
|
+
- Minimal columns for running depth analysis per pool.
|
|
11
|
+
- Columns:
|
|
12
|
+
- pool: Uniswap v3 pool address (required)
|
|
13
|
+
- usd_sizes: Optional comma-separated USD sizes for price impact
|
|
14
|
+
|
|
15
|
+
- pools_with_vault.csv
|
|
16
|
+
- Includes Steer vault withdraw simulation columns.
|
|
17
|
+
- Columns:
|
|
18
|
+
- pool: Uniswap v3 pool address (required)
|
|
19
|
+
- vault: Steer vault address (required for withdraw simulation)
|
|
20
|
+
- owner: Address whose shares will be withdrawn (required for withdraw simulation)
|
|
21
|
+
- withdraw_pct: Percent of owner shares to withdraw (e.g., 25). Use `withdraw_shares` if you prefer exact shares.
|
|
22
|
+
- usd_sizes: Optional comma-separated USD sizes for price impact
|
|
23
|
+
|
|
24
|
+
Example commands
|
|
25
|
+
|
|
26
|
+
- Minimal (on-chain via Tevm fork):
|
|
27
|
+
- liquidity-depth --csv ./templates/pools_minimal.csv --outdir ./reports --rpc https://developer-access-mainnet.base.org
|
|
28
|
+
|
|
29
|
+
- With withdraw simulation (per-row):
|
|
30
|
+
- liquidity-depth --csv ./templates/pools_with_vault.csv --outdir ./reports --rpc https://developer-access-mainnet.base.org
|
|
31
|
+
|
|
32
|
+
Notes
|
|
33
|
+
|
|
34
|
+
- You can also set `--owner` and `--withdraw-pct` as CLI flags to apply globally and omit them from the CSV.
|
|
35
|
+
- Prices source defaults to `auto`. You can override per-row with a `prices` column or globally with `--prices`.
|
|
36
|
+
- If any row resolves invalid or missing addresses, that row will be skipped with a warning.
|
|
37
|
+
|
|
@@ -1,51 +0,0 @@
|
|
|
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>;
|
package/dist/adapters/onchain.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
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>;
|