@logswap/sdk 0.5.75 → 0.5.76
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 +32 -0
- package/dist/fees.d.ts +1 -1
- package/dist/fees.js +31 -7
- package/dist/fpool.d.ts +1 -1
- package/dist/fpool.js +16 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/positions.js +17 -2
- package/dist/views.d.ts +285 -0
- package/dist/views.js +188 -0
- package/package.json +1 -1
- package/src/fees.ts +29 -8
- package/src/fpool.ts +15 -2
- package/src/index.ts +1 -0
- package/src/positions.ts +19 -3
- package/src/views.ts +485 -0
package/src/views.ts
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* views.ts — the indexer's derived views (indexing phase 2, logswap-be `devnet/VIEWS.md`).
|
|
3
|
+
*
|
|
4
|
+
* The indexer records events and the snapshotter samples state; these views join the two in
|
|
5
|
+
* Postgres: holders, a wallet's positions and flows, per-swap volume and fee, price points and
|
|
6
|
+
* candles, TVL by the hour, the pools table's 24 h / 7 d figures, the generation steps and pad
|
|
7
|
+
* status, protocol revenue by day, and who did each routed action. Each reader returns typed rows:
|
|
8
|
+
*
|
|
9
|
+
* - **amounts** (Postgres `numeric`, GraphQL BigFloat strings) as `bigint`. The views floor every
|
|
10
|
+
* amount they compute, so the integer part is the value;
|
|
11
|
+
* - **prices, APYs, progress** (`double precision`) as `number`. `priceRaw` is quote wei per base
|
|
12
|
+
* wei: scale by 10^(baseDecimals − quoteDecimals) to display;
|
|
13
|
+
* - **times** as unix seconds (`number`), **addresses** checksummed, pool ids lowercase `0x…`.
|
|
14
|
+
*
|
|
15
|
+
* A view is a cache like any indexed table: the readers throw when the indexer cannot answer, and
|
|
16
|
+
* the SDK functions that use them ({@link holdingsOf}, {@link swapFees}, {@link fPoolShareHolders})
|
|
17
|
+
* keep their node path as the fallback.
|
|
18
|
+
*/
|
|
19
|
+
import { getAddress, type Address, type Hex } from "viem";
|
|
20
|
+
import { gql } from "./indexer.js";
|
|
21
|
+
|
|
22
|
+
const PAGE = 1000; // postgraphile's cap
|
|
23
|
+
const MAX_PAGES = 200;
|
|
24
|
+
const PARALLEL_PAGES = 8;
|
|
25
|
+
|
|
26
|
+
/** What a view query takes: postgraphile's `condition` (equality), `filter` (the connection-filter plugin's operators), an `orderBy` of the view's enum names, a row cap. */
|
|
27
|
+
export interface ViewQuery {
|
|
28
|
+
condition?: Record<string, unknown>;
|
|
29
|
+
filter?: Record<string, unknown>;
|
|
30
|
+
/** e.g. `["BLOCK_NUMBER_ASC"]`, `["VOLUME_24H_DESC"]`: the column's CONSTANT_CASE name and a direction. */
|
|
31
|
+
orderBy?: string[];
|
|
32
|
+
/** At most this many rows (one page up to 1000). All rows when omitted. */
|
|
33
|
+
first?: number;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Row = Record<string, unknown>;
|
|
38
|
+
|
|
39
|
+
/** Every row of a view, paged by offset (a view has no primary key to page a cursor on). */
|
|
40
|
+
async function viewRows(url: string, field: string, type: string, selection: string, q: ViewQuery): Promise<Row[]> {
|
|
41
|
+
for (const o of q.orderBy ?? []) if (!/^[A-Z0-9_]+$/.test(o)) throw new Error(`views: orderBy ${JSON.stringify(o)} is not an enum name`);
|
|
42
|
+
const hasCondition = q.condition !== undefined && Object.keys(q.condition).length > 0;
|
|
43
|
+
const hasFilter = q.filter !== undefined && Object.keys(q.filter).length > 0;
|
|
44
|
+
const pageSize = Math.min(q.first ?? PAGE, PAGE);
|
|
45
|
+
// postgraphile refuses a null or empty condition/filter: each appears only when it has content
|
|
46
|
+
const decl = `$offset: Int${hasCondition ? `, $condition: ${type}Condition` : ""}${hasFilter ? `, $filter: ${type}Filter` : ""}`;
|
|
47
|
+
const args = [
|
|
48
|
+
`first: ${pageSize}`,
|
|
49
|
+
"offset: $offset",
|
|
50
|
+
q.orderBy?.length ? `orderBy: [${q.orderBy.join(", ")}]` : "",
|
|
51
|
+
hasCondition ? "condition: $condition" : "",
|
|
52
|
+
hasFilter ? "filter: $filter" : "",
|
|
53
|
+
].filter(Boolean).join(", ");
|
|
54
|
+
const query = `query View(${decl}) { ${field}(${args}) { totalCount nodes { ${selection} } } }`;
|
|
55
|
+
type Conn = { totalCount: number; nodes: Row[] };
|
|
56
|
+
const page = async (offset: number): Promise<Conn> =>
|
|
57
|
+
(await gql<Record<string, Conn>>(url, query, { offset, ...(hasCondition ? { condition: q.condition } : {}), ...(hasFilter ? { filter: q.filter } : {}) }, q.signal))[field]!;
|
|
58
|
+
const first = await page(0);
|
|
59
|
+
const rows = [...first.nodes];
|
|
60
|
+
if (q.first === undefined && first.totalCount > first.nodes.length) {
|
|
61
|
+
const pages = Math.ceil(first.totalCount / PAGE);
|
|
62
|
+
if (pages > MAX_PAGES) throw new Error(`views: ${field} has more than ${PAGE * MAX_PAGES} rows; narrow the query`);
|
|
63
|
+
const offsets = Array.from({ length: pages - 1 }, (_, i) => (i + 1) * PAGE);
|
|
64
|
+
for (let i = 0; i < offsets.length; i += PARALLEL_PAGES) {
|
|
65
|
+
for (const g of await Promise.all(offsets.slice(i, i + PARALLEL_PAGES).map(page))) rows.push(...g.nodes);
|
|
66
|
+
}
|
|
67
|
+
} else if (q.first !== undefined && q.first > PAGE) {
|
|
68
|
+
// a cap above one page: the next pages up to it
|
|
69
|
+
for (let offset = PAGE; offset < Math.min(q.first, first.totalCount); offset += PAGE) rows.push(...(await page(offset)).nodes);
|
|
70
|
+
rows.length = Math.min(rows.length, q.first);
|
|
71
|
+
}
|
|
72
|
+
return rows;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── column parsers ──────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/** A numeric / bigint column (BigFloat or BigInt string, or a number) → bigint; the integer part. */
|
|
78
|
+
function big(v: unknown): bigint {
|
|
79
|
+
if (typeof v === "bigint") return v;
|
|
80
|
+
if (typeof v === "number") return BigInt(Math.trunc(v));
|
|
81
|
+
if (typeof v !== "string" || v === "") throw new Error(`views: not a number: ${JSON.stringify(v)}`);
|
|
82
|
+
return BigInt(v.split(".")[0]!);
|
|
83
|
+
}
|
|
84
|
+
const bigOr = (v: unknown): bigint | null => (v === null || v === undefined ? null : big(v));
|
|
85
|
+
const num = (v: unknown): number => Number(v);
|
|
86
|
+
const numOr = (v: unknown): number | null => (v === null || v === undefined ? null : Number(v));
|
|
87
|
+
const intOr = (v: unknown): number | null => (v === null || v === undefined ? null : Number(v));
|
|
88
|
+
const addr = (v: unknown): Address => getAddress(String(v));
|
|
89
|
+
const addrOr = (v: unknown): Address | null => (v === null || v === undefined ? null : getAddress(String(v)));
|
|
90
|
+
const secs = (v: unknown): number => Date.parse(String(v)) / 1000;
|
|
91
|
+
const secsOr = (v: unknown): number | null => (v === null || v === undefined ? null : secs(v));
|
|
92
|
+
const hex = (v: unknown): Hex => String(v).toLowerCase() as Hex;
|
|
93
|
+
const addrs = (v: unknown): Address[] => ((v as unknown[] | null) ?? []).map(addr);
|
|
94
|
+
|
|
95
|
+
export type Side = "C" | "F";
|
|
96
|
+
|
|
97
|
+
// ─── pool_directory ──────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/** A pool of either manager: C markets are kind `market`; F pools carry their policy's registry name (`public`, `private`, `pol`, `pad`). */
|
|
100
|
+
export interface PoolDirectoryRow {
|
|
101
|
+
side: Side;
|
|
102
|
+
poolId: Hex;
|
|
103
|
+
manager: Address;
|
|
104
|
+
kind: string;
|
|
105
|
+
quote: Address;
|
|
106
|
+
bases: Address[];
|
|
107
|
+
swapFee: bigint;
|
|
108
|
+
/** C only. */
|
|
109
|
+
tickSpacing: bigint | null;
|
|
110
|
+
/** F only. */
|
|
111
|
+
policy: Address | null;
|
|
112
|
+
creator: Address | null;
|
|
113
|
+
birthBlock: bigint;
|
|
114
|
+
birthTime: number;
|
|
115
|
+
/** F only: the pool's share-id key (the id's bits above the generation). */
|
|
116
|
+
shareKey: bigint | null;
|
|
117
|
+
}
|
|
118
|
+
export async function poolDirectory(url: string, q: ViewQuery = {}): Promise<PoolDirectoryRow[]> {
|
|
119
|
+
const rows = await viewRows(url, "allPoolDirectories", "PoolDirectory", "side poolId manager kind quote bases swapFee tickSpacing policy creator birthBlock birthTime shareKey", q);
|
|
120
|
+
return rows.map((r) => ({
|
|
121
|
+
side: r.side as Side, poolId: hex(r.poolId), manager: addr(r.manager), kind: String(r.kind), quote: addr(r.quote), bases: addrs(r.bases),
|
|
122
|
+
swapFee: big(r.swapFee), tickSpacing: bigOr(r.tickSpacing), policy: addrOr(r.policy), creator: addrOr(r.creator),
|
|
123
|
+
birthBlock: big(r.birthBlock), birthTime: secs(r.birthTime), shareKey: bigOr(r.shareKey),
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── event_user ──────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
/** A manager event and who did it: the router's caller when the transaction went through the router (`viaRouter`), else the address the event names (`actor`). */
|
|
130
|
+
export interface EventUserRow {
|
|
131
|
+
side: Side;
|
|
132
|
+
/** `Swap` `Mint` `Burn` `Update` `Claim` (C); `FSwap` `FMint` `FBurn` `Harvest` `DividendClaimed` `Redeem` (F). */
|
|
133
|
+
event: string;
|
|
134
|
+
poolId: Hex;
|
|
135
|
+
actor: Address;
|
|
136
|
+
viaRouter: boolean;
|
|
137
|
+
user: Address;
|
|
138
|
+
txHash: Hex;
|
|
139
|
+
logIndex: number;
|
|
140
|
+
blockNumber: bigint;
|
|
141
|
+
blockTimestamp: number;
|
|
142
|
+
manager: Address;
|
|
143
|
+
}
|
|
144
|
+
export async function eventUsers(url: string, q: ViewQuery = {}): Promise<EventUserRow[]> {
|
|
145
|
+
const rows = await viewRows(url, "allEventUsers", "EventUser", "side event poolId actor viaRouter user txHash logIndex blockNumber blockTimestamp manager", q);
|
|
146
|
+
return rows.map((r) => ({
|
|
147
|
+
side: r.side as Side, event: String(r.event), poolId: hex(r.poolId), actor: addr(r.actor), viaRouter: Boolean(r.viaRouter), user: addr(r.user),
|
|
148
|
+
txHash: hex(r.txHash), logIndex: num(r.logIndex), blockNumber: big(r.blockNumber), blockTimestamp: secs(r.blockTimestamp), manager: addr(r.manager),
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── pool_holder ─────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/** A current ERC-6909 balance: C ids resolve to their class (floor, cap), F ids to their pool and generation. C's backstop lock is held by address(0) (`isBackstop`), F's dead shares by 0xdead (`isDead`). */
|
|
155
|
+
export interface PoolHolderRow {
|
|
156
|
+
side: Side;
|
|
157
|
+
manager: Address;
|
|
158
|
+
id: bigint;
|
|
159
|
+
poolId: Hex | null;
|
|
160
|
+
/** F only. */
|
|
161
|
+
gen: number | null;
|
|
162
|
+
/** C only. */
|
|
163
|
+
floor: bigint | null;
|
|
164
|
+
cap: bigint | null;
|
|
165
|
+
capped: boolean | null;
|
|
166
|
+
holder: Address;
|
|
167
|
+
balance: bigint;
|
|
168
|
+
isBackstop: boolean;
|
|
169
|
+
isDead: boolean;
|
|
170
|
+
}
|
|
171
|
+
export async function poolHolders(url: string, q: ViewQuery = {}): Promise<PoolHolderRow[]> {
|
|
172
|
+
const rows = await viewRows(url, "allPoolHolders", "PoolHolder", "side manager id poolId gen floor cap capped holder balance isBackstop isDead", q);
|
|
173
|
+
return rows.map((r) => ({
|
|
174
|
+
side: r.side as Side, manager: addr(r.manager), id: big(r.id), poolId: r.poolId == null ? null : hex(r.poolId), gen: intOr(r.gen),
|
|
175
|
+
floor: bigOr(r.floor), cap: bigOr(r.cap), capped: r.capped == null ? null : Boolean(r.capped),
|
|
176
|
+
holder: addr(r.holder), balance: big(r.balance), isBackstop: Boolean(r.isBackstop), isDead: Boolean(r.isDead),
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── wallet_position ─────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/** A wallet's holding (backstop and dead excluded). `valueQuote`: an F share of the live generation at the latest snapshot's share value, a retired one at its bundle's quote per share; NULL for C (a lens read: {@link legsOf}). */
|
|
183
|
+
export interface WalletPositionRow {
|
|
184
|
+
side: Side;
|
|
185
|
+
manager: Address;
|
|
186
|
+
wallet: Address;
|
|
187
|
+
poolId: Hex | null;
|
|
188
|
+
kind: string | null;
|
|
189
|
+
quote: Address | null;
|
|
190
|
+
bases: Address[];
|
|
191
|
+
id: bigint;
|
|
192
|
+
gen: number | null;
|
|
193
|
+
floor: bigint | null;
|
|
194
|
+
cap: bigint | null;
|
|
195
|
+
capped: boolean | null;
|
|
196
|
+
shares: bigint;
|
|
197
|
+
valueQuote: bigint | null;
|
|
198
|
+
/** `live` / `retired` / `unknown` (F), `class` (C). */
|
|
199
|
+
status: string;
|
|
200
|
+
valuedAtBlock: bigint | null;
|
|
201
|
+
}
|
|
202
|
+
export async function walletPositions(url: string, q: ViewQuery = {}): Promise<WalletPositionRow[]> {
|
|
203
|
+
const rows = await viewRows(url, "allWalletPositions", "WalletPosition", "side manager wallet poolId kind quote bases id gen floor cap capped shares valueQuote status valuedAtBlock", q);
|
|
204
|
+
return rows.map((r) => ({
|
|
205
|
+
side: r.side as Side, manager: addr(r.manager), wallet: addr(r.wallet), poolId: r.poolId == null ? null : hex(r.poolId), kind: r.kind == null ? null : String(r.kind),
|
|
206
|
+
quote: addrOr(r.quote), bases: addrs(r.bases), id: big(r.id), gen: intOr(r.gen), floor: bigOr(r.floor), cap: bigOr(r.cap),
|
|
207
|
+
capped: r.capped == null ? null : Boolean(r.capped), shares: big(r.shares), valueQuote: bigOr(r.valueQuote), status: String(r.status), valuedAtBlock: bigOr(r.valuedAtBlock),
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── wallet_pool_flow ────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
/** A wallet's flows in a pool, from its own events: what it put in and took out, fees and dividends realised. F mints and burns carry the quote leg only. C `Update` is not counted (it repeats its Burn and Mint). */
|
|
214
|
+
export interface WalletPoolFlowRow {
|
|
215
|
+
side: Side;
|
|
216
|
+
manager: Address;
|
|
217
|
+
wallet: Address;
|
|
218
|
+
poolId: Hex;
|
|
219
|
+
baseIn: bigint;
|
|
220
|
+
quoteIn: bigint;
|
|
221
|
+
baseOut: bigint;
|
|
222
|
+
quoteOut: bigint;
|
|
223
|
+
feesRealised: bigint;
|
|
224
|
+
dividendsClaimed: bigint;
|
|
225
|
+
redeemedQuote: bigint;
|
|
226
|
+
entries: number;
|
|
227
|
+
exits: number;
|
|
228
|
+
firstTime: number;
|
|
229
|
+
lastTime: number;
|
|
230
|
+
}
|
|
231
|
+
export async function walletPoolFlows(url: string, q: ViewQuery = {}): Promise<WalletPoolFlowRow[]> {
|
|
232
|
+
const rows = await viewRows(url, "allWalletPoolFlows", "WalletPoolFlow", "side manager wallet poolId baseIn quoteIn baseOut quoteOut feesRealised dividendsClaimed redeemedQuote entries exits firstTime lastTime", q);
|
|
233
|
+
return rows.map((r) => ({
|
|
234
|
+
side: r.side as Side, manager: addr(r.manager), wallet: addr(r.wallet), poolId: hex(r.poolId),
|
|
235
|
+
baseIn: big(r.baseIn), quoteIn: big(r.quoteIn), baseOut: big(r.baseOut), quoteOut: big(r.quoteOut),
|
|
236
|
+
feesRealised: big(r.feesRealised), dividendsClaimed: big(r.dividendsClaimed), redeemedQuote: big(r.redeemedQuote),
|
|
237
|
+
entries: num(r.entries), exits: num(r.exits), firstTime: secs(r.firstTime), lastTime: secs(r.lastTime),
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── swap_flow ───────────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
/** A swap's volume and fee in quote, the app's definitions (C: the quote leg and the event's fee; F: see VIEWS.md). F rows carry the traded legs and their L_j = w_j·L. */
|
|
244
|
+
export interface SwapFlowRow {
|
|
245
|
+
side: Side;
|
|
246
|
+
manager: Address;
|
|
247
|
+
poolId: Hex;
|
|
248
|
+
txHash: Hex;
|
|
249
|
+
logIndex: number;
|
|
250
|
+
blockNumber: bigint;
|
|
251
|
+
blockTimestamp: number;
|
|
252
|
+
volumeQuote: bigint;
|
|
253
|
+
feeQuote: bigint;
|
|
254
|
+
tokenIn: Address | null;
|
|
255
|
+
tokenOut: Address | null;
|
|
256
|
+
reserveIn: bigint | null;
|
|
257
|
+
reserveOut: bigint | null;
|
|
258
|
+
lIn: bigint | null;
|
|
259
|
+
lOut: bigint | null;
|
|
260
|
+
}
|
|
261
|
+
export async function swapFlows(url: string, q: ViewQuery = {}): Promise<SwapFlowRow[]> {
|
|
262
|
+
const rows = await viewRows(url, "allSwapFlows", "SwapFlow", "side manager poolId txHash logIndex blockNumber blockTimestamp volumeQuote feeQuote tokenIn tokenOut reserveIn reserveOut lIn lOut", q);
|
|
263
|
+
return rows.map((r) => ({
|
|
264
|
+
side: r.side as Side, manager: addr(r.manager), poolId: hex(r.poolId), txHash: hex(r.txHash), logIndex: num(r.logIndex),
|
|
265
|
+
blockNumber: big(r.blockNumber), blockTimestamp: secs(r.blockTimestamp), volumeQuote: big(r.volumeQuote), feeQuote: big(r.feeQuote),
|
|
266
|
+
tokenIn: addrOr(r.tokenIn), tokenOut: addrOr(r.tokenOut), reserveIn: bigOr(r.reserveIn), reserveOut: bigOr(r.reserveOut), lIn: bigOr(r.lIn), lOut: bigOr(r.lOut),
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ─── price_tick, candles ─────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
/** A price point per (pool, base) after a swap: C e^newX, F the traded leg's mark L_j / R_j. Quote wei per base wei. */
|
|
273
|
+
export interface PriceTickRow {
|
|
274
|
+
side: Side;
|
|
275
|
+
poolId: Hex;
|
|
276
|
+
base: Address;
|
|
277
|
+
blockNumber: bigint;
|
|
278
|
+
blockTimestamp: number;
|
|
279
|
+
logIndex: number;
|
|
280
|
+
priceRaw: number;
|
|
281
|
+
volumeQuote: bigint;
|
|
282
|
+
feeQuote: bigint;
|
|
283
|
+
}
|
|
284
|
+
export async function priceTicks(url: string, q: ViewQuery = {}): Promise<PriceTickRow[]> {
|
|
285
|
+
const rows = await viewRows(url, "allPriceTicks", "PriceTick", "side poolId base blockNumber blockTimestamp logIndex priceRaw volumeQuote feeQuote", q);
|
|
286
|
+
return rows.map((r) => ({
|
|
287
|
+
side: r.side as Side, poolId: hex(r.poolId), base: addr(r.base), blockNumber: big(r.blockNumber), blockTimestamp: secs(r.blockTimestamp),
|
|
288
|
+
logIndex: num(r.logIndex), priceRaw: num(r.priceRaw), volumeQuote: big(r.volumeQuote), feeQuote: big(r.feeQuote),
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** OHLC per (pool, base, UTC bucket). A base → base F swap counts in both legs' candles; pool totals come from {@link poolSummaries}. */
|
|
293
|
+
export interface CandleRow {
|
|
294
|
+
side: Side;
|
|
295
|
+
poolId: Hex;
|
|
296
|
+
base: Address;
|
|
297
|
+
/** Bucket start, unix seconds. */
|
|
298
|
+
bucket: number;
|
|
299
|
+
open: number;
|
|
300
|
+
high: number;
|
|
301
|
+
low: number;
|
|
302
|
+
close: number;
|
|
303
|
+
volumeQuote: bigint;
|
|
304
|
+
feeQuote: bigint;
|
|
305
|
+
swaps: number;
|
|
306
|
+
}
|
|
307
|
+
export type CandleInterval = "minute" | "hour" | "day";
|
|
308
|
+
const CANDLE: Record<CandleInterval, [string, string]> = { minute: ["allCandleMinutes", "CandleMinute"], hour: ["allCandleHours", "CandleHour"], day: ["allCandleDays", "CandleDay"] };
|
|
309
|
+
export async function candles(url: string, interval: CandleInterval, q: ViewQuery = {}): Promise<CandleRow[]> {
|
|
310
|
+
const [field, type] = CANDLE[interval];
|
|
311
|
+
const rows = await viewRows(url, field, type, "side poolId base bucket open high low close volumeQuote feeQuote swaps", q);
|
|
312
|
+
return rows.map((r) => ({
|
|
313
|
+
side: r.side as Side, poolId: hex(r.poolId), base: addr(r.base), bucket: secs(r.bucket), open: num(r.open), high: num(r.high), low: num(r.low), close: num(r.close),
|
|
314
|
+
volumeQuote: big(r.volumeQuote), feeQuote: big(r.feeQuote), swaps: num(r.swaps),
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ─── pool_tvl_hour ───────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
/** The hour's last snapshot per pool (forward-fill hours with none). `liquidity` is C's active L or F's L. */
|
|
321
|
+
export interface PoolTvlHourRow {
|
|
322
|
+
side: Side;
|
|
323
|
+
poolId: Hex;
|
|
324
|
+
bucket: number;
|
|
325
|
+
blockNumber: bigint;
|
|
326
|
+
tvlQuote: bigint | null;
|
|
327
|
+
liquidity: bigint | null;
|
|
328
|
+
/** C only. */
|
|
329
|
+
priceRaw: number | null;
|
|
330
|
+
feeGrowth: bigint | null;
|
|
331
|
+
/** F only. */
|
|
332
|
+
shareValue: bigint | null;
|
|
333
|
+
gen: number | null;
|
|
334
|
+
}
|
|
335
|
+
export async function poolTvlHours(url: string, q: ViewQuery = {}): Promise<PoolTvlHourRow[]> {
|
|
336
|
+
const rows = await viewRows(url, "allPoolTvlHours", "PoolTvlHour", "side poolId bucket blockNumber tvlQuote liquidity priceRaw feeGrowth shareValue gen", q);
|
|
337
|
+
return rows.map((r) => ({
|
|
338
|
+
side: r.side as Side, poolId: hex(r.poolId), bucket: secs(r.bucket), blockNumber: big(r.blockNumber), tvlQuote: bigOr(r.tvlQuote),
|
|
339
|
+
liquidity: bigOr(r.liquidity), priceRaw: numOr(r.priceRaw), feeGrowth: bigOr(r.feeGrowth), shareValue: bigOr(r.shareValue), gen: intOr(r.gen),
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ─── pool_summary ────────────────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* A pool's trailing 24 h / 7 d figures and its latest state. APY, as the app shows it: the window's
|
|
347
|
+
* swap fees over the pool's current L, annualised over the window measured (clipped to the pool's
|
|
348
|
+
* birth) — `fees / L × 31 536 000 / seconds`; the same formula as {@link swapFeeApy}.
|
|
349
|
+
*/
|
|
350
|
+
export interface PoolSummaryRow {
|
|
351
|
+
side: Side;
|
|
352
|
+
poolId: Hex;
|
|
353
|
+
kind: string;
|
|
354
|
+
quote: Address;
|
|
355
|
+
bases: Address[];
|
|
356
|
+
swapFee: bigint;
|
|
357
|
+
birthTime: number;
|
|
358
|
+
volume24h: bigint;
|
|
359
|
+
fees24h: bigint;
|
|
360
|
+
swaps24h: number;
|
|
361
|
+
volume7d: bigint;
|
|
362
|
+
fees7d: bigint;
|
|
363
|
+
volumeAll: bigint;
|
|
364
|
+
feesAll: bigint;
|
|
365
|
+
lastSwap: number | null;
|
|
366
|
+
tvlQuote: bigint | null;
|
|
367
|
+
liquidity: bigint | null;
|
|
368
|
+
priceRaw: number | null;
|
|
369
|
+
shareValue: bigint | null;
|
|
370
|
+
gen: number | null;
|
|
371
|
+
stateBlock: bigint | null;
|
|
372
|
+
seconds24h: number;
|
|
373
|
+
seconds7d: number;
|
|
374
|
+
apy24h: number | null;
|
|
375
|
+
apy7d: number | null;
|
|
376
|
+
}
|
|
377
|
+
export async function poolSummaries(url: string, q: ViewQuery = {}): Promise<PoolSummaryRow[]> {
|
|
378
|
+
const rows = await viewRows(
|
|
379
|
+
url, "allPoolSummaries", "PoolSummary",
|
|
380
|
+
"side poolId kind quote bases swapFee birthTime volume24H fees24H swaps24H volume7D fees7D volumeAll feesAll lastSwap tvlQuote liquidity priceRaw shareValue gen stateBlock seconds24H seconds7D apy24H apy7D",
|
|
381
|
+
q,
|
|
382
|
+
);
|
|
383
|
+
return rows.map((r) => ({
|
|
384
|
+
side: r.side as Side, poolId: hex(r.poolId), kind: String(r.kind), quote: addr(r.quote), bases: addrs(r.bases), swapFee: big(r.swapFee), birthTime: secs(r.birthTime),
|
|
385
|
+
volume24h: big(r.volume24H), fees24h: big(r.fees24H), swaps24h: num(r.swaps24H), volume7d: big(r.volume7D), fees7d: big(r.fees7D),
|
|
386
|
+
volumeAll: big(r.volumeAll), feesAll: big(r.feesAll), lastSwap: secsOr(r.lastSwap), tvlQuote: bigOr(r.tvlQuote), liquidity: bigOr(r.liquidity),
|
|
387
|
+
priceRaw: numOr(r.priceRaw), shareValue: bigOr(r.shareValue), gen: intOr(r.gen), stateBlock: bigOr(r.stateBlock),
|
|
388
|
+
seconds24h: num(r.seconds24H), seconds7d: num(r.seconds7D), apy24h: numOr(r.apy24H), apy7d: numOr(r.apy7D),
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ─── generation_step, pad_status ─────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
/** A `regenerate` step: the pot and its split (payee, the protocol's cut, the burn, the rest deepening the bid); a pad's `rung` from `Graduated`. */
|
|
395
|
+
export interface GenerationStepRow {
|
|
396
|
+
kind: string | null;
|
|
397
|
+
poolId: Hex;
|
|
398
|
+
/** The pool's generation after this step. */
|
|
399
|
+
genAfter: number;
|
|
400
|
+
pot: bigint;
|
|
401
|
+
payee: Address;
|
|
402
|
+
toPayee: bigint;
|
|
403
|
+
cut: bigint;
|
|
404
|
+
burnt: bigint;
|
|
405
|
+
deepened: bigint;
|
|
406
|
+
rung: number | null;
|
|
407
|
+
txHash: Hex;
|
|
408
|
+
blockNumber: bigint;
|
|
409
|
+
blockTimestamp: number;
|
|
410
|
+
}
|
|
411
|
+
export async function generationSteps(url: string, q: ViewQuery = {}): Promise<GenerationStepRow[]> {
|
|
412
|
+
const rows = await viewRows(url, "allGenerationSteps", "GenerationStep", "kind poolId genAfter pot payee toPayee cut burnt deepened rung txHash blockNumber blockTimestamp", q);
|
|
413
|
+
return rows.map((r) => ({
|
|
414
|
+
kind: r.kind == null ? null : String(r.kind), poolId: hex(r.poolId), genAfter: num(r.genAfter), pot: big(r.pot), payee: addr(r.payee),
|
|
415
|
+
toPayee: big(r.toPayee), cut: big(r.cut), burnt: big(r.burnt), deepened: big(r.deepened), rung: intOr(r.rung),
|
|
416
|
+
txHash: hex(r.txHash), blockNumber: big(r.blockNumber), blockTimestamp: secs(r.blockTimestamp),
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** Every pad and POL now: the latest snapshot's state, the next graduation's threshold (pads) and the pot's progress to it, and the steps so far. */
|
|
421
|
+
export interface PadStatusRow {
|
|
422
|
+
kind: string;
|
|
423
|
+
poolId: Hex;
|
|
424
|
+
quote: Address;
|
|
425
|
+
bases: Address[];
|
|
426
|
+
creator: Address | null;
|
|
427
|
+
birthTime: number;
|
|
428
|
+
gen: number | null;
|
|
429
|
+
pot: bigint | null;
|
|
430
|
+
unharvested: bigint | null;
|
|
431
|
+
l: bigint | null;
|
|
432
|
+
q: bigint | null;
|
|
433
|
+
tvlQuote: bigint | null;
|
|
434
|
+
shareValue: bigint | null;
|
|
435
|
+
threshold: bigint | null;
|
|
436
|
+
/** pot / threshold, a fraction; null without a threshold. */
|
|
437
|
+
progress: number | null;
|
|
438
|
+
stateBlock: bigint | null;
|
|
439
|
+
stateTime: number | null;
|
|
440
|
+
steps: number;
|
|
441
|
+
burntTotal: bigint;
|
|
442
|
+
cutTotal: bigint;
|
|
443
|
+
paidTotal: bigint;
|
|
444
|
+
deepenedTotal: bigint;
|
|
445
|
+
lastStep: number | null;
|
|
446
|
+
}
|
|
447
|
+
export async function padStatuses(url: string, q: ViewQuery = {}): Promise<PadStatusRow[]> {
|
|
448
|
+
const rows = await viewRows(
|
|
449
|
+
url, "allPadStatuses", "PadStatus",
|
|
450
|
+
"kind poolId quote bases creator birthTime gen pot unharvested l q tvlQuote shareValue threshold progress stateBlock stateTime steps burntTotal cutTotal paidTotal deepenedTotal lastStep",
|
|
451
|
+
q,
|
|
452
|
+
);
|
|
453
|
+
return rows.map((r) => ({
|
|
454
|
+
kind: String(r.kind), poolId: hex(r.poolId), quote: addr(r.quote), bases: addrs(r.bases), creator: addrOr(r.creator), birthTime: secs(r.birthTime),
|
|
455
|
+
gen: intOr(r.gen), pot: bigOr(r.pot), unharvested: bigOr(r.unharvested), l: bigOr(r.l), q: bigOr(r.q), tvlQuote: bigOr(r.tvlQuote),
|
|
456
|
+
shareValue: bigOr(r.shareValue), threshold: bigOr(r.threshold), progress: numOr(r.progress), stateBlock: bigOr(r.stateBlock), stateTime: secsOr(r.stateTime),
|
|
457
|
+
steps: num(r.steps), burntTotal: big(r.burntTotal), cutTotal: big(r.cutTotal), paidTotal: big(r.paidTotal), deepenedTotal: big(r.deepenedTotal), lastStep: secsOr(r.lastStep),
|
|
458
|
+
}));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// ─── protocol_revenue_day ────────────────────────────────────────────────────
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Protocol revenue per UTC day, side, source and token. Sources: F `mint_fee` (shares minted to the
|
|
465
|
+
* collector, valued at the share value) and `pot_cut`; C `accrued` (the swap-fee tax and the mint fee
|
|
466
|
+
* together, from the snapshots) and `collected` — basis `collected`, cash moved, not revenue on top
|
|
467
|
+
* of `accrued`.
|
|
468
|
+
*/
|
|
469
|
+
export interface ProtocolRevenueDayRow {
|
|
470
|
+
day: number;
|
|
471
|
+
side: Side;
|
|
472
|
+
source: string;
|
|
473
|
+
basis: "earned" | "collected";
|
|
474
|
+
token: Address;
|
|
475
|
+
amount: bigint;
|
|
476
|
+
amountQuote: bigint;
|
|
477
|
+
events: number;
|
|
478
|
+
}
|
|
479
|
+
export async function protocolRevenueDays(url: string, q: ViewQuery = {}): Promise<ProtocolRevenueDayRow[]> {
|
|
480
|
+
const rows = await viewRows(url, "allProtocolRevenueDays", "ProtocolRevenueDay", "day side source basis token amount amountQuote events", q);
|
|
481
|
+
return rows.map((r) => ({
|
|
482
|
+
day: secs(r.day), side: r.side as Side, source: String(r.source), basis: r.basis as "earned" | "collected", token: addr(r.token),
|
|
483
|
+
amount: big(r.amount), amountQuote: big(r.amountQuote), events: num(r.events),
|
|
484
|
+
}));
|
|
485
|
+
}
|