@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.
- package/README.md +47 -0
- package/dist/client.d.ts +2 -0
- package/dist/fees.d.ts +2 -1
- package/dist/fees.js +32 -7
- package/dist/fpool.d.ts +3 -2
- package/dist/fpool.js +17 -9
- package/dist/history.d.ts +82 -2
- package/dist/history.js +108 -16
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/pools.d.ts +2 -1
- package/dist/pools.js +10 -3
- package/dist/positions.d.ts +3 -1
- package/dist/positions.js +19 -3
- package/dist/views.d.ts +285 -0
- package/dist/views.js +188 -0
- package/package.json +1 -1
- package/src/client.ts +2 -0
- package/src/fees.ts +30 -8
- package/src/fpool.ts +18 -11
- package/src/history.ts +150 -17
- package/src/index.ts +1 -0
- package/src/pools.ts +16 -11
- package/src/positions.ts +20 -3
- package/src/views.ts +485 -0
package/src/history.ts
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* the decoding (the inputs' types — numbers as bigint, addresses checksummed, arrays from JSON).
|
|
14
14
|
* With no indexer URL, or when the query fails, it asks the node and says so in `source`.
|
|
15
15
|
*/
|
|
16
|
-
import { getAddress, type AbiEvent, type Address, type Hex } from "viem";
|
|
16
|
+
import { getAddress, parseAbiItem, type AbiEvent, type Address, type Hex } from "viem";
|
|
17
17
|
import type { LogswapClient } from "./client.js";
|
|
18
|
+
import type { FPoolKind } from "./fpool.js";
|
|
18
19
|
import { bytea, gql } from "./indexer.js";
|
|
19
20
|
import { blockAtOrAfter } from "./fees.js";
|
|
20
21
|
|
|
@@ -32,7 +33,14 @@ export interface HistoryLog<A = Record<string, unknown>> {
|
|
|
32
33
|
export interface EventLogsQuery {
|
|
33
34
|
/** The rindexer GraphQL endpoint; absent means the chain. */
|
|
34
35
|
indexer?: string;
|
|
35
|
-
|
|
36
|
+
/** The emitter, or several: the four policies share one table per event (indexer phase 1). */
|
|
37
|
+
address: Address | readonly Address[];
|
|
38
|
+
/**
|
|
39
|
+
* The emitter's name in the indexer's config, for an event two indexed contracts share — only
|
|
40
|
+
* needed when the address is not in the client's artifact (a policy the registry approved later):
|
|
41
|
+
* see {@link indexedContractOf}.
|
|
42
|
+
*/
|
|
43
|
+
contract?: string;
|
|
36
44
|
event: AbiEvent;
|
|
37
45
|
/** Indexed arguments to match, as `getLogs` takes them; an array matches any of its values. */
|
|
38
46
|
args?: Record<string, unknown>;
|
|
@@ -70,29 +78,52 @@ export async function getEventLogs<A = Record<string, unknown>>(
|
|
|
70
78
|
|
|
71
79
|
// ─── the indexer path ────────────────────────────────────────────────────────
|
|
72
80
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
81
|
+
/**
|
|
82
|
+
* What the rindexer deployment calls each contract — the prefix of a table two contracts share
|
|
83
|
+
* (logswap-be `devnet/indexer/rindexer.yaml`). The four policies are one factory-tracked contract,
|
|
84
|
+
* `Policy`, except `Listed`, whose two signatures are two contracts (`PublicPolicy`, `PrivatePolicy`).
|
|
85
|
+
*/
|
|
86
|
+
function contractName(c: LogswapClient, address: Address, event: string): string | null {
|
|
75
87
|
const a = address.toLowerCase();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (
|
|
88
|
+
const is = (x?: Address) => x !== undefined && a === x.toLowerCase();
|
|
89
|
+
const { addresses: d } = c;
|
|
90
|
+
if (is(d.cPoolManager)) return "LogswapManager";
|
|
91
|
+
if (is(d.fPoolManager)) return "FPoolManager";
|
|
92
|
+
if (is(d.router)) return "LogswapRouter";
|
|
93
|
+
if (is(d.policyRegistry)) return "PolicyRegistry";
|
|
94
|
+
if (is(d.tokenFactory)) return "TokenFactory";
|
|
95
|
+
if (is(d.publicPolicy)) return indexedContractOf("public", event);
|
|
96
|
+
if (is(d.privatePolicy)) return indexedContractOf("private", event);
|
|
97
|
+
if (is(d.polPolicy)) return indexedContractOf("pol", event);
|
|
98
|
+
if (is(d.padPolicy)) return indexedContractOf("pad", event);
|
|
79
99
|
return null;
|
|
80
100
|
}
|
|
81
101
|
|
|
102
|
+
/**
|
|
103
|
+
* A policy's name in the indexer for one of its events: `Policy`, or the kind's own contract for the
|
|
104
|
+
* two-signature `Listed`. Pass it as {@link EventLogsQuery.contract} for a policy outside the artifact.
|
|
105
|
+
*/
|
|
106
|
+
export function indexedContractOf(kind: FPoolKind, event: string): string {
|
|
107
|
+
if (event === "Listed") return kind === "private" ? "PrivatePolicy" : "PublicPolicy";
|
|
108
|
+
return "Policy";
|
|
109
|
+
}
|
|
110
|
+
|
|
82
111
|
/** Events whose name two indexed contracts share: postgraphile prefixes their tables. */
|
|
83
|
-
const SHARED = new Set(["Transfer"]);
|
|
112
|
+
const SHARED = new Set(["Transfer", "Approval", "OperatorSet", "CollectorProposed", "CollectorAccepted", "Approved", "Listed"]);
|
|
84
113
|
|
|
85
114
|
const plural = (n: string) => (/(s|x|ch|sh)$/.test(n) ? `${n}es` : `${n}s`);
|
|
86
115
|
/** A column's GraphQL name: postgraphile camelCases the snake_case rindexer writes (`F` → `f`). */
|
|
87
116
|
const column = (name: string) => name[0]!.toLowerCase() + name.slice(1);
|
|
88
117
|
|
|
89
|
-
export function tableFor(c: LogswapClient, address: Address, event: string): { field: string; type: string } {
|
|
90
|
-
const prefix = SHARED.has(event) ? contractName(c, address) : "";
|
|
118
|
+
export function tableFor(c: LogswapClient, address: Address, event: string, contract?: string): { field: string; type: string } {
|
|
119
|
+
const prefix = SHARED.has(event) ? (contract ?? contractName(c, address, event)) : "";
|
|
91
120
|
if (prefix === null) throw new Error(`history: no indexed contract at ${address}`);
|
|
92
121
|
const type = `${prefix}${event}`;
|
|
93
122
|
return { field: `all${plural(type)}`, type };
|
|
94
123
|
}
|
|
95
124
|
|
|
125
|
+
const emitters = (q: { address: Address | readonly Address[] }): Address[] => (Array.isArray(q.address) ? [...q.address] : [q.address as Address]);
|
|
126
|
+
|
|
96
127
|
function decode(type: string, v: unknown): unknown {
|
|
97
128
|
if (v === null || v === undefined) return v;
|
|
98
129
|
if (type.endsWith("[]")) return (v as unknown[]).map((x) => decode(type.slice(0, -2), x));
|
|
@@ -107,7 +138,9 @@ const MAX_PAGES = 200;
|
|
|
107
138
|
const PARALLEL_PAGES = 8;
|
|
108
139
|
|
|
109
140
|
async function fromIndexer<A>(c: LogswapClient, q: EventLogsQuery): Promise<HistoryLog<A>[]> {
|
|
110
|
-
const
|
|
141
|
+
const all = emitters(q);
|
|
142
|
+
if (all.length === 0) return [];
|
|
143
|
+
const { field, type } = tableFor(c, all[0]!, q.event.name, q.contract);
|
|
111
144
|
const inputs = q.event.inputs;
|
|
112
145
|
const condition: Record<string, unknown> = {};
|
|
113
146
|
const filter: Record<string, unknown> = {};
|
|
@@ -164,11 +197,11 @@ async function fromIndexer<A>(c: LogswapClient, q: EventLogsQuery): Promise<Hist
|
|
|
164
197
|
}
|
|
165
198
|
}
|
|
166
199
|
// a table can hold a previous deployment's rows (an indexer not yet reset): keep this contract's
|
|
167
|
-
const want =
|
|
200
|
+
const want = new Map(all.map((a) => [a.toLowerCase(), a]));
|
|
168
201
|
return rows
|
|
169
|
-
.filter((r) => String(r.contractAddress).toLowerCase()
|
|
202
|
+
.filter((r) => want.has(String(r.contractAddress).toLowerCase()))
|
|
170
203
|
.map((r) => ({
|
|
171
|
-
address:
|
|
204
|
+
address: want.get(String(r.contractAddress).toLowerCase())!,
|
|
172
205
|
args: Object.fromEntries(inputs.map((i) => [i.name!, decode(i.type, r[column(i.name!)])])) as A,
|
|
173
206
|
blockNumber: BigInt(r.blockNumber as string),
|
|
174
207
|
blockTimestamp: Math.floor(Date.parse(r.blockTimestamp as string) / 1000),
|
|
@@ -185,10 +218,13 @@ export const LATEST_SCAN_BLOCKS = 5_000n;
|
|
|
185
218
|
|
|
186
219
|
async function fromChain<A>(c: LogswapClient, q: EventLogsQuery): Promise<HistoryLog<A>[]> {
|
|
187
220
|
let fromBlock = q.fromBlock ?? 0n;
|
|
188
|
-
const toBlock = q.toBlock ?? (await c.public.getBlockNumber());
|
|
221
|
+
const toBlock = q.toBlock ?? (await c.public.getBlockNumber({ cacheTime: 0 })); // uncached: viem caches the head, and a scan bounded by a stale one misses the block just mined
|
|
189
222
|
if (q.since !== undefined) fromBlock = await blockAtOrAfter(c, BigInt(q.since), fromBlock, toBlock);
|
|
190
223
|
if (q.latest !== undefined && toBlock - fromBlock > LATEST_SCAN_BLOCKS) fromBlock = toBlock - LATEST_SCAN_BLOCKS;
|
|
191
|
-
|
|
224
|
+
const all = emitters(q);
|
|
225
|
+
if (all.length === 0) return [];
|
|
226
|
+
let logs = (await c.public.getLogs({ address: Array.isArray(q.address) ? all : q.address, event: q.event, args: q.args, fromBlock, toBlock } as never)) as unknown as Array<{
|
|
227
|
+
address?: Address;
|
|
192
228
|
args: A;
|
|
193
229
|
blockNumber: bigint;
|
|
194
230
|
transactionHash: Hex;
|
|
@@ -200,10 +236,107 @@ async function fromChain<A>(c: LogswapClient, q: EventLogsQuery): Promise<Histor
|
|
|
200
236
|
}
|
|
201
237
|
if (q.latest !== undefined) logs = logs.slice(-q.latest);
|
|
202
238
|
return logs.map((l) => ({
|
|
203
|
-
address:
|
|
239
|
+
address: l.address ? getAddress(l.address) : all[0]!,
|
|
204
240
|
args: l.args,
|
|
205
241
|
blockNumber: l.blockNumber,
|
|
206
242
|
transactionHash: l.transactionHash,
|
|
207
243
|
logIndex: l.logIndex,
|
|
208
244
|
}));
|
|
209
245
|
}
|
|
246
|
+
|
|
247
|
+
// ─── several events, and the token factory ────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/** A row of {@link getEventsLogs}: one of the asked events, named. */
|
|
250
|
+
export interface NamedHistoryLog extends HistoryLog {
|
|
251
|
+
eventName: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Several events of one contract (or of several emitters sharing them), merged oldest first and
|
|
256
|
+
* tagged with `eventName` — the shape of viem's multi-event `getLogs`, which is what the chain path
|
|
257
|
+
* runs. One indexer table per event; if any of them cannot answer, the whole read goes to the node,
|
|
258
|
+
* so a history is never half the indexer's and half the node's.
|
|
259
|
+
*/
|
|
260
|
+
export async function getEventsLogs(
|
|
261
|
+
c: LogswapClient,
|
|
262
|
+
q: Omit<EventLogsQuery, "event" | "args" | "latest" | "contract" | "transactionHashes"> & {
|
|
263
|
+
events: readonly AbiEvent[];
|
|
264
|
+
/** The emitters' policy kind, to name a shared event's table for a policy outside the artifact. */
|
|
265
|
+
kind?: FPoolKind;
|
|
266
|
+
/** The emitter's name in the indexer (e.g. `PolicyRegistry`), for a contract outside the artifact; `kind` wins for a policy. */
|
|
267
|
+
contract?: string;
|
|
268
|
+
},
|
|
269
|
+
): Promise<{ logs: NamedHistoryLog[]; source: "indexer" | "chain" }> {
|
|
270
|
+
const order = (a: HistoryLog, b: HistoryLog) => (a.blockNumber === b.blockNumber ? a.logIndex - b.logIndex : a.blockNumber < b.blockNumber ? -1 : 1);
|
|
271
|
+
if (q.indexer && q.events.length > 0) {
|
|
272
|
+
try {
|
|
273
|
+
const got = await Promise.all(
|
|
274
|
+
q.events.map(async (event) => {
|
|
275
|
+
const contract = q.kind ? indexedContractOf(q.kind, event.name) : q.contract;
|
|
276
|
+
return (await fromIndexer<Record<string, unknown>>(c, { ...q, event, contract })).map((l) => ({ ...l, eventName: event.name }));
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
return { logs: got.flat().sort(order), source: "indexer" };
|
|
280
|
+
} catch {
|
|
281
|
+
/* the indexer is a cache: the node holds the same events */
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const all = emitters(q);
|
|
285
|
+
if (all.length === 0 || q.events.length === 0) return { logs: [], source: "chain" };
|
|
286
|
+
let fromBlock = q.fromBlock ?? 0n;
|
|
287
|
+
const toBlock = q.toBlock ?? (await c.public.getBlockNumber({ cacheTime: 0 })); // uncached: viem caches the head, and a scan bounded by a stale one misses the block just mined
|
|
288
|
+
if (q.since !== undefined) fromBlock = await blockAtOrAfter(c, BigInt(q.since), fromBlock, toBlock);
|
|
289
|
+
const logs = (await c.public.getLogs({ address: Array.isArray(q.address) ? all : q.address, events: q.events, fromBlock, toBlock } as never)) as unknown as Array<{
|
|
290
|
+
eventName: string;
|
|
291
|
+
address?: Address;
|
|
292
|
+
args: Record<string, unknown>;
|
|
293
|
+
blockNumber: bigint;
|
|
294
|
+
transactionHash: Hex;
|
|
295
|
+
logIndex: number;
|
|
296
|
+
}>;
|
|
297
|
+
return {
|
|
298
|
+
logs: logs
|
|
299
|
+
.map((l) => ({ eventName: l.eventName, address: l.address ? getAddress(l.address) : all[0]!, args: l.args, blockNumber: l.blockNumber, transactionHash: l.transactionHash, logIndex: l.logIndex }))
|
|
300
|
+
.sort(order),
|
|
301
|
+
source: "chain",
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** The devnet's token factory event (`src/mocks/TokenFactory.sol`): provenance for every launchable token. */
|
|
306
|
+
export const TOKEN_CREATED = parseAbiItem("event TokenCreated(address indexed creator, address indexed token, string name, string symbol, uint256 supply)");
|
|
307
|
+
|
|
308
|
+
export interface CreatedToken {
|
|
309
|
+
creator: Address;
|
|
310
|
+
token: Address;
|
|
311
|
+
name: string;
|
|
312
|
+
symbol: string;
|
|
313
|
+
supply: bigint;
|
|
314
|
+
blockNumber: bigint;
|
|
315
|
+
/** Unix seconds; set when the indexer answered. */
|
|
316
|
+
blockTimestamp?: number;
|
|
317
|
+
transactionHash: Hex;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Every token the deployment's factory created, oldest first — the token list's provenance (a token
|
|
322
|
+
* named like a factory token but deployed elsewhere is not in it). Optionally one creator's.
|
|
323
|
+
*/
|
|
324
|
+
export async function tokensCreated(
|
|
325
|
+
c: LogswapClient,
|
|
326
|
+
q: { indexer?: string; creator?: Address; fromBlock?: bigint; signal?: AbortSignal },
|
|
327
|
+
): Promise<{ tokens: CreatedToken[]; source: "indexer" | "chain" }> {
|
|
328
|
+
const factory = c.addresses.tokenFactory;
|
|
329
|
+
if (!factory) throw new Error("logswap: tokensCreated needs addresses.tokenFactory");
|
|
330
|
+
const { logs, source } = await getEventLogs<{ creator: Address; token: Address; name: string; symbol: string; supply: bigint }>(c, {
|
|
331
|
+
indexer: q.indexer,
|
|
332
|
+
address: factory,
|
|
333
|
+
event: TOKEN_CREATED,
|
|
334
|
+
args: q.creator ? { creator: q.creator } : undefined,
|
|
335
|
+
fromBlock: q.fromBlock,
|
|
336
|
+
signal: q.signal,
|
|
337
|
+
});
|
|
338
|
+
return {
|
|
339
|
+
tokens: logs.map((l) => ({ ...l.args, blockNumber: l.blockNumber, ...(l.blockTimestamp !== undefined ? { blockTimestamp: l.blockTimestamp } : {}), transactionHash: l.transactionHash })),
|
|
340
|
+
source,
|
|
341
|
+
};
|
|
342
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -21,5 +21,6 @@ export * from "./tokencache.js";
|
|
|
21
21
|
export * from "./fpool.js";
|
|
22
22
|
export * from "./fees.js";
|
|
23
23
|
export * from "./history.js";
|
|
24
|
+
export * from "./views.js";
|
|
24
25
|
export { cPoolManagerAbi, fPoolManagerAbi, logswapLensAbi, logswapRouterAbi, padPolicyAbi, polPolicyAbi, policyRegistryAbi, privatePolicyAbi, publicPolicyAbi } from "./generated.js";
|
|
25
26
|
export * from "./admin.js";
|
package/src/pools.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { signerFor } from "./write.js";
|
|
|
19
19
|
import { readCachedToken, registerCacheClear, writeCachedToken } from "./tokencache.js";
|
|
20
20
|
import { poolId, type PoolKey } from "./keys.js";
|
|
21
21
|
import { presetMintFee } from "./positions.js";
|
|
22
|
+
import { getEventLogs } from "./history.js";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* A market's scalar state, field-for-field with `CPoolState.PoolView` — what the manager's `getPool`
|
|
@@ -162,7 +163,7 @@ export interface DiscoveredMarket {
|
|
|
162
163
|
*/
|
|
163
164
|
export async function discoverMarkets(
|
|
164
165
|
c: LogswapClient,
|
|
165
|
-
opts: { fromBlock?: bigint; toBlock?: bigint; base?: Address; quote?: Address } = {},
|
|
166
|
+
opts: { fromBlock?: bigint; toBlock?: bigint; base?: Address; quote?: Address; /** The rindexer GraphQL endpoint: `Initialize` from there, the node otherwise. */ indexer?: string } = {},
|
|
166
167
|
): Promise<DiscoveredMarket[]> {
|
|
167
168
|
// Narrow the const ABI to the Initialize entry while KEEPING its literal type, so viem can infer
|
|
168
169
|
// the log shape. `as never` would compile but collapse every arg to `never` at the call site.
|
|
@@ -170,28 +171,32 @@ export async function discoverMarkets(
|
|
|
170
171
|
const ev = cPoolManagerAbi.find((x): x is Ev => x.type === "event" && x.name === "Initialize");
|
|
171
172
|
if (!ev) throw new Error("logswap: Initialize event missing from the generated ABI");
|
|
172
173
|
|
|
173
|
-
const
|
|
174
|
+
const args: Record<string, unknown> = {};
|
|
175
|
+
if (opts.base) args.base = opts.base;
|
|
176
|
+
if (opts.quote) args.quote = opts.quote;
|
|
177
|
+
const { logs } = await getEventLogs<{ base: Address; quote: Address; tickSpacing: bigint; swapFee: bigint; x0: bigint }>(c, {
|
|
178
|
+
indexer: opts.indexer,
|
|
174
179
|
address: c.addresses.cPoolManager,
|
|
175
180
|
event: ev,
|
|
176
|
-
args
|
|
181
|
+
args,
|
|
177
182
|
fromBlock: opts.fromBlock ?? 0n,
|
|
178
|
-
toBlock: opts.toBlock
|
|
183
|
+
toBlock: opts.toBlock,
|
|
179
184
|
});
|
|
180
185
|
|
|
181
186
|
return logs.map((l) => {
|
|
182
187
|
const a = l.args;
|
|
183
188
|
const key: PoolKey = {
|
|
184
|
-
base: a.base
|
|
185
|
-
quote: a.quote
|
|
186
|
-
tickSpacing: a.tickSpacing
|
|
187
|
-
swapFee: a.swapFee
|
|
189
|
+
base: a.base,
|
|
190
|
+
quote: a.quote,
|
|
191
|
+
tickSpacing: a.tickSpacing,
|
|
192
|
+
swapFee: a.swapFee,
|
|
188
193
|
};
|
|
189
194
|
return {
|
|
190
195
|
key,
|
|
191
196
|
poolId: poolId(key),
|
|
192
|
-
x0: a.x0
|
|
193
|
-
blockNumber: l.blockNumber
|
|
194
|
-
transactionHash: l.transactionHash
|
|
197
|
+
x0: a.x0,
|
|
198
|
+
blockNumber: l.blockNumber,
|
|
199
|
+
transactionHash: l.transactionHash,
|
|
195
200
|
};
|
|
196
201
|
});
|
|
197
202
|
}
|
package/src/positions.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { FEE_MAX, poolId, type PoolKey } from "./keys.js";
|
|
|
16
16
|
const C_POSITIONS_SLOT = 4n;
|
|
17
17
|
const U128 = (1n << 128n) - 1n;
|
|
18
18
|
import { isPosition, NO_CAP, positionId, unpackFloorCap } from "./ids.js";
|
|
19
|
+
import { getEventLogs } from "./history.js";
|
|
20
|
+
import { poolHolders } from "./views.js";
|
|
19
21
|
|
|
20
22
|
/** A class's aggregate state — the whole class, not one holder's slice. */
|
|
21
23
|
export interface PositionClass {
|
|
@@ -262,6 +264,22 @@ export async function positionsOf(c: LogswapClient, key: PoolKey, owner: Address
|
|
|
262
264
|
|
|
263
265
|
const ID_REGISTERED = parseAbiItem("event IdRegistered(uint256 indexed id, bytes32 indexed poolId, int256 floor, int256 cap, bool capped)");
|
|
264
266
|
|
|
267
|
+
/** The class ids `owner` holds in `pool`, from the indexer's `pool_holder` view; null when the indexer cannot answer. */
|
|
268
|
+
async function heldIdsFromView(c: LogswapClient, indexer: string, pool: Hex, owner: Address): Promise<bigint[] | null> {
|
|
269
|
+
try {
|
|
270
|
+
const rows = await poolHolders(indexer, { condition: { side: "C", poolId: pool.toLowerCase(), holder: owner.toLowerCase(), manager: c.addresses.cPoolManager.toLowerCase() } });
|
|
271
|
+
return rows.filter((r) => r.balance > 0n && !r.isBackstop).map((r) => r.id);
|
|
272
|
+
} catch {
|
|
273
|
+
return null; // the view is a cache: the IdRegistered replay below answers the same
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Every class id ever registered in `pool` (`IdRegistered`), from the indexer's table or the node. */
|
|
278
|
+
async function registeredIds(c: LogswapClient, pool: Hex, fromBlock: bigint, indexer?: string): Promise<bigint[]> {
|
|
279
|
+
const { logs } = await getEventLogs<{ id?: bigint }>(c, { indexer, address: c.addresses.cPoolManager, event: ID_REGISTERED, args: { poolId: pool }, fromBlock });
|
|
280
|
+
return logs.map((l) => l.args.id ?? 0n).filter((id) => id !== 0n);
|
|
281
|
+
}
|
|
282
|
+
|
|
265
283
|
/**
|
|
266
284
|
* EVERY class this owner holds in a pool, capped ones included — the complete book.
|
|
267
285
|
*
|
|
@@ -272,10 +290,9 @@ const ID_REGISTERED = parseAbiItem("event IdRegistered(uint256 indexed id, bytes
|
|
|
272
290
|
* holder's slice is L · shares_owner / shares_class, and fees from the lens. One log query plus
|
|
273
291
|
* a read per registered class; fine for a pool's lifetime of classes.
|
|
274
292
|
*/
|
|
275
|
-
export async function holdingsOf(c: LogswapClient, key: PoolKey, owner: Address, fromBlock: bigint = 0n): Promise<HeldPositionRow[]> {
|
|
293
|
+
export async function holdingsOf(c: LogswapClient, key: PoolKey, owner: Address, fromBlock: bigint = 0n, opts: { /** The rindexer GraphQL endpoint: the owner's held ids from its `pool_holder` view (the class registry as fallback), the node otherwise. */ indexer?: string } = {}): Promise<HeldPositionRow[]> {
|
|
276
294
|
const pool = poolId(key);
|
|
277
|
-
const
|
|
278
|
-
const ids = logs.map((l) => (l.args as { id?: bigint }).id ?? 0n).filter((id) => id !== 0n);
|
|
295
|
+
const ids = (opts.indexer ? await heldIdsFromView(c, opts.indexer, pool, owner) : null) ?? (await registeredIds(c, pool, fromBlock, opts.indexer));
|
|
279
296
|
if (ids.length === 0) return [];
|
|
280
297
|
// one lens call: balance, class L, fees, floor and cap for every id the owner holds
|
|
281
298
|
const rows = (await c.public.readContract({
|