@secondlayer/subgraphs 7.1.0 → 7.3.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.1.0",
20
+ version: "7.3.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,8 +77,8 @@ var package_default = {
77
77
  prepublishOnly: "bun run build"
78
78
  },
79
79
  dependencies: {
80
- "@secondlayer/shared": "^13.0.0",
81
- "@secondlayer/stacks": "^6.3.0",
80
+ "@secondlayer/shared": "^13.2.0",
81
+ "@secondlayer/stacks": "^6.4.0",
82
82
  "@secondlayer/verify": "^0.6.0",
83
83
  esbuild: "^0.28.2",
84
84
  kysely: "0.28.17",
@@ -1134,13 +1134,199 @@ 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 TRAIT_REFRESH_MS = 5000;
1170
+ var TRAIT_OVERLAP_MS = 5 * 60000;
1171
+ var traitCaches = new WeakMap;
1172
+ function traitCacheFor(client) {
1173
+ let cache = traitCaches.get(client);
1174
+ if (!cache) {
1175
+ cache = new Map;
1176
+ traitCaches.set(client, cache);
1177
+ }
1178
+ return cache;
1179
+ }
1180
+ function toSbtcEventRow(e) {
1181
+ return {
1182
+ topic: e.topic,
1183
+ sender: e.sender,
1184
+ bitcoin_txid: e.bitcoin_txid,
1185
+ request_id: e.request_id === null ? null : String(e.request_id),
1186
+ amount: e.amount,
1187
+ sweep_txid: e.sweep_txid,
1188
+ block_height: String(e.block_height),
1189
+ tx_id: e.tx_id,
1190
+ event_index: e.event_index
1191
+ };
1192
+ }
1193
+ function oneWithdrawalPerSweep(rows) {
1194
+ const bySweep = new Map;
1195
+ for (const row of rows) {
1196
+ if (!row.sweep_txid || !row.confirmed_at)
1197
+ continue;
1198
+ const seen = bySweep.get(row.sweep_txid);
1199
+ if (!seen || row.request_id < seen.request_id) {
1200
+ bySweep.set(row.sweep_txid, row);
1201
+ }
1202
+ }
1203
+ return [...bySweep.values()];
1204
+ }
1205
+ function hostedChainDataReader(client, opts = {}) {
1206
+ const now = opts.now ?? Date.now;
1207
+ return {
1208
+ async sbtcEvents(fromHeight, toHeight, topics) {
1209
+ if (topics.length === 0)
1210
+ return [];
1211
+ const rows = await client.fetchSbtcEvents(fromHeight, toHeight, topics);
1212
+ return rows.map(toSbtcEventRow);
1213
+ },
1214
+ async traitContractIds(trait, asOfBlock) {
1215
+ const cache = traitCacheFor(client);
1216
+ let entry = cache.get(trait);
1217
+ const due = !entry || asOfBlock > entry.checkedThrough && now() - entry.fetchedAt >= TRAIT_REFRESH_MS;
1218
+ if (due) {
1219
+ const since = entry?.classifiedThrough == null ? undefined : new Date(entry.classifiedThrough - TRAIT_OVERLAP_MS);
1220
+ const rows = await client.listContractsByTrait(trait, since ? { classifiedAfter: since } : undefined);
1221
+ const members = entry?.members ?? new Map;
1222
+ let classifiedThrough = entry?.classifiedThrough ?? null;
1223
+ for (const r of rows) {
1224
+ members.set(r.contract_id, Number(r.block_height));
1225
+ if (r.classified_at) {
1226
+ classifiedThrough = Math.max(classifiedThrough ?? 0, new Date(r.classified_at).getTime());
1227
+ }
1228
+ }
1229
+ entry = {
1230
+ members,
1231
+ classifiedThrough,
1232
+ checkedThrough: asOfBlock,
1233
+ fetchedAt: now()
1234
+ };
1235
+ cache.set(trait, entry);
1236
+ }
1237
+ if (!entry)
1238
+ return [];
1239
+ const ids = [];
1240
+ for (const [contractId, height] of entry.members) {
1241
+ if (height <= asOfBlock)
1242
+ ids.push(contractId);
1243
+ }
1244
+ return ids;
1245
+ },
1246
+ async settlementsConfirmedAfter(after) {
1247
+ const withdrawals = oneWithdrawalPerSweep(await client.fetchSettledWithdrawals(after));
1248
+ const out = [];
1249
+ for (const w of withdrawals) {
1250
+ const accepts = await client.fetchSbtcEvents(0, THROUGH_TIP, ["withdrawal-accept"], { requestId: w.request_id });
1251
+ const accept = accepts.filter((e) => e.sweep_txid === w.sweep_txid).at(-1);
1252
+ if (!accept)
1253
+ continue;
1254
+ const [block] = await client.walkBlocks(accept.block_height, accept.block_height);
1255
+ if (!block)
1256
+ continue;
1257
+ out.push({
1258
+ sweep_txid: w.sweep_txid,
1259
+ request_id: w.request_id,
1260
+ btc_confirmations: w.btc_confirmations ?? 0,
1261
+ btc_block_height: w.btc_block_height,
1262
+ confirmed_at: new Date(w.confirmed_at),
1263
+ tx_id: accept.tx_id,
1264
+ stacks_block_height: accept.block_height,
1265
+ amount: accept.amount,
1266
+ sender: accept.sender,
1267
+ block_hash: block.block_hash,
1268
+ block_timestamp: isoToUnixSeconds(block.block_time)
1269
+ });
1270
+ }
1271
+ return out;
1272
+ }
1273
+ };
1274
+ }
1275
+ function chainDataReader(opts) {
1276
+ let resolved;
1277
+ const reader = () => {
1278
+ resolved ??= (async () => {
1279
+ const localDb = opts?.localDb ?? getSourceDb2();
1280
+ if (await hasNoLocalChain(getSourceDb2())) {
1281
+ return hostedChainDataReader(opts?.httpClient ?? sharedHttpClient());
1282
+ }
1283
+ return databaseChainDataReader(localDb);
1284
+ })();
1285
+ return resolved;
1286
+ };
1287
+ return {
1288
+ sbtcEvents: async (...args) => (await reader()).sbtcEvents(...args),
1289
+ traitContractIds: async (...args) => (await reader()).traitContractIds(...args),
1290
+ settlementsConfirmedAfter: async (...args) => (await reader()).settlementsConfirmedAfter(...args)
1291
+ };
1292
+ }
1293
+ var processHttpClient;
1294
+ function sharedHttpClient() {
1295
+ processHttpClient ??= buildHttpClient();
1296
+ return processHttpClient;
1297
+ }
1140
1298
 
1141
1299
  // src/runtime/decoder-bound.ts
1142
1300
  import { committedHeight } from "@secondlayer/shared";
1143
- import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
1301
+ import { getSourceDb as getSourceDb3 } from "@secondlayer/shared/db";
1302
+
1303
+ // src/runtime/clarity.ts
1304
+ import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
1305
+ function decodeClarityValue(hex) {
1306
+ try {
1307
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
1308
+ const cv = deserializeCV(cleanHex);
1309
+ return cvToValue(cv);
1310
+ } catch {
1311
+ return hex;
1312
+ }
1313
+ }
1314
+ function decodeEventData(data) {
1315
+ if (typeof data === "string" && data.startsWith("0x") && data.length > 10) {
1316
+ return decodeClarityValue(data);
1317
+ }
1318
+ if (Array.isArray(data)) {
1319
+ return data.map(decodeEventData);
1320
+ }
1321
+ if (typeof data === "object" && data !== null) {
1322
+ const decoded = {};
1323
+ for (const [key, value] of Object.entries(data)) {
1324
+ decoded[key] = decodeEventData(value);
1325
+ }
1326
+ return decoded;
1327
+ }
1328
+ return data;
1329
+ }
1144
1330
 
1145
1331
  // src/runtime/source-matcher.ts
1146
1332
  var patternCache = new Map;
@@ -1183,6 +1369,41 @@ function printContractId(data) {
1183
1369
  const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
1184
1370
  return id || undefined;
1185
1371
  }
1372
+ var decodedPrints = new WeakMap;
1373
+ function decodedPrintTuple(event, data) {
1374
+ if (decodedPrints.has(event))
1375
+ return decodedPrints.get(event) ?? null;
1376
+ const raw = data.raw_value;
1377
+ const value = typeof raw === "string" && raw.startsWith("0x") ? decodeClarityValue(raw) : null;
1378
+ const tuple = value && typeof value === "object" && !Array.isArray(value) ? value : null;
1379
+ decodedPrints.set(event, tuple);
1380
+ return tuple;
1381
+ }
1382
+ function printFieldEquals(decoded, key, expected) {
1383
+ const value = decoded[key];
1384
+ let actual;
1385
+ if (typeof value === "string")
1386
+ actual = value;
1387
+ else if (typeof value === "bigint" || typeof value === "boolean")
1388
+ actual = String(value);
1389
+ else
1390
+ return false;
1391
+ return typeof expected === "string" ? actual === expected : expected.includes(actual);
1392
+ }
1393
+ function printFieldsAllow(filter, event, data) {
1394
+ if (!filter.topic && !filter.where)
1395
+ return true;
1396
+ const decoded = decodedPrintTuple(event, data);
1397
+ if (!decoded)
1398
+ return false;
1399
+ if (filter.topic && !printFieldEquals(decoded, "topic", filter.topic))
1400
+ return false;
1401
+ for (const [key, expected] of Object.entries(filter.where ?? {})) {
1402
+ if (!printFieldEquals(decoded, key, expected))
1403
+ return false;
1404
+ }
1405
+ return true;
1406
+ }
1186
1407
  function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map, vmEventsByTx = new Map) {
1187
1408
  const results = [];
1188
1409
  switch (filter.type) {
@@ -1419,7 +1640,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryCo
1419
1640
  return false;
1420
1641
  if (!factoryAllows(filter, contractId, factoryContracts))
1421
1642
  return false;
1422
- return true;
1643
+ return printFieldsAllow(filter, e, data);
1423
1644
  });
