@secondlayer/subgraphs 0.8.1 → 0.9.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/dist/src/index.d.ts +113 -32
- package/dist/src/index.js +486 -99
- package/dist/src/index.js.map +9 -10
- package/dist/src/runtime/block-processor.d.ts +110 -20
- package/dist/src/runtime/block-processor.js +452 -90
- package/dist/src/runtime/block-processor.js.map +7 -8
- package/dist/src/runtime/catchup.d.ts +110 -20
- package/dist/src/runtime/catchup.js +457 -94
- package/dist/src/runtime/catchup.js.map +8 -9
- package/dist/src/runtime/clarity.js +3 -3
- package/dist/src/runtime/clarity.js.map +3 -3
- package/dist/src/runtime/context.d.ts +23 -5
- package/dist/src/runtime/context.js +57 -1
- package/dist/src/runtime/context.js.map +3 -3
- package/dist/src/runtime/processor.js +457 -94
- package/dist/src/runtime/processor.js.map +8 -9
- package/dist/src/runtime/reindex.d.ts +110 -20
- package/dist/src/runtime/reindex.js +452 -90
- package/dist/src/runtime/reindex.js.map +7 -8
- package/dist/src/runtime/reorg.d.ts +110 -20
- package/dist/src/runtime/reorg.js +452 -90
- package/dist/src/runtime/reorg.js.map +7 -8
- package/dist/src/runtime/runner.d.ts +152 -42
- package/dist/src/runtime/runner.js +226 -30
- package/dist/src/runtime/runner.js.map +4 -4
- package/dist/src/runtime/source-matcher.d.ts +88 -31
- package/dist/src/runtime/source-matcher.js +171 -61
- package/dist/src/runtime/source-matcher.js.map +4 -5
- package/dist/src/schema/index.d.ts +110 -20
- package/dist/src/schema/index.js +34 -9
- package/dist/src/schema/index.js.map +3 -3
- package/dist/src/service.js +457 -94
- package/dist/src/service.js.map +8 -9
- package/dist/src/templates.js +52 -51
- package/dist/src/templates.js.map +3 -3
- package/dist/src/types.d.ts +111 -29
- package/dist/src/types.js +1 -20
- package/dist/src/types.js.map +3 -4
- package/dist/src/validate.d.ts +112 -22
- package/dist/src/validate.js +35 -10
- package/dist/src/validate.js.map +3 -3
- package/package.json +2 -2
package/dist/src/index.js
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
3
|
|
|
4
|
-
// src/types.ts
|
|
5
|
-
function sourceKey(source) {
|
|
6
|
-
if (source.contract) {
|
|
7
|
-
if (source.function)
|
|
8
|
-
return `${source.contract}::${source.function}`;
|
|
9
|
-
if (source.event)
|
|
10
|
-
return `${source.contract}::${source.event}`;
|
|
11
|
-
return source.contract;
|
|
12
|
-
}
|
|
13
|
-
if (source.type)
|
|
14
|
-
return source.type;
|
|
15
|
-
return "*";
|
|
16
|
-
}
|
|
17
|
-
|
|
18
4
|
// src/validate.ts
|
|
19
5
|
import { z } from "zod/v4";
|
|
20
6
|
var SubgraphNameSchema = z.string().min(1).max(63).regex(/^[a-z][a-z0-9-]*$/, "Must start with lowercase letter, contain only lowercase alphanumeric and hyphens");
|
|
@@ -40,18 +26,43 @@ var SubgraphTableSchema = z.object({
|
|
|
40
26
|
uniqueKeys: z.array(z.array(z.string())).optional()
|
|
41
27
|
});
|
|
42
28
|
var SubgraphSchemaSchema = z.record(z.string(), SubgraphTableSchema).refine((s) => Object.keys(s).length > 0, "Schema must have at least one table");
|
|
43
|
-
var
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
var VALID_FILTER_TYPES = [
|
|
30
|
+
"stx_transfer",
|
|
31
|
+
"stx_mint",
|
|
32
|
+
"stx_burn",
|
|
33
|
+
"stx_lock",
|
|
34
|
+
"ft_transfer",
|
|
35
|
+
"ft_mint",
|
|
36
|
+
"ft_burn",
|
|
37
|
+
"nft_transfer",
|
|
38
|
+
"nft_mint",
|
|
39
|
+
"nft_burn",
|
|
40
|
+
"contract_call",
|
|
41
|
+
"contract_deploy",
|
|
42
|
+
"print_event"
|
|
43
|
+
];
|
|
44
|
+
var SubgraphFilterSchema = z.object({
|
|
45
|
+
type: z.enum(VALID_FILTER_TYPES),
|
|
46
|
+
sender: z.string().optional(),
|
|
47
|
+
recipient: z.string().optional(),
|
|
48
|
+
minAmount: z.bigint().optional(),
|
|
49
|
+
maxAmount: z.bigint().optional(),
|
|
50
|
+
assetIdentifier: z.string().optional(),
|
|
51
|
+
contractId: z.string().optional(),
|
|
52
|
+
functionName: z.string().optional(),
|
|
53
|
+
caller: z.string().optional(),
|
|
54
|
+
deployer: z.string().optional(),
|
|
55
|
+
contractName: z.string().optional(),
|
|
56
|
+
topic: z.string().optional(),
|
|
57
|
+
lockedAddress: z.string().optional(),
|
|
58
|
+
abi: z.record(z.string(), z.any()).optional()
|
|
59
|
+
}).passthrough();
|
|
50
60
|
var SubgraphDefinitionSchema = z.object({
|
|
51
61
|
name: SubgraphNameSchema,
|
|
52
62
|
version: z.string().optional(),
|
|
53
63
|
description: z.string().optional(),
|
|
54
|
-
|
|
64
|
+
startBlock: z.number().int().nonnegative().optional(),
|
|
65
|
+
sources: z.record(z.string(), SubgraphFilterSchema).refine((s) => Object.keys(s).length > 0, "Must have at least one source"),
|
|
55
66
|
schema: SubgraphSchemaSchema,
|
|
56
67
|
handlers: z.record(z.string(), z.any())
|
|
57
68
|
});
|
|
@@ -61,6 +72,7 @@ function validateSubgraphDefinition(def) {
|
|
|
61
72
|
|
|
62
73
|
// src/runtime/context.ts
|
|
63
74
|
import { logger } from "@secondlayer/shared/logger";
|
|
75
|
+
import { formatUnits } from "@secondlayer/stacks/utils";
|
|
64
76
|
import { sql } from "kysely";
|
|
65
77
|
function validateColumnName(name) {
|
|
66
78
|
if (!/^[a-z_][a-z0-9_]*$/i.test(name)) {
|
|
@@ -128,6 +140,20 @@ class SubgraphContext {
|
|
|
128
140
|
this.validateTable(table);
|
|
129
141
|
this.ops.push({ kind: "delete", table, data: where });
|
|
130
142
|
}
|
|
143
|
+
patch(table, where, set) {
|
|
144
|
+
this.update(table, where, set);
|
|
145
|
+
}
|
|
146
|
+
async patchOrInsert(table, key, row) {
|
|
147
|
+
const existing = await this.findOne(table, key);
|
|
148
|
+
const resolved = {};
|
|
149
|
+
for (const [k, v] of Object.entries(row)) {
|
|
150
|
+
resolved[k] = typeof v === "function" ? v(existing) : v;
|
|
151
|
+
}
|
|
152
|
+
this.upsert(table, key, resolved);
|
|
153
|
+
}
|
|
154
|
+
formatUnits(value, decimals) {
|
|
155
|
+
return formatUnits(value, decimals);
|
|
156
|
+
}
|
|
131
157
|
async findOne(table, where) {
|
|
132
158
|
this.validateTable(table);
|
|
133
159
|
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
@@ -145,6 +171,47 @@ class SubgraphContext {
|
|
|
145
171
|
const { rows } = await sql.raw(query).execute(this.db);
|
|
146
172
|
return rows.map((r) => this.coerceRow(table, r));
|
|
147
173
|
}
|
|
174
|
+
async count(table, where) {
|
|
175
|
+
this.validateTable(table);
|
|
176
|
+
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
177
|
+
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
178
|
+
const { rows } = await sql.raw(`SELECT COUNT(*)::int AS count FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
179
|
+
return Number(rows[0]?.count ?? 0);
|
|
180
|
+
}
|
|
181
|
+
async sum(table, column, where) {
|
|
182
|
+
this.validateTable(table);
|
|
183
|
+
validateColumnName(column);
|
|
184
|
+
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
185
|
+
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
186
|
+
const { rows } = await sql.raw(`SELECT COALESCE(SUM("${column}"), 0) AS total FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
187
|
+
return BigInt(rows[0]?.total?.toString() ?? "0");
|
|
188
|
+
}
|
|
189
|
+
async min(table, column, where) {
|
|
190
|
+
this.validateTable(table);
|
|
191
|
+
validateColumnName(column);
|
|
192
|
+
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
193
|
+
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
194
|
+
const { rows } = await sql.raw(`SELECT MIN("${column}") AS val FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
195
|
+
const val = rows[0]?.val;
|
|
196
|
+
return val != null ? BigInt(val.toString()) : null;
|
|
197
|
+
}
|
|
198
|
+
async max(table, column, where) {
|
|
199
|
+
this.validateTable(table);
|
|
200
|
+
validateColumnName(column);
|
|
201
|
+
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
202
|
+
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
203
|
+
const { rows } = await sql.raw(`SELECT MAX("${column}") AS val FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
204
|
+
const val = rows[0]?.val;
|
|
205
|
+
return val != null ? BigInt(val.toString()) : null;
|
|
206
|
+
}
|
|
207
|
+
async countDistinct(table, column, where) {
|
|
208
|
+
this.validateTable(table);
|
|
209
|
+
validateColumnName(column);
|
|
210
|
+
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
211
|
+
const whereClause = where ? `WHERE ${buildWhereClause(where).clause}` : "";
|
|
212
|
+
const { rows } = await sql.raw(`SELECT COUNT(DISTINCT "${column}")::int AS count FROM ${qualifiedTable} ${whereClause}`).execute(this.db);
|
|
213
|
+
return Number(rows[0]?.count ?? 0);
|
|
214
|
+
}
|
|
148
215
|
coerceRow(table, row) {
|
|
149
216
|
const tableDef = this.subgraphSchema[table];
|
|
150
217
|
if (!tableDef)
|
|
@@ -285,12 +352,12 @@ function buildWhereClause(where) {
|
|
|
285
352
|
}
|
|
286
353
|
|
|
287
354
|
// src/runtime/clarity.ts
|
|
288
|
-
import {
|
|
355
|
+
import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
|
|
289
356
|
function decodeClarityValue(hex) {
|
|
290
357
|
try {
|
|
291
358
|
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
292
359
|
const cv = deserializeCV(cleanHex);
|
|
293
|
-
return
|
|
360
|
+
return cvToValue(cv);
|
|
294
361
|
} catch {
|
|
295
362
|
return hex;
|
|
296
363
|
}
|
|
@@ -319,19 +386,210 @@ function decodeFunctionArgs(args) {
|
|
|
319
386
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
320
387
|
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
321
388
|
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
322
|
-
function
|
|
323
|
-
return
|
|
389
|
+
function camelCase(str) {
|
|
390
|
+
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
391
|
+
}
|
|
392
|
+
function camelizeKeys(obj) {
|
|
393
|
+
if (obj === null || obj === undefined)
|
|
394
|
+
return obj;
|
|
395
|
+
if (typeof obj !== "object")
|
|
396
|
+
return obj;
|
|
397
|
+
if (Array.isArray(obj))
|
|
398
|
+
return obj.map(camelizeKeys);
|
|
399
|
+
const result = {};
|
|
400
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
401
|
+
result[camelCase(k)] = camelizeKeys(v);
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
function decodeFunctionArgs2(args) {
|
|
406
|
+
if (!Array.isArray(args))
|
|
407
|
+
return [];
|
|
408
|
+
return args.map((arg) => {
|
|
409
|
+
if (typeof arg === "string")
|
|
410
|
+
return decodeClarityValue(arg);
|
|
411
|
+
return arg;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function decodeRawResult(raw) {
|
|
415
|
+
if (typeof raw === "string" && raw.length > 2) {
|
|
416
|
+
return decodeClarityValue(raw);
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
function safeBigInt(val) {
|
|
421
|
+
if (typeof val === "bigint")
|
|
422
|
+
return val;
|
|
423
|
+
if (typeof val === "number")
|
|
424
|
+
return BigInt(val);
|
|
425
|
+
if (typeof val === "string") {
|
|
426
|
+
try {
|
|
427
|
+
return BigInt(val);
|
|
428
|
+
} catch {
|
|
429
|
+
return 0n;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return 0n;
|
|
433
|
+
}
|
|
434
|
+
function buildEventPayload(filter, tx, event) {
|
|
435
|
+
const txMeta = {
|
|
436
|
+
txId: tx.tx_id,
|
|
437
|
+
sender: tx.sender,
|
|
438
|
+
type: tx.type,
|
|
439
|
+
status: tx.status,
|
|
440
|
+
contractId: tx.contract_id ?? null,
|
|
441
|
+
functionName: tx.function_name ?? null
|
|
442
|
+
};
|
|
443
|
+
const decodedArgs = decodeFunctionArgs2(tx.function_args);
|
|
444
|
+
const decodedResult = decodeRawResult(tx.raw_result);
|
|
445
|
+
if (!event) {
|
|
446
|
+
switch (filter.type) {
|
|
447
|
+
case "contract_call":
|
|
448
|
+
return {
|
|
449
|
+
contractId: tx.contract_id ?? "",
|
|
450
|
+
functionName: tx.function_name ?? "",
|
|
451
|
+
caller: tx.sender,
|
|
452
|
+
args: decodedArgs,
|
|
453
|
+
result: decodedResult,
|
|
454
|
+
tx: txMeta
|
|
455
|
+
};
|
|
456
|
+
case "contract_deploy":
|
|
457
|
+
return {
|
|
458
|
+
contractId: tx.contract_id ?? "",
|
|
459
|
+
deployer: tx.sender,
|
|
460
|
+
tx: txMeta
|
|
461
|
+
};
|
|
462
|
+
default:
|
|
463
|
+
return { tx: txMeta };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const decoded = decodeEventData(event.data);
|
|
467
|
+
switch (filter.type) {
|
|
468
|
+
case "ft_transfer":
|
|
469
|
+
return {
|
|
470
|
+
sender: decoded.sender,
|
|
471
|
+
recipient: decoded.recipient,
|
|
472
|
+
amount: safeBigInt(decoded.amount),
|
|
473
|
+
assetIdentifier: decoded.asset_identifier,
|
|
474
|
+
tx: txMeta
|
|
475
|
+
};
|
|
476
|
+
case "ft_mint":
|
|
477
|
+
return {
|
|
478
|
+
recipient: decoded.recipient,
|
|
479
|
+
amount: safeBigInt(decoded.amount),
|
|
480
|
+
assetIdentifier: decoded.asset_identifier,
|
|
481
|
+
tx: txMeta
|
|
482
|
+
};
|
|
483
|
+
case "ft_burn":
|
|
484
|
+
return {
|
|
485
|
+
sender: decoded.sender,
|
|
486
|
+
amount: safeBigInt(decoded.amount),
|
|
487
|
+
assetIdentifier: decoded.asset_identifier,
|
|
488
|
+
tx: txMeta
|
|
489
|
+
};
|
|
490
|
+
case "nft_transfer":
|
|
491
|
+
return {
|
|
492
|
+
sender: decoded.sender,
|
|
493
|
+
recipient: decoded.recipient,
|
|
494
|
+
tokenId: decoded.value,
|
|
495
|
+
assetIdentifier: decoded.asset_identifier,
|
|
496
|
+
tx: txMeta
|
|
497
|
+
};
|
|
498
|
+
case "nft_mint":
|
|
499
|
+
return {
|
|
500
|
+
recipient: decoded.recipient,
|
|
501
|
+
tokenId: decoded.value,
|
|
502
|
+
assetIdentifier: decoded.asset_identifier,
|
|
503
|
+
tx: txMeta
|
|
504
|
+
};
|
|
505
|
+
case "nft_burn":
|
|
506
|
+
return {
|
|
507
|
+
sender: decoded.sender,
|
|
508
|
+
tokenId: decoded.value,
|
|
509
|
+
assetIdentifier: decoded.asset_identifier,
|
|
510
|
+
tx: txMeta
|
|
511
|
+
};
|
|
512
|
+
case "stx_transfer":
|
|
513
|
+
return {
|
|
514
|
+
sender: decoded.sender,
|
|
515
|
+
recipient: decoded.recipient,
|
|
516
|
+
amount: safeBigInt(decoded.amount),
|
|
517
|
+
memo: decoded.memo ?? "",
|
|
518
|
+
tx: txMeta
|
|
519
|
+
};
|
|
520
|
+
case "stx_mint":
|
|
521
|
+
return {
|
|
522
|
+
recipient: decoded.recipient,
|
|
523
|
+
amount: safeBigInt(decoded.amount),
|
|
524
|
+
tx: txMeta
|
|
525
|
+
};
|
|
526
|
+
case "stx_burn":
|
|
527
|
+
return {
|
|
528
|
+
sender: decoded.sender,
|
|
529
|
+
amount: safeBigInt(decoded.amount),
|
|
530
|
+
tx: txMeta
|
|
531
|
+
};
|
|
532
|
+
case "stx_lock":
|
|
533
|
+
return {
|
|
534
|
+
lockedAddress: decoded.locked_address,
|
|
535
|
+
lockedAmount: safeBigInt(decoded.locked_amount),
|
|
536
|
+
unlockHeight: safeBigInt(decoded.unlock_height),
|
|
537
|
+
tx: txMeta
|
|
538
|
+
};
|
|
539
|
+
case "print_event": {
|
|
540
|
+
const rawValue = decoded.value;
|
|
541
|
+
const clarityObj = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue) ? rawValue : null;
|
|
542
|
+
const topic = clarityObj?.topic ? String(clarityObj.topic) : decoded.topic ?? "";
|
|
543
|
+
const { topic: _, ...rest } = clarityObj ?? {};
|
|
544
|
+
const data = Object.keys(rest).length > 0 ? camelizeKeys(rest) : rawValue && typeof rawValue !== "object" ? rawValue : {};
|
|
545
|
+
return {
|
|
546
|
+
contractId: decoded.contract_identifier ?? tx.contract_id ?? "",
|
|
547
|
+
topic,
|
|
548
|
+
data: data ?? {},
|
|
549
|
+
tx: txMeta
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
case "contract_call":
|
|
553
|
+
return {
|
|
554
|
+
...decoded,
|
|
555
|
+
_eventType: event.type,
|
|
556
|
+
contractId: tx.contract_id ?? "",
|
|
557
|
+
functionName: tx.function_name ?? "",
|
|
558
|
+
caller: tx.sender,
|
|
559
|
+
args: decodedArgs,
|
|
560
|
+
result: decodedResult,
|
|
561
|
+
tx: txMeta
|
|
562
|
+
};
|
|
563
|
+
case "contract_deploy":
|
|
564
|
+
return {
|
|
565
|
+
contractId: tx.contract_id ?? "",
|
|
566
|
+
deployer: tx.sender,
|
|
567
|
+
tx: txMeta
|
|
568
|
+
};
|
|
569
|
+
default:
|
|
570
|
+
return {
|
|
571
|
+
...decoded,
|
|
572
|
+
_eventType: event.type,
|
|
573
|
+
tx: txMeta
|
|
574
|
+
};
|
|
575
|
+
}
|
|
324
576
|
}
|
|
325
577
|
async function runHandlers(subgraph, matched, ctx, opts) {
|
|
326
578
|
let processed = 0;
|
|
327
579
|
let errors = 0;
|
|
328
580
|
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
329
|
-
|
|
330
|
-
|
|
581
|
+
const filterLookup = new Map;
|
|
582
|
+
if (!Array.isArray(subgraph.sources)) {
|
|
583
|
+
for (const [name, filter] of Object.entries(subgraph.sources)) {
|
|
584
|
+
filterLookup.set(name, filter);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
for (const { tx, events, sourceName } of matched) {
|
|
588
|
+
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
331
589
|
if (!handler) {
|
|
332
|
-
logger2.warn("No handler found for source
|
|
590
|
+
logger2.warn("No handler found for source", {
|
|
333
591
|
subgraph: subgraph.name,
|
|
334
|
-
|
|
592
|
+
sourceName,
|
|
335
593
|
txId: tx.tx_id
|
|
336
594
|
});
|
|
337
595
|
continue;
|
|
@@ -340,11 +598,14 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
340
598
|
txId: tx.tx_id,
|
|
341
599
|
sender: tx.sender,
|
|
342
600
|
type: tx.type,
|
|
343
|
-
status: tx.status
|
|
601
|
+
status: tx.status,
|
|
602
|
+
contractId: tx.contract_id ?? null,
|
|
603
|
+
functionName: tx.function_name ?? null
|
|
344
604
|
});
|
|
605
|
+
const filter = filterLookup.get(sourceName);
|
|
345
606
|
if (events.length === 0) {
|
|
346
607
|
try {
|
|
347
|
-
const
|
|
608
|
+
const payload = filter ? buildEventPayload(filter, tx, null) : {
|
|
348
609
|
tx: {
|
|
349
610
|
txId: tx.tx_id,
|
|
350
611
|
sender: tx.sender,
|
|
@@ -354,13 +615,13 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
354
615
|
functionName: tx.function_name
|
|
355
616
|
}
|
|
356
617
|
};
|
|
357
|
-
await handler(
|
|
618
|
+
await handler(payload, ctx);
|
|
358
619
|
processed++;
|
|
359
620
|
} catch (err) {
|
|
360
621
|
errors++;
|
|
361
622
|
logger2.error("Subgraph handler error", {
|
|
362
623
|
subgraph: subgraph.name,
|
|
363
|
-
|
|
624
|
+
sourceName,
|
|
364
625
|
txId: tx.tx_id,
|
|
365
626
|
error: getErrorMessage(err)
|
|
366
627
|
});
|
|
@@ -377,28 +638,30 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
377
638
|
return { processed, errors };
|
|
378
639
|
}
|
|
379
640
|
try {
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
641
|
+
const payload = filter ? buildEventPayload(filter, tx, event) : (() => {
|
|
642
|
+
const decoded = decodeEventData(event.data);
|
|
643
|
+
return {
|
|
644
|
+
...decoded,
|
|
645
|
+
_eventId: event.id,
|
|
646
|
+
_eventType: event.type,
|
|
647
|
+
_eventIndex: event.event_index,
|
|
648
|
+
tx: {
|
|
649
|
+
txId: tx.tx_id,
|
|
650
|
+
sender: tx.sender,
|
|
651
|
+
type: tx.type,
|
|
652
|
+
status: tx.status,
|
|
653
|
+
contractId: tx.contract_id,
|
|
654
|
+
functionName: tx.function_name
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
})();
|
|
658
|
+
await handler(payload, ctx);
|
|
396
659
|
processed++;
|
|
397
660
|
} catch (err) {
|
|
398
661
|
errors++;
|
|
399
662
|
logger2.error("Subgraph handler error", {
|
|
400
663
|
subgraph: subgraph.name,
|
|
401
|
-
|
|
664
|
+
sourceName,
|
|
402
665
|
txId: tx.tx_id,
|
|
403
666
|
eventId: event.id,
|
|
404
667
|
eventType: event.type,
|
|
@@ -423,62 +686,186 @@ function matchPattern(value, pattern) {
|
|
|
423
686
|
}
|
|
424
687
|
return re.test(value);
|
|
425
688
|
}
|
|
426
|
-
function
|
|
689
|
+
function matchFilter(filter, transactions, eventsByTx) {
|
|
427
690
|
const results = [];
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const
|
|
691
|
+
switch (filter.type) {
|
|
692
|
+
case "stx_transfer":
|
|
693
|
+
case "stx_mint":
|
|
694
|
+
case "stx_burn":
|
|
695
|
+
case "stx_lock": {
|
|
696
|
+
const eventType = `${filter.type}_event`;
|
|
697
|
+
for (const tx of transactions) {
|
|
698
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
699
|
+
const matched = txEvents.filter((e) => e.type === eventType);
|
|
700
|
+
if (matched.length === 0)
|
|
701
|
+
continue;
|
|
702
|
+
const filtered = matched.filter((e) => {
|
|
437
703
|
const data = e.data;
|
|
438
|
-
|
|
439
|
-
if (rawAmount === undefined)
|
|
704
|
+
if (!data)
|
|
440
705
|
return false;
|
|
441
|
-
|
|
442
|
-
|
|
706
|
+
if ("sender" in filter && filter.sender) {
|
|
707
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
if ("recipient" in filter && filter.recipient) {
|
|
711
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
715
|
+
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
719
|
+
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
720
|
+
if (amount < filter.minAmount)
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
724
|
+
const amount = BigInt(data.amount ?? "0");
|
|
725
|
+
if (amount > filter.maxAmount)
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
return true;
|
|
443
729
|
});
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
730
|
+
if (filtered.length > 0) {
|
|
731
|
+
results.push({ tx, events: filtered });
|
|
732
|
+
}
|
|
447
733
|
}
|
|
448
|
-
|
|
449
|
-
continue;
|
|
734
|
+
break;
|
|
450
735
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
736
|
+
case "ft_transfer":
|
|
737
|
+
case "ft_mint":
|
|
738
|
+
case "ft_burn": {
|
|
739
|
+
const eventType = `${filter.type}_event`;
|
|
740
|
+
for (const tx of transactions) {
|
|
741
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
742
|
+
const matched = txEvents.filter((e) => {
|
|
743
|
+
if (e.type !== eventType)
|
|
744
|
+
return false;
|
|
745
|
+
const data = e.data;
|
|
746
|
+
if (!data)
|
|
747
|
+
return false;
|
|
748
|
+
if (filter.assetIdentifier) {
|
|
749
|
+
const assetId = data.asset_identifier;
|
|
750
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
if ("sender" in filter && filter.sender) {
|
|
754
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
if ("recipient" in filter && filter.recipient) {
|
|
758
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
if (filter.minAmount !== undefined) {
|
|
762
|
+
const amount = BigInt(data.amount ?? "0");
|
|
763
|
+
if (amount < filter.minAmount)
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
return true;
|
|
767
|
+
});
|
|
768
|
+
if (matched.length > 0) {
|
|
769
|
+
results.push({ tx, events: matched });
|
|
770
|
+
}
|
|
458
771
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
case "nft_transfer":
|
|
775
|
+
case "nft_mint":
|
|
776
|
+
case "nft_burn": {
|
|
777
|
+
const eventType = `${filter.type}_event`;
|
|
778
|
+
for (const tx of transactions) {
|
|
779
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
780
|
+
const matched = txEvents.filter((e) => {
|
|
781
|
+
if (e.type !== eventType)
|
|
782
|
+
return false;
|
|
463
783
|
const data = e.data;
|
|
464
|
-
|
|
465
|
-
|
|
784
|
+
if (!data)
|
|
785
|
+
return false;
|
|
786
|
+
if (filter.assetIdentifier) {
|
|
787
|
+
const assetId = data.asset_identifier;
|
|
788
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
if ("sender" in filter && filter.sender) {
|
|
792
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
if ("recipient" in filter && filter.recipient) {
|
|
796
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
return true;
|
|
466
800
|
});
|
|
467
|
-
if (
|
|
801
|
+
if (matched.length > 0) {
|
|
802
|
+
results.push({ tx, events: matched });
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
case "contract_call": {
|
|
808
|
+
for (const tx of transactions) {
|
|
809
|
+
if (tx.type !== "contract_call")
|
|
810
|
+
continue;
|
|
811
|
+
if (filter.contractId) {
|
|
812
|
+
if (!tx.contract_id || !matchPattern(tx.contract_id, filter.contractId))
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (filter.functionName) {
|
|
816
|
+
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if (filter.caller) {
|
|
820
|
+
if (!matchPattern(tx.sender, filter.caller))
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
824
|
+
results.push({ tx, events: txEvents });
|
|
825
|
+
}
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
828
|
+
case "contract_deploy": {
|
|
829
|
+
for (const tx of transactions) {
|
|
830
|
+
if (tx.type !== "smart_contract")
|
|
468
831
|
continue;
|
|
832
|
+
if (filter.deployer) {
|
|
833
|
+
if (!matchPattern(tx.sender, filter.deployer))
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (filter.contractName) {
|
|
837
|
+
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
838
|
+
if (!matchPattern(name, filter.contractName))
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
842
|
+
results.push({ tx, events: txEvents });
|
|
469
843
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
case "print_event": {
|
|
847
|
+
for (const tx of transactions) {
|
|
848
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
849
|
+
const matched = txEvents.filter((e) => {
|
|
850
|
+
if (e.type !== "smart_contract_event")
|
|
851
|
+
return false;
|
|
474
852
|
const data = e.data;
|
|
475
|
-
|
|
476
|
-
|
|
853
|
+
if (!data)
|
|
854
|
+
return false;
|
|
855
|
+
if (data.topic !== "print")
|
|
856
|
+
return false;
|
|
857
|
+
if (filter.contractId) {
|
|
858
|
+
const contractId = data.contract_identifier;
|
|
859
|
+
if (!contractId || !matchPattern(contractId, filter.contractId))
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
return true;
|
|
477
863
|
});
|
|
864
|
+
if (matched.length > 0) {
|
|
865
|
+
results.push({ tx, events: matched });
|
|
866
|
+
}
|
|
478
867
|
}
|
|
479
|
-
|
|
480
|
-
results.push({ tx, events: matchedEvents, sourceKey: key });
|
|
481
|
-
}
|
|
868
|
+
break;
|
|
482
869
|
}
|
|
483
870
|
}
|
|
484
871
|
return results;
|
|
@@ -492,13 +879,13 @@ function matchSources(sources, transactions, events) {
|
|
|
492
879
|
}
|
|
493
880
|
const seen = new Set;
|
|
494
881
|
const results = [];
|
|
495
|
-
for (const
|
|
496
|
-
const matches =
|
|
882
|
+
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
883
|
+
const matches = matchFilter(filter, transactions, eventsByTx);
|
|
497
884
|
for (const match of matches) {
|
|
498
|
-
const dedupeKey = `${match.tx.tx_id}:${
|
|
885
|
+
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
499
886
|
if (!seen.has(dedupeKey)) {
|
|
500
887
|
seen.add(dedupeKey);
|
|
501
|
-
results.push(match);
|
|
888
|
+
results.push({ ...match, sourceName });
|
|
502
889
|
}
|
|
503
890
|
}
|
|
504
891
|
}
|
|
@@ -1175,6 +1562,7 @@ async function backfillSubgraph(def, opts) {
|
|
|
1175
1562
|
throw err;
|
|
1176
1563
|
}
|
|
1177
1564
|
}
|
|
1565
|
+
|
|
1178
1566
|
// src/define.ts
|
|
1179
1567
|
function defineSubgraph(def) {
|
|
1180
1568
|
return def;
|
|
@@ -1351,7 +1739,6 @@ function getDefault(type) {
|
|
|
1351
1739
|
}
|
|
1352
1740
|
export {
|
|
1353
1741
|
validateSubgraphDefinition,
|
|
1354
|
-
sourceKey,
|
|
1355
1742
|
resumeReindex,
|
|
1356
1743
|
reindexSubgraph,
|
|
1357
1744
|
pgSchemaName,
|
|
@@ -1362,5 +1749,5 @@ export {
|
|
|
1362
1749
|
backfillSubgraph
|
|
1363
1750
|
};
|
|
1364
1751
|
|
|
1365
|
-
//# debugId=
|
|
1752
|
+
//# debugId=E806638CA5CB2E9E64756E2164756E21
|
|
1366
1753
|
//# sourceMappingURL=index.js.map
|