@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
@@ -100,6 +100,18 @@ function emitJournalDDL(schemaName) {
100
100
  `CREATE INDEX IF NOT EXISTS idx_${schemaName}_journal_height ON ${schemaName}._journal (block_height)`
101
101
  ];
102
102
  }
103
+ function emitFactoryDDL(schemaName) {
104
+ return [
105
+ `CREATE TABLE IF NOT EXISTS ${schemaName}._factory_addresses (
106
+ source_name TEXT NOT NULL,
107
+ address TEXT NOT NULL,
108
+ block_height BIGINT NOT NULL,
109
+ _created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
110
+ PRIMARY KEY (source_name, address)
111
+ )`,
112
+ `CREATE INDEX IF NOT EXISTS idx_${schemaName}_factory_height ON ${schemaName}._factory_addresses (block_height)`
113
+ ];
114
+ }
103
115
  function emitForeignKeyDDL(schemaName, tableName, tableDef) {
104
116
  return (tableDef.relations ?? []).map((rel) => {
105
117
  const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
@@ -118,6 +130,9 @@ function generateSubgraphSQL(def, schemaNameOverride) {
118
130
  statements.push(...emitTableDDL(schemaName, tableName, tableDef));
119
131
  }
120
132
  statements.push(...emitJournalDDL(schemaName));
133
+ if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
134
+ statements.push(...emitFactoryDDL(schemaName));
135
+ }
121
136
  for (const [tableName, tableDef] of Object.entries(def.schema)) {
122
137
  statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
123
138
  }
@@ -130,6 +145,139 @@ function generateSubgraphSQL(def, schemaNameOverride) {
130
145
  return { statements, hash };
131
146
  }
132
147
 
148
+ // src/runtime/chain-read.ts
149
+ import { getSourceDb } from "@secondlayer/shared/db";
150
+ import { http, createPublicClient } from "@secondlayer/stacks";
151
+ import {
152
+ ContractResponseError,
153
+ buildFunctionArgs,
154
+ isResponseOutput,
155
+ readContract
156
+ } from "@secondlayer/stacks/actions";
157
+ import {
158
+ clarityValueToJSUntyped,
159
+ deserializeCV,
160
+ normalizeAbi,
161
+ serializeCV,
162
+ toCamelCase
163
+ } from "@secondlayer/stacks/clarity";
164
+ function readContractAt(ctx, contractId, abi, options) {
165
+ return ctx.client.contract(contractId, abi, options);
166
+ }
167
+
168
+ class ChainReadError extends Error {
169
+ name = "ChainReadError";
170
+ }
171
+ function nodeClient(rpcUrl) {
172
+ return createPublicClient({
173
+ transport: http(rpcUrl, { retryCount: 0, timeout: 1e4 })
174
+ });
175
+ }
176
+
177
+ class ConcurrencyGate {
178
+ limit;
179
+ active = 0;
180
+ queue = [];
181
+ constructor(limit) {
182
+ this.limit = limit;
183
+ }
184
+ async run(fn) {
185
+ if (this.active >= this.limit) {
186
+ await new Promise((resolve) => this.queue.push(resolve));
187
+ }
188
+ this.active++;
189
+ try {
190
+ return await fn();
191
+ } finally {
192
+ this.active--;
193
+ this.queue.shift()?.();
194
+ }
195
+ }
196
+ }
197
+ var DEFAULT_MAX_CONCURRENT_READS = 4;
198
+ function maxConcurrentReads() {
199
+ const raw = process.env.SUBGRAPH_CHAIN_READ_CONCURRENCY;
200
+ const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
201
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT_READS;
202
+ }
203
+ var gate = new ConcurrencyGate(maxConcurrentReads());
204
+ function hashArgs(args) {
205
+ const serialized = args.map((arg) => serializeCV(arg)).join(",");
206
+ return Bun.hash(serialized).toString(16);
207
+ }
208
+ function findFunction(abi, name) {
209
+ const fn = abi.functions.find((f) => f.name === name);
210
+ if (!fn) {
211
+ throw new ChainReadError(`No function "${name}" in the supplied ABI.`);
212
+ }
213
+ if (fn.access !== "read-only") {
214
+ throw new ChainReadError(`"${name}" is ${fn.access}, not read-only — a handler cannot call it.`);
215
+ }
216
+ return fn;
217
+ }
218
+ function createChainReadClient(ctx) {
219
+ const readValue = async (contractId, abi, functionName, args, options) => {
220
+ const rpcUrl = ctx.rpcUrl ?? process.env.STACKS_NODE_RPC_URL;
221
+ if (!rpcUrl) {
222
+ throw new ChainReadError("ctx.client needs a Stacks node: set STACKS_NODE_RPC_URL on the subgraph processor.");
223
+ }
224
+ const constant = options.cache === "contract-constant";
225
+ if (!constant && !ctx.indexBlockHash) {
226
+ 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.`);
227
+ }
228
+ const fn = findFunction(abi, functionName);
229
+ const clarityArgs = buildFunctionArgs(fn, args);
230
+ const db = ctx.db ?? getSourceDb();
231
+ const argsHash = hashArgs(clarityArgs);
232
+ const pinnedTo = constant ? null : ctx.indexBlockHash;
233
+ 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();
234
+ const resultHex = cached?.result_hex ?? await gate.run(async () => {
235
+ const value2 = await readContract(nodeClient(rpcUrl), {
236
+ contract: contractId,
237
+ functionName: fn.name,
238
+ args: clarityArgs,
239
+ tip: ctx.indexBlockHash ?? undefined
240
+ });
241
+ return serializeCV(value2);
242
+ });
243
+ if (!cached) {
244
+ await db.insertInto("chain_read_cache").values({
245
+ contract_id: contractId,
246
+ function_name: fn.name,
247
+ args_hash: argsHash,
248
+ index_block_hash: pinnedTo,
249
+ block_height: constant ? null : ctx.blockHeight,
250
+ result_hex: resultHex
251
+ }).onConflict((oc) => oc.doNothing()).execute();
252
+ }
253
+ const value = deserializeCV(resultHex);
254
+ const js = clarityValueToJSUntyped(fn.outputs, value);
255
+ if (isResponseOutput(fn.outputs)) {
256
+ const response = js;
257
+ if ("ok" in response)
258
+ return response.ok;
259
+ throw new ContractResponseError(`${contractId}::${fn.name} returned an err response.`, response.err);
260
+ }
261
+ return js;
262
+ };
263
+ return {
264
+ contract(contractId, rawAbi, options = {}) {
265
+ const abi = normalizeAbi(rawAbi);
266
+ const camelToKebab = new Map;
267
+ for (const fn of abi.functions) {
268
+ camelToKebab.set(toCamelCase(fn.name), fn.name);
269
+ }
270
+ const read = new Proxy({}, {
271
+ get(_target, prop) {
272
+ const fnName = camelToKebab.get(prop) ?? prop;
273
+ return (args = {}) => readValue(contractId, abi, fnName, args, options);
274
+ }
275
+ });
276
+ return { read };
277
+ }
278
+ };
279
+ }
280
+
133
281
  // src/runtime/context.ts
134
282
  var JOURNAL_RETENTION_BLOCKS = 300;
135
283
  var journalEnsured = new Set;
@@ -148,6 +296,7 @@ class SubgraphContext {
148
296
  ops = [];
149
297
  byo;
150
298
  journal;
299
+ _client;
151
300
  constructor(db, pgSchemaName2, subgraphSchema, block, tx, byo = false, journal = false) {
152
301
  this.db = db;
153
302
  this.pgSchemaName = pgSchemaName2;
@@ -157,6 +306,13 @@ class SubgraphContext {
157
306
  this.byo = byo;
158
307
  this.journal = journal;
159
308
  }
309
+ get client() {
310
+ this._client ??= createChainReadClient({
311
+ blockHeight: this.block.height,
312
+ indexBlockHash: this.block.indexBlockHash
313
+ });
314
+ return this._client;
315
+ }
160
316
  get tx() {
161
317
  return this._tx;
162
318
  }
@@ -262,23 +418,21 @@ class SubgraphContext {
262
418
  formatUnits(value, decimals) {
263
419
  return formatUnits(value, decimals);
264
420
  }
265
- async findOne(table, where) {
266
- this.validateTable(table);
421
+ async readRows(table, where, limit) {
267
422
  const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
268
423
  const { clause } = buildWhereClause(where);
269
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} LIMIT 1`;
424
+ const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
270
425
  const { rows } = await sql.raw(query).execute(this.db);
271
- const row = rows[0] ?? null;
272
- return this.overlayOne(table, where, row ? this.coerceRow(table, row) : null);
426
+ return rows.map((r) => this.coerceRow(table, r));
427
+ }
428
+ async findOne(table, where) {
429
+ this.validateTable(table);
430
+ const row = (await this.readRows(table, where, 1))[0] ?? null;
431
+ return this.overlayOne(table, where, row);
273
432
  }
274
433
  async findMany(table, where) {
275
434
  this.validateTable(table);
276
- const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
277
- const { clause } = buildWhereClause(where);
278
- const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}`;
279
- const { rows } = await sql.raw(query).execute(this.db);
280
- const dbRows = rows.map((r) => this.coerceRow(table, r));
281
- return this.overlayMany(table, where, dbRows);
435
+ return this.overlayMany(table, where, await this.readRows(table, where));
282
436
  }
283
437
  overlayOne(table, where, dbRow) {
284
438
  if (this.ops.length === 0)
@@ -720,11 +874,11 @@ function buildWhereClause(where) {
720
874
  }
721
875
 
722
876
  // src/runtime/clarity.ts
723
- import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
877
+ import { cvToValue, deserializeCV as deserializeCV2 } from "@secondlayer/stacks/clarity";
724
878
  function decodeClarityValue(hex) {
725
879
  try {
726
880
  const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
727
- const cv = deserializeCV(cleanHex);
881
+ const cv = deserializeCV2(cleanHex);
728
882
  return cvToValue(cv);
729
883
  } catch {
730
884
  return hex;
@@ -755,8 +909,8 @@ import { getErrorMessage } from "@secondlayer/shared";
755
909
  import { logger as logger2 } from "@secondlayer/shared/logger";
756
910
  import {
757
911
  clarityValueToJS,
758
- deserializeCV as deserializeCV2,
759
- toCamelCase
912
+ deserializeCV as deserializeCV3,
913
+ toCamelCase as toCamelCase2
760
914
  } from "@secondlayer/stacks/clarity";
761
915
 
762
916
  // src/runtime/print-validate.ts
@@ -919,7 +1073,7 @@ function buildContractCallInput(filter, tx) {
919
1073
  return;
920
1074
  try {
921
1075
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
922
- input[toCamelCase(arg.name)] = decodeArg(arg.type, deserializeCV2(clean));
1076
+ input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV3(clean));
923
1077
  } catch {}
924
1078
  });
925
1079
  return input;
@@ -1198,6 +1352,9 @@ async function runHandlers(subgraph, matched, ctx, opts) {
1198
1352
 
1199
1353
  // src/runtime/source-matcher.ts
1200
1354
  var patternCache = new Map;
1355
+ function matchAny(value, pattern) {
1356
+ return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
1357
+ }
1201
1358
  function matchPattern(value, pattern) {
1202
1359
  if (!pattern.includes("*"))
1203
1360
  return value === pattern;
@@ -1210,6 +1367,22 @@ function matchPattern(value, pattern) {
1210
1367
  return re.test(value);
1211
1368
  }
1212
1369
  var EMPTY_SET = new Set;
1370
+ function factoryAllows(filter, contractId, factoryContracts) {
1371
+ if (!filter.factory)
1372
+ return true;
1373
+ if (!contractId)
1374
+ return false;
1375
+ return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
1376
+ }
1377
+ function readPath(payload, path) {
1378
+ let cur = payload;
1379
+ for (const key of path.split(".")) {
1380
+ if (cur === null || typeof cur !== "object")
1381
+ return;
1382
+ cur = cur[key];
1383
+ }
1384
+ return cur;
1385
+ }
1213
1386
  function traitAllows(filter, contractId, traitContracts) {
1214
1387
  const trait = filter.trait;
1215
1388
  if (!trait)
@@ -1221,7 +1394,7 @@ function traitAllows(filter, contractId, traitContracts) {
1221
1394
  function assetContract(assetId) {
1222
1395
  return assetId?.split("::")[0];
1223
1396
  }
1224
- function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1397
+ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
1225
1398
  const results = [];
1226
1399
  switch (filter.type) {
1227
1400
  case "stx_transfer":
@@ -1348,9 +1521,11 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1348
1521
  if (tx.type !== "contract_call")
1349
1522
  continue;
1350
1523
  if (filter.contractId) {
1351
- if (!tx.contract_id || !matchPattern(tx.contract_id, filter.contractId))
1524
+ if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
1352
1525
  continue;
1353
1526
  }
1527
+ if (!factoryAllows(filter, tx.contract_id, factoryContracts))
1528
+ continue;
1354
1529
  if (filter.functionName) {
1355
1530
  if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
1356
1531
  continue;
@@ -1397,11 +1572,13 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1397
1572
  return false;
1398
1573
  const printContractId = data.contract_identifier ?? data.contract_id;
1399
1574
  if (filter.contractId) {
1400
- if (!printContractId || !matchPattern(printContractId, filter.contractId))
1575
+ if (!printContractId || !matchAny(printContractId, filter.contractId))
1401
1576
  return false;
1402
1577
  }
1403
1578
  if (!traitAllows(filter, printContractId, traitContracts))
1404
1579
  return false;
1580
+ if (!factoryAllows(filter, printContractId, factoryContracts))
1581
+ return false;
1405
1582
  return true;
1406
1583
  });
1407
1584
  if (matched.length > 0) {
@@ -1413,7 +1590,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
1413
1590
  }
1414
1591
  return results;
1415
1592
  }
1416
- function matchSources(sources, transactions, events, traitContracts = new Map) {
1593
+ function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
1417
1594
  const eventsByTx = new Map;
1418
1595
  for (const event of events) {
1419
1596
  const list = eventsByTx.get(event.tx_id) ?? [];
@@ -1423,7 +1600,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
1423
1600
  const seen = new Set;
1424
1601
  const results = [];
1425
1602
  for (const [sourceName, filter] of Object.entries(sources)) {
1426
- const matches = matchFilter(filter, transactions, eventsByTx, traitContracts);
1603
+ const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
1427
1604
  for (const match of matches) {
1428
1605
  const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
1429
1606
  if (!seen.has(dedupeKey)) {
@@ -1450,7 +1627,7 @@ import { logger as logger5 } from "@secondlayer/shared/logger";
1450
1627
  import { sql as sql3 } from "kysely";
1451
1628
 
1452
1629
  // src/runtime/block-source.ts
1453
- import { getSourceDb } from "@secondlayer/shared/db";
1630
+ import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
1454
1631
  import {
1455
1632
  IndexHttpClient
1456
1633
  } from "@secondlayer/shared/index-http";
@@ -1511,6 +1688,7 @@ function reconstructBlock(b) {
1511
1688
  parent_hash: b.parent_hash,
1512
1689
  burn_block_height: b.burn_block_height,
1513
1690
  burn_block_hash: b.burn_block_hash,
1691
+ index_block_hash: b.index_block_hash ?? null,
1514
1692
  timestamp: isoToUnixSeconds(b.block_time),
1515
1693
  canonical: true,
1516
1694
  created_at: new Date(0)
@@ -1645,11 +1823,11 @@ function sparseProbeTargets(subgraph) {
1645
1823
 
1646
1824
  class PostgresBlockSource {
1647
1825
  async getTip() {
1648
- const progress = await getSourceDb().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
1826
+ const progress = await getSourceDb2().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
1649
1827
  return progress ? Number(progress.highest_seen_block) : 0;
1650
1828
  }
1651
1829
  loadBlockRange(fromHeight, toHeight) {
1652
- return loadBlockRange(getSourceDb(), fromHeight, toHeight);
1830
+ return loadBlockRange(getSourceDb2(), fromHeight, toHeight);
1653
1831
  }
1654
1832
  }
1655
1833
  var EVENT_FILTER_TO_INDEX_TYPE = {
@@ -1718,8 +1896,8 @@ class PublicApiBlockSource {
1718
1896
  eventTypes;
1719
1897
  probeTargets;
1720
1898
  needsTransactions;
1721
- constructor(http, eventTypes, probeTargets, needsTransactions = true) {
1722
- this.http = http;
1899
+ constructor(http2, eventTypes, probeTargets, needsTransactions = true) {
1900
+ this.http = http2;
1723
1901
  this.eventTypes = eventTypes;
1724
1902
  this.probeTargets = probeTargets;
1725
1903
  this.needsTransactions = needsTransactions;
@@ -2078,6 +2256,57 @@ async function resolveRoute(subgraphName, targetDb) {
2078
2256
  function invalidateSubgraphRoute(subgraphName) {
2079
2257
  routeCache.delete(subgraphName);
2080
2258
  }
2259
+ async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, txs, evts) {
2260
+ const factories = new Map;
2261
+ for (const source of Object.values(subgraph.sources)) {
2262
+ const factory = source.factory;
2263
+ if (factory)
2264
+ factories.set(factory.from, factory);
2265
+ }
2266
+ const resolved = new Map;
2267
+ const discovered = [];
2268
+ if (factories.size === 0)
2269
+ return { resolved, discovered };
2270
+ for (const [discoveringSource, factory] of factories) {
2271
+ const known = new Set;
2272
+ try {
2273
+ const rows = await sql3`
2274
+ SELECT address FROM ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
2275
+ WHERE source_name = ${discoveringSource} AND block_height <= ${blockHeight}
2276
+ `.execute(db);
2277
+ for (const row of rows.rows)
2278
+ known.add(row.address);
2279
+ } catch {}
2280
+ const discovering = subgraph.sources[factory.from];
2281
+ if (discovering) {
2282
+ const matches = matchSources({ [factory.from]: discovering }, txs, evts, new Map, new Map);
2283
+ for (const match of matches) {
2284
+ for (const event of match.events ?? []) {
2285
+ const payload = buildEventPayload(discovering, match.tx, event);
2286
+ const value = readPath(payload, factory.field);
2287
+ if (typeof value === "string" && value.length > 0 && !known.has(value)) {
2288
+ known.add(value);
2289
+ discovered.push({ sourceName: discoveringSource, address: value });
2290
+ }
2291
+ }
2292
+ }
2293
+ }
2294
+ resolved.set(discoveringSource, known);
2295
+ }
2296
+ return { resolved, discovered };
2297
+ }
2298
+ async function persistFactoryDiscoveries(schemaName, db, blockHeight, discovered) {
2299
+ if (discovered.length === 0)
2300
+ return;
2301
+ for (const { sourceName, address } of discovered) {
2302
+ await sql3`
2303
+ INSERT INTO ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
2304
+ (source_name, address, block_height)
2305
+ VALUES (${sourceName}, ${address}, ${blockHeight})
2306
+ ON CONFLICT (source_name, address) DO NOTHING
2307
+ `.execute(db);
2308
+ }
2309
+ }
2081
2310
  async function resolveTraitContracts(subgraph, blockHeight, db) {
2082
2311
  const traits = new Set;
2083
2312
  for (const source of Object.values(subgraph.sources)) {
@@ -2164,8 +2393,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2164
2393
  txs = data.txs;
2165
2394
  evts = data.events;
2166
2395
  }
2396
+ const route = await resolveRoute(subgraphName, targetDb);
2397
+ const schemaName = route.schemaName;
2167
2398
  const traitContracts = await resolveTraitContracts(subgraph, blockHeight, targetDb);
2168
- const matched = matchSources(subgraph.sources, txs, evts, traitContracts);
2399
+ const { resolved: factoryContracts, discovered } = await resolveFactoryContracts(subgraph, blockHeight, schemaName, targetDb, txs, evts);
2400
+ const matched = matchSources(subgraph.sources, txs, evts, traitContracts, factoryContracts);
2169
2401
  result.matched = matched.length;
2170
2402
  if (matched.length === 0) {
2171
2403
  if (!opts?.skipProgressUpdate) {
@@ -2173,13 +2405,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2173
2405
  }
2174
2406
  return result;
2175
2407
  }
2176
- const route = await resolveRoute(subgraphName, targetDb);
2177
- const schemaName = route.schemaName;
2178
2408
  const blockMeta = {
2179
2409
  height: block.height,
2180
2410
  hash: block.hash,
2181
2411
  timestamp: block.timestamp,
2182
- burnBlockHeight: block.burn_block_height
2412
+ burnBlockHeight: block.burn_block_height,
2413
+ indexBlockHash: block.index_block_hash
2183
2414
  };
2184
2415
  const initialTx = {
2185
2416
  txId: "",
@@ -2232,6 +2463,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
2232
2463
  });
2233
2464
  result.processed = runResult.processed;
2234
2465
  result.errors = runResult.errors;
2466
+ await persistFactoryDiscoveries(schemaName, route.dataDb, blockHeight, discovered);
2235
2467
  await targetDb.transaction().execute(async (tx) => {
2236
2468
  if (manifest && manifest.count > 0) {
2237
2469
  await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
@@ -3230,6 +3462,11 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
3230
3462
  await client.unsafe(`DELETE FROM "${schemaName}"."${tableName}" WHERE "_block_height" >= $1`, [blockHeight]);
3231
3463
  }
3232
3464
  }
3465
+ await client.unsafe(`DO $$ BEGIN
3466
+ IF to_regclass('"${schemaName}"."_factory_addresses"') IS NOT NULL THEN
3467
+ DELETE FROM "${schemaName}"."_factory_addresses" WHERE block_height >= ${Number(blockHeight)};
3468
+ END IF;
3469
+ END $$`);
3233
3470
  for (const [tableName, rows] of Object.entries(revertedByTable)) {
3234
3471
  if (rows.length === 0)
3235
3472
  continue;
@@ -3356,8 +3593,8 @@ import { IndexHttpClient as IndexHttpClient2 } from "@secondlayer/shared/index-h
3356
3593
  import { logger as logger10 } from "@secondlayer/shared/logger";
3357
3594
  var POLL_MS = Number(process.env.SUBGRAPH_REORG_POLL_MS) || 15000;
3358
3595
  var STARTUP_MARGIN_MS = 60 * 60 * 1000;
3359
- async function pollReorgsOnce(http, cursor, onReorg, handled) {
3360
- const { reorgs, next_since } = await http.listReorgs(cursor);
3596
+ async function pollReorgsOnce(http2, cursor, onReorg, handled) {
3597
+ const { reorgs, next_since } = await http2.listReorgs(cursor);
3361
3598
  const sorted = [...reorgs].sort((a, b) => a.fork_point_height - b.fork_point_height);
3362
3599
  for (const r of sorted) {
3363
3600
  if (handled && r.id && handled.has(r.id)) {
@@ -3378,7 +3615,7 @@ async function pollReorgsOnce(http, cursor, onReorg, handled) {
3378
3615
  }
3379
3616
  function startStreamsReorgPoll(onReorg) {
3380
3617
  const baseUrl = process.env.SUBGRAPH_INDEX_API_URL ?? process.env.STREAMS_API_URL ?? "http://api:3800";
3381
- const http = new IndexHttpClient2({
3618
+ const http2 = new IndexHttpClient2({
3382
3619
  indexBaseUrl: baseUrl,
3383
3620
  streamsBaseUrl: baseUrl,
3384
3621
  streamsApiKey: process.env.STREAMS_INTERNAL_API_KEY ?? "sk-sl_streams_decode_internal"
@@ -3393,7 +3630,7 @@ function startStreamsReorgPoll(onReorg) {
3393
3630
  if (handled.size > 1e4)
3394
3631
  handled.clear();
3395
3632
  try {
3396
- since = await pollReorgsOnce(http, since, onReorg, handled);
3633
+ since = await pollReorgsOnce(http2, since, onReorg, handled);
3397
3634
  } catch (err) {
3398
3635
  logger10.error("Streams reorg poll failed", {
3399
3636
  error: getErrorMessage5(err)
@@ -3794,5 +4031,5 @@ var shutdown = async () => {
3794
4031
  process.on("SIGINT", shutdown);
3795
4032
  process.on("SIGTERM", shutdown);
3796
4033
 
3797
- //# debugId=A84F29D475298B5D64756E2164756E21
4034
+ //# debugId=12163624A2820BC464756E2164756E21
3798
4035
  //# sourceMappingURL=service.js.map