@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.
@@ -1,9 +1,9 @@
1
1
  // src/runtime/replay.ts
2
- import { createHash } from "node:crypto";
2
+ import { createHash as createHash2 } from "node:crypto";
3
3
  import { getTargetDb } from "@secondlayer/shared/db";
4
4
  import { getWebhook } from "@secondlayer/shared/db/queries/webhooks";
5
5
  import { logger as logger2 } from "@secondlayer/shared/logger";
6
- import { sql } from "kysely";
6
+ import { sql as sql2 } from "kysely";
7
7
 
8
8
  // src/schema/utils.ts
9
9
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
@@ -28,16 +28,405 @@ import {
28
28
  } from "@secondlayer/shared/index-http";
29
29
  import { logger } from "@secondlayer/shared/logger";
30
30
 
31
+ // src/verification.ts
32
+ import { createHash } from "node:crypto";
33
+ // package.json
34
+ var package_default = {
35
+ name: "@secondlayer/subgraphs",
36
+ version: "6.5.0",
37
+ description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
38
+ license: "MIT",
39
+ repository: {
40
+ type: "git",
41
+ url: "git+https://github.com/ryanwaits/secondlayer.git",
42
+ directory: "packages/subgraphs"
43
+ },
44
+ homepage: "https://github.com/ryanwaits/secondlayer/tree/main/packages/subgraphs#readme",
45
+ type: "module",
46
+ main: "./dist/index.js",
47
+ types: "./dist/index.d.ts",
48
+ exports: {
49
+ ".": {
50
+ types: "./dist/index.d.ts",
51
+ import: "./dist/index.js"
52
+ },
53
+ "./types": {
54
+ types: "./dist/types.d.ts",
55
+ import: "./dist/types.js"
56
+ },
57
+ "./schema": {
58
+ types: "./dist/schema/index.d.ts",
59
+ import: "./dist/schema/index.js"
60
+ },
61
+ "./validate": {
62
+ types: "./dist/validate.d.ts",
63
+ import: "./dist/validate.js"
64
+ },
65
+ "./verification": {
66
+ types: "./dist/verification.d.ts",
67
+ import: "./dist/verification.js"
68
+ },
69
+ "./runtime/replay": {
70
+ types: "./dist/runtime/replay.d.ts",
71
+ import: "./dist/runtime/replay.js"
72
+ },
73
+ "./runtime/emitter": {
74
+ types: "./dist/runtime/emitter.d.ts",
75
+ import: "./dist/runtime/emitter.js"
76
+ },
77
+ "./testing": {
78
+ types: "./dist/testing/index.d.ts",
79
+ import: "./dist/testing/index.js"
80
+ },
81
+ "./verify": {
82
+ types: "./dist/verify/index.d.ts",
83
+ import: "./dist/verify/index.js"
84
+ }
85
+ },
86
+ files: ["dist"],
87
+ scripts: {
88
+ build: "bunup",
89
+ dev: "bunup --watch",
90
+ test: "bun test",
91
+ typecheck: "tsc --noEmit",
92
+ prepublishOnly: "bun run build"
93
+ },
94
+ dependencies: {
95
+ "@secondlayer/shared": "^11.23.0",
96
+ "@secondlayer/stacks": "^6.2.0",
97
+ "@secondlayer/verify": "^0.3.0",
98
+ esbuild: "^0.28.2",
99
+ kysely: "0.28.17",
100
+ zod: "^4.3.6"
101
+ },
102
+ devDependencies: {
103
+ "@types/bun": "latest",
104
+ bunup: "0.16.32",
105
+ typescript: "^5"
106
+ },
107
+ publishConfig: {
108
+ access: "public"
109
+ }
110
+ };
111
+
112
+ // src/verification.ts
113
+ var SUBGRAPHS_RUNTIME = `@secondlayer/subgraphs@${package_default.version}`;
114
+ var FORBIDDEN_GLOBALS = {
115
+ Date: "wall-clock time differs per run",
116
+ performance: "timers differ per run",
117
+ process: "host environment differs per run",
118
+ fetch: "network reads are not chain inputs",
119
+ crypto: "randomness differs per run",
120
+ setTimeout: "scheduling order differs per run",
121
+ setInterval: "scheduling order differs per run",
122
+ setImmediate: "scheduling order differs per run",
123
+ clearTimeout: "scheduling order differs per run",
124
+ clearInterval: "scheduling order differs per run",
125
+ queueMicrotask: "scheduling order differs per run",
126
+ WeakRef: "garbage collection timing differs per run",
127
+ FinalizationRegistry: "garbage collection timing differs per run",
128
+ Atomics: "shared memory is not single-threaded",
129
+ SharedArrayBuffer: "shared memory is not single-threaded",
130
+ Intl: "locale and time zone data differ per host",
131
+ Temporal: "locale and time zone data differ per host",
132
+ eval: "generated code hides inputs from the scan",
133
+ WebAssembly: "compiled code hides inputs from the scan",
134
+ ShadowRealm: "a nested realm escapes the allow-list",
135
+ require: "imports must be bundled into the handler"
136
+ };
137
+ var ALLOWED_GLOBALS = [
138
+ "undefined",
139
+ "NaN",
140
+ "Infinity",
141
+ "globalThis",
142
+ "isNaN",
143
+ "isFinite",
144
+ "parseInt",
145
+ "parseFloat",
146
+ "decodeURI",
147
+ "decodeURIComponent",
148
+ "encodeURI",
149
+ "encodeURIComponent",
150
+ "escape",
151
+ "unescape",
152
+ "Object",
153
+ "Function",
154
+ "Array",
155
+ "Number",
156
+ "Boolean",
157
+ "String",
158
+ "Symbol",
159
+ "BigInt",
160
+ "Math",
161
+ "JSON",
162
+ "Reflect",
163
+ "Proxy",
164
+ "Promise",
165
+ "Map",
166
+ "Set",
167
+ "WeakMap",
168
+ "WeakSet",
169
+ "RegExp",
170
+ "Iterator",
171
+ "DisposableStack",
172
+ "AsyncDisposableStack",
173
+ "Error",
174
+ "AggregateError",
175
+ "SuppressedError",
176
+ "EvalError",
177
+ "RangeError",
178
+ "ReferenceError",
179
+ "SyntaxError",
180
+ "TypeError",
181
+ "URIError",
182
+ "ArrayBuffer",
183
+ "DataView",
184
+ "Int8Array",
185
+ "Uint8Array",
186
+ "Uint8ClampedArray",
187
+ "Int16Array",
188
+ "Uint16Array",
189
+ "Int32Array",
190
+ "Uint32Array",
191
+ "Float16Array",
192
+ "Float32Array",
193
+ "Float64Array",
194
+ "BigInt64Array",
195
+ "BigUint64Array",
196
+ "TextEncoder",
197
+ "TextDecoder",
198
+ "atob",
199
+ "btoa",
200
+ "console"
201
+ ];
202
+ var ALLOWED_MATH = [
203
+ "abs",
204
+ "min",
205
+ "max",
206
+ "floor",
207
+ "ceil",
208
+ "trunc",
209
+ "sign",
210
+ "round",
211
+ "E",
212
+ "LN10",
213
+ "LN2",
214
+ "LOG10E",
215
+ "LOG2E",
216
+ "PI",
217
+ "SQRT1_2",
218
+ "SQRT2"
219
+ ];
220
+ var LOCALE_METHODS = [
221
+ "toLocaleString",
222
+ "toLocaleDateString",
223
+ "toLocaleTimeString",
224
+ "toLocaleUpperCase",
225
+ "toLocaleLowerCase",
226
+ "localeCompare"
227
+ ];
228
+ function formatFinding(f) {
229
+ return `${f.file}:${f.line}:${f.column} ${f.name}: ${f.reason}`;
230
+ }
231
+ var STATE_SOURCE_TYPES = new Set([
232
+ "var_set",
233
+ "map_set",
234
+ "map_delete"
235
+ ]);
236
+ var LEVEL_RANK = { state: 0, events: 1, none: 2 };
237
+ var MAX_LISTED_FINDINGS = 10;
238
+ function deriveVerification(def, findings = []) {
239
+ let level = "state";
240
+ const raise = (to) => {
241
+ if (LEVEL_RANK[to] > LEVEL_RANK[level])
242
+ level = to;
243
+ };
244
+ const reasons = [];
245
+ for (const [name, source] of Object.entries(def.sources ?? {})) {
246
+ const filter = source;
247
+ if (filter.trait) {
248
+ raise("none");
249
+ reasons.push(`source "${name}": trait scope needs a proven contract registry`);
250
+ } else if (filter.type === "map_insert") {
251
+ raise("events");
252
+ reasons.push(`map_insert source "${name}": map_insert vs map_set needs event proofs`);
253
+ } else if (!STATE_SOURCE_TYPES.has(filter.type)) {
254
+ raise("events");
255
+ reasons.push(`${filter.type} source "${name}" needs event proofs`);
256
+ }
257
+ }
258
+ if (def.backfillMode === "concurrent") {
259
+ raise("none");
260
+ reasons.push("backfillMode concurrent: tip-first order is not chain order");
261
+ }
262
+ if (findings.some((f) => f.kind === "needs-events"))
263
+ raise("events");
264
+ for (const f of findings.slice(0, MAX_LISTED_FINDINGS)) {
265
+ reasons.push(f.kind === "needs-events" ? `${formatFinding(f)} (needs event proofs)` : formatFinding(f));
266
+ }
267
+ if (findings.length > MAX_LISTED_FINDINGS) {
268
+ reasons.push(`…and ${findings.length - MAX_LISTED_FINDINGS} more handler findings`);
269
+ }
270
+ return {
271
+ level,
272
+ reasons,
273
+ unproven: reasons.length === 0 ? [
274
+ "tx attribution of writes: event.tx, _tx_id (needs event proofs)",
275
+ "intermediate writes within a block (needs event proofs)"
276
+ ] : []
277
+ };
278
+ }
279
+ function isDeterminismViolation(v) {
280
+ return v.level === "state" && v.reasons.length > 0;
281
+ }
282
+ function sha256(input) {
283
+ return createHash("sha256").update(input).digest("hex");
284
+ }
285
+ function pinPreimage(input) {
286
+ const fields = {
287
+ handlerHash: sha256(input.handlerCode),
288
+ network: input.network,
289
+ runtime: input.runtime ?? SUBGRAPHS_RUNTIME,
290
+ schemaHash: input.schemaHash,
291
+ startBlock: input.startBlock ?? null
292
+ };
293
+ return JSON.stringify(fields);
294
+ }
295
+ function computePin(input) {
296
+ return sha256(pinPreimage(input));
297
+ }
298
+
31
299
  // src/runtime/batch-loader.ts
