@secondlayer/subgraphs 7.0.4 → 7.2.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.
@@ -17,7 +17,7 @@ import { createHash } from "node:crypto";
17
17
  // package.json
18
18
  var package_default = {
19
19
  name: "@secondlayer/subgraphs",
20
- version: "7.0.4",
20
+ version: "7.2.0",
21
21
  description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
22
22
  license: "MIT",
23
23
  repository: {
@@ -77,9 +77,9 @@ var package_default = {
77
77
  prepublishOnly: "bun run build"
78
78
  },
79
79
  dependencies: {
80
- "@secondlayer/shared": "^13.0.0",
81
- "@secondlayer/stacks": "^6.2.2",
82
- "@secondlayer/verify": "^0.5.4",
80
+ "@secondlayer/shared": "^13.1.0",
81
+ "@secondlayer/stacks": "^6.4.0",
82
+ "@secondlayer/verify": "^0.6.0",
83
83
  esbuild: "^0.28.2",
84
84
  kysely: "0.28.17",
85
85
  zod: "^4.3.6"
@@ -1134,13 +1134,181 @@ function buildHttpClient() {
1134
1134
  return createInternalIndexHttpClient();
1135
1135
  }
1136
1136
 
1137
- // src/runtime/trigger-evaluator.ts
1138
- import { getSourceDb as getSourceDb3 } from "@secondlayer/shared/db";
1137
+ // src/runtime/chain-data-reader.ts
1138
+ import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
1139
1139
  import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
1140
+ import { hasNoLocalChain } from "@secondlayer/shared/db/queries/instance";
1141
+ function databaseChainDataReader(db) {
1142
+ return {
1143
+ async sbtcEvents(fromHeight, toHeight, topics) {
1144
+ if (topics.length === 0)
1145
+ return [];
1146
+ return db.selectFrom("sbtc_events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).where("canonical", "=", true).where("topic", "in", [...topics]).orderBy("block_height").orderBy("event_index").execute();
1147
+ },
1148
+ traitContractIds(trait, asOfBlock) {
1149
+ return resolveTraitContractIds(db, trait, asOfBlock);
1150
+ },
1151
+ settlementsConfirmedAfter(after) {
1152
+ return db.selectFrom("sbtc_settlements as s").innerJoin("sbtc_events as e", (join) => join.onRef("e.sweep_txid", "=", "s.sweep_txid").on("e.topic", "=", "withdrawal-accept").on("e.canonical", "=", true)).innerJoin("blocks as b", (join) => join.onRef("b.height", "=", "e.block_height").on("b.canonical", "=", true)).where("s.settlement_confirmed", "=", true).where("s.confirmed_at", ">", after).select([
1153
+ "s.sweep_txid",
1154
+ "s.request_id",
1155
+ "s.btc_confirmations",
1156
+ "s.block_height as btc_block_height",
1157
+ "s.confirmed_at",
1158
+ "e.tx_id",
1159
+ "e.block_height as stacks_block_height",
1160
+ "e.amount",
1161
+ "e.sender",
1162
+ "b.hash as block_hash",
1163
+ "b.timestamp as block_timestamp"
1164
+ ]).execute();
1165
+ }
1166
+ };
1167
+ }
1168
+ var THROUGH_TIP = Number.MAX_SAFE_INTEGER;
1169
+ var traitCaches = new WeakMap;
1170
+ function traitCacheFor(client) {
1171
+ let cache = traitCaches.get(client);
1172
+ if (!cache) {
1173
+ cache = new Map;
1174
+ traitCaches.set(client, cache);
1175
+ }
1176
+ return cache;
1177
+ }
1178
+ function toSbtcEventRow(e) {
1179
+ return {
1180
+ topic: e.topic,
1181
+ sender: e.sender,
1182
+ bitcoin_txid: e.bitcoin_txid,
1183
+ request_id: e.request_id === null ? null : String(e.request_id),
1184
+ amount: e.amount,
1185
+ sweep_txid: e.sweep_txid,
1186
+ block_height: String(e.block_height),
1187
+ tx_id: e.tx_id,
1188
+ event_index: e.event_index
1189
+ };
1190
+ }
1191
+ function oneWithdrawalPerSweep(rows) {
1192
+ const bySweep = new Map;
1193
+ for (const row of rows) {
1194
+ if (!row.sweep_txid || !row.confirmed_at)
1195
+ continue;
1196
+ const seen = bySweep.get(row.sweep_txid);
1197
+ if (!seen || row.request_id < seen.request_id) {
1198
+ bySweep.set(row.sweep_txid, row);
1199
+ }
1200
+ }
1201
+ return [...bySweep.values()];
1202
+ }
1203
+ function hostedChainDataReader(client) {
1204
+ return {
1205
+ async sbtcEvents(fromHeight, toHeight, topics) {
1206
+ if (topics.length === 0)
1207
+ return [];
1208
+ const rows = await client.fetchSbtcEvents(fromHeight, toHeight, topics);
1209
+ return rows.map(toSbtcEventRow);
1210
+ },
1211
+ async traitContractIds(trait, asOfBlock) {
1212
+ const cache = traitCacheFor(client);
1213
+ let entry = cache.get(trait);
1214
+ if (!entry || asOfBlock > entry.coveredThrough) {
1215
+ const rows = await client.listContractsByTrait(trait);
1216
+ const members = rows.map((r) => ({
1217
+ contractId: r.contract_id,
1218
+ blockHeight: Number(r.block_height)
1219
+ }));
1220
+ entry = {
1221
+ members,
1222
+ coveredThrough: Math.max(asOfBlock, ...members.map((m) => m.blockHeight))
1223
+ };
1224
+ cache.set(trait, entry);
1225
+ }
1226
+ return entry.members.filter((m) => m.blockHeight <= asOfBlock).map((m) => m.contractId);
1227
+ },
1228
+ async settlementsConfirmedAfter(after) {
1229
+ const withdrawals = oneWithdrawalPerSweep(await client.fetchSettledWithdrawals(after));
1230
+ const out = [];
1231
+ for (const w of withdrawals) {
1232
+ const accepts = await client.fetchSbtcEvents(0, THROUGH_TIP, ["withdrawal-accept"], { requestId: w.request_id });
1233
+ const accept = accepts.filter((e) => e.sweep_txid === w.sweep_txid).at(-1);
1234
+ if (!accept)
1235
+ continue;
1236
+ const [block] = await client.walkBlocks(accept.block_height, accept.block_height);
1237
+ if (!block)
1238
+ continue;
1239
+ out.push({
1240
+ sweep_txid: w.sweep_txid,
1241
+ request_id: w.request_id,
1242
+ btc_confirmations: w.btc_confirmations ?? 0,
1243
+ btc_block_height: w.btc_block_height,
1244
+ confirmed_at: new Date(w.confirmed_at),
1245
+ tx_id: accept.tx_id,
1246
+ stacks_block_height: accept.block_height,
1247
+ amount: accept.amount,
1248
+ sender: accept.sender,
1249
+ block_hash: block.block_hash,
1250
+ block_timestamp: isoToUnixSeconds(block.block_time)
1251
+ });
1252
+ }
1253
+ return out;
1254
+ }
1255
+ };
1256
+ }
1257
+ function chainDataReader(opts) {
1258
+ let resolved;
1259
+ const reader = () => {
1260
+ resolved ??= (async () => {
1261
+ const localDb = opts?.localDb ?? getSourceDb2();
1262
+ if (await hasNoLocalChain(getSourceDb2())) {
1263
+ return hostedChainDataReader(opts?.httpClient ?? sharedHttpClient());
1264
+ }
1265
+ return databaseChainDataReader(localDb);
1266
+ })();
1267
+ return resolved;
1268
+ };
1269
+ return {
1270
+ sbtcEvents: async (...args) => (await reader()).sbtcEvents(...args),
1271
+ traitContractIds: async (...args) => (await reader()).traitContractIds(...args),
1272
+ settlementsConfirmedAfter: async (...args) => (await reader()).settlementsConfirmedAfter(...args)
1273
+ };
1274
+ }
1275
+ var processHttpClient;
1276
+ function sharedHttpClient() {
1277
+ processHttpClient ??= buildHttpClient();
1278
+ return processHttpClient;
1279
+ }
1140
1280
 
1141
1281
  // src/runtime/decoder-bound.ts
1142
1282
  import { committedHeight } from "@secondlayer/shared";
1143
- import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
1283
+ import { getSourceDb as getSourceDb3 } from "@secondlayer/shared/db";
1284
+
1285
+ // src/runtime/clarity.ts
1286
+ import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
1287
+ function decodeClarityValue(hex) {
1288
+ try {
1289
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
1290
+ const cv = deserializeCV(cleanHex);
1291
+ return cvToValue(cv);
1292
+ } catch {
1293
+ return hex;
1294
+ }
1295
+ }
1296
+ function decodeEventData(data) {
1297
+ if (typeof data === "string" && data.startsWith("0x") && data.length > 10) {
1298
+ return decodeClarityValue(data);
1299
+ }
1300
+ if (Array.isArray(data)) {
1301
+ return data.map(decodeEventData);
1302
+ }
1303
+ if (typeof data === "object" && data !== null) {
1304
+ const decoded = {};
1305
+ for (const [key, value] of Object.entries(data)) {
1306
+ decoded[key] = decodeEventData(value);
1307
+ }
1308
+ return decoded;
1309
+ }
1310
+ return data;
1311
+ }
1144
1312
 
1145
1313
  // src/runtime/source-matcher.ts
1146
1314
  var patternCache = new Map;
@@ -1183,6 +1351,41 @@ function printContractId(data) {
1183
1351
  const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
1184
1352
  return id || undefined;
1185
1353
  }
1354
+ var decodedPrints = new WeakMap;
1355
+ function decodedPrintTuple(event, data) {
1356
+ if (decodedPrints.has(event))
1357
+ return decodedPrints.get(event) ?? null;
1358
+ const raw = data.raw_value;
1359
+ const value = typeof raw === "string" && raw.startsWith("0x") ? decodeClarityValue(raw) : null;
1360
+ const tuple = value && typeof value === "object" && !Array.isArray(value) ? value : null;
1361
+ decodedPrints.set(event, tuple);
1362
+ return tuple;
1363
+ }
1364
+ function printFieldEquals(decoded, key, expected) {
1365
+ const value = decoded[key];
1366
+ let actual;
1367
+ if (typeof value === "string")
1368
+ actual = value;
1369
+ else if (typeof value === "bigint" || typeof value === "boolean")
1370
+ actual = String(value);
1371
+ else
1372
+ return false;
1373
+ return typeof expected === "string" ? actual === expected : expected.includes(actual);
1374
+ }
1375
+ function printFieldsAllow(filter, event, data) {
1376
+ if (!filter.topic && !filter.where)
1377
+ return true;
1378
+ const decoded = decodedPrintTuple(event, data);
1379
+ if (!decoded)
1380
+ return false;
1381
+ if (filter.topic && !printFieldEquals(decoded, "topic", filter.topic))
1382
+ return false;
1383
+ for (const [key, expected] of Object.entries(filter.where ?? {})) {
1384
+ if (!printFieldEquals(decoded, key, expected))
1385
+ return false;
1386
+ }
1387
+ return true;
1388
+ }
1186
1389
  function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map, vmEventsByTx = new Map) {
1187
1390
  const results = [];
1188
1391
  switch (filter.type) {
@@ -1419,7 +1622,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryCo
1419
1622
  return false;
1420
1623
  if (!factoryAllows(filter, contractId, factoryContracts))
1421
1624
  return false;
1422
- return true;
1625
+ return printFieldsAllow(filter, e, data);
1423
1626
  });
1424
1627
  if (matched.length > 0) {
1425
1628
  results.push({ tx, events: matched });
@@ -1539,11 +1742,21 @@ function referencedTraits(chainSubs) {
1539
1742
  }
1540
1743
  return [...traits];
1541
1744
  }
1745
+ function readerFor(opts) {
1746
+ if (opts?.reader)
1747
+ return opts.reader;
1748
+ if (opts?.sourceDb)
1749
+ return databaseChainDataReader(opts.sourceDb);
1750
+ return chainDataReader();
1751
+ }
1542
1752
  async function buildTraitContracts(chainSubs, asOfBlock, opts) {
1543
- const sourceDb = opts?.sourceDb ?? getSourceDb3();
1753
+ const traits = referencedTraits(chainSubs);
1754
+ if (traits.length === 0)
1755
+ return new Map;
1756
+ const reader = readerFor(opts);
1544
1757
  const resolved = new Map;
1545
- for (const trait of referencedTraits(chainSubs)) {
1546
- const ids = await resolveTraitContractIds(sourceDb, trait, asOfBlock);
1758
+ for (const trait of traits) {
1759
+ const ids = await reader.traitContractIds(trait, asOfBlock);
1547
1760
  resolved.set(trait, new Set(ids));
1548
1761
  }
1549
1762
  return resolved;
@@ -1626,10 +1839,35 @@ async function emitChainOutbox(db, matches, keyMeta, blockHeight, blockHash, blo
1626
1839
  const result = await db.insertInto("webhook_outbox").values(rows).onConflict((oc) => oc.columns(["webhook_id", "dedup_key"]).doNothing()).executeTakeFirst();
1627
1840
  return Number(result.numInsertedOrUpdatedRows ?? 0);
1628
1841
  }
1629
- async function loadSbtcEventsForBlock(db, blockHeight, topics) {
1842
+ function sbtcSubsOf(chainSubs) {
1843
+ return chainSubs.filter((sub) => triggersOf(sub).some((t) => isSbtcTriggerType(t.type)));
1844
+ }
1845
+ function sbtcTopicsOf(sbtcSubs) {
1846
+ const topics = new Set;
1847
+ for (const sub of sbtcSubs) {
1848
+ for (const trigger of triggersOf(sub)) {
1849
+ const topic = SBTC_TRIGGER_TO_TOPIC[trigger.type];
1850
+ if (topic)
1851
+ topics.add(topic);
1852
+ }
1853
+ }
1854
+ return [...topics];
1855
+ }
1856
+ async function loadSbtcEventsForRange(chainSubs, fromHeight, toHeight, opts) {
1857
+ const byHeight = new Map;
1858
+ const topics = sbtcTopicsOf(sbtcSubsOf(chainSubs));
1630
1859
  if (topics.length === 0)
1631
- return [];
1632
- return db.selectFrom("sbtc_events").selectAll().where("block_height", "=", blockHeight).where("canonical", "=", true).where("topic", "in", topics).execute();
1860
+ return byHeight;
1861
+ const rows = await readerFor(opts).sbtcEvents(fromHeight, toHeight, topics);
1862
+ for (const row of rows) {
1863
+ const height = Number(row.block_height);
1864
+ const list = byHeight.get(height);
1865
+ if (list)
1866
+ list.push(row);
1867
+ else
1868
+ byHeight.set(height, [row]);
1869
+ }
1870
+ return byHeight;
1633
1871
  }
1634
1872
  function toAmountBigInt(v) {
1635
1873
  return v === undefined ? undefined : BigInt(v);
@@ -1714,18 +1952,10 @@ function buildSbtcEventPayload(triggerType, row) {
1714
1952
  };
1715
1953
  }
1716
1954
  async function emitSbtcOutbox(db, chainSubs, blockHeight, blockHash, blockTime, opts) {
1717
- const sbtcSubs = chainSubs.filter((sub) => triggersOf(sub).some((t) => isSbtcTriggerType(t.type)));
1955
+ const sbtcSubs = sbtcSubsOf(chainSubs);
1718
1956
  if (sbtcSubs.length === 0)
1719
1957
  return 0;
1720
- const neededTopics = new Set;
1721
- for (const sub of sbtcSubs) {
1722
- for (const trigger of triggersOf(sub)) {
1723
- const topic = SBTC_TRIGGER_TO_TOPIC[trigger.type];
1724
- if (topic)
1725
- neededTopics.add(topic);
1726
- }
1727
- }
1728
- const sbtcRows = await loadSbtcEventsForBlock(opts?.sourceDb ?? getSourceDb3(), blockHeight, [...neededTopics]);
1958
+ const sbtcRows = opts?.rows ?? (await loadSbtcEventsForRange(sbtcSubs, blockHeight, blockHeight, opts)).get(blockHeight) ?? [];
1729
1959
  if (sbtcRows.length === 0)
1730
1960
  return 0;
1731
1961
  const replayId = opts?.replayId;
@@ -1810,12 +2040,16 @@ async function replayChainWebhook(db, sub, input, opts) {
1810
2040
  const replayId = deterministicReplayId(sub.id, input.fromBlock, input.toBlock, input.replayIdSuffix);
1811
2041
  const { sources, keyMeta } = buildSourcesMap([sub]);
1812
2042
  const source = opts?.source ?? new PublicApiBlockSource(buildHttpClient(), referencedEventTypes([sub]));
2043
+ const reader = chainDataReader();
1813
2044
  let scanned = 0;
1814
2045
  let enqueued = 0;
1815
2046
  for (let from = input.fromBlock;from <= input.toBlock; from += CHAIN_REPLAY_BATCH) {
1816
2047
  const to = Math.min(from + CHAIN_REPLAY_BATCH - 1, input.toBlock);
1817
2048
  const blocks = await source.loadBlockRange(from, to);
1818
- const traitContracts = await buildTraitContracts([sub], to);
2049
+ const traitContracts = await buildTraitContracts([sub], to, { reader });
2050
+ const sbtcByHeight = await loadSbtcEventsForRange([sub], from, to, {
2051
+ reader
2052
+ });
1819
2053
  for (let h = from;h <= to; h++) {
1820
2054
  const bd = blocks.get(h);
1821
2055
  if (!bd)
@@ -1829,7 +2063,8 @@ async function replayChainWebhook(db, sub, input, opts) {
1829
2063
  });
1830
2064
  }
1831
2065
  enqueued += await emitSbtcOutbox(db, [sub], h, bd.block.hash, blockTime, {
1832
- replayId
2066
+ replayId,
2067
+ rows: sbtcByHeight.get(h) ?? []
1833
2068
  });
1834
2069
  }
1835
2070
  }
@@ -1850,5 +2085,5 @@ export {
1850
2085
  replayWebhook
1851
2086
  };
1852
2087
 
1853
- //# debugId=E6626C8093DB964A64756E2164756E21
2088
+ //# debugId=719BB77ECD650B4564756E2164756E21
1854
2089
  //# sourceMappingURL=replay.js.map