@logswap/sdk 0.5.74 → 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.
@@ -0,0 +1,285 @@
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 { type Address, type Hex } from "viem";
20
+ /** 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. */
21
+ export interface ViewQuery {
22
+ condition?: Record<string, unknown>;
23
+ filter?: Record<string, unknown>;
24
+ /** e.g. `["BLOCK_NUMBER_ASC"]`, `["VOLUME_24H_DESC"]`: the column's CONSTANT_CASE name and a direction. */
25
+ orderBy?: string[];
26
+ /** At most this many rows (one page up to 1000). All rows when omitted. */
27
+ first?: number;
28
+ signal?: AbortSignal;
29
+ }
30
+ export type Side = "C" | "F";
31
+ /** A pool of either manager: C markets are kind `market`; F pools carry their policy's registry name (`public`, `private`, `pol`, `pad`). */
32
+ export interface PoolDirectoryRow {
33
+ side: Side;
34
+ poolId: Hex;
35
+ manager: Address;
36
+ kind: string;
37
+ quote: Address;
38
+ bases: Address[];
39
+ swapFee: bigint;
40
+ /** C only. */
41
+ tickSpacing: bigint | null;
42
+ /** F only. */
43
+ policy: Address | null;
44
+ creator: Address | null;
45
+ birthBlock: bigint;
46
+ birthTime: number;
47
+ /** F only: the pool's share-id key (the id's bits above the generation). */
48
+ shareKey: bigint | null;
49
+ }
50
+ export declare function poolDirectory(url: string, q?: ViewQuery): Promise<PoolDirectoryRow[]>;
51
+ /** 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`). */
52
+ export interface EventUserRow {
53
+ side: Side;
54
+ /** `Swap` `Mint` `Burn` `Update` `Claim` (C); `FSwap` `FMint` `FBurn` `Harvest` `DividendClaimed` `Redeem` (F). */
55
+ event: string;
56
+ poolId: Hex;
57
+ actor: Address;
58
+ viaRouter: boolean;
59
+ user: Address;
60
+ txHash: Hex;
61
+ logIndex: number;
62
+ blockNumber: bigint;
63
+ blockTimestamp: number;
64
+ manager: Address;
65
+ }
66
+ export declare function eventUsers(url: string, q?: ViewQuery): Promise<EventUserRow[]>;
67
+ /** 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`). */
68
+ export interface PoolHolderRow {
69
+ side: Side;
70
+ manager: Address;
71
+ id: bigint;
72
+ poolId: Hex | null;
73
+ /** F only. */
74
+ gen: number | null;
75
+ /** C only. */
76
+ floor: bigint | null;
77
+ cap: bigint | null;
78
+ capped: boolean | null;
79
+ holder: Address;
80
+ balance: bigint;
81
+ isBackstop: boolean;
82
+ isDead: boolean;
83
+ }
84
+ export declare function poolHolders(url: string, q?: ViewQuery): Promise<PoolHolderRow[]>;
85
+ /** 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}). */
86
+ export interface WalletPositionRow {
87
+ side: Side;
88
+ manager: Address;
89
+ wallet: Address;
90
+ poolId: Hex | null;
91
+ kind: string | null;
92
+ quote: Address | null;
93
+ bases: Address[];
94
+ id: bigint;
95
+ gen: number | null;
96
+ floor: bigint | null;
97
+ cap: bigint | null;
98
+ capped: boolean | null;
99
+ shares: bigint;
100
+ valueQuote: bigint | null;
101
+ /** `live` / `retired` / `unknown` (F), `class` (C). */
102
+ status: string;
103
+ valuedAtBlock: bigint | null;
104
+ }
105
+ export declare function walletPositions(url: string, q?: ViewQuery): Promise<WalletPositionRow[]>;
106
+ /** 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). */
107
+ export interface WalletPoolFlowRow {
108
+ side: Side;
109
+ manager: Address;
110
+ wallet: Address;
111
+ poolId: Hex;
112
+ baseIn: bigint;
113
+ quoteIn: bigint;
114
+ baseOut: bigint;
115
+ quoteOut: bigint;
116
+ feesRealised: bigint;
117
+ dividendsClaimed: bigint;
118
+ redeemedQuote: bigint;
119
+ entries: number;
120
+ exits: number;
121
+ firstTime: number;
122
+ lastTime: number;
123
+ }
124
+ export declare function walletPoolFlows(url: string, q?: ViewQuery): Promise<WalletPoolFlowRow[]>;
125
+ /** 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. */
126
+ export interface SwapFlowRow {
127
+ side: Side;
128
+ manager: Address;
129
+ poolId: Hex;
130
+ txHash: Hex;
131
+ logIndex: number;
132
+ blockNumber: bigint;
133
+ blockTimestamp: number;
134
+ volumeQuote: bigint;
135
+ feeQuote: bigint;
136
+ tokenIn: Address | null;
137
+ tokenOut: Address | null;
138
+ reserveIn: bigint | null;
139
+ reserveOut: bigint | null;
140
+ lIn: bigint | null;
141
+ lOut: bigint | null;
142
+ }
143
+ export declare function swapFlows(url: string, q?: ViewQuery): Promise<SwapFlowRow[]>;
144
+ /** 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. */
145
+ export interface PriceTickRow {
146
+ side: Side;
147
+ poolId: Hex;
148
+ base: Address;
149
+ blockNumber: bigint;
150
+ blockTimestamp: number;
151
+ logIndex: number;
152
+ priceRaw: number;
153
+ volumeQuote: bigint;
154
+ feeQuote: bigint;
155
+ }
156
+ export declare function priceTicks(url: string, q?: ViewQuery): Promise<PriceTickRow[]>;
157
+ /** OHLC per (pool, base, UTC bucket). A base → base F swap counts in both legs' candles; pool totals come from {@link poolSummaries}. */
158
+ export interface CandleRow {
159
+ side: Side;
160
+ poolId: Hex;
161
+ base: Address;
162
+ /** Bucket start, unix seconds. */
163
+ bucket: number;
164
+ open: number;
165
+ high: number;
166
+ low: number;
167
+ close: number;
168
+ volumeQuote: bigint;
169
+ feeQuote: bigint;
170
+ swaps: number;
171
+ }
172
+ export type CandleInterval = "minute" | "hour" | "day";
173
+ export declare function candles(url: string, interval: CandleInterval, q?: ViewQuery): Promise<CandleRow[]>;
174
+ /** The hour's last snapshot per pool (forward-fill hours with none). `liquidity` is C's active L or F's L. */
175
+ export interface PoolTvlHourRow {
176
+ side: Side;
177
+ poolId: Hex;
178
+ bucket: number;
179
+ blockNumber: bigint;
180
+ tvlQuote: bigint | null;
181
+ liquidity: bigint | null;
182
+ /** C only. */
183
+ priceRaw: number | null;
184
+ feeGrowth: bigint | null;
185
+ /** F only. */
186
+ shareValue: bigint | null;
187
+ gen: number | null;
188
+ }
189
+ export declare function poolTvlHours(url: string, q?: ViewQuery): Promise<PoolTvlHourRow[]>;
190
+ /**
191
+ * A pool's trailing 24 h / 7 d figures and its latest state. APY, as the app shows it: the window's
192
+ * swap fees over the pool's current L, annualised over the window measured (clipped to the pool's
193
+ * birth) — `fees / L × 31 536 000 / seconds`; the same formula as {@link swapFeeApy}.
194
+ */
195
+ export interface PoolSummaryRow {
196
+ side: Side;
197
+ poolId: Hex;
198
+ kind: string;
199
+ quote: Address;
200
+ bases: Address[];
201
+ swapFee: bigint;
202
+ birthTime: number;
203
+ volume24h: bigint;
204
+ fees24h: bigint;
205
+ swaps24h: number;
206
+ volume7d: bigint;
207
+ fees7d: bigint;
208
+ volumeAll: bigint;
209
+ feesAll: bigint;
210
+ lastSwap: number | null;
211
+ tvlQuote: bigint | null;
212
+ liquidity: bigint | null;
213
+ priceRaw: number | null;
214
+ shareValue: bigint | null;
215
+ gen: number | null;
216
+ stateBlock: bigint | null;
217
+ seconds24h: number;
218
+ seconds7d: number;
219
+ apy24h: number | null;
220
+ apy7d: number | null;
221
+ }
222
+ export declare function poolSummaries(url: string, q?: ViewQuery): Promise<PoolSummaryRow[]>;
223
+ /** 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`. */
224
+ export interface GenerationStepRow {
225
+ kind: string | null;
226
+ poolId: Hex;
227
+ /** The pool's generation after this step. */
228
+ genAfter: number;
229
+ pot: bigint;
230
+ payee: Address;
231
+ toPayee: bigint;
232
+ cut: bigint;
233
+ burnt: bigint;
234
+ deepened: bigint;
235
+ rung: number | null;
236
+ txHash: Hex;
237
+ blockNumber: bigint;
238
+ blockTimestamp: number;
239
+ }
240
+ export declare function generationSteps(url: string, q?: ViewQuery): Promise<GenerationStepRow[]>;
241
+ /** 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. */
242
+ export interface PadStatusRow {
243
+ kind: string;
244
+ poolId: Hex;
245
+ quote: Address;
246
+ bases: Address[];
247
+ creator: Address | null;
248
+ birthTime: number;
249
+ gen: number | null;
250
+ pot: bigint | null;
251
+ unharvested: bigint | null;
252
+ l: bigint | null;
253
+ q: bigint | null;
254
+ tvlQuote: bigint | null;
255
+ shareValue: bigint | null;
256
+ threshold: bigint | null;
257
+ /** pot / threshold, a fraction; null without a threshold. */
258
+ progress: number | null;
259
+ stateBlock: bigint | null;
260
+ stateTime: number | null;
261
+ steps: number;
262
+ burntTotal: bigint;
263
+ cutTotal: bigint;
264
+ paidTotal: bigint;
265
+ deepenedTotal: bigint;
266
+ lastStep: number | null;
267
+ }
268
+ export declare function padStatuses(url: string, q?: ViewQuery): Promise<PadStatusRow[]>;
269
+ /**
270
+ * Protocol revenue per UTC day, side, source and token. Sources: F `mint_fee` (shares minted to the
271
+ * collector, valued at the share value) and `pot_cut`; C `accrued` (the swap-fee tax and the mint fee
272
+ * together, from the snapshots) and `collected` — basis `collected`, cash moved, not revenue on top
273
+ * of `accrued`.
274
+ */
275
+ export interface ProtocolRevenueDayRow {
276
+ day: number;
277
+ side: Side;
278
+ source: string;
279
+ basis: "earned" | "collected";
280
+ token: Address;
281
+ amount: bigint;
282
+ amountQuote: bigint;
283
+ events: number;
284
+ }
285
+ export declare function protocolRevenueDays(url: string, q?: ViewQuery): Promise<ProtocolRevenueDayRow[]>;
package/dist/views.js ADDED
@@ -0,0 +1,188 @@
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 } from "viem";
20
+ import { gql } from "./indexer.js";
21
+ const PAGE = 1000; // postgraphile's cap
22
+ const MAX_PAGES = 200;
23
+ const PARALLEL_PAGES = 8;
24
+ /** Every row of a view, paged by offset (a view has no primary key to page a cursor on). */
25
+ async function viewRows(url, field, type, selection, q) {
26
+ for (const o of q.orderBy ?? [])
27
+ if (!/^[A-Z0-9_]+$/.test(o))
28
+ throw new Error(`views: orderBy ${JSON.stringify(o)} is not an enum name`);
29
+ const hasCondition = q.condition !== undefined && Object.keys(q.condition).length > 0;
30
+ const hasFilter = q.filter !== undefined && Object.keys(q.filter).length > 0;
31
+ const pageSize = Math.min(q.first ?? PAGE, PAGE);
32
+ // postgraphile refuses a null or empty condition/filter: each appears only when it has content
33
+ const decl = `$offset: Int${hasCondition ? `, $condition: ${type}Condition` : ""}${hasFilter ? `, $filter: ${type}Filter` : ""}`;
34
+ const args = [
35
+ `first: ${pageSize}`,
36
+ "offset: $offset",
37
+ q.orderBy?.length ? `orderBy: [${q.orderBy.join(", ")}]` : "",
38
+ hasCondition ? "condition: $condition" : "",
39
+ hasFilter ? "filter: $filter" : "",
40
+ ].filter(Boolean).join(", ");
41
+ const query = `query View(${decl}) { ${field}(${args}) { totalCount nodes { ${selection} } } }`;
42
+ const page = async (offset) => (await gql(url, query, { offset, ...(hasCondition ? { condition: q.condition } : {}), ...(hasFilter ? { filter: q.filter } : {}) }, q.signal))[field];
43
+ const first = await page(0);
44
+ const rows = [...first.nodes];
45
+ if (q.first === undefined && first.totalCount > first.nodes.length) {
46
+ const pages = Math.ceil(first.totalCount / PAGE);
47
+ if (pages > MAX_PAGES)
48
+ throw new Error(`views: ${field} has more than ${PAGE * MAX_PAGES} rows; narrow the query`);
49
+ const offsets = Array.from({ length: pages - 1 }, (_, i) => (i + 1) * PAGE);
50
+ for (let i = 0; i < offsets.length; i += PARALLEL_PAGES) {
51
+ for (const g of await Promise.all(offsets.slice(i, i + PARALLEL_PAGES).map(page)))
52
+ rows.push(...g.nodes);
53
+ }
54
+ }
55
+ else if (q.first !== undefined && q.first > PAGE) {
56
+ // a cap above one page: the next pages up to it
57
+ for (let offset = PAGE; offset < Math.min(q.first, first.totalCount); offset += PAGE)
58
+ rows.push(...(await page(offset)).nodes);
59
+ rows.length = Math.min(rows.length, q.first);
60
+ }
61
+ return rows;
62
+ }
63
+ // ─── column parsers ──────────────────────────────────────────────────────────
64
+ /** A numeric / bigint column (BigFloat or BigInt string, or a number) → bigint; the integer part. */
65
+ function big(v) {
66
+ if (typeof v === "bigint")
67
+ return v;
68
+ if (typeof v === "number")
69
+ return BigInt(Math.trunc(v));
70
+ if (typeof v !== "string" || v === "")
71
+ throw new Error(`views: not a number: ${JSON.stringify(v)}`);
72
+ return BigInt(v.split(".")[0]);
73
+ }
74
+ const bigOr = (v) => (v === null || v === undefined ? null : big(v));
75
+ const num = (v) => Number(v);
76
+ const numOr = (v) => (v === null || v === undefined ? null : Number(v));
77
+ const intOr = (v) => (v === null || v === undefined ? null : Number(v));
78
+ const addr = (v) => getAddress(String(v));
79
+ const addrOr = (v) => (v === null || v === undefined ? null : getAddress(String(v)));
80
+ const secs = (v) => Date.parse(String(v)) / 1000;
81
+ const secsOr = (v) => (v === null || v === undefined ? null : secs(v));
82
+ const hex = (v) => String(v).toLowerCase();
83
+ const addrs = (v) => (v ?? []).map(addr);
84
+ export async function poolDirectory(url, q = {}) {
85
+ const rows = await viewRows(url, "allPoolDirectories", "PoolDirectory", "side poolId manager kind quote bases swapFee tickSpacing policy creator birthBlock birthTime shareKey", q);
86
+ return rows.map((r) => ({
87
+ side: r.side, poolId: hex(r.poolId), manager: addr(r.manager), kind: String(r.kind), quote: addr(r.quote), bases: addrs(r.bases),
88
+ swapFee: big(r.swapFee), tickSpacing: bigOr(r.tickSpacing), policy: addrOr(r.policy), creator: addrOr(r.creator),
89
+ birthBlock: big(r.birthBlock), birthTime: secs(r.birthTime), shareKey: bigOr(r.shareKey),
90
+ }));
91
+ }
92
+ export async function eventUsers(url, q = {}) {
93
+ const rows = await viewRows(url, "allEventUsers", "EventUser", "side event poolId actor viaRouter user txHash logIndex blockNumber blockTimestamp manager", q);
94
+ return rows.map((r) => ({
95
+ side: r.side, event: String(r.event), poolId: hex(r.poolId), actor: addr(r.actor), viaRouter: Boolean(r.viaRouter), user: addr(r.user),
96
+ txHash: hex(r.txHash), logIndex: num(r.logIndex), blockNumber: big(r.blockNumber), blockTimestamp: secs(r.blockTimestamp), manager: addr(r.manager),
97
+ }));
98
+ }
99
+ export async function poolHolders(url, q = {}) {
100
+ const rows = await viewRows(url, "allPoolHolders", "PoolHolder", "side manager id poolId gen floor cap capped holder balance isBackstop isDead", q);
101
+ return rows.map((r) => ({
102
+ side: r.side, manager: addr(r.manager), id: big(r.id), poolId: r.poolId == null ? null : hex(r.poolId), gen: intOr(r.gen),
103
+ floor: bigOr(r.floor), cap: bigOr(r.cap), capped: r.capped == null ? null : Boolean(r.capped),
104
+ holder: addr(r.holder), balance: big(r.balance), isBackstop: Boolean(r.isBackstop), isDead: Boolean(r.isDead),
105
+ }));
106
+ }
107
+ export async function walletPositions(url, q = {}) {
108
+ const rows = await viewRows(url, "allWalletPositions", "WalletPosition", "side manager wallet poolId kind quote bases id gen floor cap capped shares valueQuote status valuedAtBlock", q);
109
+ return rows.map((r) => ({
110
+ side: r.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),
111
+ quote: addrOr(r.quote), bases: addrs(r.bases), id: big(r.id), gen: intOr(r.gen), floor: bigOr(r.floor), cap: bigOr(r.cap),
112
+ capped: r.capped == null ? null : Boolean(r.capped), shares: big(r.shares), valueQuote: bigOr(r.valueQuote), status: String(r.status), valuedAtBlock: bigOr(r.valuedAtBlock),
113
+ }));
114
+ }
115
+ export async function walletPoolFlows(url, q = {}) {
116
+ const rows = await viewRows(url, "allWalletPoolFlows", "WalletPoolFlow", "side manager wallet poolId baseIn quoteIn baseOut quoteOut feesRealised dividendsClaimed redeemedQuote entries exits firstTime lastTime", q);
117
+ return rows.map((r) => ({
118
+ side: r.side, manager: addr(r.manager), wallet: addr(r.wallet), poolId: hex(r.poolId),
119
+ baseIn: big(r.baseIn), quoteIn: big(r.quoteIn), baseOut: big(r.baseOut), quoteOut: big(r.quoteOut),
120
+ feesRealised: big(r.feesRealised), dividendsClaimed: big(r.dividendsClaimed), redeemedQuote: big(r.redeemedQuote),
121
+ entries: num(r.entries), exits: num(r.exits), firstTime: secs(r.firstTime), lastTime: secs(r.lastTime),
122
+ }));
123
+ }
124
+ export async function swapFlows(url, q = {}) {
125
+ const rows = await viewRows(url, "allSwapFlows", "SwapFlow", "side manager poolId txHash logIndex blockNumber blockTimestamp volumeQuote feeQuote tokenIn tokenOut reserveIn reserveOut lIn lOut", q);
126
+ return rows.map((r) => ({
127
+ side: r.side, manager: addr(r.manager), poolId: hex(r.poolId), txHash: hex(r.txHash), logIndex: num(r.logIndex),
128
+ blockNumber: big(r.blockNumber), blockTimestamp: secs(r.blockTimestamp), volumeQuote: big(r.volumeQuote), feeQuote: big(r.feeQuote),
129
+ tokenIn: addrOr(r.tokenIn), tokenOut: addrOr(r.tokenOut), reserveIn: bigOr(r.reserveIn), reserveOut: bigOr(r.reserveOut), lIn: bigOr(r.lIn), lOut: bigOr(r.lOut),
130
+ }));
131
+ }
132
+ export async function priceTicks(url, q = {}) {
133
+ const rows = await viewRows(url, "allPriceTicks", "PriceTick", "side poolId base blockNumber blockTimestamp logIndex priceRaw volumeQuote feeQuote", q);
134
+ return rows.map((r) => ({
135
+ side: r.side, poolId: hex(r.poolId), base: addr(r.base), blockNumber: big(r.blockNumber), blockTimestamp: secs(r.blockTimestamp),
136
+ logIndex: num(r.logIndex), priceRaw: num(r.priceRaw), volumeQuote: big(r.volumeQuote), feeQuote: big(r.feeQuote),
137
+ }));
138
+ }
139
+ const CANDLE = { minute: ["allCandleMinutes", "CandleMinute"], hour: ["allCandleHours", "CandleHour"], day: ["allCandleDays", "CandleDay"] };
140
+ export async function candles(url, interval, q = {}) {
141
+ const [field, type] = CANDLE[interval];
142
+ const rows = await viewRows(url, field, type, "side poolId base bucket open high low close volumeQuote feeQuote swaps", q);
143
+ return rows.map((r) => ({
144
+ side: r.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),
145
+ volumeQuote: big(r.volumeQuote), feeQuote: big(r.feeQuote), swaps: num(r.swaps),
146
+ }));
147
+ }
148
+ export async function poolTvlHours(url, q = {}) {
149
+ const rows = await viewRows(url, "allPoolTvlHours", "PoolTvlHour", "side poolId bucket blockNumber tvlQuote liquidity priceRaw feeGrowth shareValue gen", q);
150
+ return rows.map((r) => ({
151
+ side: r.side, poolId: hex(r.poolId), bucket: secs(r.bucket), blockNumber: big(r.blockNumber), tvlQuote: bigOr(r.tvlQuote),
152
+ liquidity: bigOr(r.liquidity), priceRaw: numOr(r.priceRaw), feeGrowth: bigOr(r.feeGrowth), shareValue: bigOr(r.shareValue), gen: intOr(r.gen),
153
+ }));
154
+ }
155
+ export async function poolSummaries(url, q = {}) {
156
+ const rows = await viewRows(url, "allPoolSummaries", "PoolSummary", "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", q);
157
+ return rows.map((r) => ({
158
+ side: r.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),
159
+ volume24h: big(r.volume24H), fees24h: big(r.fees24H), swaps24h: num(r.swaps24H), volume7d: big(r.volume7D), fees7d: big(r.fees7D),
160
+ volumeAll: big(r.volumeAll), feesAll: big(r.feesAll), lastSwap: secsOr(r.lastSwap), tvlQuote: bigOr(r.tvlQuote), liquidity: bigOr(r.liquidity),
161
+ priceRaw: numOr(r.priceRaw), shareValue: bigOr(r.shareValue), gen: intOr(r.gen), stateBlock: bigOr(r.stateBlock),
162
+ seconds24h: num(r.seconds24H), seconds7d: num(r.seconds7D), apy24h: numOr(r.apy24H), apy7d: numOr(r.apy7D),
163
+ }));
164
+ }
165
+ export async function generationSteps(url, q = {}) {
166
+ const rows = await viewRows(url, "allGenerationSteps", "GenerationStep", "kind poolId genAfter pot payee toPayee cut burnt deepened rung txHash blockNumber blockTimestamp", q);
167
+ return rows.map((r) => ({
168
+ kind: r.kind == null ? null : String(r.kind), poolId: hex(r.poolId), genAfter: num(r.genAfter), pot: big(r.pot), payee: addr(r.payee),
169
+ toPayee: big(r.toPayee), cut: big(r.cut), burnt: big(r.burnt), deepened: big(r.deepened), rung: intOr(r.rung),
170
+ txHash: hex(r.txHash), blockNumber: big(r.blockNumber), blockTimestamp: secs(r.blockTimestamp),
171
+ }));
172
+ }
173
+ export async function padStatuses(url, q = {}) {
174
+ const rows = await viewRows(url, "allPadStatuses", "PadStatus", "kind poolId quote bases creator birthTime gen pot unharvested l q tvlQuote shareValue threshold progress stateBlock stateTime steps burntTotal cutTotal paidTotal deepenedTotal lastStep", q);
175
+ return rows.map((r) => ({
176
+ kind: String(r.kind), poolId: hex(r.poolId), quote: addr(r.quote), bases: addrs(r.bases), creator: addrOr(r.creator), birthTime: secs(r.birthTime),
177
+ gen: intOr(r.gen), pot: bigOr(r.pot), unharvested: bigOr(r.unharvested), l: bigOr(r.l), q: bigOr(r.q), tvlQuote: bigOr(r.tvlQuote),
178
+ shareValue: bigOr(r.shareValue), threshold: bigOr(r.threshold), progress: numOr(r.progress), stateBlock: bigOr(r.stateBlock), stateTime: secsOr(r.stateTime),
179
+ steps: num(r.steps), burntTotal: big(r.burntTotal), cutTotal: big(r.cutTotal), paidTotal: big(r.paidTotal), deepenedTotal: big(r.deepenedTotal), lastStep: secsOr(r.lastStep),
180
+ }));
181
+ }
182
+ export async function protocolRevenueDays(url, q = {}) {
183
+ const rows = await viewRows(url, "allProtocolRevenueDays", "ProtocolRevenueDay", "day side source basis token amount amountQuote events", q);
184
+ return rows.map((r) => ({
185
+ day: secs(r.day), side: r.side, source: String(r.source), basis: r.basis, token: addr(r.token),
186
+ amount: big(r.amount), amountQuote: big(r.amountQuote), events: num(r.events),
187
+ }));
188
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logswap/sdk",
3
- "version": "0.5.74",
3
+ "version": "0.5.76",
4
4
  "description": "TypeScript SDK for Logswap, the liquidity primitive introducing the logarithmic invariant — typed wrappers over the CPoolManager / FPoolManager / Router / Lens surface.",
5
5
  "license": "GPL-2.0-or-later",
6
6
  "type": "module",
package/src/client.ts CHANGED
@@ -37,6 +37,8 @@ export interface LogswapAddresses {
37
37
  * absent; a deployment with none asks nobody.
38
38
  */
39
39
  policyRegistry?: Address;
40
+ /** The devnet's token factory (`TokenFactory`, a mock): where launchable tokens come from. Optional. */
41
+ tokenFactory?: Address;
40
42
  /** Optional — the router falls back to plain approvals when absent. */
41
43
  permit2?: Address;
42
44
  }
package/src/fees.ts CHANGED
@@ -14,6 +14,23 @@
14
14
  import type { LogswapClient } from "./client.js";
15
15
  import { cPoolManagerAbi } from "./generated.js";
16
16
  import type { DiscoveredMarket } from "./pools.js";
17
+ import { getEventLogs } from "./history.js";
18
+ import { swapFlows } from "./views.js";
19
+
20
+ /** A C market's swap fees over [fromBlock, toBlock] from the indexer's swap_flow view; null when the indexer cannot answer. */
21
+ async function windowFromView(c: LogswapClient, indexer: string, poolId: string, fromBlock: bigint, toBlock: bigint): Promise<{ fees: bigint; swaps: number } | null> {
22
+ try {
23
+ const rows = await swapFlows(indexer, {
24
+ condition: { side: "C", poolId: poolId.toLowerCase(), manager: c.addresses.cPoolManager.toLowerCase() },
25
+ filter: { blockNumber: { greaterThanOrEqualTo: fromBlock.toString(), lessThanOrEqualTo: toBlock.toString() } },
26
+ });
27
+ let fees = 0n;
28
+ for (const r of rows) fees += r.feeQuote;
29
+ return { fees, swaps: rows.length };
30
+ } catch {
31
+ return null; // the view is a cache: the Swap rows answer the same
32
+ }
33
+ }
17
34
 
18
35
  const WAD = 10n ** 18n;
19
36
  const YEAR_SECONDS = 31_536_000;
@@ -85,7 +102,7 @@ export async function blockAtOrAfter(c: LogswapClient, t: bigint, lo: bigint, hi
85
102
  export async function swapFees(
86
103
  c: LogswapClient,
87
104
  m: DiscoveredMarket,
88
- opts: { since?: bigint; fromBlock?: bigint; toBlock?: bigint } = {},
105
+ opts: { since?: bigint; fromBlock?: bigint; toBlock?: bigint; /** The rindexer GraphQL endpoint: the window from its swap_flow view (the Swap rows as fallback), the node otherwise. */ indexer?: string } = {},
89
106
  ): Promise<SwapFeeWindow> {
90
107
  const toBlock = opts.toBlock ?? (await c.public.getBlockNumber());
91
108
  const [opened, last] = await Promise.all([
@@ -110,16 +127,21 @@ export async function swapFees(
110
127
  const ev = cPoolManagerAbi.find((x): x is Ev => x.type === "event" && x.name === "Swap");
111
128
  if (!ev) throw new Error("logswap: Swap event missing from the generated ABI");
112
129
 
113
- const logs = fromBlock > toBlock
114
- ? []
115
- : await c.public.getLogs({ address: c.addresses.cPoolManager, event: ev, args: { poolId: m.poolId }, fromBlock, toBlock });
116
-
117
- // the event carries the fee the swap charged (decisions 050), exactly — no re-derivation from φ
130
+ // the event carries the fee the swap charged (decisions 050), exactly — no re-derivation from φ;
131
+ // the indexer's swap_flow view carries the same figure per swap (`feeQuote`, C)
118
132
  let fees = 0n;
119
- for (const l of logs) fees += l.args.fee!;
133
+ let swaps = 0;
134
+ const viewed = fromBlock > toBlock || !opts.indexer ? null : await windowFromView(c, opts.indexer, m.poolId, fromBlock, toBlock);
135
+ if (viewed) {
136
+ ({ fees, swaps } = viewed);
137
+ } else if (fromBlock <= toBlock) {
138
+ const { logs } = await getEventLogs<{ fee: bigint }>(c, { indexer: opts.indexer, address: c.addresses.cPoolManager, event: ev, args: { poolId: m.poolId }, fromBlock, toBlock });
139
+ for (const l of logs) fees += l.args.fee;
140
+ swaps = logs.length;
141
+ }
120
142
 
121
143
  const to = last.timestamp;
122
- return { fromBlock, toBlock, from, to, seconds: to > from ? to - from : 0n, swapFees: fees, swaps: logs.length };
144
+ return { fromBlock, toBlock, from, to, seconds: to > from ? to - from : 0n, swapFees: fees, swaps };
123
145
  }
124
146
 
125
147
  /**
package/src/fpool.ts CHANGED
@@ -50,6 +50,7 @@ export const fPolicyBaseAbi = polPolicyAbi;
50
50
  import type { LogswapClient } from "./client.js";
51
51
  import { fallBackUnlessRejected, signerFor } from "./write.js";
52
52
  import { getEventLogs } from "./history.js";
53
+ import { poolHolders } from "./views.js";
53
54
  import { presetMintFee } from "./positions.js";
54
55
 
55
56
  const WAD = 10n ** 18n;
@@ -1150,19 +1151,13 @@ export async function fPoolIdOf(c: LogswapClient, a: Omit<FPoolCreateArgs, "acco
1150
1151
  * without naming one; a pad's carries its split, so it is rebuilt from the policy's record with nonce 0 — pass `opts.salt` for another nonce). The result is checked against the id, so a wrong salt throws rather than
1151
1152
  * returning a key that names another pool. What {@link fPoolMigrate} needs when handed an id.
1152
1153
  */
1153
- export async function fPoolKeyOfPool(c: LogswapClient, poolId: Hex, opts: { salt?: Hex; fromBlock?: bigint } = {}): Promise<FPoolKey> {
1154
+ export async function fPoolKeyOfPool(c: LogswapClient, poolId: Hex, opts: { salt?: Hex; fromBlock?: bigint; /** The rindexer GraphQL endpoint: `FInitialize` from there, the node otherwise. The salt is in neither (the event does not carry it). */ indexer?: string } = {}): Promise<FPoolKey> {
1154
1155
  type Ev = Extract<(typeof fPoolManagerAbi)[number], { type: "event"; name: "FInitialize" }>;
1155
1156
  const ev = fPoolManagerAbi.find((x): x is Ev => x.type === "event" && x.name === "FInitialize")!;
1156
- const logs = await c.public.getLogs({
1157
- address: c.addresses.fPoolManager!,
1158
- event: ev,
1159
- args: { poolId },
1160
- fromBlock: opts.fromBlock ?? 0n,
1161
- toBlock: "latest",
1162
- });
1157
+ const { logs } = await getEventLogs(c, { indexer: opts.indexer, address: c.addresses.fPoolManager!, event: ev as never, args: { poolId }, fromBlock: opts.fromBlock ?? 0n });
1163
1158
  const l = logs[0];
1164
1159
  if (!l) throw new Error(`logswap: no FInitialize for F pool ${poolId}`);
1165
- const a = l.args as { quote: Address; creator: Address; bases: readonly Address[]; weights: readonly bigint[]; swapFee: bigint; policy: Address; cap: number };
1160
+ const a = l.args as unknown as { quote: Address; creator: Address; bases: readonly Address[]; weights: readonly bigint[]; swapFee: bigint; policy: Address; cap: number | bigint };
1166
1161
  const key: FPoolKey = {
1167
1162
  quote: a.quote,
1168
1163
  bases: [...a.bases],
@@ -2022,7 +2017,7 @@ export async function fPoolsControlledBy(
2022
2017
  /**
2023
2018
  * Every holder of a pool's shares, from the ERC-6909 Transfer stream — the id is an indexed
2024
2019
  * topic, so one filtered log query aggregates the whole ledger. The 0xdead entry is the
2025
- * MIN_SHARES lock from seeding; callers usually label rather than hide it. Pass `indexer`: a node
2020
+ * MIN_SHARES lock from seeding; callers usually label rather than hide it. Pass `indexer`: the indexer's `pool_holder` view answers directly (the Transfer replay is the fallback) — a node
2026
2021
  * prunes its logs (anvil keeps about a day), and a ledger rebuilt from a pruned stream loses every
2027
2022
  * holder minted before the cut.
2028
2023
  */
@@ -2034,7 +2029,19 @@ export async function fPoolShareHolders(
2034
2029
  type Ev = Extract<(typeof fPoolManagerAbi)[number], { type: "event"; name: "Transfer" }>;
2035
2030
  const ev = fPoolManagerAbi.find((x): x is Ev => x.type === "event" && x.name === "Transfer");
2036
2031
  if (!ev) throw new Error("logswap: 6909 Transfer event missing from the generated ABI");
2037
- const id = fPoolShareId(poolIdHex, opts.gen ?? (await fPoolGen(c, poolIdHex)));
2032
+ const gen = opts.gen ?? (await fPoolGen(c, poolIdHex));
2033
+ if (opts.indexer) {
2034
+ try {
2035
+ const rows = await poolHolders(opts.indexer, { condition: { side: "F", poolId: poolIdHex.toLowerCase(), gen: Number(gen), manager: c.addresses.fPoolManager!.toLowerCase() } });
2036
+ return rows
2037
+ .filter((r) => r.balance > 0n)
2038
+ .map((r) => ({ holder: r.holder, shares: r.balance }))
2039
+ .sort((a, b) => (b.shares > a.shares ? 1 : -1));
2040
+ } catch {
2041
+ /* the view is a cache: the Transfer replay below answers the same */
2042
+ }
2043
+ }
2044
+ const id = fPoolShareId(poolIdHex, gen);
2038
2045
  const { logs } = await getEventLogs(c, { indexer: opts.indexer, address: c.addresses.fPoolManager!, event: ev, args: { id }, fromBlock: opts.fromBlock });
2039
2046
  const bal = new Map<string, bigint>();
2040
2047
  for (const l of logs) {