@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,331 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
-
|
|
4
|
-
// src/runtime/source-matcher.ts
|
|
5
|
-
var patternCache = new Map;
|
|
6
|
-
function matchAny(value, pattern) {
|
|
7
|
-
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
8
|
-
}
|
|
9
|
-
function matchPattern(value, pattern) {
|
|
10
|
-
if (!pattern.includes("*"))
|
|
11
|
-
return value === pattern;
|
|
12
|
-
let re = patternCache.get(pattern);
|
|
13
|
-
if (!re) {
|
|
14
|
-
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
15
|
-
re = new RegExp(`^${regex}$`);
|
|
16
|
-
patternCache.set(pattern, re);
|
|
17
|
-
}
|
|
18
|
-
return re.test(value);
|
|
19
|
-
}
|
|
20
|
-
var EMPTY_SET = new Set;
|
|
21
|
-
function factoryAllows(filter, contractId, factoryContracts) {
|
|
22
|
-
if (!filter.factory)
|
|
23
|
-
return true;
|
|
24
|
-
if (!contractId)
|
|
25
|
-
return false;
|
|
26
|
-
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
27
|
-
}
|
|
28
|
-
function readPath(payload, path) {
|
|
29
|
-
let cur = payload;
|
|
30
|
-
for (const key of path.split(".")) {
|
|
31
|
-
if (cur === null || typeof cur !== "object")
|
|
32
|
-
return;
|
|
33
|
-
cur = cur[key];
|
|
34
|
-
}
|
|
35
|
-
return cur;
|
|
36
|
-
}
|
|
37
|
-
function traitAllows(filter, contractId, traitContracts) {
|
|
38
|
-
const trait = filter.trait;
|
|
39
|
-
if (!trait)
|
|
40
|
-
return true;
|
|
41
|
-
if (!contractId)
|
|
42
|
-
return false;
|
|
43
|
-
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
44
|
-
}
|
|
45
|
-
function assetContract(assetId) {
|
|
46
|
-
return assetId?.split("::")[0];
|
|
47
|
-
}
|
|
48
|
-
function printContractId(data) {
|
|
49
|
-
if (!data)
|
|
50
|
-
return;
|
|
51
|
-
const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
|
|
52
|
-
return id || undefined;
|
|
53
|
-
}
|
|
54
|
-
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map, vmEventsByTx = new Map) {
|
|
55
|
-
const results = [];
|
|
56
|
-
switch (filter.type) {
|
|
57
|
-
case "stx_transfer":
|
|
58
|
-
case "stx_mint":
|
|
59
|
-
case "stx_burn":
|
|
60
|
-
case "stx_lock": {
|
|
61
|
-
const eventType = `${filter.type}_event`;
|
|
62
|
-
for (const tx of transactions) {
|
|
63
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
64
|
-
const matched = txEvents.filter((e) => e.type === eventType);
|
|
65
|
-
if (matched.length === 0)
|
|
66
|
-
continue;
|
|
67
|
-
const filtered = matched.filter((e) => {
|
|
68
|
-
const data = e.data;
|
|
69
|
-
if (!data)
|
|
70
|
-
return false;
|
|
71
|
-
if ("sender" in filter && filter.sender) {
|
|
72
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
if ("recipient" in filter && filter.recipient) {
|
|
76
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
80
|
-
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
84
|
-
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
85
|
-
if (amount < filter.minAmount)
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
89
|
-
const amount = BigInt(data.amount ?? "0");
|
|
90
|
-
if (amount > filter.maxAmount)
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return true;
|
|
94
|
-
});
|
|
95
|
-
if (filtered.length > 0) {
|
|
96
|
-
results.push({ tx, events: filtered });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
case "ft_transfer":
|
|
102
|
-
case "ft_mint":
|
|
103
|
-
case "ft_burn": {
|
|
104
|
-
const eventType = `${filter.type}_event`;
|
|
105
|
-
for (const tx of transactions) {
|
|
106
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
107
|
-
const matched = txEvents.filter((e) => {
|
|
108
|
-
if (e.type !== eventType)
|
|
109
|
-
return false;
|
|
110
|
-
const data = e.data;
|
|
111
|
-
if (!data)
|
|
112
|
-
return false;
|
|
113
|
-
if (filter.assetIdentifier) {
|
|
114
|
-
const assetId = data.asset_identifier;
|
|
115
|
-
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
119
|
-
return false;
|
|
120
|
-
if ("sender" in filter && filter.sender) {
|
|
121
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
if ("recipient" in filter && filter.recipient) {
|
|
125
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
if (filter.minAmount !== undefined) {
|
|
129
|
-
const amount = BigInt(data.amount ?? "0");
|
|
130
|
-
if (amount < filter.minAmount)
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
return true;
|
|
134
|
-
});
|
|
135
|
-
if (matched.length > 0) {
|
|
136
|
-
results.push({ tx, events: matched });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
case "nft_transfer":
|
|
142
|
-
case "nft_mint":
|
|
143
|
-
case "nft_burn": {
|
|
144
|
-
const eventType = `${filter.type}_event`;
|
|
145
|
-
for (const tx of transactions) {
|
|
146
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
147
|
-
const matched = txEvents.filter((e) => {
|
|
148
|
-
if (e.type !== eventType)
|
|
149
|
-
return false;
|
|
150
|
-
const data = e.data;
|
|
151
|
-
if (!data)
|
|
152
|
-
return false;
|
|
153
|
-
if (filter.assetIdentifier) {
|
|
154
|
-
const assetId = data.asset_identifier;
|
|
155
|
-
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
159
|
-
return false;
|
|
160
|
-
if ("sender" in filter && filter.sender) {
|
|
161
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
if ("recipient" in filter && filter.recipient) {
|
|
165
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
return true;
|
|
169
|
-
});
|
|
170
|
-
if (matched.length > 0) {
|
|
171
|
-
results.push({ tx, events: matched });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
case "contract_call": {
|
|
177
|
-
for (const tx of transactions) {
|
|
178
|
-
if (tx.type !== "contract_call")
|
|
179
|
-
continue;
|
|
180
|
-
if (filter.contractId) {
|
|
181
|
-
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
185
|
-
continue;
|
|
186
|
-
if (filter.functionName) {
|
|
187
|
-
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
if (filter.caller) {
|
|
191
|
-
if (!matchPattern(tx.sender, filter.caller))
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
195
|
-
continue;
|
|
196
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
197
|
-
results.push({ tx, events: txEvents });
|
|
198
|
-
}
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
case "contract_deploy": {
|
|
202
|
-
for (const tx of transactions) {
|
|
203
|
-
if (tx.type !== "smart_contract")
|
|
204
|
-
continue;
|
|
205
|
-
if (filter.deployer) {
|
|
206
|
-
if (!matchPattern(tx.sender, filter.deployer))
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
if (filter.contractName) {
|
|
210
|
-
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
211
|
-
if (!matchPattern(name, filter.contractName))
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
215
|
-
results.push({ tx, events: txEvents });
|
|
216
|
-
}
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
case "nested_contract_call":
|
|
220
|
-
case "var_set":
|
|
221
|
-
case "map_set":
|
|
222
|
-
case "map_insert":
|
|
223
|
-
case "map_delete": {
|
|
224
|
-
for (const tx of transactions) {
|
|
225
|
-
const txEvents = vmEventsByTx.get(tx.tx_id) ?? [];
|
|
226
|
-
const matched = txEvents.filter((e) => {
|
|
227
|
-
if (e.type !== filter.type)
|
|
228
|
-
return false;
|
|
229
|
-
const data = e.data;
|
|
230
|
-
if (!data)
|
|
231
|
-
return false;
|
|
232
|
-
const contractId = printContractId(data);
|
|
233
|
-
if ("contractId" in filter && filter.contractId) {
|
|
234
|
-
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
if (!traitAllows(filter, contractId, traitContracts))
|
|
238
|
-
return false;
|
|
239
|
-
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
240
|
-
return false;
|
|
241
|
-
if (filter.type === "nested_contract_call") {
|
|
242
|
-
if (filter.functionName) {
|
|
243
|
-
if (typeof data.function_name !== "string" || !matchPattern(data.function_name, filter.functionName))
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
if (filter.caller) {
|
|
247
|
-
if (typeof data.caller !== "string" || !matchPattern(data.caller, filter.caller))
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
if (filter.sender) {
|
|
251
|
-
if (typeof data.sender !== "string" || !matchPattern(data.sender, filter.sender))
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (filter.type === "var_set" && filter.varName) {
|
|
256
|
-
if (typeof data.var_name !== "string" || !matchPattern(data.var_name, filter.varName))
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
if ((filter.type === "map_set" || filter.type === "map_insert" || filter.type === "map_delete") && filter.map) {
|
|
260
|
-
if (typeof data.map_name !== "string" || !matchPattern(data.map_name, filter.map))
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
return true;
|
|
264
|
-
});
|
|
265
|
-
if (matched.length > 0)
|
|
266
|
-
results.push({ tx, events: matched });
|
|
267
|
-
}
|
|
268
|
-
break;
|
|
269
|
-
}
|
|
270
|
-
case "print_event": {
|
|
271
|
-
for (const tx of transactions) {
|
|
272
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
273
|
-
const matched = txEvents.filter((e) => {
|
|
274
|
-
if (e.type !== "smart_contract_event" && e.type !== "contract_event")
|
|
275
|
-
return false;
|
|
276
|
-
const data = e.data;
|
|
277
|
-
if (!data)
|
|
278
|
-
return false;
|
|
279
|
-
if (data.topic !== "print")
|
|
280
|
-
return false;
|
|
281
|
-
const contractId = printContractId(data);
|
|
282
|
-
if (filter.contractId) {
|
|
283
|
-
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
if (!traitAllows(filter, contractId, traitContracts))
|
|
287
|
-
return false;
|
|
288
|
-
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
289
|
-
return false;
|
|
290
|
-
return true;
|
|
291
|
-
});
|
|
292
|
-
if (matched.length > 0) {
|
|
293
|
-
results.push({ tx, events: matched });
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return results;
|
|
300
|
-
}
|
|
301
|
-
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map, vmEvents = []) {
|
|
302
|
-
const eventsByTx = new Map;
|
|
303
|
-
for (const event of events) {
|
|
304
|
-
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
305
|
-
list.push(event);
|
|
306
|
-
eventsByTx.set(event.tx_id, list);
|
|
307
|
-
}
|
|
308
|
-
const vmEventsByTx = new Map;
|
|
309
|
-
for (const event of vmEvents) {
|
|
310
|
-
const list = vmEventsByTx.get(event.tx_id) ?? [];
|
|
311
|
-
list.push(event);
|
|
312
|
-
vmEventsByTx.set(event.tx_id, list);
|
|
313
|
-
}
|
|
314
|
-
const seen = new Set;
|
|
315
|
-
const results = [];
|
|
316
|
-
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
317
|
-
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts, vmEventsByTx);
|
|
318
|
-
for (const match of matches) {
|
|
319
|
-
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
320
|
-
if (!seen.has(dedupeKey)) {
|
|
321
|
-
seen.add(dedupeKey);
|
|
322
|
-
results.push({ ...match, sourceName });
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return results;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
1
|
// src/runtime/replay.ts
|
|
330
2
|
import { createHash } from "node:crypto";
|
|
331
3
|
import { getTargetDb } from "@secondlayer/shared/db";
|
|
@@ -408,201 +80,11 @@ async function loadBlockRange(db, fromHeight, toHeight) {
|
|
|
408
80
|
}
|
|
409
81
|
return result;
|
|
410
82
|
}
|
|
411
|
-
function avgEventsPerBlock(batch) {
|
|
412
|
-
if (batch.size === 0)
|
|
413
|
-
return 0;
|
|
414
|
-
let totalEvents = 0;
|
|
415
|
-
for (const data of batch.values()) {
|
|
416
|
-
totalEvents += data.events.length;
|
|
417
|
-
}
|
|
418
|
-
return totalEvents / batch.size;
|
|
419
|
-
}
|
|
420
83
|
|
|
421
84
|
// src/runtime/observer-http-source.ts
|
|
422
85
|
import {
|
|
423
86
|
VM_NODE_TO_STORED_TYPE
|
|
424
87
|
} from "@secondlayer/shared";
|
|
425
|
-
var OBSERVER_HTTP_EXPORT_PATH = "/internal/observer-events";
|
|
426
|
-
function stripTrailingSlash(url) {
|
|
427
|
-
return url.replace(/\/+$/, "");
|
|
428
|
-
}
|
|
429
|
-
function blockTimestamp(payload) {
|
|
430
|
-
for (const value of [
|
|
431
|
-
payload.timestamp,
|
|
432
|
-
payload.block_time,
|
|
433
|
-
payload.burn_block_time,
|
|
434
|
-
payload.burn_block_timestamp
|
|
435
|
-
]) {
|
|
436
|
-
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
437
|
-
return value;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
return 0;
|
|
441
|
-
}
|
|
442
|
-
function eventData(event) {
|
|
443
|
-
if (typeof event.type === "string" && event.type in event) {
|
|
444
|
-
return event[event.type] ?? {};
|
|
445
|
-
}
|
|
446
|
-
return {};
|
|
447
|
-
}
|
|
448
|
-
function mapNewBlockPayloadToBlockData(payload) {
|
|
449
|
-
const p = payload;
|
|
450
|
-
if (typeof p.block_height !== "number" || !Number.isFinite(p.block_height) || typeof p.index_block_hash !== "string" || p.index_block_hash.length === 0) {
|
|
451
|
-
throw new Error("observer /new_block payload missing block_height or index_block_hash");
|
|
452
|
-
}
|
|
453
|
-
const height = p.block_height;
|
|
454
|
-
const block = {
|
|
455
|
-
height,
|
|
456
|
-
hash: p.block_hash ?? "",
|
|
457
|
-
parent_hash: p.parent_block_hash ?? "",
|
|
458
|
-
burn_block_height: p.burn_block_height ?? 0,
|
|
459
|
-
burn_block_hash: p.burn_block_hash ?? null,
|
|
460
|
-
index_block_hash: p.index_block_hash,
|
|
461
|
-
timestamp: blockTimestamp(p),
|
|
462
|
-
canonical: true,
|
|
463
|
-
created_at: new Date(0)
|
|
464
|
-
};
|
|
465
|
-
const txs = (p.transactions ?? []).map((tx) => {
|
|
466
|
-
let contractId = null;
|
|
467
|
-
let functionName = null;
|
|
468
|
-
if (tx.tx_type === "contract_call" && tx.contract_call) {
|
|
469
|
-
contractId = tx.contract_call.contract_id ?? null;
|
|
470
|
-
functionName = tx.contract_call.function_name ?? null;
|
|
471
|
-
} else if (tx.tx_type === "smart_contract" && tx.smart_contract) {
|
|
472
|
-
contractId = tx.smart_contract.contract_id ?? null;
|
|
473
|
-
}
|
|
474
|
-
return {
|
|
475
|
-
tx_id: tx.txid ?? "",
|
|
476
|
-
block_height: height,
|
|
477
|
-
tx_index: tx.tx_index ?? 0,
|
|
478
|
-
type: tx.tx_type ?? "",
|
|
479
|
-
sender: tx.sender_address ?? "",
|
|
480
|
-
status: tx.status ?? "success",
|
|
481
|
-
contract_id: contractId,
|
|
482
|
-
function_name: functionName,
|
|
483
|
-
function_args: [],
|
|
484
|
-
raw_result: tx.raw_result ?? null,
|
|
485
|
-
raw_tx: tx.raw_tx ?? "",
|
|
486
|
-
created_at: new Date(0)
|
|
487
|
-
};
|
|
488
|
-
});
|
|
489
|
-
const events = (p.events ?? []).map((event) => {
|
|
490
|
-
const txId = event.txid ?? "";
|
|
491
|
-
const eventIndex = event.event_index ?? 0;
|
|
492
|
-
return {
|
|
493
|
-
id: `${txId}#${eventIndex}`,
|
|
494
|
-
tx_id: txId,
|
|
495
|
-
block_height: height,
|
|
496
|
-
event_index: eventIndex,
|
|
497
|
-
type: event.type ?? "",
|
|
498
|
-
data: eventData(event),
|
|
499
|
-
created_at: new Date(0)
|
|
500
|
-
};
|
|
501
|
-
});
|
|
502
|
-
const vmEvents = [];
|
|
503
|
-
for (const [i, trace] of (p.vm_events ?? []).entries()) {
|
|
504
|
-
const nodeType = trace.type;
|
|
505
|
-
if (typeof nodeType !== "string" || !(nodeType in VM_NODE_TO_STORED_TYPE))
|
|
506
|
-
continue;
|
|
507
|
-
const stored = VM_NODE_TO_STORED_TYPE[nodeType];
|
|
508
|
-
const body = trace[nodeType];
|
|
509
|
-
if (!body || typeof body !== "object")
|
|
510
|
-
continue;
|
|
511
|
-
const txId = trace.txid ?? "";
|
|
512
|
-
const vmIndex = i;
|
|
513
|
-
vmEvents.push({
|
|
514
|
-
id: vmEventId(txId, vmIndex),
|
|
515
|
-
tx_id: txId,
|
|
516
|
-
block_height: height,
|
|
517
|
-
event_index: vmIndex,
|
|
518
|
-
type: stored,
|
|
519
|
-
data: body,
|
|
520
|
-
created_at: new Date(0),
|
|
521
|
-
clock: "vm"
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
return { block, txs, events, vmEvents };
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
class ObserverHttpBlockSource {
|
|
528
|
-
baseUrl;
|
|
529
|
-
token;
|
|
530
|
-
fetchFn;
|
|
531
|
-
constructor(opts) {
|
|
532
|
-
this.baseUrl = stripTrailingSlash(opts.baseUrl);
|
|
533
|
-
this.token = opts.token ?? null;
|
|
534
|
-
this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
535
|
-
}
|
|
536
|
-
headers() {
|
|
537
|
-
if (!this.token)
|
|
538
|
-
return {};
|
|
539
|
-
return { Authorization: `Bearer ${this.token}` };
|
|
540
|
-
}
|
|
541
|
-
async getTip() {
|
|
542
|
-
const res = await this.fetchFn(`${this.baseUrl}${OBSERVER_HTTP_EXPORT_PATH}/tip`, { headers: this.headers() });
|
|
543
|
-
if (!res.ok) {
|
|
544
|
-
throw new Error(`observer tip HTTP ${res.status}: ${await res.text().catch(() => "")}`);
|
|
545
|
-
}
|
|
546
|
-
let body;
|
|
547
|
-
try {
|
|
548
|
-
body = await res.json();
|
|
549
|
-
} catch (err) {
|
|
550
|
-
throw new Error(`observer tip invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
551
|
-
}
|
|
552
|
-
if (typeof body.block_height !== "number" || !Number.isFinite(body.block_height)) {
|
|
553
|
-
throw new Error("observer tip missing block_height");
|
|
554
|
-
}
|
|
555
|
-
return body.block_height;
|
|
556
|
-
}
|
|
557
|
-
async loadBlockRange(fromHeight, toHeight) {
|
|
558
|
-
const result = new Map;
|
|
559
|
-
let afterHeight = fromHeight > 0 ? fromHeight - 1 : undefined;
|
|
560
|
-
let afterIndexBlockHash;
|
|
561
|
-
for (;; ) {
|
|
562
|
-
const url = new URL(`${this.baseUrl}${OBSERVER_HTTP_EXPORT_PATH}`);
|
|
563
|
-
url.searchParams.set("path", "/new_block");
|
|
564
|
-
url.searchParams.set("limit", "100");
|
|
565
|
-
if (afterHeight != null) {
|
|
566
|
-
url.searchParams.set("after_height", String(afterHeight));
|
|
567
|
-
}
|
|
568
|
-
if (afterIndexBlockHash) {
|
|
569
|
-
url.searchParams.set("after_index_block_hash", afterIndexBlockHash);
|
|
570
|
-
}
|
|
571
|
-
const res = await this.fetchFn(url.toString(), {
|
|
572
|
-
headers: this.headers()
|
|
573
|
-
});
|
|
574
|
-
if (!res.ok) {
|
|
575
|
-
throw new Error(`observer events HTTP ${res.status}: ${await res.text().catch(() => "")}`);
|
|
576
|
-
}
|
|
577
|
-
let body;
|
|
578
|
-
try {
|
|
579
|
-
body = await res.json();
|
|
580
|
-
} catch (err) {
|
|
581
|
-
throw new Error(`observer events invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
582
|
-
}
|
|
583
|
-
let pastTo = false;
|
|
584
|
-
for (const message of body.events ?? []) {
|
|
585
|
-
if (message.path === "/new_burn_block")
|
|
586
|
-
continue;
|
|
587
|
-
const height = message.payload?.block_height;
|
|
588
|
-
if (typeof height === "number" && height > toHeight) {
|
|
589
|
-
pastTo = true;
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
const data = mapNewBlockPayloadToBlockData(message.payload);
|
|
593
|
-
const h = data.block.height;
|
|
594
|
-
if (h >= fromHeight && h <= toHeight) {
|
|
595
|
-
result.set(h, data);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
if (pastTo || body.next == null)
|
|
599
|
-
break;
|
|
600
|
-
afterHeight = body.next.after_height;
|
|
601
|
-
afterIndexBlockHash = body.next.after_index_block_hash;
|
|
602
|
-
}
|
|
603
|
-
return result;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
88
|
|
|
607
89
|
// src/runtime/reconstruct.ts
|
|
608
90
|
function isoToUnixSeconds(iso) {
|
|
@@ -816,9 +298,6 @@ function sourceFilters(subgraph) {
|
|
|
816
298
|
const sources = subgraph.sources;
|
|
817
299
|
return Array.isArray(sources) ? sources : Object.values(sources);
|
|
818
300
|
}
|
|
819
|
-
function needsTransactionData(subgraph) {
|
|
820
|
-
return sourceFilters(subgraph).some((f) => TX_SOURCE_TYPES.has(f.type));
|
|
821
|
-
}
|
|
822
301
|
function synthesizeTxsFromEvents(events) {
|
|
823
302
|
const byId = new Map;
|
|
824
303
|
for (const e of events) {
|
|
@@ -840,25 +319,6 @@ function indexEventTypesForFilterTypes(filterTypes) {
|
|
|
840
319
|
}
|
|
841
320
|
return [...types];
|
|
842
321
|
}
|
|
843
|
-
function referencedIndexEventTypes(subgraph) {
|
|
844
|
-
if (!subgraph.sources)
|
|
845
|
-
return [];
|
|
846
|
-
return indexEventTypesForFilterTypes(sourceFilters(subgraph).map((f) => f.type));
|
|
847
|
-
}
|
|
848
|
-
function isStreamsIndexEligible(subgraph) {
|
|
849
|
-
if (Array.isArray(subgraph.sources))
|
|
850
|
-
return false;
|
|
851
|
-
const filters = sourceFilters(subgraph);
|
|
852
|
-
if (filters.length === 0)
|
|
853
|
-
return false;
|
|
854
|
-
for (const f of filters) {
|
|
855
|
-
const known = EVENT_FILTER_TO_INDEX_TYPE[f.type] || TX_SOURCE_TYPES.has(f.type);
|
|
856
|
-
if (!known)
|
|
857
|
-
return false;
|
|
858
|
-
}
|
|
859
|
-
return true;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
322
|
class PublicApiBlockSource {
|
|
863
323
|
http;
|
|
864
324
|
eventTypes;
|
|
@@ -915,142 +375,341 @@ class PublicApiBlockSource {
|
|
|
915
375
|
}
|
|
916
376
|
}
|
|
917
377
|
}
|
|
918
|
-
for (const bd of map.values()) {
|
|
919
|
-
bd.txs.sort((a, b) => a.tx_index - b.tx_index);
|
|
920
|
-
bd.events.sort((a, b) => a.event_index - b.event_index);
|
|
921
|
-
bd.vmEvents?.sort((a, b) => a.event_index - b.event_index);
|
|
378
|
+
for (const bd of map.values()) {
|
|
379
|
+
bd.txs.sort((a, b) => a.tx_index - b.tx_index);
|
|
380
|
+
bd.events.sort((a, b) => a.event_index - b.event_index);
|
|
381
|
+
bd.vmEvents?.sort((a, b) => a.event_index - b.event_index);
|
|
382
|
+
}
|
|
383
|
+
return map;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
var postgresBlockSource = new PostgresBlockSource;
|
|
387
|
+
function buildHttpClient() {
|
|
388
|
+
return createInternalIndexHttpClient();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/runtime/trigger-evaluator.ts
|
|
392
|
+
import { getSourceDb as getSourceDb3 } from "@secondlayer/shared/db";
|
|
393
|
+
import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
|
|
394
|
+
|
|
395
|
+
// src/runtime/decoder-bound.ts
|
|
396
|
+
import { decodeStreamsCursor, isEmptyRangeCursor } from "@secondlayer/shared";
|
|
397
|
+
import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
398
|
+
|
|
399
|
+
// src/runtime/source-matcher.ts
|
|
400
|
+
var patternCache = new Map;
|
|
401
|
+
function matchAny(value, pattern) {
|
|
402
|
+
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
403
|
+
}
|
|
404
|
+
function matchPattern(value, pattern) {
|
|
405
|
+
if (!pattern.includes("*"))
|
|
406
|
+
return value === pattern;
|
|
407
|
+
let re = patternCache.get(pattern);
|
|
408
|
+
if (!re) {
|
|
409
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
410
|
+
re = new RegExp(`^${regex}$`);
|
|
411
|
+
patternCache.set(pattern, re);
|
|
412
|
+
}
|
|
413
|
+
return re.test(value);
|
|
414
|
+
}
|
|
415
|
+
var EMPTY_SET = new Set;
|
|
416
|
+
function factoryAllows(filter, contractId, factoryContracts) {
|
|
417
|
+
if (!filter.factory)
|
|
418
|
+
return true;
|
|
419
|
+
if (!contractId)
|
|
420
|
+
return false;
|
|
421
|
+
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
422
|
+
}
|
|
423
|
+
function traitAllows(filter, contractId, traitContracts) {
|
|
424
|
+
const trait = filter.trait;
|
|
425
|
+
if (!trait)
|
|
426
|
+
return true;
|
|
427
|
+
if (!contractId)
|
|
428
|
+
return false;
|
|
429
|
+
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
430
|
+
}
|
|
431
|
+
function assetContract(assetId) {
|
|
432
|
+
return assetId?.split("::")[0];
|
|
433
|
+
}
|
|
434
|
+
function printContractId(data) {
|
|
435
|
+
if (!data)
|
|
436
|
+
return;
|
|
437
|
+
const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
|
|
438
|
+
return id || undefined;
|
|
439
|
+
}
|
|
440
|
+
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map, vmEventsByTx = new Map) {
|
|
441
|
+
const results = [];
|
|
442
|
+
switch (filter.type) {
|
|
443
|
+
case "stx_transfer":
|
|
444
|
+
case "stx_mint":
|
|
445
|
+
case "stx_burn":
|
|
446
|
+
case "stx_lock": {
|
|
447
|
+
const eventType = `${filter.type}_event`;
|
|
448
|
+
for (const tx of transactions) {
|
|
449
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
450
|
+
const matched = txEvents.filter((e) => e.type === eventType);
|
|
451
|
+
if (matched.length === 0)
|
|
452
|
+
continue;
|
|
453
|
+
const filtered = matched.filter((e) => {
|
|
454
|
+
const data = e.data;
|
|
455
|
+
if (!data)
|
|
456
|
+
return false;
|
|
457
|
+
if ("sender" in filter && filter.sender) {
|
|
458
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
if ("recipient" in filter && filter.recipient) {
|
|
462
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
466
|
+
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
470
|
+
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
471
|
+
if (amount < filter.minAmount)
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
475
|
+
const amount = BigInt(data.amount ?? "0");
|
|
476
|
+
if (amount > filter.maxAmount)
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
return true;
|
|
480
|
+
});
|
|
481
|
+
if (filtered.length > 0) {
|
|
482
|
+
results.push({ tx, events: filtered });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
case "ft_transfer":
|
|
488
|
+
case "ft_mint":
|
|
489
|
+
case "ft_burn": {
|
|
490
|
+
const eventType = `${filter.type}_event`;
|
|
491
|
+
for (const tx of transactions) {
|
|
492
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
493
|
+
const matched = txEvents.filter((e) => {
|
|
494
|
+
if (e.type !== eventType)
|
|
495
|
+
return false;
|
|
496
|
+
const data = e.data;
|
|
497
|
+
if (!data)
|
|
498
|
+
return false;
|
|
499
|
+
if (filter.assetIdentifier) {
|
|
500
|
+
const assetId = data.asset_identifier;
|
|
501
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
505
|
+
return false;
|
|
506
|
+
if ("sender" in filter && filter.sender) {
|
|
507
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
if ("recipient" in filter && filter.recipient) {
|
|
511
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
if (filter.minAmount !== undefined) {
|
|
515
|
+
const amount = BigInt(data.amount ?? "0");
|
|
516
|
+
if (amount < filter.minAmount)
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
return true;
|
|
520
|
+
});
|
|
521
|
+
if (matched.length > 0) {
|
|
522
|
+
results.push({ tx, events: matched });
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
case "nft_transfer":
|
|
528
|
+
case "nft_mint":
|
|
529
|
+
case "nft_burn": {
|
|
530
|
+
const eventType = `${filter.type}_event`;
|
|
531
|
+
for (const tx of transactions) {
|
|
532
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
533
|
+
const matched = txEvents.filter((e) => {
|
|
534
|
+
if (e.type !== eventType)
|
|
535
|
+
return false;
|
|
536
|
+
const data = e.data;
|
|
537
|
+
if (!data)
|
|
538
|
+
return false;
|
|
539
|
+
if (filter.assetIdentifier) {
|
|
540
|
+
const assetId = data.asset_identifier;
|
|
541
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
545
|
+
return false;
|
|
546
|
+
if ("sender" in filter && filter.sender) {
|
|
547
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
if ("recipient" in filter && filter.recipient) {
|
|
551
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
return true;
|
|
555
|
+
});
|
|
556
|
+
if (matched.length > 0) {
|
|
557
|
+
results.push({ tx, events: matched });
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
922
561
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
562
|
+
case "contract_call": {
|
|
563
|
+
for (const tx of transactions) {
|
|
564
|
+
if (tx.type !== "contract_call")
|
|
565
|
+
continue;
|
|
566
|
+
if (filter.contractId) {
|
|
567
|
+
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
571
|
+
continue;
|
|
572
|
+
if (filter.functionName) {
|
|
573
|
+
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (filter.caller) {
|
|
577
|
+
if (!matchPattern(tx.sender, filter.caller))
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
581
|
+
continue;
|
|
582
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
583
|
+
results.push({ tx, events: txEvents });
|
|
584
|
+
}
|
|
585
|
+
break;
|
|
942
586
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
587
|
+
case "contract_deploy": {
|
|
588
|
+
for (const tx of transactions) {
|
|
589
|
+
if (tx.type !== "smart_contract")
|
|
590
|
+
continue;
|
|
591
|
+
if (filter.deployer) {
|
|
592
|
+
if (!matchPattern(tx.sender, filter.deployer))
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (filter.contractName) {
|
|
596
|
+
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
597
|
+
if (!matchPattern(name, filter.contractName))
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
601
|
+
results.push({ tx, events: txEvents });
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
954
604
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
605
|
+
case "nested_contract_call":
|
|
606
|
+
case "var_set":
|
|
607
|
+
case "map_set":
|
|
608
|
+
case "map_insert":
|
|
609
|
+
case "map_delete": {
|
|
610
|
+
for (const tx of transactions) {
|
|
611
|
+
const txEvents = vmEventsByTx.get(tx.tx_id) ?? [];
|
|
612
|
+
const matched = txEvents.filter((e) => {
|
|
613
|
+
if (e.type !== filter.type)
|
|
614
|
+
return false;
|
|
615
|
+
const data = e.data;
|
|
616
|
+
if (!data)
|
|
617
|
+
return false;
|
|
618
|
+
const contractId = printContractId(data);
|
|
619
|
+
if ("contractId" in filter && filter.contractId) {
|
|
620
|
+
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
if (!traitAllows(filter, contractId, traitContracts))
|
|
624
|
+
return false;
|
|
625
|
+
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
626
|
+
return false;
|
|
627
|
+
if (filter.type === "nested_contract_call") {
|
|
628
|
+
if (filter.functionName) {
|
|
629
|
+
if (typeof data.function_name !== "string" || !matchPattern(data.function_name, filter.functionName))
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
if (filter.caller) {
|
|
633
|
+
if (typeof data.caller !== "string" || !matchPattern(data.caller, filter.caller))
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
if (filter.sender) {
|
|
637
|
+
if (typeof data.sender !== "string" || !matchPattern(data.sender, filter.sender))
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (filter.type === "var_set" && filter.varName) {
|
|
642
|
+
if (typeof data.var_name !== "string" || !matchPattern(data.var_name, filter.varName))
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
if ((filter.type === "map_set" || filter.type === "map_insert" || filter.type === "map_delete") && filter.map) {
|
|
646
|
+
if (typeof data.map_name !== "string" || !matchPattern(data.map_name, filter.map))
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
return true;
|
|
650
|
+
});
|
|
651
|
+
if (matched.length > 0)
|
|
652
|
+
results.push({ tx, events: matched });
|
|
653
|
+
}
|
|
654
|
+
break;
|
|
963
655
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
656
|
+
case "print_event": {
|
|
657
|
+
for (const tx of transactions) {
|
|
658
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
659
|
+
const matched = txEvents.filter((e) => {
|
|
660
|
+
if (e.type !== "smart_contract_event" && e.type !== "contract_event")
|
|
661
|
+
return false;
|
|
662
|
+
const data = e.data;
|
|
663
|
+
if (!data)
|
|
664
|
+
return false;
|
|
665
|
+
if (data.topic !== "print")
|
|
666
|
+
return false;
|
|
667
|
+
const contractId = printContractId(data);
|
|
668
|
+
if (filter.contractId) {
|
|
669
|
+
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
if (!traitAllows(filter, contractId, traitContracts))
|
|
673
|
+
return false;
|
|
674
|
+
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
675
|
+
return false;
|
|
676
|
+
return true;
|
|
677
|
+
});
|
|
678
|
+
if (matched.length > 0) {
|
|
679
|
+
results.push({ tx, events: matched });
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
break;
|
|
975
683
|
}
|
|
976
|
-
return new ObserverHttpBlockSource({
|
|
977
|
-
baseUrl,
|
|
978
|
-
token: process.env.OBSERVER_HTTP_EXPORT_TOKEN || null
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph && isStreamsIndexEligible(subgraph)) {
|
|
982
|
-
return new FallbackBlockSource(new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph), canSparseScan(subgraph) ? sparseProbeTargets(subgraph) : undefined, needsTransactionData(subgraph)), postgresBlockSource);
|
|
983
|
-
}
|
|
984
|
-
if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
|
|
985
|
-
logger.debug("Subgraph not streams-index eligible, using DB tap", {
|
|
986
|
-
subgraph: subgraph.name
|
|
987
|
-
});
|
|
988
684
|
}
|
|
989
|
-
return
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// src/runtime/trigger-evaluator.ts
|
|
993
|
-
import { getSourceDb as getSourceDb3 } from "@secondlayer/shared/db";
|
|
994
|
-
import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
|
|
995
|
-
|
|
996
|
-
// src/runtime/decoder-bound.ts
|
|
997
|
-
import { decodeStreamsCursor, isEmptyRangeCursor } from "@secondlayer/shared";
|
|
998
|
-
import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
999
|
-
function decoderNameForEventType(indexEventType) {
|
|
1000
|
-
return `decode.${indexEventType}.v1`;
|
|
1001
|
-
}
|
|
1002
|
-
function decoderNamesForIndexEventTypes(eventTypes) {
|
|
1003
|
-
return eventTypes.filter((t) => !VM_INDEX_EVENT_TYPES.has(t)).map(decoderNameForEventType);
|
|
1004
|
-
}
|
|
1005
|
-
function decoderNamesForSubgraph(subgraph) {
|
|
1006
|
-
return decoderNamesForIndexEventTypes(referencedIndexEventTypes(subgraph));
|
|
1007
|
-
}
|
|
1008
|
-
function usesDecodedIndexPlane(subgraph) {
|
|
1009
|
-
return process.env.SUBGRAPH_SOURCE === "streams-index" && isStreamsIndexEligible(subgraph);
|
|
685
|
+
return results;
|
|
1010
686
|
}
|
|
1011
|
-
function
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
return decoded.block_height;
|
|
1018
|
-
return Math.max(0, decoded.block_height - 1);
|
|
1019
|
-
} catch {
|
|
1020
|
-
return null;
|
|
687
|
+
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map, vmEvents = []) {
|
|
688
|
+
const eventsByTx = new Map;
|
|
689
|
+
for (const event of events) {
|
|
690
|
+
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
691
|
+
list.push(event);
|
|
692
|
+
eventsByTx.set(event.tx_id, list);
|
|
1021
693
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
const rows = await sourceDb.selectFrom("decoder_checkpoints").select(["decoder_name", "last_cursor"]).where("decoder_name", "in", decoderNames).execute();
|
|
1028
|
-
const byName = new Map(rows.map((r) => [r.decoder_name, r.last_cursor]));
|
|
1029
|
-
const missing = [];
|
|
1030
|
-
const heights = [];
|
|
1031
|
-
for (const name of decoderNames) {
|
|
1032
|
-
const height = committedHeight(byName.get(name));
|
|
1033
|
-
if (height === null)
|
|
1034
|
-
missing.push(name);
|
|
1035
|
-
else
|
|
1036
|
-
heights.push(height);
|
|
694
|
+
const vmEventsByTx = new Map;
|
|
695
|
+
for (const event of vmEvents) {
|
|
696
|
+
const list = vmEventsByTx.get(event.tx_id) ?? [];
|
|
697
|
+
list.push(event);
|
|
698
|
+
vmEventsByTx.set(event.tx_id, list);
|
|
1037
699
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
700
|
+
const seen = new Set;
|
|
701
|
+
const results = [];
|
|
702
|
+
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
703
|
+
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts, vmEventsByTx);
|
|
704
|
+
for (const match of matches) {
|
|
705
|
+
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
706
|
+
if (!seen.has(dedupeKey)) {
|
|
707
|
+
seen.add(dedupeKey);
|
|
708
|
+
results.push({ ...match, sourceName });
|
|
709
|
+
}
|
|
710
|
+
}
|
|
1048
711
|
}
|
|
1049
|
-
return
|
|
1050
|
-
ok: true,
|
|
1051
|
-
tip: Math.min(rawTip, bound.height),
|
|
1052
|
-
floor: bound.height
|
|
1053
|
-
};
|
|
712
|
+
return results;
|
|
1054
713
|
}
|
|
1055
714
|
|
|
1056
715
|
// src/runtime/trigger-evaluator.ts
|
|
@@ -1347,6 +1006,31 @@ async function emitSbtcOutbox(db, chainSubs, blockHeight, blockHash, opts) {
|
|
|
1347
1006
|
|
|
1348
1007
|
// src/runtime/replay.ts
|
|
1349
1008
|
var BATCH_SIZE = 500;
|
|
1009
|
+
var WEBHOOK_REPLAY_MAX_BLOCKS_DEFAULT = 1e5;
|
|
1010
|
+
function webhookReplayMaxBlocks() {
|
|
1011
|
+
const raw = process.env.WEBHOOK_REPLAY_MAX_BLOCKS;
|
|
1012
|
+
if (raw === undefined)
|
|
1013
|
+
return WEBHOOK_REPLAY_MAX_BLOCKS_DEFAULT;
|
|
1014
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1015
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : WEBHOOK_REPLAY_MAX_BLOCKS_DEFAULT;
|
|
1016
|
+
}
|
|
1017
|
+
function formatBlockCount(n) {
|
|
1018
|
+
return n % 1000 === 0 ? `${n / 1000}k` : `${n}`;
|
|
1019
|
+
}
|
|
1020
|
+
var inFlightReplays = new Set;
|
|
1021
|
+
|
|
1022
|
+
class ReplayInProgressError extends Error {
|
|
1023
|
+
constructor() {
|
|
1024
|
+
super("replay already in progress for this webhook");
|
|
1025
|
+
this.name = "ReplayInProgressError";
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
function __setReplayInFlightForTest(webhookId, inFlight) {
|
|
1029
|
+
if (inFlight)
|
|
1030
|
+
inFlightReplays.add(webhookId);
|
|
1031
|
+
else
|
|
1032
|
+
inFlightReplays.delete(webhookId);
|
|
1033
|
+
}
|
|
1350
1034
|
function replayDedupKey(subgraphName, tableName, row, replayId) {
|
|
1351
1035
|
const canonical = `replay:${replayId}:${subgraphName}:${tableName}:${stableStringify(row)}`;
|
|
1352
1036
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 32);
|
|
@@ -1373,67 +1057,76 @@ async function replayWebhook(input) {
|
|
|
1373
1057
|
if (input.fromBlock > input.toBlock) {
|
|
1374
1058
|
throw new Error("fromBlock must be <= toBlock");
|
|
1375
1059
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1060
|
+
const maxBlocks = webhookReplayMaxBlocks();
|
|
1061
|
+
if (input.toBlock - input.fromBlock > maxBlocks) {
|
|
1062
|
+
throw new Error(`replay range exceeds ${formatBlockCount(maxBlocks)} blocks`);
|
|
1378
1063
|
}
|
|
1379
1064
|
const db = getTargetDb();
|
|
1380
1065
|
const sub = await getWebhook(db, input.accountId, input.webhookId);
|
|
1381
1066
|
if (!sub)
|
|
1382
1067
|
throw new Error("Webhook not found");
|
|
1383
|
-
if (sub.
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1386
|
-
const subgraphName = sub.subgraph_name;
|
|
1387
|
-
const tableName = sub.table_name;
|
|
1388
|
-
if (sub.kind !== "subgraph" || !subgraphName || !tableName) {
|
|
1389
|
-
throw new Error("replay is only supported for subgraph or chain webhooks");
|
|
1068
|
+
if (inFlightReplays.has(sub.id)) {
|
|
1069
|
+
throw new ReplayInProgressError;
|
|
1390
1070
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
const
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1071
|
+
inFlightReplays.add(sub.id);
|
|
1072
|
+
try {
|
|
1073
|
+
if (sub.kind === "chain") {
|
|
1074
|
+
return await replayChainWebhook(db, sub, input);
|
|
1075
|
+
}
|
|
1076
|
+
const subgraphName = sub.subgraph_name;
|
|
1077
|
+
const tableName = sub.table_name;
|
|
1078
|
+
if (sub.kind !== "subgraph" || !subgraphName || !tableName) {
|
|
1079
|
+
throw new Error("replay is only supported for subgraph or chain webhooks");
|
|
1080
|
+
}
|
|
1081
|
+
const schema = await resolveSchemaName(db, subgraphName);
|
|
1082
|
+
const replayId = deterministicReplayId(sub.id, input.fromBlock, input.toBlock, input.replayIdSuffix);
|
|
1083
|
+
let scanned = 0;
|
|
1084
|
+
let enqueued = 0;
|
|
1085
|
+
let lastId = 0n;
|
|
1086
|
+
while (true) {
|
|
1087
|
+
const { rows } = await sql`SELECT * FROM ${sql.raw(`"${schema}"."${tableName}"`)}
|
|
1088
|
+
WHERE _block_height >= ${sql.lit(input.fromBlock)}
|
|
1089
|
+
AND _block_height <= ${sql.lit(input.toBlock)}
|
|
1090
|
+
AND _id > ${sql.lit(lastId)}
|
|
1091
|
+
ORDER BY _id ASC
|
|
1092
|
+
LIMIT ${sql.lit(BATCH_SIZE)}`.execute(db);
|
|
1093
|
+
if (rows.length === 0)
|
|
1094
|
+
break;
|
|
1095
|
+
scanned += rows.length;
|
|
1096
|
+
const inserts = rows.map((row) => ({
|
|
1097
|
+
webhook_id: sub.id,
|
|
1098
|
+
subgraph_name: subgraphName,
|
|
1099
|
+
table_name: tableName,
|
|
1100
|
+
block_height: Number(row._block_height),
|
|
1101
|
+
tx_id: row._tx_id ?? null,
|
|
1102
|
+
row_pk: {
|
|
1103
|
+
blockHeight: Number(row._block_height),
|
|
1104
|
+
txId: row._tx_id ?? "",
|
|
1105
|
+
replayId
|
|
1106
|
+
},
|
|
1107
|
+
event_type: `${subgraphName}.${tableName}.replay`,
|
|
1108
|
+
payload: row,
|
|
1109
|
+
dedup_key: replayDedupKey(subgraphName, tableName, row, replayId),
|
|
1110
|
+
is_replay: true
|
|
1111
|
+
}));
|
|
1112
|
+
const result = await db.insertInto("webhook_outbox").values(inserts).onConflict((oc) => oc.columns(["webhook_id", "dedup_key"]).doNothing()).executeTakeFirst();
|
|
1113
|
+
enqueued += Number(result.numInsertedOrUpdatedRows ?? 0);
|
|
1114
|
+
lastId = BigInt(String(rows[rows.length - 1]?._id));
|
|
1115
|
+
if (rows.length < BATCH_SIZE)
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
logger2.info("Replay enqueued", {
|
|
1119
|
+
webhook: sub.name,
|
|
1120
|
+
replayId,
|
|
1121
|
+
scanned,
|
|
1122
|
+
enqueued,
|
|
1123
|
+
fromBlock: input.fromBlock,
|
|
1124
|
+
toBlock: input.toBlock
|
|
1125
|
+
});
|
|
1126
|
+
return { replayId, enqueuedCount: enqueued, scannedCount: scanned };
|
|
1127
|
+
} finally {
|
|
1128
|
+
inFlightReplays.delete(sub.id);
|
|
1427
1129
|
}
|
|
1428
|
-
logger2.info("Replay enqueued", {
|
|
1429
|
-
webhook: sub.name,
|
|
1430
|
-
replayId,
|
|
1431
|
-
scanned,
|
|
1432
|
-
enqueued,
|
|
1433
|
-
fromBlock: input.fromBlock,
|
|
1434
|
-
toBlock: input.toBlock
|
|
1435
|
-
});
|
|
1436
|
-
return { replayId, enqueuedCount: enqueued, scannedCount: scanned };
|
|
1437
1130
|
}
|
|
1438
1131
|
var CHAIN_REPLAY_BATCH = 200;
|
|
1439
1132
|
async function replayChainWebhook(db, sub, input, opts) {
|
|
@@ -1473,9 +1166,11 @@ async function replayChainWebhook(db, sub, input, opts) {
|
|
|
1473
1166
|
return { replayId, enqueuedCount: enqueued, scannedCount: scanned };
|
|
1474
1167
|
}
|
|
1475
1168
|
export {
|
|
1476
|
-
|
|
1477
|
-
|
|
1169
|
+
ReplayInProgressError,
|
|
1170
|
+
__setReplayInFlightForTest,
|
|
1171
|
+
replayChainWebhook,
|
|
1172
|
+
replayWebhook
|
|
1478
1173
|
};
|
|
1479
1174
|
|
|
1480
|
-
//# debugId=
|
|
1175
|
+
//# debugId=8038149B4D7A86CB64756E2164756E21
|
|
1481
1176
|
//# sourceMappingURL=replay.js.map
|