@secondlayer/subgraphs 4.4.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +1 -1
- package/dist/{src/runtime → runtime}/emitter.js +8 -12
- package/dist/{src/runtime → runtime}/emitter.js.map +3 -3
- package/dist/{src/runtime → runtime}/replay.d.ts +1 -1
- package/dist/{src/runtime → runtime}/replay.js +342 -683
- package/dist/{src/runtime → runtime}/replay.js.map +5 -5
- 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/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,1332 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
-
|
|
4
|
-
// src/runtime/clarity.ts
|
|
5
|
-
import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
|
|
6
|
-
function decodeClarityValue(hex) {
|
|
7
|
-
try {
|
|
8
|
-
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
9
|
-
const cv = deserializeCV(cleanHex);
|
|
10
|
-
return cvToValue(cv);
|
|
11
|
-
} catch {
|
|
12
|
-
return hex;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function decodeEventData(data) {
|
|
16
|
-
if (typeof data === "string" && data.startsWith("0x") && data.length > 10) {
|
|
17
|
-
return decodeClarityValue(data);
|
|
18
|
-
}
|
|
19
|
-
if (Array.isArray(data)) {
|
|
20
|
-
return data.map(decodeEventData);
|
|
21
|
-
}
|
|
22
|
-
if (typeof data === "object" && data !== null) {
|
|
23
|
-
const decoded = {};
|
|
24
|
-
for (const [key, value] of Object.entries(data)) {
|
|
25
|
-
decoded[key] = decodeEventData(value);
|
|
26
|
-
}
|
|
27
|
-
return decoded;
|
|
28
|
-
}
|
|
29
|
-
return data;
|
|
30
|
-
}
|
|
31
|
-
function decodeFunctionArgs(args) {
|
|
32
|
-
return args.map(decodeClarityValue);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/runtime/source-matcher.ts
|
|
36
|
-
var patternCache = new Map;
|
|
37
|
-
function matchAny(value, pattern) {
|
|
38
|
-
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
39
|
-
}
|
|
40
|
-
function matchPattern(value, pattern) {
|
|
41
|
-
if (!pattern.includes("*"))
|
|
42
|
-
return value === pattern;
|
|
43
|
-
let re = patternCache.get(pattern);
|
|
44
|
-
if (!re) {
|
|
45
|
-
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
46
|
-
re = new RegExp(`^${regex}$`);
|
|
47
|
-
patternCache.set(pattern, re);
|
|
48
|
-
}
|
|
49
|
-
return re.test(value);
|
|
50
|
-
}
|
|
51
|
-
var EMPTY_SET = new Set;
|
|
52
|
-
function factoryAllows(filter, contractId, factoryContracts) {
|
|
53
|
-
if (!filter.factory)
|
|
54
|
-
return true;
|
|
55
|
-
if (!contractId)
|
|
56
|
-
return false;
|
|
57
|
-
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
58
|
-
}
|
|
59
|
-
function readPath(payload, path) {
|
|
60
|
-
let cur = payload;
|
|
61
|
-
for (const key of path.split(".")) {
|
|
62
|
-
if (cur === null || typeof cur !== "object")
|
|
63
|
-
return;
|
|
64
|
-
cur = cur[key];
|
|
65
|
-
}
|
|
66
|
-
return cur;
|
|
67
|
-
}
|
|
68
|
-
function traitAllows(filter, contractId, traitContracts) {
|
|
69
|
-
const trait = filter.trait;
|
|
70
|
-
if (!trait)
|
|
71
|
-
return true;
|
|
72
|
-
if (!contractId)
|
|
73
|
-
return false;
|
|
74
|
-
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
75
|
-
}
|
|
76
|
-
function assetContract(assetId) {
|
|
77
|
-
return assetId?.split("::")[0];
|
|
78
|
-
}
|
|
79
|
-
function printContractId(data) {
|
|
80
|
-
if (!data)
|
|
81
|
-
return;
|
|
82
|
-
const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
|
|
83
|
-
return id || undefined;
|
|
84
|
-
}
|
|
85
|
-
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map, vmEventsByTx = new Map) {
|
|
86
|
-
const results = [];
|
|
87
|
-
switch (filter.type) {
|
|
88
|
-
case "stx_transfer":
|
|
89
|
-
case "stx_mint":
|
|
90
|
-
case "stx_burn":
|
|
91
|
-
case "stx_lock": {
|
|
92
|
-
const eventType = `${filter.type}_event`;
|
|
93
|
-
for (const tx of transactions) {
|
|
94
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
95
|
-
const matched = txEvents.filter((e) => e.type === eventType);
|
|
96
|
-
if (matched.length === 0)
|
|
97
|
-
continue;
|
|
98
|
-
const filtered = matched.filter((e) => {
|
|
99
|
-
const data = e.data;
|
|
100
|
-
if (!data)
|
|
101
|
-
return false;
|
|
102
|
-
if ("sender" in filter && filter.sender) {
|
|
103
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
if ("recipient" in filter && filter.recipient) {
|
|
107
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
111
|
-
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
115
|
-
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
116
|
-
if (amount < filter.minAmount)
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
120
|
-
const amount = BigInt(data.amount ?? "0");
|
|
121
|
-
if (amount > filter.maxAmount)
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
return true;
|
|
125
|
-
});
|
|
126
|
-
if (filtered.length > 0) {
|
|
127
|
-
results.push({ tx, events: filtered });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
case "ft_transfer":
|
|
133
|
-
case "ft_mint":
|
|
134
|
-
case "ft_burn": {
|
|
135
|
-
const eventType = `${filter.type}_event`;
|
|
136
|
-
for (const tx of transactions) {
|
|
137
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
138
|
-
const matched = txEvents.filter((e) => {
|
|
139
|
-
if (e.type !== eventType)
|
|
140
|
-
return false;
|
|
141
|
-
const data = e.data;
|
|
142
|
-
if (!data)
|
|
143
|
-
return false;
|
|
144
|
-
if (filter.assetIdentifier) {
|
|
145
|
-
const assetId = data.asset_identifier;
|
|
146
|
-
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
150
|
-
return false;
|
|
151
|
-
if ("sender" in filter && filter.sender) {
|
|
152
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
if ("recipient" in filter && filter.recipient) {
|
|
156
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
if (filter.minAmount !== undefined) {
|
|
160
|
-
const amount = BigInt(data.amount ?? "0");
|
|
161
|
-
if (amount < filter.minAmount)
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
return true;
|
|
165
|
-
});
|
|
166
|
-
if (matched.length > 0) {
|
|
167
|
-
results.push({ tx, events: matched });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
case "nft_transfer":
|
|
173
|
-
case "nft_mint":
|
|
174
|
-
case "nft_burn": {
|
|
175
|
-
const eventType = `${filter.type}_event`;
|
|
176
|
-
for (const tx of transactions) {
|
|
177
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
178
|
-
const matched = txEvents.filter((e) => {
|
|
179
|
-
if (e.type !== eventType)
|
|
180
|
-
return false;
|
|
181
|
-
const data = e.data;
|
|
182
|
-
if (!data)
|
|
183
|
-
return false;
|
|
184
|
-
if (filter.assetIdentifier) {
|
|
185
|
-
const assetId = data.asset_identifier;
|
|
186
|
-
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
190
|
-
return false;
|
|
191
|
-
if ("sender" in filter && filter.sender) {
|
|
192
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
if ("recipient" in filter && filter.recipient) {
|
|
196
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
return true;
|
|
200
|
-
});
|
|
201
|
-
if (matched.length > 0) {
|
|
202
|
-
results.push({ tx, events: matched });
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
207
|
-
case "contract_call": {
|
|
208
|
-
for (const tx of transactions) {
|
|
209
|
-
if (tx.type !== "contract_call")
|
|
210
|
-
continue;
|
|
211
|
-
if (filter.contractId) {
|
|
212
|
-
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
216
|
-
continue;
|
|
217
|
-
if (filter.functionName) {
|
|
218
|
-
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
if (filter.caller) {
|
|
222
|
-
if (!matchPattern(tx.sender, filter.caller))
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
226
|
-
continue;
|
|
227
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
228
|
-
results.push({ tx, events: txEvents });
|
|
229
|
-
}
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
case "contract_deploy": {
|
|
233
|
-
for (const tx of transactions) {
|
|
234
|
-
if (tx.type !== "smart_contract")
|
|
235
|
-
continue;
|
|
236
|
-
if (filter.deployer) {
|
|
237
|
-
if (!matchPattern(tx.sender, filter.deployer))
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (filter.contractName) {
|
|
241
|
-
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
242
|
-
if (!matchPattern(name, filter.contractName))
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
246
|
-
results.push({ tx, events: txEvents });
|
|
247
|
-
}
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
case "nested_contract_call":
|
|
251
|
-
case "var_set":
|
|
252
|
-
case "map_set":
|
|
253
|
-
case "map_insert":
|
|
254
|
-
case "map_delete": {
|
|
255
|
-
for (const tx of transactions) {
|
|
256
|
-
const txEvents = vmEventsByTx.get(tx.tx_id) ?? [];
|
|
257
|
-
const matched = txEvents.filter((e) => {
|
|
258
|
-
if (e.type !== filter.type)
|
|
259
|
-
return false;
|
|
260
|
-
const data = e.data;
|
|
261
|
-
if (!data)
|
|
262
|
-
return false;
|
|
263
|
-
const contractId = printContractId(data);
|
|
264
|
-
if ("contractId" in filter && filter.contractId) {
|
|
265
|
-
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
if (!traitAllows(filter, contractId, traitContracts))
|
|
269
|
-
return false;
|
|
270
|
-
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
271
|
-
return false;
|
|
272
|
-
if (filter.type === "nested_contract_call") {
|
|
273
|
-
if (filter.functionName) {
|
|
274
|
-
if (typeof data.function_name !== "string" || !matchPattern(data.function_name, filter.functionName))
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
if (filter.caller) {
|
|
278
|
-
if (typeof data.caller !== "string" || !matchPattern(data.caller, filter.caller))
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
if (filter.sender) {
|
|
282
|
-
if (typeof data.sender !== "string" || !matchPattern(data.sender, filter.sender))
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
if (filter.type === "var_set" && filter.varName) {
|
|
287
|
-
if (typeof data.var_name !== "string" || !matchPattern(data.var_name, filter.varName))
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
if ((filter.type === "map_set" || filter.type === "map_insert" || filter.type === "map_delete") && filter.map) {
|
|
291
|
-
if (typeof data.map_name !== "string" || !matchPattern(data.map_name, filter.map))
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
return true;
|
|
295
|
-
});
|
|
296
|
-
if (matched.length > 0)
|
|
297
|
-
results.push({ tx, events: matched });
|
|
298
|
-
}
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
case "print_event": {
|
|
302
|
-
for (const tx of transactions) {
|
|
303
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
304
|
-
const matched = txEvents.filter((e) => {
|
|
305
|
-
if (e.type !== "smart_contract_event" && e.type !== "contract_event")
|
|
306
|
-
return false;
|
|
307
|
-
const data = e.data;
|
|
308
|
-
if (!data)
|
|
309
|
-
return false;
|
|
310
|
-
if (data.topic !== "print")
|
|
311
|
-
return false;
|
|
312
|
-
const contractId = printContractId(data);
|
|
313
|
-
if (filter.contractId) {
|
|
314
|
-
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
if (!traitAllows(filter, contractId, traitContracts))
|
|
318
|
-
return false;
|
|
319
|
-
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
320
|
-
return false;
|
|
321
|
-
return true;
|
|
322
|
-
});
|
|
323
|
-
if (matched.length > 0) {
|
|
324
|
-
results.push({ tx, events: matched });
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return results;
|
|
331
|
-
}
|
|
332
|
-
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map, vmEvents = []) {
|
|
333
|
-
const eventsByTx = new Map;
|
|
334
|
-
for (const event of events) {
|
|
335
|
-
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
336
|
-
list.push(event);
|
|
337
|
-
eventsByTx.set(event.tx_id, list);
|
|
338
|
-
}
|
|
339
|
-
const vmEventsByTx = new Map;
|
|
340
|
-
for (const event of vmEvents) {
|
|
341
|
-
const list = vmEventsByTx.get(event.tx_id) ?? [];
|
|
342
|
-
list.push(event);
|
|
343
|
-
vmEventsByTx.set(event.tx_id, list);
|
|
344
|
-
}
|
|
345
|
-
const seen = new Set;
|
|
346
|
-
const results = [];
|
|
347
|
-
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
348
|
-
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts, vmEventsByTx);
|
|
349
|
-
for (const match of matches) {
|
|
350
|
-
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
351
|
-
if (!seen.has(dedupeKey)) {
|
|
352
|
-
seen.add(dedupeKey);
|
|
353
|
-
results.push({ ...match, sourceName });
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return results;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// src/runtime/runner.ts
|
|
361
|
-
import { getErrorMessage } from "@secondlayer/shared";
|
|
362
|
-
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
363
|
-
import {
|
|
364
|
-
clarityValueToJS,
|
|
365
|
-
deserializeCV as deserializeCV3,
|
|
366
|
-
toCamelCase
|
|
367
|
-
} from "@secondlayer/stacks/clarity";
|
|
368
|
-
|
|
369
|
-
// src/print-schema.ts
|
|
370
|
-
import {
|
|
371
|
-
deserializeCV as deserializeCV2
|
|
372
|
-
} from "@secondlayer/stacks/clarity";
|
|
373
|
-
function camelizeDataKey(str) {
|
|
374
|
-
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
375
|
-
}
|
|
376
|
-
function camelizeKeys(obj) {
|
|
377
|
-
if (obj === null || obj === undefined)
|
|
378
|
-
return obj;
|
|
379
|
-
if (typeof obj !== "object")
|
|
380
|
-
return obj;
|
|
381
|
-
if (Array.isArray(obj))
|
|
382
|
-
return obj.map(camelizeKeys);
|
|
383
|
-
const result = {};
|
|
384
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
385
|
-
result[camelizeDataKey(k)] = camelizeKeys(v);
|
|
386
|
-
}
|
|
387
|
-
return result;
|
|
388
|
-
}
|
|
389
|
-
function cvToTree(cv) {
|
|
390
|
-
switch (cv.type) {
|
|
391
|
-
case "uint":
|
|
392
|
-
case "int":
|
|
393
|
-
return { kind: cv.type };
|
|
394
|
-
case "true":
|
|
395
|
-
case "false":
|
|
396
|
-
return { kind: "bool" };
|
|
397
|
-
case "address":
|
|
398
|
-
case "contract":
|
|
399
|
-
return { kind: "principal" };
|
|
400
|
-
case "buffer":
|
|
401
|
-
return { kind: "buffer", len: cv.value.length / 2 };
|
|
402
|
-
case "ascii":
|
|
403
|
-
return { kind: "ascii", len: cv.value.length };
|
|
404
|
-
case "utf8":
|
|
405
|
-
return { kind: "utf8", len: new TextEncoder().encode(cv.value).length };
|
|
406
|
-
case "none":
|
|
407
|
-
return { kind: "optional", inner: null };
|
|
408
|
-
case "some":
|
|
409
|
-
return { kind: "optional", inner: cvToTree(cv.value) };
|
|
410
|
-
case "ok":
|
|
411
|
-
return { kind: "response", ok: cvToTree(cv.value), err: null };
|
|
412
|
-
case "err":
|
|
413
|
-
return { kind: "response", ok: null, err: cvToTree(cv.value) };
|
|
414
|
-
case "list": {
|
|
415
|
-
let inner = null;
|
|
416
|
-
for (const el of cv.value) {
|
|
417
|
-
const t = cvToTree(el);
|
|
418
|
-
inner = inner ? unify(inner, t) : t;
|
|
419
|
-
}
|
|
420
|
-
return { kind: "list", inner };
|
|
421
|
-
}
|
|
422
|
-
case "tuple": {
|
|
423
|
-
const entries = new Map;
|
|
424
|
-
for (const [k, v] of Object.entries(cv.value)) {
|
|
425
|
-
entries.set(k, { tree: cvToTree(v), present: 1 });
|
|
426
|
-
}
|
|
427
|
-
return { kind: "tuple", count: 1, entries };
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
function unifyNullable(a, b) {
|
|
432
|
-
if (!a)
|
|
433
|
-
return b;
|
|
434
|
-
if (!b)
|
|
435
|
-
return a;
|
|
436
|
-
return unify(a, b);
|
|
437
|
-
}
|
|
438
|
-
var UNION_KIND_ORDER = {
|
|
439
|
-
uint: 0,
|
|
440
|
-
int: 1,
|
|
441
|
-
bool: 2,
|
|
442
|
-
principal: 3,
|
|
443
|
-
buffer: 4,
|
|
444
|
-
ascii: 5,
|
|
445
|
-
utf8: 6,
|
|
446
|
-
list: 7,
|
|
447
|
-
tuple: 8,
|
|
448
|
-
response: 9
|
|
449
|
-
};
|
|
450
|
-
function makeUnion(members) {
|
|
451
|
-
const sorted = [...members].sort((a, b) => (UNION_KIND_ORDER[a.kind] ?? 99) - (UNION_KIND_ORDER[b.kind] ?? 99));
|
|
452
|
-
return { kind: "union", members: sorted };
|
|
453
|
-
}
|
|
454
|
-
function unify(a, b) {
|
|
455
|
-
if (a.kind === "optional" || b.kind === "optional") {
|
|
456
|
-
const ai = a.kind === "optional" ? a.inner : a;
|
|
457
|
-
const bi = b.kind === "optional" ? b.inner : b;
|
|
458
|
-
return { kind: "optional", inner: unifyNullable(ai, bi) };
|
|
459
|
-
}
|
|
460
|
-
if (a.kind === "union")
|
|
461
|
-
return unionAdd(a.members, b);
|
|
462
|
-
if (b.kind === "union")
|
|
463
|
-
return unionAdd(b.members, a);
|
|
464
|
-
if (a.kind !== b.kind)
|
|
465
|
-
return makeUnion([a, b]);
|
|
466
|
-
switch (a.kind) {
|
|
467
|
-
case "uint":
|
|
468
|
-
case "int":
|
|
469
|
-
case "bool":
|
|
470
|
-
case "principal":
|
|
471
|
-
return a;
|
|
472
|
-
case "buffer":
|
|
473
|
-
case "ascii":
|
|
474
|
-
case "utf8":
|
|
475
|
-
return { kind: a.kind, len: Math.max(a.len, b.len) };
|
|
476
|
-
case "list":
|
|
477
|
-
return {
|
|
478
|
-
kind: "list",
|
|
479
|
-
inner: unifyNullable(a.inner, b.inner)
|
|
480
|
-
};
|
|
481
|
-
case "response": {
|
|
482
|
-
const rb = b;
|
|
483
|
-
return {
|
|
484
|
-
kind: "response",
|
|
485
|
-
ok: unifyNullable(a.ok, rb.ok),
|
|
486
|
-
err: unifyNullable(a.err, rb.err)
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
case "tuple": {
|
|
490
|
-
const tb = b;
|
|
491
|
-
const entries = new Map([...a.entries].map(([k, e]) => [k, { ...e }]));
|
|
492
|
-
for (const [k, e] of tb.entries) {
|
|
493
|
-
const existing = entries.get(k);
|
|
494
|
-
entries.set(k, existing ? {
|
|
495
|
-
tree: unify(existing.tree, e.tree),
|
|
496
|
-
present: existing.present + e.present
|
|
497
|
-
} : { ...e });
|
|
498
|
-
}
|
|
499
|
-
return { kind: "tuple", count: a.count + tb.count, entries };
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
function unionAdd(members, t) {
|
|
504
|
-
if (t.kind === "union") {
|
|
505
|
-
let acc = { kind: "union", members };
|
|
506
|
-
for (const m of t.members)
|
|
507
|
-
acc = unify(acc, m);
|
|
508
|
-
return acc;
|
|
509
|
-
}
|
|
510
|
-
const next = [...members];
|
|
511
|
-
for (let i = 0;i < next.length; i++) {
|
|
512
|
-
const member = next[i];
|
|
513
|
-
if (!member)
|
|
514
|
-
continue;
|
|
515
|
-
const merged = unify(member, t);
|
|
516
|
-
if (merged.kind !== "union") {
|
|
517
|
-
next[i] = merged;
|
|
518
|
-
return makeUnion(next);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
next.push(t);
|
|
522
|
-
return makeUnion(next);
|
|
523
|
-
}
|
|
524
|
-
function wrapOptional(t) {
|
|
525
|
-
return t.kind === "optional" ? t : { kind: "optional", inner: t };
|
|
526
|
-
}
|
|
527
|
-
function renderClarity(t) {
|
|
528
|
-
if (!t)
|
|
529
|
-
return "?";
|
|
530
|
-
switch (t.kind) {
|
|
531
|
-
case "uint":
|
|
532
|
-
case "int":
|
|
533
|
-
case "bool":
|
|
534
|
-
case "principal":
|
|
535
|
-
return t.kind;
|
|
536
|
-
case "buffer":
|
|
537
|
-
return `(buff ${t.len})`;
|
|
538
|
-
case "ascii":
|
|
539
|
-
return `(string-ascii ${t.len})`;
|
|
540
|
-
case "utf8":
|
|
541
|
-
return `(string-utf8 ${t.len})`;
|
|
542
|
-
case "optional":
|
|
543
|
-
return `(optional ${renderClarity(t.inner)})`;
|
|
544
|
-
case "list":
|
|
545
|
-
return `(list ${renderClarity(t.inner)})`;
|
|
546
|
-
case "response":
|
|
547
|
-
return `(response ${renderClarity(t.ok)} ${renderClarity(t.err)})`;
|
|
548
|
-
case "tuple": {
|
|
549
|
-
const parts = [...t.entries].map(([k, e]) => {
|
|
550
|
-
const tree = e.present < t.count ? wrapOptional(e.tree) : e.tree;
|
|
551
|
-
return `(${k} ${renderClarity(tree)})`;
|
|
552
|
-
});
|
|
553
|
-
return `(tuple ${parts.join(" ")})`;
|
|
554
|
-
}
|
|
555
|
-
case "union":
|
|
556
|
-
return t.members.map(renderClarity).join(" | ");
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
function renderTs(t) {
|
|
560
|
-
if (!t)
|
|
561
|
-
return "unknown";
|
|
562
|
-
switch (t.kind) {
|
|
563
|
-
case "uint":
|
|
564
|
-
case "int":
|
|
565
|
-
return "bigint";
|
|
566
|
-
case "bool":
|
|
567
|
-
return "boolean";
|
|
568
|
-
case "principal":
|
|
569
|
-
case "buffer":
|
|
570
|
-
case "ascii":
|
|
571
|
-
case "utf8":
|
|
572
|
-
return "string";
|
|
573
|
-
case "optional":
|
|
574
|
-
return t.inner ? `${renderTs(t.inner)} | null` : "unknown | null";
|
|
575
|
-
case "list": {
|
|
576
|
-
const inner = renderTs(t.inner);
|
|
577
|
-
return inner.includes(" | ") ? `(${inner})[]` : `${inner}[]`;
|
|
578
|
-
}
|
|
579
|
-
case "response": {
|
|
580
|
-
const sides = [...new Set([renderTs(t.ok), renderTs(t.err)])];
|
|
581
|
-
return sides.join(" | ");
|
|
582
|
-
}
|
|
583
|
-
case "tuple": {
|
|
584
|
-
const parts = [...t.entries].map(([k, e]) => {
|
|
585
|
-
const opt = e.present < t.count ? "?" : "";
|
|
586
|
-
return `${camelizeDataKey(k)}${opt}: ${renderTs(e.tree)}`;
|
|
587
|
-
});
|
|
588
|
-
return `{ ${parts.join("; ")} }`;
|
|
589
|
-
}
|
|
590
|
-
case "union":
|
|
591
|
-
return [...new Set(t.members.map((m) => renderTs(m)))].join(" | ");
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
function toColumnType(t) {
|
|
595
|
-
if (!t)
|
|
596
|
-
return "jsonb";
|
|
597
|
-
switch (t.kind) {
|
|
598
|
-
case "uint":
|
|
599
|
-
return "uint";
|
|
600
|
-
case "int":
|
|
601
|
-
return "int";
|
|
602
|
-
case "bool":
|
|
603
|
-
return "boolean";
|
|
604
|
-
case "principal":
|
|
605
|
-
return "principal";
|
|
606
|
-
case "buffer":
|
|
607
|
-
case "ascii":
|
|
608
|
-
case "utf8":
|
|
609
|
-
return "text";
|
|
610
|
-
case "list":
|
|
611
|
-
case "tuple":
|
|
612
|
-
return "jsonb";
|
|
613
|
-
case "optional":
|
|
614
|
-
return toColumnType(t.inner);
|
|
615
|
-
case "response":
|
|
616
|
-
return t.ok ? toColumnType(t.ok) : "jsonb";
|
|
617
|
-
case "union":
|
|
618
|
-
return "jsonb";
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
var MAX_DECODED_PER_TOPIC_NEWEST = 75;
|
|
622
|
-
var MAX_DECODED_PER_TOPIC_OLDEST = 25;
|
|
623
|
-
function inferPrintTopics(samples) {
|
|
624
|
-
const groups = new Map;
|
|
625
|
-
for (const s of samples) {
|
|
626
|
-
const group = groups.get(s.topic);
|
|
627
|
-
if (group)
|
|
628
|
-
group.push(s);
|
|
629
|
-
else
|
|
630
|
-
groups.set(s.topic, [s]);
|
|
631
|
-
}
|
|
632
|
-
const out = [];
|
|
633
|
-
for (const [topic, rows] of groups) {
|
|
634
|
-
let first = Number.POSITIVE_INFINITY;
|
|
635
|
-
let last = Number.NEGATIVE_INFINITY;
|
|
636
|
-
for (const r of rows) {
|
|
637
|
-
if (r.blockHeight < first)
|
|
638
|
-
first = r.blockHeight;
|
|
639
|
-
if (r.blockHeight > last)
|
|
640
|
-
last = r.blockHeight;
|
|
641
|
-
}
|
|
642
|
-
const withHex = [...rows].filter((r) => r.rawHex !== null).sort((a, b) => b.blockHeight - a.blockHeight);
|
|
643
|
-
const budget = MAX_DECODED_PER_TOPIC_NEWEST + MAX_DECODED_PER_TOPIC_OLDEST;
|
|
644
|
-
const picked = withHex.length <= budget ? withHex : [
|
|
645
|
-
...withHex.slice(0, MAX_DECODED_PER_TOPIC_NEWEST),
|
|
646
|
-
...withHex.slice(-MAX_DECODED_PER_TOPIC_OLDEST)
|
|
647
|
-
];
|
|
648
|
-
const tuples = [];
|
|
649
|
-
let decoded = 0;
|
|
650
|
-
for (const p of picked) {
|
|
651
|
-
try {
|
|
652
|
-
const cv = deserializeCV2(p.rawHex);
|
|
653
|
-
decoded++;
|
|
654
|
-
if (cv.type === "tuple")
|
|
655
|
-
tuples.push(cv);
|
|
656
|
-
} catch {}
|
|
657
|
-
}
|
|
658
|
-
const nonTuple = decoded > 0 && tuples.length === 0;
|
|
659
|
-
const fields = [];
|
|
660
|
-
if (!nonTuple) {
|
|
661
|
-
const stats = new Map;
|
|
662
|
-
for (const t of tuples) {
|
|
663
|
-
for (const [key, value] of Object.entries(t.value)) {
|
|
664
|
-
if (key === "topic")
|
|
665
|
-
continue;
|
|
666
|
-
const tree = cvToTree(value);
|
|
667
|
-
const existing = stats.get(key);
|
|
668
|
-
if (existing) {
|
|
669
|
-
existing.present++;
|
|
670
|
-
existing.tree = unify(existing.tree, tree);
|
|
671
|
-
if (value.type === "none")
|
|
672
|
-
existing.noneCount++;
|
|
673
|
-
if (value.type === "none" || value.type === "some") {
|
|
674
|
-
existing.optionalSeen = true;
|
|
675
|
-
}
|
|
676
|
-
} else {
|
|
677
|
-
stats.set(key, {
|
|
678
|
-
tree,
|
|
679
|
-
present: 1,
|
|
680
|
-
noneCount: value.type === "none" ? 1 : 0,
|
|
681
|
-
optionalSeen: value.type === "none" || value.type === "some"
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
for (const [name, st] of [...stats].sort(([a], [b]) => a.localeCompare(b))) {
|
|
687
|
-
const field = {
|
|
688
|
-
name,
|
|
689
|
-
camel_name: camelizeDataKey(name),
|
|
690
|
-
clarity_type: renderClarity(st.tree),
|
|
691
|
-
ts_type: renderTs(st.tree),
|
|
692
|
-
column_type: toColumnType(st.tree),
|
|
693
|
-
always_present: st.present === tuples.length
|
|
694
|
-
};
|
|
695
|
-
if (st.optionalSeen) {
|
|
696
|
-
field.optional_some_rate = (st.present - st.noneCount) / st.present;
|
|
697
|
-
}
|
|
698
|
-
fields.push(field);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
out.push({
|
|
702
|
-
topic,
|
|
703
|
-
count: rows.length,
|
|
704
|
-
first_height: first,
|
|
705
|
-
last_height: last,
|
|
706
|
-
non_tuple: nonTuple,
|
|
707
|
-
fields
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
return out.sort((a, b) => b.count - a.count);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// src/runtime/materialize.ts
|
|
714
|
-
function resolveColumn(col, event, tx, block) {
|
|
715
|
-
if ("fromTx" in col) {
|
|
716
|
-
return { ok: true, value: tx[col.fromTx] };
|
|
717
|
-
}
|
|
718
|
-
if ("fromBlock" in col) {
|
|
719
|
-
return { ok: true, value: block[col.fromBlock] };
|
|
720
|
-
}
|
|
721
|
-
const data = event.data;
|
|
722
|
-
const dataObj = data !== null && typeof data === "object" && !Array.isArray(data) ? data : undefined;
|
|
723
|
-
if (dataObj && Object.prototype.hasOwnProperty.call(dataObj, col.from)) {
|
|
724
|
-
return { ok: true, value: dataObj[col.from] };
|
|
725
|
-
}
|
|
726
|
-
if (col.from === "topic" && "topic" in event) {
|
|
727
|
-
return { ok: true, value: event.topic };
|
|
728
|
-
}
|
|
729
|
-
if (Object.prototype.hasOwnProperty.call(event, col.from)) {
|
|
730
|
-
return { ok: true, value: event[col.from] };
|
|
731
|
-
}
|
|
732
|
-
return { ok: false, missing: col.from };
|
|
733
|
-
}
|
|
734
|
-
function buildMaterializeRow(spec, event, ctx, tableColumns) {
|
|
735
|
-
const row = {};
|
|
736
|
-
for (const [colName, mapping] of Object.entries(spec.columns)) {
|
|
737
|
-
const resolved = resolveColumn(mapping, event, ctx.tx, ctx.block);
|
|
738
|
-
if (!resolved.ok) {
|
|
739
|
-
const colDef = tableColumns[colName];
|
|
740
|
-
if (colDef?.nullable) {
|
|
741
|
-
row[colName] = null;
|
|
742
|
-
continue;
|
|
743
|
-
}
|
|
744
|
-
return {
|
|
745
|
-
ok: false,
|
|
746
|
-
reason: `required materialize field "${resolved.missing}" missing for column "${colName}"`
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
row[colName] = resolved.value ?? null;
|
|
750
|
-
}
|
|
751
|
-
return { ok: true, row };
|
|
752
|
-
}
|
|
753
|
-
function applyMaterializeInsert(spec, event, ctx, schema) {
|
|
754
|
-
const table = schema[spec.table];
|
|
755
|
-
if (!table) {
|
|
756
|
-
return { ok: false, reason: `materialize table "${spec.table}" missing` };
|
|
757
|
-
}
|
|
758
|
-
const built = buildMaterializeRow(spec, event, ctx, table.columns);
|
|
759
|
-
if (!built.ok)
|
|
760
|
-
return built;
|
|
761
|
-
ctx.insert(spec.table, built.row);
|
|
762
|
-
return { ok: true };
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// src/runtime/print-validate.ts
|
|
766
|
-
var COLUMN_TYPES = new Set([
|
|
767
|
-
"text",
|
|
768
|
-
"uint",
|
|
769
|
-
"int",
|
|
770
|
-
"principal",
|
|
771
|
-
"boolean",
|
|
772
|
-
"timestamp",
|
|
773
|
-
"jsonb"
|
|
774
|
-
]);
|
|
775
|
-
function isColumnType(field) {
|
|
776
|
-
return typeof field === "string" && COLUMN_TYPES.has(field);
|
|
777
|
-
}
|
|
778
|
-
function matchesField(field, value) {
|
|
779
|
-
if (isColumnType(field)) {
|
|
780
|
-
switch (field) {
|
|
781
|
-
case "uint":
|
|
782
|
-
case "int":
|
|
783
|
-
return typeof value === "bigint" || typeof value === "number";
|
|
784
|
-
case "boolean":
|
|
785
|
-
return typeof value === "boolean";
|
|
786
|
-
case "text":
|
|
787
|
-
case "principal":
|
|
788
|
-
case "timestamp":
|
|
789
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint";
|
|
790
|
-
case "jsonb":
|
|
791
|
-
return value !== undefined;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
if (typeof field !== "object" || field === null)
|
|
795
|
-
return false;
|
|
796
|
-
if ("optional" in field) {
|
|
797
|
-
return value === undefined || value === null || matchesField(field.type, value);
|
|
798
|
-
}
|
|
799
|
-
if ("tuple" in field) {
|
|
800
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
801
|
-
return false;
|
|
802
|
-
}
|
|
803
|
-
return matchesTopic(field.tuple, value).ok;
|
|
804
|
-
}
|
|
805
|
-
if ("list" in field) {
|
|
806
|
-
return Array.isArray(value) && value.every((item) => matchesField(field.list, item));
|
|
807
|
-
}
|
|
808
|
-
return false;
|
|
809
|
-
}
|
|
810
|
-
function matchesTopic(declared, data) {
|
|
811
|
-
for (const [name, field] of Object.entries(declared)) {
|
|
812
|
-
const present = name in data;
|
|
813
|
-
const optional = typeof field === "object" && field !== null && "optional" in field;
|
|
814
|
-
if (!present) {
|
|
815
|
-
if (optional)
|
|
816
|
-
continue;
|
|
817
|
-
return { ok: false, reason: `missing declared field "${name}"` };
|
|
818
|
-
}
|
|
819
|
-
if (!matchesField(field, data[name])) {
|
|
820
|
-
return {
|
|
821
|
-
ok: false,
|
|
822
|
-
reason: `field "${name}" does not match its declared type (got ${describe(data[name])})`
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
return { ok: true };
|
|
827
|
-
}
|
|
828
|
-
function describe(value) {
|
|
829
|
-
if (value === null)
|
|
830
|
-
return "null";
|
|
831
|
-
if (Array.isArray(value))
|
|
832
|
-
return "array";
|
|
833
|
-
return typeof value;
|
|
834
|
-
}
|
|
835
|
-
function validatePrintPayload(prints, topic, data) {
|
|
836
|
-
if (!prints)
|
|
837
|
-
return { ok: true };
|
|
838
|
-
const declared = prints[topic];
|
|
839
|
-
if (!declared)
|
|
840
|
-
return { ok: true };
|
|
841
|
-
return matchesTopic(declared, data);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// src/runtime/violations.ts
|
|
845
|
-
import { getTargetDb } from "@secondlayer/shared/db";
|
|
846
|
-
import { logger } from "@secondlayer/shared/logger";
|
|
847
|
-
import { sql } from "kysely";
|
|
848
|
-
var VIOLATIONS_CAP_PER_SUBGRAPH = 100;
|
|
849
|
-
function jsonSafe(value) {
|
|
850
|
-
if (typeof value === "bigint")
|
|
851
|
-
return value.toString();
|
|
852
|
-
if (value === null || value === undefined)
|
|
853
|
-
return value;
|
|
854
|
-
if (Array.isArray(value))
|
|
855
|
-
return value.map(jsonSafe);
|
|
856
|
-
if (typeof value === "object") {
|
|
857
|
-
const out = {};
|
|
858
|
-
for (const [k, v] of Object.entries(value)) {
|
|
859
|
-
out[k] = jsonSafe(v);
|
|
860
|
-
}
|
|
861
|
-
return out;
|
|
862
|
-
}
|
|
863
|
-
return value;
|
|
864
|
-
}
|
|
865
|
-
async function defaultRecordPrintViolation(input) {
|
|
866
|
-
try {
|
|
867
|
-
const db = getTargetDb();
|
|
868
|
-
const sample = jsonSafe(input.samplePayload);
|
|
869
|
-
await db.insertInto("subgraph_violations").values({
|
|
870
|
-
subgraph_name: input.subgraphName,
|
|
871
|
-
source_name: input.sourceName,
|
|
872
|
-
block_height: input.blockHeight,
|
|
873
|
-
tx_id: input.txId,
|
|
874
|
-
reason: input.reason,
|
|
875
|
-
sample_payload: sample
|
|
876
|
-
}).execute();
|
|
877
|
-
await sql`
|
|
878
|
-
DELETE FROM subgraph_violations
|
|
879
|
-
WHERE id IN (
|
|
880
|
-
SELECT id FROM subgraph_violations
|
|
881
|
-
WHERE subgraph_name = ${input.subgraphName}
|
|
882
|
-
ORDER BY seen_at DESC
|
|
883
|
-
OFFSET ${VIOLATIONS_CAP_PER_SUBGRAPH}
|
|
884
|
-
)
|
|
885
|
-
`.execute(db);
|
|
886
|
-
} catch (err) {
|
|
887
|
-
logger.warn("Failed to record print violation", {
|
|
888
|
-
subgraph: input.subgraphName,
|
|
889
|
-
error: err instanceof Error ? err.message : String(err)
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
var recorder = defaultRecordPrintViolation;
|
|
894
|
-
async function recordPrintViolation(input) {
|
|
895
|
-
await recorder(input);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// src/runtime/runner.ts
|
|
899
|
-
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
900
|
-
function decodeFunctionArgs2(args) {
|
|
901
|
-
let parsed = args;
|
|
902
|
-
if (typeof parsed === "string") {
|
|
903
|
-
try {
|
|
904
|
-
parsed = JSON.parse(parsed);
|
|
905
|
-
} catch {
|
|
906
|
-
return [];
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
if (!Array.isArray(parsed))
|
|
910
|
-
return [];
|
|
911
|
-
return parsed.map((arg) => {
|
|
912
|
-
if (typeof arg === "string")
|
|
913
|
-
return decodeClarityValue(arg);
|
|
914
|
-
return arg;
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
function decodeRawResult(raw) {
|
|
918
|
-
if (typeof raw === "string" && raw.length > 2) {
|
|
919
|
-
return decodeClarityValue(raw);
|
|
920
|
-
}
|
|
921
|
-
return null;
|
|
922
|
-
}
|
|
923
|
-
function decodeRawHex(hex) {
|
|
924
|
-
if (hex.startsWith("0x") && hex.length > 2) {
|
|
925
|
-
return decodeClarityValue(hex);
|
|
926
|
-
}
|
|
927
|
-
return hex;
|
|
928
|
-
}
|
|
929
|
-
function safeBigInt(val) {
|
|
930
|
-
if (typeof val === "bigint")
|
|
931
|
-
return val;
|
|
932
|
-
if (typeof val === "number")
|
|
933
|
-
return BigInt(val);
|
|
934
|
-
if (typeof val === "string") {
|
|
935
|
-
try {
|
|
936
|
-
return BigInt(val);
|
|
937
|
-
} catch {
|
|
938
|
-
return 0n;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
return 0n;
|
|
942
|
-
}
|
|
943
|
-
function buildContractCallInput(filter, tx) {
|
|
944
|
-
const abi = filter.abi;
|
|
945
|
-
const fnName = tx.function_name;
|
|
946
|
-
if (!abi || !fnName)
|
|
947
|
-
return;
|
|
948
|
-
const fn = abi.functions?.find((f) => f.name === fnName);
|
|
949
|
-
if (!fn || !Array.isArray(fn.args))
|
|
950
|
-
return;
|
|
951
|
-
let rawArgs = tx.function_args;
|
|
952
|
-
if (typeof rawArgs === "string") {
|
|
953
|
-
try {
|
|
954
|
-
rawArgs = JSON.parse(rawArgs);
|
|
955
|
-
} catch {
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
if (!Array.isArray(rawArgs))
|
|
960
|
-
return;
|
|
961
|
-
const decodeArg = clarityValueToJS;
|
|
962
|
-
const input = {};
|
|
963
|
-
fn.args.forEach((arg, i) => {
|
|
964
|
-
const hex = rawArgs[i];
|
|
965
|
-
if (typeof hex !== "string")
|
|
966
|
-
return;
|
|
967
|
-
try {
|
|
968
|
-
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
969
|
-
input[toCamelCase(arg.name)] = decodeArg(arg.type, deserializeCV3(clean));
|
|
970
|
-
} catch {}
|
|
971
|
-
});
|
|
972
|
-
return input;
|
|
973
|
-
}
|
|
974
|
-
function buildEventPayload(filter, tx, event) {
|
|
975
|
-
const txMeta = {
|
|
976
|
-
txId: tx.tx_id,
|
|
977
|
-
sender: tx.sender,
|
|
978
|
-
type: tx.type,
|
|
979
|
-
status: tx.status,
|
|
980
|
-
contractId: tx.contract_id ?? null,
|
|
981
|
-
functionName: tx.function_name ?? null
|
|
982
|
-
};
|
|
983
|
-
const decodedArgs = decodeFunctionArgs2(tx.function_args);
|
|
984
|
-
const decodedResult = decodeRawResult(tx.raw_result);
|
|
985
|
-
if (!event) {
|
|
986
|
-
switch (filter.type) {
|
|
987
|
-
case "contract_call": {
|
|
988
|
-
const input = buildContractCallInput(filter, tx);
|
|
989
|
-
return {
|
|
990
|
-
type: "contract_call",
|
|
991
|
-
contractId: tx.contract_id ?? "",
|
|
992
|
-
functionName: tx.function_name ?? "",
|
|
993
|
-
sender: tx.sender,
|
|
994
|
-
args: decodedArgs,
|
|
995
|
-
...input !== undefined ? { input } : {},
|
|
996
|
-
result: decodedResult,
|
|
997
|
-
resultHex: tx.raw_result ?? null,
|
|
998
|
-
tx: txMeta
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
case "contract_deploy":
|
|
1002
|
-
return {
|
|
1003
|
-
contractId: tx.contract_id ?? "",
|
|
1004
|
-
deployer: tx.sender,
|
|
1005
|
-
tx: txMeta
|
|
1006
|
-
};
|
|
1007
|
-
default:
|
|
1008
|
-
return { tx: txMeta };
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
const decoded = decodeEventData(event.data);
|
|
1012
|
-
switch (filter.type) {
|
|
1013
|
-
case "ft_transfer":
|
|
1014
|
-
return {
|
|
1015
|
-
sender: decoded.sender,
|
|
1016
|
-
recipient: decoded.recipient,
|
|
1017
|
-
amount: safeBigInt(decoded.amount),
|
|
1018
|
-
assetIdentifier: decoded.asset_identifier,
|
|
1019
|
-
tx: txMeta
|
|
1020
|
-
};
|
|
1021
|
-
case "ft_mint":
|
|
1022
|
-
return {
|
|
1023
|
-
recipient: decoded.recipient,
|
|
1024
|
-
amount: safeBigInt(decoded.amount),
|
|
1025
|
-
assetIdentifier: decoded.asset_identifier,
|
|
1026
|
-
tx: txMeta
|
|
1027
|
-
};
|
|
1028
|
-
case "ft_burn":
|
|
1029
|
-
return {
|
|
1030
|
-
sender: decoded.sender,
|
|
1031
|
-
amount: safeBigInt(decoded.amount),
|
|
1032
|
-
assetIdentifier: decoded.asset_identifier,
|
|
1033
|
-
tx: txMeta
|
|
1034
|
-
};
|
|
1035
|
-
case "nft_transfer":
|
|
1036
|
-
return {
|
|
1037
|
-
sender: decoded.sender,
|
|
1038
|
-
recipient: decoded.recipient,
|
|
1039
|
-
tokenId: decoded.raw_value ?? decoded.value,
|
|
1040
|
-
assetIdentifier: decoded.asset_identifier,
|
|
1041
|
-
tx: txMeta
|
|
1042
|
-
};
|
|
1043
|
-
case "nft_mint":
|
|
1044
|
-
return {
|
|
1045
|
-
recipient: decoded.recipient,
|
|
1046
|
-
tokenId: decoded.raw_value ?? decoded.value,
|
|
1047
|
-
assetIdentifier: decoded.asset_identifier,
|
|
1048
|
-
tx: txMeta
|
|
1049
|
-
};
|
|
1050
|
-
case "nft_burn":
|
|
1051
|
-
return {
|
|
1052
|
-
sender: decoded.sender,
|
|
1053
|
-
tokenId: decoded.raw_value ?? decoded.value,
|
|
1054
|
-
assetIdentifier: decoded.asset_identifier,
|
|
1055
|
-
tx: txMeta
|
|
1056
|
-
};
|
|
1057
|
-
case "stx_transfer":
|
|
1058
|
-
return {
|
|
1059
|
-
sender: decoded.sender,
|
|
1060
|
-
recipient: decoded.recipient,
|
|
1061
|
-
amount: safeBigInt(decoded.amount),
|
|
1062
|
-
memo: decoded.memo ?? "",
|
|
1063
|
-
tx: txMeta
|
|
1064
|
-
};
|
|
1065
|
-
case "stx_mint":
|
|
1066
|
-
return {
|
|
1067
|
-
recipient: decoded.recipient,
|
|
1068
|
-
amount: safeBigInt(decoded.amount),
|
|
1069
|
-
tx: txMeta
|
|
1070
|
-
};
|
|
1071
|
-
case "stx_burn":
|
|
1072
|
-
return {
|
|
1073
|
-
sender: decoded.sender,
|
|
1074
|
-
amount: safeBigInt(decoded.amount),
|
|
1075
|
-
tx: txMeta
|
|
1076
|
-
};
|
|
1077
|
-
case "stx_lock":
|
|
1078
|
-
return {
|
|
1079
|
-
lockedAddress: decoded.locked_address,
|
|
1080
|
-
lockedAmount: safeBigInt(decoded.locked_amount),
|
|
1081
|
-
unlockHeight: safeBigInt(decoded.unlock_height),
|
|
1082
|
-
tx: txMeta
|
|
1083
|
-
};
|
|
1084
|
-
case "print_event": {
|
|
1085
|
-
const rawHex = event.data?.raw_value;
|
|
1086
|
-
const rawValue = typeof rawHex === "string" && rawHex.startsWith("0x") ? decodeClarityValue(rawHex) : decoded.value;
|
|
1087
|
-
const clarityObj = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue) ? rawValue : null;
|
|
1088
|
-
const topic = clarityObj?.topic ? String(clarityObj.topic) : decoded.topic ?? "";
|
|
1089
|
-
const { topic: _, ...rest } = clarityObj ?? {};
|
|
1090
|
-
const data = Object.keys(rest).length > 0 ? camelizeKeys(rest) : rawValue && typeof rawValue !== "object" ? rawValue : {};
|
|
1091
|
-
return {
|
|
1092
|
-
contractId: printContractId(decoded) ?? tx.contract_id ?? "",
|
|
1093
|
-
topic,
|
|
1094
|
-
data: data ?? {},
|
|
1095
|
-
tx: txMeta
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
case "contract_call": {
|
|
1099
|
-
const ccRawHex = event.data?.raw_value;
|
|
1100
|
-
const normalized = typeof ccRawHex === "string" && ccRawHex.startsWith("0x") ? { ...decoded, value: decodeClarityValue(ccRawHex) } : decoded;
|
|
1101
|
-
const input = buildContractCallInput(filter, tx);
|
|
1102
|
-
return {
|
|
1103
|
-
...normalized,
|
|
1104
|
-
type: "contract_call",
|
|
1105
|
-
_eventType: event.type,
|
|
1106
|
-
contractId: tx.contract_id ?? "",
|
|
1107
|
-
functionName: tx.function_name ?? "",
|
|
1108
|
-
sender: tx.sender,
|
|
1109
|
-
args: decodedArgs,
|
|
1110
|
-
...input !== undefined ? { input } : {},
|
|
1111
|
-
result: decodedResult,
|
|
1112
|
-
resultHex: tx.raw_result ?? null,
|
|
1113
|
-
tx: txMeta
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
|
-
case "contract_deploy":
|
|
1117
|
-
return {
|
|
1118
|
-
contractId: tx.contract_id ?? "",
|
|
1119
|
-
deployer: tx.sender,
|
|
1120
|
-
tx: txMeta
|
|
1121
|
-
};
|
|
1122
|
-
case "nested_contract_call": {
|
|
1123
|
-
const data = event.data ?? {};
|
|
1124
|
-
const argsHex = Array.isArray(data.function_args) ? data.function_args.filter((a) => typeof a === "string") : [];
|
|
1125
|
-
return {
|
|
1126
|
-
contractId: printContractId(data) ?? "",
|
|
1127
|
-
sender: typeof data.sender === "string" ? data.sender : null,
|
|
1128
|
-
caller: typeof data.caller === "string" ? data.caller : "",
|
|
1129
|
-
functionName: typeof data.function_name === "string" ? data.function_name : "",
|
|
1130
|
-
args: decodeFunctionArgs2(argsHex),
|
|
1131
|
-
arguments: argsHex,
|
|
1132
|
-
rawResult: typeof data.raw_result === "string" ? data.raw_result : "",
|
|
1133
|
-
result: decodeRawResult(data.raw_result),
|
|
1134
|
-
tx: txMeta
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
case "var_set": {
|
|
1138
|
-
const data = event.data ?? {};
|
|
1139
|
-
const rawValue = typeof data.raw_value === "string" ? data.raw_value : "";
|
|
1140
|
-
return {
|
|
1141
|
-
contractId: printContractId(data) ?? "",
|
|
1142
|
-
varName: typeof data.var_name === "string" ? data.var_name : "",
|
|
1143
|
-
rawValue,
|
|
1144
|
-
value: decodeRawHex(rawValue),
|
|
1145
|
-
tx: txMeta
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
case "map_set":
|
|
1149
|
-
case "map_insert": {
|
|
1150
|
-
const data = event.data ?? {};
|
|
1151
|
-
const rawKey = typeof data.raw_key === "string" ? data.raw_key : "";
|
|
1152
|
-
const rawValue = typeof data.raw_value === "string" ? data.raw_value : "";
|
|
1153
|
-
return {
|
|
1154
|
-
contractId: printContractId(data) ?? "",
|
|
1155
|
-
map: typeof data.map_name === "string" ? data.map_name : "",
|
|
1156
|
-
rawKey,
|
|
1157
|
-
key: decodeRawHex(rawKey),
|
|
1158
|
-
rawValue,
|
|
1159
|
-
value: decodeRawHex(rawValue),
|
|
1160
|
-
tx: txMeta
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
case "map_delete": {
|
|
1164
|
-
const data = event.data ?? {};
|
|
1165
|
-
const rawKey = typeof data.raw_key === "string" ? data.raw_key : "";
|
|
1166
|
-
return {
|
|
1167
|
-
contractId: printContractId(data) ?? "",
|
|
1168
|
-
map: typeof data.map_name === "string" ? data.map_name : "",
|
|
1169
|
-
rawKey,
|
|
1170
|
-
key: decodeRawHex(rawKey),
|
|
1171
|
-
tx: txMeta
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
default:
|
|
1175
|
-
return {
|
|
1176
|
-
...decoded,
|
|
1177
|
-
_eventType: event.type,
|
|
1178
|
-
tx: txMeta
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
async function runHandlers(subgraph, matched, ctx, opts) {
|
|
1183
|
-
let processed = 0;
|
|
1184
|
-
let errors = 0;
|
|
1185
|
-
let skipped = 0;
|
|
1186
|
-
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
1187
|
-
const filterLookup = new Map;
|
|
1188
|
-
if (!Array.isArray(subgraph.sources)) {
|
|
1189
|
-
for (const [name, filter] of Object.entries(subgraph.sources)) {
|
|
1190
|
-
filterLookup.set(name, filter);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
const units = [];
|
|
1194
|
-
for (const { tx, events, sourceName } of matched) {
|
|
1195
|
-
if (events.length === 0) {
|
|
1196
|
-
units.push({ tx, sourceName, event: null });
|
|
1197
|
-
} else {
|
|
1198
|
-
for (const event of events)
|
|
1199
|
-
units.push({ tx, sourceName, event });
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
const clockRank = (e) => e?.clock === "vm" ? 1 : 0;
|
|
1203
|
-
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));
|
|
1204
|
-
for (const { tx, event, sourceName } of units) {
|
|
1205
|
-
if (errors >= threshold) {
|
|
1206
|
-
logger2.error("Subgraph error threshold reached, skipping remaining events", {
|
|
1207
|
-
subgraph: subgraph.name,
|
|
1208
|
-
errors,
|
|
1209
|
-
threshold
|
|
1210
|
-
});
|
|
1211
|
-
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1212
|
-
}
|
|
1213
|
-
const filter = filterLookup.get(sourceName);
|
|
1214
|
-
const materialize = filter && "materialize" in filter && filter.materialize !== undefined ? filter.materialize : undefined;
|
|
1215
|
-
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1216
|
-
if (!handler && !materialize) {
|
|
1217
|
-
logger2.warn("No handler found for source", {
|
|
1218
|
-
subgraph: subgraph.name,
|
|
1219
|
-
sourceName,
|
|
1220
|
-
txId: tx.tx_id
|
|
1221
|
-
});
|
|
1222
|
-
continue;
|
|
1223
|
-
}
|
|
1224
|
-
ctx.setTx({
|
|
1225
|
-
txId: tx.tx_id,
|
|
1226
|
-
sender: tx.sender,
|
|
1227
|
-
type: tx.type,
|
|
1228
|
-
status: tx.status,
|
|
1229
|
-
contractId: tx.contract_id ?? null,
|
|
1230
|
-
functionName: tx.function_name ?? null
|
|
1231
|
-
});
|
|
1232
|
-
const checkpoint = ctx.opsCheckpoint();
|
|
1233
|
-
try {
|
|
1234
|
-
let payload;
|
|
1235
|
-
if (event === null) {
|
|
1236
|
-
payload = filter ? buildEventPayload(filter, tx, null) : {
|
|
1237
|
-
tx: {
|
|
1238
|
-
txId: tx.tx_id,
|
|
1239
|
-
sender: tx.sender,
|
|
1240
|
-
type: tx.type,
|
|
1241
|
-
status: tx.status,
|
|
1242
|
-
contractId: tx.contract_id,
|
|
1243
|
-
functionName: tx.function_name
|
|
1244
|
-
}
|
|
1245
|
-
};
|
|
1246
|
-
} else if (filter) {
|
|
1247
|
-
payload = buildEventPayload(filter, tx, event);
|
|
1248
|
-
} else {
|
|
1249
|
-
const decoded = decodeEventData(event.data);
|
|
1250
|
-
payload = {
|
|
1251
|
-
...decoded,
|
|
1252
|
-
_eventId: event.id,
|
|
1253
|
-
_eventType: event.type,
|
|
1254
|
-
_eventIndex: event.event_index,
|
|
1255
|
-
tx: {
|
|
1256
|
-
txId: tx.tx_id,
|
|
1257
|
-
sender: tx.sender,
|
|
1258
|
-
type: tx.type,
|
|
1259
|
-
status: tx.status,
|
|
1260
|
-
contractId: tx.contract_id,
|
|
1261
|
-
functionName: tx.function_name
|
|
1262
|
-
}
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
1266
|
-
continue;
|
|
1267
|
-
}
|
|
1268
|
-
if (event !== null && filter?.type === "print_event" && filter.prints) {
|
|
1269
|
-
const printPayload = payload;
|
|
1270
|
-
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1271
|
-
if (!verdict.ok) {
|
|
1272
|
-
skipped++;
|
|
1273
|
-
logger2.warn("Print payload does not match declared prints schema", {
|
|
1274
|
-
subgraph: subgraph.name,
|
|
1275
|
-
sourceName,
|
|
1276
|
-
topic: printPayload.topic,
|
|
1277
|
-
txId: tx.tx_id,
|
|
1278
|
-
reason: verdict.reason
|
|
1279
|
-
});
|
|
1280
|
-
recordPrintViolation({
|
|
1281
|
-
subgraphName: subgraph.name,
|
|
1282
|
-
sourceName,
|
|
1283
|
-
blockHeight: ctx.block.height,
|
|
1284
|
-
txId: tx.tx_id,
|
|
1285
|
-
reason: verdict.reason,
|
|
1286
|
-
samplePayload: {
|
|
1287
|
-
topic: printPayload.topic ?? "",
|
|
1288
|
-
data: printPayload.data ?? {}
|
|
1289
|
-
}
|
|
1290
|
-
});
|
|
1291
|
-
continue;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
if (materialize) {
|
|
1295
|
-
const result = applyMaterializeInsert(materialize, payload, ctx, subgraph.schema);
|
|
1296
|
-
if (!result.ok) {
|
|
1297
|
-
skipped++;
|
|
1298
|
-
logger2.warn("Materialize skipped: required field missing", {
|
|
1299
|
-
subgraph: subgraph.name,
|
|
1300
|
-
sourceName,
|
|
1301
|
-
txId: tx.tx_id,
|
|
1302
|
-
reason: result.reason
|
|
1303
|
-
});
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
processed++;
|
|
1307
|
-
} else if (handler) {
|
|
1308
|
-
await handler(payload, ctx);
|
|
1309
|
-
processed++;
|
|
1310
|
-
}
|
|
1311
|
-
} catch (err) {
|
|
1312
|
-
ctx.rollbackTo(checkpoint);
|
|
1313
|
-
errors++;
|
|
1314
|
-
logger2.error("Subgraph handler error", {
|
|
1315
|
-
subgraph: subgraph.name,
|
|
1316
|
-
sourceName,
|
|
1317
|
-
txId: tx.tx_id,
|
|
1318
|
-
...event !== null ? { eventId: event.id, eventType: event.type } : {},
|
|
1319
|
-
error: getErrorMessage(err)
|
|
1320
|
-
});
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1324
|
-
}
|
|
1325
|
-
export {
|
|
1326
|
-
runHandlers,
|
|
1327
|
-
buildEventPayload,
|
|
1328
|
-
buildContractCallInput
|
|
1329
|
-
};
|
|
1330
|
-
|
|
1331
|
-
//# debugId=90E8BB694CDBE41B64756E2164756E21
|
|
1332
|
-
//# sourceMappingURL=runner.js.map
|