@secondlayer/subgraphs 3.0.0 → 3.2.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.
@@ -1,3 +1,4 @@
1
+ import { AbiContract } from "@secondlayer/stacks/clarity";
1
2
  /** Supported column types for subgraph schemas */
2
3
  type ColumnType = "text" | "uint" | "int" | "principal" | "boolean" | "timestamp" | "jsonb";
3
4
  /** Column definition in a subgraph table */
@@ -125,8 +126,13 @@ interface ContractCallFilter {
125
126
  contractId?: string;
126
127
  functionName?: string;
127
128
  caller?: string;
128
- /** ABI for typed event.args. If omitted, auto-fetched at deploy time. */
129
- abi?: Record<string, unknown>;
129
+ /**
130
+ * Contract ABI (pass it `as const`) used to type `event.input` — the named,
131
+ * decoded function arguments. Dev-provided; serialized into the deployed
132
+ * definition and used at runtime to decode args by name. Omit to keep
133
+ * `event.args` as a positional `unknown[]`.
134
+ */
135
+ abi?: AbiContract;
130
136
  }
131
137
  interface ContractDeployFilter {
132
138
  type: "contract_deploy";
@@ -137,6 +143,14 @@ interface PrintEventFilter {
137
143
  type: "print_event";
138
144
  contractId?: string;
139
145
  topic?: string;
146
+ /**
147
+ * Optional per-topic field schema. When declared, the handler's `event` is
148
+ * a discriminated union keyed by `topic` and `event.data` is typed per topic
149
+ * (e.g. `{ "completed-deposit": { amount: "uint", sender: "principal" } }`).
150
+ * Uses the same `ColumnType` vocab as `schema`; nested fields use `"jsonb"`.
151
+ * Type-level only — not validated at runtime.
152
+ */
153
+ prints?: Record<string, Record<string, ColumnType>>;
140
154
  }
141
155
  /** All subgraph filter types — discriminated on `type` */
142
156
  type SubgraphFilter = StxTransferFilter | StxMintFilter | StxBurnFilter | StxLockFilter | FtTransferFilter | FtMintFilter | FtBurnFilter | NftTransferFilter | NftMintFilter | NftBurnFilter | ContractCallFilter | ContractDeployFilter | PrintEventFilter;
@@ -246,114 +260,7 @@ declare function backfillSubgraph(def: SubgraphDefinition, opts: {
246
260
  }): Promise<{
247
261
  processed: number
248
262
  }>;
249
- interface FtTransferPayload {
250
- sender: string;
251
- recipient: string;
252
- amount: bigint;
253
- assetIdentifier: string;
254
- tx: TxMeta;
255
- }
256
- interface FtMintPayload {
257
- recipient: string;
258
- amount: bigint;
259
- assetIdentifier: string;
260
- tx: TxMeta;
261
- }
262
- interface FtBurnPayload {
263
- sender: string;
264
- amount: bigint;
265
- assetIdentifier: string;
266
- tx: TxMeta;
267
- }
268
- interface NftTransferPayload {
269
- sender: string;
270
- recipient: string;
271
- tokenId: unknown;
272
- assetIdentifier: string;
273
- tx: TxMeta;
274
- }
275
- interface NftMintPayload {
276
- recipient: string;
277
- tokenId: unknown;
278
- assetIdentifier: string;
279
- tx: TxMeta;
280
- }
281
- interface NftBurnPayload {
282
- sender: string;
283
- tokenId: unknown;
284
- assetIdentifier: string;
285
- tx: TxMeta;
286
- }
287
- interface StxTransferPayload {
288
- sender: string;
289
- recipient: string;
290
- amount: bigint;
291
- /** Memo string, or "" when none was attached. */
292
- memo: string;
293
- tx: TxMeta;
294
- }
295
- interface StxMintPayload {
296
- recipient: string;
297
- amount: bigint;
298
- tx: TxMeta;
299
- }
300
- interface StxBurnPayload {
301
- sender: string;
302
- amount: bigint;
303
- tx: TxMeta;
304
- }
305
- interface StxLockPayload {
306
- lockedAddress: string;
307
- lockedAmount: bigint;
308
- unlockHeight: bigint;
309
- tx: TxMeta;
310
- }
311
- interface PrintEventPayload {
312
- contractId: string;
313
- /** Decoded print topic (the Clarity tuple's `topic` field), or "". */
314
- topic: string;
315
- /**
316
- * Remaining decoded tuple fields, camelCased. Empty object when the print
317
- * value isn't a tuple. Narrow per `topic` to access typed fields.
318
- */
319
- data: Record<string, unknown>;
320
- tx: TxMeta;
321
- }
322
- interface ContractDeployPayload {
323
- contractId: string;
324
- deployer: string;
325
- tx: TxMeta;
326
- }
327
- /** The event payload a handler receives for a given source filter. */
328
- type EventForFilter<F extends SubgraphFilter> = F extends {
329
- type: "print_event"
330
- } ? PrintEventPayload : F extends {
331
- type: "ft_transfer"
332
- } ? FtTransferPayload : F extends {
333
- type: "ft_mint"
334
- } ? FtMintPayload : F extends {
335
- type: "ft_burn"
336
- } ? FtBurnPayload : F extends {
337
- type: "nft_transfer"
338
- } ? NftTransferPayload : F extends {
339
- type: "nft_mint"
340
- } ? NftMintPayload : F extends {
341
- type: "nft_burn"
342
- } ? NftBurnPayload : F extends {
343
- type: "stx_transfer"
344
- } ? StxTransferPayload : F extends {
345
- type: "stx_mint"
346
- } ? StxMintPayload : F extends {
347
- type: "stx_burn"
348
- } ? StxBurnPayload : F extends {
349
- type: "stx_lock"
350
- } ? StxLockPayload : F extends {
351
- type: "contract_call"
352
- } ? ContractCallEvent : F extends {
353
- type: "contract_deploy"
354
- } ? ContractDeployPayload : never;
355
- /** Union of every event payload — the `"*"` catch-all handler receives this. */
356
- type AnyEvent = FtTransferPayload | FtMintPayload | FtBurnPayload | NftTransferPayload | NftMintPayload | NftBurnPayload | StxTransferPayload | StxMintPayload | StxBurnPayload | StxLockPayload | PrintEventPayload | ContractCallEvent | ContractDeployPayload;
263
+ import { AbiContract as AbiContract2, ExtractFunctionArgs, ExtractFunctionNames } from "@secondlayer/stacks/clarity";
357
264
  /** Maps a ColumnType string literal to its TypeScript equivalent */
358
265
  type ColumnToTS<T extends string> = T extends "uint" | "int" ? bigint : T extends "text" | "principal" | "timestamp" ? string : T extends "boolean" ? boolean : T extends "jsonb" ? Record<string, unknown> : unknown;
359
266
  /** Infer TS type for a single column definition, respecting nullable */
@@ -461,6 +368,139 @@ interface TypedSubgraphContext<S extends SubgraphSchema> {
461
368
  type InferSubgraphClient<T> = T extends {
462
369
  schema: infer S
463
370
  } ? { [K in keyof S] : S[K] extends SubgraphTable ? SubgraphTableClient<InferTableRow<S[K]>> : never } : never;
371
+ interface FtTransferPayload {
372
+ sender: string;
373
+ recipient: string;
374
+ amount: bigint;
375
+ assetIdentifier: string;
376
+ tx: TxMeta;
377
+ }
378
+ interface FtMintPayload {
379
+ recipient: string;
380
+ amount: bigint;
381
+ assetIdentifier: string;
382
+ tx: TxMeta;
383
+ }
384
+ interface FtBurnPayload {
385
+ sender: string;
386
+ amount: bigint;
387
+ assetIdentifier: string;
388
+ tx: TxMeta;
389
+ }
390
+ interface NftTransferPayload {
391
+ sender: string;
392
+ recipient: string;
393
+ tokenId: unknown;
394
+ assetIdentifier: string;
395
+ tx: TxMeta;
396
+ }
397
+ interface NftMintPayload {
398
+ recipient: string;
399
+ tokenId: unknown;
400
+ assetIdentifier: string;
401
+ tx: TxMeta;
402
+ }
403
+ interface NftBurnPayload {
404
+ sender: string;
405
+ tokenId: unknown;
406
+ assetIdentifier: string;
407
+ tx: TxMeta;
408
+ }
409
+ interface StxTransferPayload {
410
+ sender: string;
411
+ recipient: string;
412
+ amount: bigint;
413
+ /** Memo string, or "" when none was attached. */
414
+ memo: string;
415
+ tx: TxMeta;
416
+ }
417
+ interface StxMintPayload {
418
+ recipient: string;
419
+ amount: bigint;
420
+ tx: TxMeta;
421
+ }
422
+ interface StxBurnPayload {
423
+ sender: string;
424
+ amount: bigint;
425
+ tx: TxMeta;
426
+ }
427
+ interface StxLockPayload {
428
+ lockedAddress: string;
429
+ lockedAmount: bigint;
430
+ unlockHeight: bigint;
431
+ tx: TxMeta;
432
+ }
433
+ interface PrintEventPayload {
434
+ contractId: string;
435
+ /** Decoded print topic (the Clarity tuple's `topic` field), or "". */
436
+ topic: string;
437
+ /**
438
+ * Remaining decoded tuple fields, camelCased. Empty object when the print
439
+ * value isn't a tuple. Narrow per `topic` to access typed fields.
440
+ */
441
+ data: Record<string, unknown>;
442
+ tx: TxMeta;
443
+ }
444
+ interface ContractDeployPayload {
445
+ contractId: string;
446
+ deployer: string;
447
+ tx: TxMeta;
448
+ }
449
+ /**
450
+ * Contract-call payload. When the source carries a `const` `abi` and a known
451
+ * `functionName`, `event.input` is the named, typed, decoded arguments
452
+ * (camelCased, via the contract ABI). The positional `event.args` is always
453
+ * present for back-compat. Without an `abi`, the payload is {@link ContractCallEvent}.
454
+ */
455
+ type ContractCallPayload<F> = F extends {
456
+ abi: infer A extends AbiContract2
457
+ functionName: infer N extends string
458
+ } ? N extends ExtractFunctionNames<A> ? ContractCallEvent & {
459
+ input: ExtractFunctionArgs<A, N>
460
+ } : ContractCallEvent : ContractCallEvent;
461
+ /**
462
+ * Print event typed per topic. When a `print_event` source declares a `prints`
463
+ * map, the payload is a discriminated union keyed by `topic` with `data` typed
464
+ * per topic; otherwise it falls back to the untyped {@link PrintEventPayload}.
465
+ */
466
+ type PrintEventFor<F> = F extends {
467
+ prints: infer P extends Record<string, Record<string, ColumnType>>
468
+ } ? { [K in keyof P] : {
469
+ contractId: string
470
+ topic: K
471
+ data: { [Field in keyof P[K]] : ColumnToTS<P[K][Field]> }
472
+ tx: TxMeta
473
+ } }[keyof P] : PrintEventPayload;
474
+ /** The event payload a handler receives for a given source filter. */
475
+ type EventForFilter<F extends SubgraphFilter> = F extends {
476
+ type: "print_event"
477
+ } ? PrintEventFor<F> : F extends {
478
+ type: "ft_transfer"
479
+ } ? FtTransferPayload : F extends {
480
+ type: "ft_mint"
481
+ } ? FtMintPayload : F extends {
482
+ type: "ft_burn"
483
+ } ? FtBurnPayload : F extends {
484
+ type: "nft_transfer"
485
+ } ? NftTransferPayload : F extends {
486
+ type: "nft_mint"
487
+ } ? NftMintPayload : F extends {
488
+ type: "nft_burn"
489
+ } ? NftBurnPayload : F extends {
490
+ type: "stx_transfer"
491
+ } ? StxTransferPayload : F extends {
492
+ type: "stx_mint"
493
+ } ? StxMintPayload : F extends {
494
+ type: "stx_burn"
495
+ } ? StxBurnPayload : F extends {
496
+ type: "stx_lock"
497
+ } ? StxLockPayload : F extends {
498
+ type: "contract_call"
499
+ } ? ContractCallPayload<F> : F extends {
500
+ type: "contract_deploy"
501
+ } ? ContractDeployPayload : never;
502
+ /** Union of every event payload — the `"*"` catch-all handler receives this. */
503
+ type AnyEvent = FtTransferPayload | FtMintPayload | FtBurnPayload | NftTransferPayload | NftMintPayload | NftBurnPayload | StxTransferPayload | StxMintPayload | StxBurnPayload | StxLockPayload | PrintEventPayload | ContractCallEvent | ContractDeployPayload;
464
504
  /**
465
505
  * Handlers keyed by source name. Each handler's `event` is typed from the
466
506
  * matching source's filter `type` (e.g. a `print_event` source → `event.topic`
@@ -567,4 +607,4 @@ declare function deploySchema(db: AnyDb, def: SubgraphDefinition, handlerPath: s
567
607
  version: string
568
608
  diff?: DeployDiff
569
609
  }>;
570
- export { validateSubgraphDefinition, resumeReindex, reindexSubgraph, pgSchemaName, generateSubgraphSQL, diffSchema, deploySchema, defineSubgraph, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, RowValue, ReindexOptions, PrintEventPayload, PrintEventFilter, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, InferTableRow, InferSubgraphClient, InferColumnType, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, ContractDeployPayload, ContractDeployFilter, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, AnyEvent };
610
+ export { validateSubgraphDefinition, resumeReindex, reindexSubgraph, pgSchemaName, generateSubgraphSQL, diffSchema, deploySchema, defineSubgraph, backfillSubgraph, WriteRow, WhereInput, TypedSubgraphDefinition, TypedSubgraphContext, TypedHandlers, TxMeta, TableDiff, SystemRow, SubgraphTableClient, SubgraphTable, SubgraphSchema, SubgraphHandler, SubgraphFilter, SubgraphDefinition, SubgraphContext, SubgraphColumn, StxTransferPayload, StxTransferFilter, StxMintPayload, StxMintFilter, StxLockPayload, StxLockFilter, StxBurnPayload, StxBurnFilter, RowValue, ReindexOptions, PrintEventPayload, PrintEventFor, PrintEventFilter, NftTransferPayload, NftTransferFilter, NftMintPayload, NftMintFilter, NftBurnPayload, NftBurnFilter, InferTableRow, InferSubgraphClient, InferColumnType, GeneratedSQL, FtTransferPayload, FtTransferFilter, FtMintPayload, FtMintFilter, FtBurnPayload, FtBurnFilter, FindManyOptions, EventForFilter, ContractDeployPayload, ContractDeployFilter, ContractCallPayload, ContractCallFilter, ContractCallEvent, ComputedValue, ComparisonFilter, ColumnType, ColumnToTS, ColumnDiff, AnyEvent };
package/dist/src/index.js CHANGED
@@ -421,6 +421,11 @@ function decodeFunctionArgs(args) {
421
421
  // src/runtime/runner.ts
422
422
  import { getErrorMessage } from "@secondlayer/shared";
423
423
  import { logger as logger2 } from "@secondlayer/shared/logger";
424
+ import {
425
+ clarityValueToJS,
426
+ deserializeCV as deserializeCV2,
427
+ toCamelCase
428
+ } from "@secondlayer/stacks/clarity";
424
429
  var DEFAULT_ERROR_THRESHOLD = 50;
425
430
  function camelCase(str) {
426
431
  return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
@@ -475,6 +480,37 @@ function safeBigInt(val) {
475
480
  }
476
481
  return 0n;
477
482
  }
483
+ function buildContractCallInput(filter, tx) {
484
+ const abi = filter.abi;
485
+ const fnName = tx.function_name;
486
+ if (!abi || !fnName)
487
+ return;
488
+ const fn = abi.functions?.find((f) => f.name === fnName);
489
+ if (!fn || !Array.isArray(fn.args))
490
+ return;
491
+ let rawArgs = tx.function_args;
492
+ if (typeof rawArgs === "string") {
493
+ try {
494
+ rawArgs = JSON.parse(rawArgs);
495
+ } catch {
496
+ return;
497
+ }
498
+ }
499
+ if (!Array.isArray(rawArgs))
500
+ return;
501
+ const decodeArg = clarityValueToJS;
502
+ const input = {};
503
+ fn.args.forEach((arg, i) => {
504
+ const hex = rawArgs[i];
505
+ if (typeof hex !== "string")
506
+ return;
507
+ try {
508
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
509
+ input[toCamelCase(arg.name)] = decodeArg(arg.type, deserializeCV2(clean));
510
+ } catch {}
511
+ });
512
+ return input;
513
+ }
478
514
  function buildEventPayload(filter, tx, event) {
479
515
  const txMeta = {
480
516
  txId: tx.tx_id,
@@ -488,17 +524,20 @@ function buildEventPayload(filter, tx, event) {
488
524
  const decodedResult = decodeRawResult(tx.raw_result);
489
525
  if (!event) {
490
526
  switch (filter.type) {
491
- case "contract_call":
527
+ case "contract_call": {
528
+ const input = buildContractCallInput(filter, tx);
492
529
  return {
493
530
  type: "contract_call",
494
531
  contractId: tx.contract_id ?? "",
495
532
  functionName: tx.function_name ?? "",
496
533
  sender: tx.sender,
497
534
  args: decodedArgs,
535
+ ...input !== undefined ? { input } : {},
498
536
  result: decodedResult,
499
537
  resultHex: tx.raw_result ?? null,
500
538
  tx: txMeta
501
539
  };
540
+ }
502
541
  case "contract_deploy":
503
542
  return {
504
543
  contractId: tx.contract_id ?? "",
@@ -596,7 +635,8 @@ function buildEventPayload(filter, tx, event) {
596
635
  tx: txMeta
597
636
  };
598
637
  }
599
- case "contract_call":
638
+ case "contract_call": {
639
+ const input = buildContractCallInput(filter, tx);
600
640
  return {
601
641
  ...decoded,
602
642
  type: "contract_call",
@@ -605,10 +645,12 @@ function buildEventPayload(filter, tx, event) {
605
645
  functionName: tx.function_name ?? "",
606
646
  sender: tx.sender,
607
647
  args: decodedArgs,
648
+ ...input !== undefined ? { input } : {},
608
649
  result: decodedResult,
609
650
  resultHex: tx.raw_result ?? null,
610
651
  tx: txMeta
611
652
  };
653
+ }
612
654
  case "contract_deploy":
613
655
  return {
614
656
  contractId: tx.contract_id ?? "",
@@ -2176,5 +2218,5 @@ export {
2176
2218
  backfillSubgraph
2177
2219
  };
2178
2220
 
2179
- //# debugId=E831E58EC98F118E64756E2164756E21
2221
+ //# debugId=E652DFA8BE73568E64756E2164756E21
2180
2222
  //# sourceMappingURL=index.js.map