1424
1645
  if (matched.length > 0) {
1425
1646
  results.push({ tx, events: matched });
@@ -1539,11 +1760,21 @@ function referencedTraits(chainSubs) {
1539
1760
  }
1540
1761
  return [...traits];
1541
1762
  }
1763
+ function readerFor(opts) {
1764
+ if (opts?.reader)
1765
+ return opts.reader;
1766
+ if (opts?.sourceDb)
1767
+ return databaseChainDataReader(opts.sourceDb);
1768
+ return chainDataReader();
1769
+ }
1542
1770
  async function buildTraitContracts(chainSubs, asOfBlock, opts) {
1543
- const sourceDb = opts?.sourceDb ?? getSourceDb3();
1771
+ const traits = referencedTraits(chainSubs);
1772
+ if (traits.length === 0)
1773
+ return new Map;
1774
+ const reader = readerFor(opts);
1544
1775
  const resolved = new Map;
1545
- for (const trait of referencedTraits(chainSubs)) {
1546
- const ids = await resolveTraitContractIds(sourceDb, trait, asOfBlock);
1776
+ for (const trait of traits) {
1777
+ const ids = await reader.traitContractIds(trait, asOfBlock);
1547
1778
  resolved.set(trait, new Set(ids));
1548
1779
  }
1549
1780
  return resolved;
@@ -1626,10 +1857,35 @@ async function emitChainOutbox(db, matches, keyMeta, blockHeight, blockHash, blo
1626
1857
  const result = await db.insertInto("webhook_outbox").values(rows).onConflict((oc) => oc.columns(["webhook_id", "dedup_key"]).doNothing()).executeTakeFirst();
1627
1858
  return Number(result.numInsertedOrUpdatedRows ?? 0);
1628
1859
  }
1629
- async function loadSbtcEventsForBlock(db, blockHeight, topics) {
1860
+ function sbtcSubsOf(chainSubs) {
1861
+ return chainSubs.filter((sub) => triggersOf(sub).some((t) => isSbtcTriggerType(t.type)));
1862
+ }
1863
+ function sbtcTopicsOf(sbtcSubs) {
1864
+ const topics = new Set;
1865
+ for (const sub of sbtcSubs) {
1866
+ for (const trigger of triggersOf(sub)) {
1867
+ const topic = SBTC_TRIGGER_TO_TOPIC[trigger.type];
1868
+ if (topic)
1869
+ topics.add(topic);
1870
+ }
1871
+ }
1872
+ return [...topics];
1873
+ }
1874
+ async function loadSbtcEventsForRange(chainSubs, fromHeight, toHeight, opts) {
1875
+ const byHeight = new Map;
1876
+ const topics = sbtcTopicsOf(sbtcSubsOf(chainSubs));
1630
1877
  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();
1878
+ return byHeight;
1879
+ const rows = await readerFor(opts).sbtcEvents(fromHeight, toHeight, topics);
1880
+ for (const row of rows) {
1881
+ const height = Number(row.block_height);
1882
+ const list = byHeight.get(height);
1883
+ if (list)
1884
+ list.push(row);
1885
+ else
1886
+ byHeight.set(height, [row]);
1887
+ }
1888
+ return byHeight;
1633
1889
  }
1634
1890
  function toAmountBigInt(v) {
1635
1891
  return v === undefined ? undefined : BigInt(v);
@@ -1714,18 +1970,10 @@ function buildSbtcEventPayload(triggerType, row) {
1714
1970
  };
1715
1971
  }
1716
1972
  async function emitSbtcOutbox(db, chainSubs, blockHeight, blockHash, blockTime, opts) {
1717
- const sbtcSubs = chainSubs.filter((sub) => triggersOf(sub).some((t) => isSbtcTriggerType(t.type)));
1973
+ const sbtcSubs = sbtcSubsOf(chainSubs);
1718
1974
  if (sbtcSubs.length === 0)
1719
1975
  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]);
1976
+ const sbtcRows = opts?.rows ?? (await loadSbtcEventsForRange(sbtcSubs, blockHeight, blockHeight, opts)).get(blockHeight) ?? [];
1729
1977
  if (sbtcRows.length === 0)
1730
1978
  return 0;
1731
1979
  const replayId = opts?.replayId;
@@ -1810,12 +2058,16 @@ async function replayChainWebhook(db, sub, input, opts) {
1810
2058
  const replayId = deterministicReplayId(sub.id, input.fromBlock, input.toBlock, input.replayIdSuffix);
1811
2059
  const { sources, keyMeta } = buildSourcesMap([sub]);
1812
2060
  const source = opts?.source ?? new PublicApiBlockSource(buildHttpClient(), referencedEventTypes([sub]));
2061
+ const reader = chainDataReader();
1813
2062
  let scanned = 0;
1814
2063
  let enqueued = 0;
1815
2064
  for (let from = input.fromBlock;from <= input.toBlock; from += CHAIN_REPLAY_BATCH) {
1816
2065
  const to = Math.min(from + CHAIN_REPLAY_BATCH - 1, input.toBlock);
1817
2066
  const blocks = await source.loadBlockRange(from, to);
1818
- const traitContracts = await buildTraitContracts([sub], to);
2067
+ const traitContracts = await buildTraitContracts([sub], to, { reader });
2068
+ const sbtcByHeight = await loadSbtcEventsForRange([sub], from, to, {
2069
+ reader
2070
+ });
1819
2071
  for (let h = from;h <= to; h++) {
1820
2072
  const bd = blocks.get(h);
1821
2073
  if (!bd)
@@ -1829,7 +2081,8 @@ async function replayChainWebhook(db, sub, input, opts) {
1829
2081
  });
1830
2082
  }
1831
2083
  enqueued += await emitSbtcOutbox(db, [sub], h, bd.block.hash, blockTime, {
1832
- replayId
2084
+ replayId,
2085
+ rows: sbtcByHeight.get(h) ?? []
1833
2086
  });
1834
2087
  }
1835
2088
  }
@@ -1850,5 +2103,5 @@ export {
1850
2103
  replayWebhook
1851
2104
  };
1852
2105
 
1853
- //# debugId=753AE060316EFD5564756E2164756E21
2106
+ //# debugId=E8EBA7C326C2348864756E2164756E21
1854
2107
  //# sourceMappingURL=replay.js.map