@secondlayer/shared 13.14.0 → 14.0.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/README.md CHANGED
@@ -37,4 +37,4 @@ DATABASE_URL=... bun run migrate
37
37
  | `@secondlayer/shared/crypto/secrets` | AES-GCM secret envelope |
38
38
  | `@secondlayer/shared/crypto/standard-webhooks` | Standard Webhooks signing |
39
39
  | `@secondlayer/shared/node` | Stacks node client |
40
- | `@secondlayer/shared/node/*` | Stacks node clients (hiro-client, local-client, hiro-pg-client, archive-client) |
40
+ | `@secondlayer/shared/node/*` | Stacks node clients (hiro-client, local-client, hiro-pg-client) |
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Hiro `DbTxStatus` → our tx status. Codes from stacks-blockchain-api
3
+ * src/datastore/common.ts: 1 success, -1 abort_by_response,
4
+ * -2 abort_by_post_condition. Anything else (0 pending, -3 problematic
5
+ * skipped, -10.. dropped) is not a mined result we can label, so throw
6
+ * rather than guess.
7
+ */
8
+ declare function mapTxStatus(status: number): string;
1
9
  declare class HiroPgClient {
2
10
  private sql;
3
11
  constructor(connectionUrl?: string);
@@ -18,4 +26,4 @@ declare class HiroPgClient {
18
26
  }): Promise<any[]>;
19
27
  close(): Promise<void>;
20
28
  }
21
- export { HiroPgClient };
29
+ export { HiroPgClient, mapTxStatus };
@@ -70,10 +70,12 @@ function mapTxStatus(status) {
70
70
  switch (status) {
71
71
  case 1:
72
72
  return "success";
73
- case 0:
73
+ case -1:
74
74
  return "abort_by_response";
75
- default:
75
+ case -2:
76
76
  return "abort_by_post_condition";
77
+ default:
78
+ throw new Error(`unmapped Hiro tx status ${status}`);
77
79
  }
78
80
  }
79
81
 
@@ -522,8 +524,9 @@ class HiroPgClient {
522
524
  }
523
525
  }
524
526
  export {
525
- HiroPgClient
527
+ HiroPgClient,
528
+ mapTxStatus
526
529
  };
527
530
 
528
- //# debugId=702841967A1BA35864756E2164756E21
531
+ //# debugId=73B240F5BDF2DABC64756E2164756E21
529
532
  //# sourceMappingURL=hiro-pg-client.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/node/hiro-pg-client.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Direct Postgres client for reading from a local Hiro API database.\n *\n * Bypasses the Hiro HTTP API entirely — queries the stacks_blockchain_api\n * schema directly. Orders of magnitude faster for bulk backfill since we\n * avoid per-tx HTTP round-trips and the API's slow UNION event queries.\n *\n * Expects HIRO_PG_URL env var pointing to the Hiro API database, e.g.:\n * postgres://secondlayer:pass@localhost:5432/stacks_blockchain_api\n */\n\nimport postgres from \"postgres\";\n\nconst SCHEMA = \"stacks_blockchain_api\";\n\n// Hiro DB stores bytea; we need 0x-prefixed hex strings\nfunction toHex(buf: Buffer | Uint8Array | null): string {\n\tif (!buf) return \"0x\";\n\treturn `0x${Buffer.from(buf).toString(\"hex\")}`;\n}\n\n// asset_event_type_id mapping: 1=transfer, 2=mint, 3=burn\nfunction stxEventType(assetTypeId: number): string {\n\tswitch (assetTypeId) {\n\t\tcase 1:\n\t\t\treturn \"stx_transfer_event\";\n\t\tcase 2:\n\t\t\treturn \"stx_mint_event\";\n\t\tcase 3:\n\t\t\treturn \"stx_burn_event\";\n\t\tdefault:\n\t\t\treturn \"stx_transfer_event\";\n\t}\n}\n\nfunction ftEventType(assetTypeId: number): string {\n\tswitch (assetTypeId) {\n\t\tcase 1:\n\t\t\treturn \"ft_transfer_event\";\n\t\tcase 2:\n\t\t\treturn \"ft_mint_event\";\n\t\tcase 3:\n\t\t\treturn \"ft_burn_event\";\n\t\tdefault:\n\t\t\treturn \"ft_transfer_event\";\n\t}\n}\n\nfunction nftEventType(assetTypeId: number): string {\n\tswitch (assetTypeId) {\n\t\tcase 1:\n\t\t\treturn \"nft_transfer_event\";\n\t\tcase 2:\n\t\t\treturn \"nft_mint_event\";\n\t\tcase 3:\n\t\t\treturn \"nft_burn_event\";\n\t\tdefault:\n\t\t\treturn \"nft_transfer_event\";\n\t}\n}\n\n// Hiro tx type_id mapping\nfunction mapTxTypeId(typeId: number): string {\n\tswitch (typeId) {\n\t\tcase 0:\n\t\t\treturn \"token_transfer\";\n\t\tcase 1:\n\t\t\treturn \"smart_contract\";\n\t\tcase 2:\n\t\t\treturn \"contract_call\";\n\t\tcase 3:\n\t\t\treturn \"poison_microblock\";\n\t\tcase 4:\n\t\t\treturn \"coinbase\";\n\t\tcase 5:\n\t\t\treturn \"coinbase\"; // coinbase-pay-to-alt\n\t\tcase 6:\n\t\t\treturn \"smart_contract\"; // versioned\n\t\tcase 7:\n\t\t\treturn \"tenure_change\";\n\t\tcase 8:\n\t\t\treturn \"coinbase\"; // nakamoto coinbase\n\t\tdefault:\n\t\t\treturn \"token_transfer\";\n\t}\n}\n\n// Hiro tx status mapping\nfunction mapTxStatus(status: number): string {\n\tswitch (status) {\n\t\tcase 1:\n\t\t\treturn \"success\";\n\t\tcase 0:\n\t\t\treturn \"abort_by_response\";\n\t\tdefault:\n\t\t\treturn \"abort_by_post_condition\";\n\t}\n}\n\ninterface BlockRow {\n\tblock_hash: Buffer;\n\tblock_height: number;\n\tindex_block_hash: Buffer;\n\tparent_block_hash: Buffer;\n\tparent_index_block_hash: Buffer;\n\tburn_block_hash: Buffer;\n\tburn_block_height: number;\n\tburn_block_time: number;\n\tblock_time: number;\n\tminer_txid: Buffer;\n}\n\ninterface TxRow {\n\ttx_id: Buffer;\n\ttx_index: number;\n\ttype_id: number;\n\tstatus: number;\n\tsender_address: string;\n\traw_tx: Buffer;\n\traw_result: Buffer | null;\n\tevent_count: number;\n\tcontract_call_contract_id: string | null;\n\tcontract_call_function_name: string | null;\n\tsmart_contract_contract_id: string | null;\n}\n\nexport class HiroPgClient {\n\tprivate sql: ReturnType<typeof postgres>;\n\n\tconstructor(connectionUrl?: string) {\n\t\tconst url = connectionUrl || process.env.HIRO_PG_URL;\n\t\tif (!url) throw new Error(\"HIRO_PG_URL is required for HiroPgClient\");\n\t\tthis.sql = postgres(url, {\n\t\t\tmax: 10,\n\t\t\tidle_timeout: 30,\n\t\t});\n\t}\n\n\tasync getChainTip(): Promise<number> {\n\t\tconst rows = await this.sql`\n SELECT MAX(block_height) as tip FROM ${this.sql(SCHEMA)}.blocks WHERE canonical = true\n `;\n\t\treturn Number(rows[0]?.tip ?? 0);\n\t}\n\n\t/**\n\t * Fetch a complete block by height directly from PG.\n\t * Returns data in the same NewBlockPayload shape the backfill expects.\n\t */\n\tasync getBlockForIndexer(\n\t\theight: number,\n\t\toptions?: { includeRawTx?: boolean },\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t): Promise<any | null> {\n\t\t// 1. Block metadata\n\t\tconst blocks = await this.sql<BlockRow[]>`\n SELECT block_hash, block_height, index_block_hash, parent_block_hash,\n parent_index_block_hash, burn_block_hash, burn_block_height,\n burn_block_time, block_time, miner_txid\n FROM ${this.sql(SCHEMA)}.blocks\n WHERE block_height = ${height} AND canonical = true\n LIMIT 1\n `;\n\t\tif (blocks.length === 0) return null;\n\t\tconst block = blocks[0];\n\n\t\t// 2. Transactions\n\t\tconst txs = await this.sql<TxRow[]>`\n SELECT tx_id, tx_index, type_id, status, sender_address, raw_tx, raw_result, event_count,\n contract_call_contract_id, contract_call_function_name, smart_contract_contract_id\n FROM ${this.sql(SCHEMA)}.txs\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY tx_index\n `;\n\n\t\tconst transactions = txs.map((tx) => {\n\t\t\tconst txType = mapTxTypeId(tx.type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst entry: any = {\n\t\t\t\ttxid: toHex(tx.tx_id),\n\t\t\t\traw_tx: options?.includeRawTx ? toHex(tx.raw_tx) : \"0x00\",\n\t\t\t\traw_result: tx.raw_result ? toHex(tx.raw_result) : undefined,\n\t\t\t\tstatus: mapTxStatus(tx.status),\n\t\t\t\ttx_index: tx.tx_index,\n\t\t\t\ttx_type: txType,\n\t\t\t\tsender_address: tx.sender_address,\n\t\t\t};\n\t\t\tif (txType === \"contract_call\" && tx.contract_call_contract_id) {\n\t\t\t\tentry.contract_call = {\n\t\t\t\t\tcontract_id: tx.contract_call_contract_id,\n\t\t\t\t\tfunction_name: tx.contract_call_function_name || \"\",\n\t\t\t\t};\n\t\t\t} else if (txType === \"smart_contract\" && tx.smart_contract_contract_id) {\n\t\t\t\tentry.smart_contract = {\n\t\t\t\t\tcontract_id: tx.smart_contract_contract_id,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn entry;\n\t\t});\n\n\t\t// 3. Events — query all event tables by block_height (fast, indexed)\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tconst events: any[] = [];\n\n\t\t// STX events\n\t\tconst stxEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, amount, sender, recipient, memo\n FROM ${this.sql(SCHEMA)}.stx_events\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of stxEvents) {\n\t\t\tconst type = stxEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tif (type === \"stx_transfer_event\") {\n\t\t\t\tevt.stx_transfer_event = {\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t\t...(e.memo ? { memo: toHex(e.memo) } : {}),\n\t\t\t\t};\n\t\t\t} else if (type === \"stx_mint_event\") {\n\t\t\t\tevt.stx_mint_event = {\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"stx_burn_event\") {\n\t\t\t\tevt.stx_burn_event = {\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t}\n\t\t\tevents.push(evt);\n\t\t}\n\n\t\t// STX lock events\n\t\tconst lockEvents = await this.sql`\n SELECT tx_id, event_index, locked_amount, unlock_height, locked_address\n FROM ${this.sql(SCHEMA)}.stx_lock_events\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of lockEvents) {\n\t\t\tevents.push({\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype: \"stx_lock_event\",\n\t\t\t\tstx_lock_event: {\n\t\t\t\t\tlocked_amount: String(e.locked_amount),\n\t\t\t\t\tunlock_height: String(e.unlock_height),\n\t\t\t\t\tlocked_address: e.locked_address,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// FT events\n\t\tconst ftEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, asset_identifier, amount, sender, recipient\n FROM ${this.sql(SCHEMA)}.ft_events\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of ftEvents) {\n\t\t\tconst type = ftEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tif (type === \"ft_transfer_event\") {\n\t\t\t\tevt.ft_transfer_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"ft_mint_event\") {\n\t\t\t\tevt.ft_mint_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"ft_burn_event\") {\n\t\t\t\tevt.ft_burn_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t}\n\t\t\tevents.push(evt);\n\t\t}\n\n\t\t// NFT events\n\t\tconst nftEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, asset_identifier, value, sender, recipient\n FROM ${this.sql(SCHEMA)}.nft_events\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of nftEvents) {\n\t\t\tconst type = nftEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tconst val = toHex(e.value);\n\t\t\tif (type === \"nft_transfer_event\") {\n\t\t\t\tevt.nft_transfer_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t} else if (type === \"nft_mint_event\") {\n\t\t\t\tevt.nft_mint_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t} else if (type === \"nft_burn_event\") {\n\t\t\t\tevt.nft_burn_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t}\n\t\t\tevents.push(evt);\n\t\t}\n\n\t\t// Contract log events\n\t\tconst logEvents = await this.sql`\n SELECT tx_id, event_index, contract_identifier, topic, value\n FROM ${this.sql(SCHEMA)}.contract_logs\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of logEvents) {\n\t\t\tevents.push({\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype: \"smart_contract_event\",\n\t\t\t\tsmart_contract_event: {\n\t\t\t\t\tcontract_identifier: e.contract_identifier,\n\t\t\t\t\ttopic: e.topic,\n\t\t\t\t\tvalue: toHex(e.value),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tblock_hash: toHex(block.block_hash),\n\t\t\tblock_height: block.block_height,\n\t\t\tindex_block_hash: toHex(block.index_block_hash),\n\t\t\tparent_block_hash: toHex(block.parent_block_hash),\n\t\t\tparent_index_block_hash: toHex(block.parent_index_block_hash),\n\t\t\tburn_block_hash: toHex(block.burn_block_hash),\n\t\t\tburn_block_height: block.burn_block_height,\n\t\t\tburn_block_timestamp: block.burn_block_time,\n\t\t\tminer_txid: toHex(block.miner_txid),\n\t\t\ttimestamp: block.block_time,\n\t\t\ttransactions,\n\t\t\tevents,\n\t\t};\n\t}\n\n\t/**\n\t * Fetch multiple blocks in bulk — 6 queries total instead of 6 per block.\n\t * Returns array of NewBlockPayload in height order.\n\t */\n\tasync getBlockBatch(\n\t\theights: number[],\n\t\toptions?: { includeRawTx?: boolean },\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t): Promise<any[]> {\n\t\tif (heights.length === 0) return [];\n\n\t\t// 1. All blocks in range\n\t\tconst blocks = await this.sql`\n SELECT block_hash, block_height, index_block_hash, parent_block_hash,\n parent_index_block_hash, burn_block_hash, burn_block_height,\n burn_block_time, block_time, miner_txid\n FROM ${this.sql(SCHEMA)}.blocks\n WHERE block_height = ANY(${heights}) AND canonical = true\n `;\n\n\t\tif (blocks.length === 0) return [];\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tconst blockMap = new Map<number, any>();\n\t\tfor (const b of blocks) {\n\t\t\tblockMap.set(b.block_height, { ...b, transactions: [], events: [] });\n\t\t}\n\n\t\t// 2. All transactions\n\t\tconst txs = await this.sql`\n SELECT tx_id, tx_index, type_id, status, sender_address, raw_tx, raw_result, event_count, block_height,\n contract_call_contract_id, contract_call_function_name, smart_contract_contract_id\n FROM ${this.sql(SCHEMA)}.txs\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n ORDER BY block_height, tx_index\n `;\n\t\tfor (const tx of txs) {\n\t\t\tconst block = blockMap.get(tx.block_height);\n\t\t\tif (!block) continue;\n\t\t\tconst txType = mapTxTypeId(tx.type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst entry: any = {\n\t\t\t\ttxid: toHex(tx.tx_id),\n\t\t\t\traw_tx: options?.includeRawTx ? toHex(tx.raw_tx) : \"0x00\",\n\t\t\t\traw_result: tx.raw_result ? toHex(tx.raw_result) : undefined,\n\t\t\t\tstatus: mapTxStatus(tx.status),\n\t\t\t\ttx_index: tx.tx_index,\n\t\t\t\ttx_type: txType,\n\t\t\t\tsender_address: tx.sender_address,\n\t\t\t};\n\t\t\tif (txType === \"contract_call\" && tx.contract_call_contract_id) {\n\t\t\t\tentry.contract_call = {\n\t\t\t\t\tcontract_id: tx.contract_call_contract_id,\n\t\t\t\t\tfunction_name: tx.contract_call_function_name || \"\",\n\t\t\t\t};\n\t\t\t} else if (txType === \"smart_contract\" && tx.smart_contract_contract_id) {\n\t\t\t\tentry.smart_contract = { contract_id: tx.smart_contract_contract_id };\n\t\t\t}\n\t\t\tblock.transactions.push(entry);\n\t\t}\n\n\t\t// 3. STX events\n\t\tconst stxEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, amount, sender, recipient, memo, block_height\n FROM ${this.sql(SCHEMA)}.stx_events\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of stxEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tconst type = stxEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tif (type === \"stx_transfer_event\") {\n\t\t\t\tevt.stx_transfer_event = {\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t\t...(e.memo ? { memo: toHex(e.memo) } : {}),\n\t\t\t\t};\n\t\t\t} else if (type === \"stx_mint_event\") {\n\t\t\t\tevt.stx_mint_event = {\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"stx_burn_event\") {\n\t\t\t\tevt.stx_burn_event = {\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t}\n\t\t\tblock.events.push(evt);\n\t\t}\n\n\t\t// 4. STX lock events\n\t\tconst lockEvents = await this.sql`\n SELECT tx_id, event_index, locked_amount, unlock_height, locked_address, block_height\n FROM ${this.sql(SCHEMA)}.stx_lock_events\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of lockEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tblock.events.push({\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype: \"stx_lock_event\",\n\t\t\t\tstx_lock_event: {\n\t\t\t\t\tlocked_amount: String(e.locked_amount),\n\t\t\t\t\tunlock_height: String(e.unlock_height),\n\t\t\t\t\tlocked_address: e.locked_address,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// 5. FT events\n\t\tconst ftEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, asset_identifier, amount, sender, recipient, block_height\n FROM ${this.sql(SCHEMA)}.ft_events\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of ftEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tconst type = ftEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tif (type === \"ft_transfer_event\") {\n\t\t\t\tevt.ft_transfer_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"ft_mint_event\") {\n\t\t\t\tevt.ft_mint_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"ft_burn_event\") {\n\t\t\t\tevt.ft_burn_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t}\n\t\t\tblock.events.push(evt);\n\t\t}\n\n\t\t// 6. NFT events\n\t\tconst nftEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, asset_identifier, value, sender, recipient, block_height\n FROM ${this.sql(SCHEMA)}.nft_events\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of nftEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tconst type = nftEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tconst val = toHex(e.value);\n\t\t\tif (type === \"nft_transfer_event\") {\n\t\t\t\tevt.nft_transfer_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t} else if (type === \"nft_mint_event\") {\n\t\t\t\tevt.nft_mint_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t} else if (type === \"nft_burn_event\") {\n\t\t\t\tevt.nft_burn_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t}\n\t\t\tblock.events.push(evt);\n\t\t}\n\n\t\t// 7. Contract log events\n\t\tconst logEvents = await this.sql`\n SELECT tx_id, event_index, contract_identifier, topic, value, block_height\n FROM ${this.sql(SCHEMA)}.contract_logs\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of logEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tblock.events.push({\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype: \"smart_contract_event\",\n\t\t\t\tsmart_contract_event: {\n\t\t\t\t\tcontract_identifier: e.contract_identifier,\n\t\t\t\t\ttopic: e.topic,\n\t\t\t\t\tvalue: toHex(e.value),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// Build results in height order\n\t\treturn heights\n\t\t\t.filter((h) => blockMap.has(h))\n\t\t\t.map((h) => {\n\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\t\t\tconst b = blockMap.get(h)!;\n\t\t\t\treturn {\n\t\t\t\t\tblock_hash: toHex(b.block_hash),\n\t\t\t\t\tblock_height: b.block_height,\n\t\t\t\t\tindex_block_hash: toHex(b.index_block_hash),\n\t\t\t\t\tparent_block_hash: toHex(b.parent_block_hash),\n\t\t\t\t\tparent_index_block_hash: toHex(b.parent_index_block_hash),\n\t\t\t\t\tburn_block_hash: toHex(b.burn_block_hash),\n\t\t\t\t\tburn_block_height: b.burn_block_height,\n\t\t\t\t\tburn_block_timestamp: b.burn_block_time,\n\t\t\t\t\tminer_txid: toHex(b.miner_txid),\n\t\t\t\t\ttimestamp: b.block_time,\n\t\t\t\t\ttransactions: b.transactions,\n\t\t\t\t\tevents: b.events,\n\t\t\t\t};\n\t\t\t});\n\t}\n\n\tasync close(): Promise<void> {\n\t\tawait this.sql.end();\n\t}\n}\n"
