@secondlayer/subgraphs 7.2.0 → 7.4.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.
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@secondlayer/subgraphs",
6
- version: "7.2.0",
6
+ version: "7.4.0",
7
7
  description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
8
8
  license: "MIT",
9
9
  repository: {
@@ -63,7 +63,7 @@ var package_default = {
63
63
  prepublishOnly: "bun run build"
64
64
  },
65
65
  dependencies: {
66
- "@secondlayer/shared": "^13.1.0",
66
+ "@secondlayer/shared": "^13.3.0",
67
67
  "@secondlayer/stacks": "^6.4.0",
68
68
  "@secondlayer/verify": "^0.6.0",
69
69
  esbuild: "^0.28.2",
@@ -279,5 +279,5 @@ export {
279
279
  pinPreimage
280
280
  };
281
281
 
282
- //# debugId=209DE650ED607B4D64756E2164756E21
282
+ //# debugId=967809A9ACF2C51964756E2164756E21
283
283
  //# sourceMappingURL=verification.js.map
@@ -5,6 +5,6 @@
5
5
  "/**\n * Verifiable subgraphs: the determinism contract, the derived level and the\n * pin. Pure (node:crypto only) so the bundler's deploy scan, the API and the\n * runtime realm all read the same rules.\n *\n * A subgraph is never configured as verifiable. Its level is derived from\n * its sources plus a scan of the bundled handler, and only a subgraph whose\n * every input is provable today (`state`: named state writes) has the contract\n * enforced. Everything else gets the same findings as advice.\n */\nimport { createHash } from \"node:crypto\";\nimport type { SubgraphVerification } from \"@secondlayer/shared/schemas/subgraphs\";\nimport pkg from \"../package.json\" with { type: \"json\" };\nimport type { SubgraphDefinition, SubgraphFilter } from \"./types.ts\";\n\nexport type { SubgraphVerification };\n\n/** Runtime identity baked into every pin: matcher, decode and flush semantics. */\nexport const SUBGRAPHS_RUNTIME: string = `@secondlayer/subgraphs@${pkg.version}`;\n\n// ── Determinism contract ────────────────────────────────────────────────\n\n/**\n * Globals a handler may not touch, with the reason shown to the developer.\n * The deploy scan reports them; the realm replaces each with a getter that\n * throws `NondeterminismError`.\n */\nexport const FORBIDDEN_GLOBALS: Readonly<Record<string, string>> = {\n\tDate: \"wall-clock time differs per run\",\n\tperformance: \"timers differ per run\",\n\tprocess: \"host environment differs per run\",\n\tfetch: \"network reads are not chain inputs\",\n\tcrypto: \"randomness differs per run\",\n\tsetTimeout: \"scheduling order differs per run\",\n\tsetInterval: \"scheduling order differs per run\",\n\tsetImmediate: \"scheduling order differs per run\",\n\tclearTimeout: \"scheduling order differs per run\",\n\tclearInterval: \"scheduling order differs per run\",\n\tqueueMicrotask: \"scheduling order differs per run\",\n\tWeakRef: \"garbage collection timing differs per run\",\n\tFinalizationRegistry: \"garbage collection timing differs per run\",\n\tAtomics: \"shared memory is not single-threaded\",\n\tSharedArrayBuffer: \"shared memory is not single-threaded\",\n\tIntl: \"locale and time zone data differ per host\",\n\tTemporal: \"locale and time zone data differ per host\",\n\teval: \"generated code hides inputs from the scan\",\n\tWebAssembly: \"compiled code hides inputs from the scan\",\n\tShadowRealm: \"a nested realm escapes the allow-list\",\n\trequire: \"imports must be bundled into the handler\",\n};\n\n/**\n * Globals a handler may use: ECMAScript built-ins whose results are fully\n * specified, plus deterministic host encoders. `Math` is narrowed separately\n * ({@link ALLOWED_MATH}).\n */\nexport const ALLOWED_GLOBALS: readonly string[] = [\n\t\"undefined\",\n\t\"NaN\",\n\t\"Infinity\",\n\t\"globalThis\",\n\t\"isNaN\",\n\t\"isFinite\",\n\t\"parseInt\",\n\t\"parseFloat\",\n\t\"decodeURI\",\n\t\"decodeURIComponent\",\n\t\"encodeURI\",\n\t\"encodeURIComponent\",\n\t\"escape\",\n\t\"unescape\",\n\t\"Object\",\n\t\"Function\",\n\t\"Array\",\n\t\"Number\",\n\t\"Boolean\",\n\t\"String\",\n\t\"Symbol\",\n\t\"BigInt\",\n\t\"Math\",\n\t\"JSON\",\n\t\"Reflect\",\n\t\"Proxy\",\n\t\"Promise\",\n\t\"Map\",\n\t\"Set\",\n\t\"WeakMap\",\n\t\"WeakSet\",\n\t\"RegExp\",\n\t\"Iterator\",\n\t\"DisposableStack\",\n\t\"AsyncDisposableStack\",\n\t\"Error\",\n\t\"AggregateError\",\n\t\"SuppressedError\",\n\t\"EvalError\",\n\t\"RangeError\",\n\t\"ReferenceError\",\n\t\"SyntaxError\",\n\t\"TypeError\",\n\t\"URIError\",\n\t\"ArrayBuffer\",\n\t\"DataView\",\n\t\"Int8Array\",\n\t\"Uint8Array\",\n\t\"Uint8ClampedArray\",\n\t\"Int16Array\",\n\t\"Uint16Array\",\n\t\"Int32Array\",\n\t\"Uint32Array\",\n\t\"Float16Array\",\n\t\"Float32Array\",\n\t\"Float64Array\",\n\t\"BigInt64Array\",\n\t\"BigUint64Array\",\n\t\"TextEncoder\",\n\t\"TextDecoder\",\n\t\"atob\",\n\t\"btoa\",\n\t\"console\",\n];\n\n/** `Math` members whose results are exact on every engine. The rest\n * (`sin`, `pow`, `log`, …) are implementation-approximated. */\nexport const ALLOWED_MATH: readonly string[] = [\n\t\"abs\",\n\t\"min\",\n\t\"max\",\n\t\"floor\",\n\t\"ceil\",\n\t\"trunc\",\n\t\"sign\",\n\t\"round\",\n\t\"E\",\n\t\"LN10\",\n\t\"LN2\",\n\t\"LOG10E\",\n\t\"LOG2E\",\n\t\"PI\",\n\t\"SQRT1_2\",\n\t\"SQRT2\",\n];\n\n/** Methods whose output depends on the host's locale data. */\nexport const LOCALE_METHODS: readonly string[] = [\n\t\"toLocaleString\",\n\t\"toLocaleDateString\",\n\t\"toLocaleTimeString\",\n\t\"toLocaleUpperCase\",\n\t\"toLocaleLowerCase\",\n\t\"localeCompare\",\n];\n\n/**\n * One thing the deploy scan found in a bundled handler.\n *\n * - `nondeterministic`: breaks the determinism contract. Fails the deploy of\n * a state subgraph; advice for everything else.\n * - `needs-events`: deterministic, but reads something only re-execution proofs cover\n * (`ctx.client`). Raises the level instead of failing.\n */\nexport interface HandlerFinding {\n\tkind: \"nondeterministic\" | \"needs-events\";\n\t/** What was used, e.g. `Date`, `Math.random`, `import()`, `ctx.client`. */\n\tname: string;\n\treason: string;\n\t/** Original source file when a sourcemap was available, else the bundle. */\n\tfile: string;\n\t/** 1-based. */\n\tline: number;\n\t/** 1-based. */\n\tcolumn: number;\n}\n\n/** Formats a handler finding as `file:line:column name: reason`. */\nexport function formatFinding(f: HandlerFinding): string {\n\treturn `${f.file}:${f.line}:${f.column} ${f.name}: ${f.reason}`;\n}\n\n// ── Derived level ───────────────────────────────────────────────────────\n\n/**\n * Sources whose every field is a named state write: provable per block from\n * the header-backed witness plus `state_writes`. Not `map_insert`: storage\n * holds `(some v)` for an insert and a set alike, so telling them apart takes\n * re-execution (event proofs).\n */\nconst STATE_SOURCE_TYPES: ReadonlySet<string> = new Set([\n\t\"var_set\",\n\t\"map_set\",\n\t\"map_delete\",\n]);\n\nconst LEVEL_RANK = { state: 0, events: 1, none: 2 } as const;\n\n/** Handler findings listed in `reasons`; the rest are counted. */\nconst MAX_LISTED_FINDINGS = 10;\n\n/**\n * Derive how far a subgraph's rows can be checked. Level = the highest any\n * source or handler read needs. `reasons` lists everything standing between\n * the subgraph and checkable rows; a `state` subgraph with any reason is\n * refused at deploy, so every stored `state` row has none. Never configured,\n * never a flag.\n */\nexport function deriveVerification(\n\tdef: Pick<SubgraphDefinition, \"sources\" | \"backfillMode\">,\n\tfindings: readonly HandlerFinding[] = [],\n): SubgraphVerification {\n\tlet level: keyof typeof LEVEL_RANK = \"state\";\n\tconst raise = (to: keyof typeof LEVEL_RANK) => {\n\t\tif (LEVEL_RANK[to] > LEVEL_RANK[level]) level = to;\n\t};\n\tconst reasons: string[] = [];\n\n\tfor (const [name, source] of Object.entries(def.sources ?? {})) {\n\t\tconst filter = source as SubgraphFilter & { trait?: string };\n\t\tif (filter.trait) {\n\t\t\traise(\"none\");\n\t\t\treasons.push(\n\t\t\t\t`source \"${name}\": trait scope needs a proven contract registry`,\n\t\t\t);\n\t\t} else if (filter.type === \"map_insert\") {\n\t\t\traise(\"events\");\n\t\t\treasons.push(\n\t\t\t\t`map_insert source \"${name}\": map_insert vs map_set needs event proofs`,\n\t\t\t);\n\t\t} else if (!STATE_SOURCE_TYPES.has(filter.type)) {\n\t\t\traise(\"events\");\n\t\t\treasons.push(`${filter.type} source \"${name}\" needs event proofs`);\n\t\t}\n\t}\n\tif (def.backfillMode === \"concurrent\") {\n\t\traise(\"none\");\n\t\treasons.push(\"backfillMode concurrent: tip-first order is not chain order\");\n\t}\n\tif (findings.some((f) => f.kind === \"needs-events\")) raise(\"events\");\n\t// A large bundled library can trip dozens of findings; list a few.\n\tfor (const f of findings.slice(0, MAX_LISTED_FINDINGS)) {\n\t\treasons.push(\n\t\t\tf.kind === \"needs-events\"\n\t\t\t\t? `${formatFinding(f)} (needs event proofs)`\n\t\t\t\t: formatFinding(f),\n\t\t);\n\t}\n\tif (findings.length > MAX_LISTED_FINDINGS) {\n\t\treasons.push(\n\t\t\t`…and ${findings.length - MAX_LISTED_FINDINGS} more handler findings`,\n\t\t);\n\t}\n\n\treturn {\n\t\tlevel,\n\t\treasons,\n\t\tunproven:\n\t\t\treasons.length === 0\n\t\t\t\t? [\n\t\t\t\t\t\t\"tx attribution of writes: event.tx, _tx_id (needs event proofs)\",\n\t\t\t\t\t\t\"intermediate writes within a block (needs event proofs)\",\n\t\t\t\t\t]\n\t\t\t\t: [],\n\t};\n}\n\n/**\n * Tells whether a deploy must be refused for breaking determinism.\n *\n * It is refused when the sources are provable (`state`), so the determinism\n * contract is enforced, and the scan broke it.\n */\nexport function isDeterminismViolation(v: SubgraphVerification): boolean {\n\treturn v.level === \"state\" && v.reasons.length > 0;\n}\n\n// ── Pin ─────────────────────────────────────────────────────────────────\n\n/** What a pin is computed from: schema hash, handler code, network. */\nexport interface PinInput {\n\t/** Existing `schema_hash` (name + schema + sources). */\n\tschemaHash: string;\n\t/** Bundled ESM exactly as stored in `subgraphs.handler_code`. */\n\thandlerCode: string;\n\tstartBlock?: number | null;\n\tnetwork: string;\n\t/** Defaults to {@link SUBGRAPHS_RUNTIME}. */\n\truntime?: string;\n}\n\nfunction sha256(input: string): string {\n\treturn createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\n/** What a pin commits to, as stored in `pin_preimage`. */\nexport interface PinFields {\n\t/** sha256 of the bundled handler, hex. */\n\thandlerHash: string;\n\tnetwork: string;\n\truntime: string;\n\tschemaHash: string;\n\tstartBlock: number | null;\n}\n\n/**\n * The canonical JSON a pin is the sha256 of: fixed key order, primitives\n * only. Stored beside the pin so anyone can recompute it from the served\n * handler code.\n */\nexport function pinPreimage(input: PinInput): string {\n\tconst fields: PinFields = {\n\t\thandlerHash: sha256(input.handlerCode),\n\t\tnetwork: input.network,\n\t\truntime: input.runtime ?? SUBGRAPHS_RUNTIME,\n\t\tschemaHash: input.schemaHash,\n\t\tstartBlock: input.startBlock ?? null,\n\t};\n\treturn JSON.stringify(fields);\n}\n\n/**\n * Content hash over everything that shapes a subgraph's rows. Unlike\n * `schema_hash`, it changes with the handler, `startBlock` and runtime, so\n * two deploys with the same pin produce the same rows from the same chain.\n */\nexport function computePin(input: PinInput): string {\n\treturn sha256(pinPreimage(input));\n}\n"
6
6
  ],
7
7
  "mappings": "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",
8
- "debugId": "209DE650ED607B4D64756E2164756E21",
8
+ "debugId": "967809A9ACF2C51964756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -475,7 +475,7 @@ import { createHash } from "node:crypto";
475
475
  // package.json
476
476
  var package_default = {
477
477
  name: "@secondlayer/subgraphs",
478
- version: "7.2.0",
478
+ version: "7.4.0",
479
479
  description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
480
480
  license: "MIT",
481
481
  repository: {
@@ -535,7 +535,7 @@ var package_default = {
535
535
  prepublishOnly: "bun run build"
536
536
  },
537
537
  dependencies: {
538
- "@secondlayer/shared": "^13.1.0",
538
+ "@secondlayer/shared": "^13.3.0",
539
539
  "@secondlayer/stacks": "^6.4.0",
540
540
  "@secondlayer/verify": "^0.6.0",
541
541
  esbuild: "^0.28.2",
@@ -1990,9 +1990,17 @@ function generateSubgraphSQL(def, schemaNameOverride) {
1990
1990
  }
1991
1991
 
1992
1992
  // src/runtime/chain-read.ts
1993
+ import { getErrorMessage as getErrorMessage2 } from "@secondlayer/shared";
1994
+ import { chainReadArgsHash } from "@secondlayer/shared/chain-read";
1993
1995
  import { getSourceDb } from "@secondlayer/shared/db";
1996
+ import { hasNoLocalChain } from "@secondlayer/shared/db/queries/instance";
1994
1997
  import { logger as logger3 } from "@secondlayer/shared/logger";
1995
- import { http, createPublicClient } from "@secondlayer/stacks";
1998
+ import {
1999
+ http,
2000
+ HttpRequestError,
2001
+ ReadContractError,
2002
+ createPublicClient
2003
+ } from "@secondlayer/stacks";
1996
2004
  import {
1997
2005
  ContractResponseError,
1998
2006
  buildFunctionArgs,
@@ -2013,9 +2021,48 @@ function readContractAt(ctx, contractId, abi, options) {
2013
2021
  class ChainReadError extends Error {
2014
2022
  name = "ChainReadError";
2015
2023
  }
2016
- function nodeClient(rpcUrl) {
2024
+ var NO_NODE_MESSAGE = "ctx.client needs a Stacks node: set STACKS_NODE_RPC_URL on the subgraph processor.";
2025
+ async function resolveReadTarget(rpcUrl, db) {
2026
+ if (rpcUrl)
2027
+ return { kind: "node", rpcUrl };
2028
+ const apiUrl = process.env.SUBGRAPH_INDEX_API_URL;
2029
+ if (!apiUrl || !await hasNoLocalChain(db)) {
2030
+ throw new HandlerReadError(NO_NODE_MESSAGE);
2031
+ }
2032
+ const apiKey = process.env.INDEX_INTERNAL_API_KEY?.trim();
2033
+ if (!apiKey) {
2034
+ throw new HandlerReadError("Hosted contract reads need the processor's key (INDEX_INTERNAL_API_KEY), and none is set. Set STACKS_NODE_RPC_URL to read a node directly instead.");
2035
+ }
2036
+ return { kind: "hosted", apiUrl, apiKey };
2037
+ }
2038
+ function hostedReadError(err, label) {
2039
+ if (!(err instanceof HttpRequestError))
2040
+ return;
2041
+ if (err.status === 400) {
2042
+ return new ChainReadError(`${label} was refused as invalid: ${err.details ?? err.message}`);
2043
+ }
2044
+ if (err.status === 403) {
2045
+ return new HandlerReadError(`${label}: hosted contract reads serve hosted subgraphs only. ${NO_NODE_MESSAGE}`, { cause: err });
2046
+ }
2047
+ return;
2048
+ }
2049
+ function isChainAnswer(err) {
2050
+ return err instanceof ContractResponseError || err instanceof ReadContractError;
2051
+ }
2052
+ function readClient(target) {
2053
+ if (target.kind === "node") {
2054
+ return createPublicClient({
2055
+ transport: http(target.rpcUrl, { retryCount: 0, timeout: 1e4 })
2056
+ });
2057
+ }
2017
2058
  return createPublicClient({
2018
- transport: http(rpcUrl, { retryCount: 0, timeout: 1e4 })
2059
+ transport: http(`${target.apiUrl.replace(/\/+$/, "")}/v1/chain-read`, {
2060
+ retryCount: 3,
2061
+ timeout: 1e4,
2062
+ fetchOptions: {
2063
+ headers: { authorization: `Bearer ${target.apiKey}` }
2064
+ }
2065
+ })
2019
2066
  });
2020
2067
  }
2021
2068
 
@@ -2047,8 +2094,7 @@ function maxConcurrentReads() {
2047
2094
  }
2048
2095
  var gate = new ConcurrencyGate(maxConcurrentReads());
2049
2096
  function hashArgs(args) {
2050
- const serialized = args.map((arg) => serializeCV(arg)).join(",");
2051
- return Bun.hash(serialized).toString(16);
2097
+ return chainReadArgsHash(args.map((arg) => serializeCV(arg)));
2052
2098
  }
2053
2099
  function findFunction(abi, name) {
2054
2100
  const fn = abi.functions.find((f) => f.name === name);
@@ -2062,46 +2108,62 @@ function findFunction(abi, name) {
2062
2108
  }
2063
2109
  function createChainReadClient(ctx) {
2064
2110
  const readValue = async (contractId, abi, functionName, args, options) => {
2065
- const rpcUrl = ctx.rpcUrl ?? process.env.STACKS_NODE_RPC_URL;
2066
- if (!rpcUrl) {
2067
- throw new ChainReadError("ctx.client needs a Stacks node: set STACKS_NODE_RPC_URL on the subgraph processor.");
2111
+ const db = ctx.db ?? getSourceDb();
2112
+ const rpcUrl = ctx.rpcUrl || process.env.STACKS_NODE_RPC_URL || undefined;
2113
+ if (!rpcUrl && !process.env.SUBGRAPH_INDEX_API_URL) {
2114
+ throw new HandlerReadError(NO_NODE_MESSAGE);
2068
2115
  }
2069
2116
  const constant = options.cache === "contract-constant";
2070
2117
  if (!constant && !ctx.indexBlockHash) {
2071
- throw new ChainReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a read cannot be pinned to it. Reading at the node's tip would make this handler non-deterministic (the same reindex would produce different rows). Re-ingest the block, or pass cache: "contract-constant" if the value genuinely cannot change.`);
2118
+ throw new HandlerReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a read cannot be pinned to it. Reading at the node's tip would make this handler non-deterministic (the same reindex would produce different rows). Re-ingest the block, or pass cache: "contract-constant" if the value genuinely cannot change.`);
2072
2119
  }
2073
2120
  const fn = findFunction(abi, functionName);
2074
2121
  const clarityArgs = buildFunctionArgs(fn, args);
2075
- const db = ctx.db ?? getSourceDb();
2076
2122
  const argsHash = hashArgs(clarityArgs);
2077
2123
  const pinnedTo = constant ? null : ctx.indexBlockHash;
2078
- const cached = await db.selectFrom("chain_read_cache").select("result_hex").where("contract_id", "=", contractId).where("function_name", "=", fn.name).where("args_hash", "=", argsHash).where((eb) => pinnedTo === null ? eb("index_block_hash", "is", null) : eb("index_block_hash", "=", pinnedTo)).executeTakeFirst();
2079
- const resultHex = cached?.result_hex ?? await gate.run(async () => {
2080
- if (!ctx.indexBlockHash) {
2081
- logger3.warn("Chain read is not pinned to a block (no index_block_hash)", {
2082
- contractId,
2083
- functionName: fn.name,
2084
- blockHeight: ctx.blockHeight,
2085
- note: 'cache: "contract-constant" asserts this value cannot change; the node answered at its own tip'
2086
- });
2087
- }
2088
- const value = await readContract(nodeClient(rpcUrl), {
2089
- contract: contractId,
2090
- functionName: fn.name,
2091
- args: clarityArgs,
2092
- tip: ctx.indexBlockHash ?? undefined
2124
+ let resultHex;
2125
+ try {
2126
+ const cached = await db.selectFrom("chain_read_cache").select("result_hex").where("contract_id", "=", contractId).where("function_name", "=", fn.name).where("args_hash", "=", argsHash).where((eb) => pinnedTo === null ? eb("index_block_hash", "is", null) : eb("index_block_hash", "=", pinnedTo)).executeTakeFirst();
2127
+ resultHex = cached?.result_hex ?? await gate.run(async () => {
2128
+ const target = await resolveReadTarget(rpcUrl, db);
2129
+ if (!ctx.indexBlockHash && target.kind === "hosted") {
2130
+ throw new HandlerReadError(`Block ${ctx.blockHeight} has no index_block_hash, so a hosted contract read cannot be pinned to it. Reindex the subgraph from this block once the block is re-ingested.`);
2131
+ }
2132
+ if (!ctx.indexBlockHash) {
2133
+ logger3.warn("Chain read is not pinned to a block (no index_block_hash)", {
2134
+ contractId,
2135
+ functionName: fn.name,
2136
+ blockHeight: ctx.blockHeight,
2137
+ note: 'cache: "contract-constant" asserts this value cannot change; the node answered at its own tip'
2138
+ });
2139
+ }
2140
+ try {
2141
+ const value = await readContract(readClient(target), {
2142
+ contract: contractId,
2143
+ functionName: fn.name,
2144
+ args: clarityArgs,
2145
+ tip: ctx.indexBlockHash ?? undefined
2146
+ });
2147
+ return serializeCV(value);
2148
+ } catch (err) {
2149
+ throw target.kind === "hosted" && hostedReadError(err, `Chain read ${contractId}::${fn.name}`) || err;
2150
+ }
2093
2151
  });
2094
- return serializeCV(value);
2095
- });
2096
- if (!cached) {
2097
- await db.insertInto("chain_read_cache").values({
2098
- contract_id: contractId,
2099
- function_name: fn.name,
2100
- args_hash: argsHash,
2101
- index_block_hash: pinnedTo,
2102
- block_height: constant ? null : ctx.blockHeight,
2103
- result_hex: resultHex
2104
- }).onConflict((oc) => oc.doNothing()).execute();
2152
+ if (!cached) {
2153
+ await db.insertInto("chain_read_cache").values({
2154
+ contract_id: contractId,
2155
+ function_name: fn.name,
2156
+ args_hash: argsHash,
2157
+ index_block_hash: pinnedTo,
2158
+ block_height: constant ? null : ctx.blockHeight,
2159
+ result_hex: resultHex
2160
+ }).onConflict((oc) => oc.doNothing()).execute();
2161
+ }
2162
+ } catch (err) {
2163
+ if (isChainAnswer(err) || err instanceof ChainReadError || err instanceof HandlerReadError) {
2164
+ throw err;
2165
+ }
2166
+ throw new HandlerReadError(`Chain read ${contractId}::${fn.name} at block ${ctx.blockHeight} failed: ${getErrorMessage2(err)}`, { cause: err });
2105
2167
  }
2106
2168
  const value = deserializeCV4(resultHex);
2107
2169
  const js = clarityValueToJSUntyped(fn.outputs, value);
@@ -3403,5 +3465,5 @@ export {
3403
3465
  replaySubgraph
3404
3466
  };
3405
3467
 
3406
- //# debugId=D57747572721831064756E2164756E21
3468
+ //# debugId=73562E7C772D0E6364756E2164756E21
3407
3469
  //# sourceMappingURL=index.js.map