300
+ import { sql } from "kysely";
301
+
302
+ // src/runtime/state-writes.ts
303
+ var DATA_MAP = "0";
304
+ var VARIABLE = "1";
305
+ var HEX = /^(?:[0-9a-f]{2})+$/;
306
+ function parseStateWriteKey(key) {
307
+ if (!key.startsWith("vm::"))
308
+ return null;
309
+ const parts = key.slice(4).split("::");
310
+ const [contractId, store] = parts;
311
+ if (!contractId?.includes("."))
312
+ return null;
313
+ if (store === DATA_MAP && parts.length === 4) {
314
+ const [, , map, rawKey] = parts;
315
+ if (!map || !rawKey || !HEX.test(rawKey))
316
+ return null;
317
+ return { contractId, kind: "map", map, rawKey };
318
+ }
319
+ if (store === VARIABLE && parts.length === 3 && parts[2]) {
320
+ return { contractId, kind: "var", varName: parts[2] };
321
+ }
322
+ return null;
323
+ }
324
+ var utf8 = new TextDecoder("utf-8", { fatal: true });
325
+ function storedValue(w) {
326
+ const h = w.value_hex.startsWith("0x") ? w.value_hex.slice(2) : w.value_hex;
327
+ const bytes = new Uint8Array(h.length / 2);
328
+ for (let i = 0;i < bytes.length; i++) {
329
+ bytes[i] = Number.parseInt(h.slice(i * 2, i * 2 + 2), 16);
330
+ }
331
+ let value;
332
+ try {
333
+ value = utf8.decode(bytes);
334
+ } catch {
335
+ throw new Error(`state write ${w.ordinal} value is not UTF-8 hex`);
336
+ }
337
+ value = value.toLowerCase();
338
+ if (!HEX.test(value)) {
339
+ throw new Error(`state write ${w.ordinal} value is not a Clarity value`);
340
+ }
341
+ return value;
342
+ }
343
+ function writeEvent(w) {
344
+ const key = parseStateWriteKey(w.key);
345
+ if (!key)
346
+ return null;
347
+ const value = storedValue(w);
348
+ if (key.kind === "var") {
349
+ return {
350
+ type: "var_set",
351
+ data: {
352
+ contract_identifier: key.contractId,
353
+ var_name: key.varName,
354
+ raw_value: `0x${value}`
355
+ }
356
+ };
357
+ }
358
+ const base = {
359
+ contract_identifier: key.contractId,
360
+ map_name: key.map,
361
+ raw_key: `0x${key.rawKey}`
362
+ };
363
+ if (value === "09")
364
+ return { type: "map_delete", data: base };
365
+ if (value.startsWith("0a")) {
366
+ return {
367
+ type: "map_set",
368
+ data: { ...base, raw_value: `0x${value.slice(2)}` }
369
+ };
370
+ }
371
+ throw new Error(`state write ${w.ordinal} map value is neither (some v) nor none`);
372
+ }
373
+ function stateWriteEvents(writes, txByIndex) {
374
+ const txs = new Map;
375
+ const vmEvents = [];
376
+ for (const w of [...writes].sort((a, b) => a.ordinal - b.ordinal)) {
377
+ const event = writeEvent(w);
378
+ if (!event)
379
+ continue;
380
+ let tx = txs.get(w.tx_index);
381
+ if (!tx) {
382
+ if (w.tx_index === null) {
383
+ tx = { tx_id: "", type: "block", sender: "", status: "success" };
384
+ } else {
385
+ const real = txByIndex.get(w.tx_index);
386
+ if (!real) {
387
+ throw new Error(`state write ${w.ordinal} names tx_index ${w.tx_index} the block does not have`);
388
+ }
389
+ tx = real;
390
+ }
391
+ tx = { ...tx, tx_index: 0 };
392
+ txs.set(w.tx_index, tx);
393
+ }
394
+ vmEvents.push({
395
+ id: vmEventId(tx.tx_id || "block", w.ordinal),
396
+ tx_id: tx.tx_id,
397
+ type: event.type,
398
+ event_index: w.ordinal,
399
+ data: event.data,
400
+ clock: "vm"
401
+ });
402
+ }
403
+ return { txs: [...txs.values()], vmEvents };
404
+ }
405
+
406
+ // src/runtime/batch-loader.ts
407
+ function stateWriteBlocks(writesByHeight, txsByHeight) {
408
+ const out = new Map;
409
+ for (const [height, writes] of writesByHeight) {
410
+ const txByIndex = new Map;
411
+ for (const tx of txsByHeight.get(height) ?? []) {
412
+ if (tx.tx_index !== undefined)
413
+ txByIndex.set(tx.tx_index, tx);
414
+ }
415
+ out.set(height, stateWriteEvents(writes, txByIndex));
416
+ }
417
+ return out;
418
+ }
32
419
  function vmEventId(txId, vmEventIndex) {
33
420
  return `${txId}#vm:${vmEventIndex}`;
34
421
  }
