@secondlayer/subgraphs 3.21.0 → 3.23.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.
Files changed (42) hide show
  1. package/dist/src/index.d.ts +238 -17
  2. package/dist/src/index.js +389 -52
  3. package/dist/src/index.js.map +12 -11
  4. package/dist/src/runtime/block-processor.d.ts +111 -10
  5. package/dist/src/runtime/block-processor.js +263 -31
  6. package/dist/src/runtime/block-processor.js.map +9 -8
  7. package/dist/src/runtime/catchup.d.ts +111 -10
  8. package/dist/src/runtime/catchup.js +263 -31
  9. package/dist/src/runtime/catchup.js.map +9 -8
  10. package/dist/src/runtime/context.d.ts +111 -11
  11. package/dist/src/runtime/context.js +166 -12
  12. package/dist/src/runtime/context.js.map +7 -6
  13. package/dist/src/runtime/processor.js +272 -35
  14. package/dist/src/runtime/processor.js.map +10 -9
  15. package/dist/src/runtime/reindex.d.ts +111 -10
  16. package/dist/src/runtime/reindex.js +263 -31
  17. package/dist/src/runtime/reindex.js.map +9 -8
  18. package/dist/src/runtime/reorg.d.ts +111 -10
  19. package/dist/src/runtime/reorg.js +268 -31
  20. package/dist/src/runtime/reorg.js.map +10 -9
  21. package/dist/src/runtime/replay.d.ts +3 -3
  22. package/dist/src/runtime/replay.js +30 -6
  23. package/dist/src/runtime/replay.js.map +4 -4
  24. package/dist/src/runtime/runner.d.ts +191 -17
  25. package/dist/src/runtime/source-matcher.d.ts +68 -8
  26. package/dist/src/runtime/source-matcher.js +30 -6
  27. package/dist/src/runtime/source-matcher.js.map +3 -3
  28. package/dist/src/runtime/stats.d.ts +3 -3
  29. package/dist/src/schema/index.d.ts +114 -13
  30. package/dist/src/schema/index.js +133 -20
  31. package/dist/src/schema/index.js.map +5 -5
  32. package/dist/src/service.js +272 -35
  33. package/dist/src/service.js.map +10 -9
  34. package/dist/src/testing/index.d.ts +667 -0
  35. package/dist/src/testing/index.js +1463 -0
  36. package/dist/src/testing/index.js.map +17 -0
  37. package/dist/src/triggers/index.d.ts +57 -6
  38. package/dist/src/types.d.ts +112 -11
  39. package/dist/src/validate.d.ts +129 -11
  40. package/dist/src/validate.js +117 -18
  41. package/dist/src/validate.js.map +3 -3
  42. package/package.json +9 -3
package/dist/src/index.js CHANGED
@@ -86,24 +86,105 @@ var VALID_FILTER_TYPES = [
86
86
  "contract_deploy",
87
87
  "print_event"
88
88
  ];
