@secondlayer/subgraphs 3.5.0 → 3.6.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.
@@ -518,21 +518,21 @@ function buildEventPayload(filter, tx, event) {
518
518
  return {
519
519
  sender: decoded.sender,
520
520
  recipient: decoded.recipient,
521
- tokenId: decoded.value,
521
+ tokenId: decoded.raw_value ?? decoded.value,
522
522
  assetIdentifier: decoded.asset_identifier,
523
523
  tx: txMeta
524
524
  };
525
525
  case "nft_mint":
526
526
  return {
527
527
  recipient: decoded.recipient,
528
- tokenId: decoded.value,
528
+ tokenId: decoded.raw_value ?? decoded.value,
529
529
  assetIdentifier: decoded.asset_identifier,
530
530
  tx: txMeta
531
531
  };
532
532
  case "nft_burn":
533
533
  return {
534
534
  sender: decoded.sender,
535
- tokenId: decoded.value,
535
+ tokenId: decoded.raw_value ?? decoded.value,
536
536
  assetIdentifier: decoded.asset_identifier,
537
537
  tx: txMeta
538
538
  };
@@ -564,8 +564,8 @@ function buildEventPayload(filter, tx, event) {
564
564
  tx: txMeta
565
565
  };
566
566
  case "print_event": {
567
- const decodedRawValue = decoded.raw_value;
568
- const rawValue = decodedRawValue && typeof decodedRawValue === "object" && !Array.isArray(decodedRawValue) ? decodedRawValue : decoded.value;
567
+ const rawHex = event.data?.raw_value;
568
+ const rawValue = typeof rawHex === "string" && rawHex.startsWith("0x") ? decodeClarityValue(rawHex) : decoded.value;
569
569
  const clarityObj = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue) ? rawValue : null;
570
570
  const topic = clarityObj?.topic ? String(clarityObj.topic) : decoded.topic ?? "";
571
571
  const { topic: _, ...rest } = clarityObj ?? {};
@@ -949,10 +949,7 @@ function matchSources(sources, transactions, events, traitContracts = new Map) {
949
949
  }
950
950
 
951
951
  // src/runtime/block-processor.ts
952
- import {
953
- getSourceDb,
954
- getTargetDb
955
- } from "@secondlayer/shared/db";
952
+ import { getTargetDb } from "@secondlayer/shared/db";
956
953
  import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
957
954
  import {
958
955
  isByoSubgraph,
@@ -960,15 +957,287 @@ import {
960
957
  resolveSubgraphDb,
961
958
  updateSubgraphStatus
962
959
  } from "@secondlayer/shared/db/queries/subgraphs";
963
- import { logger as logger4 } from "@secondlayer/shared/logger";
960
+ import { logger as logger5 } from "@secondlayer/shared/logger";
964
961
  import { sql as sql3 } from "kysely";
965
962
 
966
963
  // src/schema/utils.ts
967
964
  import { pgSchemaName } from "@secondlayer/shared/db/queries/subgraphs";
968
965
 
966
+ // src/runtime/block-source.ts
967
+ import { getSourceDb } from "@secondlayer/shared/db";
968
+ import { IndexHttpClient } from "@secondlayer/shared/index-http";
969
+ import { logger as logger3 } from "@secondlayer/shared/logger";
970
+
971
+ // src/runtime/batch-loader.ts
972
+ async function loadBlockRange(db, fromHeight, toHeight) {
973
+ const [blocks, txs, events] = await Promise.all([
974
+ db.selectFrom("blocks").selectAll().where("height", ">=", fromHeight).where("height", "<=", toHeight).where("canonical", "=", true).execute(),
975
+ db.selectFrom("transactions").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute(),
976
+ db.selectFrom("events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).execute()
977
+ ]);
978
+ const txsByHeight = new Map;
979
+ for (const tx of txs) {
980
+ const h = Number(tx.block_height);
981
+ const list = txsByHeight.get(h) ?? [];
982
+ list.push(tx);
983
+ txsByHeight.set(h, list);
984
+ }
985
+ const eventsByHeight = new Map;
986
+ for (const evt of events) {
987
+ const h = Number(evt.block_height);
988
+ const list = eventsByHeight.get(h) ?? [];
989
+ list.push(evt);
990
+ eventsByHeight.set(h, list);
991
+ }
992
+ const result = new Map;
993
+ for (const block of blocks) {
994
+ const h = Number(block.height);
995
+ result.set(h, {
996
+ block,
997
+ txs: txsByHeight.get(h) ?? [],
998
+ events: eventsByHeight.get(h) ?? []
999
+ });
1000
+ }
1001
+ return result;
1002
+ }
1003
+ function avgEventsPerBlock(batch) {
1004
+ if (batch.size === 0)
1005
+ return 0;
1006
+ let totalEvents = 0;
1007
+ for (const data of batch.values()) {
1008
+ totalEvents += data.events.length;
1009
+ }
1010
+ return totalEvents / batch.size;
1011
+ }
1012
+
1013
+ // src/runtime/reconstruct.ts
1014
+ function isoToUnixSeconds(iso) {
1015
+ if (!iso)
1016
+ return 0;
1017
+ return Math.floor(new Date(iso).getTime() / 1000);
1018
+ }
1019
+ function reconstructBlock(b) {
1020
+ return {
1021
+ height: b.block_height,
1022
+ hash: b.block_hash,
1023
+ parent_hash: b.parent_hash,
1024
+ burn_block_height: b.burn_block_height,
1025
+ burn_block_hash: b.burn_block_hash,
1026
+ timestamp: isoToUnixSeconds(b.block_time),
1027
+ canonical: true,
1028
+ created_at: new Date(0)
1029
+ };
1030
+ }
1031
+ function reconstructTransaction(t) {
1032
+ return {
1033
+ tx_id: t.tx_id,
1034
+ block_height: t.block_height,
1035
+ tx_index: t.tx_index,
1036
+ type: t.tx_type,
1037
+ sender: t.sender,
1038
+ status: t.status,
1039
+ contract_id: t.contract_call?.contract_id ?? t.smart_contract?.contract_id ?? null,
1040
+ function_name: t.contract_call?.function_name ?? null,
1041
+ function_args: t.contract_call?.function_args_hex ?? [],
1042
+ raw_result: t.contract_call?.result_hex ?? null,
1043
+ raw_tx: "",
1044
+ created_at: new Date(0)
1045
+ };
1046
+ }
1047
+ function reconstructEvent(e) {
1048
+ const base = {
1049
+ id: `${e.tx_id}#${e.event_index}`,
1050
+ tx_id: e.tx_id,
1051
+ block_height: e.block_height,
1052
+ event_index: e.event_index,
1053
+ created_at: new Date(0)
1054
+ };
1055
+ switch (e.event_type) {
1056
+ case "ft_transfer":
1057
+ case "ft_mint":
1058
+ case "ft_burn":
1059
+ return {
1060
+ ...base,
1061
+ type: `${e.event_type}_event`,
1062
+ data: {
1063
+ asset_identifier: e.asset_identifier,
1064
+ sender: e.sender,
1065
+ recipient: e.recipient,
1066
+ amount: e.amount
1067
+ }
1068
+ };
1069
+ case "nft_transfer":
1070
+ case "nft_mint":
1071
+ case "nft_burn":
1072
+ return {
1073
+ ...base,
1074
+ type: `${e.event_type}_event`,
1075
+ data: {
1076
+ asset_identifier: e.asset_identifier,
1077
+ sender: e.sender,
1078
+ recipient: e.recipient,
1079
+ raw_value: e.value
1080
+ }
1081
+ };
1082
+ case "stx_transfer":
1083
+ case "stx_mint":
1084
+ case "stx_burn":
1085
+ return {
1086
+ ...base,
1087
+ type: `${e.event_type}_event`,
1088
+ data: {
1089
+ sender: e.sender,
1090
+ recipient: e.recipient,
1091
+ amount: e.amount,
1092
+ ..."memo" in e ? { memo: e.memo ?? undefined } : {}
1093
+ }
1094
+ };
1095
+ case "stx_lock":
1096
+ return {
1097
+ ...base,
1098
+ type: "stx_lock_event",
1099
+ data: {
1100
+ locked_address: e.sender,
1101
+ locked_amount: e.amount,
1102
+ unlock_height: e.payload.unlock_height
1103
+ }
1104
+ };
1105
+ case "print":
1106
+ return {
1107
+ ...base,
1108
+ type: "contract_event",
1109
+ data: {
1110
+ topic: e.payload.topic,
1111
+ contract_id: e.contract_id,
1112
+ contract_identifier: e.contract_id,
1113
+ value: e.payload.value,
1114
+ raw_value: e.payload.raw_value
1115
+ }
1116
+ };
1117
+ }
1118
+ }
1119
+
1120
+ // src/runtime/block-source.ts
1121
+ class PostgresBlockSource {
1122
+ async getTip() {
1123
+ const progress = await getSourceDb().selectFrom("index_progress").selectAll().where("network", "=", process.env.NETWORK ?? "mainnet").executeTakeFirst();
1124
+ return progress ? Number(progress.highest_seen_block) : 0;
1125
+ }
1126
+ loadBlockRange(fromHeight, toHeight) {
1127
+ return loadBlockRange(getSourceDb(), fromHeight, toHeight);
1128
+ }
1129
+ }
1130
+ var EVENT_FILTER_TO_INDEX_TYPE = {
1131
+ stx_transfer: "stx_transfer",
1132
+ stx_mint: "stx_mint",
1133
+ stx_burn: "stx_burn",
1134
+ stx_lock: "stx_lock",
1135
+ ft_transfer: "ft_transfer",
1136
+ ft_mint: "ft_mint",
1137
+ ft_burn: "ft_burn",
1138
+ nft_transfer: "nft_transfer",
1139
+ nft_mint: "nft_mint",
1140
+ nft_burn: "nft_burn",
1141
+ print_event: "print"
1142
+ };
1143
+ var TX_SOURCE_TYPES = new Set(["contract_call", "contract_deploy"]);
1144
+ var ALL_INDEX_EVENT_TYPES = [
1145
+ ...new Set(Object.values(EVENT_FILTER_TO_INDEX_TYPE))
1146
+ ];
1147
+ function sourceFilters(subgraph) {
1148
+ const sources = subgraph.sources;
1149
+ return Array.isArray(sources) ? sources : Object.values(sources);
1150
+ }
1151
+ function referencedIndexEventTypes(subgraph) {
1152
+ const filters = sourceFilters(subgraph);
1153
+ if (filters.some((f) => TX_SOURCE_TYPES.has(f.type))) {
1154
+ return ALL_INDEX_EVENT_TYPES;
1155
+ }
1156
+ const types = new Set;
1157
+ for (const f of filters) {
1158
+ const t = EVENT_FILTER_TO_INDEX_TYPE[f.type];
1159
+ if (t)
1160
+ types.add(t);
1161
+ }
1162
+ return [...types];
1163
+ }
1164
+ function isStreamsIndexEligible(subgraph) {
1165
+ if (Array.isArray(subgraph.sources))
1166
+ return false;
1167
+ const filters = sourceFilters(subgraph);
1168
+ if (filters.length === 0)
1169
+ return false;
1170
+ for (const f of filters) {
1171
+ const known = EVENT_FILTER_TO_INDEX_TYPE[f.type] || TX_SOURCE_TYPES.has(f.type);
1172
+ if (!known)
1173
+ return false;
1174
+ }
1175
+ return true;
1176
+ }
1177
+
1178
+ class PublicApiBlockSource {
1179
+ http;
1180
+ eventTypes;
1181
+ constructor(http, eventTypes) {
1182
+ this.http = http;
1183
+ this.eventTypes = eventTypes;
1184
+ }
1185
+ getTip() {
1186
+ return this.http.getIndexTip();
1187
+ }
1188
+ async loadBlockRange(fromHeight, toHeight) {
1189
+ const [blocks, txs, eventLists] = await Promise.all([
1190
+ this.http.walkBlocks(fromHeight, toHeight),
1191
+ this.http.walkTransactions(fromHeight, toHeight),
1192
+ Promise.all(this.eventTypes.map((t) => this.http.walkEvents(t, fromHeight, toHeight)))
1193
+ ]);
1194
+ const map = new Map;
1195
+ for (const b of blocks) {
1196
+ map.set(b.block_height, {
1197
+ block: reconstructBlock(b),
1198
+ txs: [],
1199
+ events: []
1200
+ });
1201
+ }
1202
+ for (const t of txs) {
1203
+ map.get(t.block_height)?.txs.push(reconstructTransaction(t));
1204
+ }
1205
+ for (const list of eventLists) {
1206
+ for (const e of list) {
1207
+ map.get(e.block_height)?.events.push(reconstructEvent(e));
1208
+ }
1209
+ }
1210
+ for (const bd of map.values()) {
1211
+ bd.txs.sort((a, b) => a.tx_index - b.tx_index);
1212
+ bd.events.sort((a, b) => a.event_index - b.event_index);
1213
+ }
1214
+ return map;
1215
+ }
1216
+ }
1217
+ var postgresBlockSource = new PostgresBlockSource;
1218
+ function buildHttpClient() {
1219
+ const baseUrl = process.env.SUBGRAPH_INDEX_API_URL ?? process.env.STREAMS_API_URL ?? "http://api:3800";
1220
+ return new IndexHttpClient({
1221
+ indexBaseUrl: baseUrl,
1222
+ streamsBaseUrl: baseUrl,
1223
+ streamsApiKey: process.env.STREAMS_INTERNAL_API_KEY ?? "sk-sl_streams_l2_internal"
1224
+ });
1225
+ }
1226
+ function resolveBlockSource(subgraph) {
1227
+ if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph && isStreamsIndexEligible(subgraph)) {
1228
+ return new PublicApiBlockSource(buildHttpClient(), referencedIndexEventTypes(subgraph));
1229
+ }
1230
+ if (process.env.SUBGRAPH_SOURCE === "streams-index" && subgraph) {
1231
+ logger3.debug("Subgraph not streams-index eligible, using DB tap", {
1232
+ subgraph: subgraph.name
1233
+ });
1234
+ }
1235
+ return postgresBlockSource;
1236
+ }
1237
+
969
1238
  // src/runtime/outbox-emit.ts
970
1239
  import { createHash } from "node:crypto";
971
- import { logger as logger3 } from "@secondlayer/shared/logger";
1240
+ import { logger as logger4 } from "@secondlayer/shared/logger";
972
1241
  var loggedKillSwitch = false;
973
1242
  function isEmitOutboxEnabled() {
974
1243
  return process.env.SECONDLAYER_EMIT_OUTBOX !== "false";
@@ -987,7 +1256,7 @@ function stableStringify(obj) {
987
1256
  async function emitSubscriptionOutbox(tx, subgraphName, manifest, matcher, blockHeight) {
988
1257
  if (!isEmitOutboxEnabled()) {
989
1258
  if (!loggedKillSwitch) {
990
- logger3.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
1259
+ logger4.warn("SECONDLAYER_EMIT_OUTBOX=false — outbox emission bypassed");
991
1260
  loggedKillSwitch = true;
992
1261
  }
993
1262
  return 0;
@@ -1228,7 +1497,6 @@ async function resolveTraitContracts(subgraph, blockHeight, db) {
1228
1497
  return resolved;
1229
1498
  }
1230
1499
  async function processBlock(subgraph, subgraphName, blockHeight, opts) {
1231
- const sourceDb = getSourceDb();
1232
1500
  const targetDb = getTargetDb();
1233
1501
  const blockStart = performance.now();
1234
1502
  const result = {
@@ -1246,25 +1514,18 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
1246
1514
  txs = opts.preloaded.txs;
1247
1515
  evts = opts.preloaded.events;
1248
1516
  } else {
1249
- block = await sourceDb.selectFrom("blocks").selectAll().where("height", "=", blockHeight).executeTakeFirst();
1250
- if (!block) {
1251
- logger4.warn("Block not found for subgraph processing", {
1517
+ const data = (await resolveBlockSource(subgraph).loadBlockRange(blockHeight, blockHeight)).get(blockHeight);
1518
+ if (!data) {
1519
+ logger5.debug("Block not found or non-canonical for subgraph processing", {
1252
1520
  subgraph: subgraphName,
1253
1521
  blockHeight
1254
1522
  });
1255
1523
  result.skipped = true;
1256
1524
  return result;
1257
1525
  }
1258
- if (!block.canonical) {
1259
- logger4.debug("Skipping non-canonical block", {
1260
- subgraph: subgraphName,
1261
- blockHeight
1262
- });
1263
- result.skipped = true;
1264
- return result;
1265
- }
1266
- txs = await sourceDb.selectFrom("transactions").selectAll().where("block_height", "=", blockHeight).execute();
1267
- evts = await sourceDb.selectFrom("events").selectAll().where("block_height", "=", blockHeight).execute();
1526
+ block = data.block;
1527
+ txs = data.txs;
1528
+ evts = data.events;
1268
1529
  }
1269
1530
  const traitContracts = await resolveTraitContracts(subgraph, blockHeight, targetDb);
1270
1531
  const matched = matchSources(subgraph.sources, txs, evts, traitContracts);
@@ -1355,7 +1616,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
1355
1616
  const { rows } = await sql3.raw(`SELECT n_live_tup AS count FROM pg_stat_user_tables WHERE schemaname = '${schemaName}' AND relname = '${table}'`).execute(route.dataDb);
1356
1617
  const count = Number(rows[0]?.count ?? 0);
1357
1618
  if (count >= 1e7) {
1358
- logger4.warn("Subgraph table exceeds 10M rows (estimate)", {
1619
+ logger5.warn("Subgraph table exceeds 10M rows (estimate)", {
1359
1620
  subgraph: subgraphName,
1360
1621
  table,
1361
1622
  count
@@ -1363,7 +1624,7 @@ async function processBlock(subgraph, subgraphName, blockHeight, opts) {
1363
1624
  }
1364
1625
  }
1365
1626
  } catch (err) {
1366
- logger4.debug("Row count sample failed", {
1627
+ logger5.debug("Row count sample failed", {
1367
1628
  subgraph: subgraphName,
1368
1629
  error: err instanceof Error ? err.message : String(err)
1369
1630
  });
@@ -1379,13 +1640,13 @@ import {
1379
1640
  listSubgraphs,
1380
1641
  resolveSubgraphRawClient
1381
1642
  } from "@secondlayer/shared/db/queries/subgraphs";
1382
- import { logger as logger5 } from "@secondlayer/shared/logger";
1643
+ import { logger as logger6 } from "@secondlayer/shared/logger";
1383
1644
  async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
1384
1645
  const targetDb = getTargetDb2();
1385
1646
  const activeSubgraphs = (await listSubgraphs(targetDb)).filter((v) => v.status === "active");
1386
1647
  if (activeSubgraphs.length === 0)
1387
1648
  return;
1388
- logger5.info("Propagating reorg to subgraphs", {
1649
+ logger6.info("Propagating reorg to subgraphs", {
1389
1650
  blockHeight,
1390
1651
  subgraphCount: activeSubgraphs.length
1391
1652
  });
@@ -1433,7 +1694,7 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
1433
1694
  }).as("row_pk"),
1434
1695
  eb2.val("pending").as("status")
1435
1696
  ]).where("subgraph_name", "=", sg.name).where("table_name", "=", tableName).where("status", "=", "active")).onConflict((oc) => oc.column("dedup_key").doNothing()).execute().catch((err) => {
1436
- logger5.warn("Failed to emit revert event for subscriptions", {
1697
+ logger6.warn("Failed to emit revert event for subscriptions", {
1437
1698
  subgraph: sg.name,
1438
1699
  table: tableName,
1439
1700
  blockHeight,
@@ -1441,19 +1702,19 @@ async function handleSubgraphReorg(blockHeight, loadSubgraphDef) {
1441
1702
  });
1442
1703
  });
1443
1704
  }
1444
- logger5.info("Subgraph reorg cleanup done", {
1705
+ logger6.info("Subgraph reorg cleanup done", {
1445
1706
  subgraph: sg.name,
1446
1707
  blockHeight,
1447
1708
  tablesAffected: Object.keys(revertedByTable).length
1448
1709
  });
1449
1710
  const def = await loadSubgraphDef(sg);
1450
1711
  await processBlock(def, sg.name, blockHeight);
1451
- logger5.info("Subgraph reorg reprocessed", {
1712
+ logger6.info("Subgraph reorg reprocessed", {
1452
1713
  subgraph: sg.name,
1453
1714
  blockHeight
1454
1715
  });
1455
1716
  } catch (err) {
1456
- logger5.error("Subgraph reorg handling failed", {
1717
+ logger6.error("Subgraph reorg handling failed", {
1457
1718
  subgraph: sg.name,
1458
1719
  blockHeight,
1459
1720
  error: getErrorMessage2(err)
@@ -1465,5 +1726,5 @@ export {
1465
1726
  handleSubgraphReorg
1466
1727
  };
1467
1728
 
1468
- //# debugId=39C8DEB104D91A4564756E2164756E21
1729
+ //# debugId=BCA72BAE451BCFF564756E2164756E21
1469
1730
  //# sourceMappingURL=reorg.js.map