@secondlayer/subgraphs 3.21.0 → 3.23.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/dist/src/index.d.ts +238 -17
- package/dist/src/index.js +389 -52
- package/dist/src/index.js.map +12 -11
- package/dist/src/runtime/block-processor.d.ts +111 -10
- package/dist/src/runtime/block-processor.js +263 -31
- package/dist/src/runtime/block-processor.js.map +9 -8
- package/dist/src/runtime/catchup.d.ts +111 -10
- package/dist/src/runtime/catchup.js +263 -31
- package/dist/src/runtime/catchup.js.map +9 -8
- package/dist/src/runtime/context.d.ts +111 -11
- package/dist/src/runtime/context.js +166 -12
- package/dist/src/runtime/context.js.map +7 -6
- package/dist/src/runtime/processor.js +272 -35
- package/dist/src/runtime/processor.js.map +10 -9
- package/dist/src/runtime/reindex.d.ts +111 -10
- package/dist/src/runtime/reindex.js +263 -31
- package/dist/src/runtime/reindex.js.map +9 -8
- package/dist/src/runtime/reorg.d.ts +111 -10
- package/dist/src/runtime/reorg.js +268 -31
- package/dist/src/runtime/reorg.js.map +10 -9
- package/dist/src/runtime/replay.d.ts +3 -3
- package/dist/src/runtime/replay.js +30 -6
- package/dist/src/runtime/replay.js.map +4 -4
- package/dist/src/runtime/runner.d.ts +191 -17
- package/dist/src/runtime/source-matcher.d.ts +68 -8
- package/dist/src/runtime/source-matcher.js +30 -6
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/runtime/stats.d.ts +3 -3
- package/dist/src/schema/index.d.ts +114 -13
- package/dist/src/schema/index.js +133 -20
- package/dist/src/schema/index.js.map +5 -5
- package/dist/src/service.js +272 -35
- package/dist/src/service.js.map +10 -9
- package/dist/src/testing/index.d.ts +667 -0
- package/dist/src/testing/index.js +1463 -0
- package/dist/src/testing/index.js.map +17 -0
- package/dist/src/triggers/index.d.ts +57 -6
- package/dist/src/types.d.ts +112 -11
- package/dist/src/validate.d.ts +129 -11
- package/dist/src/validate.js +117 -18
- package/dist/src/validate.js.map +3 -3
- package/package.json +9 -3
package/dist/src/index.js
CHANGED
|
@@ -86,24 +86,105 @@ var VALID_FILTER_TYPES = [
|
|
|
86
86
|
"contract_deploy",
|
|
87
87
|
"print_event"
|
|
88
88
|
];
|
|
89
|
-
var
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
var contractIdField = z.union([
|
|
90
|
+
z.string(),
|
|
91
|
+
z.array(z.string()).min(1).max(20)
|
|
92
|
+
]);
|
|
93
|
+
var traitScope = { trait: z.string().optional() };
|
|
94
|
+
var factoryScope = {
|
|
95
|
+
factory: z.object({ from: z.string().min(1), field: z.string().min(1) }).strict().optional()
|
|
96
|
+
};
|
|
97
|
+
var amountRange = {
|
|
93
98
|
minAmount: z.bigint().optional(),
|
|
94
|
-
maxAmount: z.bigint().optional()
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
99
|
+
maxAmount: z.bigint().optional()
|
|
100
|
+
};
|
|
101
|
+
var SubgraphFilterUnion = z.discriminatedUnion("type", [
|
|
102
|
+
z.object({
|
|
103
|
+
type: z.literal("stx_transfer"),
|
|
104
|
+
sender: z.string().optional(),
|
|
105
|
+
recipient: z.string().optional(),
|
|
106
|
+
...amountRange
|
|
107
|
+
}).strict(),
|
|
108
|
+
z.object({
|
|
109
|
+
type: z.literal("stx_mint"),
|
|
110
|
+
recipient: z.string().optional(),
|
|
111
|
+
minAmount: z.bigint().optional()
|
|
112
|
+
}).strict(),
|
|
113
|
+
z.object({
|
|
114
|
+
type: z.literal("stx_burn"),
|
|
115
|
+
sender: z.string().optional(),
|
|
116
|
+
minAmount: z.bigint().optional()
|
|
117
|
+
}).strict(),
|
|
118
|
+
z.object({
|
|
119
|
+
type: z.literal("stx_lock"),
|
|
120
|
+
lockedAddress: z.string().optional(),
|
|
121
|
+
minAmount: z.bigint().optional()
|
|
122
|
+
}).strict(),
|
|
123
|
+
z.object({
|
|
124
|
+
type: z.literal("ft_transfer"),
|
|
125
|
+
assetIdentifier: z.string().optional(),
|
|
126
|
+
sender: z.string().optional(),
|
|
127
|
+
recipient: z.string().optional(),
|
|
128
|
+
minAmount: z.bigint().optional(),
|
|
129
|
+
...traitScope
|
|
130
|
+
}).strict(),
|
|
131
|
+
z.object({
|
|
132
|
+
type: z.literal("ft_mint"),
|
|
133
|
+
assetIdentifier: z.string().optional(),
|
|
134
|
+
recipient: z.string().optional(),
|
|
135
|
+
minAmount: z.bigint().optional(),
|
|
136
|
+
...traitScope
|
|
137
|
+
}).strict(),
|
|
138
|
+
z.object({
|
|
139
|
+
type: z.literal("ft_burn"),
|
|
140
|
+
assetIdentifier: z.string().optional(),
|
|
141
|
+
sender: z.string().optional(),
|
|
142
|
+
minAmount: z.bigint().optional(),
|
|
143
|
+
...traitScope
|
|
144
|
+
}).strict(),
|
|
145
|
+
z.object({
|
|
146
|
+
type: z.literal("nft_transfer"),
|
|
147
|
+
assetIdentifier: z.string().optional(),
|
|
148
|
+
sender: z.string().optional(),
|
|
149
|
+
recipient: z.string().optional(),
|
|
150
|
+
...traitScope
|
|
151
|
+
}).strict(),
|
|
152
|
+
z.object({
|
|
153
|
+
type: z.literal("nft_mint"),
|
|
154
|
+
assetIdentifier: z.string().optional(),
|
|
155
|
+
recipient: z.string().optional(),
|
|
156
|
+
...traitScope
|
|
157
|
+
}).strict(),
|
|
158
|
+
z.object({
|
|
159
|
+
type: z.literal("nft_burn"),
|
|
160
|
+
assetIdentifier: z.string().optional(),
|
|
161
|
+
sender: z.string().optional(),
|
|
162
|
+
...traitScope
|
|
163
|
+
}).strict(),
|
|
164
|
+
z.object({
|
|
165
|
+
type: z.literal("contract_call"),
|
|
166
|
+
contractId: contractIdField.optional(),
|
|
167
|
+
functionName: z.string().optional(),
|
|
168
|
+
caller: z.string().optional(),
|
|
169
|
+
abi: AbiContractSchema.optional(),
|
|
170
|
+
...traitScope,
|
|
171
|
+
...factoryScope
|
|
172
|
+
}).strict(),
|
|
173
|
+
z.object({
|
|
174
|
+
type: z.literal("contract_deploy"),
|
|
175
|
+
deployer: z.string().optional(),
|
|
176
|
+
contractName: z.string().optional()
|
|
177
|
+
}).strict(),
|
|
178
|
+
z.object({
|
|
179
|
+
type: z.literal("print_event"),
|
|
180
|
+
contractId: contractIdField.optional(),
|
|
181
|
+
topic: z.string().optional(),
|
|
182
|
+
prints: z.record(z.string(), z.record(z.string(), PrintFieldSchema)).optional(),
|
|
183
|
+
...traitScope,
|
|
184
|
+
...factoryScope
|
|
185
|
+
}).strict()
|
|
186
|
+
]);
|
|
187
|
+
var SubgraphFilterSchema = SubgraphFilterUnion;
|
|
107
188
|
var SubgraphDefinitionSchema = z.object({
|
|
108
189
|
name: SubgraphNameSchema,
|
|
109
190
|
version: z.string().optional(),
|
|
@@ -117,6 +198,23 @@ var SubgraphDefinitionSchema = z.object({
|
|
|
117
198
|
function validateSubgraphDefinition(def) {
|
|
118
199
|
return SubgraphDefinitionSchema.parse(def);
|
|
119
200
|
}
|
|
201
|
+
function filterFieldsByType() {
|
|
202
|
+
const options = SubgraphFilterUnion?._def?.options ?? [];
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const option of options) {
|
|
205
|
+
const shape = option?.shape ?? option?._def?.shape;
|
|
206
|
+
if (!shape)
|
|
207
|
+
continue;
|
|
208
|
+
const literal = shape.type?._def?.values?.[0] ?? shape.type?._def?.value ?? undefined;
|
|
209
|
+
if (typeof literal !== "string")
|
|
210
|
+
continue;
|
|
211
|
+
out.push({
|
|
212
|
+
type: literal,
|
|
213
|
+
fields: Object.keys(shape).filter((k) => k !== "type")
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
120
218
|
|
|
121
219
|
// src/runtime/context.ts
|
|
122
220
|
import { logger } from "@secondlayer/shared/logger";
|
|
@@ -217,6 +315,18 @@ function emitJournalDDL(schemaName) {
|
|
|
217
315
|
`CREATE INDEX IF NOT EXISTS idx_${schemaName}_journal_height ON ${schemaName}._journal (block_height)`
|
|
218
316
|
];
|
|
219
317
|
}
|
|
318
|
+
function emitFactoryDDL(schemaName) {
|
|
319
|
+
return [
|
|
320
|
+
`CREATE TABLE IF NOT EXISTS ${schemaName}._factory_addresses (
|
|
321
|
+
source_name TEXT NOT NULL,
|
|
322
|
+
address TEXT NOT NULL,
|
|
323
|
+
block_height BIGINT NOT NULL,
|
|
324
|
+
_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
325
|
+
PRIMARY KEY (source_name, address)
|
|
326
|
+
)`,
|
|
327
|
+
`CREATE INDEX IF NOT EXISTS idx_${schemaName}_factory_height ON ${schemaName}._factory_addresses (block_height)`
|
|
328
|
+
];
|
|
329
|
+
}
|
|
220
330
|
function emitForeignKeyDDL(schemaName, tableName, tableDef) {
|
|
221
331
|
return (tableDef.relations ?? []).map((rel) => {
|
|
222
332
|
const constraintName = `fk_${schemaName}_${tableName}_${rel.name}`;
|
|
@@ -235,6 +345,9 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
235
345
|
statements.push(...emitTableDDL(schemaName, tableName, tableDef));
|
|
236
346
|
}
|
|
237
347
|
statements.push(...emitJournalDDL(schemaName));
|
|
348
|
+
if (Object.values(def.sources ?? {}).some((src) => src.factory !== undefined)) {
|
|
349
|
+
statements.push(...emitFactoryDDL(schemaName));
|
|
350
|
+
}
|
|
238
351
|
for (const [tableName, tableDef] of Object.entries(def.schema)) {
|
|
239
352
|
statements.push(...emitForeignKeyDDL(schemaName, tableName, tableDef));
|
|
240
353
|
}
|
|
@@ -247,6 +360,139 @@ function generateSubgraphSQL(def, schemaNameOverride) {
|
|
|
247
360
|
return { statements, hash };
|
|
248
361
|
}
|
|
249
362
|
|
|
363
|
+
// src/runtime/chain-read.ts
|
|
364
|
+
import { getSourceDb } from "@secondlayer/shared/db";
|
|
365
|
+
import { http, createPublicClient } from "@secondlayer/stacks";
|
|
366
|
+
import {
|
|
367
|
+
ContractResponseError,
|
|
368
|
+
buildFunctionArgs,
|
|
369
|
+
isResponseOutput,
|
|
370
|
+
readContract
|
|
371
|
+
} from "@secondlayer/stacks/actions";
|
|
372
|
+
import {
|
|
373
|
+
clarityValueToJSUntyped,
|
|
374
|
+
deserializeCV,
|
|
375
|
+
normalizeAbi,
|
|
376
|
+
serializeCV,
|
|
377
|
+
toCamelCase
|
|
378
|
+
} from "@secondlayer/stacks/clarity";
|
|
379
|
+
function readContractAt(ctx, contractId, abi, options) {
|
|
380
|
+
return ctx.client.contract(contractId, abi, options);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
class ChainReadError extends Error {
|
|
384
|
+
name = "ChainReadError";
|
|
385
|
+
}
|
|
386
|
+
function nodeClient(rpcUrl) {
|
|
387
|
+
return createPublicClient({
|
|
388
|
+
transport: http(rpcUrl, { retryCount: 0, timeout: 1e4 })
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class ConcurrencyGate {
|
|
393
|
+
limit;
|
|
394
|
+
active = 0;
|
|
395
|
+
queue = [];
|
|
396
|
+
constructor(limit) {
|
|
397
|
+
this.limit = limit;
|
|
398
|
+
}
|
|
399
|
+
async run(fn) {
|
|
400
|
+
if (this.active >= this.limit) {
|
|
401
|
+
await new Promise((resolve) => this.queue.push(resolve));
|
|
402
|
+
}
|
|
403
|
+
this.active++;
|
|
404
|
+
try {
|
|
405
|
+
return await fn();
|
|
406
|
+
} finally {
|
|
407
|
+
this.active--;
|
|
408
|
+
this.queue.shift()?.();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
var DEFAULT_MAX_CONCURRENT_READS = 4;
|
|
413
|
+
function maxConcurrentReads() {
|
|
414
|
+
const raw = process.env.SUBGRAPH_CHAIN_READ_CONCURRENCY;
|
|
415
|
+
const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
416
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT_READS;
|
|
417
|
+
}
|
|
418
|
+
var gate = new ConcurrencyGate(maxConcurrentReads());
|
|
419
|
+
function hashArgs(args) {
|
|
420
|
+
const serialized = args.map((arg) => serializeCV(arg)).join(",");
|
|
421
|
+
return Bun.hash(serialized).toString(16);
|
|
422
|
+
}
|
|
423
|
+
function findFunction(abi, name) {
|
|
424
|
+
const fn = abi.functions.find((f) => f.name === name);
|
|
425
|
+
if (!fn) {
|
|
426
|
+
throw new ChainReadError(`No function "${name}" in the supplied ABI.`);
|
|
427
|
+
}
|
|
428
|
+
if (fn.access !== "read-only") {
|
|
429
|
+
throw new ChainReadError(`"${name}" is ${fn.access}, not read-only — a handler cannot call it.`);
|
|
430
|
+
}
|
|
431
|
+
return fn;
|
|
432
|
+
}
|
|
433
|
+
function createChainReadClient(ctx) {
|
|
434
|
+
const readValue = async (contractId, abi, functionName, args, options) => {
|
|
435
|
+
const rpcUrl = ctx.rpcUrl ?? process.env.STACKS_NODE_RPC_URL;
|
|
436
|
+
if (!rpcUrl) {
|
|
437
|
+
throw new ChainReadError("ctx.client needs a Stacks node: set STACKS_NODE_RPC_URL on the subgraph processor.");
|
|
438
|
+
}
|
|
439
|
+
const constant = options.cache === "contract-constant";
|
|
440
|
+
if (!constant && !ctx.indexBlockHash) {
|
|
441
|
+
throw new ChainReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a read cannot be pinned to it. Reading at the node's tip would make this handler non-deterministic (the same reindex would produce different rows). Re-ingest the block, or pass cache: "contract-constant" if the value genuinely cannot change.`);
|
|
442
|
+
}
|
|
443
|
+
const fn = findFunction(abi, functionName);
|
|
444
|
+
const clarityArgs = buildFunctionArgs(fn, args);
|
|
445
|
+
const db = ctx.db ?? getSourceDb();
|
|
446
|
+
const argsHash = hashArgs(clarityArgs);
|
|
447
|
+
const pinnedTo = constant ? null : ctx.indexBlockHash;
|
|
448
|
+
const cached = await db.selectFrom("chain_read_cache").select("result_hex").where("contract_id", "=", contractId).where("function_name", "=", fn.name).where("args_hash", "=", argsHash).where((eb) => pinnedTo === null ? eb("index_block_hash", "is", null) : eb("index_block_hash", "=", pinnedTo)).executeTakeFirst();
|
|
449
|
+
const resultHex = cached?.result_hex ?? await gate.run(async () => {
|
|
450
|
+
const value2 = await readContract(nodeClient(rpcUrl), {
|
|
451
|
+
contract: contractId,
|
|
452
|
+
functionName: fn.name,
|
|
453
|
+
args: clarityArgs,
|
|
454
|
+
tip: ctx.indexBlockHash ?? undefined
|
|
455
|
+
});
|
|
456
|
+
return serializeCV(value2);
|
|
457
|
+
});
|
|
458
|
+
if (!cached) {
|
|
459
|
+
await db.insertInto("chain_read_cache").values({
|
|
460
|
+
contract_id: contractId,
|
|
461
|
+
function_name: fn.name,
|
|
462
|
+
args_hash: argsHash,
|
|
463
|
+
index_block_hash: pinnedTo,
|
|
464
|
+
block_height: constant ? null : ctx.blockHeight,
|
|
465
|
+
result_hex: resultHex
|
|
466
|
+
}).onConflict((oc) => oc.doNothing()).execute();
|
|
467
|
+
}
|
|
468
|
+
const value = deserializeCV(resultHex);
|
|
469
|
+
const js = clarityValueToJSUntyped(fn.outputs, value);
|
|
470
|
+
if (isResponseOutput(fn.outputs)) {
|
|
471
|
+
const response = js;
|
|
472
|
+
if ("ok" in response)
|
|
473
|
+
return response.ok;
|
|
474
|
+
throw new ContractResponseError(`${contractId}::${fn.name} returned an err response.`, response.err);
|
|
475
|
+
}
|
|
476
|
+
return js;
|
|
477
|
+
};
|
|
478
|
+
return {
|
|
479
|
+
contract(contractId, rawAbi, options = {}) {
|
|
480
|
+
const abi = normalizeAbi(rawAbi);
|
|
481
|
+
const camelToKebab = new Map;
|
|
482
|
+
for (const fn of abi.functions) {
|
|
483
|
+
camelToKebab.set(toCamelCase(fn.name), fn.name);
|
|
484
|
+
}
|
|
485
|
+
const read = new Proxy({}, {
|
|
486
|
+
get(_target, prop) {
|
|
487
|
+
const fnName = camelToKebab.get(prop) ?? prop;
|
|
488
|
+
return (args = {}) => readValue(contractId, abi, fnName, args, options);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
return { read };
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
250
496
|
// src/runtime/context.ts
|
|
251
497
|
var JOURNAL_RETENTION_BLOCKS = 300;
|
|
252
498
|
var journalEnsured = new Set;
|
|
@@ -265,6 +511,7 @@ class SubgraphContext {
|
|
|
265
511
|
ops = [];
|
|
266
512
|
byo;
|
|
267
513
|
journal;
|
|
514
|
+
_client;
|
|
268
515
|
constructor(db, pgSchemaName2, subgraphSchema, block, tx, byo = false, journal = false) {
|
|
269
516
|
this.db = db;
|
|
270
517
|
this.pgSchemaName = pgSchemaName2;
|
|
@@ -274,6 +521,13 @@ class SubgraphContext {
|
|
|
274
521
|
this.byo = byo;
|
|
275
522
|
this.journal = journal;
|
|
276
523
|
}
|
|
524
|
+
get client() {
|
|
525
|
+
this._client ??= createChainReadClient({
|
|
526
|
+
blockHeight: this.block.height,
|
|
527
|
+
indexBlockHash: this.block.indexBlockHash
|
|
528
|
+
});
|
|
529
|
+
return this._client;
|
|
530
|
+
}
|
|
277
531
|
get tx() {
|
|
278
532
|
return this._tx;
|
|
279
533
|
}
|
|
@@ -379,23 +633,21 @@ class SubgraphContext {
|
|
|
379
633
|
formatUnits(value, decimals) {
|
|
380
634
|
return formatUnits(value, decimals);
|
|
381
635
|
}
|
|
382
|
-
async
|
|
383
|
-
this.validateTable(table);
|
|
636
|
+
async readRows(table, where, limit) {
|
|
384
637
|
const qualifiedTable = `"${this.pgSchemaName}"."${table}"`;
|
|
385
638
|
const { clause } = buildWhereClause(where);
|
|
386
|
-
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause} LIMIT
|
|
639
|
+
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}${limit !== undefined ? ` LIMIT ${limit}` : ""}`;
|
|
387
640
|
const { rows } = await sql.raw(query).execute(this.db);
|
|
388
|
-
|
|
389
|
-
|
|
641
|
+
return rows.map((r) => this.coerceRow(table, r));
|
|
642
|
+
}
|
|
643
|
+
async findOne(table, where) {
|
|
644
|
+
this.validateTable(table);
|
|
645
|
+
const row = (await this.readRows(table, where, 1))[0] ?? null;
|
|
646
|
+
return this.overlayOne(table, where, row);
|
|
390
647
|
}
|
|
391
648
|
async findMany(table, where) {
|
|
392
649
|
this.validateTable(table);
|
|
393
|
-
|
|
394
|
-
const { clause } = buildWhereClause(where);
|
|
395
|
-
const query = `SELECT * FROM ${qualifiedTable} WHERE ${clause}`;
|
|
396
|
-
const { rows } = await sql.raw(query).execute(this.db);
|
|
397
|
-
const dbRows = rows.map((r) => this.coerceRow(table, r));
|
|
398
|
-
return this.overlayMany(table, where, dbRows);
|
|
650
|
+
return this.overlayMany(table, where, await this.readRows(table, where));
|
|
399
651
|
}
|
|
400
652
|
overlayOne(table, where, dbRow) {
|
|
401
653
|
if (this.ops.length === 0)
|
|
@@ -837,11 +1089,11 @@ function buildWhereClause(where) {
|
|
|
837
1089
|
}
|
|
838
1090
|
|
|
839
1091
|
// src/runtime/clarity.ts
|
|
840
|
-
import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
|
|
1092
|
+
import { cvToValue, deserializeCV as deserializeCV2 } from "@secondlayer/stacks/clarity";
|
|
841
1093
|
function decodeClarityValue(hex) {
|
|
842
1094
|
try {
|
|
843
1095
|
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
844
|
-
const cv =
|
|
1096
|
+
const cv = deserializeCV2(cleanHex);
|
|
845
1097
|
return cvToValue(cv);
|
|
846
1098
|
} catch {
|
|
847
1099
|
return hex;
|
|
@@ -872,8 +1124,8 @@ import { getErrorMessage } from "@secondlayer/shared";
|
|
|
872
1124
|
import { logger as logger2 } from "@secondlayer/shared/logger";
|
|
873
1125
|
import {
|
|
874
1126
|
clarityValueToJS,
|
|
875
|
-
deserializeCV as
|
|
876
|
-
toCamelCase
|
|
1127
|
+
deserializeCV as deserializeCV3,
|
|
1128
|
+
toCamelCase as toCamelCase2
|
|
877
1129
|
} from "@secondlayer/stacks/clarity";
|
|
878
1130
|
|
|
879
1131
|
// src/runtime/print-validate.ts
|
|
@@ -1036,7 +1288,7 @@ function buildContractCallInput(filter, tx) {
|
|
|
1036
1288
|
return;
|
|
1037
1289
|
try {
|
|
1038
1290
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1039
|
-
input[
|
|
1291
|
+
input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV3(clean));
|
|
1040
1292
|
} catch {}
|
|
1041
1293
|
});
|
|
1042
1294
|
return input;
|
|
@@ -1315,6 +1567,9 @@ async function runHandlers(subgraph, matched, ctx, opts) {
|
|
|
1315
1567
|
|
|
1316
1568
|
// src/runtime/source-matcher.ts
|
|
1317
1569
|
var patternCache = new Map;
|
|
1570
|
+
function matchAny(value, pattern) {
|
|
1571
|
+
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
1572
|
+
}
|
|
1318
1573
|
function matchPattern(value, pattern) {
|
|
1319
1574
|
if (!pattern.includes("*"))
|
|
1320
1575
|
return value === pattern;
|
|
@@ -1327,6 +1582,22 @@ function matchPattern(value, pattern) {
|
|
|
1327
1582
|
return re.test(value);
|
|
1328
1583
|
}
|
|
1329
1584
|
var EMPTY_SET = new Set;
|
|
1585
|
+
function factoryAllows(filter, contractId, factoryContracts) {
|
|
1586
|
+
if (!filter.factory)
|
|
1587
|
+
return true;
|
|
1588
|
+
if (!contractId)
|
|
1589
|
+
return false;
|
|
1590
|
+
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
1591
|
+
}
|
|
1592
|
+
function readPath(payload, path) {
|
|
1593
|
+
let cur = payload;
|
|
1594
|
+
for (const key of path.split(".")) {
|
|
1595
|
+
if (cur === null || typeof cur !== "object")
|
|
1596
|
+
return;
|
|
1597
|
+
cur = cur[key];
|
|
1598
|
+
}
|
|
1599
|
+
return cur;
|
|
1600
|
+
}
|
|
1330
1601
|
function traitAllows(filter, contractId, traitContracts) {
|
|
1331
1602
|
const trait = filter.trait;
|
|
1332
1603
|
if (!trait)
|
|
@@ -1338,7 +1609,7 @@ function traitAllows(filter, contractId, traitContracts) {
|
|
|
1338
1609
|
function assetContract(assetId) {
|
|
1339
1610
|
return assetId?.split("::")[0];
|
|
1340
1611
|
}
|
|
1341
|
-
function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
1612
|
+
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
|
|
1342
1613
|
const results = [];
|
|
1343
1614
|
switch (filter.type) {
|
|
1344
1615
|
case "stx_transfer":
|
|
@@ -1465,9 +1736,11 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
|
1465
1736
|
if (tx.type !== "contract_call")
|
|
1466
1737
|
continue;
|
|
1467
1738
|
if (filter.contractId) {
|
|
1468
|
-
if (!tx.contract_id || !
|
|
1739
|
+
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
1469
1740
|
continue;
|
|
1470
1741
|
}
|
|
1742
|
+
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
1743
|
+
continue;
|
|
1471
1744
|
if (filter.functionName) {
|
|
1472
1745
|
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
1473
1746
|
continue;
|
|
@@ -1514,11 +1787,13 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
|
1514
1787
|
return false;
|
|
1515
1788
|
const printContractId = data.contract_identifier ?? data.contract_id;
|
|
1516
1789
|
if (filter.contractId) {
|
|
1517
|
-
if (!printContractId || !
|
|
1790
|
+
if (!printContractId || !matchAny(printContractId, filter.contractId))
|
|
1518
1791
|
return false;
|
|
1519
1792
|
}
|
|
1520
1793
|
if (!traitAllows(filter, printContractId, traitContracts))
|
|
1521
1794
|
return false;
|
|
1795
|
+
if (!factoryAllows(filter, printContractId, factoryContracts))
|
|
1796
|
+
return false;
|
|
1522
1797
|
return true;
|
|
1523
1798
|
});
|
|
1524
1799
|
if (matched.length > 0) {
|
|
@@ -1530,7 +1805,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts) {
|
|
|
1530
1805
|
}
|
|
1531
1806
|
return results;
|
|
1532
1807
|
}
|
|
1533
|
-
function matchSources(sources, transactions, events, traitContracts = new Map) {
|
|
1808
|
+
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
|
|
1534
1809
|
const eventsByTx = new Map;
|
|
1535
1810
|
for (const event of events) {
|
|
1536
1811
|
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
@@ -1540,7 +1815,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
|
|
|
1540
1815
|
const seen = new Set;
|
|
1541
1816
|
const results = [];
|
|
1542
1817
|
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
1543
|
-
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts);
|
|
1818
|
+
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
|
|
1544
1819
|
for (const match of matches) {
|
|
1545
1820
|
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
1546
1821
|
if (!seen.has(dedupeKey)) {
|
|
@@ -1567,7 +1842,7 @@ import { logger as logger5 } from "@secondlayer/shared/logger";
|
|
|
1567
1842
|
import { sql as sql3 } from "kysely";
|
|
1568
1843
|
|
|
1569
1844
|
// src/runtime/block-source.ts
|
|
1570
|
-
import { getSourceDb } from "@secondlayer/shared/db";
|
|
1845
|
+
import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
1571
1846
|
import {
|
|
1572
1847
|
IndexHttpClient
|
|
1573
1848
|
} from "@secondlayer/shared/index-http";
|
|
@@ -1628,6 +1903,7 @@ function reconstructBlock(b) {
|
|
|
1628
1903
|
parent_hash: b.parent_hash,
|
|
1629
1904
|
burn_block_height: b.burn_block_height,
|
|
1630
1905
|
burn_block_hash: b.burn_block_hash,
|
|
1906
|
+
index_block_hash: b.index_block_hash ?? null,
|
|
1631
1907
|
timestamp: isoToUnixSeconds(b.block_time),
|
|
1632
1908
|
canonical: true,
|
|
1633
1909
|
created_at: new Date(0)
|
|
@@ -1762,11 +2038,11 @@ function sparseProbeTargets(subgraph) {
|
|
|
1762
2038
|
|
|
1763
2039
|
class PostgresBlockSource {
|
|
1764
2040
|
async getTip() {
|
|
1765
|
-
const progress = await
|
|
2041
|
+
const progress = await getSourceDb2().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
|
|
1766
2042
|
return progress ? Number(progress.highest_seen_block) : 0;
|
|
1767
2043
|
}
|
|
1768
2044
|
loadBlockRange(fromHeight, toHeight) {
|
|
1769
|
-
return loadBlockRange(
|
|
2045
|
+
return loadBlockRange(getSourceDb2(), fromHeight, toHeight);
|
|
1770
2046
|
}
|
|
1771
2047
|
}
|
|
1772
2048
|
var EVENT_FILTER_TO_INDEX_TYPE = {
|
|
@@ -1835,8 +2111,8 @@ class PublicApiBlockSource {
|
|
|
1835
2111
|
eventTypes;
|
|
1836
2112
|
probeTargets;
|
|
1837
2113
|
needsTransactions;
|
|
1838
|
-
constructor(
|
|
1839
|
-
this.http =
|
|
2114
|
+
constructor(http2, eventTypes, probeTargets, needsTransactions = true) {
|
|
2115
|
+
this.http = http2;
|
|
1840
2116
|
this.eventTypes = eventTypes;
|
|
1841
2117
|
this.probeTargets = probeTargets;
|
|
1842
2118
|
this.needsTransactions = needsTransactions;
|
|
@@ -2195,6 +2471,57 @@ async function resolveRoute(subgraphName, targetDb) {
|
|
|
2195
2471
|
function invalidateSubgraphRoute(subgraphName) {
|
|
2196
2472
|
routeCache.delete(subgraphName);
|
|
2197
2473
|
}
|
|
2474
|
+
async function resolveFactoryContracts(subgraph, blockHeight, schemaName, db, txs, evts) {
|
|
2475
|
+
const factories = new Map;
|
|
2476
|
+
for (const source of Object.values(subgraph.sources)) {
|
|
2477
|
+
const factory = source.factory;
|
|
2478
|
+
if (factory)
|
|
2479
|
+
factories.set(factory.from, factory);
|
|
2480
|
+
}
|
|
2481
|
+
const resolved = new Map;
|
|
2482
|
+
const discovered = [];
|
|
2483
|
+
if (factories.size === 0)
|
|
2484
|
+
return { resolved, discovered };
|
|
2485
|
+
for (const [discoveringSource, factory] of factories) {
|
|
2486
|
+
const known = new Set;
|
|
2487
|
+
try {
|
|
2488
|
+
const rows = await sql3`
|
|
2489
|
+
SELECT address FROM ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
|
|
2490
|
+
WHERE source_name = ${discoveringSource} AND block_height <= ${blockHeight}
|
|
2491
|
+
`.execute(db);
|
|
2492
|
+
for (const row of rows.rows)
|
|
2493
|
+
known.add(row.address);
|
|
2494
|
+
} catch {}
|
|
2495
|
+
const discovering = subgraph.sources[factory.from];
|
|
2496
|
+
if (discovering) {
|
|
2497
|
+
const matches = matchSources({ [factory.from]: discovering }, txs, evts, new Map, new Map);
|
|
2498
|
+
for (const match of matches) {
|
|
2499
|
+
for (const event of match.events ?? []) {
|
|
2500
|
+
const payload = buildEventPayload(discovering, match.tx, event);
|
|
2501
|
+
const value = readPath(payload, factory.field);
|
|
2502
|
+
if (typeof value === "string" && value.length > 0 && !known.has(value)) {
|
|
2503
|
+
known.add(value);
|
|
2504
|
+
discovered.push({ sourceName: discoveringSource, address: value });
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
resolved.set(discoveringSource, known);
|
|
2510
|
+
}
|
|
2511
|
+
return { resolved, discovered };
|
|
2512
|
+
}
|
|
2513
|
+
async function persistFactoryDiscoveries(schemaName, db, blockHeight, discovered) {
|
|
2514
|
+
if (discovered.length === 0)
|
|
2515
|
+
return;
|
|
2516
|
+
for (const { sourceName, address } of discovered) {
|
|
2517
|
+
await sql3`
|
|
2518
|
+
INSERT INTO ${sql3.raw(`"${schemaName}"."_factory_addresses"`)}
|
|
2519
|
+
(source_name, address, block_height)
|
|
2520
|
+
VALUES (${sourceName}, ${address}, ${blockHeight})
|
|
2521
|
+
ON CONFLICT (source_name, address) DO NOTHING
|
|
2522
|
+
`.execute(db);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2198
2525
|
async function resolveTraitContracts(subgraph, blockHeight, db) {
|
|
2199
2526
|
const traits = new Set;
|
|
2200
2527
|
for (const source of Object.values(subgraph.sources)) {
|
|
@@ -2281,8 +2608,11 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2281
2608
|
txs = data.txs;
|
|
2282
2609
|
evts = data.events;
|
|
2283
2610
|
}
|
|
2611
|
+
const route = await resolveRoute(subgraphName, targetDb);
|
|
2612
|
+
const schemaName = route.schemaName;
|
|
2284
2613
|
const traitContracts = await resolveTraitContracts(subgraph, blockHeight, targetDb);
|
|
2285
|
-
const
|
|
2614
|
+
const { resolved: factoryContracts, discovered } = await resolveFactoryContracts(subgraph, blockHeight, schemaName, targetDb, txs, evts);
|
|
2615
|
+
const matched = matchSources(subgraph.sources, txs, evts, traitContracts, factoryContracts);
|
|
2286
2616
|
result.matched = matched.length;
|
|
2287
2617
|
if (matched.length === 0) {
|
|
2288
2618
|
if (!opts?.skipProgressUpdate) {
|
|
@@ -2290,13 +2620,12 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2290
2620
|
}
|
|
2291
2621
|
return result;
|
|
2292
2622
|
}
|
|
2293
|
-
const route = await resolveRoute(subgraphName, targetDb);
|
|
2294
|
-
const schemaName = route.schemaName;
|
|
2295
2623
|
const blockMeta = {
|
|
2296
2624
|
height: block.height,
|
|
2297
2625
|
hash: block.hash,
|
|
2298
2626
|
timestamp: block.timestamp,
|
|
2299
|
-
burnBlockHeight: block.burn_block_height
|
|
2627
|
+
burnBlockHeight: block.burn_block_height,
|
|
2628
|
+
indexBlockHash: block.index_block_hash
|
|
2300
2629
|
};
|
|
2301
2630
|
const initialTx = {
|
|
2302
2631
|
txId: "",
|
|
@@ -2349,6 +2678,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
|
|
|
2349
2678
|
});
|
|
2350
2679
|
result.processed = runResult.processed;
|
|
2351
2680
|
result.errors = runResult.errors;
|
|
2681
|
+
await persistFactoryDiscoveries(schemaName, route.dataDb, blockHeight, discovered);
|
|
2352
2682
|
await targetDb.transaction().execute(async (tx) => {
|
|
2353
2683
|
if (manifest && manifest.count > 0) {
|
|
2354
2684
|
await emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, block.height);
|
|
@@ -3068,9 +3398,12 @@ async function backfillSubgraph(def, opts) {
|
|
|
3068
3398
|
function defineSubgraph(def) {
|
|
3069
3399
|
return def;
|
|
3070
3400
|
}
|
|
3401
|
+
function defineSchema(schema) {
|
|
3402
|
+
return schema;
|
|
3403
|
+
}
|
|
3071
3404
|
// src/print-schema.ts
|
|
3072
3405
|
import {
|
|
3073
|
-
deserializeCV as
|
|
3406
|
+
deserializeCV as deserializeCV4
|
|
3074
3407
|
} from "@secondlayer/stacks/clarity";
|
|
3075
3408
|
function camelizeDataKey(str) {
|
|
3076
3409
|
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
@@ -3338,7 +3671,7 @@ function inferPrintTopics(samples) {
|
|
|
3338
3671
|
let decoded = 0;
|
|
3339
3672
|
for (const p of picked) {
|
|
3340
3673
|
try {
|
|
3341
|
-
const cv =
|
|
3674
|
+
const cv = deserializeCV4(p.rawHex);
|
|
3342
3675
|
decoded++;
|
|
3343
3676
|
if (cv.type === "tuple")
|
|
3344
3677
|
tuples.push(cv);
|
|
@@ -3987,8 +4320,8 @@ function diffKeyLists(existing, incoming) {
|
|
|
3987
4320
|
const existingSet = new Set((existing ?? []).map(key2));
|
|
3988
4321
|
const incomingSet = new Set((incoming ?? []).map(key2));
|
|
3989
4322
|
return {
|
|
3990
|
-
added: (incoming ?? []).filter((cols) => !existingSet.has(key2(cols))),
|
|
3991
|
-
removed: (existing ?? []).filter((cols) => !incomingSet.has(key2(cols)))
|
|
4323
|
+
added: (incoming ?? []).filter((cols) => !existingSet.has(key2(cols))).map((cols) => [...cols]),
|
|
4324
|
+
removed: (existing ?? []).filter((cols) => !incomingSet.has(key2(cols))).map((cols) => [...cols])
|
|
3992
4325
|
};
|
|
3993
4326
|
}
|
|
3994
4327
|
function diffSchema(existing, incoming) {
|
|
@@ -4337,6 +4670,7 @@ export {
|
|
|
4337
4670
|
resumeReindex,
|
|
4338
4671
|
renderDeployPlan,
|
|
4339
4672
|
reindexSubgraph,
|
|
4673
|
+
readContractAt,
|
|
4340
4674
|
pgSchemaName,
|
|
4341
4675
|
inferPrintTopics,
|
|
4342
4676
|
hasBreakingChanges,
|
|
@@ -4345,15 +4679,18 @@ export {
|
|
|
4345
4679
|
generateKyselySchema,
|
|
4346
4680
|
generateIndexSchema,
|
|
4347
4681
|
generateDrizzleSchema,
|
|
4682
|
+
filterFieldsByType,
|
|
4348
4683
|
diffSchema,
|
|
4349
4684
|
deploySchema,
|
|
4350
4685
|
defineSubgraph,
|
|
4686
|
+
defineSchema,
|
|
4351
4687
|
canSparseScan,
|
|
4352
4688
|
camelizeDataKey,
|
|
4353
4689
|
backfillSubgraph,
|
|
4354
4690
|
INDEX_CODEGEN_TABLES,
|
|
4691
|
+
ChainReadError,
|
|
4355
4692
|
ByoBreakingChangeError
|
|
4356
4693
|
};
|
|
4357
4694
|
|
|
4358
|
-
//# debugId=
|
|
4695
|
+
//# debugId=A7D0F6BE4A80734464756E2164756E21
|
|
4359
4696
|
//# sourceMappingURL=index.js.map
|