@secondlayer/subgraphs 6.4.0 → 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.
package/dist/index.js CHANGED
@@ -787,7 +787,7 @@ import { createHash } from "node:crypto";
787
787
  // package.json
788
788
  var package_default = {
789
789
  name: "@secondlayer/subgraphs",
790
- version: "6.4.0",
790
+ version: "6.5.0",
791
791
  description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
792
792
  license: "MIT",
793
793
  repository: {
@@ -831,6 +831,10 @@ var package_default = {
831
831
  "./testing": {
832
832
  types: "./dist/testing/index.d.ts",
833
833
  import: "./dist/testing/index.js"
834
+ },
835
+ "./verify": {
836
+ types: "./dist/verify/index.d.ts",
837
+ import: "./dist/verify/index.js"
834
838
  }
835
839
  },
836
840
  files: ["dist"],
@@ -842,14 +846,16 @@ var package_default = {
842
846
  prepublishOnly: "bun run build"
843
847
  },
844
848
  dependencies: {
845
- "@secondlayer/shared": "^11.22.0",
846
- "@secondlayer/stacks": "^6.1.0",
849
+ "@secondlayer/shared": "^11.23.0",
850
+ "@secondlayer/stacks": "^6.2.0",
851
+ "@secondlayer/verify": "^0.3.0",
847
852
  esbuild: "^0.28.2",
848
853
  kysely: "0.28.17",
849
854
  zod: "^4.3.6"
850
855
  },
851
856
  devDependencies: {
852
857
  "@types/bun": "latest",
858
+ bunup: "0.16.32",
853
859
  typescript: "^5"
854
860
  },
855
861
  publishConfig: {
@@ -979,7 +985,6 @@ function formatFinding(f) {
979
985
  var STATE_SOURCE_TYPES = new Set([
980
986
  "var_set",
981
987
  "map_set",
982
- "map_insert",
983
988
  "map_delete"
984
989
  ]);
985
990
  var LEVEL_RANK = { state: 0, events: 1, none: 2 };
@@ -996,6 +1001,9 @@ function deriveVerification(def, findings = []) {
996
1001
  if (filter.trait) {
997
1002
  raise("none");
998
1003
  reasons.push(`source "${name}": trait scope needs a proven contract registry`);
1004
+ } else if (filter.type === "map_insert") {
1005
+ raise("events");
1006
+ reasons.push(`map_insert source "${name}": map_insert vs map_set needs event proofs`);
999
1007
  } else if (!STATE_SOURCE_TYPES.has(filter.type)) {
1000
1008
  raise("events");
1001
1009
  reasons.push(`${filter.type} source "${name}" needs event proofs`);
@@ -1013,14 +1021,12 @@ function deriveVerification(def, findings = []) {
1013
1021
  if (findings.length > MAX_LISTED_FINDINGS) {
1014
1022
  reasons.push(`…and ${findings.length - MAX_LISTED_FINDINGS} more handler findings`);
1015
1023
  }
1016
- const writesMapInsert = Object.values(def.sources ?? {}).some((s) => s.type === "map_insert");
1017
1024
  return {
1018
1025
  level,
1019
1026
  reasons,
1020
1027
  unproven: reasons.length === 0 ? [
1021
1028
  "tx attribution of writes: event.tx, _tx_id (needs event proofs)",
1022
- "intermediate writes within a block (needs event proofs)",
1023
- ...writesMapInsert ? ["map_insert vs map_set (needs event proofs)"] : []
1029
+ "intermediate writes within a block (needs event proofs)"
1024
1030
  ] : []
1025
1031
  };
1026
1032
  }
@@ -1030,15 +1036,18 @@ function isDeterminismViolation(v) {
1030
1036
  function sha256(input) {
1031
1037
  return createHash("sha256").update(input).digest("hex");
1032
1038
  }
1033
- function computePin(input) {
1034
- const canonical = JSON.stringify({
1039
+ function pinPreimage(input) {
1040
+ const fields = {
1035
1041
  handlerHash: sha256(input.handlerCode),
1036
1042
  network: input.network,
1037
1043
  runtime: input.runtime ?? SUBGRAPHS_RUNTIME,
1038
1044
  schemaHash: input.schemaHash,
1039
1045
  startBlock: input.startBlock ?? null
1040
- });
1041
- return sha256(canonical);
1046
+ };
1047
+ return JSON.stringify(fields);
1048
+ }
1049
+ function computePin(input) {
1050
+ return sha256(pinPreimage(input));
1042
1051
  }
1043
1052
  // src/runtime/realm.ts
1044
1053
  import vm from "node:vm";
@@ -1049,6 +1058,9 @@ class NondeterminismError extends Error {
1049
1058
  class HandlerReadError extends Error {
1050
1059
  name = "HandlerReadError";
1051
1060
  }
1061
+ function abortsBlock(err) {
1062
+ return err instanceof NondeterminismError || err instanceof HandlerReadError;
1063
+ }
1052
1064
  var HOST_GLOBALS = [
1053
1065
  "ArrayBuffer",
1054
1066
  "DataView",
@@ -1220,6 +1232,7 @@ function deterministicContext(ctx, schema) {
1220
1232
  }
1221
1233
  };
1222
1234
  }
1235
+ var realmDefinitions = new WeakSet;
1223
1236
  async function loadDeterministicDefinition(handlerCode) {
1224
1237
  const { transform } = await import("esbuild");
1225
1238
  const { code } = await transform(handlerCode, {
@@ -1248,7 +1261,9 @@ ${code}
1248
1261
  return handler(event, facade);
1249
1262
  };
1250
1263
  }
1251
- return { ...def, sources, schema, handlers };
1264
+ const loaded = { ...def, sources, schema, handlers };
1265
+ realmDefinitions.add(loaded);
1266
+ return loaded;
1252
1267
  }
1253
1268
  // src/schema/generator.ts
1254
1269
  import { createHash as createHash2 } from "node:crypto";
@@ -2000,12 +2015,14 @@ function renderDeployPlan(def, schemaName) {
2000
2015
  async function deploySchema(db, def, handlerPath, opts) {
2001
2016
  validateSubgraphDefinition(def);
2002
2017
  const { statements, hash } = generateSubgraphSQL(def, opts?.schemaName);
2003
- const pin = opts?.handlerCode != null ? computePin({
2018
+ const pinInput = opts?.handlerCode != null ? {
2004
2019
  schemaHash: hash,
2005
2020
  handlerCode: opts.handlerCode,
2006
2021
  startBlock: def.startBlock,
2007
2022
  network: opts.network ?? process.env.NETWORK ?? "mainnet"
2008
- }) : null;
2023
+ } : null;
2024
+ const pin = pinInput ? computePin(pinInput) : null;
2025
+ const preimage = pinInput ? pinPreimage(pinInput) : null;
2009
2026
  const identity = pin ? { pin } : {};
2010
2027
  const { getSubgraph, registerSubgraph } = await import("@secondlayer/shared/db/queries/subgraphs");
2011
2028
  const ddlDb = db;
@@ -2031,6 +2048,7 @@ async function deploySchema(db, def, handlerPath, opts) {
2031
2048
  schemaName,
2032
2049
  startBlock: def.startBlock,
2033
2050
  pin,
2051
+ pinPreimage: preimage,
2034
2052
  verification: opts?.verification ?? null
2035
2053
  };
2036
2054
  if (existing) {
@@ -2059,6 +2077,7 @@ async function deploySchema(db, def, handlerPath, opts) {
2059
2077
  handlerCode: opts?.handlerCode,
2060
2078
  sourceCode: opts?.sourceCode,
2061
2079
  pin,
2080
+ pinPreimage: preimage,
2062
2081
  verification: opts?.verification ?? null
2063
2082
  });
2064
2083
  return {
@@ -2263,15 +2282,136 @@ import {
2263
2282
  import { logger as logger2 } from "@secondlayer/shared/logger";
2264
2283
 
2265
2284
  // src/runtime/batch-loader.ts
2285
+ import { sql as sql2 } from "kysely";
2286
+
2287
+ // src/runtime/state-writes.ts
2288
+ var DATA_MAP = "0";
2289
+ var VARIABLE = "1";
2290
+ var HEX = /^(?:[0-9a-f]{2})+$/;
2291
+ function parseStateWriteKey(key) {
2292
+ if (!key.startsWith("vm::"))
2293
+ return null;
2294
+ const parts = key.slice(4).split("::");
2295
+ const [contractId, store] = parts;
2296
+ if (!contractId?.includes("."))
2297
+ return null;
2298
+ if (store === DATA_MAP && parts.length === 4) {
2299
+ const [, , map, rawKey] = parts;
2300
+ if (!map || !rawKey || !HEX.test(rawKey))
2301
+ return null;
2302
+ return { contractId, kind: "map", map, rawKey };
2303
+ }
2304
+ if (store === VARIABLE && parts.length === 3 && parts[2]) {
2305
+ return { contractId, kind: "var", varName: parts[2] };
2306
+ }
2307
+ return null;
2308
+ }
2309
+ var utf8 = new TextDecoder("utf-8", { fatal: true });
2310
+ function storedValue(w) {
2311
+ const h = w.value_hex.startsWith("0x") ? w.value_hex.slice(2) : w.value_hex;
2312
+ const bytes = new Uint8Array(h.length / 2);
2313
+ for (let i = 0;i < bytes.length; i++) {
2314
+ bytes[i] = Number.parseInt(h.slice(i * 2, i * 2 + 2), 16);
2315
+ }
2316
+ let value;
2317
+ try {
2318
+ value = utf8.decode(bytes);
2319
+ } catch {
2320
+ throw new Error(`state write ${w.ordinal} value is not UTF-8 hex`);
2321
+ }
2322
+ value = value.toLowerCase();
2323
+ if (!HEX.test(value)) {
2324
+ throw new Error(`state write ${w.ordinal} value is not a Clarity value`);
2325
+ }
2326
+ return value;
2327
+ }
2328
+ function writeEvent(w) {
2329
+ const key = parseStateWriteKey(w.key);
2330
+ if (!key)
2331
+ return null;
2332
+ const value = storedValue(w);
2333
+ if (key.kind === "var") {
2334
+ return {
2335
+ type: "var_set",
2336
+ data: {
2337
+ contract_identifier: key.contractId,
2338
+ var_name: key.varName,
2339
+ raw_value: `0x${value}`
2340
+ }
2341
+ };
2342
+ }
2343
+ const base = {
2344
+ contract_identifier: key.contractId,
2345
+ map_name: key.map,
2346
+ raw_key: `0x${key.rawKey}`
2347
+ };
2348
+ if (value === "09")
2349
+ return { type: "map_delete", data: base };
2350
+ if (value.startsWith("0a")) {
2351
+ return {
2352
+ type: "map_set",
2353
+ data: { ...base, raw_value: `0x${value.slice(2)}` }
2354
+ };
2355
+ }
2356
+ throw new Error(`state write ${w.ordinal} map value is neither (some v) nor none`);
2357
+ }
2358
+ function stateWriteEvents(writes, txByIndex) {
2359
+ const txs = new Map;
2360
+ const vmEvents = [];
2361
+ for (const w of [...writes].sort((a, b) => a.ordinal - b.ordinal)) {
2362
+ const event = writeEvent(w);
2363
+ if (!event)
2364
+ continue;
2365
+ let tx = txs.get(w.tx_index);
2366
+ if (!tx) {
2367
+ if (w.tx_index === null) {
2368
+ tx = { tx_id: "", type: "block", sender: "", status: "success" };
2369
+ } else {
2370
+ const real = txByIndex.get(w.tx_index);
2371
+ if (!real) {
2372
+ throw new Error(`state write ${w.ordinal} names tx_index ${w.tx_index} the block does not have`);
2373
+ }
2374
+ tx = real;
2375
+ }
2376
+ tx = { ...tx, tx_index: 0 };
2377
+ txs.set(w.tx_index, tx);
2378
+ }
2379
+ vmEvents.push({
2380
+ id: vmEventId(tx.tx_id || "block", w.ordinal),
2381
+ tx_id: tx.tx_id,
2382
+ type: event.type,
2383
+ event_index: w.ordinal,
2384
+ data: event.data,
2385
+ clock: "vm"
2386
+ });
2387
+ }
2388
+ return { txs: [...txs.values()], vmEvents };
2389
+ }
2390
+
2391
+ // src/runtime/batch-loader.ts
2392
+ function stateWriteBlocks(writesByHeight, txsByHeight) {
2393
+ const out = new Map;
2394
+ for (const [height, writes] of writesByHeight) {
2395
+ const txByIndex = new Map;
2396
+ for (const tx of txsByHeight.get(height) ?? []) {
2397
+ if (tx.tx_index !== undefined)
2398
+ txByIndex.set(tx.tx_index, tx);
2399
+ }
2400
+ out.set(height, stateWriteEvents(writes, txByIndex));
2401
+ }
2402
+ return out;
2403
+ }
2266
2404
  function vmEventId(txId, vmEventIndex) {
2267
2405
  return `${txId}#vm:${vmEventIndex}`;
2268
2406
  }
2269
- async function loadBlockRange(db, fromHeight, toHeight) {
2270
- const [blocks, txs, events, vmRows] = await Promise.all([
2407
+ async function loadBlockRange(db, fromHeight, toHeight, opts = {}) {
2408
+ const feed = opts.stateWrites;
2409
+ const [blocks, txs, events, vmRows, writeRows] = await Promise.all([
2271
2410
  db.selectFrom("blocks").selectAll().where("height", ">=", fromHeight).where("height", "<=", toHeight).where("canonical", "=", true).execute(),
2272
2411
  db.selectFrom("transactions").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute(),
2273
2412
  db.selectFrom("events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute(),
2274
- db.selectFrom("vm_events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).orderBy("ordinal", "asc").execute()
2413
+ feed ? Promise.resolve([]) : db.selectFrom("vm_events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).orderBy("ordinal", "asc").execute(),
2414
+ feed ? loadStateWrites(db, fromHeight, toHeight, feed) : Promise.resolve([])
2275
2415
  ]);
2276
2416
  const txsByHeight = new Map;
2277
2417
  for (const tx of txs) {
@@ -2303,18 +2443,42 @@ async function loadBlockRange(db, fromHeight, toHeight) {
2303
2443
  });
2304
2444
  vmByHeight.set(h, list);
2305
2445
  }
2446
+ const writesByHeight = new Map;
2447
+ for (const row of writeRows) {
2448
+ const h = Number(row.block_height);
2449
+ const list = writesByHeight.get(h) ?? [];
2450
+ list.push({
2451
+ ordinal: Number(row.ordinal),
2452
+ tx_index: row.tx_index === null ? null : Number(row.tx_index),
2453
+ key: row.key,
2454
+ value_hex: row.value_hex
2455
+ });
2456
+ writesByHeight.set(h, list);
2457
+ }
2458
+ const named = stateWriteBlocks(writesByHeight, txsByHeight);
2306
2459
  const result = new Map;
2307
2460
  for (const block of blocks) {
2308
2461
  const h = Number(block.height);
2462
+ const fed = named.get(h);
2309
2463
  result.set(h, {
2310
2464
  block,
2311
- txs: txsByHeight.get(h) ?? [],
2465
+ txs: feed ? fed?.txs ?? [] : txsByHeight.get(h) ?? [],
2312
2466
  events: eventsByHeight.get(h) ?? [],
2313
- vmEvents: vmByHeight.get(h) ?? []
2467
+ vmEvents: feed ? fed?.vmEvents ?? [] : vmByHeight.get(h) ?? []
2314
2468
  });
2315
2469
  }
2316
2470
  return result;
2317
2471
  }
2472
+ async function loadStateWrites(db, fromHeight, toHeight, feed) {
2473
+ const scope = feed.contracts ? sql2`AND (${sql2.join(feed.contracts.map((c) => sql2`starts_with(key, ${`vm::${c}::`})`), sql2` OR `)})` : sql2``;
2474
+ const { rows } = await sql2`
2475
+ SELECT block_height, ordinal, tx_index, key, value_hex
2476
+ FROM state_writes
2477
+ WHERE block_height >= ${fromHeight} AND block_height <= ${toHeight} ${scope}
2478
+ ORDER BY block_height, ordinal
2479
+ `.execute(db);
2480
+ return rows;
2481
+ }
2318
2482
 
2319
2483
  // src/runtime/observer-http-source.ts
2320
2484
  import {
@@ -2516,14 +2680,39 @@ function sparseProbeTargets(subgraph) {
2516
2680
  }
2517
2681
  return targets;
2518
2682
  }
2519
-
2683
+ var COVERAGE_TTL_MS = 60000;
2684
+ function cachedCoverage(read, ttlMs = COVERAGE_TTL_MS) {
2685
+ let cached;
2686
+ const coverage = () => {
2687
+ const now = Date.now();
2688
+ if (!cached || now - cached.at > ttlMs) {
2689
+ cached = { at: now, value: read().catch(() => null) };
2690
+ }
2691
+ return cached.value;
2692
+ };
2693
+ return Object.assign(coverage, {
2694
+ forget: () => {
2695
+ cached = undefined;
2696
+ }
2697
+ });
2698
+ }
2699
+ var dbStateWritesCoverage = cachedCoverage(async () => {
2700
+ const row = await getSourceDb2().selectFrom("state_writes").select("block_height").orderBy("block_height", "asc").limit(1).executeTakeFirst();
2701
+ return row ? Number(row.block_height) : null;
2702
+ });
2520
2703
  class PostgresBlockSource {
2704
+ feed;
2705
+ constructor(feed) {
2706
+ this.feed = feed;
2707
+ }
2521
2708
  async getTip() {
2522
2709
  const progress = await getSourceDb2().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
2523
2710
  return progress ? Number(progress.highest_seen_block) : 0;
2524
2711
  }
2525
2712
  loadBlockRange(fromHeight, toHeight) {
2526
- return loadBlockRange(getSourceDb2(), fromHeight, toHeight);
2713
+ return loadBlockRange(getSourceDb2(), fromHeight, toHeight, {
2714
+ stateWrites: this.feed
2715
+ });
2527
2716
  }
2528
2717
  }
2529
2718
  var EVENT_FILTER_TO_INDEX_TYPE = {
@@ -2609,15 +2798,18 @@ class PublicApiBlockSource {
2609
2798
  eventTypes;
2610
2799
  probeTargets;
2611
2800
  needsTransactions;
2612
- constructor(http, eventTypes, probeTargets, needsTransactions = true) {
2801
+ feed;
2802
+ constructor(http, eventTypes, probeTargets, needsTransactions = true, feed) {
2613
2803
  this.http = http;
2614
2804
  this.eventTypes = eventTypes;
2615
2805
  this.probeTargets = probeTargets;
2616
2806
  this.needsTransactions = needsTransactions;
2807
+ this.feed = feed;
2617
2808
  }
2618
2809
  walkTargets() {
2619
2810
  const scoped = this.needsTransactions ? undefined : this.probeTargets;
2620
- return this.eventTypes.flatMap((eventType) => {
2811
+ const types = this.feed ? this.eventTypes.filter((t) => !VM_INDEX_EVENT_TYPES.has(t)) : this.eventTypes;
2812
+ return types.flatMap((eventType) => {
2621
2813
  const own = scoped?.filter((t) => t.eventType === eventType);
2622
2814
  return own?.length ? own : [{ eventType }];
2623
2815
  });
@@ -2640,10 +2832,11 @@ class PublicApiBlockSource {
2640
2832
  }
2641
2833
  async loadBlockRange(fromHeight, toHeight) {
2642
2834
  const withTx = !this.needsTransactions;
2643
- const [blocks, txRows, eventLists] = await Promise.all([
2835
+ const [blocks, txRows, eventLists, writes] = await Promise.all([
2644
2836
  this.http.walkBlocks(fromHeight, toHeight),
2645
2837
  this.needsTransactions ? this.http.walkTransactions(fromHeight, toHeight) : Promise.resolve([]),
2646
- Promise.all(this.walkTargets().map((t) => this.http.walkEvents(t.eventType, fromHeight, toHeight, withTx, t.contractId)))
2838
+ Promise.all(this.walkTargets().map((t) => this.http.walkEvents(t.eventType, fromHeight, toHeight, withTx, t.contractId))),
2839
+ this.feed ? this.walkWrites(fromHeight, toHeight, this.feed) : null
2647
2840
  ]);
2648
2841
  const events = dedupeEvents(eventLists.flat());
2649
2842
  const map = new Map;
@@ -2669,13 +2862,55 @@ class PublicApiBlockSource {
2669
2862
  bd.events.push(reconstructEvent(e));
2670
2863
  }
2671
2864
  }
2865
+ if (writes) {
2866
+ for (const [height, fed] of writes) {
2867
+ const bd = map.get(height);
2868
+ if (!bd)
2869
+ continue;
2870
+ bd.txs.push(...fed.txs);
2871
+ bd.vmEvents = fed.vmEvents;
2872
+ }
2873
+ }
2672
2874
  for (const bd of map.values()) {
2673
- bd.txs.sort((a, b) => a.tx_index - b.tx_index);
2875
+ bd.txs.sort((a, b) => (a.tx_index ?? 0) - (b.tx_index ?? 0));
2674
2876
  bd.events.sort((a, b) => a.event_index - b.event_index);
2675
2877
  bd.vmEvents?.sort((a, b) => a.event_index - b.event_index);
2676
2878
  }
2677
2879
  return map;
2678
2880
  }
2881
+ async walkWrites(fromHeight, toHeight, feed) {
2882
+ const walks = await Promise.all((feed.contracts ?? [undefined]).map((c) => this.http.walkStateWrites(fromHeight, toHeight, c)));
2883
+ const writesByHeight = new Map;
2884
+ const txsByHeight = new Map;
2885
+ for (const row of walks.flat()) {
2886
+ const writes = writesByHeight.get(row.block_height) ?? new Map;
2887
+ writes.set(row.ordinal, row);
2888
+ writesByHeight.set(row.block_height, writes);
2889
+ const tx = writeTx(row);
2890
+ if (tx) {
2891
+ const txs = txsByHeight.get(row.block_height) ?? new Map;
2892
+ txs.set(row.tx_index, tx);
2893
+ txsByHeight.set(row.block_height, txs);
2894
+ }
2895
+ }
2896
+ return stateWriteBlocks(new Map([...writesByHeight].map(([h, w]) => [
2897
+ h,
2898
+ [...w.values()].sort((a, b) => a.ordinal - b.ordinal)
2899
+ ])), new Map([...txsByHeight].map(([h, t]) => [h, [...t.values()]])));
2900
+ }
2901
+ }
2902
+ function writeTx(row) {
2903
+ if (row.tx_index === null || !row.tx_id)
2904
+ return null;
2905
+ return {
2906
+ tx_id: row.tx_id,
2907
+ tx_index: row.tx_index,
2908
+ type: row.tx_type ?? "",
2909
+ sender: row.tx_sender ?? "",
2910
+ status: row.tx_status ?? "",
2911
+ contract_id: row.tx_contract_id ?? null,
2912
+ function_name: row.tx_function_name ?? null
2913
+ };
2679
2914
  }
2680
2915
  var postgresBlockSource = new PostgresBlockSource;
2681
2916
  function buildHttpClient() {
@@ -2713,5 +2948,5 @@ export {
2713
2948
  validateSubgraphDefinition
2714
2949
  };
2715
2950
 
2716
- //# debugId=BDCC478FBE9DE96064756E2164756E21
2951
+ //# debugId=411D5E1C7A6D7DEB64756E2164756E21
2717
2952
  //# sourceMappingURL=index.js.map