@secondlayer/subgraphs 6.3.6 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,441 @@
1
+ import { SubgraphVerification } from "@secondlayer/shared/schemas/subgraphs";
2
+ import { AbiContract as AbiContract2 } from "@secondlayer/stacks/clarity";
3
+ import { AssetIdentifier } from "@secondlayer/stacks/filters";
4
+ /**
5
+ * How long a read stays valid.
6
+ *
7
+ * - `per-block` (default): pinned to the block being processed, cached under
8
+ * its `index_block_hash`. Sound for any read-only function — reindexing the
9
+ * same block always returns the same value, and a reorg-replaced block has a
10
+ * different id so it can't inherit the orphaned answer.
11
+ * - `contract-constant`: the caller asserts the value cannot change for this
12
+ * contract and these args (SIP-010 `get-decimals`/`get-symbol`), so it is
13
+ * resolved ONCE instead of once per block. This is the mode that makes a
14
+ * backfill affordable — under `per-block` every block is a distinct key and
15
+ * therefore a distinct RPC. Declaring it on a value that does change pins the
16
+ * first answer forever, so it is opt-in and never inferred.
17
+ */
18
+ type ChainReadCacheMode = "per-block" | "contract-constant";
19
+ type ChainReadOptions = {
20
+ cache?: ChainReadCacheMode;
21
+ };
22
+ /**
23
+ * `ChainReadClient` with the ABI generic erased. Structurally cheap, so the
24
+ * base `SubgraphContext` can carry it; `TypedSubgraphContext` uses the real
25
+ * one and handlers get full inference.
26
+ */
27
+ interface ErasedChainReadClient {
28
+ contract(contractId: string, abi: unknown, options?: ChainReadOptions): {
29
+ read: Record<string, (args?: Record<string, unknown>) => Promise<unknown>>;
30
+ };
31
+ }
32
+ /** Supported column types for subgraph schemas */
33
+ type ColumnType = "text" | "uint" | "int" | "principal" | "boolean" | "timestamp" | "jsonb";
34
+ /** Column definition in a subgraph table */
35
+ interface SubgraphColumn {
36
+ type: ColumnType;
37
+ nullable?: boolean;
38
+ indexed?: boolean;
39
+ search?: boolean;
40
+ default?: string | number | boolean;
41
+ }
42
+ /**
43
+ * Table definition within a subgraph schema.
44
+ *
45
+ * The array fields are `readonly` so a schema literal can be hoisted out of
46
+ * the `defineSubgraph()` call with `as const` (or {@link defineSchema}) and
47
+ * still typecheck — `as const` produces `readonly [readonly ["holder"]]`,
48
+ * which a mutable `string[][]` rejects. Without that, extracting a helper
49
+ * that takes `ctx` meant losing the entire typed surface, which is why the
50
+ * shipped starter reached for `ctx: any`.
51
+ */
52
+ interface SubgraphTable {
53
+ columns: Record<string, SubgraphColumn>;
54
+ /** Composite indexes (each entry is an array of column names) */
55
+ indexes?: readonly (readonly string[])[];
56
+ /** Unique key constraints (each entry is an array of column names). Required for upsert. */
57
+ uniqueKeys?: readonly (readonly string[])[];
58
+ }
59
+ /** Subgraph schema — maps table names to table definitions */
60
+ type SubgraphSchema = Record<string, SubgraphTable>;
61
+ /** STX event filters */
62
+ interface StxTransferFilter {
63
+ type: "stx_transfer";
64
+ sender?: string;
65
+ recipient?: string;
66
+ minAmount?: bigint;
67
+ maxAmount?: bigint;
68
+ }
69
+ interface StxMintFilter {
70
+ type: "stx_mint";
71
+ recipient?: string;
72
+ minAmount?: bigint;
73
+ }
74
+ interface StxBurnFilter {
75
+ type: "stx_burn";
76
+ sender?: string;
77
+ minAmount?: bigint;
78
+ }
79
+ interface StxLockFilter {
80
+ type: "stx_lock";
81
+ lockedAddress?: string;
82
+ minAmount?: bigint;
83
+ }
84
+ /**
85
+ * Restrict a source to contracts conforming to a trait/standard (e.g. "sip-010")
86
+ * instead of a fixed contract — resolved from the contract registry at match time,
87
+ * as-of each processed block. Lets a source index "all SIP-010 tokens" etc.
88
+ */
89
+ type TraitScope = {
90
+ trait?: string;
91
+ };
92
+ /**
93
+ * Scope a source to a DYNAMIC address set discovered from another source's
94
+ * events, instead of a fixed contract list.
95
+ *
96
+ * The standard shape it unlocks: a router plus pools created after you
97
+ * deploy. `contractId` arrays cover a set you know at authoring time; a
98
+ * factory covers the set that grows. It is deliberately NOT tied to contract
99
+ * deployment — the address can come from any field of any event, so it also
100
+ * covers launchpad-minted tokens, DAO extension allowlists, and registry
101
+ * entries.
102
+ *
103
+ * ```ts
104
+ * sources: {
105
+ * registry: {
106
+ * type: "print_event",
107
+ * contractId: REGISTRY,
108
+ * topic: "pool-created",
109
+ * prints: { "pool-created": { pool: "principal" } },
110
+ * },
111
+ * swaps: {
112
+ * type: "print_event",
113
+ * topic: "swap",
114
+ * // every pool the registry has ever announced, including new ones
115
+ * factory: { from: "registry", field: "data.pool" },
116
+ * },
117
+ * }
118
+ * ```
119
+ *
120
+ * Two guarantees the runtime provides:
121
+ * - **Same-block ordering** — a contract discovered in block N receives its
122
+ * own block-N events. Discovery runs before matching, within the block.
123
+ * - **Reorg-scoped** — the discovered set is stamped with the block that
124
+ * revealed it and rolled back with everything else on a fork, so an address
125
+ * announced on an orphaned chain does not linger forever.
126
+ */
127
+ interface FactoryScope {
128
+ factory?: {
129
+ /** Source name whose events reveal the addresses. */
130
+ from: string;
131
+ /** Dotted path to the address on that source's payload
132
+ * (e.g. `"data.pool"`, `"contractId"`, `"data.token.id"`). */
133
+ field: string;
134
+ };
135
+ }
136
+ /** FT event filters */
137
+ interface FtTransferFilter extends TraitScope {
138
+ type: "ft_transfer";
139
+ assetIdentifier?: AssetIdentifier;
140
+ sender?: string;
141
+ recipient?: string;
142
+ minAmount?: bigint;
143
+ }
144
+ interface FtMintFilter extends TraitScope {
145
+ type: "ft_mint";
146
+ assetIdentifier?: AssetIdentifier;
147
+ recipient?: string;
148
+ minAmount?: bigint;
149
+ }
150
+ interface FtBurnFilter extends TraitScope {
151
+ type: "ft_burn";
152
+ assetIdentifier?: AssetIdentifier;
153
+ sender?: string;
154
+ minAmount?: bigint;
155
+ }
156
+ /** NFT event filters */
157
+ interface NftTransferFilter extends TraitScope {
158
+ type: "nft_transfer";
159
+ assetIdentifier?: AssetIdentifier;
160
+ sender?: string;
161
+ recipient?: string;
162
+ }
163
+ interface NftMintFilter extends TraitScope {
164
+ type: "nft_mint";
165
+ assetIdentifier?: AssetIdentifier;
166
+ recipient?: string;
167
+ }
168
+ interface NftBurnFilter extends TraitScope {
169
+ type: "nft_burn";
170
+ assetIdentifier?: AssetIdentifier;
171
+ sender?: string;
172
+ }
173
+ /** Contract event filters */
174
+ interface ContractCallFilter extends TraitScope, FactoryScope {
175
+ type: "contract_call";
176
+ /**
177
+ * One contract id, or a SET of them (max 20) — the standard DeFi shape is
178
+ * a router plus N pools, which used to cost N near-identical sources and
179
+ * handlers. Payload types are identical per `type`, so one handler serves
180
+ * the set and `event.contractId` disambiguates.
181
+ *
182
+ * Supports `*` wildcards (e.g. `"SP1ABC….pool-*"`). Mutually exclusive
183
+ * with `trait`, which resolves its own set per block.
184
+ */
185
+ contractId?: string | readonly string[];
186
+ functionName?: string;
187
+ caller?: string;
188
+ /**
189
+ * Contract ABI (pass it `as const`) used to type `event.input` — the named,
190
+ * decoded function arguments. Dev-provided; serialized into the deployed
191
+ * definition and used at runtime to decode args by name. Omit to keep
192
+ * `event.args` as a positional `unknown[]`.
193
+ *
194
+ * Required whenever `functionName` is set. `secondlayer subgraphs deploy`
195
+ * fills it in from the deployed contract for a single concrete
196
+ * `contractId`; pass it yourself for wildcard, multi-contract or trait
197
+ * sources, or a local deploy (`normalizeAbi()` from
198
+ * `@secondlayer/stacks/clarity`).
199
+ */
200
+ abi?: AbiContract2;
201
+ }
202
+ interface ContractDeployFilter {
203
+ type: "contract_deploy";
204
+ deployer?: string;
205
+ contractName?: string;
206
+ }
207
+ interface NestedContractCallFilter extends TraitScope, FactoryScope {
208
+ type: "nested_contract_call";
209
+ contractId?: string | readonly string[];
210
+ functionName?: string;
211
+ caller?: string;
212
+ sender?: string;
213
+ }
214
+ interface VarSetFilter extends TraitScope, FactoryScope {
215
+ type: "var_set";
216
+ contractId?: string | readonly string[];
217
+ varName?: string;
218
+ }
219
+ interface MapWriteFilter extends TraitScope, FactoryScope {
220
+ type: "map_set" | "map_insert" | "map_delete";
221
+ contractId?: string | readonly string[];
222
+ map?: string;
223
+ }
224
+ /**
225
+ * One declared field of a print payload.
226
+ *
227
+ * A bare {@link ColumnType} is a required scalar (`amount: "uint"`). The
228
+ * composite forms exist because real print payloads are not flat: BNS-V2
229
+ * emits `name` as a NESTED tuple, and a `prints` vocabulary that could only
230
+ * say `"jsonb"` is why a handler reading flat `data.name` type-checked,
231
+ * deployed, and then decoded to null on every event.
232
+ *
233
+ * - `{ type: F, optional: true }` — the KEY becomes optional on `event.data`
234
+ * (mirrors `always_present: false` in the inferred schema).
235
+ * - `{ tuple: { … } }` — a nested tuple, typed all the way down.
236
+ * - `{ list: F }` — a list of `F`.
237
+ */
238
+ type PrintField = ColumnType | {
239
+ type: PrintField;
240
+ optional: true;
241
+ } | {
242
+ tuple: Record<string, PrintField>;
243
+ } | {
244
+ list: PrintField;
245
+ };
246
+ /**
247
+ * Per-topic field schema for a print_event source. When declared, the handler's
248
+ * `event` is a discriminated union keyed by `topic` and `event.data` is typed
249
+ * per topic. Nested tuples, lists, and optional fields — see {@link PrintField}.
250
+ *
251
+ * Also opts the source into RUNTIME validation: a payload that does not match
252
+ * is skipped and logged rather than written as nulls.
253
+ */
254
+ type PrintEventPrints = Record<string, Record<string, PrintField>>;
255
+ type PrintEventFields = FactoryScope & {
256
+ type: "print_event";
257
+ topic?: string;
258
+ };
259
+ /**
260
+ * Pinned to one or more contract ids with no trait — `prints` is required so
261
+ * `event.data` is typed and empty-table handlers cannot typecheck.
262
+ */
263
+ type PinnedPrintEventFilter = PrintEventFields & {
264
+ contractId: string | readonly string[];
265
+ trait?: undefined;
266
+ prints: PrintEventPrints;
267
+ };
268
+ /**
269
+ * Trait-scoped print source (optionally narrowed by contractId). No single
270
+ * schema — `prints` stays optional.
271
+ */
272
+ type TraitPrintEventFilter = PrintEventFields & {
273
+ contractId?: string | readonly string[];
274
+ trait: string;
275
+ prints?: PrintEventPrints;
276
+ };
277
+ /**
278
+ * Unpinned / factory-only print source (no contractId). `prints` optional.
279
+ */
280
+ type UnpinnedPrintEventFilter = PrintEventFields & {
281
+ contractId?: undefined;
282
+ trait?: string;
283
+ prints?: PrintEventPrints;
284
+ };
285
+ /**
286
+ * Print-event filter. Pinned sources (contractId set, no trait) require
287
+ * `prints`; trait and unpinned sources may omit it.
288
+ */
289
+ type PrintEventFilter = PinnedPrintEventFilter | TraitPrintEventFilter | UnpinnedPrintEventFilter;
290
+ /** All subgraph filter types — discriminated on `type` */
291
+ type SubgraphFilter = StxTransferFilter | StxMintFilter | StxBurnFilter | StxLockFilter | FtTransferFilter | FtMintFilter | FtBurnFilter | NftTransferFilter | NftMintFilter | NftBurnFilter | ContractCallFilter | ContractDeployFilter | PrintEventFilter | NestedContractCallFilter | VarSetFilter | MapWriteFilter;
292
+ /** Transaction metadata available in handlers */
293
+ interface TxMeta {
294
+ txId: string;
295
+ sender: string;
296
+ type: string;
297
+ status: string;
298
+ contractId?: string | null;
299
+ functionName?: string | null;
300
+ }
301
+ /** Context passed to subgraph handlers during event processing */
302
+ interface SubgraphContext {
303
+ block: {
304
+ height: number;
305
+ hash: string;
306
+ timestamp: number;
307
+ burnBlockHeight: number;
308
+ /** Nakamoto StacksBlockId, when the block row carries one. */
309
+ indexBlockHash?: string | null;
310
+ };
311
+ tx: TxMeta;
312
+ /**
313
+ * Read-only contract calls pinned to this block. Cached, so the same
314
+ * (contract, function, args) is fetched once per block rather than once
315
+ * per event.
316
+ *
317
+ * Erased here on purpose: this interface is compared structurally wherever
318
+ * a subgraph definition is passed around, and carrying an ABI-generic
319
+ * method pushes those comparisons past TS's instantiation depth. Handlers
320
+ * receive `TypedSubgraphContext`, which exposes the fully typed version.
321
+ */
322
+ client: ErasedChainReadClient;
323
+ insert(table: string, row: Record<string, unknown>): void;
324
+ update(table: string, where: Record<string, unknown>, set: Record<string, unknown>): void;
325
+ upsert(table: string, key: Record<string, unknown>, row: Record<string, unknown>): void;
326
+ delete(table: string, where: Record<string, unknown>): void;
327
+ /**
328
+ * Atomic counter update — the blessed accumulator primitive. Applies
329
+ * `col = COALESCE(col, 0) + delta` per column (insert-or-add); deltas may
330
+ * be negative. Requires a uniqueKeys constraint matching `key`. Prefer
331
+ * this over read-modify-write handlers for running totals.
332
+ */
333
+ increment(table: string, key: Record<string, unknown>, deltas: Record<string, bigint | number>): void;
334
+ findOne(table: string, where: Record<string, unknown>): Promise<Record<string, unknown> | null>;
335
+ findMany(table: string, where: Record<string, unknown>): Promise<Record<string, unknown>[]>;
336
+ }
337
+ /** Handler function that processes events and writes to the subgraph */
338
+ type SubgraphHandler = (event: Record<string, unknown>, ctx: SubgraphContext) => Promise<void> | void;
339
+ /** Complete subgraph definition */
340
+ interface SubgraphDefinition {
341
+ /** Unique subgraph name (lowercase, alphanumeric + hyphens) */
342
+ name: string;
343
+ /** Human description */
344
+ description?: string;
345
+ /** Block height to start indexing from (default: 1) */
346
+ startBlock?: number;
347
+ /** 'concurrent' = tip-first: live at tip now, history backfills behind.
348
+ * Requires order-tolerant handlers. Default 'blocking'. */
349
+ backfillMode?: "blocking" | "concurrent";
350
+ /** Named source filters — keys become handler keys */
351
+ sources: Record<string, SubgraphFilter>;
352
+ /** Tables in this subgraph */
353
+ schema: SubgraphSchema;
354
+ /** Handler functions, one per source, keyed by source name */
355
+ handlers: Record<string, SubgraphHandler>;
356
+ }
357
+ /** Runtime identity baked into every pin: matcher, decode and flush semantics. */
358
+ declare const SUBGRAPHS_RUNTIME: string;
359
+ /**
360
+ * Globals a handler may not touch, with the reason shown to the developer.
361
+ * The deploy scan reports them; the realm replaces each with a getter that
362
+ * throws `NondeterminismError`.
363
+ */
364
+ declare const FORBIDDEN_GLOBALS: Readonly<Record<string, string>>;
365
+ /**
366
+ * Globals a handler may use: ECMAScript built-ins whose results are fully
367
+ * specified, plus deterministic host encoders. `Math` is narrowed separately
368
+ * ({@link ALLOWED_MATH}).
369
+ */
370
+ declare const ALLOWED_GLOBALS: readonly string[];
371
+ /** `Math` members whose results are exact on every engine. The rest
372
+ * (`sin`, `pow`, `log`, …) are implementation-approximated. */
373
+ declare const ALLOWED_MATH: readonly string[];
374
+ /** Methods whose output depends on the host's locale data. */
375
+ declare const LOCALE_METHODS: readonly string[];
376
+ /**
377
+ * One thing the deploy scan found in a bundled handler.
378
+ *
379
+ * - `nondeterministic`: breaks the determinism contract. Fails the deploy of
380
+ * a state subgraph; advice for everything else.
381
+ * - `needs-events`: deterministic, but reads something only re-execution proofs cover
382
+ * (`ctx.client`). Raises the level instead of failing.
383
+ */
384
+ interface HandlerFinding {
385
+ kind: "nondeterministic" | "needs-events";
386
+ /** What was used, e.g. `Date`, `Math.random`, `import()`, `ctx.client`. */
387
+ name: string;
388
+ reason: string;
389
+ /** Original source file when a sourcemap was available, else the bundle. */
390
+ file: string;
391
+ /** 1-based. */
392
+ line: number;
393
+ /** 1-based. */
394
+ column: number;
395
+ }
396
+ declare function formatFinding(f: HandlerFinding): string;
397
+ /**
398
+ * Derive how far a subgraph's rows can be checked. Level = the highest any
399
+ * source or handler read needs. `reasons` lists everything standing between
400
+ * the subgraph and checkable rows; a `state` subgraph with any reason is
401
+ * refused at deploy, so every stored `state` row has none. Never configured,
402
+ * never a flag.
403
+ */
404
+ declare function deriveVerification(def: Pick<SubgraphDefinition, "sources" | "backfillMode">, findings?: readonly HandlerFinding[]): SubgraphVerification;
405
+ /**
406
+ * True when the deploy must be refused: the sources are provable (`state`), so
407
+ * the determinism contract is enforced, and the scan broke it.
408
+ */
409
+ declare function isDeterminismViolation(v: SubgraphVerification): boolean;
410
+ interface PinInput {
411
+ /** Existing `schema_hash` (name + schema + sources). */
412
+ schemaHash: string;
413
+ /** Bundled ESM exactly as stored in `subgraphs.handler_code`. */
414
+ handlerCode: string;
415
+ startBlock?: number | null;
416
+ network: string;
417
+ /** Defaults to {@link SUBGRAPHS_RUNTIME}. */
418
+ runtime?: string;
419
+ }
420
+ /** What a pin commits to, as stored in `pin_preimage`. */
421
+ interface PinFields {
422
+ /** sha256 of the bundled handler, hex. */
423
+ handlerHash: string;
424
+ network: string;
425
+ runtime: string;
426
+ schemaHash: string;
427
+ startBlock: number | null;
428
+ }
429
+ /**
430
+ * The canonical JSON a pin is the sha256 of: fixed key order, primitives
431
+ * only. Stored beside the pin so anyone can recompute it from the served
432
+ * handler code.
433
+ */
434
+ declare function pinPreimage(input: PinInput): string;
435
+ /**
436
+ * Content hash over everything that shapes a subgraph's rows. Unlike
437
+ * `schema_hash`, it changes with the handler, `startBlock` and runtime, so
438
+ * two deploys with the same pin produce the same rows from the same chain.
439
+ */
440
+ declare function computePin(input: PinInput): string;
441
+ export { ALLOWED_GLOBALS, ALLOWED_MATH, FORBIDDEN_GLOBALS, HandlerFinding, LOCALE_METHODS, PinFields, PinInput, SUBGRAPHS_RUNTIME, SubgraphVerification, computePin, deriveVerification, formatFinding, isDeterminismViolation, pinPreimage };