5
+ "/**\n * Direct Postgres client for reading from a local Hiro API database.\n *\n * Bypasses the Hiro HTTP API entirely — queries the stacks_blockchain_api\n * schema directly. Orders of magnitude faster for bulk backfill since we\n * avoid per-tx HTTP round-trips and the API's slow UNION event queries.\n *\n * Expects HIRO_PG_URL env var pointing to the Hiro API database, e.g.:\n * postgres://secondlayer:pass@localhost:5432/stacks_blockchain_api\n */\n\nimport postgres from \"postgres\";\n\nconst SCHEMA = \"stacks_blockchain_api\";\n\n// Hiro DB stores bytea; we need 0x-prefixed hex strings\nfunction toHex(buf: Buffer | Uint8Array | null): string {\n\tif (!buf) return \"0x\";\n\treturn `0x${Buffer.from(buf).toString(\"hex\")}`;\n}\n\n// asset_event_type_id mapping: 1=transfer, 2=mint, 3=burn\nfunction stxEventType(assetTypeId: number): string {\n\tswitch (assetTypeId) {\n\t\tcase 1:\n\t\t\treturn \"stx_transfer_event\";\n\t\tcase 2:\n\t\t\treturn \"stx_mint_event\";\n\t\tcase 3:\n\t\t\treturn \"stx_burn_event\";\n\t\tdefault:\n\t\t\treturn \"stx_transfer_event\";\n\t}\n}\n\nfunction ftEventType(assetTypeId: number): string {\n\tswitch (assetTypeId) {\n\t\tcase 1:\n\t\t\treturn \"ft_transfer_event\";\n\t\tcase 2:\n\t\t\treturn \"ft_mint_event\";\n\t\tcase 3:\n\t\t\treturn \"ft_burn_event\";\n\t\tdefault:\n\t\t\treturn \"ft_transfer_event\";\n\t}\n}\n\nfunction nftEventType(assetTypeId: number): string {\n\tswitch (assetTypeId) {\n\t\tcase 1:\n\t\t\treturn \"nft_transfer_event\";\n\t\tcase 2:\n\t\t\treturn \"nft_mint_event\";\n\t\tcase 3:\n\t\t\treturn \"nft_burn_event\";\n\t\tdefault:\n\t\t\treturn \"nft_transfer_event\";\n\t}\n}\n\n// Hiro tx type_id mapping\nfunction mapTxTypeId(typeId: number): string {\n\tswitch (typeId) {\n\t\tcase 0:\n\t\t\treturn \"token_transfer\";\n\t\tcase 1:\n\t\t\treturn \"smart_contract\";\n\t\tcase 2:\n\t\t\treturn \"contract_call\";\n\t\tcase 3:\n\t\t\treturn \"poison_microblock\";\n\t\tcase 4:\n\t\t\treturn \"coinbase\";\n\t\tcase 5:\n\t\t\treturn \"coinbase\"; // coinbase-pay-to-alt\n\t\tcase 6:\n\t\t\treturn \"smart_contract\"; // versioned\n\t\tcase 7:\n\t\t\treturn \"tenure_change\";\n\t\tcase 8:\n\t\t\treturn \"coinbase\"; // nakamoto coinbase\n\t\tdefault:\n\t\t\treturn \"token_transfer\";\n\t}\n}\n\n/**\n * Hiro `DbTxStatus` → our tx status. Codes from stacks-blockchain-api\n * src/datastore/common.ts: 1 success, -1 abort_by_response,\n * -2 abort_by_post_condition. Anything else (0 pending, -3 problematic\n * skipped, -10.. dropped) is not a mined result we can label, so throw\n * rather than guess.\n */\nexport function mapTxStatus(status: number): string {\n\tswitch (status) {\n\t\tcase 1:\n\t\t\treturn \"success\";\n\t\tcase -1:\n\t\t\treturn \"abort_by_response\";\n\t\tcase -2:\n\t\t\treturn \"abort_by_post_condition\";\n\t\tdefault:\n\t\t\tthrow new Error(`unmapped Hiro tx status ${status}`);\n\t}\n}\n\ninterface BlockRow {\n\tblock_hash: Buffer;\n\tblock_height: number;\n\tindex_block_hash: Buffer;\n\tparent_block_hash: Buffer;\n\tparent_index_block_hash: Buffer;\n\tburn_block_hash: Buffer;\n\tburn_block_height: number;\n\tburn_block_time: number;\n\tblock_time: number;\n\tminer_txid: Buffer;\n}\n\ninterface TxRow {\n\ttx_id: Buffer;\n\ttx_index: number;\n\ttype_id: number;\n\tstatus: number;\n\tsender_address: string;\n\traw_tx: Buffer;\n\traw_result: Buffer | null;\n\tevent_count: number;\n\tcontract_call_contract_id: string | null;\n\tcontract_call_function_name: string | null;\n\tsmart_contract_contract_id: string | null;\n}\n\nexport class HiroPgClient {\n\tprivate sql: ReturnType<typeof postgres>;\n\n\tconstructor(connectionUrl?: string) {\n\t\tconst url = connectionUrl || process.env.HIRO_PG_URL;\n\t\tif (!url) throw new Error(\"HIRO_PG_URL is required for HiroPgClient\");\n\t\tthis.sql = postgres(url, {\n\t\t\tmax: 10,\n\t\t\tidle_timeout: 30,\n\t\t});\n\t}\n\n\tasync getChainTip(): Promise<number> {\n\t\tconst rows = await this.sql`\n SELECT MAX(block_height) as tip FROM ${this.sql(SCHEMA)}.blocks WHERE canonical = true\n `;\n\t\treturn Number(rows[0]?.tip ?? 0);\n\t}\n\n\t/**\n\t * Fetch a complete block by height directly from PG.\n\t * Returns data in the same NewBlockPayload shape the backfill expects.\n\t */\n\tasync getBlockForIndexer(\n\t\theight: number,\n\t\toptions?: { includeRawTx?: boolean },\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t): Promise<any | null> {\n\t\t// 1. Block metadata\n\t\tconst blocks = await this.sql<BlockRow[]>`\n SELECT block_hash, block_height, index_block_hash, parent_block_hash,\n parent_index_block_hash, burn_block_hash, burn_block_height,\n burn_block_time, block_time, miner_txid\n FROM ${this.sql(SCHEMA)}.blocks\n WHERE block_height = ${height} AND canonical = true\n LIMIT 1\n `;\n\t\tif (blocks.length === 0) return null;\n\t\tconst block = blocks[0];\n\n\t\t// 2. Transactions\n\t\tconst txs = await this.sql<TxRow[]>`\n SELECT tx_id, tx_index, type_id, status, sender_address, raw_tx, raw_result, event_count,\n contract_call_contract_id, contract_call_function_name, smart_contract_contract_id\n FROM ${this.sql(SCHEMA)}.txs\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY tx_index\n `;\n\n\t\tconst transactions = txs.map((tx) => {\n\t\t\tconst txType = mapTxTypeId(tx.type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst entry: any = {\n\t\t\t\ttxid: toHex(tx.tx_id),\n\t\t\t\traw_tx: options?.includeRawTx ? toHex(tx.raw_tx) : \"0x00\",\n\t\t\t\traw_result: tx.raw_result ? toHex(tx.raw_result) : undefined,\n\t\t\t\tstatus: mapTxStatus(tx.status),\n\t\t\t\ttx_index: tx.tx_index,\n\t\t\t\ttx_type: txType,\n\t\t\t\tsender_address: tx.sender_address,\n\t\t\t};\n\t\t\tif (txType === \"contract_call\" && tx.contract_call_contract_id) {\n\t\t\t\tentry.contract_call = {\n\t\t\t\t\tcontract_id: tx.contract_call_contract_id,\n\t\t\t\t\tfunction_name: tx.contract_call_function_name || \"\",\n\t\t\t\t};\n\t\t\t} else if (txType === \"smart_contract\" && tx.smart_contract_contract_id) {\n\t\t\t\tentry.smart_contract = {\n\t\t\t\t\tcontract_id: tx.smart_contract_contract_id,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn entry;\n\t\t});\n\n\t\t// 3. Events — query all event tables by block_height (fast, indexed)\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tconst events: any[] = [];\n\n\t\t// STX events\n\t\tconst stxEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, amount, sender, recipient, memo\n FROM ${this.sql(SCHEMA)}.stx_events\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of stxEvents) {\n\t\t\tconst type = stxEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tif (type === \"stx_transfer_event\") {\n\t\t\t\tevt.stx_transfer_event = {\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t\t...(e.memo ? { memo: toHex(e.memo) } : {}),\n\t\t\t\t};\n\t\t\t} else if (type === \"stx_mint_event\") {\n\t\t\t\tevt.stx_mint_event = {\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"stx_burn_event\") {\n\t\t\t\tevt.stx_burn_event = {\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t}\n\t\t\tevents.push(evt);\n\t\t}\n\n\t\t// STX lock events\n\t\tconst lockEvents = await this.sql`\n SELECT tx_id, event_index, locked_amount, unlock_height, locked_address\n FROM ${this.sql(SCHEMA)}.stx_lock_events\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of lockEvents) {\n\t\t\tevents.push({\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype: \"stx_lock_event\",\n\t\t\t\tstx_lock_event: {\n\t\t\t\t\tlocked_amount: String(e.locked_amount),\n\t\t\t\t\tunlock_height: String(e.unlock_height),\n\t\t\t\t\tlocked_address: e.locked_address,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// FT events\n\t\tconst ftEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, asset_identifier, amount, sender, recipient\n FROM ${this.sql(SCHEMA)}.ft_events\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of ftEvents) {\n\t\t\tconst type = ftEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tif (type === \"ft_transfer_event\") {\n\t\t\t\tevt.ft_transfer_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"ft_mint_event\") {\n\t\t\t\tevt.ft_mint_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"ft_burn_event\") {\n\t\t\t\tevt.ft_burn_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t}\n\t\t\tevents.push(evt);\n\t\t}\n\n\t\t// NFT events\n\t\tconst nftEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, asset_identifier, value, sender, recipient\n FROM ${this.sql(SCHEMA)}.nft_events\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of nftEvents) {\n\t\t\tconst type = nftEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tconst val = toHex(e.value);\n\t\t\tif (type === \"nft_transfer_event\") {\n\t\t\t\tevt.nft_transfer_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t} else if (type === \"nft_mint_event\") {\n\t\t\t\tevt.nft_mint_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t} else if (type === \"nft_burn_event\") {\n\t\t\t\tevt.nft_burn_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t}\n\t\t\tevents.push(evt);\n\t\t}\n\n\t\t// Contract log events\n\t\tconst logEvents = await this.sql`\n SELECT tx_id, event_index, contract_identifier, topic, value\n FROM ${this.sql(SCHEMA)}.contract_logs\n WHERE block_height = ${height} AND canonical = true AND microblock_canonical = true\n ORDER BY event_index\n `;\n\t\tfor (const e of logEvents) {\n\t\t\tevents.push({\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype: \"smart_contract_event\",\n\t\t\t\tsmart_contract_event: {\n\t\t\t\t\tcontract_identifier: e.contract_identifier,\n\t\t\t\t\ttopic: e.topic,\n\t\t\t\t\tvalue: toHex(e.value),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tblock_hash: toHex(block.block_hash),\n\t\t\tblock_height: block.block_height,\n\t\t\tindex_block_hash: toHex(block.index_block_hash),\n\t\t\tparent_block_hash: toHex(block.parent_block_hash),\n\t\t\tparent_index_block_hash: toHex(block.parent_index_block_hash),\n\t\t\tburn_block_hash: toHex(block.burn_block_hash),\n\t\t\tburn_block_height: block.burn_block_height,\n\t\t\tburn_block_timestamp: block.burn_block_time,\n\t\t\tminer_txid: toHex(block.miner_txid),\n\t\t\ttimestamp: block.block_time,\n\t\t\ttransactions,\n\t\t\tevents,\n\t\t};\n\t}\n\n\t/**\n\t * Fetch multiple blocks in bulk — 6 queries total instead of 6 per block.\n\t * Returns array of NewBlockPayload in height order.\n\t */\n\tasync getBlockBatch(\n\t\theights: number[],\n\t\toptions?: { includeRawTx?: boolean },\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t): Promise<any[]> {\n\t\tif (heights.length === 0) return [];\n\n\t\t// 1. All blocks in range\n\t\tconst blocks = await this.sql`\n SELECT block_hash, block_height, index_block_hash, parent_block_hash,\n parent_index_block_hash, burn_block_hash, burn_block_height,\n burn_block_time, block_time, miner_txid\n FROM ${this.sql(SCHEMA)}.blocks\n WHERE block_height = ANY(${heights}) AND canonical = true\n `;\n\n\t\tif (blocks.length === 0) return [];\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tconst blockMap = new Map<number, any>();\n\t\tfor (const b of blocks) {\n\t\t\tblockMap.set(b.block_height, { ...b, transactions: [], events: [] });\n\t\t}\n\n\t\t// 2. All transactions\n\t\tconst txs = await this.sql`\n SELECT tx_id, tx_index, type_id, status, sender_address, raw_tx, raw_result, event_count, block_height,\n contract_call_contract_id, contract_call_function_name, smart_contract_contract_id\n FROM ${this.sql(SCHEMA)}.txs\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n ORDER BY block_height, tx_index\n `;\n\t\tfor (const tx of txs) {\n\t\t\tconst block = blockMap.get(tx.block_height);\n\t\t\tif (!block) continue;\n\t\t\tconst txType = mapTxTypeId(tx.type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst entry: any = {\n\t\t\t\ttxid: toHex(tx.tx_id),\n\t\t\t\traw_tx: options?.includeRawTx ? toHex(tx.raw_tx) : \"0x00\",\n\t\t\t\traw_result: tx.raw_result ? toHex(tx.raw_result) : undefined,\n\t\t\t\tstatus: mapTxStatus(tx.status),\n\t\t\t\ttx_index: tx.tx_index,\n\t\t\t\ttx_type: txType,\n\t\t\t\tsender_address: tx.sender_address,\n\t\t\t};\n\t\t\tif (txType === \"contract_call\" && tx.contract_call_contract_id) {\n\t\t\t\tentry.contract_call = {\n\t\t\t\t\tcontract_id: tx.contract_call_contract_id,\n\t\t\t\t\tfunction_name: tx.contract_call_function_name || \"\",\n\t\t\t\t};\n\t\t\t} else if (txType === \"smart_contract\" && tx.smart_contract_contract_id) {\n\t\t\t\tentry.smart_contract = { contract_id: tx.smart_contract_contract_id };\n\t\t\t}\n\t\t\tblock.transactions.push(entry);\n\t\t}\n\n\t\t// 3. STX events\n\t\tconst stxEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, amount, sender, recipient, memo, block_height\n FROM ${this.sql(SCHEMA)}.stx_events\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of stxEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tconst type = stxEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tif (type === \"stx_transfer_event\") {\n\t\t\t\tevt.stx_transfer_event = {\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t\t...(e.memo ? { memo: toHex(e.memo) } : {}),\n\t\t\t\t};\n\t\t\t} else if (type === \"stx_mint_event\") {\n\t\t\t\tevt.stx_mint_event = {\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"stx_burn_event\") {\n\t\t\t\tevt.stx_burn_event = {\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t}\n\t\t\tblock.events.push(evt);\n\t\t}\n\n\t\t// 4. STX lock events\n\t\tconst lockEvents = await this.sql`\n SELECT tx_id, event_index, locked_amount, unlock_height, locked_address, block_height\n FROM ${this.sql(SCHEMA)}.stx_lock_events\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of lockEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tblock.events.push({\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype: \"stx_lock_event\",\n\t\t\t\tstx_lock_event: {\n\t\t\t\t\tlocked_amount: String(e.locked_amount),\n\t\t\t\t\tunlock_height: String(e.unlock_height),\n\t\t\t\t\tlocked_address: e.locked_address,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// 5. FT events\n\t\tconst ftEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, asset_identifier, amount, sender, recipient, block_height\n FROM ${this.sql(SCHEMA)}.ft_events\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of ftEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tconst type = ftEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tif (type === \"ft_transfer_event\") {\n\t\t\t\tevt.ft_transfer_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"ft_mint_event\") {\n\t\t\t\tevt.ft_mint_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t} else if (type === \"ft_burn_event\") {\n\t\t\t\tevt.ft_burn_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tamount: String(e.amount),\n\t\t\t\t};\n\t\t\t}\n\t\t\tblock.events.push(evt);\n\t\t}\n\n\t\t// 6. NFT events\n\t\tconst nftEvents = await this.sql`\n SELECT tx_id, event_index, asset_event_type_id, asset_identifier, value, sender, recipient, block_height\n FROM ${this.sql(SCHEMA)}.nft_events\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of nftEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tconst type = nftEventType(e.asset_event_type_id);\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\t\tconst evt: any = {\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype,\n\t\t\t};\n\t\t\tconst val = toHex(e.value);\n\t\t\tif (type === \"nft_transfer_event\") {\n\t\t\t\tevt.nft_transfer_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t} else if (type === \"nft_mint_event\") {\n\t\t\t\tevt.nft_mint_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\trecipient: e.recipient || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t} else if (type === \"nft_burn_event\") {\n\t\t\t\tevt.nft_burn_event = {\n\t\t\t\t\tasset_identifier: e.asset_identifier,\n\t\t\t\t\tsender: e.sender || \"\",\n\t\t\t\t\tvalue: val,\n\t\t\t\t};\n\t\t\t}\n\t\t\tblock.events.push(evt);\n\t\t}\n\n\t\t// 7. Contract log events\n\t\tconst logEvents = await this.sql`\n SELECT tx_id, event_index, contract_identifier, topic, value, block_height\n FROM ${this.sql(SCHEMA)}.contract_logs\n WHERE block_height = ANY(${heights}) AND canonical = true AND microblock_canonical = true\n `;\n\t\tfor (const e of logEvents) {\n\t\t\tconst block = blockMap.get(e.block_height);\n\t\t\tif (!block) continue;\n\t\t\tblock.events.push({\n\t\t\t\ttxid: toHex(e.tx_id),\n\t\t\t\tevent_index: e.event_index,\n\t\t\t\tcommitted: true,\n\t\t\t\ttype: \"smart_contract_event\",\n\t\t\t\tsmart_contract_event: {\n\t\t\t\t\tcontract_identifier: e.contract_identifier,\n\t\t\t\t\ttopic: e.topic,\n\t\t\t\t\tvalue: toHex(e.value),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// Build results in height order\n\t\treturn heights\n\t\t\t.filter((h) => blockMap.has(h))\n\t\t\t.map((h) => {\n\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\t\t\tconst b = blockMap.get(h)!;\n\t\t\t\treturn {\n\t\t\t\t\tblock_hash: toHex(b.block_hash),\n\t\t\t\t\tblock_height: b.block_height,\n\t\t\t\t\tindex_block_hash: toHex(b.index_block_hash),\n\t\t\t\t\tparent_block_hash: toHex(b.parent_block_hash),\n\t\t\t\t\tparent_index_block_hash: toHex(b.parent_index_block_hash),\n\t\t\t\t\tburn_block_hash: toHex(b.burn_block_hash),\n\t\t\t\t\tburn_block_height: b.burn_block_height,\n\t\t\t\t\tburn_block_timestamp: b.burn_block_time,\n\t\t\t\t\tminer_txid: toHex(b.miner_txid),\n\t\t\t\t\ttimestamp: b.block_time,\n\t\t\t\t\ttransactions: b.transactions,\n\t\t\t\t\tevents: b.events,\n\t\t\t\t};\n\t\t\t});\n\t}\n\n\tasync close(): Promise<void> {\n\t\tawait this.sql.end();\n\t}\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "702841967A1BA35864756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "73B240F5BDF2DABC64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -50,17 +50,18 @@ class LocalClient {
50
50
  const vmRows = await db.selectFrom("vm_events").select(["tx_id", "ordinal", "type", "data"]).where("block_height", "=", height).orderBy("ordinal", "asc").execute();