89
- var SubgraphFilterSchema = z.object({
90
- type: z.enum(VALID_FILTER_TYPES),
91
- sender: z.string().optional(),
92
- recipient: z.string().optional(),
89
+ var contractIdField = z.union([
90
+ z.string(),
91
+ z.array(z.string()).min(1).max(20)
92
+ ]);
93
+ var traitScope = { trait: z.string().optional() };
94
+ var factoryScope = {
95
+ factory: z.object({ from: z.string().min(1), field: z.string().min(1) }).strict().optional()
96
+ };
97
+ var amountRange = {
93
98
  minAmount: z.bigint().optional(),
94
- maxAmount: z.bigint().optional(),
95
- assetIdentifier: z.string().optional(),
96
- contractId: z.string().optional(),
97
- functionName: z.string().optional(),
98
- caller: z.string().optional(),
99
- deployer: z.string().optional(),
100
- contractName: z.string().optional(),
101
- topic: z.string().optional(),
102
- lockedAddress: z.string().optional(),
103
- abi: AbiContractSchema.optional(),
104
- trait: z.string().optional(),
105
- prints: z.record(z.string(), z.record(z.string(), PrintFieldSchema)).optional()
106
- }).strict();
99
+ maxAmount: z.bigint().optional()
100
+ };
101
+ var SubgraphFilterUnion = z.discriminatedUnion("type", [
102
+ z.object({
103
+ type: z.literal("stx_transfer"),
104
+ sender: z.string().optional(),
105
+ recipient: z.string().optional(),
106
+ ...amountRange
107
+ }).strict(),
108
+ z.object({
109
+ type: z.literal("stx_mint"),
110
+ recipient: z.string().optional(),
111
+ minAmount: z.bigint().optional()
112
+ }).strict(),
113
+ z.object({
114
+ type: z.literal("stx_burn"),
115
+ sender: z.string().optional(),
116
+ minAmount: z.bigint().optional()
117
+ }).strict(),
118
+ z.object({
119
+ type: z.literal("stx_lock"),
120
+ lockedAddress: z.string().optional(),
121
+ minAmount: z.bigint().optional()
122
+ }).strict(),
123
+ z.object({
124
+ type: z.literal("ft_transfer"),
125
+ assetIdentifier: z.string().optional(),
126
+ sender: z.string().optional(),
127
+ recipient: z.string().optional(),
128
+ minAmount: z.bigint().optional(),
129
+ ...traitScope
130
+ }).strict(),
131
+ z.object({
132
+ type: z.literal("ft_mint"),
133
+ assetIdentifier: z.string().optional(),
134
+ recipient: z.string().optional(),
135
+ minAmount: z.bigint().optional(),
136
+ ...traitScope
137
+ }).strict(),
138
+ z.object({
139
+ type: z.literal("ft_burn"),
140
+ assetIdentifier: z.string().optional(),
141
+ sender: z.string().optional(),
142
+ minAmount: z.bigint().optional(),
143
+ ...traitScope
144
+ }).strict(),
145
+ z.object({
146
+ type: z.literal("nft_transfer"),
147
+ assetIdentifier: z.string().optional(),
148
+ sender: z.string().optional(),
149
+ recipient: z.string().optional(),
150
+ ...traitScope
151
+ }).strict(),
152
+ z.object({
153
+ type: z.literal("nft_mint"),
154
+ assetIdentifier: z.string().optional(),
155
+ recipient: z.string().optional(),
156
+ ...traitScope
157
+ }).strict(),
158
+ z.object({
159
+ type: z.literal("nft_burn"),
160
+ assetIdentifier: z.string().optional(),
161
+ sender: z.string().optional(),
162
+ ...traitScope
163
+ }).strict(),
164
+ z.object({
165
+ type: z.literal("contract_call"),
166
+ contractId: contractIdField.optional(),
167
+ functionName: z.string().optional(),
168
+ caller: z.string().optional(),
169
+ abi: AbiContractSchema.optional(),
170
+ ...traitScope,
171
+ ...factoryScope
172
+ }).strict(),
173
+ z.object({
174
+ type: z.literal("contract_deploy"),
175
+ deployer: z.string().optional(),
176
+ contractName: z.string().optional()
177
+ }).strict(),
178
+ z.object({
179
+ type: z.literal("print_event"),
180
+ contractId: contractIdField.optional(),
181
+ topic: z.string().optional(),
182
+ prints: z.record(z.string(), z.record(z.string(), PrintFieldSchema)).optional(),
183
+ ...traitScope,
184
+ ...factoryScope
185
+ }).strict()
186
+ ]);
187
+ var SubgraphFilterSchema = SubgraphFilterUnion;
107
188
  var SubgraphDefinitionSchema = z.object({
108
189
  name: SubgraphNameSchema,
109
190
  version: z.string().optional(),
@@ -117,6 +198,23 @@ var SubgraphDefinitionSchema = z.object({
117
198
  function validateSubgraphDefinition(def) {
118
199
  return SubgraphDefinitionSchema.parse(def);
119
200
  }
201
+ function filterFieldsByType() {
202
+ const options = SubgraphFilterUnion?._def?.options ?? [];
203
+ const out = [];
204
+ for (const option of options) {
205
+ const shape = option?.shape ?? option?._def?.shape;
206
+ if (!shape)
207
+ continue;
208
+ const literal = shape.type?._def?.values?.[0] ?? shape.type?._def?.value ?? undefined;
209
+ if (typeof literal !== "string")
210
+ continue;
211
+ out.push({
212
+ type: literal,
213
+ fields: Object.keys(shape).filter((k) => k !== "type")
214
+ });
215
+ }
216
+ return out;
217
+ }
120
218
 
121
219
  // src/runtime/context.ts
122
220
  import { logger } from "@secondlayer/shared/logger";
@@ -217,6 +315,18 @@ function emitJournalDDL(schemaName) {
217
315
  `CREATE INDEX IF NOT EXISTS idx_${schemaName}_journal_height ON ${schemaName}._journal (block_height)`
218
316
  ];
219
317
  }
318
+ function emitFactoryDDL(schemaName) {
319
+ return [
320
+ `CREATE TABLE IF NOT EXISTS ${schemaName}._factory_addresses (
321
+ source_name TEXT NOT NULL,
322
+ address TEXT NOT NULL,
323
+ block_height BIGINT NOT NULL,
324
+ _created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
325
+ PRIMARY KEY (source_name, address)
326
+ )`,
327
+ `CREATE INDEX IF NOT EXISTS idx_${schemaName}_factory_height ON ${schemaName}._factory_addresses (block_height)`
328
+ ];
329
+ }
220
330
  function emitForeignKeyDDL(schemaName, tableName, tableDef) {
221
331
  return (tableDef.relations ?? []).map((rel) => {
222
332
  const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
@@ -235,6 +345,9 @@ function generateSubgraphSQL(def, schemaNameOverride) {
235
345
  statements.push(...emitTableDDL(schemaName, tableName, tableDef));
236
346
  }
237
347
  statements.push(...emitJournalDDL(schemaName));
348
+ if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
349
+ statements.push(...emitFactoryDDL(schemaName));
350
+ }
238
351
  for (const [tableName, tableDef] of Object.entries(def.schema)) {
239
352
  statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
240
353
  }
@@ -247,6 +360,139 @@ function generateSubgraphSQL(def, schemaNameOverride) {
247
360
  return { statements, hash };
248
361
  }
249
362
 
363
+ // src/runtime/chain-read.ts
364
+ import { getSourceDb } from "@secondlayer/shared/db";
365
+ import { http, createPublicClient } from "@secondlayer/stacks";
366
+ import {
367
+ ContractResponseError,
368
+ buildFunctionArgs,
369
+ isResponseOutput,
370
+ readContract
371
+ } from "@secondlayer/stacks/actions";
372
+ import {
373
+ clarityValueToJSUntyped,
374
+ deserializeCV,
375
+ normalizeAbi,
376
+ serializeCV,
377
+ toCamelCase
378
+ } from "@secondlayer/stacks/clarity";
379
+ function readContractAt(ctx, contractId, abi, options) {
380
+ return ctx.client.contract(contractId, abi, options);
381
+ }
382
+
383
+ class ChainReadError extends Error {
384
+ name = "ChainReadError";
385
+ }
386
+ function nodeClient(rpcUrl) {
387
+ return createPublicClient({
388
+ transport: http(rpcUrl, { retryCount: 0, timeout: 1e4 })
389
+ });
390
+ }
391
+
392
+ class ConcurrencyGate {
393
+ limit;
394
+ active = 0;
395
+ queue = [];
396
+ constructor(limit) {
397
+ this.limit = limit;
398
+ }
399
+ async run(fn) {
400
+ if (this.active >= this.limit) {
401
+ await new Promise((resolve) => this.queue.push(resolve));
402
+ }
403
+ this.active++;
404
+ try {
405
+ return await fn();
406
+ } finally {
407
+ this.active--;
408
+ this.queue.shift()?.();
409
+ }
410
+ }
411
+ }
412
+ var DEFAULT_MAX_CONCURRENT_READS = 4;
413
+ function maxConcurrentReads() {
414
+ const raw = process.env.SUBGRAPH_CHAIN_READ_CONCURRENCY;
415
+ const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
416
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT_READS;
417
+ }
418
+ var gate = new ConcurrencyGate(maxConcurrentReads());
419
+ function hashArgs(args) {
420
+ const serialized = args.map((arg) => serializeCV(arg)).join(",");
421
+ return Bun.hash(serialized).toString(16);
422
+ }
423
+ function findFunction(abi, name) {
424
+ const fn = abi.functions.find((f) => f.name === name);
425
+ if (!fn) {
426
+ throw new ChainReadError(`No function "${name}" in the supplied ABI.`);
427
+ }
428
+ if (fn.access !== "read-only") {
429
+ throw new ChainReadError(`"${name}" is ${fn.access}, not read-only — a handler cannot call it.`);
430
+ }
431
+ return fn;
432
+ }
433
+ function createChainReadClient(ctx) {
434
+ const readValue = async (contractId, abi, functionName, args, options) => {
435
+ const rpcUrl = ctx.rpcUrl ?? process.env.STACKS_NODE_RPC_URL;
436
+ if (!rpcUrl) {
437
+ throw new ChainReadError("ctx.client needs a Stacks node: set STACKS_NODE_RPC_URL on the subgraph processor.");
438
+ }
439
+ const constant = options.cache === "contract-constant";
440
+ if (!constant && !ctx.indexBlockHash) {
441
+ throw new ChainReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a read cannot be pinned to it. Reading at the node's tip would make this handler non-deterministic (the same reindex would produce different rows). Re-ingest the block, or pass cache: "contract-constant" if the value genuinely cannot change.`);
442
+ }
443
+ const fn = findFunction(abi, functionName);
444
+ const clarityArgs = buildFunctionArgs(fn, args);
445
+ const db = ctx.db ?? getSourceDb();
446
+ const argsHash = hashArgs(clarityArgs);
447
+ const pinnedTo = constant ? null : ctx.indexBlockHash;
448
+ const cached = await db.selectFrom("chain_read_cache").select("result_hex").where("contract_id", "=", contractId).where("function_name", "=", fn.name).where("args_hash", "=", argsHash).where((eb) => pinnedTo === null ? eb("index_block_hash", "is", null) : eb("index_block_hash", "=", pinnedTo)).executeTakeFirst();
449
+ const resultHex = cached?.result_hex ?? await gate.run(async () => {
450
+ const value2 = await readContract(nodeClient(rpcUrl), {
451
+ contract: contractId,
452
+ functionName: fn.name,
453
+ args: clarityArgs,
454
+ tip: ctx.indexBlockHash ?? undefined
455
+ });
456
+ return serializeCV(value2);
457
+ });
458
+ if (!cached) {
459
+ await db.insertInto("chain_read_cache").values({
460
+ contract_id: contractId,
461
+ function_name: fn.name,
462
+ args_hash: argsHash,
463
+ index_block_hash: pinnedTo,
464
+ block_height: constant ? null : ctx.blockHeight,
465
+ result_hex: resultHex
466
+ }).onConflict((oc) => oc.doNothing()).execute();
467
+ }
468
+ const value = deserializeCV(resultHex);
469
+ const js = clarityValueToJSUntyped(fn.outputs, value);
470
+ if (isResponseOutput(fn.outputs)) {
471
+ const response = js;
472
+ if ("ok" in response)
473
+ return response.ok;
474
+ throw new ContractResponseError(`${contractId}::${fn.name} returned an err response.`, response.err);
475
+ }
476
+ return js;
477
+ };
478
+ return {
479
+ contract(contractId, rawAbi, options = {}) {
480
+ const abi = normalizeAbi(rawAbi);
481
+ const camelToKebab = new Map;
482
+ for (const fn of abi.functions) {
483
+ camelToKebab.set(toCamelCase(fn.name), fn.name);
484
+ }
485
+ const read = new Proxy({}, {
486
+ get(_target, prop) {
487
+ const fnName = camelToKebab.get(prop) ?? prop;
488
+ return (args = {}) => readValue(contractId, abi, fnName, args, options);
489
+ }
490
+ });
491
+ return { read };
492
+ }
493
+ };
494
+ }
495
+
250
496
  // src/runtime/context.ts
251
497
  var JOURNAL_RETENTION_BLOCKS = 300;
252
498
  var journalEnsured = new Set;
@@ -265,6 +511,7 @@ class SubgraphContext {
265
511
  ops = [];
266
512
  byo;
267
513
  journal;
514
+ _client;
268
515
  constructor(db, pgSchemaName2, subgraphSchema, block, tx, byo = false, journal = false) {
269
516
  this.db = db;
270
517
  this.pgSchemaName = pgSchemaName2;
@@ -274,6 +521,13 @@ class SubgraphContext {
274
521
  this.byo = byo;
275
522
  this.journal = journal;
276
523
  }
524
+ get client() {
525
+ this._client ??= createChainReadClient({
526
+ blockHeight: this.block.height,
527
+ indexBlockHash: this.block.indexBlockHash
528
+ });
529
+ return this._client;
530
+ }
277
531
  get tx() {
278
532
  return this._tx;
279
533
  }
@@ -379,23 +633,21 @@ class SubgraphContext {
379
633
  formatUnits(value, decimals) {
380
634
  return formatUnits(value, decimals);
381
635
  }
382
- async findOne(table, where) {
383
- this.validateTable(table);
636
+ async readRows(table, where, limit) {
384
637
  const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
385
638
  const { clause } = buildWhereClause(where);
386
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} LIMIT 1`;
639
+ const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
387
640
  const { rows } = await sql.raw(query).execute(this.db);
388
- const row = rows[0] ?? null;
389
- return this.overlayOne(table, where, row ? this.coerceRow(table, row) : null);
641
+ return rows.map((r) => this.coerceRow(table, r));
642
+ }
643
+ async findOne(table, where) {
644
+ this.validateTable(table);
645
+ const row = (await this.readRows(table, where, 1))[0] ?? null;
646
+ return this.overlayOne(table, where, row);
390
647
  }
391
648
  async findMany(table, where) {
392
649
  this.validateTable(table);
393
- const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
394
- const { clause } = buildWhereClause(where);
395
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}`;
396
- const { rows } = await sql.raw(query).execute(this.db);
397
- const dbRows = rows.map((r) => this.coerceRow(table, r));
398
- return this.overlayMany(table, where, dbRows);
650
+ return this.overlayMany(table, where, await this.readRows(table, where));
399
651
  }
400
652
  overlayOne(table, where, dbRow) {
401
653
  if (this.ops.length === 0)
@@ -837,11 +1089,11 @@ function buildWhereClause(where) {
837
1089
  }
838
1090
 
839
1091
  // src/runtime/clarity.ts
840
- import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
1092
+ import { cvToValue, deserializeCV as deserializeCV2 } from "@secondlayer/stacks/clarity";
841
1093
  function decodeClarityValue(hex) {
842
1094
  try {
843
1095
  const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
844
- const cv = deserializeCV(cleanHex);
1096
+ const cv = deserializeCV2(cleanHex);
845
1097
  return cvToValue(cv);
846
1098
  } catch {
847
1099
  return hex;
@@ -872,8 +1124,8 @@ import { getErrorMessage } from "@secondlayer/shared";
872
1124
  import { logger as logger2 } from "@secondlayer/shared/logger";
873
1125
  import {
874
1126
  clarityValueToJS,
875
- deserializeCV as deserializeCV2,
876
- toCamelCase
1127
+ deserializeCV as deserializeCV3,
1128
+ toCamelCase as toCamelCase2
877
1129
  } from "@secondlayer/stacks/clarity";
878
1130
 
879
1131
  // src/runtime/print-validate.ts
@@ -1036,7 +1288,7 @@ function buildContractCallInput(filter, tx) {
1036
1288
  return;
1037
1289
  try {
1038
1290
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1039
- input[toCamelCase(arg.name)] = decodeArg(arg.type, deserializeCV2(clean));
1291
+ input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV3(clean));
1040
1292
  } catch {}
1041
1293
  });
1042
1294
  return input;
@@ -1315,6 +1567,9 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1315
1567
 
1316
1568
  // src/runtime/source-matcher.ts
1317
1569
  var patternCache = new Map;
1570
+ function matchAny(value, pattern) {
1571
+ return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
1572
+ }
1318
1573
  function matchPattern(value, pattern) {
1319
1574
  if (!pattern.includes("*"))
1320
1575
  return value === pattern;
@@ -1327,6 +1582,22 @@ function matchPattern(value, pattern) {
1327
1582
  return re.test(value);
1328
1583
  }
1329
1584
  var EMPTY_SET = new Set;
1585
+ function factoryAllows(filter, contractId, factoryContracts) {
1586
+ if (!filter.factory)
1587
+ return true;
1588
+ if (!contractId)
1589
+ return false;
1590
+ return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
1591
+ }
1592
+ function readPath(payload, path) {
1593
+ let cur = payload;
1594
+ for (const key of path.split(".")) {
1595
+ if (cur === null || typeof cur !== "object")
1596
+ return;
1597
+ cur = cur[key];
1598
+ }
1599
+ return cur;
1600
+ }
1330
1601
  function traitAllows(filter, contractId, traitContracts) {
1331
1602
  const trait = filter.trait;
1332
1603
  if (!trait)
@@ -1338,7 +1609,7 @@ function traitAllows(filter, contractId, traitContracts) {
1338
1609
  function assetContract(assetId) {
1339
1610
  return assetId?.split("::")[0];
1340
1611
  }
1341
- function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1612
+ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
1342
1613
  const results = [];
1343
1614
  switch (filter.type) {
1344
1615
  case "stx_transfer":
@@ -1465,9 +1736,11 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1465
1736
  if (tx.type !== "contract_call")
1466
1737
  continue;
1467
1738
  if (filter.contractId) {
1468
- if (!tx.contract_id || !matchPattern(tx.contract_id, filter.contractId))
1739
+ if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
1469
1740
  continue;
1470
1741
  }
1742
+ if (!factoryAllows(filter, tx.contract_id, factoryContracts))
1743
+ continue;
1471
1744
  if (filter.functionName) {
1472
1745
  if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
1473
1746
  continue;
@@ -1514,11 +1787,13 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1514
1787
  return false;
1515
1788
  const printContractId = data.contract_identifier ?? data.contract_id;
1516
1789
  if (filter.contractId) {
1517
- if (!printContractId || !matchPattern(printContractId, filter.contractId))
1790
+ if (!printContractId || !matchAny(printContractId, filter.contractId))
1518
1791
  return false;
1519
1792
  }
1520
1793
  if (!traitAllows(filter, printContractId, traitContracts))
1521
1794
  return false;
1795
+ if (!factoryAllows(filter, printContractId, factoryContracts))
1796
+ return false;
1522
1797
  return true;
1523
1798
  });
1524
1799
  if (matched.length > 0) {
@@ -1530,7 +1805,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1530
1805
  }
1531
1806
  return results;
1532
1807
  }
1533
- function matchSources(sources, transactions, events, traitContracts = new Map) {
1808
+ function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
1534
1809
  const eventsByTx = new Map;
1535
1810
  for (const event of events) {
1536
1811
  const list = eventsByTx.get(event.tx_id) ?? [];
@@ -1540,7 +1815,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
1540
1815
  const seen = new Set;
1541
1816
  const results = [];
1542
1817
  for (const [sourceName, filter] of Object.entries(sources)) {
1543
- const matches = matchFilter(filter, transactions, eventsByTx, traitContracts);
1818
+ const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
1544
1819
  for (const match of matches) {
1545
1820
  const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
1546
1821
  if (!seen.has(dedupeKey)) {
@@ -1567,7 +1842,7 @@ import { logger as logger5 } from "@secondlayer/shared/logger";
1567
1842
  import { sql as sql3 } from "kysely";
1568
1843
 
1569
1844
  // src/runtime/block-source.ts
1570
- import { getSourceDb } from "@secondlayer/shared/db";
1845
+ import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
1571
1846
  import {
1572
1847
  IndexHttpClient
1573
1848
  } from "@secondlayer/shared/index-http";
@@ -1628,6 +1903,7 @@ function reconstructBlock(b) {
1628
1903
  parent_hash: b.parent_hash,
1629
1904
  burn_block_height: b.burn_block_height,
1630
1905
  burn_block_hash: b.burn_block_hash,
1906
+ index_block_hash: b.index_block_hash ?? null,
1631
1907
  timestamp: isoToUnixSeconds(b.block_time),
1632
1908
  canonical: true,
1633
1909
  created_at: new Date(0)
@@ -1762,11 +2038,11 @@ function sparseProbeTargets(subgraph) {
1762
2038
 
1763
2039
  class PostgresBlockSource {
1764
2040
  async getTip() {
1765
- const progress = await getSourceDb().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
2041
+ const progress = await getSourceDb2().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
1766
2042
  return progress ? Number(progress.highest_seen_block) : 0;
1767
2043
  }
1768
2044
  loadBlockRange(fromHeight, toHeight) {
1769
- return loadBlockRange(getSourceDb(), fromHeight, toHeight);
2045
+ return loadBlockRange(getSourceDb2(), fromHeight, toHeight);
1770
2046
  }
1771
2047
  }
1772
2048
  var EVENT_FILTER_TO_INDEX_TYPE = {
@@ -1835,8 +2111,8 @@ class PublicApiBlockSource {
1835
2111
  eventTypes;
1836
2112
  probeTargets;
1837
2113
  needsTransactions;
1838
- constructor(http, eventTypes, probeTargets, needsTransactions = true) {
1839
- this.http = http;
2114
+ constructor(http2, eventTypes, probeTargets, needsTransactions = true) {
2115
+ this.http = http2;
1840
2116
  this.eventTypes = eventTypes;
1841
2117
  this.probeTargets = probeTargets;
1842
2118
  this.needsTransactions = needsTransactions;
@@ -2195,6 +2471,57 @@ async function resolveRoute(subgraphName, targetDb) {
2195
2471
  function invalidateSubgraphRoute(subgraphName) {
2196
2472
  routeCache.delete(subgraphName);
2197
2473
  }
2474
+ async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, txs, evts) {
2475
+ const factories = new Map;
2476
+ for (const source of Object.values(subgraph.sources)) {
2477
+ const factory = source.factory;
2478
+ if (factory)
2479
+ factories.set(factory.from, factory);
2480
+ }
2481
+ const resolved = new Map;
2482
+ const discovered = [];
2483
+ if (factories.size === 0)
2484
+ return { resolved, discovered };
2485
+ for (const [discoveringSource, factory] of factories) {
2486
+ const known = new Set;
2487
+ try {
2488
+ const rows = await sql3`
2489
+ SELECT address FROM ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
2490
+ WHERE source_name = ${discoveringSource} AND block_height <= ${blockHeight}
2491
+ `.execute(db);
2492
+ for (const row of rows.rows)
2493
+ known.add(row.address);
2494
+ } catch {}
2495
+ const discovering = subgraph.sources[factory.from];
2496
+ if (discovering) {
2497
+ const matches = matchSources({ [factory.from]: discovering }, txs, evts, new Map, new Map);
2498
+ for (const match of matches) {
2499
+ for (const event of match.events ?? []) {
2500
+ const payload = buildEventPayload(discovering, match.tx, event);
2501
+ const value = readPath(payload, factory.field);
2502
+ if (typeof value === "string" && value.length > 0 && !known.has(value)) {
2503
+ known.add(value);
2504
+ discovered.push({ sourceName: discoveringSource, address: value });
2505
+ }
2506
+ }
2507
+ }
2508
+ }
2509
+ resolved.set(discoveringSource, known);
2510
+ }
2511
+ return { resolved, discovered };
2512
+ }
2513
+ async function persistFactoryDiscoveries(schemaName, db, blockHeight, discovered) {
2514
+ if (discovered.length === 0)
2515
+ return;
2516
+ for (const { sourceName, address } of discovered) {
2517
+ await sql3`
2518
+ INSERT INTO ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
2519
+ (source_name, address, block_height)
2520
+ VALUES (${sourceName}, ${address}, ${blockHeight})
2521
+ ON CONFLICT (source_name, address) DO NOTHING
2522
+ `.execute(db);
2523
+ }
2524
+ }
2198
2525
  async function resolveTraitContracts(subgraph, blockHeight, db) {
2199
2526
  const traits = new Set;
2200
2527
  for (const source of Object.values(subgraph.sources)) {
@@ -2281,8 +2608,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2281
2608
  txs = data.txs;
2282
2609
  evts = data.events;
2283
2610
  }
2611
+ const route = await resolveRoute(subgraphName, targetDb);
2612
+ const schemaName = route.schemaName;
2284
2613
  const traitContracts = await resolveTraitContracts(subgraph, blockHeight, targetDb);
2285
- const matched = matchSources(subgraph.sources, txs, evts, traitContracts);
2614
+ const { resolved: factoryContracts, discovered } = await resolveFactoryContracts(subgraph, blockHeight, schemaName, targetDb, txs, evts);
2615
+ const matched = matchSources(subgraph.sources, txs, evts, traitContracts, factoryContracts);
2286
2616
  result.matched = matched.length;
2287
2617
  if (matched.length === 0) {
2288
2618
  if (!opts?.skipProgressUpdate) {
@@ -2290,13 +2620,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2290
2620
  }
2291
2621
  return result;
2292
2622
  }
2293
- const route = await resolveRoute(subgraphName, targetDb);
2294
- const schemaName = route.schemaName;
2295
2623
  const blockMeta = {
2296
2624
  height: block.height,
2297
2625
  hash: block.hash,
2298
2626
  timestamp: block.timestamp,
2299
- burnBlockHeight: block.burn_block_height
2627
+ burnBlockHeight: block.burn_block_height,
2628
+ indexBlockHash: block.index_block_hash
2300
2629
  };
2301
2630
  const initialTx = {
2302
2631
  txId: "",
@@ -2349,6 +2678,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2349
2678
  });
2350
2679
  result.processed = runResult.processed;
2351
2680
  result.errors = runResult.errors;
2681
+ await persistFactoryDiscoveries(schemaName, route.dataDb, blockHeight, discovered);
2352
2682
  await targetDb.transaction().execute(async (tx) => {
2353
2683
  if (manifest && manifest.count > 0) {
2354
2684
  await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
@@ -3068,9 +3398,12 @@ async function backfillSubgraph(def, opts) {
3068
3398
  function defineSubgraph(def) {
3069
3399
  return def;
3070
3400
  }
3401
+ function defineSchema(schema) {
3402
+ return schema;
3403
+ }
3071
3404
  // src/print-schema.ts
3072
3405
  import {
3073
- deserializeCV as deserializeCV3
3406
+ deserializeCV as deserializeCV4
3074
3407
  } from "@secondlayer/stacks/clarity";
3075
3408
  function camelizeDataKey(str) {
3076
3409
  return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
@@ -3338,7 +3671,7 @@ function inferPrintTopics(samples) {
3338
3671
  let decoded = 0;
3339
3672
  for (const p of picked) {
3340
3673
  try {
3341
- const cv = deserializeCV3(p.rawHex);
3674
+ const cv = deserializeCV4(p.rawHex);
3342
3675
  decoded++;
3343
3676
  if (cv.type === "tuple")
3344
3677
  tuples.push(cv);
@@ -3987,8 +4320,8 @@ function diffKeyLists(existing, incoming) {
3987
4320
  const existingSet = new Set((existing ?? []).map(key2));
3988
4321
  const incomingSet = new Set((incoming ?? []).map(key2));
3989
4322
  return {
3990
- added: (incoming ?? []).filter((cols) => !existingSet.has(key2(cols))),
3991
- removed: (existing ?? []).filter((cols) => !incomingSet.has(key2(cols)))
4323
+ added: (incoming ?? []).filter((cols) => !existingSet.has(key2(cols))).map((cols) => [...cols]),
4324
+ removed: (existing ?? []).filter((cols) => !incomingSet.has(key2(cols))).map((cols) => [...cols])
3992
4325
  };
3993
4326
  }
3994
4327
  function diffSchema(existing, incoming) {
@@ -4337,6 +4670,7 @@ export {
4337
4670
  resumeReindex,
4338
4671
  renderDeployPlan,
4339
4672
  reindexSubgraph,
4673
+ readContractAt,
4340
4674
  pgSchemaName,
4341
4675
  inferPrintTopics,
4342
4676
  hasBreakingChanges,
@@ -4345,15 +4679,18 @@ export {
4345
4679
  generateKyselySchema,
4346
4680
  generateIndexSchema,
4347
4681
  generateDrizzleSchema,
4682
+ filterFieldsByType,
4348
4683
  diffSchema,
4349
4684
  deploySchema,
4350
4685
  defineSubgraph,
4686
+ defineSchema,
4351
4687
  canSparseScan,
4352
4688
  camelizeDataKey,
4353
4689
  backfillSubgraph,
4354
4690
  INDEX_CODEGEN_TABLES,
4691
+ ChainReadError,
4355
4692
  ByoBreakingChangeError
4356
4693
  };
4357
4694
 
4358
- //# debugId=1014943AF0D5A26B64756E2164756E21
4695
+ //# debugId=A7D0F6BE4A80734464756E2164756E21
4359
4696
  //# sourceMappingURL=index.js.map