@secondlayer/subgraphs 4.4.1 → 5.0.1
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/README.md +13 -21
- package/dist/{src/index.d.ts → index.d.ts} +59 -189
- package/dist/index.js +2151 -0
- package/dist/index.js.map +24 -0
- package/dist/{src/runtime → runtime}/emitter.d.ts +17 -11
- package/dist/{src/runtime → runtime}/emitter.js +58 -18
- package/dist/runtime/emitter.js.map +19 -0
- package/dist/{src/runtime → runtime}/replay.d.ts +15 -1
- package/dist/{src/runtime → runtime}/replay.js +418 -723
- package/dist/{src/runtime → runtime}/replay.js.map +6 -6
- package/dist/{src/schema → schema}/index.d.ts +10 -69
- package/dist/{src/schema → schema}/index.js +188 -281
- package/dist/schema/index.js.map +13 -0
- package/dist/{src/testing → testing}/index.d.ts +65 -115
- package/dist/{src/testing → testing}/index.js +554 -968
- package/dist/testing/index.js.map +21 -0
- package/dist/{src/types.d.ts → types.d.ts} +10 -66
- package/dist/{src/validate.d.ts → validate.d.ts} +11 -69
- package/dist/{src/validate.js → validate.js} +18 -98
- package/dist/validate.js.map +10 -0
- package/package.json +18 -26
- package/dist/src/index.js +0 -6081
- package/dist/src/index.js.map +0 -44
- package/dist/src/runtime/block-processor.d.ts +0 -559
- package/dist/src/runtime/block-processor.js +0 -3810
- package/dist/src/runtime/block-processor.js.map +0 -31
- package/dist/src/runtime/catchup.d.ts +0 -473
- package/dist/src/runtime/catchup.js +0 -4228
- package/dist/src/runtime/catchup.js.map +0 -35
- package/dist/src/runtime/clarity.d.ts +0 -15
- package/dist/src/runtime/clarity.js +0 -41
- package/dist/src/runtime/clarity.js.map +0 -10
- package/dist/src/runtime/context.d.ts +0 -325
- package/dist/src/runtime/context.js +0 -936
- package/dist/src/runtime/context.js.map +0 -13
- package/dist/src/runtime/emitter.js.map +0 -19
- package/dist/src/runtime/processor.d.ts +0 -11
- package/dist/src/runtime/processor.js +0 -5372
- package/dist/src/runtime/processor.js.map +0 -40
- package/dist/src/runtime/reindex.d.ts +0 -539
- package/dist/src/runtime/reindex.js +0 -4802
- package/dist/src/runtime/reindex.js.map +0 -37
- package/dist/src/runtime/reorg.d.ts +0 -432
- package/dist/src/runtime/reorg.js +0 -4355
- package/dist/src/runtime/reorg.js.map +0 -36
- package/dist/src/runtime/runner.d.ts +0 -719
- package/dist/src/runtime/runner.js +0 -1332
- package/dist/src/runtime/runner.js.map +0 -16
- package/dist/src/runtime/source-matcher.d.ts +0 -304
- package/dist/src/runtime/source-matcher.js +0 -335
- package/dist/src/runtime/source-matcher.js.map +0 -10
- package/dist/src/runtime/stats.d.ts +0 -23
- package/dist/src/runtime/stats.js +0 -71
- package/dist/src/runtime/stats.js.map +0 -10
- package/dist/src/schema/index.js.map +0 -13
- package/dist/src/service.d.ts +0 -0
- package/dist/src/service.js +0 -5398
- package/dist/src/service.js.map +0 -41
- package/dist/src/testing/index.js.map +0 -22
- package/dist/src/triggers/index.d.ts +0 -381
- package/dist/src/triggers/index.js +0 -31
- package/dist/src/triggers/index.js.map +0 -10
- package/dist/src/validate.js.map +0 -10
- /package/dist/{src/types.js → types.js} +0 -0
- /package/dist/{src/types.js.map → types.js.map} +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// src/testing/harness.ts
|
|
2
|
+
import { normalizeAbi as normalizeAbi2, toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
|
|
3
3
|
|
|
4
4
|
// src/runtime/context.ts
|
|
5
5
|
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
6
|
-
import { formatUnits } from "@secondlayer/stacks/utils";
|
|
7
6
|
import { sql } from "kysely";
|
|
8
7
|
|
|
9
8
|
// src/schema/generator.ts
|
|
@@ -149,12 +148,6 @@ function emitFactoryDDL(schemaName) {
|
|
|
149
148
|
`CREATE INDEX IF NOT EXISTS idx_${schemaName}_factory_height ON ${schemaName}._factory_addresses (block_height)`
|
|
150
149
|
];
|
|
151
150
|
}
|
|
152
|
-
function emitForeignKeyDDL(schemaName, tableName, tableDef) {
|
|
153
|
-
return (tableDef.relations ?? []).map((rel) => {
|
|
154
|
-
const constraintName = quotePgIdent(`fk_${schemaName}_${tableName}_${rel.name}`);
|
|
155
|
-
return `ALTER TABLE ${quotePgIdent(schemaName)}.${quotePgIdent(tableName)} ADD CONSTRAINT ${constraintName} ` + `FOREIGN KEY (${rel.fields.map(quotePgIdent).join(", ")}) ` + `REFERENCES ${quotePgIdent(schemaName)}.${quotePgIdent(rel.references)} (${rel.referencedColumns.map(quotePgIdent).join(", ")})`;
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
151
|
function generateSubgraphSQL(def, schemaNameOverride) {
|
|
159
152
|
const schemaName = schemaNameOverride ?? pgSchemaName(def.name);
|
|
160
153
|
const statements = [];
|
|
@@ -170,9 +163,6 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
170
163
|
if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
|
|
171
164
|
statements.push(...emitFactoryDDL(schemaName));
|
|
172
165
|
}
|
|
173
|
-
for (const [tableName, tableDef] of Object.entries(def.schema)) {
|
|
174
|
-
statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
|
|
175
|
-
}
|
|
176
166
|
const hashInput = JSON.stringify({
|
|
177
167
|
name: def.name,
|
|
178
168
|
schema: def.schema,
|
|
@@ -278,13 +268,13 @@ function createChainReadClient(ctx) {
|
|
|
278
268
|
note: 'cache: "contract-constant" asserts this value cannot change; the node answered at its own tip'
|
|
279
269
|
});
|
|
280
270
|
}
|
|
281
|
-
const
|
|
271
|
+
const value = await readContract(nodeClient(rpcUrl), {
|
|
282
272
|
contract: contractId,
|
|
283
273
|
functionName: fn.name,
|
|
284
274
|
args: clarityArgs,
|
|
285
275
|
tip: ctx.indexBlockHash ?? undefined
|
|
286
276
|
});
|
|
287
|
-
return serializeCV(
|
|
277
|
+
return serializeCV(value);
|
|
288
278
|
});
|
|
289
279
|
if (!cached) {
|
|
290
280
|
await db.insertInto("chain_read_cache").values({
|
|
@@ -325,7 +315,6 @@ function createChainReadClient(ctx) {
|
|
|
325
315
|
}
|
|
326
316
|
|
|
327
317
|
// src/runtime/context.ts
|
|
328
|
-
var JOURNAL_RETENTION_BLOCKS = 300;
|
|
329
318
|
var journalEnsured = new Set;
|
|
330
319
|
function validateColumnName(name) {
|
|
331
320
|
if (!/^[a-z_][a-z0-9_]*$/i.test(name)) {
|
|
@@ -342,9 +331,9 @@ class SubgraphContext {
|
|
|
342
331
|
ops = [];
|
|
343
332
|
journal;
|
|
344
333
|
_client;
|
|
345
|
-
constructor(db,
|
|
334
|
+
constructor(db, pgSchemaName, subgraphSchema, block, tx, journal = false) {
|
|
346
335
|
this.db = db;
|
|
347
|
-
this.pgSchemaName =
|
|
336
|
+
this.pgSchemaName = pgSchemaName;
|
|
348
337
|
this.subgraphSchema = subgraphSchema;
|
|
349
338
|
this.block = block;
|
|
350
339
|
this._tx = tx;
|
|
@@ -448,20 +437,6 @@ class SubgraphContext {
|
|
|
448
437
|
return;
|
|
449
438
|
this.ops.length = checkpoint;
|
|
450
439
|
}
|
|
451
|
-
patch(table, where, set) {
|
|
452
|
-
this.update(table, where, set);
|
|
453
|
-
}
|
|
454
|
-
async patchOrInsert(table, key, row) {
|
|
455
|
-
const existing = await this.findOne(table, key);
|
|
456
|
-
const resolved = {};
|
|
457
|
-
for (const [k, v] of Object.entries(row)) {
|
|
458
|
-
resolved[k] = typeof v === "function" ? v(existing) : v;
|
|
459
|
-
}
|
|
460
|
-
this.upsert(table, key, resolved);
|
|
461
|
-
}
|
|
462
|
-
formatUnits(value, decimals) {
|
|
463
|
-
return formatUnits(value, decimals);
|
|
464
|
-
}
|
|
465
440
|
async readRows(table, where, limit) {
|
|
466
441
|
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
467
442
|
const { clause } = buildWhereClause(where);
|
|
@@ -563,47 +538,6 @@ class SubgraphContext {
|
|
|
563
538
|
return row && rowMatches(row, op.data) ? null : row;
|
|
564
539
|
}
|
|
565
540
|
}
|
|
566
|
-
async count(table, where) {
|
|
567
|
-
this.validateTable(table);
|
|
568
|
-
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
569
|
-
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
570
|
-
const { rows } = await sql.raw(`SELECT COUNT(*)::int AS count FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
571
|
-
return Number(rows[0]?.count ?? 0);
|
|
572
|
-
}
|
|
573
|
-
async sum(table, column, where) {
|
|
574
|
-
this.validateTable(table);
|
|
575
|
-
validateColumnName(column);
|
|
576
|
-
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
577
|
-
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
578
|
-
const { rows } = await sql.raw(`SELECT COALESCE(SUM("${column}"), 0) AS total FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
579
|
-
return BigInt(rows[0]?.total?.toString() ?? "0");
|
|
580
|
-
}
|
|
581
|
-
async min(table, column, where) {
|
|
582
|
-
this.validateTable(table);
|
|
583
|
-
validateColumnName(column);
|
|
584
|
-
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
585
|
-
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
586
|
-
const { rows } = await sql.raw(`SELECT MIN("${column}") AS val FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
587
|
-
const val = rows[0]?.val;
|
|
588
|
-
return val != null ? BigInt(val.toString()) : null;
|
|
589
|
-
}
|
|
590
|
-
async max(table, column, where) {
|
|
591
|
-
this.validateTable(table);
|
|
592
|
-
validateColumnName(column);
|
|
593
|
-
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
594
|
-
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
595
|
-
const { rows } = await sql.raw(`SELECT MAX("${column}") AS val FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
596
|
-
const val = rows[0]?.val;
|
|
597
|
-
return val != null ? BigInt(val.toString()) : null;
|
|
598
|
-
}
|
|
599
|
-
async countDistinct(table, column, where) {
|
|
600
|
-
this.validateTable(table);
|
|
601
|
-
validateColumnName(column);
|
|
602
|
-
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
603
|
-
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
604
|
-
const { rows } = await sql.raw(`SELECT COUNT(DISTINCT "${column}")::int AS count FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
605
|
-
return Number(rows[0]?.count ?? 0);
|
|
606
|
-
}
|
|
607
541
|
coerceRow(table, row) {
|
|
608
542
|
const tableDef = this.subgraphSchema[table];
|
|
609
543
|
if (!tableDef)
|
|
@@ -928,528 +862,172 @@ function buildWhereClause(where) {
|
|
|
928
862
|
return { clause: parts.join(" AND "), values: [] };
|
|
929
863
|
}
|
|
930
864
|
|
|
931
|
-
// src/runtime/
|
|
932
|
-
import {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
865
|
+
// src/runtime/runner.ts
|
|
866
|
+
import { getErrorMessage } from "@secondlayer/shared";
|
|
867
|
+
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
868
|
+
import {
|
|
869
|
+
clarityValueToJS,
|
|
870
|
+
deserializeCV as deserializeCV4,
|
|
871
|
+
toCamelCase as toCamelCase2
|
|
872
|
+
} from "@secondlayer/stacks/clarity";
|
|
873
|
+
|
|
874
|
+
// src/print-schema.ts
|
|
875
|
+
import {
|
|
876
|
+
deserializeCV as deserializeCV2
|
|
877
|
+
} from "@secondlayer/stacks/clarity";
|
|
878
|
+
function camelizeDataKey(str) {
|
|
879
|
+
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
941
880
|
}
|
|
942
|
-
function
|
|
943
|
-
if (
|
|
944
|
-
return
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
881
|
+
function camelizeKeys(obj) {
|
|
882
|
+
if (obj === null || obj === undefined)
|
|
883
|
+
return obj;
|
|
884
|
+
if (typeof obj !== "object")
|
|
885
|
+
return obj;
|
|
886
|
+
if (Array.isArray(obj))
|
|
887
|
+
return obj.map(camelizeKeys);
|
|
888
|
+
const result = {};
|
|
889
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
890
|
+
result[camelizeDataKey(k)] = camelizeKeys(v);
|
|
948
891
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
892
|
+
return result;
|
|
893
|
+
}
|
|
894
|
+
function cvToTree(cv) {
|
|
895
|
+
switch (cv.type) {
|
|
896
|
+
case "uint":
|
|
897
|
+
case "int":
|
|
898
|
+
return { kind: cv.type };
|
|
899
|
+
case "true":
|
|
900
|
+
case "false":
|
|
901
|
+
return { kind: "bool" };
|
|
902
|
+
case "address":
|
|
903
|
+
case "contract":
|
|
904
|
+
return { kind: "principal" };
|
|
905
|
+
case "buffer":
|
|
906
|
+
return { kind: "buffer", len: cv.value.length / 2 };
|
|
907
|
+
case "ascii":
|
|
908
|
+
return { kind: "ascii", len: cv.value.length };
|
|
909
|
+
case "utf8":
|
|
910
|
+
return { kind: "utf8", len: new TextEncoder().encode(cv.value).length };
|
|
911
|
+
case "none":
|
|
912
|
+
return { kind: "optional", inner: null };
|
|
913
|
+
case "some":
|
|
914
|
+
return { kind: "optional", inner: cvToTree(cv.value) };
|
|
915
|
+
case "ok":
|
|
916
|
+
return { kind: "response", ok: cvToTree(cv.value), err: null };
|
|
917
|
+
case "err":
|
|
918
|
+
return { kind: "response", ok: null, err: cvToTree(cv.value) };
|
|
919
|
+
case "list": {
|
|
920
|
+
let inner = null;
|
|
921
|
+
for (const el of cv.value) {
|
|
922
|
+
const t = cvToTree(el);
|
|
923
|
+
inner = inner ? unify(inner, t) : t;
|
|
924
|
+
}
|
|
925
|
+
return { kind: "list", inner };
|
|
926
|
+
}
|
|
927
|
+
case "tuple": {
|
|
928
|
+
const entries = new Map;
|
|
929
|
+
for (const [k, v] of Object.entries(cv.value)) {
|
|
930
|
+
entries.set(k, { tree: cvToTree(v), present: 1 });
|
|
931
|
+
}
|
|
932
|
+
return { kind: "tuple", count: 1, entries };
|
|
953
933
|
}
|
|
954
|
-
return decoded;
|
|
955
934
|
}
|
|
956
|
-
return data;
|
|
957
935
|
}
|
|
958
|
-
function
|
|
959
|
-
|
|
936
|
+
function unifyNullable(a, b) {
|
|
937
|
+
if (!a)
|
|
938
|
+
return b;
|
|
939
|
+
if (!b)
|
|
940
|
+
return a;
|
|
941
|
+
return unify(a, b);
|
|
960
942
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
943
|
+
var UNION_KIND_ORDER = {
|
|
944
|
+
uint: 0,
|
|
945
|
+
int: 1,
|
|
946
|
+
bool: 2,
|
|
947
|
+
principal: 3,
|
|
948
|
+
buffer: 4,
|
|
949
|
+
ascii: 5,
|
|
950
|
+
utf8: 6,
|
|
951
|
+
list: 7,
|
|
952
|
+
tuple: 8,
|
|
953
|
+
response: 9
|
|
954
|
+
};
|
|
955
|
+
function makeUnion(members) {
|
|
956
|
+
const sorted = [...members].sort((a, b) => (UNION_KIND_ORDER[a.kind] ?? 99) - (UNION_KIND_ORDER[b.kind] ?? 99));
|
|
957
|
+
return { kind: "union", members: sorted };
|
|
966
958
|
}
|
|
967
|
-
function
|
|
968
|
-
if (
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
973
|
-
re = new RegExp(`^${regex}$`);
|
|
974
|
-
patternCache.set(pattern, re);
|
|
959
|
+
function unify(a, b) {
|
|
960
|
+
if (a.kind === "optional" || b.kind === "optional") {
|
|
961
|
+
const ai = a.kind === "optional" ? a.inner : a;
|
|
962
|
+
const bi = b.kind === "optional" ? b.inner : b;
|
|
963
|
+
return { kind: "optional", inner: unifyNullable(ai, bi) };
|
|
975
964
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
if (
|
|
981
|
-
return
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
965
|
+
if (a.kind === "union")
|
|
966
|
+
return unionAdd(a.members, b);
|
|
967
|
+
if (b.kind === "union")
|
|
968
|
+
return unionAdd(b.members, a);
|
|
969
|
+
if (a.kind !== b.kind)
|
|
970
|
+
return makeUnion([a, b]);
|
|
971
|
+
switch (a.kind) {
|
|
972
|
+
case "uint":
|
|
973
|
+
case "int":
|
|
974
|
+
case "bool":
|
|
975
|
+
case "principal":
|
|
976
|
+
return a;
|
|
977
|
+
case "buffer":
|
|
978
|
+
case "ascii":
|
|
979
|
+
case "utf8":
|
|
980
|
+
return { kind: a.kind, len: Math.max(a.len, b.len) };
|
|
981
|
+
case "list":
|
|
982
|
+
return {
|
|
983
|
+
kind: "list",
|
|
984
|
+
inner: unifyNullable(a.inner, b.inner)
|
|
985
|
+
};
|
|
986
|
+
case "response": {
|
|
987
|
+
const rb = b;
|
|
988
|
+
return {
|
|
989
|
+
kind: "response",
|
|
990
|
+
ok: unifyNullable(a.ok, rb.ok),
|
|
991
|
+
err: unifyNullable(a.err, rb.err)
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
case "tuple": {
|
|
995
|
+
const tb = b;
|
|
996
|
+
const entries = new Map([...a.entries].map(([k, e]) => [k, { ...e }]));
|
|
997
|
+
for (const [k, e] of tb.entries) {
|
|
998
|
+
const existing = entries.get(k);
|
|
999
|
+
entries.set(k, existing ? {
|
|
1000
|
+
tree: unify(existing.tree, e.tree),
|
|
1001
|
+
present: existing.present + e.present
|
|
1002
|
+
} : { ...e });
|
|
1003
|
+
}
|
|
1004
|
+
return { kind: "tuple", count: a.count + tb.count, entries };
|
|
1005
|
+
}
|
|
992
1006
|
}
|
|
993
|
-
return cur;
|
|
994
|
-
}
|
|
995
|
-
function traitAllows(filter, contractId, traitContracts) {
|
|
996
|
-
const trait = filter.trait;
|
|
997
|
-
if (!trait)
|
|
998
|
-
return true;
|
|
999
|
-
if (!contractId)
|
|
1000
|
-
return false;
|
|
1001
|
-
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
1002
1007
|
}
|
|
1003
|
-
function
|
|
1004
|
-
|
|
1008
|
+
function unionAdd(members, t) {
|
|
1009
|
+
if (t.kind === "union") {
|
|
1010
|
+
let acc = { kind: "union", members };
|
|
1011
|
+
for (const m of t.members)
|
|
1012
|
+
acc = unify(acc, m);
|
|
1013
|
+
return acc;
|
|
1014
|
+
}
|
|
1015
|
+
const next = [...members];
|
|
1016
|
+
for (let i = 0;i < next.length; i++) {
|
|
1017
|
+
const member = next[i];
|
|
1018
|
+
if (!member)
|
|
1019
|
+
continue;
|
|
1020
|
+
const merged = unify(member, t);
|
|
1021
|
+
if (merged.kind !== "union") {
|
|
1022
|
+
next[i] = merged;
|
|
1023
|
+
return makeUnion(next);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
next.push(t);
|
|
1027
|
+
return makeUnion(next);
|
|
1005
1028
|
}
|
|
1006
|
-
function
|
|
1007
|
-
|
|
1008
|
-
return;
|
|
1009
|
-
const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
|
|
1010
|
-
return id || undefined;
|
|
1011
|
-
}
|
|
1012
|
-
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map, vmEventsByTx = new Map) {
|
|
1013
|
-
const results = [];
|
|
1014
|
-
switch (filter.type) {
|
|
1015
|
-
case "stx_transfer":
|
|
1016
|
-
case "stx_mint":
|
|
1017
|
-
case "stx_burn":
|
|
1018
|
-
case "stx_lock": {
|
|
1019
|
-
const eventType = `${filter.type}_event`;
|
|
1020
|
-
for (const tx of transactions) {
|
|
1021
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1022
|
-
const matched = txEvents.filter((e) => e.type === eventType);
|
|
1023
|
-
if (matched.length === 0)
|
|
1024
|
-
continue;
|
|
1025
|
-
const filtered = matched.filter((e) => {
|
|
1026
|
-
const data = e.data;
|
|
1027
|
-
if (!data)
|
|
1028
|
-
return false;
|
|
1029
|
-
if ("sender" in filter && filter.sender) {
|
|
1030
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
1031
|
-
return false;
|
|
1032
|
-
}
|
|
1033
|
-
if ("recipient" in filter && filter.recipient) {
|
|
1034
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
1035
|
-
return false;
|
|
1036
|
-
}
|
|
1037
|
-
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
1038
|
-
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
1039
|
-
return false;
|
|
1040
|
-
}
|
|
1041
|
-
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
1042
|
-
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
1043
|
-
if (amount < filter.minAmount)
|
|
1044
|
-
return false;
|
|
1045
|
-
}
|
|
1046
|
-
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
1047
|
-
const amount = BigInt(data.amount ?? "0");
|
|
1048
|
-
if (amount > filter.maxAmount)
|
|
1049
|
-
return false;
|
|
1050
|
-
}
|
|
1051
|
-
return true;
|
|
1052
|
-
});
|
|
1053
|
-
if (filtered.length > 0) {
|
|
1054
|
-
results.push({ tx, events: filtered });
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
break;
|
|
1058
|
-
}
|
|
1059
|
-
case "ft_transfer":
|
|
1060
|
-
case "ft_mint":
|
|
1061
|
-
case "ft_burn": {
|
|
1062
|
-
const eventType = `${filter.type}_event`;
|
|
1063
|
-
for (const tx of transactions) {
|
|
1064
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1065
|
-
const matched = txEvents.filter((e) => {
|
|
1066
|
-
if (e.type !== eventType)
|
|
1067
|
-
return false;
|
|
1068
|
-
const data = e.data;
|
|
1069
|
-
if (!data)
|
|
1070
|
-
return false;
|
|
1071
|
-
if (filter.assetIdentifier) {
|
|
1072
|
-
const assetId = data.asset_identifier;
|
|
1073
|
-
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1074
|
-
return false;
|
|
1075
|
-
}
|
|
1076
|
-
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1077
|
-
return false;
|
|
1078
|
-
if ("sender" in filter && filter.sender) {
|
|
1079
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
1080
|
-
return false;
|
|
1081
|
-
}
|
|
1082
|
-
if ("recipient" in filter && filter.recipient) {
|
|
1083
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
1084
|
-
return false;
|
|
1085
|
-
}
|
|
1086
|
-
if (filter.minAmount !== undefined) {
|
|
1087
|
-
const amount = BigInt(data.amount ?? "0");
|
|
1088
|
-
if (amount < filter.minAmount)
|
|
1089
|
-
return false;
|
|
1090
|
-
}
|
|
1091
|
-
return true;
|
|
1092
|
-
});
|
|
1093
|
-
if (matched.length > 0) {
|
|
1094
|
-
results.push({ tx, events: matched });
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
break;
|
|
1098
|
-
}
|
|
1099
|
-
case "nft_transfer":
|
|
1100
|
-
case "nft_mint":
|
|
1101
|
-
case "nft_burn": {
|
|
1102
|
-
const eventType = `${filter.type}_event`;
|
|
1103
|
-
for (const tx of transactions) {
|
|
1104
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1105
|
-
const matched = txEvents.filter((e) => {
|
|
1106
|
-
if (e.type !== eventType)
|
|
1107
|
-
return false;
|
|
1108
|
-
const data = e.data;
|
|
1109
|
-
if (!data)
|
|
1110
|
-
return false;
|
|
1111
|
-
if (filter.assetIdentifier) {
|
|
1112
|
-
const assetId = data.asset_identifier;
|
|
1113
|
-
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1114
|
-
return false;
|
|
1115
|
-
}
|
|
1116
|
-
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1117
|
-
return false;
|
|
1118
|
-
if ("sender" in filter && filter.sender) {
|
|
1119
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
1120
|
-
return false;
|
|
1121
|
-
}
|
|
1122
|
-
if ("recipient" in filter && filter.recipient) {
|
|
1123
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
1124
|
-
return false;
|
|
1125
|
-
}
|
|
1126
|
-
return true;
|
|
1127
|
-
});
|
|
1128
|
-
if (matched.length > 0) {
|
|
1129
|
-
results.push({ tx, events: matched });
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
break;
|
|
1133
|
-
}
|
|
1134
|
-
case "contract_call": {
|
|
1135
|
-
for (const tx of transactions) {
|
|
1136
|
-
if (tx.type !== "contract_call")
|
|
1137
|
-
continue;
|
|
1138
|
-
if (filter.contractId) {
|
|
1139
|
-
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
1140
|
-
continue;
|
|
1141
|
-
}
|
|
1142
|
-
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
1143
|
-
continue;
|
|
1144
|
-
if (filter.functionName) {
|
|
1145
|
-
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
1146
|
-
continue;
|
|
1147
|
-
}
|
|
1148
|
-
if (filter.caller) {
|
|
1149
|
-
if (!matchPattern(tx.sender, filter.caller))
|
|
1150
|
-
continue;
|
|
1151
|
-
}
|
|
1152
|
-
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
1153
|
-
continue;
|
|
1154
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1155
|
-
results.push({ tx, events: txEvents });
|
|
1156
|
-
}
|
|
1157
|
-
break;
|
|
1158
|
-
}
|
|
1159
|
-
case "contract_deploy": {
|
|
1160
|
-
for (const tx of transactions) {
|
|
1161
|
-
if (tx.type !== "smart_contract")
|
|
1162
|
-
continue;
|
|
1163
|
-
if (filter.deployer) {
|
|
1164
|
-
if (!matchPattern(tx.sender, filter.deployer))
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
if (filter.contractName) {
|
|
1168
|
-
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
1169
|
-
if (!matchPattern(name, filter.contractName))
|
|
1170
|
-
continue;
|
|
1171
|
-
}
|
|
1172
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1173
|
-
results.push({ tx, events: txEvents });
|
|
1174
|
-
}
|
|
1175
|
-
break;
|
|
1176
|
-
}
|
|
1177
|
-
case "nested_contract_call":
|
|
1178
|
-
case "var_set":
|
|
1179
|
-
case "map_set":
|
|
1180
|
-
case "map_insert":
|
|
1181
|
-
case "map_delete": {
|
|
1182
|
-
for (const tx of transactions) {
|
|
1183
|
-
const txEvents = vmEventsByTx.get(tx.tx_id) ?? [];
|
|
1184
|
-
const matched = txEvents.filter((e) => {
|
|
1185
|
-
if (e.type !== filter.type)
|
|
1186
|
-
return false;
|
|
1187
|
-
const data = e.data;
|
|
1188
|
-
if (!data)
|
|
1189
|
-
return false;
|
|
1190
|
-
const contractId = printContractId(data);
|
|
1191
|
-
if ("contractId" in filter && filter.contractId) {
|
|
1192
|
-
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
1193
|
-
return false;
|
|
1194
|
-
}
|
|
1195
|
-
if (!traitAllows(filter, contractId, traitContracts))
|
|
1196
|
-
return false;
|
|
1197
|
-
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
1198
|
-
return false;
|
|
1199
|
-
if (filter.type === "nested_contract_call") {
|
|
1200
|
-
if (filter.functionName) {
|
|
1201
|
-
if (typeof data.function_name !== "string" || !matchPattern(data.function_name, filter.functionName))
|
|
1202
|
-
return false;
|
|
1203
|
-
}
|
|
1204
|
-
if (filter.caller) {
|
|
1205
|
-
if (typeof data.caller !== "string" || !matchPattern(data.caller, filter.caller))
|
|
1206
|
-
return false;
|
|
1207
|
-
}
|
|
1208
|
-
if (filter.sender) {
|
|
1209
|
-
if (typeof data.sender !== "string" || !matchPattern(data.sender, filter.sender))
|
|
1210
|
-
return false;
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
if (filter.type === "var_set" && filter.varName) {
|
|
1214
|
-
if (typeof data.var_name !== "string" || !matchPattern(data.var_name, filter.varName))
|
|
1215
|
-
return false;
|
|
1216
|
-
}
|
|
1217
|
-
if ((filter.type === "map_set" || filter.type === "map_insert" || filter.type === "map_delete") && filter.map) {
|
|
1218
|
-
if (typeof data.map_name !== "string" || !matchPattern(data.map_name, filter.map))
|
|
1219
|
-
return false;
|
|
1220
|
-
}
|
|
1221
|
-
return true;
|
|
1222
|
-
});
|
|
1223
|
-
if (matched.length > 0)
|
|
1224
|
-
results.push({ tx, events: matched });
|
|
1225
|
-
}
|
|
1226
|
-
break;
|
|
1227
|
-
}
|
|
1228
|
-
case "print_event": {
|
|
1229
|
-
for (const tx of transactions) {
|
|
1230
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1231
|
-
const matched = txEvents.filter((e) => {
|
|
1232
|
-
if (e.type !== "smart_contract_event" && e.type !== "contract_event")
|
|
1233
|
-
return false;
|
|
1234
|
-
const data = e.data;
|
|
1235
|
-
if (!data)
|
|
1236
|
-
return false;
|
|
1237
|
-
if (data.topic !== "print")
|
|
1238
|
-
return false;
|
|
1239
|
-
const contractId = printContractId(data);
|
|
1240
|
-
if (filter.contractId) {
|
|
1241
|
-
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
1242
|
-
return false;
|
|
1243
|
-
}
|
|
1244
|
-
if (!traitAllows(filter, contractId, traitContracts))
|
|
1245
|
-
return false;
|
|
1246
|
-
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
1247
|
-
return false;
|
|
1248
|
-
return true;
|
|
1249
|
-
});
|
|
1250
|
-
if (matched.length > 0) {
|
|
1251
|
-
results.push({ tx, events: matched });
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
break;
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
return results;
|
|
1258
|
-
}
|
|
1259
|
-
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map, vmEvents = []) {
|
|
1260
|
-
const eventsByTx = new Map;
|
|
1261
|
-
for (const event of events) {
|
|
1262
|
-
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
1263
|
-
list.push(event);
|
|
1264
|
-
eventsByTx.set(event.tx_id, list);
|
|
1265
|
-
}
|
|
1266
|
-
const vmEventsByTx = new Map;
|
|
1267
|
-
for (const event of vmEvents) {
|
|
1268
|
-
const list = vmEventsByTx.get(event.tx_id) ?? [];
|
|
1269
|
-
list.push(event);
|
|
1270
|
-
vmEventsByTx.set(event.tx_id, list);
|
|
1271
|
-
}
|
|
1272
|
-
const seen = new Set;
|
|
1273
|
-
const results = [];
|
|
1274
|
-
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
1275
|
-
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts, vmEventsByTx);
|
|
1276
|
-
for (const match of matches) {
|
|
1277
|
-
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
1278
|
-
if (!seen.has(dedupeKey)) {
|
|
1279
|
-
seen.add(dedupeKey);
|
|
1280
|
-
results.push({ ...match, sourceName });
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
return results;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
// src/runtime/runner.ts
|
|
1288
|
-
import { getErrorMessage } from "@secondlayer/shared";
|
|
1289
|
-
import { logger as logger4 } from "@secondlayer/shared/logger";
|
|
1290
|
-
import {
|
|
1291
|
-
clarityValueToJS,
|
|
1292
|
-
deserializeCV as deserializeCV4,
|
|
1293
|
-
toCamelCase as toCamelCase2
|
|
1294
|
-
} from "@secondlayer/stacks/clarity";
|
|
1295
|
-
|
|
1296
|
-
// src/print-schema.ts
|
|
1297
|
-
import {
|
|
1298
|
-
deserializeCV as deserializeCV3
|
|
1299
|
-
} from "@secondlayer/stacks/clarity";
|
|
1300
|
-
function camelizeDataKey(str) {
|
|
1301
|
-
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1302
|
-
}
|
|
1303
|
-
function camelizeKeys(obj) {
|
|
1304
|
-
if (obj === null || obj === undefined)
|
|
1305
|
-
return obj;
|
|
1306
|
-
if (typeof obj !== "object")
|
|
1307
|
-
return obj;
|
|
1308
|
-
if (Array.isArray(obj))
|
|
1309
|
-
return obj.map(camelizeKeys);
|
|
1310
|
-
const result = {};
|
|
1311
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
1312
|
-
result[camelizeDataKey(k)] = camelizeKeys(v);
|
|
1313
|
-
}
|
|
1314
|
-
return result;
|
|
1315
|
-
}
|
|
1316
|
-
function cvToTree(cv) {
|
|
1317
|
-
switch (cv.type) {
|
|
1318
|
-
case "uint":
|
|
1319
|
-
case "int":
|
|
1320
|
-
return { kind: cv.type };
|
|
1321
|
-
case "true":
|
|
1322
|
-
case "false":
|
|
1323
|
-
return { kind: "bool" };
|
|
1324
|
-
case "address":
|
|
1325
|
-
case "contract":
|
|
1326
|
-
return { kind: "principal" };
|
|
1327
|
-
case "buffer":
|
|
1328
|
-
return { kind: "buffer", len: cv.value.length / 2 };
|
|
1329
|
-
case "ascii":
|
|
1330
|
-
return { kind: "ascii", len: cv.value.length };
|
|
1331
|
-
case "utf8":
|
|
1332
|
-
return { kind: "utf8", len: new TextEncoder().encode(cv.value).length };
|
|
1333
|
-
case "none":
|
|
1334
|
-
return { kind: "optional", inner: null };
|
|
1335
|
-
case "some":
|
|
1336
|
-
return { kind: "optional", inner: cvToTree(cv.value) };
|
|
1337
|
-
case "ok":
|
|
1338
|
-
return { kind: "response", ok: cvToTree(cv.value), err: null };
|
|
1339
|
-
case "err":
|
|
1340
|
-
return { kind: "response", ok: null, err: cvToTree(cv.value) };
|
|
1341
|
-
case "list": {
|
|
1342
|
-
let inner = null;
|
|
1343
|
-
for (const el of cv.value) {
|
|
1344
|
-
const t = cvToTree(el);
|
|
1345
|
-
inner = inner ? unify(inner, t) : t;
|
|
1346
|
-
}
|
|
1347
|
-
return { kind: "list", inner };
|
|
1348
|
-
}
|
|
1349
|
-
case "tuple": {
|
|
1350
|
-
const entries = new Map;
|
|
1351
|
-
for (const [k, v] of Object.entries(cv.value)) {
|
|
1352
|
-
entries.set(k, { tree: cvToTree(v), present: 1 });
|
|
1353
|
-
}
|
|
1354
|
-
return { kind: "tuple", count: 1, entries };
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
function unifyNullable(a, b) {
|
|
1359
|
-
if (!a)
|
|
1360
|
-
return b;
|
|
1361
|
-
if (!b)
|
|
1362
|
-
return a;
|
|
1363
|
-
return unify(a, b);
|
|
1364
|
-
}
|
|
1365
|
-
var UNION_KIND_ORDER = {
|
|
1366
|
-
uint: 0,
|
|
1367
|
-
int: 1,
|
|
1368
|
-
bool: 2,
|
|
1369
|
-
principal: 3,
|
|
1370
|
-
buffer: 4,
|
|
1371
|
-
ascii: 5,
|
|
1372
|
-
utf8: 6,
|
|
1373
|
-
list: 7,
|
|
1374
|
-
tuple: 8,
|
|
1375
|
-
response: 9
|
|
1376
|
-
};
|
|
1377
|
-
function makeUnion(members) {
|
|
1378
|
-
const sorted = [...members].sort((a, b) => (UNION_KIND_ORDER[a.kind] ?? 99) - (UNION_KIND_ORDER[b.kind] ?? 99));
|
|
1379
|
-
return { kind: "union", members: sorted };
|
|
1380
|
-
}
|
|
1381
|
-
function unify(a, b) {
|
|
1382
|
-
if (a.kind === "optional" || b.kind === "optional") {
|
|
1383
|
-
const ai = a.kind === "optional" ? a.inner : a;
|
|
1384
|
-
const bi = b.kind === "optional" ? b.inner : b;
|
|
1385
|
-
return { kind: "optional", inner: unifyNullable(ai, bi) };
|
|
1386
|
-
}
|
|
1387
|
-
if (a.kind === "union")
|
|
1388
|
-
return unionAdd(a.members, b);
|
|
1389
|
-
if (b.kind === "union")
|
|
1390
|
-
return unionAdd(b.members, a);
|
|
1391
|
-
if (a.kind !== b.kind)
|
|
1392
|
-
return makeUnion([a, b]);
|
|
1393
|
-
switch (a.kind) {
|
|
1394
|
-
case "uint":
|
|
1395
|
-
case "int":
|
|
1396
|
-
case "bool":
|
|
1397
|
-
case "principal":
|
|
1398
|
-
return a;
|
|
1399
|
-
case "buffer":
|
|
1400
|
-
case "ascii":
|
|
1401
|
-
case "utf8":
|
|
1402
|
-
return { kind: a.kind, len: Math.max(a.len, b.len) };
|
|
1403
|
-
case "list":
|
|
1404
|
-
return {
|
|
1405
|
-
kind: "list",
|
|
1406
|
-
inner: unifyNullable(a.inner, b.inner)
|
|
1407
|
-
};
|
|
1408
|
-
case "response": {
|
|
1409
|
-
const rb = b;
|
|
1410
|
-
return {
|
|
1411
|
-
kind: "response",
|
|
1412
|
-
ok: unifyNullable(a.ok, rb.ok),
|
|
1413
|
-
err: unifyNullable(a.err, rb.err)
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
case "tuple": {
|
|
1417
|
-
const tb = b;
|
|
1418
|
-
const entries = new Map([...a.entries].map(([k, e]) => [k, { ...e }]));
|
|
1419
|
-
for (const [k, e] of tb.entries) {
|
|
1420
|
-
const existing = entries.get(k);
|
|
1421
|
-
entries.set(k, existing ? {
|
|
1422
|
-
tree: unify(existing.tree, e.tree),
|
|
1423
|
-
present: existing.present + e.present
|
|
1424
|
-
} : { ...e });
|
|
1425
|
-
}
|
|
1426
|
-
return { kind: "tuple", count: a.count + tb.count, entries };
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
function unionAdd(members, t) {
|
|
1431
|
-
if (t.kind === "union") {
|
|
1432
|
-
let acc = { kind: "union", members };
|
|
1433
|
-
for (const m of t.members)
|
|
1434
|
-
acc = unify(acc, m);
|
|
1435
|
-
return acc;
|
|
1436
|
-
}
|
|
1437
|
-
const next = [...members];
|
|
1438
|
-
for (let i = 0;i < next.length; i++) {
|
|
1439
|
-
const member = next[i];
|
|
1440
|
-
if (!member)
|
|
1441
|
-
continue;
|
|
1442
|
-
const merged = unify(member, t);
|
|
1443
|
-
if (merged.kind !== "union") {
|
|
1444
|
-
next[i] = merged;
|
|
1445
|
-
return makeUnion(next);
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
next.push(t);
|
|
1449
|
-
return makeUnion(next);
|
|
1450
|
-
}
|
|
1451
|
-
function wrapOptional(t) {
|
|
1452
|
-
return t.kind === "optional" ? t : { kind: "optional", inner: t };
|
|
1029
|
+
function wrapOptional(t) {
|
|
1030
|
+
return t.kind === "optional" ? t : { kind: "optional", inner: t };
|
|
1453
1031
|
}
|
|
1454
1032
|
function renderClarity(t) {
|
|
1455
1033
|
if (!t)
|
|
@@ -1576,7 +1154,7 @@ function inferPrintTopics(samples) {
|
|
|
1576
1154
|
let decoded = 0;
|
|
1577
1155
|
for (const p of picked) {
|
|
1578
1156
|
try {
|
|
1579
|
-
const cv =
|
|
1157
|
+
const cv = deserializeCV2(p.rawHex);
|
|
1580
1158
|
decoded++;
|
|
1581
1159
|
if (cv.type === "tuple")
|
|
1582
1160
|
tuples.push(cv);
|
|
@@ -1610,221 +1188,395 @@ function inferPrintTopics(samples) {
|
|
|
1610
1188
|
}
|
|
1611
1189
|
}
|
|
1612
1190
|
}
|
|
1613
|
-
for (const [name, st] of [...stats].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1614
|
-
const field = {
|
|
1615
|
-
name,
|
|
1616
|
-
camel_name: camelizeDataKey(name),
|
|
1617
|
-
clarity_type: renderClarity(st.tree),
|
|
1618
|
-
ts_type: renderTs(st.tree),
|
|
1619
|
-
column_type: toColumnType(st.tree),
|
|
1620
|
-
always_present: st.present === tuples.length
|
|
1621
|
-
};
|
|
1622
|
-
if (st.optionalSeen) {
|
|
1623
|
-
field.optional_some_rate = (st.present - st.noneCount) / st.present;
|
|
1191
|
+
for (const [name, st] of [...stats].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1192
|
+
const field = {
|
|
1193
|
+
name,
|
|
1194
|
+
camel_name: camelizeDataKey(name),
|
|
1195
|
+
clarity_type: renderClarity(st.tree),
|
|
1196
|
+
ts_type: renderTs(st.tree),
|
|
1197
|
+
column_type: toColumnType(st.tree),
|
|
1198
|
+
always_present: st.present === tuples.length
|
|
1199
|
+
};
|
|
1200
|
+
if (st.optionalSeen) {
|
|
1201
|
+
field.optional_some_rate = (st.present - st.noneCount) / st.present;
|
|
1202
|
+
}
|
|
1203
|
+
fields.push(field);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
out.push({
|
|
1207
|
+
topic,
|
|
1208
|
+
count: rows.length,
|
|
1209
|
+
first_height: first,
|
|
1210
|
+
last_height: last,
|
|
1211
|
+
non_tuple: nonTuple,
|
|
1212
|
+
fields
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
return out.sort((a, b) => b.count - a.count);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/runtime/clarity.ts
|
|
1219
|
+
import { cvToValue, deserializeCV as deserializeCV3 } from "@secondlayer/stacks/clarity";
|
|
1220
|
+
function decodeClarityValue(hex) {
|
|
1221
|
+
try {
|
|
1222
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1223
|
+
const cv = deserializeCV3(cleanHex);
|
|
1224
|
+
return cvToValue(cv);
|
|
1225
|
+
} catch {
|
|
1226
|
+
return hex;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
function decodeEventData(data) {
|
|
1230
|
+
if (typeof data === "string" && data.startsWith("0x") && data.length > 10) {
|
|
1231
|
+
return decodeClarityValue(data);
|
|
1232
|
+
}
|
|
1233
|
+
if (Array.isArray(data)) {
|
|
1234
|
+
return data.map(decodeEventData);
|
|
1235
|
+
}
|
|
1236
|
+
if (typeof data === "object" && data !== null) {
|
|
1237
|
+
const decoded = {};
|
|
1238
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1239
|
+
decoded[key] = decodeEventData(value);
|
|
1240
|
+
}
|
|
1241
|
+
return decoded;
|
|
1242
|
+
}
|
|
1243
|
+
return data;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/runtime/print-validate.ts
|
|
1247
|
+
var COLUMN_TYPES = new Set([
|
|
1248
|
+
"text",
|
|
1249
|
+
"uint",
|
|
1250
|
+
"int",
|
|
1251
|
+
"principal",
|
|
1252
|
+
"boolean",
|
|
1253
|
+
"timestamp",
|
|
1254
|
+
"jsonb"
|
|
1255
|
+
]);
|
|
1256
|
+
|
|
1257
|
+
// src/runtime/source-matcher.ts
|
|
1258
|
+
var patternCache = new Map;
|
|
1259
|
+
function matchAny(value, pattern) {
|
|
1260
|
+
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
1261
|
+
}
|
|
1262
|
+
function matchPattern(value, pattern) {
|
|
1263
|
+
if (!pattern.includes("*"))
|
|
1264
|
+
return value === pattern;
|
|
1265
|
+
let re = patternCache.get(pattern);
|
|
1266
|
+
if (!re) {
|
|
1267
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
1268
|
+
re = new RegExp(`^${regex}$`);
|
|
1269
|
+
patternCache.set(pattern, re);
|
|
1270
|
+
}
|
|
1271
|
+
return re.test(value);
|
|
1272
|
+
}
|
|
1273
|
+
var EMPTY_SET = new Set;
|
|
1274
|
+
function factoryAllows(filter, contractId, factoryContracts) {
|
|
1275
|
+
if (!filter.factory)
|
|
1276
|
+
return true;
|
|
1277
|
+
if (!contractId)
|
|
1278
|
+
return false;
|
|
1279
|
+
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
1280
|
+
}
|
|
1281
|
+
function traitAllows(filter, contractId, traitContracts) {
|
|
1282
|
+
const trait = filter.trait;
|
|
1283
|
+
if (!trait)
|
|
1284
|
+
return true;
|
|
1285
|
+
if (!contractId)
|
|
1286
|
+
return false;
|
|
1287
|
+
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
1288
|
+
}
|
|
1289
|
+
function assetContract(assetId) {
|
|
1290
|
+
return assetId?.split("::")[0];
|
|
1291
|
+
}
|
|
1292
|
+
function printContractId(data) {
|
|
1293
|
+
if (!data)
|
|
1294
|
+
return;
|
|
1295
|
+
const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
|
|
1296
|
+
return id || undefined;
|
|
1297
|
+
}
|
|
1298
|
+
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map, vmEventsByTx = new Map) {
|
|
1299
|
+
const results = [];
|
|
1300
|
+
switch (filter.type) {
|
|
1301
|
+
case "stx_transfer":
|
|
1302
|
+
case "stx_mint":
|
|
1303
|
+
case "stx_burn":
|
|
1304
|
+
case "stx_lock": {
|
|
1305
|
+
const eventType = `${filter.type}_event`;
|
|
1306
|
+
for (const tx of transactions) {
|
|
1307
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1308
|
+
const matched = txEvents.filter((e) => e.type === eventType);
|
|
1309
|
+
if (matched.length === 0)
|
|
1310
|
+
continue;
|
|
1311
|
+
const filtered = matched.filter((e) => {
|
|
1312
|
+
const data = e.data;
|
|
1313
|
+
if (!data)
|
|
1314
|
+
return false;
|
|
1315
|
+
if ("sender" in filter && filter.sender) {
|
|
1316
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1320
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
1324
|
+
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1327
|
+
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
1328
|
+
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
1329
|
+
if (amount < filter.minAmount)
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
1333
|
+
const amount = BigInt(data.amount ?? "0");
|
|
1334
|
+
if (amount > filter.maxAmount)
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
return true;
|
|
1338
|
+
});
|
|
1339
|
+
if (filtered.length > 0) {
|
|
1340
|
+
results.push({ tx, events: filtered });
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
case "ft_transfer":
|
|
1346
|
+
case "ft_mint":
|
|
1347
|
+
case "ft_burn": {
|
|
1348
|
+
const eventType = `${filter.type}_event`;
|
|
1349
|
+
for (const tx of transactions) {
|
|
1350
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1351
|
+
const matched = txEvents.filter((e) => {
|
|
1352
|
+
if (e.type !== eventType)
|
|
1353
|
+
return false;
|
|
1354
|
+
const data = e.data;
|
|
1355
|
+
if (!data)
|
|
1356
|
+
return false;
|
|
1357
|
+
if (filter.assetIdentifier) {
|
|
1358
|
+
const assetId = data.asset_identifier;
|
|
1359
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1363
|
+
return false;
|
|
1364
|
+
if ("sender" in filter && filter.sender) {
|
|
1365
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1369
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1370
|
+
return false;
|
|
1371
|
+
}
|
|
1372
|
+
if (filter.minAmount !== undefined) {
|
|
1373
|
+
const amount = BigInt(data.amount ?? "0");
|
|
1374
|
+
if (amount < filter.minAmount)
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
return true;
|
|
1378
|
+
});
|
|
1379
|
+
if (matched.length > 0) {
|
|
1380
|
+
results.push({ tx, events: matched });
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
case "nft_transfer":
|
|
1386
|
+
case "nft_mint":
|
|
1387
|
+
case "nft_burn": {
|
|
1388
|
+
const eventType = `${filter.type}_event`;
|
|
1389
|
+
for (const tx of transactions) {
|
|
1390
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1391
|
+
const matched = txEvents.filter((e) => {
|
|
1392
|
+
if (e.type !== eventType)
|
|
1393
|
+
return false;
|
|
1394
|
+
const data = e.data;
|
|
1395
|
+
if (!data)
|
|
1396
|
+
return false;
|
|
1397
|
+
if (filter.assetIdentifier) {
|
|
1398
|
+
const assetId = data.asset_identifier;
|
|
1399
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1403
|
+
return false;
|
|
1404
|
+
if ("sender" in filter && filter.sender) {
|
|
1405
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1406
|
+
return false;
|
|
1407
|
+
}
|
|
1408
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1409
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
return true;
|
|
1413
|
+
});
|
|
1414
|
+
if (matched.length > 0) {
|
|
1415
|
+
results.push({ tx, events: matched });
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
break;
|
|
1419
|
+
}
|
|
1420
|
+
case "contract_call": {
|
|
1421
|
+
for (const tx of transactions) {
|
|
1422
|
+
if (tx.type !== "contract_call")
|
|
1423
|
+
continue;
|
|
1424
|
+
if (filter.contractId) {
|
|
1425
|
+
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
1429
|
+
continue;
|
|
1430
|
+
if (filter.functionName) {
|
|
1431
|
+
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
if (filter.caller) {
|
|
1435
|
+
if (!matchPattern(tx.sender, filter.caller))
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
1439
|
+
continue;
|
|
1440
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1441
|
+
results.push({ tx, events: txEvents });
|
|
1442
|
+
}
|
|
1443
|
+
break;
|
|
1444
|
+
}
|
|
1445
|
+
case "contract_deploy": {
|
|
1446
|
+
for (const tx of transactions) {
|
|
1447
|
+
if (tx.type !== "smart_contract")
|
|
1448
|
+
continue;
|
|
1449
|
+
if (filter.deployer) {
|
|
1450
|
+
if (!matchPattern(tx.sender, filter.deployer))
|
|
1451
|
+
continue;
|
|
1624
1452
|
}
|
|
1625
|
-
|
|
1453
|
+
if (filter.contractName) {
|
|
1454
|
+
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
1455
|
+
if (!matchPattern(name, filter.contractName))
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1459
|
+
results.push({ tx, events: txEvents });
|
|
1626
1460
|
}
|
|
1461
|
+
break;
|
|
1627
1462
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1463
|
+
case "nested_contract_call":
|
|
1464
|
+
case "var_set":
|
|
1465
|
+
case "map_set":
|
|
1466
|
+
case "map_insert":
|
|
1467
|
+
case "map_delete": {
|
|
1468
|
+
for (const tx of transactions) {
|
|
1469
|
+
const txEvents = vmEventsByTx.get(tx.tx_id) ?? [];
|
|
1470
|
+
const matched = txEvents.filter((e) => {
|
|
1471
|
+
if (e.type !== filter.type)
|
|
1472
|
+
return false;
|
|
1473
|
+
const data = e.data;
|
|
1474
|
+
if (!data)
|
|
1475
|
+
return false;
|
|
1476
|
+
const contractId = printContractId(data);
|
|
1477
|
+
if ("contractId" in filter && filter.contractId) {
|
|
1478
|
+
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
1479
|
+
return false;
|
|
1480
|
+
}
|
|
1481
|
+
if (!traitAllows(filter, contractId, traitContracts))
|
|
1482
|
+
return false;
|
|
1483
|
+
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
1484
|
+
return false;
|
|
1485
|
+
if (filter.type === "nested_contract_call") {
|
|
1486
|
+
if (filter.functionName) {
|
|
1487
|
+
if (typeof data.function_name !== "string" || !matchPattern(data.function_name, filter.functionName))
|
|
1488
|
+
return false;
|
|
1489
|
+
}
|
|
1490
|
+
if (filter.caller) {
|
|
1491
|
+
if (typeof data.caller !== "string" || !matchPattern(data.caller, filter.caller))
|
|
1492
|
+
return false;
|
|
1493
|
+
}
|
|
1494
|
+
if (filter.sender) {
|
|
1495
|
+
if (typeof data.sender !== "string" || !matchPattern(data.sender, filter.sender))
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
if (filter.type === "var_set" && filter.varName) {
|
|
1500
|
+
if (typeof data.var_name !== "string" || !matchPattern(data.var_name, filter.varName))
|
|
1501
|
+
return false;
|
|
1502
|
+
}
|
|
1503
|
+
if ((filter.type === "map_set" || filter.type === "map_insert" || filter.type === "map_delete") && filter.map) {
|
|
1504
|
+
if (typeof data.map_name !== "string" || !matchPattern(data.map_name, filter.map))
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
return true;
|
|
1508
|
+
});
|
|
1509
|
+
if (matched.length > 0)
|
|
1510
|
+
results.push({ tx, events: matched });
|
|
1670
1511
|
}
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
case "print_event": {
|
|
1515
|
+
for (const tx of transactions) {
|
|
1516
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1517
|
+
const matched = txEvents.filter((e) => {
|
|
1518
|
+
if (e.type !== "smart_contract_event" && e.type !== "contract_event")
|
|
1519
|
+
return false;
|
|
1520
|
+
const data = e.data;
|
|
1521
|
+
if (!data)
|
|
1522
|
+
return false;
|
|
1523
|
+
if (data.topic !== "print")
|
|
1524
|
+
return false;
|
|
1525
|
+
const contractId = printContractId(data);
|
|
1526
|
+
if (filter.contractId) {
|
|
1527
|
+
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
if (!traitAllows(filter, contractId, traitContracts))
|
|
1531
|
+
return false;
|
|
1532
|
+
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
1533
|
+
return false;
|
|
1534
|
+
return true;
|
|
1535
|
+
});
|
|
1536
|
+
if (matched.length > 0) {
|
|
1537
|
+
results.push({ tx, events: matched });
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
break;
|
|
1675
1541
|
}
|
|
1676
|
-
row[colName] = resolved.value ?? null;
|
|
1677
|
-
}
|
|
1678
|
-
return { ok: true, row };
|
|
1679
|
-
}
|
|
1680
|
-
function applyMaterializeInsert(spec, event, ctx, schema) {
|
|
1681
|
-
const table = schema[spec.table];
|
|
1682
|
-
if (!table) {
|
|
1683
|
-
return { ok: false, reason: `materialize table "${spec.table}" missing` };
|
|
1684
1542
|
}
|
|
1685
|
-
|
|
1686
|
-
if (!built.ok)
|
|
1687
|
-
return built;
|
|
1688
|
-
ctx.insert(spec.table, built.row);
|
|
1689
|
-
return { ok: true };
|
|
1543
|
+
return results;
|
|
1690
1544
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
"principal",
|
|
1698
|
-
"boolean",
|
|
1699
|
-
"timestamp",
|
|
1700
|
-
"jsonb"
|
|
1701
|
-
]);
|
|
1702
|
-
function isColumnType(field) {
|
|
1703
|
-
return typeof field === "string" && COLUMN_TYPES.has(field);
|
|
1704
|
-
}
|
|
1705
|
-
function matchesField(field, value) {
|
|
1706
|
-
if (isColumnType(field)) {
|
|
1707
|
-
switch (field) {
|
|
1708
|
-
case "uint":
|
|
1709
|
-
case "int":
|
|
1710
|
-
return typeof value === "bigint" || typeof value === "number";
|
|
1711
|
-
case "boolean":
|
|
1712
|
-
return typeof value === "boolean";
|
|
1713
|
-
case "text":
|
|
1714
|
-
case "principal":
|
|
1715
|
-
case "timestamp":
|
|
1716
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint";
|
|
1717
|
-
case "jsonb":
|
|
1718
|
-
return value !== undefined;
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
if (typeof field !== "object" || field === null)
|
|
1722
|
-
return false;
|
|
1723
|
-
if ("optional" in field) {
|
|
1724
|
-
return value === undefined || value === null || matchesField(field.type, value);
|
|
1725
|
-
}
|
|
1726
|
-
if ("tuple" in field) {
|
|
1727
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1728
|
-
return false;
|
|
1729
|
-
}
|
|
1730
|
-
return matchesTopic(field.tuple, value).ok;
|
|
1545
|
+
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map, vmEvents = []) {
|
|
1546
|
+
const eventsByTx = new Map;
|
|
1547
|
+
for (const event of events) {
|
|
1548
|
+
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
1549
|
+
list.push(event);
|
|
1550
|
+
eventsByTx.set(event.tx_id, list);
|
|
1731
1551
|
}
|
|
1732
|
-
|
|
1733
|
-
|
|
1552
|
+
const vmEventsByTx = new Map;
|
|
1553
|
+
for (const event of vmEvents) {
|
|
1554
|
+
const list = vmEventsByTx.get(event.tx_id) ?? [];
|
|
1555
|
+
list.push(event);
|
|
1556
|
+
vmEventsByTx.set(event.tx_id, list);
|
|
1734
1557
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
const
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
}
|
|
1746
|
-
if (!matchesField(field, data[name])) {
|
|
1747
|
-
return {
|
|
1748
|
-
ok: false,
|
|
1749
|
-
reason: `field "${name}" does not match its declared type (got ${describe(data[name])})`
|
|
1750
|
-
};
|
|
1558
|
+
const seen = new Set;
|
|
1559
|
+
const results = [];
|
|
1560
|
+
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
1561
|
+
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts, vmEventsByTx);
|
|
1562
|
+
for (const match of matches) {
|
|
1563
|
+
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
1564
|
+
if (!seen.has(dedupeKey)) {
|
|
1565
|
+
seen.add(dedupeKey);
|
|
1566
|
+
results.push({ ...match, sourceName });
|
|
1567
|
+
}
|
|
1751
1568
|
}
|
|
1752
1569
|
}
|
|
1753
|
-
return
|
|
1754
|
-
}
|
|
1755
|
-
function describe(value) {
|
|
1756
|
-
if (value === null)
|
|
1757
|
-
return "null";
|
|
1758
|
-
if (Array.isArray(value))
|
|
1759
|
-
return "array";
|
|
1760
|
-
return typeof value;
|
|
1761
|
-
}
|
|
1762
|
-
function validatePrintPayload(prints, topic, data) {
|
|
1763
|
-
if (!prints)
|
|
1764
|
-
return { ok: true };
|
|
1765
|
-
const declared = prints[topic];
|
|
1766
|
-
if (!declared)
|
|
1767
|
-
return { ok: true };
|
|
1768
|
-
return matchesTopic(declared, data);
|
|
1570
|
+
return results;
|
|
1769
1571
|
}
|
|
1770
1572
|
|
|
1771
1573
|
// src/runtime/violations.ts
|
|
1772
1574
|
import { getTargetDb } from "@secondlayer/shared/db";
|
|
1773
1575
|
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
1774
1576
|
import { sql as sql2 } from "kysely";
|
|
1775
|
-
var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
|
|
1776
|
-
function jsonSafe2(value) {
|
|
1777
|
-
if (typeof value === "bigint")
|
|
1778
|
-
return value.toString();
|
|
1779
|
-
if (value === null || value === undefined)
|
|
1780
|
-
return value;
|
|
1781
|
-
if (Array.isArray(value))
|
|
1782
|
-
return value.map(jsonSafe2);
|
|
1783
|
-
if (typeof value === "object") {
|
|
1784
|
-
const out = {};
|
|
1785
|
-
for (const [k, v] of Object.entries(value)) {
|
|
1786
|
-
out[k] = jsonSafe2(v);
|
|
1787
|
-
}
|
|
1788
|
-
return out;
|
|
1789
|
-
}
|
|
1790
|
-
return value;
|
|
1791
|
-
}
|
|
1792
|
-
async function defaultRecordPrintViolation(input) {
|
|
1793
|
-
try {
|
|
1794
|
-
const db = getTargetDb();
|
|
1795
|
-
const sample = jsonSafe2(input.samplePayload);
|
|
1796
|
-
await db.insertInto("subgraph_violations").values({
|
|
1797
|
-
subgraph_name: input.subgraphName,
|
|
1798
|
-
source_name: input.sourceName,
|
|
1799
|
-
block_height: input.blockHeight,
|
|
1800
|
-
tx_id: input.txId,
|
|
1801
|
-
reason: input.reason,
|
|
1802
|
-
sample_payload: sample
|
|
1803
|
-
}).execute();
|
|
1804
|
-
await sql2`
|
|
1805
|
-
DELETE FROM subgraph_violations
|
|
1806
|
-
WHERE id IN (
|
|
1807
|
-
SELECT id FROM subgraph_violations
|
|
1808
|
-
WHERE subgraph_name = ${input.subgraphName}
|
|
1809
|
-
ORDER BY seen_at DESC
|
|
1810
|
-
OFFSET ${VIOLATIONS_CAP_PER_SUBGRAPH}
|
|
1811
|
-
)
|
|
1812
|
-
`.execute(db);
|
|
1813
|
-
} catch (err) {
|
|
1814
|
-
logger3.warn("Failed to record print violation", {
|
|
1815
|
-
subgraph: input.subgraphName,
|
|
1816
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1817
|
-
});
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
var recorder = defaultRecordPrintViolation;
|
|
1821
|
-
async function recordPrintViolation(input) {
|
|
1822
|
-
await recorder(input);
|
|
1823
|
-
}
|
|
1824
1577
|
|
|
1825
1578
|
// src/runtime/runner.ts
|
|
1826
|
-
|
|
1827
|
-
function decodeFunctionArgs2(args) {
|
|
1579
|
+
function decodeFunctionArgs(args) {
|
|
1828
1580
|
let parsed = args;
|
|
1829
1581
|
if (typeof parsed === "string") {
|
|
1830
1582
|
try {
|
|
@@ -1907,7 +1659,7 @@ function buildEventPayload(filter, tx, event) {
|
|
|
1907
1659
|
contractId: tx.contract_id ?? null,
|
|
1908
1660
|
functionName: tx.function_name ?? null
|
|
1909
1661
|
};
|
|
1910
|
-
const decodedArgs =
|
|
1662
|
+
const decodedArgs = decodeFunctionArgs(tx.function_args);
|
|
1911
1663
|
const decodedResult = decodeRawResult(tx.raw_result);
|
|
1912
1664
|
if (!event) {
|
|
1913
1665
|
switch (filter.type) {
|
|
@@ -2054,7 +1806,7 @@ function buildEventPayload(filter, tx, event) {
|
|
|
2054
1806
|
sender: typeof data.sender === "string" ? data.sender : null,
|
|
2055
1807
|
caller: typeof data.caller === "string" ? data.caller : "",
|
|
2056
1808
|
functionName: typeof data.function_name === "string" ? data.function_name : "",
|
|
2057
|
-
args:
|
|
1809
|
+
args: decodeFunctionArgs(argsHex),
|
|
2058
1810
|
arguments: argsHex,
|
|
2059
1811
|
rawResult: typeof data.raw_result === "string" ? data.raw_result : "",
|
|
2060
1812
|
result: decodeRawResult(data.raw_result),
|
|
@@ -2106,152 +1858,8 @@ function buildEventPayload(filter, tx, event) {
|
|
|
2106
1858
|
};
|
|
2107
1859
|
}
|
|
2108
1860
|
}
|
|
2109
|
-
async function runHandlers(subgraph, matched, ctx, opts) {
|
|
2110
|
-
let processed = 0;
|
|
2111
|
-
let errors = 0;
|
|
2112
|
-
let skipped = 0;
|
|
2113
|
-
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
2114
|
-
const filterLookup = new Map;
|
|
2115
|
-
if (!Array.isArray(subgraph.sources)) {
|
|
2116
|
-
for (const [name, filter] of Object.entries(subgraph.sources)) {
|
|
2117
|
-
filterLookup.set(name, filter);
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
const units = [];
|
|
2121
|
-
for (const { tx, events, sourceName } of matched) {
|
|
2122
|
-
if (events.length === 0) {
|
|
2123
|
-
units.push({ tx, sourceName, event: null });
|
|
2124
|
-
} else {
|
|
2125
|
-
for (const event of events)
|
|
2126
|
-
units.push({ tx, sourceName, event });
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
const clockRank = (e) => e?.clock === "vm" ? 1 : 0;
|
|
2130
|
-
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || clockRank(a.event) - clockRank(b.event) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
2131
|
-
for (const { tx, event, sourceName } of units) {
|
|
2132
|
-
if (errors >= threshold) {
|
|
2133
|
-
logger4.error("Subgraph error threshold reached, skipping remaining events", {
|
|
2134
|
-
subgraph: subgraph.name,
|
|
2135
|
-
errors,
|
|
2136
|
-
threshold
|
|
2137
|
-
});
|
|
2138
|
-
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
2139
|
-
}
|
|
2140
|
-
const filter = filterLookup.get(sourceName);
|
|
2141
|
-
const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
|
|
2142
|
-
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
2143
|
-
if (!handler && !materialize) {
|
|
2144
|
-
logger4.warn("No handler found for source", {
|
|
2145
|
-
subgraph: subgraph.name,
|
|
2146
|
-
sourceName,
|
|
2147
|
-
txId: tx.tx_id
|
|
2148
|
-
});
|
|
2149
|
-
continue;
|
|
2150
|
-
}
|
|
2151
|
-
ctx.setTx({
|
|
2152
|
-
txId: tx.tx_id,
|
|
2153
|
-
sender: tx.sender,
|
|
2154
|
-
type: tx.type,
|
|
2155
|
-
status: tx.status,
|
|
2156
|
-
contractId: tx.contract_id ?? null,
|
|
2157
|
-
functionName: tx.function_name ?? null
|
|
2158
|
-
});
|
|
2159
|
-
const checkpoint = ctx.opsCheckpoint();
|
|
2160
|
-
try {
|
|
2161
|
-
let payload;
|
|
2162
|
-
if (event === null) {
|
|
2163
|
-
payload = filter ? buildEventPayload(filter, tx, null) : {
|
|
2164
|
-
tx: {
|
|
2165
|
-
txId: tx.tx_id,
|
|
2166
|
-
sender: tx.sender,
|
|
2167
|
-
type: tx.type,
|
|
2168
|
-
status: tx.status,
|
|
2169
|
-
contractId: tx.contract_id,
|
|
2170
|
-
functionName: tx.function_name
|
|
2171
|
-
}
|
|
2172
|
-
};
|
|
2173
|
-
} else if (filter) {
|
|
2174
|
-
payload = buildEventPayload(filter, tx, event);
|
|
2175
|
-
} else {
|
|
2176
|
-
const decoded = decodeEventData(event.data);
|
|
2177
|
-
payload = {
|
|
2178
|
-
...decoded,
|
|
2179
|
-
_eventId: event.id,
|
|
2180
|
-
_eventType: event.type,
|
|
2181
|
-
_eventIndex: event.event_index,
|
|
2182
|
-
tx: {
|
|
2183
|
-
txId: tx.tx_id,
|
|
2184
|
-
sender: tx.sender,
|
|
2185
|
-
type: tx.type,
|
|
2186
|
-
status: tx.status,
|
|
2187
|
-
contractId: tx.contract_id,
|
|
2188
|
-
functionName: tx.function_name
|
|
2189
|
-
}
|
|
2190
|
-
};
|
|
2191
|
-
}
|
|
2192
|
-
if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
2193
|
-
continue;
|
|
2194
|
-
}
|
|
2195
|
-
if (event !== null && filter?.type === "print_event" && filter.prints) {
|
|
2196
|
-
const printPayload = payload;
|
|
2197
|
-
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
2198
|
-
if (!verdict.ok) {
|
|
2199
|
-
skipped++;
|
|
2200
|
-
logger4.warn("Print payload does not match declared prints schema", {
|
|
2201
|
-
subgraph: subgraph.name,
|
|
2202
|
-
sourceName,
|
|
2203
|
-
topic: printPayload.topic,
|
|
2204
|
-
txId: tx.tx_id,
|
|
2205
|
-
reason: verdict.reason
|
|
2206
|
-
});
|
|
2207
|
-
recordPrintViolation({
|
|
2208
|
-
subgraphName: subgraph.name,
|
|
2209
|
-
sourceName,
|
|
2210
|
-
blockHeight: ctx.block.height,
|
|
2211
|
-
txId: tx.tx_id,
|
|
2212
|
-
reason: verdict.reason,
|
|
2213
|
-
samplePayload: {
|
|
2214
|
-
topic: printPayload.topic ?? "",
|
|
2215
|
-
data: printPayload.data ?? {}
|
|
2216
|
-
}
|
|
2217
|
-
});
|
|
2218
|
-
continue;
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
if (materialize) {
|
|
2222
|
-
const result = applyMaterializeInsert(materialize, payload, ctx, subgraph.schema);
|
|
2223
|
-
if (!result.ok) {
|
|
2224
|
-
skipped++;
|
|
2225
|
-
logger4.warn("Materialize skipped: required field missing", {
|
|
2226
|
-
subgraph: subgraph.name,
|
|
2227
|
-
sourceName,
|
|
2228
|
-
txId: tx.tx_id,
|
|
2229
|
-
reason: result.reason
|
|
2230
|
-
});
|
|
2231
|
-
continue;
|
|
2232
|
-
}
|
|
2233
|
-
processed++;
|
|
2234
|
-
} else if (handler) {
|
|
2235
|
-
await handler(payload, ctx);
|
|
2236
|
-
processed++;
|
|
2237
|
-
}
|
|
2238
|
-
} catch (err) {
|
|
2239
|
-
ctx.rollbackTo(checkpoint);
|
|
2240
|
-
errors++;
|
|
2241
|
-
logger4.error("Subgraph handler error", {
|
|
2242
|
-
subgraph: subgraph.name,
|
|
2243
|
-
sourceName,
|
|
2244
|
-
txId: tx.tx_id,
|
|
2245
|
-
...event !== null ? { eventId: event.id, eventType: event.type } : {},
|
|
2246
|
-
error: getErrorMessage(err)
|
|
2247
|
-
});
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
2251
|
-
}
|
|
2252
1861
|
|
|
2253
1862
|
// src/testing/harness.ts
|
|
2254
|
-
import { normalizeAbi as normalizeAbi2, toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
|
|
2255
1863
|
var TEST_SCHEMA_NAME = "subgraph_test";
|
|
2256
1864
|
function defaultBlock(overrides = {}) {
|
|
2257
1865
|
return {
|
|
@@ -2457,17 +2065,17 @@ function toContractCallPayload(row) {
|
|
|
2457
2065
|
}
|
|
2458
2066
|
};
|
|
2459
2067
|
}
|
|
2460
|
-
function
|
|
2068
|
+
function jsonSafe2(value) {
|
|
2461
2069
|
if (typeof value === "bigint")
|
|
2462
2070
|
return value.toString();
|
|
2463
2071
|
if (value === null || value === undefined)
|
|
2464
2072
|
return value;
|
|
2465
2073
|
if (Array.isArray(value))
|
|
2466
|
-
return value.map(
|
|
2074
|
+
return value.map(jsonSafe2);
|
|
2467
2075
|
if (typeof value === "object") {
|
|
2468
2076
|
const out = {};
|
|
2469
2077
|
for (const [k, v] of Object.entries(value)) {
|
|
2470
|
-
out[k] =
|
|
2078
|
+
out[k] = jsonSafe2(v);
|
|
2471
2079
|
}
|
|
2472
2080
|
return out;
|
|
2473
2081
|
}
|
|
@@ -2533,9 +2141,8 @@ async function runSubgraphTest(input) {
|
|
|
2533
2141
|
const traces = [];
|
|
2534
2142
|
for (const [name, rows] of Object.entries(events)) {
|
|
2535
2143
|
const filter = sources[name];
|
|
2536
|
-
const handler = handlers[name]
|
|
2537
|
-
|
|
2538
|
-
if (typeof handler !== "function" && !materialize)
|
|
2144
|
+
const handler = handlers[name];
|
|
2145
|
+
if (typeof handler !== "function")
|
|
2539
2146
|
continue;
|
|
2540
2147
|
for (const row of rows) {
|
|
2541
2148
|
const payload = filter?.type === "contract_call" ? toContractCallPayload(row) : toHandlerPayload(filter, row);
|
|
@@ -2556,17 +2163,7 @@ async function runSubgraphTest(input) {
|
|
|
2556
2163
|
try {
|
|
2557
2164
|
ctx.setTx(txFromRow(row));
|
|
2558
2165
|
const event = buildEvent(filter, payload, { tx: ctx.tx });
|
|
2559
|
-
|
|
2560
|
-
applyMaterializeInsert(materialize, event, {
|
|
2561
|
-
tx: ctx.tx,
|
|
2562
|
-
block: ctx.block,
|
|
2563
|
-
insert: (table, insertRow) => {
|
|
2564
|
-
ctx.insert(table, insertRow);
|
|
2565
|
-
}
|
|
2566
|
-
}, schema);
|
|
2567
|
-
} else {
|
|
2568
|
-
await handler(event, ctx);
|
|
2569
|
-
}
|
|
2166
|
+
await handler(event, ctx);
|
|
2570
2167
|
} catch {}
|
|
2571
2168
|
if (trace) {
|
|
2572
2169
|
const inserts = ctx.pendingInsertsSince(checkpoint);
|
|
@@ -2589,7 +2186,7 @@ async function runSubgraphTest(input) {
|
|
|
2589
2186
|
tables.push({
|
|
2590
2187
|
name: table,
|
|
2591
2188
|
rows: tableRows.length,
|
|
2592
|
-
...sample ? { sampleRow:
|
|
2189
|
+
...sample ? { sampleRow: jsonSafe2(sample) } : {}
|
|
2593
2190
|
});
|
|
2594
2191
|
}
|
|
2595
2192
|
const fetched = Object.values(events).reduce((n, r) => n + r.length, 0);
|
|
@@ -2618,7 +2215,7 @@ async function runSubgraphTest(input) {
|
|
|
2618
2215
|
matched,
|
|
2619
2216
|
written: 0,
|
|
2620
2217
|
tables,
|
|
2621
|
-
firstEvent: firstEvent ? { ...firstEvent, data:
|
|
2218
|
+
firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
|
|
2622
2219
|
hint: `${matched} event${matched === 1 ? "" : "s"} matched, but NO rows were written — the shape of the field-mapping bug that ships a 0-row subgraph.${keyPart} Map only observed keys; do not invent fields.`,
|
|
2623
2220
|
...traceFields
|
|
2624
2221
|
};
|
|
@@ -2632,7 +2229,7 @@ async function runSubgraphTest(input) {
|
|
|
2632
2229
|
matched,
|
|
2633
2230
|
written,
|
|
2634
2231
|
tables,
|
|
2635
|
-
firstEvent: firstEvent ? { ...firstEvent, data:
|
|
2232
|
+
firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
|
|
2636
2233
|
hint: `At least one matched event wrote 0 rows.${keyPart}`,
|
|
2637
2234
|
...traceFields
|
|
2638
2235
|
};
|
|
@@ -2642,7 +2239,7 @@ async function runSubgraphTest(input) {
|
|
|
2642
2239
|
matched,
|
|
2643
2240
|
written,
|
|
2644
2241
|
tables,
|
|
2645
|
-
firstEvent: firstEvent ? { ...firstEvent, data:
|
|
2242
|
+
firstEvent: firstEvent ? { ...firstEvent, data: jsonSafe2(firstEvent.data) } : undefined,
|
|
2646
2243
|
...traceFields
|
|
2647
2244
|
};
|
|
2648
2245
|
}
|
|
@@ -2654,9 +2251,8 @@ async function probeHandlers(def, samples) {
|
|
|
2654
2251
|
const filter = def.sources[sample.source];
|
|
2655
2252
|
if (!filter)
|
|
2656
2253
|
continue;
|
|
2657
|
-
const handler = def.handlers[sample.source]
|
|
2658
|
-
|
|
2659
|
-
if (typeof handler !== "function" && !materialize)
|
|
2254
|
+
const handler = def.handlers[sample.source];
|
|
2255
|
+
if (typeof handler !== "function")
|
|
2660
2256
|
continue;
|
|
2661
2257
|
if (firstEventKeys === undefined) {
|
|
2662
2258
|
firstEventKeys = dataKeysOf(sample.event.data);
|
|
@@ -2664,17 +2260,7 @@ async function probeHandlers(def, samples) {
|
|
|
2664
2260
|
matched++;
|
|
2665
2261
|
try {
|
|
2666
2262
|
const event = buildEvent(filter, sample.event);
|
|
2667
|
-
|
|
2668
|
-
applyMaterializeInsert(materialize, event, {
|
|
2669
|
-
tx: ctx.tx,
|
|
2670
|
-
block: ctx.block,
|
|
2671
|
-
insert: (table, row) => {
|
|
2672
|
-
ctx.insert(table, row);
|
|
2673
|
-
}
|
|
2674
|
-
}, def.schema);
|
|
2675
|
-
} else {
|
|
2676
|
-
await handler(event, ctx);
|
|
2677
|
-
}
|
|
2263
|
+
await handler(event, ctx);
|
|
2678
2264
|
} catch {}
|
|
2679
2265
|
}
|
|
2680
2266
|
const tables = Object.keys(def.schema);
|
|
@@ -2690,14 +2276,14 @@ async function probeHandlers(def, samples) {
|
|
|
2690
2276
|
};
|
|
2691
2277
|
}
|
|
2692
2278
|
export {
|
|
2693
|
-
|
|
2694
|
-
toContractCallPayload,
|
|
2695
|
-
runSubgraphTest,
|
|
2696
|
-
probeHandlers,
|
|
2697
|
-
createTestContext,
|
|
2279
|
+
buildEvent,
|
|
2698
2280
|
buildEventPayload,
|
|
2699
|
-
|
|
2281
|
+
createTestContext,
|
|
2282
|
+
probeHandlers,
|
|
2283
|
+
runSubgraphTest,
|
|
2284
|
+
toContractCallPayload,
|
|
2285
|
+
toHandlerPayload
|
|
2700
2286
|
};
|
|
2701
2287
|
|
|
2702
|
-
//# debugId=
|
|
2288
|
+
//# debugId=73BD3E2068CDEA0464756E2164756E21
|
|
2703
2289
|
//# sourceMappingURL=index.js.map
|