35
- async function loadBlockRange(db, fromHeight, toHeight) {
36
- const [blocks, txs, events, vmRows] = await Promise.all([
422
+ async function loadBlockRange(db, fromHeight, toHeight, opts = {}) {
423
+ const feed = opts.stateWrites;
424
+ const [blocks, txs, events, vmRows, writeRows] = await Promise.all([
37
425
  db.selectFrom("blocks").selectAll().where("height", ">=", fromHeight).where("height", "<=", toHeight).where("canonical", "=", true).execute(),
38
426
  db.selectFrom("transactions").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute(),
39
427
  db.selectFrom("events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute(),
40
- db.selectFrom("vm_events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).orderBy("ordinal", "asc").execute()
428
+ feed ? Promise.resolve([]) : db.selectFrom("vm_events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).orderBy("ordinal", "asc").execute(),
429
+ feed ? loadStateWrites(db, fromHeight, toHeight, feed) : Promise.resolve([])
41
430
  ]);
42
431
  const txsByHeight = new Map;
43
432
  for (const tx of txs) {
@@ -69,24 +458,265 @@ async function loadBlockRange(db, fromHeight, toHeight) {
69
458
  });
70
459
  vmByHeight.set(h, list);
71
460
  }
461
+ const writesByHeight = new Map;
462
+ for (const row of writeRows) {
463
+ const h = Number(row.block_height);
464
+ const list = writesByHeight.get(h) ?? [];
465
+ list.push({
466
+ ordinal: Number(row.ordinal),
467
+ tx_index: row.tx_index === null ? null : Number(row.tx_index),
468
+ key: row.key,
469
+ value_hex: row.value_hex
470
+ });
471
+ writesByHeight.set(h, list);
472
+ }
473
+ const named = stateWriteBlocks(writesByHeight, txsByHeight);
72
474
  const result = new Map;
73
475
  for (const block of blocks) {
74
476
  const h = Number(block.height);
477
+ const fed = named.get(h);
75
478
  result.set(h, {
76
479
  block,
77
- txs: txsByHeight.get(h) ?? [],
480
+ txs: feed ? fed?.txs ?? [] : txsByHeight.get(h) ?? [],
78
481
  events: eventsByHeight.get(h) ?? [],
79
- vmEvents: vmByHeight.get(h) ?? []
482
+ vmEvents: feed ? fed?.vmEvents ?? [] : vmByHeight.get(h) ?? []
80
483
  });
81
484
  }
82
485
  return result;
83
486
  }
487
+ async function loadStateWrites(db, fromHeight, toHeight, feed) {
488
+ const scope = feed.contracts ? sql`AND (${sql.join(feed.contracts.map((c) => sql`starts_with(key, ${`vm::${c}::`})`), sql` OR `)})` : sql``;
489
+ const { rows } = await sql`
490
+ SELECT block_height, ordinal, tx_index, key, value_hex
491
+ FROM state_writes
492
+ WHERE block_height >= ${fromHeight} AND block_height <= ${toHeight} ${scope}
493
+ ORDER BY block_height, ordinal
494
+ `.execute(db);
495
+ return rows;
496
+ }
84
497
 
85
498
  // src/runtime/observer-http-source.ts
86
499
  import {
87
500
  VM_NODE_TO_STORED_TYPE
88
501
  } from "@secondlayer/shared";
89
502
 
503
+ // src/runtime/realm.ts
504
+ import vm from "node:vm";
505
+ class NondeterminismError extends Error {
506
+ name = "NondeterminismError";
507
+ }
508
+
509
+ class HandlerReadError extends Error {
510
+ name = "HandlerReadError";
511
+ }
512
+ function abortsBlock(err) {
513
+ return err instanceof NondeterminismError || err instanceof HandlerReadError;
514
+ }
515
+ var HOST_GLOBALS = [
516
+ "ArrayBuffer",
517
+ "DataView",
518
+ "Int8Array",
519
+ "Uint8Array",
520
+ "Uint8ClampedArray",
521
+ "Int16Array",
522
+ "Uint16Array",
523
+ "Int32Array",
524
+ "Uint32Array",
525
+ "Float16Array",
526
+ "Float32Array",
527
+ "Float64Array",
528
+ "BigInt64Array",
529
+ "BigUint64Array",
530
+ "TextEncoder",
531
+ "TextDecoder",
532
+ "atob",
533
+ "btoa"
534
+ ];
535
+ function forbid(name, reason) {
536
+ return () => {
537
+ throw new NondeterminismError(`${name}: ${reason}`);
538
+ };
539
+ }
540
+ function createRealm() {
541
+ const host = globalThis;
542
+ const sandbox = Object.create(null);
543
+ for (const name of HOST_GLOBALS) {
544
+ if (host[name] !== undefined)
545
+ sandbox[name] = host[name];
546
+ }
547
+ const context = vm.createContext(sandbox, {
548
+ name: "subgraph-handler",
549
+ codeGeneration: { strings: false, wasm: false }
550
+ });
551
+ const g = vm.runInContext("globalThis", context);
552
+ const allowed = new Set(ALLOWED_GLOBALS);
553
+ for (const name of Object.getOwnPropertyNames(g)) {
554
+ if (!allowed.has(name))
555
+ delete g[name];
556
+ }
557
+ for (const [name, reason] of Object.entries(FORBIDDEN_GLOBALS)) {
558
+ Object.defineProperty(g, name, { get: forbid(name, reason) });
559
+ }
560
+ const realmMath = g.Math;
561
+ const safeMath = {};
562
+ const allowedMath = new Set(ALLOWED_MATH);
563
+ for (const key of Object.getOwnPropertyNames(realmMath)) {
564
+ if (allowedMath.has(key))
565
+ safeMath[key] = realmMath[key];
566
+ else
567
+ Object.defineProperty(safeMath, key, {
568
+ get: forbid(`Math.${key}`, key === "random" ? "randomness differs per run" : "result is engine-approximated")
569
+ });
570
+ }
571
+ Object.defineProperty(g, "Math", { value: Object.freeze(safeMath) });
572
+ for (const ctor of ["String", "Number", "BigInt", "Array", "Object"]) {
573
+ const proto = g[ctor].prototype;
574
+ for (const method of LOCALE_METHODS) {
575
+ if (Object.prototype.hasOwnProperty.call(proto, method)) {
576
+ Object.defineProperty(proto, method, {
577
+ value: forbid(method, "output depends on the host's locale data")
578
+ });
579
+ }
580
+ }
581
+ }
582
+ Object.preventExtensions(g);
583
+ return context;
584
+ }
585
+ function checkRowValues(where, values) {
586
+ for (const [col, value] of Object.entries(values)) {
587
+ checkValue(value, `${where}.${col}`);
588
+ }
589
+ }
590
+ function checkValue(value, path) {
591
+ switch (typeof value) {
592
+ case "number":
593
+ if (!Number.isSafeInteger(value)) {
594
+ throw new TypeError(`${path}: ${value} is not a safe integer; verifiable subgraphs store bigint or integers only`);
595
+ }
596
+ return;
597
+ case "function":
598
+ case "symbol":
599
+ throw new TypeError(`${path}: a ${typeof value} is not a row value`);
600
+ case "object":
601
+ if (value === null || ArrayBuffer.isView(value))
602
+ return;
603
+ if (Array.isArray(value)) {
604
+ value.forEach((v, i) => checkValue(v, `${path}[${i}]`));
605
+ return;
606
+ }
607
+ for (const [k, v] of Object.entries(value))
608
+ checkValue(v, `${path}.${k}`);
609
+ return;
610
+ default:
611
+ return;
612
+ }
613
+ }
614
+ function deterministicContext(ctx, schema) {
615
+ let pendingReads = 0;
616
+ const knownTable = (table) => {
617
+ if (!Object.prototype.hasOwnProperty.call(schema, table)) {
618
+ throw new Error(`Table "${table}" not found in subgraph schema`);
619
+ }
620
+ };
621
+ const guard = (op) => {
622
+ if (pendingReads > 0) {
623
+ throw new NondeterminismError(`ctx.${op} while a ctx read is pending: concurrent ctx calls order writes by database latency; await each read before the next call`);
624
+ }
625
+ };
626
+ const read = async (op, run) => {
627
+ guard(op);
628
+ pendingReads++;
629
+ try {
630
+ return await run();
631
+ } catch (err) {
632
+ if (err instanceof NondeterminismError)
633
+ throw err;
634
+ throw new HandlerReadError(`ctx.${op} failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
635
+ } finally {
636
+ pendingReads--;
637
+ }
638
+ };
639
+ return {
640
+ get block() {
641
+ return ctx.block;
642
+ },
643
+ get tx() {
644
+ return ctx.tx;
645
+ },
646
+ get client() {
647
+ throw new NondeterminismError("ctx.client: contract reads need re-execution proofs");
648
+ },
649
+ insert(table, row) {
650
+ guard("insert");
651
+ checkRowValues(`insert ${table}`, row);
652
+ ctx.insert(table, row);
653
+ },
654
+ update(table, where, set) {
655
+ guard("update");
656
+ checkRowValues(`update ${table}`, where);
657
+ checkRowValues(`update ${table}`, set);
658
+ ctx.update(table, where, set);
659
+ },
660
+ upsert(table, key, row) {
661
+ guard("upsert");
662
+ checkRowValues(`upsert ${table}`, key);
663
+ checkRowValues(`upsert ${table}`, row);
664
+ ctx.upsert(table, key, row);
665
+ },
666
+ delete(table, where) {
667
+ guard("delete");
668
+ ctx.delete(table, where);
669
+ },
670
+ increment(table, key, deltas) {
671
+ guard("increment");
672
+ checkRowValues(`increment ${table}`, key);
673
+ checkRowValues(`increment ${table}`, deltas);
674
+ ctx.increment(table, key, deltas);
675
+ },
676
+ findOne: (table, where) => {
677
+ knownTable(table);
678
+ return read("findOne", () => ctx.findOne(table, where));
679
+ },
680
+ findMany: (table, ...rest) => {
681
+ knownTable(table);
682
+ return read("findMany", () => ctx.findMany(table, ...rest));
683
+ }
684
+ };
685
+ }
686
+ var realmDefinitions = new WeakSet;
687
+ async function loadDeterministicDefinition(handlerCode) {
688
+ const { transform } = await import("esbuild");
689
+ const { code } = await transform(handlerCode, {
690
+ format: "cjs",
691
+ loader: "js",
692
+ logLevel: "silent"
693
+ });
694
+ const context = createRealm();
695
+ const evaluate = vm.runInContext(`(function (module, exports) {
696
+ ${code}
697
+ })`, context, { filename: "handler.js" });
698
+ const mod = { exports: {} };
699
+ evaluate(mod, mod.exports);
700
+ const def = mod.exports.default ?? mod.exports;
701
+ const sources = structuredClone(def.sources);
702
+ const schema = structuredClone(def.schema);
703
+ const facades = new WeakMap;
704
+ const handlers = {};
705
+ for (const [name, handler] of Object.entries(def.handlers ?? {})) {
706
+ handlers[name] = (event, ctx) => {
707
+ let facade = facades.get(ctx);
708
+ if (!facade) {
709
+ facade = deterministicContext(ctx, schema);
710
+ facades.set(ctx, facade);
711
+ }
712
+ return handler(event, facade);
713
+ };
714
+ }
715
+ const loaded = { ...def, sources, schema, handlers };
716
+ realmDefinitions.add(loaded);
717
+ return loaded;
718
+ }
719
+
90
720
  // src/runtime/reconstruct.ts
91
721
  function isoToUnixSeconds(iso) {
92
722
  if (!iso)
@@ -282,14 +912,39 @@ function sparseProbeTargets(subgraph) {
282
912
  }
283
913
  return targets;
284
914
  }
285
-
915
+ var COVERAGE_TTL_MS = 60000;
916
+ function cachedCoverage(read, ttlMs = COVERAGE_TTL_MS) {
917
+ let cached;
918
+ const coverage = () => {
919
+ const now = Date.now();
920
+ if (!cached || now - cached.at > ttlMs) {
921
+ cached = { at: now, value: read().catch(() => null) };
922
+ }
923
+ return cached.value;
924
+ };
925
+ return Object.assign(coverage, {
926
+ forget: () => {
927
+ cached = undefined;
928
+ }
929
+ });
930
+ }
931
+ var dbStateWritesCoverage = cachedCoverage(async () => {
932
+ const row = await getSourceDb().selectFrom("state_writes").select("block_height").orderBy("block_height", "asc").limit(1).executeTakeFirst();
933
+ return row ? Number(row.block_height) : null;
934
+ });
286
935
  class PostgresBlockSource {
936
+ feed;
937
+ constructor(feed) {
938
+ this.feed = feed;
939
+ }
287
940
  async getTip() {
288
941
  const progress = await getSourceDb().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
289
942
  return progress ? Number(progress.highest_seen_block) : 0;
290
943
  }
291
944
  loadBlockRange(fromHeight, toHeight) {
292
- return loadBlockRange(getSourceDb(), fromHeight, toHeight);
945
+ return loadBlockRange(getSourceDb(), fromHeight, toHeight, {
946
+ stateWrites: this.feed
947
+ });
293
948
  }
294
949
  }
295
950
  var EVENT_FILTER_TO_INDEX_TYPE = {
@@ -375,15 +1030,18 @@ class PublicApiBlockSource {
375
1030
  eventTypes;
376
1031
  probeTargets;
377
1032
  needsTransactions;
378
- constructor(http, eventTypes, probeTargets, needsTransactions = true) {
1033
+ feed;
1034
+ constructor(http, eventTypes, probeTargets, needsTransactions = true, feed) {
379
1035
  this.http = http;
380
1036
  this.eventTypes = eventTypes;
381
1037
  this.probeTargets = probeTargets;
382
1038
  this.needsTransactions = needsTransactions;
1039
+ this.feed = feed;
383
1040
  }
384
1041
  walkTargets() {
385
1042
  const scoped = this.needsTransactions ? undefined : this.probeTargets;
386
- return this.eventTypes.flatMap((eventType) => {
1043
+ const types = this.feed ? this.eventTypes.filter((t) => !VM_INDEX_EVENT_TYPES.has(t)) : this.eventTypes;
1044
+ return types.flatMap((eventType) => {
387
1045
  const own = scoped?.filter((t) => t.eventType === eventType);
388
1046
  return own?.length ? own : [{ eventType }];
389
1047
  });
@@ -406,10 +1064,11 @@ class PublicApiBlockSource {
406
1064
  }
407
1065
  async loadBlockRange(fromHeight, toHeight) {
408
1066
  const withTx = !this.needsTransactions;
409
- const [blocks, txRows, eventLists] = await Promise.all([
1067
+ const [blocks, txRows, eventLists, writes] = await Promise.all([
410
1068
  this.http.walkBlocks(fromHeight, toHeight),
411
1069
  this.needsTransactions ? this.http.walkTransactions(fromHeight, toHeight) : Promise.resolve([]),
412
- Promise.all(this.walkTargets().map((t) => this.http.walkEvents(t.eventType, fromHeight, toHeight, withTx, t.contractId)))
1070
+ Promise.all(this.walkTargets().map((t) => this.http.walkEvents(t.eventType, fromHeight, toHeight, withTx, t.contractId))),
1071
+ this.feed ? this.walkWrites(fromHeight, toHeight, this.feed) : null
413
1072
  ]);
414
1073
  const events = dedupeEvents(eventLists.flat());
415
1074
  const map = new Map;
@@ -435,13 +1094,55 @@ class PublicApiBlockSource {
435
1094
  bd.events.push(reconstructEvent(e));
436
1095
  }
437
1096
  }
1097
+ if (writes) {
1098
+ for (const [height, fed] of writes) {
1099
+ const bd = map.get(height);
1100
+ if (!bd)
1101
+ continue;
1102
+ bd.txs.push(...fed.txs);
1103
+ bd.vmEvents = fed.vmEvents;
1104
+ }
1105
+ }
438
1106
  for (const bd of map.values()) {
439
- bd.txs.sort((a, b) => a.tx_index - b.tx_index);
1107
+ bd.txs.sort((a, b) => (a.tx_index ?? 0) - (b.tx_index ?? 0));
440
1108
  bd.events.sort((a, b) => a.event_index - b.event_index);
441
1109
  bd.vmEvents?.sort((a, b) => a.event_index - b.event_index);
442
1110
  }
443
1111
  return map;
444
1112
  }
1113
+ async walkWrites(fromHeight, toHeight, feed) {
1114
+ const walks = await Promise.all((feed.contracts ?? [undefined]).map((c) => this.http.walkStateWrites(fromHeight, toHeight, c)));
1115
+ const writesByHeight = new Map;
1116
+ const txsByHeight = new Map;
1117
+ for (const row of walks.flat()) {
1118
+ const writes = writesByHeight.get(row.block_height) ?? new Map;
1119
+ writes.set(row.ordinal, row);
1120
+ writesByHeight.set(row.block_height, writes);
1121
+ const tx = writeTx(row);
1122
+ if (tx) {
1123
+ const txs = txsByHeight.get(row.block_height) ?? new Map;
1124
+ txs.set(row.tx_index, tx);
1125
+ txsByHeight.set(row.block_height, txs);
1126
+ }
1127
+ }
1128
+ return stateWriteBlocks(new Map([...writesByHeight].map(([h, w]) => [
1129
+ h,
1130
+ [...w.values()].sort((a, b) => a.ordinal - b.ordinal)
1131
+ ])), new Map([...txsByHeight].map(([h, t]) => [h, [...t.values()]])));
1132
+ }
1133
+ }
1134
+ function writeTx(row) {
1135
+ if (row.tx_index === null || !row.tx_id)
1136
+ return null;
1137
+ return {
1138
+ tx_id: row.tx_id,
1139
+ tx_index: row.tx_index,
1140
+ type: row.tx_type ?? "",
1141
+ sender: row.tx_sender ?? "",
1142
+ status: row.tx_status ?? "",
1143
+ contract_id: row.tx_contract_id ?? null,
1144
+ function_name: row.tx_function_name ?? null
1145
+ };
445
1146
  }
446
1147
  var postgresBlockSource = new PostgresBlockSource;
447
1148
  function buildHttpClient() {
@@ -1097,7 +1798,7 @@ function __setReplayInFlightForTest(webhookId, inFlight) {
1097
1798
  }
1098
1799
  function replayDedupKey(subgraphName, tableName, row, replayId) {
1099
1800
  const canonical = `replay:${replayId}:${subgraphName}:${tableName}:${stableStringify(row)}`;
1100
- return createHash("sha256").update(canonical).digest("hex").slice(0, 32);
1801
+ return createHash2("sha256").update(canonical).digest("hex").slice(0, 32);
1101
1802
  }
1102
1803
  function stableStringify(obj) {
1103
1804
  const keys = Object.keys(obj).sort();
@@ -1108,7 +1809,7 @@ function stableStringify(obj) {
1108
1809
  }
1109
1810
  function deterministicReplayId(webhookId, fromBlock, toBlock, suffix) {
1110
1811
  const canonical = `${webhookId}:${fromBlock}:${toBlock}${suffix ? `:${suffix}` : ""}`;
1111
- return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
1812
+ return createHash2("sha256").update(canonical).digest("hex").slice(0, 16);
1112
1813
  }
1113
1814
  async function resolveSchemaName(db, subgraphName) {
1114
1815
  const row = await db.selectFrom("subgraphs").select("schema_name").where("name", "=", subgraphName).executeTakeFirst();
@@ -1148,12 +1849,12 @@ async function replayWebhook(input) {
1148
1849
  let enqueued = 0;
1149
1850
  let lastId = 0n;
1150
1851
  while (true) {
1151
- const { rows } = await sql`SELECT * FROM ${sql.raw(`"${schema}"."${tableName}"`)}
1152
- WHERE _block_height >= ${sql.lit(input.fromBlock)}
1153
- AND _block_height <= ${sql.lit(input.toBlock)}
1154
- AND _id > ${sql.lit(lastId)}
1852
+ const { rows } = await sql2`SELECT * FROM ${sql2.raw(`"${schema}"."${tableName}"`)}
1853
+ WHERE _block_height >= ${sql2.lit(input.fromBlock)}
1854
+ AND _block_height <= ${sql2.lit(input.toBlock)}
1855
+ AND _id > ${sql2.lit(lastId)}
1155
1856
  ORDER BY _id ASC
1156
- LIMIT ${sql.lit(BATCH_SIZE)}`.execute(db);
1857
+ LIMIT ${sql2.lit(BATCH_SIZE)}`.execute(db);
1157
1858
  if (rows.length === 0)
1158
1859
  break;
1159
1860
  scanned += rows.length;
@@ -1237,5 +1938,5 @@ export {
1237
1938
  replayWebhook
1238
1939
  };
1239
1940
 
1240
- //# debugId=82CDE01A57BAF7CC64756E2164756E21
1941
+ //# debugId=AEC33CA4613E4B7264756E2164756E21
1241
1942
  //# sourceMappingURL=replay.js.map