51
51
  const vm_events = reconstructVmEventsForReplay(vmRows);
52
52
  const state_writes = await db.selectFrom("state_writes").select(["tx_index", "ordinal", "key", "value_hex"]).where("block_height", "=", height).orderBy("ordinal", "asc").execute();
53
+ const timestamp = Number(block.timestamp);
53
54
  return {
54
55
  block_hash: block.hash,
55
- block_height: block.height,
56
- index_block_hash: "",
56
+ block_height: Number(block.height),
57
+ index_block_hash: block.index_block_hash ?? "",
57
58
  parent_block_hash: block.parent_hash,
58
59
  parent_index_block_hash: "",
59
- burn_block_hash: "",
60
- burn_block_height: block.burn_block_height,
61
- burn_block_timestamp: block.timestamp,
60
+ burn_block_hash: block.burn_block_hash ?? "",
61
+ burn_block_height: Number(block.burn_block_height),
62
+ burn_block_timestamp: timestamp,
62
63
  miner_txid: "",
63
- timestamp: block.timestamp,
64
+ timestamp,
64
65
  transactions: transactions.map((tx) => {
65
66
  const entry = {
66
67
  txid: tx.tx_id,
@@ -111,5 +112,5 @@ export {
111
112
  reconstructVmEventsForReplay
112
113
  };
113
114
 
114
- //# debugId=0E6E16C36A8DD8AA64756E2164756E21
115
+ //# debugId=85374ECE2AE4234264756E2164756E21
115
116
  //# sourceMappingURL=local-client.js.map
@@ -3,9 +3,9 @@
3
3
  "sources": ["../src/event-types.ts", "../src/node/local-client.ts"],
4
4
  "sourcesContent": [
5
5
  "// The decoded event-type vocabulary — canonical home is now\n// `@secondlayer/stacks/filters` (the LEAF of the dependency graph: shared\n// depends on stacks, never the reverse, so no package can drift a private\n// copy upward). Re-exported here so every existing `@secondlayer/shared`\n// import keeps working unchanged.\nimport {\n\tDECODED_EVENT_TYPES,\n\ttype VM_NODE_TO_STORED_TYPE,\n} from \"@secondlayer/stacks/filters\";\nimport type { DecodedEventType } from \"@secondlayer/stacks/filters\";\n\nexport {\n\tDECODED_EVENT_TYPES,\n\tVM_EVENT_TYPES,\n\tVM_NODE_TO_STORED_TYPE,\n\ttype DecodedEventType,\n\ttype VmEventType,\n\ttype VmNodeEventType,\n} from \"@secondlayer/stacks/filters\";\n\ntype VmStoredToNode = {\n\t[K in keyof typeof VM_NODE_TO_STORED_TYPE as (typeof VM_NODE_TO_STORED_TYPE)[K]]: K;\n};\n\n/** Inverse of {@link VM_NODE_TO_STORED_TYPE}. Replay reconstruction emits the\n * node-shaped `/new_block.vm_events` body, so ingest can re-parse it. */\nexport const VM_STORED_TO_NODE_TYPE: VmStoredToNode = {\n\tnested_contract_call: \"contract_call_event\",\n\tvar_set: \"var_set_event\",\n\tmap_set: \"map_set_event\",\n\tmap_insert: \"map_insert_event\",\n\tmap_delete: \"map_delete_event\",\n};\n\n/** Alias kept for the Streams surface (identical to {@link DECODED_EVENT_TYPES}).\n * Explicit type annotation required — isolatedDeclarations emits `unknown` for\n * a bare const-to-const alias. */\nexport const STREAMS_EVENT_TYPES: typeof DECODED_EVENT_TYPES =\n\tDECODED_EVENT_TYPES;\nexport type StreamsEventType = DecodedEventType;\n\n/**\n * Wire vocabulary for `chain=bitcoin` Streams events (plan 059) — Runes\n * activity, mirroring the Stacks `ft_*` family. Same four kinds as 058's\n * `RuneEventKind` (`packages/api/src/index/runes.ts`), duplicated here as the\n * canonical shared const rather than imported from the api package (shared\n * never depends on api); the DB `kind` column (`etch|mint|transfer|burn`) is\n * mapped at the edge in both readers.\n */\nexport const RUNE_EVENT_TYPES = [\n\t\"rune_etch\",\n\t\"rune_mint\",\n\t\"rune_transfer\",\n\t\"rune_burn\",\n] as const;\nexport type RuneEventType = (typeof RUNE_EVENT_TYPES)[number];\n",
6
- "/**\n * Local replay client — reconstructs NewBlockPayload from our own Postgres.\n *\n * Used for re-orgs, reprocessing, and self-serve replay after genesis sync.\n * Eliminates need for self-hosted Hiro API for blocks already in our DB.\n */\n\nimport type { Kysely } from \"kysely\";\nimport type { Database } from \"../db/types.ts\";\nimport {\n\tVM_STORED_TO_NODE_TYPE,\n\ttype VmEventType,\n\ttype VmNodeEventType,\n} from \"../event-types.ts\";\n\n/** Matches the NewBlockPayload shape expected by the indexer's /new_block endpoint */\nexport interface ReplayBlockPayload {\n\tblock_hash: string;\n\tblock_height: number;\n\tindex_block_hash: string;\n\tparent_block_hash: string;\n\tparent_index_block_hash: string;\n\tburn_block_hash: string;\n\tburn_block_height: number;\n\tburn_block_timestamp: number;\n\tminer_txid: string;\n\ttimestamp: number;\n\ttransactions: ReplayTransactionPayload[];\n\tevents: ReplayEventPayload[];\n\t/** Node-shaped opt-in traces. Omitted when the height has none. */\n\tvm_events?: ReplayVmEventPayload[];\n\t/** Node-shaped storage-layer writes. Omitted when the height has none. */\n\tstate_writes?: ReplayStateWritePayload[];\n}\n\n/** Node-shaped `/new_block.state_writes[]` row; stored 1:1. */\nexport interface ReplayStateWritePayload {\n\ttx_index: number | null;\n\tordinal: number;\n\tkey: string;\n\tvalue_hex: string;\n}\n\ninterface ReplayTransactionPayload {\n\ttxid: string;\n\traw_tx: string;\n\tstatus: string;\n\ttx_index: number;\n\ttx_type?: string;\n\tsender_address?: string;\n\traw_result?: string | null;\n\tcontract_call?: { function_args: string[] };\n}\n\ninterface ReplayEventPayload {\n\ttxid: string;\n\tevent_index: number;\n\tcommitted: boolean;\n\ttype: string;\n\t[key: string]: unknown;\n}\n\n/** Node-shaped `/new_block.vm_events[]` row. Body lives under the node type\n * key so `parseVmEvent` can re-ingest it on fork restoration. Array order\n * is the index (sorted by stored `ordinal`). */\nexport interface ReplayVmEventPayload {\n\ttxid: string;\n\tcommitted: boolean;\n\ttype: VmNodeEventType;\n\t[key: string]: unknown;\n}\n\n/** Rebuild node-shaped vm traces from stored rows. Sorted so re-ingest\n * array order matches the stored second clock. */\nexport function reconstructVmEventsForReplay(\n\trows: ReadonlyArray<{\n\t\ttx_id: string;\n\t\tordinal: number | string;\n\t\ttype: string;\n\t\tdata: unknown;\n\t}>,\n): ReplayVmEventPayload[] {\n\tconst sorted = [...rows].sort(\n\t\t(a, b) => Number(a.ordinal) - Number(b.ordinal),\n\t);\n\tconst out: ReplayVmEventPayload[] = [];\n\tfor (const row of sorted) {\n\t\tconst nodeType =\n\t\t\trow.type in VM_STORED_TO_NODE_TYPE\n\t\t\t\t? VM_STORED_TO_NODE_TYPE[row.type as VmEventType]\n\t\t\t\t: undefined;\n\t\tif (!nodeType) continue;\n\t\tout.push({\n\t\t\ttxid: row.tx_id,\n\t\t\tcommitted: true,\n\t\t\ttype: nodeType,\n\t\t\t[nodeType]: row.data,\n\t\t});\n\t}\n\treturn out;\n}\n\nexport class LocalClient {\n\t/**\n\t * Reconstruct a NewBlockPayload from local DB for replay.\n\t * Returns null if block not found.\n\t */\n\tasync getBlockForReplay(\n\t\tdb: Kysely<Database>,\n\t\theight: number,\n\t): Promise<ReplayBlockPayload | null> {\n\t\tconst block = await db\n\t\t\t.selectFrom(\"blocks\")\n\t\t\t.selectAll()\n\t\t\t.where(\"height\", \"=\", height)\n\t\t\t.where(\"canonical\", \"=\", true)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!block) return null;\n\n\t\tconst transactions = await db\n\t\t\t.selectFrom(\"transactions\")\n\t\t\t.selectAll()\n\t\t\t.where(\"block_height\", \"=\", height)\n\t\t\t.orderBy(\"tx_index\", \"asc\")\n\t\t\t.execute();\n\n\t\tconst events = await db\n\t\t\t.selectFrom(\"events\")\n\t\t\t.selectAll()\n\t\t\t.where(\"block_height\", \"=\", height)\n\t\t\t.orderBy(\"event_index\", \"asc\")\n\t\t\t.execute();\n\n\t\tconst vmRows = await db\n\t\t\t.selectFrom(\"vm_events\")\n\t\t\t.select([\"tx_id\", \"ordinal\", \"type\", \"data\"])\n\t\t\t.where(\"block_height\", \"=\", height)\n\t\t\t.orderBy(\"ordinal\", \"asc\")\n\t\t\t.execute();\n\t\tconst vm_events = reconstructVmEventsForReplay(vmRows);\n\n\t\tconst state_writes: ReplayStateWritePayload[] = await db\n\t\t\t.selectFrom(\"state_writes\")\n\t\t\t.select([\"tx_index\", \"ordinal\", \"key\", \"value_hex\"])\n\t\t\t.where(\"block_height\", \"=\", height)\n\t\t\t.orderBy(\"ordinal\", \"asc\")\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tblock_hash: block.hash,\n\t\t\tblock_height: block.height,\n\t\t\t// Not stored in our DB — not needed by parser/deliveries\n\t\t\tindex_block_hash: \"\",\n\t\t\tparent_block_hash: block.parent_hash,\n\t\t\tparent_index_block_hash: \"\",\n\t\t\tburn_block_hash: \"\",\n\t\t\tburn_block_height: block.burn_block_height,\n\t\t\tburn_block_timestamp: block.timestamp,\n\t\t\tminer_txid: \"\",\n\t\t\ttimestamp: block.timestamp,\n\t\t\ttransactions: transactions.map((tx) => {\n\t\t\t\tconst entry: ReplayTransactionPayload = {\n\t\t\t\t\ttxid: tx.tx_id,\n\t\t\t\t\traw_tx: tx.raw_tx,\n\t\t\t\t\tstatus: tx.status,\n\t\t\t\t\ttx_index: tx.tx_index,\n\t\t\t\t\ttx_type: tx.type,\n\t\t\t\t\tsender_address: tx.sender,\n\t\t\t\t\traw_result: tx.raw_result ?? null,\n\t\t\t\t};\n\t\t\t\t// Include function_args if stored (for contract_call txs)\n\t\t\t\tif (tx.function_args) {\n\t\t\t\t\tconst args =\n\t\t\t\t\t\ttypeof tx.function_args === \"string\"\n\t\t\t\t\t\t\t? JSON.parse(tx.function_args)\n\t\t\t\t\t\t\t: tx.function_args;\n\t\t\t\t\tif (Array.isArray(args)) {\n\t\t\t\t\t\tentry.contract_call = { function_args: args };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn entry;\n\t\t\t}),\n\t\t\tevents: events.map((evt) => {\n\t\t\t\tconst data = (evt.data ?? {}) as Record<string, unknown>;\n\t\t\t\tconst eventType = evt.type;\n\n\t\t\t\t// Reconstruct the flat event structure the indexer expects:\n\t\t\t\t// { txid, event_index, committed, type, [type_key]: data }\n\t\t\t\tconst payload: ReplayEventPayload = {\n\t\t\t\t\ttxid: evt.tx_id,\n\t\t\t\t\tevent_index: evt.event_index,\n\t\t\t\t\tcommitted: true,\n\t\t\t\t\ttype: eventType,\n\t\t\t\t};\n\n\t\t\t\t// Attach event-specific data under the correct key\n\t\t\t\tif (eventType && data) {\n\t\t\t\t\tpayload[eventType] = data;\n\t\t\t\t}\n\n\t\t\t\treturn payload;\n\t\t\t}),\n\t\t\t...(vm_events.length > 0 ? { vm_events } : {}),\n\t\t\t...(state_writes.length > 0 ? { state_writes } : {}),\n\t\t};\n\t}\n\n\t/** Get highest block height in local DB */\n\tasync getChainTip(db: Kysely<Database>): Promise<number> {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"blocks\")\n\t\t\t.select((eb) => eb.fn.max(\"height\").as(\"max_height\"))\n\t\t\t.where(\"canonical\", \"=\", true)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(row?.max_height ?? 0);\n\t}\n\n\t/** Check if a specific block height exists in local DB */\n\tasync hasBlock(db: Kysely<Database>, height: number): Promise<boolean> {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"blocks\")\n\t\t\t.select(\"height\")\n\t\t\t.where(\"height\", \"=\", height)\n\t\t\t.where(\"canonical\", \"=\", true)\n\t\t\t.executeTakeFirst();\n\t\treturn !!row;\n\t}\n}\n"
6
+ "/**\n * Local replay client — reconstructs NewBlockPayload from our own Postgres.\n *\n * Used for re-orgs, reprocessing, and self-serve replay after genesis sync.\n * Eliminates need for self-hosted Hiro API for blocks already in our DB.\n */\n\nimport type { Kysely } from \"kysely\";\nimport type { Database } from \"../db/types.ts\";\nimport {\n\tVM_STORED_TO_NODE_TYPE,\n\ttype VmEventType,\n\ttype VmNodeEventType,\n} from \"../event-types.ts\";\n\n/** Matches the NewBlockPayload shape expected by the indexer's /new_block endpoint */\nexport interface ReplayBlockPayload {\n\tblock_hash: string;\n\tblock_height: number;\n\tindex_block_hash: string;\n\tparent_block_hash: string;\n\tparent_index_block_hash: string;\n\tburn_block_hash: string;\n\tburn_block_height: number;\n\tburn_block_timestamp: number;\n\tminer_txid: string;\n\ttimestamp: number;\n\ttransactions: ReplayTransactionPayload[];\n\tevents: ReplayEventPayload[];\n\t/** Node-shaped opt-in traces. Omitted when the height has none. */\n\tvm_events?: ReplayVmEventPayload[];\n\t/** Node-shaped storage-layer writes. Omitted when the height has none. */\n\tstate_writes?: ReplayStateWritePayload[];\n}\n\n/** Node-shaped `/new_block.state_writes[]` row; stored 1:1. */\nexport interface ReplayStateWritePayload {\n\ttx_index: number | null;\n\tordinal: number;\n\tkey: string;\n\tvalue_hex: string;\n}\n\ninterface ReplayTransactionPayload {\n\ttxid: string;\n\traw_tx: string;\n\tstatus: string;\n\ttx_index: number;\n\ttx_type?: string;\n\tsender_address?: string;\n\traw_result?: string | null;\n\tcontract_call?: { function_args: string[] };\n}\n\ninterface ReplayEventPayload {\n\ttxid: string;\n\tevent_index: number;\n\tcommitted: boolean;\n\ttype: string;\n\t[key: string]: unknown;\n}\n\n/** Node-shaped `/new_block.vm_events[]` row. Body lives under the node type\n * key so `parseVmEvent` can re-ingest it on fork restoration. Array order\n * is the index (sorted by stored `ordinal`). */\nexport interface ReplayVmEventPayload {\n\ttxid: string;\n\tcommitted: boolean;\n\ttype: VmNodeEventType;\n\t[key: string]: unknown;\n}\n\n/** Rebuild node-shaped vm traces from stored rows. Sorted so re-ingest\n * array order matches the stored second clock. */\nexport function reconstructVmEventsForReplay(\n\trows: ReadonlyArray<{\n\t\ttx_id: string;\n\t\tordinal: number | string;\n\t\ttype: string;\n\t\tdata: unknown;\n\t}>,\n): ReplayVmEventPayload[] {\n\tconst sorted = [...rows].sort(\n\t\t(a, b) => Number(a.ordinal) - Number(b.ordinal),\n\t);\n\tconst out: ReplayVmEventPayload[] = [];\n\tfor (const row of sorted) {\n\t\tconst nodeType =\n\t\t\trow.type in VM_STORED_TO_NODE_TYPE\n\t\t\t\t? VM_STORED_TO_NODE_TYPE[row.type as VmEventType]\n\t\t\t\t: undefined;\n\t\tif (!nodeType) continue;\n\t\tout.push({\n\t\t\ttxid: row.tx_id,\n\t\t\tcommitted: true,\n\t\t\ttype: nodeType,\n\t\t\t[nodeType]: row.data,\n\t\t});\n\t}\n\treturn out;\n}\n\nexport class LocalClient {\n\t/**\n\t * Reconstruct a NewBlockPayload from local DB for replay.\n\t * Returns null if block not found.\n\t */\n\tasync getBlockForReplay(\n\t\tdb: Kysely<Database>,\n\t\theight: number,\n\t): Promise<ReplayBlockPayload | null> {\n\t\tconst block = await db\n\t\t\t.selectFrom(\"blocks\")\n\t\t\t.selectAll()\n\t\t\t.where(\"height\", \"=\", height)\n\t\t\t.where(\"canonical\", \"=\", true)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!block) return null;\n\n\t\tconst transactions = await db\n\t\t\t.selectFrom(\"transactions\")\n\t\t\t.selectAll()\n\t\t\t.where(\"block_height\", \"=\", height)\n\t\t\t.orderBy(\"tx_index\", \"asc\")\n\t\t\t.execute();\n\n\t\tconst events = await db\n\t\t\t.selectFrom(\"events\")\n\t\t\t.selectAll()\n\t\t\t.where(\"block_height\", \"=\", height)\n\t\t\t.orderBy(\"event_index\", \"asc\")\n\t\t\t.execute();\n\n\t\tconst vmRows = await db\n\t\t\t.selectFrom(\"vm_events\")\n\t\t\t.select([\"tx_id\", \"ordinal\", \"type\", \"data\"])\n\t\t\t.where(\"block_height\", \"=\", height)\n\t\t\t.orderBy(\"ordinal\", \"asc\")\n\t\t\t.execute();\n\t\tconst vm_events = reconstructVmEventsForReplay(vmRows);\n\n\t\tconst state_writes: ReplayStateWritePayload[] = await db\n\t\t\t.selectFrom(\"state_writes\")\n\t\t\t.select([\"tx_index\", \"ordinal\", \"key\", \"value_hex\"])\n\t\t\t.where(\"block_height\", \"=\", height)\n\t\t\t.orderBy(\"ordinal\", \"asc\")\n\t\t\t.execute();\n\n\t\t// height, burn_block_height and timestamp are bigint columns: postgres.js\n\t\t// returns them as strings, which the parser's numeric checks reject\n\t\t// (timestamp silently became 0 on every flip-back).\n\t\tconst timestamp = Number(block.timestamp);\n\t\treturn {\n\t\t\tblock_hash: block.hash,\n\t\t\tblock_height: Number(block.height),\n\t\t\t// Null on rows ingested before these were persisted; the parser\n\t\t\t// stores \"\" as NULL, so the restored row stays honest.\n\t\t\tindex_block_hash: block.index_block_hash ?? \"\",\n\t\t\tparent_block_hash: block.parent_hash,\n\t\t\t// Not stored in our DB — not needed by parser/deliveries\n\t\t\tparent_index_block_hash: \"\",\n\t\t\tburn_block_hash: block.burn_block_hash ?? \"\",\n\t\t\tburn_block_height: Number(block.burn_block_height),\n\t\t\tburn_block_timestamp: timestamp,\n\t\t\tminer_txid: \"\",\n\t\t\ttimestamp,\n\t\t\ttransactions: transactions.map((tx) => {\n\t\t\t\tconst entry: ReplayTransactionPayload = {\n\t\t\t\t\ttxid: tx.tx_id,\n\t\t\t\t\traw_tx: tx.raw_tx,\n\t\t\t\t\tstatus: tx.status,\n\t\t\t\t\ttx_index: tx.tx_index,\n\t\t\t\t\ttx_type: tx.type,\n\t\t\t\t\tsender_address: tx.sender,\n\t\t\t\t\traw_result: tx.raw_result ?? null,\n\t\t\t\t};\n\t\t\t\t// Include function_args if stored (for contract_call txs)\n\t\t\t\tif (tx.function_args) {\n\t\t\t\t\tconst args =\n\t\t\t\t\t\ttypeof tx.function_args === \"string\"\n\t\t\t\t\t\t\t? JSON.parse(tx.function_args)\n\t\t\t\t\t\t\t: tx.function_args;\n\t\t\t\t\tif (Array.isArray(args)) {\n\t\t\t\t\t\tentry.contract_call = { function_args: args };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn entry;\n\t\t\t}),\n\t\t\tevents: events.map((evt) => {\n\t\t\t\tconst data = (evt.data ?? {}) as Record<string, unknown>;\n\t\t\t\tconst eventType = evt.type;\n\n\t\t\t\t// Reconstruct the flat event structure the indexer expects:\n\t\t\t\t// { txid, event_index, committed, type, [type_key]: data }\n\t\t\t\tconst payload: ReplayEventPayload = {\n\t\t\t\t\ttxid: evt.tx_id,\n\t\t\t\t\tevent_index: evt.event_index,\n\t\t\t\t\tcommitted: true,\n\t\t\t\t\ttype: eventType,\n\t\t\t\t};\n\n\t\t\t\t// Attach event-specific data under the correct key\n\t\t\t\tif (eventType && data) {\n\t\t\t\t\tpayload[eventType] = data;\n\t\t\t\t}\n\n\t\t\t\treturn payload;\n\t\t\t}),\n\t\t\t...(vm_events.length > 0 ? { vm_events } : {}),\n\t\t\t...(state_writes.length > 0 ? { state_writes } : {}),\n\t\t};\n\t}\n\n\t/** Get highest block height in local DB */\n\tasync getChainTip(db: Kysely<Database>): Promise<number> {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"blocks\")\n\t\t\t.select((eb) => eb.fn.max(\"height\").as(\"max_height\"))\n\t\t\t.where(\"canonical\", \"=\", true)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(row?.max_height ?? 0);\n\t}\n\n\t/** Check if a specific block height exists in local DB */\n\tasync hasBlock(db: Kysely<Database>, height: number): Promise<boolean> {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"blocks\")\n\t\t\t.select(\"height\")\n\t\t\t.where(\"height\", \"=\", height)\n\t\t\t.where(\"canonical\", \"=\", true)\n\t\t\t.executeTakeFirst();\n\t\treturn !!row;\n\t}\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "0E6E16C36A8DD8AA64756E2164756E21",
8
+ "mappings": ";AAKA;AAAA;AAAA;AAMA;AAAA,yBACC;AAAA;AAAA;AAAA;AAcM,IAAM,yBAAyC;AAAA,EACrD,sBAAsB;AAAA,EACtB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,YAAY;AACb;AAKO,IAAM,sBACZ;AAWM,IAAM,mBAAmB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;;;ACoBO,SAAS,4BAA4B,CAC3C,MAMyB;AAAA,EACzB,MAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KACxB,CAAC,GAAG,MAAM,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,OAAO,CAC/C;AAAA,EACA,MAAM,MAA8B,CAAC;AAAA,EACrC,WAAW,OAAO,QAAQ;AAAA,IACzB,MAAM,WACL,IAAI,QAAQ,yBACT,uBAAuB,IAAI,QAC3B;AAAA,IACJ,IAAI,CAAC;AAAA,MAAU;AAAA,IACf,IAAI,KAAK;AAAA,MACR,MAAM,IAAI;AAAA,MACV,WAAW;AAAA,MACX,MAAM;AAAA,OACL,WAAW,IAAI;AAAA,IACjB,CAAC;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAAA;AAGD,MAAM,YAAY;AAAA,OAKlB,kBAAiB,CACtB,IACA,QACqC;AAAA,IACrC,MAAM,QAAQ,MAAM,GAClB,WAAW,QAAQ,EACnB,UAAU,EACV,MAAM,UAAU,KAAK,MAAM,EAC3B,MAAM,aAAa,KAAK,IAAI,EAC5B,iBAAiB;AAAA,IAEnB,IAAI,CAAC;AAAA,MAAO,OAAO;AAAA,IAEnB,MAAM,eAAe,MAAM,GACzB,WAAW,cAAc,EACzB,UAAU,EACV,MAAM,gBAAgB,KAAK,MAAM,EACjC,QAAQ,YAAY,KAAK,EACzB,QAAQ;AAAA,IAEV,MAAM,SAAS,MAAM,GACnB,WAAW,QAAQ,EACnB,UAAU,EACV,MAAM,gBAAgB,KAAK,MAAM,EACjC,QAAQ,eAAe,KAAK,EAC5B,QAAQ;AAAA,IAEV,MAAM,SAAS,MAAM,GACnB,WAAW,WAAW,EACtB,OAAO,CAAC,SAAS,WAAW,QAAQ,MAAM,CAAC,EAC3C,MAAM,gBAAgB,KAAK,MAAM,EACjC,QAAQ,WAAW,KAAK,EACxB,QAAQ;AAAA,IACV,MAAM,YAAY,6BAA6B,MAAM;AAAA,IAErD,MAAM,eAA0C,MAAM,GACpD,WAAW,cAAc,EACzB,OAAO,CAAC,YAAY,WAAW,OAAO,WAAW,CAAC,EAClD,MAAM,gBAAgB,KAAK,MAAM,EACjC,QAAQ,WAAW,KAAK,EACxB,QAAQ;AAAA,IAKV,MAAM,YAAY,OAAO,MAAM,SAAS;AAAA,IACxC,OAAO;AAAA,MACN,YAAY,MAAM;AAAA,MAClB,cAAc,OAAO,MAAM,MAAM;AAAA,MAGjC,kBAAkB,MAAM,oBAAoB;AAAA,MAC5C,mBAAmB,MAAM;AAAA,MAEzB,yBAAyB;AAAA,MACzB,iBAAiB,MAAM,mBAAmB;AAAA,MAC1C,mBAAmB,OAAO,MAAM,iBAAiB;AAAA,MACjD,sBAAsB;AAAA,MACtB,YAAY;AAAA,MACZ;AAAA,MACA,cAAc,aAAa,IAAI,CAAC,OAAO;AAAA,QACtC,MAAM,QAAkC;AAAA,UACvC,MAAM,GAAG;AAAA,UACT,QAAQ,GAAG;AAAA,UACX,QAAQ,GAAG;AAAA,UACX,UAAU,GAAG;AAAA,UACb,SAAS,GAAG;AAAA,UACZ,gBAAgB,GAAG;AAAA,UACnB,YAAY,GAAG,cAAc;AAAA,QAC9B;AAAA,QAEA,IAAI,GAAG,eAAe;AAAA,UACrB,MAAM,OACL,OAAO,GAAG,kBAAkB,WACzB,KAAK,MAAM,GAAG,aAAa,IAC3B,GAAG;AAAA,UACP,IAAI,MAAM,QAAQ,IAAI,GAAG;AAAA,YACxB,MAAM,gBAAgB,EAAE,eAAe,KAAK;AAAA,UAC7C;AAAA,QACD;AAAA,QACA,OAAO;AAAA,OACP;AAAA,MACD,QAAQ,OAAO,IAAI,CAAC,QAAQ;AAAA,QAC3B,MAAM,OAAQ,IAAI,QAAQ,CAAC;AAAA,QAC3B,MAAM,YAAY,IAAI;AAAA,QAItB,MAAM,UAA8B;AAAA,UACnC,MAAM,IAAI;AAAA,UACV,aAAa,IAAI;AAAA,UACjB,WAAW;AAAA,UACX,MAAM;AAAA,QACP;AAAA,QAGA,IAAI,aAAa,MAAM;AAAA,UACtB,QAAQ,aAAa;AAAA,QACtB;AAAA,QAEA,OAAO;AAAA,OACP;AAAA,SACG,UAAU,SAAS,IAAI,EAAE,UAAU,IAAI,CAAC;AAAA,SACxC,aAAa,SAAS,IAAI,EAAE,aAAa,IAAI,CAAC;AAAA,IACnD;AAAA;AAAA,OAIK,YAAW,CAAC,IAAuC;AAAA,IACxD,MAAM,MAAM,MAAM,GAChB,WAAW,QAAQ,EACnB,OAAO,CAAC,OAAO,GAAG,GAAG,IAAI,QAAQ,EAAE,GAAG,YAAY,CAAC,EACnD,MAAM,aAAa,KAAK,IAAI,EAC5B,iBAAiB;AAAA,IACnB,OAAO,OAAO,KAAK,cAAc,CAAC;AAAA;AAAA,OAI7B,SAAQ,CAAC,IAAsB,QAAkC;AAAA,IACtE,MAAM,MAAM,MAAM,GAChB,WAAW,QAAQ,EACnB,OAAO,QAAQ,EACf,MAAM,UAAU,KAAK,MAAM,EAC3B,MAAM,aAAa,KAAK,IAAI,EAC5B,iBAAiB;AAAA,IACnB,OAAO,CAAC,CAAC;AAAA;AAEX;",
9
+ "debugId": "85374ECE2AE4234264756E2164756E21",
10
10
  "names": []
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/shared",
3
- "version": "13.14.0",
3
+ "version": "14.0.0",
4
4
  "description": "Internal building blocks shared across Secondlayer packages.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -237,10 +237,6 @@
237
237
  "types": "./dist/src/node/client.d.ts",
238
238
  "import": "./dist/src/node/client.js"
239
239
  },
240
- "./node/archive-client": {
241
- "types": "./dist/src/node/archive-client.d.ts",
242
- "import": "./dist/src/node/archive-client.js"
243
- },
244
240
  "./node/tx-summary": {
245
241
  "types": "./dist/src/node/tx-summary.d.ts",
246
242
  "import": "./dist/src/node/tx-summary.js"
@@ -277,7 +273,7 @@
277
273
  "prepublishOnly": "bun run build"
278
274
  },
279
275
  "dependencies": {
280
- "@secondlayer/stacks": "^6.4.1",
276
+ "@secondlayer/stacks": "^6.5.1",
281
277
  "kysely": "0.28.17",
282
278
  "kysely-postgres-js": "3.0.0",
283
279
  "postgres": "^3.4.6",
@@ -1,39 +0,0 @@
1
- interface ReplayResult {
2
- replayed: number;
3
- errors: number;
4
- }
5
- /** Options for replaying archive blocks, with a progress callback. */
6
- interface ReplayOptions {
7
- onProgress?: (count: number, height: number) => void;
8
- }
9
- declare class ArchiveReplayClient {
10
- private archiveUrl;
11
- private archiveDir;
12
- constructor(opts?: {
13
- archiveUrl?: string
14
- archiveDir?: string
15
- });
16
- private get archivePath();
17
- private get metaPath();
18
- private get partialPath();
19
- /** HEAD request to archive URL — verify reachable and has content */
20
- isAvailable(): Promise<boolean>;
21
- /**
22
- * Download archive, stream-decompress, replay blocks matching gapHeights
23
- * to the indexer's /new_block endpoint.
24
- */
25
- replayGaps(gapHeights: Set<number>, indexerUrl: string, opts?: ReplayOptions): Promise<ReplayResult>;
26
- /**
27
- * Ensure a fresh-enough archive exists locally.
28
- * Uses HTTP conditional requests to avoid redundant downloads.
29
- */
30
- private ensureArchive;
31
- /** Remove stale cache (> 24h) and orphaned partial files */
32
- private cleanStaleFiles;
33
- private readMeta;
34
- private writeMeta;
35
- private cleanupFile;
36
- /** Download archive to disk with streaming. Returns Last-Modified header. */
37
- private download;
38
- }
39
- export { ArchiveReplayClient, ReplayOptions, ReplayResult };
@@ -1,399 +0,0 @@
1
- // src/node/archive-client.ts
2
- import {
3
- existsSync,
4
- readFileSync,
5
- renameSync,
6
- unlinkSync,
7
- writeFileSync
8
- } from "node:fs";
9
-
10
- // src/env.ts
11
- import { z } from "zod";
12
- var networksSchema = z.string().transform((val) => {
13
- const networks = val.split(",").map((n) => n.trim()).filter(Boolean);
14
- const valid = ["mainnet", "testnet"];
15
- for (const n of networks) {
16
- if (!valid.includes(n)) {
17
- throw new Error(`Invalid network: ${n}. Must be one of: ${valid.join(", ")}`);
18
- }
19
- }
20
- return networks;
21
- });
22
- var envSchema = z.object({
23
- DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
24
- SOURCE_DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
25
- TARGET_DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
26
- NETWORK: z.enum(["mainnet", "testnet"]).optional(),
27
- NETWORKS: networksSchema.optional(),
28
- LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
29
- NODE_ENV: z.enum(["development", "production", "test"]).default("development")
30
- });
31
- var cachedEnv = null;
32
- function getEnv() {
33
- if (cachedEnv) {
34
- return cachedEnv;
35
- }
36
- const result = envSchema.safeParse(process.env);
37
- if (!result.success) {
38
- console.error("❌ Invalid environment configuration:");
39
- console.error(z.treeifyError(result.error));
40
- throw new Error("Invalid environment configuration");
41
- }
42
- let enabledNetworks;
43
- if (result.data.NETWORKS && result.data.NETWORKS.length > 0) {
44
- enabledNetworks = result.data.NETWORKS;
45
- } else if (result.data.NETWORK) {
46
- enabledNetworks = [result.data.NETWORK];
47
- } else {
48
- enabledNetworks = ["mainnet"];
49
- }
50
- cachedEnv = { ...result.data, enabledNetworks };
51
- return cachedEnv;
52
- }
53
- function isProductionEnv() {
54
- return process.env["NODE_ENV"] === "production";
55
- }
56
- function isPox4DecoderEnabled(env = process.env) {
57
- return env.POX4_DECODER_ENABLED !== "false";
58
- }
59
- function isPox5DecoderEnabled(env = process.env) {
60
- return env.POX5_DECODER_ENABLED !== "false";
61
- }
62
- function isSbtcDecoderEnabled(env = process.env) {
63
- return env.SBTC_DECODER_ENABLED !== "false";
64
- }
65
- function isBnsDecoderEnabled(env = process.env) {
66
- return env.BNS_DECODER_ENABLED === "true";
67
- }
68
- // src/logger.ts
69
- var LOG_LEVELS = {
70
- debug: 0,
71
- info: 1,
72
- warn: 2,
73
- error: 3
74
- };
75
-
76
- class Logger {
77
- _level;
78
- _isProduction;
79
- _initialized = false;
80
- init() {
81
- if (this._initialized)
82
- return;
83
- this._initialized = true;
84
- try {
85
- const env = getEnv();
86
- this._level = env.LOG_LEVEL;
87
- this._isProduction = env.NODE_ENV === "production";
88
- } catch {
89
- this._level = "info";
90
- this._isProduction = false;
91
- }
92
- }
93
- get level() {
94
- this.init();
95
- return this._level;
96
- }
97
- get isProduction() {
98
- this.init();
99
- return this._isProduction;
100
- }
101
- shouldLog(level) {
102
- return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
103
- }
104
- formatMessage(level, message, meta) {
105
- const timestamp = new Date().toISOString();
106
- if (this.isProduction) {
107
- return JSON.stringify({
108
- timestamp,
109
- level,
110
- message,
111
- ...meta
112
- });
113
- }
114
- const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
115
- return `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;
116
- }
117
- debug(message, meta) {
118
- if (this.shouldLog("debug")) {
119
- console.debug(this.formatMessage("debug", message, meta));
120
- }
121
- }
122
- info(message, meta) {
123
- if (this.shouldLog("info")) {
124
- console.info(this.formatMessage("info", message, meta));
125
- }
126
- }
127
- warn(message, meta) {
128
- if (this.shouldLog("warn")) {
129
- console.warn(this.formatMessage("warn", message, meta));
130
- }
131
- }
132
- error(message, meta) {
133
- if (this.shouldLog("error")) {
134
- console.error(this.formatMessage("error", message, meta));
135
- }
136
- }
137
- }
138
- var logger = new Logger;
139
-
140
- // src/node/archive-client.ts
141
- var DEFAULT_ARCHIVE_URL = "https://archive.hiro.so/mainnet/stacks-blockchain-api/mainnet-stacks-blockchain-api-latest.zst";
142
- var HEIGHT_REGEX = /"block_height":\s*(\d+)/;
143
- var CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
144
-
145
- class ArchiveReplayClient {
146
- archiveUrl;
147
- archiveDir;
148
- constructor(opts) {
149
- this.archiveUrl = opts?.archiveUrl || process.env.ARCHIVE_URL || DEFAULT_ARCHIVE_URL;
150
- this.archiveDir = opts?.archiveDir || process.env.ARCHIVE_DIR || "/tmp";
151
- }
152
- get archivePath() {
153
- return `${this.archiveDir}/secondlayer-archive.zst`;
154
- }
155
- get metaPath() {
156
- return `${this.archiveDir}/secondlayer-archive.meta.json`;
157
- }
158
- get partialPath() {
159
- return `${this.archiveDir}/secondlayer-archive.zst.partial`;
160
- }
161
- async isAvailable() {
162
- try {
163
- const res = await fetch(this.archiveUrl, {
164
- method: "HEAD",
165
- signal: AbortSignal.timeout(15000)
166
- });
167
- const contentLength = Number(res.headers.get("content-length") || 0);
168
- return res.ok && contentLength > 0;
169
- } catch {
170
- return false;
171
- }
172
- }
173
- async replayGaps(gapHeights, indexerUrl, opts) {
174
- if (gapHeights.size === 0)
175
- return { replayed: 0, errors: 0 };
176
- let maxHeight = 0;
177
- for (const h of gapHeights)
178
- if (h > maxHeight)
179
- maxHeight = h;
180
- let replayed = 0;
181
- let errors = 0;
182
- try {
183
- await this.ensureArchive();
184
- logger.info("Archive replay: starting decompression + replay", {
185
- targetHeights: gapHeights.size,
186
- maxHeight
187
- });
188
- const proc = Bun.spawn(["zstd", "-d", this.archivePath, "--stdout"], {
189
- stdout: "pipe",
190
- stderr: "ignore"
191
- });
192
- const reader = proc.stdout.getReader();
193
- const decoder = new TextDecoder;
194
- let buffer = "";
195
- const remaining = new Set(gapHeights);
196
- while (true) {
197
- const { done, value } = await reader.read();
198
- if (done)
199
- break;
200
- buffer += decoder.decode(value, { stream: true });
201
- const lines = buffer.split(`
202
- `);
203
- buffer = lines.pop() || "";
204
- for (const line of lines) {
205
- if (remaining.size === 0)
206
- break;
207
- const match = HEIGHT_REGEX.exec(line);
208
- if (!match)
209
- continue;
210
- const height = Number.parseInt(match[1]);
211
- if (!remaining.has(height)) {
212
- if (height > maxHeight)
213
- break;
214
- continue;
215
- }
216
- const tabIdx3 = nthIndex(line, "\t", 3);
217
- if (tabIdx3 === -1)
218
- continue;
219
- const path = line.substring(nthIndex(line, "\t", 2) + 1, tabIdx3);
220
- if (path !== "/new_block")
221
- continue;
222
- const payload = line.substring(tabIdx3 + 1);
223
- try {
224
- const res = await fetch(`${indexerUrl}/new_block`, {
225
- method: "POST",
226
- headers: {
227
- "Content-Type": "application/json",
228
- "X-Source": "archive-replay"
229
- },
230
- body: payload
231
- });
232
- if (res.ok) {
233
- replayed++;
234
- remaining.delete(height);
235
- opts?.onProgress?.(replayed, height);
236
- } else {
237
- errors++;
238
- logger.warn("Archive replay: indexer rejected block", {
239
- height,
240
- status: res.status
241
- });
242
- }
243
- } catch (err) {
244
- errors++;
245
- logger.warn("Archive replay: POST failed", {
246
- height,
247
- error: String(err)
248
- });
249
- }
250
- }
251
- if (remaining.size === 0) {
252
- proc.kill();
253
- break;
254
- }
255
- }
256
- await proc.exited;
257
- if (remaining.size > 0) {
258
- const sample = [];
259
- for (const h of remaining) {
260
- sample.push(h);
261
- if (sample.length === 5)
262
- break;
263
- }
264
- logger.warn("Archive replay: some heights not found in archive", {
265
- missing: remaining.size,
266
- sample
267
- });
268
- }
269
- logger.info("Archive replay: complete", {
270
- replayed,
271
- errors,
272
- missing: remaining.size
273
- });
274
- } catch (err) {
275
- this.cleanupFile(this.archivePath);
276
- this.cleanupFile(this.metaPath);
277
- this.cleanupFile(this.partialPath);
278
- throw err;
279
- }
280
- return { replayed, errors };
281
- }
282
- async ensureArchive() {
283
- this.cleanStaleFiles();
284
- const meta = this.readMeta();
285
- const cached = existsSync(this.archivePath) && meta !== null;
286
- if (cached) {
287
- const age = Date.now() - new Date(meta.downloadedAt).getTime();
288
- if (age < CACHE_MAX_AGE_MS) {
289
- const headers = {};
290
- if (meta.lastModified) {
291
- headers["If-Modified-Since"] = meta.lastModified;
292
- }
293
- try {
294
- const res = await fetch(this.archiveUrl, {
295
- method: "HEAD",
296
- headers,
297
- signal: AbortSignal.timeout(15000)
298
- });
299
- if (res.status === 304) {
300
- logger.info("Archive replay: using cached archive", {
301
- ageHrs: (age / 3600000).toFixed(1)
302
- });
303
- return;
304
- }
305
- logger.info("Archive replay: remote archive is newer, re-downloading");
306
- } catch {
307
- logger.info("Archive replay: remote unreachable, using cached archive");
308
- return;
309
- }
310
- } else {
311
- logger.info("Archive replay: cache expired, re-downloading");
312
- }
313
- }
314
- logger.info("Archive replay: downloading archive", {
315
- url: this.archiveUrl.split("/").pop()
316
- });
317
- const lastModified = await this.download(this.partialPath);
318
- renameSync(this.partialPath, this.archivePath);
319
- this.writeMeta({ lastModified, downloadedAt: new Date().toISOString() });
320
- logger.info("Archive replay: download complete");
321
- }
322
- cleanStaleFiles() {
323
- try {
324
- if (existsSync(this.partialPath)) {
325
- unlinkSync(this.partialPath);
326
- }
327
- const meta = this.readMeta();
328
- if (meta) {
329
- const age = Date.now() - new Date(meta.downloadedAt).getTime();
330
- if (age > CACHE_MAX_AGE_MS) {
331
- this.cleanupFile(this.archivePath);
332
- this.cleanupFile(this.metaPath);
333
- logger.info("Archive replay: cleaned stale cache");
334
- }
335
- } else if (existsSync(this.archivePath)) {
336
- this.cleanupFile(this.archivePath);
337
- }
338
- } catch {}
339
- }
340
- readMeta() {
341
- try {
342
- if (!existsSync(this.metaPath))
343
- return null;
344
- return JSON.parse(readFileSync(this.metaPath, "utf-8"));
345
- } catch {
346
- return null;
347
- }
348
- }
349
- writeMeta(meta) {
350
- try {
351
- writeFileSync(this.metaPath, JSON.stringify(meta));
352
- } catch {}
353
- }
354
- cleanupFile(path) {
355
- try {
356
- if (existsSync(path))
357
- unlinkSync(path);
358
- } catch {}
359
- }
360
- async download(destPath) {
361
- const res = await fetch(this.archiveUrl, {
362
- signal: AbortSignal.timeout(30 * 60 * 1000)
363
- });
364
- if (!res.ok || !res.body) {
365
- throw new Error(`Archive download failed: HTTP ${res.status}`);
366
- }
367
- const lastModified = res.headers.get("last-modified");
368
- const totalBytes = Number(res.headers.get("content-length") || 0);
369
- const writer = Bun.file(destPath).writer();
370
- let downloaded = 0;
371
- let lastLog = 0;
372
- for await (const chunk of res.body) {
373
- writer.write(chunk);
374
- downloaded += chunk.byteLength;
375
- if (totalBytes > 0 && downloaded - lastLog > 5000000000) {
376
- lastLog = downloaded;
377
- const pct = (downloaded / totalBytes * 100).toFixed(0);
378
- logger.info("Archive replay: downloading", { progress: `${pct}%` });
379
- }
380
- }
381
- await writer.end();
382
- return lastModified;
383
- }
384
- }
385
- function nthIndex(str, char, n) {
386
- let idx = -1;
387
- for (let i = 0;i < n; i++) {
388
- idx = str.indexOf(char, idx + 1);
389
- if (idx === -1)
390
- return -1;
391
- }
392
- return idx;
393
- }
394
- export {
395
- ArchiveReplayClient
396
- };
397
-
398
- //# debugId=9E72CC1B9C56340B64756E2164756E21
399
- //# sourceMappingURL=archive-client.js.map
@@ -1,12 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/node/archive-client.ts", "../src/env.ts", "../src/logger.ts"],
4
- "sourcesContent": [
5
- "/**\n * Archive Replay Client — backfills blocks from Hiro's daily event observer archive.\n *\n * The archive at archive.hiro.so contains zstd-compressed TSV files of raw\n * /new_block payloads (the exact NewBlockPayload JSON our indexer expects).\n * This client downloads the archive, streams + filters for specific block\n * heights, and POSTs matching payloads directly to the indexer.\n *\n * Caches the archive locally for up to 24h to avoid redundant ~25GB downloads.\n * Zero external API dependency — only needs the static archive file.\n */\n\nimport {\n\texistsSync,\n\treadFileSync,\n\trenameSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { logger } from \"../logger.ts\";\n\nconst DEFAULT_ARCHIVE_URL =\n\t\"https://archive.hiro.so/mainnet/stacks-blockchain-api/mainnet-stacks-blockchain-api-latest.zst\";\n\nconst HEIGHT_REGEX = /\"block_height\":\\s*(\\d+)/;\nconst CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours\n\ninterface ArchiveMeta {\n\tlastModified: string | null;\n\tdownloadedAt: string;\n}\n\nexport interface ReplayResult {\n\treplayed: number;\n\terrors: number;\n}\n\n/** Options for replaying archive blocks, with a progress callback. */\nexport interface ReplayOptions {\n\tonProgress?: (count: number, height: number) => void;\n}\n\nexport class ArchiveReplayClient {\n\tprivate archiveUrl: string;\n\tprivate archiveDir: string;\n\n\tconstructor(opts?: { archiveUrl?: string; archiveDir?: string }) {\n\t\tthis.archiveUrl =\n\t\t\topts?.archiveUrl || process.env.ARCHIVE_URL || DEFAULT_ARCHIVE_URL;\n\t\tthis.archiveDir = opts?.archiveDir || process.env.ARCHIVE_DIR || \"/tmp\";\n\t}\n\n\tprivate get archivePath() {\n\t\treturn `${this.archiveDir}/secondlayer-archive.zst`;\n\t}\n\tprivate get metaPath() {\n\t\treturn `${this.archiveDir}/secondlayer-archive.meta.json`;\n\t}\n\tprivate get partialPath() {\n\t\treturn `${this.archiveDir}/secondlayer-archive.zst.partial`;\n\t}\n\n\t/** HEAD request to archive URL — verify reachable and has content */\n\tasync isAvailable(): Promise<boolean> {\n\t\ttry {\n\t\t\tconst res = await fetch(this.archiveUrl, {\n\t\t\t\tmethod: \"HEAD\",\n\t\t\t\tsignal: AbortSignal.timeout(15_000),\n\t\t\t});\n\t\t\tconst contentLength = Number(res.headers.get(\"content-length\") || 0);\n\t\t\treturn res.ok && contentLength > 0;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Download archive, stream-decompress, replay blocks matching gapHeights\n\t * to the indexer's /new_block endpoint.\n\t */\n\tasync replayGaps(\n\t\tgapHeights: Set<number>,\n\t\tindexerUrl: string,\n\t\topts?: ReplayOptions,\n\t): Promise<ReplayResult> {\n\t\tif (gapHeights.size === 0) return { replayed: 0, errors: 0 };\n\n\t\t// Iterate, don't spread: a full-history gap set is millions of heights,\n\t\t// and `Math.max(...set)` spreads them all as call arguments → instant\n\t\t// RangeError (max call-stack) on any large backfill.\n\t\tlet maxHeight = 0;\n\t\tfor (const h of gapHeights) if (h > maxHeight) maxHeight = h;\n\t\tlet replayed = 0;\n\t\tlet errors = 0;\n\n\t\ttry {\n\t\t\tawait this.ensureArchive();\n\n\t\t\tlogger.info(\"Archive replay: starting decompression + replay\", {\n\t\t\t\ttargetHeights: gapHeights.size,\n\t\t\t\tmaxHeight,\n\t\t\t});\n\n\t\t\t// Decompress via zstd subprocess\n\t\t\tconst proc = Bun.spawn([\"zstd\", \"-d\", this.archivePath, \"--stdout\"], {\n\t\t\t\tstdout: \"pipe\",\n\t\t\t\tstderr: \"ignore\",\n\t\t\t});\n\n\t\t\tconst reader = proc.stdout.getReader();\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = \"\";\n\t\t\tconst remaining = new Set(gapHeights);\n\n\t\t\twhile (true) {\n\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\tif (done) break;\n\n\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\tbuffer = lines.pop() || \"\";\n\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\tif (remaining.size === 0) break;\n\n\t\t\t\t\t// Quick height check via regex (avoid full JSON parse)\n\t\t\t\t\tconst match = HEIGHT_REGEX.exec(line);\n\t\t\t\t\tif (!match) continue;\n\n\t\t\t\t\tconst height = Number.parseInt(match[1]);\n\n\t\t\t\t\t// Skip blocks we don't need\n\t\t\t\t\tif (!remaining.has(height)) {\n\t\t\t\t\t\t// Early exit if past all gap heights\n\t\t\t\t\t\tif (height > maxHeight) break;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Extract payload from TSV (id \\t timestamp \\t path \\t payload)\n\t\t\t\t\tconst tabIdx3 = nthIndex(line, \"\\t\", 3);\n\t\t\t\t\tif (tabIdx3 === -1) continue;\n\n\t\t\t\t\tconst path = line.substring(nthIndex(line, \"\\t\", 2) + 1, tabIdx3);\n\t\t\t\t\tif (path !== \"/new_block\") continue;\n\n\t\t\t\t\tconst payload = line.substring(tabIdx3 + 1);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst res = await fetch(`${indexerUrl}/new_block`, {\n\t\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t\t\"X-Source\": \"archive-replay\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: payload,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (res.ok) {\n\t\t\t\t\t\t\treplayed++;\n\t\t\t\t\t\t\tremaining.delete(height);\n\t\t\t\t\t\t\topts?.onProgress?.(replayed, height);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\terrors++;\n\t\t\t\t\t\t\tlogger.warn(\"Archive replay: indexer rejected block\", {\n\t\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\t\tstatus: res.status,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\terrors++;\n\t\t\t\t\t\tlogger.warn(\"Archive replay: POST failed\", {\n\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\terror: String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Early exit if all gaps filled or past max height\n\t\t\t\tif (remaining.size === 0) {\n\t\t\t\t\tproc.kill();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Wait for process to exit\n\t\t\tawait proc.exited;\n\n\t\t\tif (remaining.size > 0) {\n\t\t\t\t// Don't spread `remaining` (can be millions) just to sample a few.\n\t\t\t\tconst sample: number[] = [];\n\t\t\t\tfor (const h of remaining) {\n\t\t\t\t\tsample.push(h);\n\t\t\t\t\tif (sample.length === 5) break;\n\t\t\t\t}\n\t\t\t\tlogger.warn(\"Archive replay: some heights not found in archive\", {\n\t\t\t\t\tmissing: remaining.size,\n\t\t\t\t\tsample,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tlogger.info(\"Archive replay: complete\", {\n\t\t\t\treplayed,\n\t\t\t\terrors,\n\t\t\t\tmissing: remaining.size,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\t// Clean up on error (corrupt/partial downloads)\n\t\t\tthis.cleanupFile(this.archivePath);\n\t\t\tthis.cleanupFile(this.metaPath);\n\t\t\tthis.cleanupFile(this.partialPath);\n\t\t\tthrow err;\n\t\t}\n\n\t\treturn { replayed, errors };\n\t}\n\n\t/**\n\t * Ensure a fresh-enough archive exists locally.\n\t * Uses HTTP conditional requests to avoid redundant downloads.\n\t */\n\tprivate async ensureArchive(): Promise<void> {\n\t\tthis.cleanStaleFiles();\n\n\t\tconst meta = this.readMeta();\n\t\tconst cached = existsSync(this.archivePath) && meta !== null;\n\n\t\tif (cached) {\n\t\t\tconst age = Date.now() - new Date(meta.downloadedAt).getTime();\n\t\t\tif (age < CACHE_MAX_AGE_MS) {\n\t\t\t\t// Cache is fresh enough — check if remote has a newer version\n\t\t\t\tconst headers: Record<string, string> = {};\n\t\t\t\tif (meta.lastModified) {\n\t\t\t\t\theaders[\"If-Modified-Since\"] = meta.lastModified;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst res = await fetch(this.archiveUrl, {\n\t\t\t\t\t\tmethod: \"HEAD\",\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tsignal: AbortSignal.timeout(15_000),\n\t\t\t\t\t});\n\n\t\t\t\t\tif (res.status === 304) {\n\t\t\t\t\t\tlogger.info(\"Archive replay: using cached archive\", {\n\t\t\t\t\t\t\tageHrs: (age / 3600000).toFixed(1),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 200 = remote is newer, re-download below\n\t\t\t\t\tlogger.info(\n\t\t\t\t\t\t\"Archive replay: remote archive is newer, re-downloading\",\n\t\t\t\t\t);\n\t\t\t\t} catch {\n\t\t\t\t\t// Can't reach remote — use cache anyway\n\t\t\t\t\tlogger.info(\n\t\t\t\t\t\t\"Archive replay: remote unreachable, using cached archive\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlogger.info(\"Archive replay: cache expired, re-downloading\");\n\t\t\t}\n\t\t}\n\n\t\t// Download fresh archive\n\t\tlogger.info(\"Archive replay: downloading archive\", {\n\t\t\turl: this.archiveUrl.split(\"/\").pop(),\n\t\t});\n\n\t\tconst lastModified = await this.download(this.partialPath);\n\n\t\t// Atomic rename: partial → final\n\t\trenameSync(this.partialPath, this.archivePath);\n\n\t\t// Write meta sidecar\n\t\tthis.writeMeta({ lastModified, downloadedAt: new Date().toISOString() });\n\n\t\tlogger.info(\"Archive replay: download complete\");\n\t}\n\n\t/** Remove stale cache (> 24h) and orphaned partial files */\n\tprivate cleanStaleFiles(): void {\n\t\ttry {\n\t\t\t// Clean orphaned partial downloads\n\t\t\tif (existsSync(this.partialPath)) {\n\t\t\t\tunlinkSync(this.partialPath);\n\t\t\t}\n\n\t\t\t// Clean stale cache\n\t\t\tconst meta = this.readMeta();\n\t\t\tif (meta) {\n\t\t\t\tconst age = Date.now() - new Date(meta.downloadedAt).getTime();\n\t\t\t\tif (age > CACHE_MAX_AGE_MS) {\n\t\t\t\t\tthis.cleanupFile(this.archivePath);\n\t\t\t\t\tthis.cleanupFile(this.metaPath);\n\t\t\t\t\tlogger.info(\"Archive replay: cleaned stale cache\");\n\t\t\t\t}\n\t\t\t} else if (existsSync(this.archivePath)) {\n\t\t\t\t// Archive without meta — orphaned, clean up\n\t\t\t\tthis.cleanupFile(this.archivePath);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Best-effort cleanup\n\t\t}\n\t}\n\n\tprivate readMeta(): ArchiveMeta | null {\n\t\ttry {\n\t\t\tif (!existsSync(this.metaPath)) return null;\n\t\t\treturn JSON.parse(readFileSync(this.metaPath, \"utf-8\")) as ArchiveMeta;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate writeMeta(meta: ArchiveMeta): void {\n\t\ttry {\n\t\t\twriteFileSync(this.metaPath, JSON.stringify(meta));\n\t\t} catch {\n\t\t\t// Best-effort\n\t\t}\n\t}\n\n\tprivate cleanupFile(path: string): void {\n\t\ttry {\n\t\t\tif (existsSync(path)) unlinkSync(path);\n\t\t} catch {\n\t\t\t// Best-effort\n\t\t}\n\t}\n\n\t/** Download archive to disk with streaming. Returns Last-Modified header. */\n\tprivate async download(destPath: string): Promise<string | null> {\n\t\tconst res = await fetch(this.archiveUrl, {\n\t\t\tsignal: AbortSignal.timeout(30 * 60 * 1000), // 30 min timeout\n\t\t});\n\n\t\tif (!res.ok || !res.body) {\n\t\t\tthrow new Error(`Archive download failed: HTTP ${res.status}`);\n\t\t}\n\n\t\tconst lastModified = res.headers.get(\"last-modified\");\n\t\tconst totalBytes = Number(res.headers.get(\"content-length\") || 0);\n\t\tconst writer = Bun.file(destPath).writer();\n\t\tlet downloaded = 0;\n\t\tlet lastLog = 0;\n\n\t\tfor await (const chunk of res.body) {\n\t\t\twriter.write(chunk);\n\t\t\tdownloaded += chunk.byteLength;\n\n\t\t\t// Log progress every 5GB\n\t\t\tif (totalBytes > 0 && downloaded - lastLog > 5_000_000_000) {\n\t\t\t\tlastLog = downloaded;\n\t\t\t\tconst pct = ((downloaded / totalBytes) * 100).toFixed(0);\n\t\t\t\tlogger.info(\"Archive replay: downloading\", { progress: `${pct}%` });\n\t\t\t}\n\t\t}\n\n\t\tawait writer.end();\n\t\treturn lastModified;\n\t}\n}\n\n/** Find the nth occurrence of a character in a string */\nfunction nthIndex(str: string, char: string, n: number): number {\n\tlet idx = -1;\n\tfor (let i = 0; i < n; i++) {\n\t\tidx = str.indexOf(char, idx + 1);\n\t\tif (idx === -1) return -1;\n\t}\n\treturn idx;\n}\n",
6
- "import { z } from \"zod\";\n\n// Parse comma-separated networks\nconst networksSchema = z.string().transform((val) => {\n\tconst networks = val\n\t\t.split(\",\")\n\t\t.map((n) => n.trim())\n\t\t.filter(Boolean);\n\tconst valid = [\"mainnet\", \"testnet\"];\n\tfor (const n of networks) {\n\t\tif (!valid.includes(n)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid network: ${n}. Must be one of: ${valid.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\treturn networks as (\"mainnet\" | \"testnet\")[];\n});\n\ninterface EnvSchemaOutput {\n\tDATABASE_URL?: string;\n\t/**\n\t * Shared indexer DB (blocks/txs/events). Falls back to DATABASE_URL.\n\t * Set this alongside TARGET_DATABASE_URL to enable dual-DB mode.\n\t */\n\tSOURCE_DATABASE_URL?: string;\n\t/**\n\t * Tenant DB (subgraph schemas + subgraphs table). Falls back to DATABASE_URL.\n\t * Set this alongside SOURCE_DATABASE_URL to enable dual-DB mode.\n\t */\n\tTARGET_DATABASE_URL?: string;\n\tNETWORK?: \"mainnet\" | \"testnet\";\n\tNETWORKS?: (\"mainnet\" | \"testnet\")[];\n\tLOG_LEVEL: \"debug\" | \"info\" | \"warn\" | \"error\";\n\tNODE_ENV: \"development\" | \"production\" | \"test\";\n}\n\n// Cast needed: z.preprocess / z.default create different _input vs _output types\n// that z.ZodType<T> can't represent without explicit input type param\nconst envSchema: z.ZodType<EnvSchemaOutput> = z.object({\n\tDATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tSOURCE_DATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tTARGET_DATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tNETWORK: z.enum([\"mainnet\", \"testnet\"]).optional(),\n\tNETWORKS: networksSchema.optional(),\n\tLOG_LEVEL: z.enum([\"debug\", \"info\", \"warn\", \"error\"]).default(\"info\"),\n\tNODE_ENV: z\n\t\t.enum([\"development\", \"production\", \"test\"])\n\t\t.default(\"development\"),\n}) as unknown as z.ZodType<EnvSchemaOutput>;\n\nexport type Env = EnvSchemaOutput & {\n\tenabledNetworks: (\"mainnet\" | \"testnet\")[];\n};\n\nlet cachedEnv: Env | null = null;\n\nexport function getEnv(): Env {\n\tif (cachedEnv) {\n\t\treturn cachedEnv;\n\t}\n\n\tconst result = envSchema.safeParse(process.env);\n\n\tif (!result.success) {\n\t\tconsole.error(\"❌ Invalid environment configuration:\");\n\t\tconsole.error(z.treeifyError(result.error));\n\t\tthrow new Error(\"Invalid environment configuration\");\n\t}\n\n\t// Compute enabled networks from NETWORKS or NETWORK\n\tlet enabledNetworks: (\"mainnet\" | \"testnet\")[];\n\tif (result.data.NETWORKS && result.data.NETWORKS.length > 0) {\n\t\tenabledNetworks = result.data.NETWORKS;\n\t} else if (result.data.NETWORK) {\n\t\tenabledNetworks = [result.data.NETWORK];\n\t} else {\n\t\tenabledNetworks = [\"mainnet\"]; // Default\n\t}\n\n\tcachedEnv = { ...result.data, enabledNetworks };\n\treturn cachedEnv;\n}\n\n/**\n * True when `NODE_ENV=production`, read at RUNTIME — the single source of truth\n * for prod-vs-dev branching in this package.\n *\n * Why a helper instead of inlining `process.env.NODE_ENV === \"production\"`:\n * bunup/esbuild constant-folds the dot-access `process.env.NODE_ENV` to its\n * BUILD-time value. `@secondlayer/shared` is consumed by other services as its\n * built `dist`, so an inlined check freezes to a literal in the shipped bundle\n * (e.g. `const isProd = false`) and silently ignores the container's real\n * NODE_ENV. The bracket access below is NOT folded, so it stays a runtime read.\n * Route every prod check through here so the footgun lives in exactly one place.\n */\nexport function isProductionEnv(): boolean {\n\t// biome-ignore lint/complexity/useLiteralKeys: bracket access is deliberate — dot-access gets constant-folded by the bundler, freezing this to a build-time literal in the shipped dist.\n\treturn process.env[\"NODE_ENV\"] === \"production\";\n}\n\ntype EnvMap = NodeJS.ProcessEnv;\n\n/**\n * PoX-4 stacking decoder is ON by default — `/v1/index/stacking` is part of the\n * public surface, so the decoder that fills `pox4_calls` runs unless explicitly\n * opted out with `POX4_DECODER_ENABLED=false` (mirrors the sBTC decoder policy).\n */\nexport function isPox4DecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.POX4_DECODER_ENABLED !== \"false\";\n}\n\n/**\n * PoX-5 print-event decoder is ON by default (mirrors pox-4/sBTC policy).\n * No activation gating needed: pox-5 events cannot exist before the Epoch 4.0\n * hard fork (Bitcoin block 960,230), so pre-activation the decoder idles at\n * tip. Opt out with `POX5_DECODER_ENABLED=false`.\n */\nexport function isPox5DecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.POX5_DECODER_ENABLED !== \"false\";\n}\n\n/** Default ON. Opt out with `SBTC_DECODER_ENABLED=false`. */\nexport function isSbtcDecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.SBTC_DECODER_ENABLED !== \"false\";\n}\n\n/** Default OFF. Opt in with `BNS_DECODER_ENABLED=true`. */\nexport function isBnsDecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.BNS_DECODER_ENABLED === \"true\";\n}\n\n// Export for testing\nexport { envSchema };\n",
7
- "import { getEnv } from \"./env.ts\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n\tdebug: 0,\n\tinfo: 1,\n\twarn: 2,\n\terror: 3,\n};\n\nclass Logger {\n\tprivate _level?: LogLevel;\n\tprivate _isProduction?: boolean;\n\tprivate _initialized = false;\n\n\tprivate init() {\n\t\tif (this._initialized) return;\n\t\tthis._initialized = true;\n\t\ttry {\n\t\t\tconst env = getEnv();\n\t\t\tthis._level = env.LOG_LEVEL;\n\t\t\tthis._isProduction = env.NODE_ENV === \"production\";\n\t\t} catch {\n\t\t\t// Fallback when env is unavailable (e.g. tests without DATABASE_URL)\n\t\t\tthis._level = \"info\";\n\t\t\tthis._isProduction = false;\n\t\t}\n\t}\n\n\tprivate get level(): LogLevel {\n\t\tthis.init();\n\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\treturn this._level!;\n\t}\n\n\tprivate get isProduction(): boolean {\n\t\tthis.init();\n\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\treturn this._isProduction!;\n\t}\n\n\tprivate shouldLog(level: LogLevel): boolean {\n\t\treturn LOG_LEVELS[level] >= LOG_LEVELS[this.level];\n\t}\n\n\tprivate formatMessage(\n\t\tlevel: LogLevel,\n\t\tmessage: string,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tmeta?: Record<string, any>,\n\t) {\n\t\tconst timestamp = new Date().toISOString();\n\n\t\tif (this.isProduction) {\n\t\t\t// JSON output for production\n\t\t\treturn JSON.stringify({\n\t\t\t\ttimestamp,\n\t\t\t\tlevel,\n\t\t\t\tmessage,\n\t\t\t\t...meta,\n\t\t\t});\n\t\t}\n\n\t\t// Human-readable output for development\n\t\tconst metaStr = meta ? ` ${JSON.stringify(meta)}` : \"\";\n\t\treturn `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\tdebug(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"debug\")) {\n\t\t\tconsole.debug(this.formatMessage(\"debug\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\tinfo(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"info\")) {\n\t\t\tconsole.info(this.formatMessage(\"info\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\twarn(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"warn\")) {\n\t\t\tconsole.warn(this.formatMessage(\"warn\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\terror(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"error\")) {\n\t\t\tconsole.error(this.formatMessage(\"error\", message, meta));\n\t\t}\n\t}\n}\n\n// Export singleton instance\nexport const logger: Logger = new Logger();\n"
8
- ],
9
- "mappings": "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",
10
- "debugId": "9E72CC1B9C56340B64756E2164756E21",
11
- "names": []
12
- }