@secondlayer/shared 13.0.0 → 13.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/src/archive/range-digest.d.ts +1 -1
  2. package/dist/src/archive/semantic-digest-builder.d.ts +1 -1
  3. package/dist/src/chain-read.d.ts +13 -0
  4. package/dist/src/chain-read.js +12 -0
  5. package/dist/src/chain-read.js.map +10 -0
  6. package/dist/src/coverage/index.d.ts +1 -1
  7. package/dist/src/db/index.d.ts +1 -1
  8. package/dist/src/db/queries/chain-reorgs.d.ts +1 -1
  9. package/dist/src/db/queries/contracts.d.ts +3 -1
  10. package/dist/src/db/queries/contracts.js +4 -1
  11. package/dist/src/db/queries/contracts.js.map +3 -3
  12. package/dist/src/db/queries/instance.d.ts +13 -2
  13. package/dist/src/db/queries/instance.js +16 -1
  14. package/dist/src/db/queries/instance.js.map +3 -3
  15. package/dist/src/db/queries/integrity.d.ts +1 -1
  16. package/dist/src/db/queries/subgraph-gaps.d.ts +1 -1
  17. package/dist/src/db/queries/subgraph-operations.d.ts +1 -1
  18. package/dist/src/db/queries/subgraphs.d.ts +1 -1
  19. package/dist/src/db/queries/sync-scope.d.ts +1 -1
  20. package/dist/src/db/queries/webhooks.d.ts +1 -1
  21. package/dist/src/db/schema.d.ts +1 -1
  22. package/dist/src/index-http.d.ts +76 -1
  23. package/dist/src/index-http.js +38 -1
  24. package/dist/src/index-http.js.map +3 -3
  25. package/dist/src/index.d.ts +3 -1
  26. package/dist/src/index.js +8 -2
  27. package/dist/src/index.js.map +3 -3
  28. package/dist/src/node/local-client.d.ts +1 -1
  29. package/dist/src/schemas/index.d.ts +2 -0
  30. package/dist/src/schemas/index.js +8 -2
  31. package/dist/src/schemas/index.js.map +3 -3
  32. package/dist/src/schemas/webhooks.d.ts +4 -1
  33. package/dist/src/schemas/webhooks.js +9 -2
  34. package/dist/src/schemas/webhooks.js.map +3 -3
  35. package/migrations/0165_api_keys_tenant_product.ts +49 -0
  36. package/package.json +6 -2
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Cache key for a contract read's arguments: SHA-256 over the hex-serialized
3
+ * Clarity args (no `0x`, lowercase), joined by `,`.
4
+ *
5
+ * One function for both sides of a hosted read: the subgraph runtime hashes
6
+ * the values it serialized, the hosted chain-read route hashes the hex it
7
+ * received, and both land on the same `chain_read_cache.args_hash`. A
8
+ * collision-resistant hash, because the hosted cache is shared across
9
+ * tenants: with a weak hash, one tenant could craft args that collide with
10
+ * another's read and pin a wrong answer to it.
11
+ */
12
+ declare function chainReadArgsHash(hexArgs: readonly string[]): string;
13
+ export { chainReadArgsHash };
@@ -0,0 +1,12 @@
1
+ // src/chain-read.ts
2
+ import { createHash } from "node:crypto";
3
+ function chainReadArgsHash(hexArgs) {
4
+ const normalized = hexArgs.map((arg) => arg.replace(/^0x/i, "").toLowerCase()).join(",");
5
+ return createHash("sha256").update(normalized).digest("hex");
6
+ }
7
+ export {
8
+ chainReadArgsHash
9
+ };
10
+
11
+ //# debugId=578F03C6DABB150A64756E2164756E21
12
+ //# sourceMappingURL=chain-read.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/chain-read.ts"],
4
+ "sourcesContent": [
5
+ "import { createHash } from \"node:crypto\";\n\n/**\n * Cache key for a contract read's arguments: SHA-256 over the hex-serialized\n * Clarity args (no `0x`, lowercase), joined by `,`.\n *\n * One function for both sides of a hosted read: the subgraph runtime hashes\n * the values it serialized, the hosted chain-read route hashes the hex it\n * received, and both land on the same `chain_read_cache.args_hash`. A\n * collision-resistant hash, because the hosted cache is shared across\n * tenants: with a weak hash, one tenant could craft args that collide with\n * another's read and pin a wrong answer to it.\n */\nexport function chainReadArgsHash(hexArgs: readonly string[]): string {\n\tconst normalized = hexArgs\n\t\t.map((arg) => arg.replace(/^0x/i, \"\").toLowerCase())\n\t\t.join(\",\");\n\treturn createHash(\"sha256\").update(normalized).digest(\"hex\");\n}\n"
6
+ ],
7
+ "mappings": ";AAAA;AAaO,SAAS,iBAAiB,CAAC,SAAoC;AAAA,EACrE,MAAM,aAAa,QACjB,IAAI,CAAC,QAAQ,IAAI,QAAQ,QAAQ,EAAE,EAAE,YAAY,CAAC,EAClD,KAAK,GAAG;AAAA,EACV,OAAO,WAAW,QAAQ,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK;AAAA;",
8
+ "debugId": "578F03C6DABB150A64756E2164756E21",
9
+ "names": []
10
+ }
@@ -601,7 +601,7 @@ interface ApiKeysTable {
601
601
  rate_limit: Generated<number>;
602
602
  ip_address: string;
603
603
  account_id: string;
604
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
604
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
605
605
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
606
606
  areas: unknown | null;
607
607
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -285,7 +285,7 @@ interface ApiKeysTable {
285
285
  rate_limit: Generated<number>;
286
286
  ip_address: string;
287
287
  account_id: string;
288
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
289
289
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
290
290
  areas: unknown | null;
291
291
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -1172,6 +1172,8 @@ declare function listContractsByTrait(db: Kysely<Database>, trait: string, opts?
1172
1172
  conformance?: Conformance
1173
1173
  limit?: number
1174
1174
  afterId?: string
1175
+ /** Only contracts classified (ABI stored) at or after this time. */
1176
+ classifiedAfter?: Date
1175
1177
  }): Promise<Contract[]>;
1176
1178
  /**
1177
1179
  * As-of-block trait resolution (B4): contract IDs conforming to `trait` whose
@@ -57,6 +57,9 @@ async function listContractsByTrait(db, trait, opts = {}) {
57
57
  }
58
58
  if (opts.afterId)
59
59
  q = q.where("contract_id", ">", opts.afterId);
60
+ if (opts.classifiedAfter) {
61
+ q = q.where("abi_fetched_at", ">=", opts.classifiedAfter);
62
+ }
60
63
  return q.orderBy("contract_id", "asc").limit(opts.limit ?? 100).execute();
61
64
  }
62
65
  async function resolveTraitContractIds(db, trait, asOfBlock) {
@@ -76,5 +79,5 @@ export {
76
79
  setContractAbi
77
80
  };
78
81
 
79
- //# debugId=E2278282AC73418664756E2164756E21
82
+ //# debugId=41DCA798C00E09D764756E2164756E21
80
83
  //# sourceMappingURL=contracts.js.map
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/queries/contracts.ts", "../src/db/jsonb.ts"],
4
4
  "sourcesContent": [
5
- "import { type Kysely, sql } from \"kysely\";\nimport { jsonb } from \"../jsonb.ts\";\nimport type { Contract, Database } from \"../types.ts\";\n\n/**\n * Contract registry queries — backing for trait-based discovery. Populated from\n * contract deploys; ABI fetched async; standards inferred by static analysis.\n */\n\nexport type AbiStatus = \"pending\" | \"fetched\" | \"failed\" | \"unparseable\";\n\n/**\n * Record a contract deploy (idempotent). ABI is fetched separately + async.\n * On conflict the row is re-canonicalized rather than skipped: after a reorg\n * flips contracts non-canonical (markContractsNonCanonical), discovery\n * re-selects any id whose deploy tx exists on the new fork and this upsert\n * restores `canonical` with the fork's height. ABI fields are untouched, so a\n * previously fetched ABI survives the round trip.\n */\nexport async function recordContractDeploy(\n\tdb: Kysely<Database>,\n\trow: { contractId: string; deployer: string; blockHeight: number },\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"contracts\")\n\t\t.values({\n\t\t\tcontract_id: row.contractId,\n\t\t\tdeployer: row.deployer,\n\t\t\tblock_height: row.blockHeight,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.column(\"contract_id\").doUpdateSet({\n\t\t\t\tdeployer: row.deployer,\n\t\t\t\tblock_height: row.blockHeight,\n\t\t\t\tcanonical: true,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\n/** Store a fetched ABI + the traits/standards derived from it. */\nexport async function setContractAbi(\n\tdb: Kysely<Database>,\n\tcontractId: string,\n\tdata: {\n\t\tabi: unknown | null;\n\t\tstatus: AbiStatus;\n\t\tdeclaredTraits?: string[];\n\t\tinferredStandards?: string[];\n\t},\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"contracts\")\n\t\t.set({\n\t\t\tabi: data.abi == null ? null : jsonb(data.abi),\n\t\t\tabi_status: data.status,\n\t\t\tabi_fetched_at: new Date(),\n\t\t\t...(data.declaredTraits ? { declared_traits: data.declaredTraits } : {}),\n\t\t\t...(data.inferredStandards\n\t\t\t\t? { inferred_standards: data.inferredStandards }\n\t\t\t\t: {}),\n\t\t})\n\t\t.where(\"contract_id\", \"=\", contractId)\n\t\t.execute();\n}\n\n/**\n * A contract reorged out and not (yet) redeployed on the new fork is not on\n * the canonical chain — serve 404, not its stale row/ABI. Matches every other\n * contracts read (list, trait scope, pending-ABI), which already filter.\n */\nexport async function getContract(\n\tdb: Kysely<Database>,\n\tcontractId: string,\n): Promise<Contract | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"contracts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"contract_id\", \"=\", contractId)\n\t\t\t.where(\"canonical\", \"=\", true)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\n/** Contracts still awaiting an ABI fetch (drives the fetch/backfill worker). */\nexport async function listContractsPendingAbi(\n\tdb: Kysely<Database>,\n\tlimit = 100,\n): Promise<Contract[]> {\n\treturn db\n\t\t.selectFrom(\"contracts\")\n\t\t.selectAll()\n\t\t.where(\"abi_status\", \"=\", \"pending\")\n\t\t.where(\"canonical\", \"=\", true)\n\t\t.orderBy(\"block_height\", \"asc\")\n\t\t.limit(limit)\n\t\t.execute();\n}\n\nexport type Conformance = \"declared\" | \"inferred\" | \"any\";\n\n/** Discovery: contracts matching a trait/standard, by conformance source. */\nexport async function listContractsByTrait(\n\tdb: Kysely<Database>,\n\ttrait: string,\n\topts: { conformance?: Conformance; limit?: number; afterId?: string } = {},\n): Promise<Contract[]> {\n\tconst conformance = opts.conformance ?? \"any\";\n\tlet q = db.selectFrom(\"contracts\").selectAll().where(\"canonical\", \"=\", true);\n\n\tif (conformance === \"declared\") {\n\t\tq = q.where(sql<boolean>`${sql.ref(\"declared_traits\")} @> ARRAY[${trait}]`);\n\t} else if (conformance === \"inferred\") {\n\t\tq = q.where(\n\t\t\tsql<boolean>`${sql.ref(\"inferred_standards\")} @> ARRAY[${trait}]`,\n\t\t);\n\t} else {\n\t\tq = q.where(\n\t\t\tsql<boolean>`(${sql.ref(\"declared_traits\")} @> ARRAY[${trait}] OR ${sql.ref(\"inferred_standards\")} @> ARRAY[${trait}])`,\n\t\t);\n\t}\n\n\tif (opts.afterId) q = q.where(\"contract_id\", \">\", opts.afterId);\n\treturn q\n\t\t.orderBy(\"contract_id\", \"asc\")\n\t\t.limit(opts.limit ?? 100)\n\t\t.execute();\n}\n\n/**\n * As-of-block trait resolution (B4): contract IDs conforming to `trait` whose\n * deploy block ≤ `asOfBlock`. Lets a trait-scoped subgraph reindex a token's\n * full history even if classification lagged its deploy.\n */\nexport async function resolveTraitContractIds(\n\tdb: Kysely<Database>,\n\ttrait: string,\n\tasOfBlock: number,\n): Promise<string[]> {\n\tconst rows = await db\n\t\t.selectFrom(\"contracts\")\n\t\t.select(\"contract_id\")\n\t\t.where(\"canonical\", \"=\", true)\n\t\t.where(\"block_height\", \"<=\", asOfBlock)\n\t\t.where(\n\t\t\tsql<boolean>`(${sql.ref(\"declared_traits\")} @> ARRAY[${trait}] OR ${sql.ref(\"inferred_standards\")} @> ARRAY[${trait}])`,\n\t\t)\n\t\t.execute();\n\treturn rows.map((r) => r.contract_id);\n}\n\n/**\n * Reorg: flip contracts deployed at/above a reorged height to non-canonical.\n * Called from the indexer's handleReorg tx. Recovery is eventual: discovery\n * (discoverDeploys) re-selects non-canonical ids whose deploy tx exists on\n * the new fork and recordContractDeploy re-canonicalizes them.\n */\nexport async function markContractsNonCanonical(\n\tdb: Kysely<Database>,\n\tfromBlockHeight: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"contracts\")\n\t\t.set({ canonical: false })\n\t\t.where(\"block_height\", \">=\", fromBlockHeight)\n\t\t.execute();\n}\n",
5
+ "import { type Kysely, sql } from \"kysely\";\nimport { jsonb } from \"../jsonb.ts\";\nimport type { Contract, Database } from \"../types.ts\";\n\n/**\n * Contract registry queries — backing for trait-based discovery. Populated from\n * contract deploys; ABI fetched async; standards inferred by static analysis.\n */\n\nexport type AbiStatus = \"pending\" | \"fetched\" | \"failed\" | \"unparseable\";\n\n/**\n * Record a contract deploy (idempotent). ABI is fetched separately + async.\n * On conflict the row is re-canonicalized rather than skipped: after a reorg\n * flips contracts non-canonical (markContractsNonCanonical), discovery\n * re-selects any id whose deploy tx exists on the new fork and this upsert\n * restores `canonical` with the fork's height. ABI fields are untouched, so a\n * previously fetched ABI survives the round trip.\n */\nexport async function recordContractDeploy(\n\tdb: Kysely<Database>,\n\trow: { contractId: string; deployer: string; blockHeight: number },\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"contracts\")\n\t\t.values({\n\t\t\tcontract_id: row.contractId,\n\t\t\tdeployer: row.deployer,\n\t\t\tblock_height: row.blockHeight,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.column(\"contract_id\").doUpdateSet({\n\t\t\t\tdeployer: row.deployer,\n\t\t\t\tblock_height: row.blockHeight,\n\t\t\t\tcanonical: true,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\n/** Store a fetched ABI + the traits/standards derived from it. */\nexport async function setContractAbi(\n\tdb: Kysely<Database>,\n\tcontractId: string,\n\tdata: {\n\t\tabi: unknown | null;\n\t\tstatus: AbiStatus;\n\t\tdeclaredTraits?: string[];\n\t\tinferredStandards?: string[];\n\t},\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"contracts\")\n\t\t.set({\n\t\t\tabi: data.abi == null ? null : jsonb(data.abi),\n\t\t\tabi_status: data.status,\n\t\t\tabi_fetched_at: new Date(),\n\t\t\t...(data.declaredTraits ? { declared_traits: data.declaredTraits } : {}),\n\t\t\t...(data.inferredStandards\n\t\t\t\t? { inferred_standards: data.inferredStandards }\n\t\t\t\t: {}),\n\t\t})\n\t\t.where(\"contract_id\", \"=\", contractId)\n\t\t.execute();\n}\n\n/**\n * A contract reorged out and not (yet) redeployed on the new fork is not on\n * the canonical chain — serve 404, not its stale row/ABI. Matches every other\n * contracts read (list, trait scope, pending-ABI), which already filter.\n */\nexport async function getContract(\n\tdb: Kysely<Database>,\n\tcontractId: string,\n): Promise<Contract | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"contracts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"contract_id\", \"=\", contractId)\n\t\t\t.where(\"canonical\", \"=\", true)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\n/** Contracts still awaiting an ABI fetch (drives the fetch/backfill worker). */\nexport async function listContractsPendingAbi(\n\tdb: Kysely<Database>,\n\tlimit = 100,\n): Promise<Contract[]> {\n\treturn db\n\t\t.selectFrom(\"contracts\")\n\t\t.selectAll()\n\t\t.where(\"abi_status\", \"=\", \"pending\")\n\t\t.where(\"canonical\", \"=\", true)\n\t\t.orderBy(\"block_height\", \"asc\")\n\t\t.limit(limit)\n\t\t.execute();\n}\n\nexport type Conformance = \"declared\" | \"inferred\" | \"any\";\n\n/** Discovery: contracts matching a trait/standard, by conformance source. */\nexport async function listContractsByTrait(\n\tdb: Kysely<Database>,\n\ttrait: string,\n\topts: {\n\t\tconformance?: Conformance;\n\t\tlimit?: number;\n\t\tafterId?: string;\n\t\t/** Only contracts classified (ABI stored) at or after this time. */\n\t\tclassifiedAfter?: Date;\n\t} = {},\n): Promise<Contract[]> {\n\tconst conformance = opts.conformance ?? \"any\";\n\tlet q = db.selectFrom(\"contracts\").selectAll().where(\"canonical\", \"=\", true);\n\n\tif (conformance === \"declared\") {\n\t\tq = q.where(sql<boolean>`${sql.ref(\"declared_traits\")} @> ARRAY[${trait}]`);\n\t} else if (conformance === \"inferred\") {\n\t\tq = q.where(\n\t\t\tsql<boolean>`${sql.ref(\"inferred_standards\")} @> ARRAY[${trait}]`,\n\t\t);\n\t} else {\n\t\tq = q.where(\n\t\t\tsql<boolean>`(${sql.ref(\"declared_traits\")} @> ARRAY[${trait}] OR ${sql.ref(\"inferred_standards\")} @> ARRAY[${trait}])`,\n\t\t);\n\t}\n\n\tif (opts.afterId) q = q.where(\"contract_id\", \">\", opts.afterId);\n\tif (opts.classifiedAfter) {\n\t\tq = q.where(\"abi_fetched_at\", \">=\", opts.classifiedAfter);\n\t}\n\treturn q\n\t\t.orderBy(\"contract_id\", \"asc\")\n\t\t.limit(opts.limit ?? 100)\n\t\t.execute();\n}\n\n/**\n * As-of-block trait resolution (B4): contract IDs conforming to `trait` whose\n * deploy block ≤ `asOfBlock`. Lets a trait-scoped subgraph reindex a token's\n * full history even if classification lagged its deploy.\n */\nexport async function resolveTraitContractIds(\n\tdb: Kysely<Database>,\n\ttrait: string,\n\tasOfBlock: number,\n): Promise<string[]> {\n\tconst rows = await db\n\t\t.selectFrom(\"contracts\")\n\t\t.select(\"contract_id\")\n\t\t.where(\"canonical\", \"=\", true)\n\t\t.where(\"block_height\", \"<=\", asOfBlock)\n\t\t.where(\n\t\t\tsql<boolean>`(${sql.ref(\"declared_traits\")} @> ARRAY[${trait}] OR ${sql.ref(\"inferred_standards\")} @> ARRAY[${trait}])`,\n\t\t)\n\t\t.execute();\n\treturn rows.map((r) => r.contract_id);\n}\n\n/**\n * Reorg: flip contracts deployed at/above a reorged height to non-canonical.\n * Called from the indexer's handleReorg tx. Recovery is eventual: discovery\n * (discoverDeploys) re-selects non-canonical ids whose deploy tx exists on\n * the new fork and recordContractDeploy re-canonicalizes them.\n */\nexport async function markContractsNonCanonical(\n\tdb: Kysely<Database>,\n\tfromBlockHeight: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"contracts\")\n\t\t.set({ canonical: false })\n\t\t.where(\"block_height\", \">=\", fromBlockHeight)\n\t\t.execute();\n}\n",
6
6
  "import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n *\n * Generic parameter lets callers set the RawBuilder's output type so they\n * don't need to cast at the insert site. Default is `unknown` — widen at\n * the call site when the column type is narrower, e.g. `jsonb<MyShape>(...)`.\n */\nexport function jsonb<T = unknown>(value: T): RawBuilder<T> {\n\tconst escaped = JSON.stringify(value, (_k, v) =>\n\t\ttypeof v === \"bigint\" ? v.toString() : v,\n\t).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "E2278282AC73418664756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "41DCA798C00E09D764756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -1162,4 +1162,15 @@ declare function getInstance(db: Kysely<Database>): Promise<InstanceRow | null>;
1162
1162
  declare function ensureInstance(db: Kysely<Database>, input: {
1163
1163
  network: InstanceNetwork
1164
1164
  }): Promise<InstanceRow>;
1165
- export { INSTANCE_NETWORKS, InstanceNetwork, InstanceNetworkMismatchError, InstanceRow, InvalidInstanceNetworkError, ensureInstance, getInstance, instanceNetworkFromEnv, parseInstanceNetwork };
1165
+ /**
1166
+ * A stack with no local chain: it reads chain data from a hosted Index/Streams
1167
+ * API (`SUBGRAPH_INDEX_API_URL` set) and its own `index_progress` has no row
1168
+ * for this network (a hosted tenant). Derived, not configured: a self-hoster
1169
+ * running a local indexer has the row and keeps reading its own tables, even
1170
+ * with its processors pointed at its own api over HTTP.
1171
+ */
1172
+ declare function hasNoLocalChain(db: Kysely<Database>, opts?: {
1173
+ env?: NodeJS.ProcessEnv
1174
+ hasProgressRow?: (network: string) => Promise<boolean>
1175
+ }): Promise<boolean>;
1176
+ export { INSTANCE_NETWORKS, InstanceNetwork, InstanceNetworkMismatchError, InstanceRow, InvalidInstanceNetworkError, ensureInstance, getInstance, hasNoLocalChain, instanceNetworkFromEnv, parseInstanceNetwork };
@@ -62,15 +62,30 @@ function toInstance(row) {
62
62
  created_at: row.created_at
63
63
  };
64
64
  }
65
+ async function hasNoLocalChain(db, opts = {}) {
66
+ const env = opts.env ?? process.env;
67
+ if (!env.SUBGRAPH_INDEX_API_URL)
68
+ return false;
69
+ const network = env.NETWORK ?? "mainnet";
70
+ const hasRow = opts.hasProgressRow ?? ((n) => hasLocalProgressRow(db, n));
71
+ return !await hasRow(network);
72
+ }
73
+ async function hasLocalProgressRow(db, network) {
74
+ const row = await db.selectFrom("index_progress").select("network").where("network", "=", network).executeTakeFirst().catch(() => {
75
+ return;
76
+ });
77
+ return row !== undefined;
78
+ }
65
79
  export {
66
80
  INSTANCE_NETWORKS,
67
81
  InstanceNetworkMismatchError,
68
82
  InvalidInstanceNetworkError,
69
83
  ensureInstance,
70
84
  getInstance,
85
+ hasNoLocalChain,
71
86
  instanceNetworkFromEnv,
72
87
  parseInstanceNetwork
73
88
  };
74
89
 
75
- //# debugId=2C46A70E1317C50664756E2164756E21
90
+ //# debugId=CD5DDEF4E82D8D8764756E2164756E21
76
91
  //# sourceMappingURL=instance.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/queries/instance.ts"],
4
4
  "sourcesContent": [
5
- "import type { Kysely } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport const INSTANCE_NETWORKS = [\"mainnet\", \"testnet\", \"devnet\"] as const;\nexport type InstanceNetwork = (typeof INSTANCE_NETWORKS)[number];\n\nexport type InstanceRow = {\n\tid: string;\n\tnetwork: InstanceNetwork;\n\tcreated_at: Date;\n};\n\nexport class InstanceNetworkMismatchError extends Error {\n\treadonly name = \"InstanceNetworkMismatchError\";\n\tconstructor(\n\t\treadonly existing: InstanceNetwork,\n\t\treadonly requested: InstanceNetwork,\n\t) {\n\t\tsuper(`instance network is ${existing}; cannot change to ${requested}`);\n\t}\n}\n\nexport class InvalidInstanceNetworkError extends Error {\n\treadonly name = \"InvalidInstanceNetworkError\";\n\tconstructor(readonly value: string) {\n\t\tsuper(\n\t\t\t`instance network must be mainnet, testnet, or devnet (got ${value})`,\n\t\t);\n\t}\n}\n\nexport function parseInstanceNetwork(value: string): InstanceNetwork {\n\tconst network = value.trim().toLowerCase();\n\tif ((INSTANCE_NETWORKS as readonly string[]).includes(network)) {\n\t\treturn network as InstanceNetwork;\n\t}\n\tthrow new InvalidInstanceNetworkError(value);\n}\n\n/** STACKS_NETWORK, then NETWORK, then mainnet. */\nexport function instanceNetworkFromEnv(\n\tenv: NodeJS.ProcessEnv = process.env,\n): InstanceNetwork {\n\treturn parseInstanceNetwork(env.STACKS_NETWORK ?? env.NETWORK ?? \"mainnet\");\n}\n\nexport async function getInstance(\n\tdb: Kysely<Database>,\n): Promise<InstanceRow | null> {\n\tconst row = await db\n\t\t.selectFrom(\"instances\")\n\t\t.select([\"id\", \"network\", \"created_at\"])\n\t\t.executeTakeFirst();\n\treturn row ? toInstance(row) : null;\n}\n\n/**\n * Return the singleton instance, creating it on first call.\n * A second call with a different network is refused — the network is\n * immutable for the life of the database.\n */\nexport async function ensureInstance(\n\tdb: Kysely<Database>,\n\tinput: { network: InstanceNetwork },\n): Promise<InstanceRow> {\n\tconst existing = await getInstance(db);\n\tif (existing) {\n\t\tif (existing.network !== input.network) {\n\t\t\tthrow new InstanceNetworkMismatchError(existing.network, input.network);\n\t\t}\n\t\treturn existing;\n\t}\n\ttry {\n\t\tconst inserted = await db\n\t\t\t.insertInto(\"instances\")\n\t\t\t.values({ network: input.network })\n\t\t\t.returning([\"id\", \"network\", \"created_at\"])\n\t\t\t.executeTakeFirstOrThrow();\n\t\treturn toInstance(inserted);\n\t} catch {\n\t\t// Lost the singleton race — the other writer won.\n\t\tconst raced = await getInstance(db);\n\t\tif (!raced) throw new Error(\"failed to create instance\");\n\t\tif (raced.network !== input.network) {\n\t\t\tthrow new InstanceNetworkMismatchError(raced.network, input.network);\n\t\t}\n\t\treturn raced;\n\t}\n}\n\nfunction toInstance(row: {\n\tid: string;\n\tnetwork: string;\n\tcreated_at: Date;\n}): InstanceRow {\n\treturn {\n\t\tid: row.id,\n\t\tnetwork: parseInstanceNetwork(row.network),\n\t\tcreated_at: row.created_at,\n\t};\n}\n"
5
+ "import type { Kysely } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport const INSTANCE_NETWORKS = [\"mainnet\", \"testnet\", \"devnet\"] as const;\nexport type InstanceNetwork = (typeof INSTANCE_NETWORKS)[number];\n\nexport type InstanceRow = {\n\tid: string;\n\tnetwork: InstanceNetwork;\n\tcreated_at: Date;\n};\n\nexport class InstanceNetworkMismatchError extends Error {\n\treadonly name = \"InstanceNetworkMismatchError\";\n\tconstructor(\n\t\treadonly existing: InstanceNetwork,\n\t\treadonly requested: InstanceNetwork,\n\t) {\n\t\tsuper(`instance network is ${existing}; cannot change to ${requested}`);\n\t}\n}\n\nexport class InvalidInstanceNetworkError extends Error {\n\treadonly name = \"InvalidInstanceNetworkError\";\n\tconstructor(readonly value: string) {\n\t\tsuper(\n\t\t\t`instance network must be mainnet, testnet, or devnet (got ${value})`,\n\t\t);\n\t}\n}\n\nexport function parseInstanceNetwork(value: string): InstanceNetwork {\n\tconst network = value.trim().toLowerCase();\n\tif ((INSTANCE_NETWORKS as readonly string[]).includes(network)) {\n\t\treturn network as InstanceNetwork;\n\t}\n\tthrow new InvalidInstanceNetworkError(value);\n}\n\n/** STACKS_NETWORK, then NETWORK, then mainnet. */\nexport function instanceNetworkFromEnv(\n\tenv: NodeJS.ProcessEnv = process.env,\n): InstanceNetwork {\n\treturn parseInstanceNetwork(env.STACKS_NETWORK ?? env.NETWORK ?? \"mainnet\");\n}\n\nexport async function getInstance(\n\tdb: Kysely<Database>,\n): Promise<InstanceRow | null> {\n\tconst row = await db\n\t\t.selectFrom(\"instances\")\n\t\t.select([\"id\", \"network\", \"created_at\"])\n\t\t.executeTakeFirst();\n\treturn row ? toInstance(row) : null;\n}\n\n/**\n * Return the singleton instance, creating it on first call.\n * A second call with a different network is refused — the network is\n * immutable for the life of the database.\n */\nexport async function ensureInstance(\n\tdb: Kysely<Database>,\n\tinput: { network: InstanceNetwork },\n): Promise<InstanceRow> {\n\tconst existing = await getInstance(db);\n\tif (existing) {\n\t\tif (existing.network !== input.network) {\n\t\t\tthrow new InstanceNetworkMismatchError(existing.network, input.network);\n\t\t}\n\t\treturn existing;\n\t}\n\ttry {\n\t\tconst inserted = await db\n\t\t\t.insertInto(\"instances\")\n\t\t\t.values({ network: input.network })\n\t\t\t.returning([\"id\", \"network\", \"created_at\"])\n\t\t\t.executeTakeFirstOrThrow();\n\t\treturn toInstance(inserted);\n\t} catch {\n\t\t// Lost the singleton race — the other writer won.\n\t\tconst raced = await getInstance(db);\n\t\tif (!raced) throw new Error(\"failed to create instance\");\n\t\tif (raced.network !== input.network) {\n\t\t\tthrow new InstanceNetworkMismatchError(raced.network, input.network);\n\t\t}\n\t\treturn raced;\n\t}\n}\n\nfunction toInstance(row: {\n\tid: string;\n\tnetwork: string;\n\tcreated_at: Date;\n}): InstanceRow {\n\treturn {\n\t\tid: row.id,\n\t\tnetwork: parseInstanceNetwork(row.network),\n\t\tcreated_at: row.created_at,\n\t};\n}\n\n/**\n * A stack with no local chain: it reads chain data from a hosted Index/Streams\n * API (`SUBGRAPH_INDEX_API_URL` set) and its own `index_progress` has no row\n * for this network (a hosted tenant). Derived, not configured: a self-hoster\n * running a local indexer has the row and keeps reading its own tables, even\n * with its processors pointed at its own api over HTTP.\n */\nexport async function hasNoLocalChain(\n\tdb: Kysely<Database>,\n\topts: {\n\t\tenv?: NodeJS.ProcessEnv;\n\t\thasProgressRow?: (network: string) => Promise<boolean>;\n\t} = {},\n): Promise<boolean> {\n\tconst env = opts.env ?? process.env;\n\tif (!env.SUBGRAPH_INDEX_API_URL) return false;\n\tconst network = env.NETWORK ?? \"mainnet\";\n\tconst hasRow =\n\t\topts.hasProgressRow ?? ((n: string) => hasLocalProgressRow(db, n));\n\treturn !(await hasRow(network));\n}\n\nasync function hasLocalProgressRow(\n\tdb: Kysely<Database>,\n\tnetwork: string,\n): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"index_progress\")\n\t\t.select(\"network\")\n\t\t.where(\"network\", \"=\", network)\n\t\t.executeTakeFirst()\n\t\t.catch(() => undefined);\n\treturn row !== undefined;\n}\n"
6
6
  ],
7
- "mappings": ";AAGO,IAAM,oBAAoB,CAAC,WAAW,WAAW,QAAQ;AAAA;AASzD,MAAM,qCAAqC,MAAM;AAAA,EAG7C;AAAA,EACA;AAAA,EAHD,OAAO;AAAA,EAChB,WAAW,CACD,UACA,WACR;AAAA,IACD,MAAM,uBAAuB,8BAA8B,WAAW;AAAA,IAH7D;AAAA,IACA;AAAA;AAIX;AAAA;AAEO,MAAM,oCAAoC,MAAM;AAAA,EAEjC;AAAA,EADZ,OAAO;AAAA,EAChB,WAAW,CAAU,OAAe;AAAA,IACnC,MACC,6DAA6D,QAC9D;AAAA,IAHoB;AAAA;AAKtB;AAEO,SAAS,oBAAoB,CAAC,OAAgC;AAAA,EACpE,MAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AAAA,EACzC,IAAK,kBAAwC,SAAS,OAAO,GAAG;AAAA,IAC/D,OAAO;AAAA,EACR;AAAA,EACA,MAAM,IAAI,4BAA4B,KAAK;AAAA;AAIrC,SAAS,sBAAsB,CACrC,MAAyB,QAAQ,KACf;AAAA,EAClB,OAAO,qBAAqB,IAAI,kBAAkB,IAAI,WAAW,SAAS;AAAA;AAG3E,eAAsB,WAAW,CAChC,IAC8B;AAAA,EAC9B,MAAM,MAAM,MAAM,GAChB,WAAW,WAAW,EACtB,OAAO,CAAC,MAAM,WAAW,YAAY,CAAC,EACtC,iBAAiB;AAAA,EACnB,OAAO,MAAM,WAAW,GAAG,IAAI;AAAA;AAQhC,eAAsB,cAAc,CACnC,IACA,OACuB;AAAA,EACvB,MAAM,WAAW,MAAM,YAAY,EAAE;AAAA,EACrC,IAAI,UAAU;AAAA,IACb,IAAI,SAAS,YAAY,MAAM,SAAS;AAAA,MACvC,MAAM,IAAI,6BAA6B,SAAS,SAAS,MAAM,OAAO;AAAA,IACvE;AAAA,IACA,OAAO;AAAA,EACR;AAAA,EACA,IAAI;AAAA,IACH,MAAM,WAAW,MAAM,GACrB,WAAW,WAAW,EACtB,OAAO,EAAE,SAAS,MAAM,QAAQ,CAAC,EACjC,UAAU,CAAC,MAAM,WAAW,YAAY,CAAC,EACzC,wBAAwB;AAAA,IAC1B,OAAO,WAAW,QAAQ;AAAA,IACzB,MAAM;AAAA,IAEP,MAAM,QAAQ,MAAM,YAAY,EAAE;AAAA,IAClC,IAAI,CAAC;AAAA,MAAO,MAAM,IAAI,MAAM,2BAA2B;AAAA,IACvD,IAAI,MAAM,YAAY,MAAM,SAAS;AAAA,MACpC,MAAM,IAAI,6BAA6B,MAAM,SAAS,MAAM,OAAO;AAAA,IACpE;AAAA,IACA,OAAO;AAAA;AAAA;AAIT,SAAS,UAAU,CAAC,KAIJ;AAAA,EACf,OAAO;AAAA,IACN,IAAI,IAAI;AAAA,IACR,SAAS,qBAAqB,IAAI,OAAO;AAAA,IACzC,YAAY,IAAI;AAAA,EACjB;AAAA;",
8
- "debugId": "2C46A70E1317C50664756E2164756E21",
7
+ "mappings": ";AAGO,IAAM,oBAAoB,CAAC,WAAW,WAAW,QAAQ;AAAA;AASzD,MAAM,qCAAqC,MAAM;AAAA,EAG7C;AAAA,EACA;AAAA,EAHD,OAAO;AAAA,EAChB,WAAW,CACD,UACA,WACR;AAAA,IACD,MAAM,uBAAuB,8BAA8B,WAAW;AAAA,IAH7D;AAAA,IACA;AAAA;AAIX;AAAA;AAEO,MAAM,oCAAoC,MAAM;AAAA,EAEjC;AAAA,EADZ,OAAO;AAAA,EAChB,WAAW,CAAU,OAAe;AAAA,IACnC,MACC,6DAA6D,QAC9D;AAAA,IAHoB;AAAA;AAKtB;AAEO,SAAS,oBAAoB,CAAC,OAAgC;AAAA,EACpE,MAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AAAA,EACzC,IAAK,kBAAwC,SAAS,OAAO,GAAG;AAAA,IAC/D,OAAO;AAAA,EACR;AAAA,EACA,MAAM,IAAI,4BAA4B,KAAK;AAAA;AAIrC,SAAS,sBAAsB,CACrC,MAAyB,QAAQ,KACf;AAAA,EAClB,OAAO,qBAAqB,IAAI,kBAAkB,IAAI,WAAW,SAAS;AAAA;AAG3E,eAAsB,WAAW,CAChC,IAC8B;AAAA,EAC9B,MAAM,MAAM,MAAM,GAChB,WAAW,WAAW,EACtB,OAAO,CAAC,MAAM,WAAW,YAAY,CAAC,EACtC,iBAAiB;AAAA,EACnB,OAAO,MAAM,WAAW,GAAG,IAAI;AAAA;AAQhC,eAAsB,cAAc,CACnC,IACA,OACuB;AAAA,EACvB,MAAM,WAAW,MAAM,YAAY,EAAE;AAAA,EACrC,IAAI,UAAU;AAAA,IACb,IAAI,SAAS,YAAY,MAAM,SAAS;AAAA,MACvC,MAAM,IAAI,6BAA6B,SAAS,SAAS,MAAM,OAAO;AAAA,IACvE;AAAA,IACA,OAAO;AAAA,EACR;AAAA,EACA,IAAI;AAAA,IACH,MAAM,WAAW,MAAM,GACrB,WAAW,WAAW,EACtB,OAAO,EAAE,SAAS,MAAM,QAAQ,CAAC,EACjC,UAAU,CAAC,MAAM,WAAW,YAAY,CAAC,EACzC,wBAAwB;AAAA,IAC1B,OAAO,WAAW,QAAQ;AAAA,IACzB,MAAM;AAAA,IAEP,MAAM,QAAQ,MAAM,YAAY,EAAE;AAAA,IAClC,IAAI,CAAC;AAAA,MAAO,MAAM,IAAI,MAAM,2BAA2B;AAAA,IACvD,IAAI,MAAM,YAAY,MAAM,SAAS;AAAA,MACpC,MAAM,IAAI,6BAA6B,MAAM,SAAS,MAAM,OAAO;AAAA,IACpE;AAAA,IACA,OAAO;AAAA;AAAA;AAIT,SAAS,UAAU,CAAC,KAIJ;AAAA,EACf,OAAO;AAAA,IACN,IAAI,IAAI;AAAA,IACR,SAAS,qBAAqB,IAAI,OAAO;AAAA,IACzC,YAAY,IAAI;AAAA,EACjB;AAAA;AAUD,eAAsB,eAAe,CACpC,IACA,OAGI,CAAC,GACc;AAAA,EACnB,MAAM,MAAM,KAAK,OAAO,QAAQ;AAAA,EAChC,IAAI,CAAC,IAAI;AAAA,IAAwB,OAAO;AAAA,EACxC,MAAM,UAAU,IAAI,WAAW;AAAA,EAC/B,MAAM,SACL,KAAK,mBAAmB,CAAC,MAAc,oBAAoB,IAAI,CAAC;AAAA,EACjE,OAAO,CAAE,MAAM,OAAO,OAAO;AAAA;AAG9B,eAAe,mBAAmB,CACjC,IACA,SACmB;AAAA,EACnB,MAAM,MAAM,MAAM,GAChB,WAAW,gBAAgB,EAC3B,OAAO,SAAS,EAChB,MAAM,WAAW,KAAK,OAAO,EAC7B,iBAAiB,EACjB,MAAM,MAAG;AAAA,IAAG;AAAA,GAAS;AAAA,EACvB,OAAO,QAAQ;AAAA;",
8
+ "debugId": "CD5DDEF4E82D8D8764756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -294,7 +294,7 @@ interface ApiKeysTable {
294
294
  rate_limit: Generated<number>;
295
295
  ip_address: string;
296
296
  account_id: string;
297
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
297
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
298
298
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
299
299
  areas: unknown | null;
300
300
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -284,7 +284,7 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -283,7 +283,7 @@ interface ApiKeysTable {
283
283
  rate_limit: Generated<number>;
284
284
  ip_address: string;
285
285
  account_id: string;
286
- product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
286
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs" | "tenant">;
287
287
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
288
288
  areas: unknown | null;
289
289
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
@@ -202,6 +202,64 @@ type IndexStateWriteRow = {
202
202
  tx_contract_id?: string | null
203
203
  tx_function_name?: string | null
204
204
  };
205
+ /** A `/v1/index/sbtc/events` row: one decoded sBTC protocol event. Numeric
206
+ * columns are JSON numbers here (the API normalizes them). */
207
+ type IndexSbtcEventRow = {
208
+ cursor: string
209
+ block_height: number
210
+ block_time?: string | null
211
+ tx_id: string
212
+ tx_index: number
213
+ event_index: number
214
+ topic: string
215
+ request_id: number | null
216
+ amount: string | null
217
+ sender: string | null
218
+ recipient_btc_version: number | null
219
+ recipient_btc_hashbytes: string | null
220
+ bitcoin_txid: string | null
221
+ output_index: number | null
222
+ sweep_txid: string | null
223
+ burn_hash: string | null
224
+ burn_height: number | null
225
+ signer_bitmap: string | null
226
+ max_fee: string | null
227
+ fee: string | null
228
+ governance_contract_type: number | null
229
+ governance_new_contract: string | null
230
+ signer_aggregate_pubkey: string | null
231
+ signer_threshold: number | null
232
+ signer_address: string | null
233
+ signer_keys_count: number | null
234
+ };
235
+ /** A `/v1/contracts?trait=` row (ABI omitted). */
236
+ type IndexContractRow = {
237
+ contract_id: string
238
+ deployer: string
239
+ block_height: number
240
+ declared_traits: string[] | null
241
+ inferred_standards: string[] | null
242
+ abi_status: string
243
+ /** When the contract's traits were last set (server clock). */
244
+ classified_at: string | null
245
+ };
246
+ /** A `/v1/index/sbtc/withdrawals` row: one peg-out rolled up per request. */
247
+ type IndexSbtcWithdrawalRow = {
248
+ cursor: string
249
+ request_id: number
250
+ status: "REQUESTED" | "ACCEPTED" | "REJECTED"
251
+ amount: string | null
252
+ sender: string | null
253
+ recipient_btc_version: number | null
254
+ recipient_btc_hashbytes: string | null
255
+ sweep_txid: string | null
256
+ settlement_confirmed: boolean | null
257
+ btc_confirmations: number | null
258
+ btc_block_height: number | null
259
+ confirmed_at: string | null
260
+ requested_at?: string | null
261
+ resolved_at?: string | null
262
+ };
205
263
  type StreamsReorgRow = {
206
264
  id: string
207
265
  detected_at: string
@@ -285,6 +343,23 @@ declare class IndexHttpClient {
285
343
  /** Lowest height the Index serves `state_writes` for, or null when it has
286
344
  * none (its node does not deliver them). One row. */
287
345
  firstStateWriteHeight(): Promise<number | null>;
346
+ /**
347
+ * Canonical sBTC protocol events over [fromHeight, toHeight] for `topics`,
348
+ * ordered by (block_height, event_index). One drained walk per topic (the
349
+ * endpoint filters on a single topic); `requestId` narrows each walk.
350
+ */
351
+ fetchSbtcEvents(fromHeight: number, toHeight: number, topics: readonly string[], opts?: {
352
+ requestId?: number
353
+ }): Promise<IndexSbtcEventRow[]>;
354
+ /** Every canonical contract conforming to `trait` (declared or inferred),
355
+ * draining `/v1/contracts`'s `next_cursor`. `classifiedAfter` limits it to
356
+ * contracts classified at or after that time. */
357
+ listContractsByTrait(trait: string, opts?: {
358
+ classifiedAfter?: Date
359
+ }): Promise<IndexContractRow[]>;
360
+ /** Withdrawals whose BTC sweep crossed the confirmation threshold strictly
361
+ * after `confirmedAfter`, across all history. */
362
+ fetchSettledWithdrawals(confirmedAfter: Date): Promise<IndexSbtcWithdrawalRow[]>;
288
363
  walkTransactions(fromHeight: number, toHeight: number): Promise<IndexTransactionRow[]>;
289
364
  /** Canonical tip height from the Streams clock. */
290
365
  getStreamsTip(): Promise<number>;
@@ -369,4 +444,4 @@ declare class IndexHttpClient {
369
444
  }
370
445
  /** Decoder + subgraph processor client. One env contract; omits bearer when no key. */
371
446
  declare function createInternalIndexHttpClient(): IndexHttpClient;
372
- export { BillingPausedCode, BillingPausedError, IndexBlockRow, IndexEventRow, IndexHttpClient, IndexHttpOptions, IndexHttpStatusError, IndexStateWriteRow, IndexTipEnvelope, IndexTransactionRow, MAX_INDEX_WAIT_SECONDS, StreamsReorgRow, createInternalIndexHttpClient };
447
+ export { BillingPausedCode, BillingPausedError, IndexBlockRow, IndexContractRow, IndexEventRow, IndexHttpClient, IndexHttpOptions, IndexHttpStatusError, IndexSbtcEventRow, IndexSbtcWithdrawalRow, IndexStateWriteRow, IndexTipEnvelope, IndexTransactionRow, MAX_INDEX_WAIT_SECONDS, StreamsReorgRow, createInternalIndexHttpClient };
@@ -78,6 +78,8 @@ function billingPausedFrom(status, body, message) {
78
78
  } catch {}
79
79
  return null;
80
80
  }
81
+ var CONTRACTS_PAGE_LIMIT = 500;
82
+ var THROUGH_TIP = Number.MAX_SAFE_INTEGER;
81
83
 
82
84
  class IndexHttpClient {
83
85
  indexBaseUrl;
@@ -189,6 +191,41 @@ class IndexHttpClient {
189
191
  const { items } = await this.getPage("/v1/index/state-writes", "state_writes", new URLSearchParams({ from_height: "0", limit: "1" }));
190
192
  return items[0]?.block_height ?? null;
191
193
  }
194
+ async fetchSbtcEvents(fromHeight, toHeight, topics, opts) {
195
+ const out = [];
196
+ for (const topic of topics) {
197
+ out.push(...await this.walk("/v1/index/sbtc/events", "events", fromHeight, toHeight, {
198
+ topic,
199
+ ...opts?.requestId !== undefined ? { request_id: String(opts.requestId) } : {}
200
+ }));
201
+ }
202
+ return out.sort((a, b) => a.block_height - b.block_height || a.event_index - b.event_index);
203
+ }
204
+ async listContractsByTrait(trait, opts = {}) {
205
+ const out = [];
206
+ let cursor = null;
207
+ do {
208
+ const params = new URLSearchParams({
209
+ trait,
210
+ limit: String(CONTRACTS_PAGE_LIMIT)
211
+ });
212
+ if (opts.classifiedAfter) {
213
+ params.set("classified_after", opts.classifiedAfter.toISOString());
214
+ }
215
+ if (cursor)
216
+ params.set("cursor", cursor);
217
+ const { items, next_cursor } = await this.getPage("/v1/contracts", "contracts", params);
218
+ out.push(...items);
219
+ cursor = next_cursor;
220
+ } while (cursor);
221
+ return out;
222
+ }
223
+ fetchSettledWithdrawals(confirmedAfter) {
224
+ return this.walk("/v1/index/sbtc/withdrawals", "withdrawals", 0, THROUGH_TIP, {
225
+ settlement_confirmed: "true",
226
+ confirmed_after: confirmedAfter.toISOString()
227
+ });
228
+ }
192
229
  walkTransactions(fromHeight, toHeight) {
193
230
  return this.walk("/v1/index/transactions", "transactions", fromHeight, toHeight);
194
231
  }
@@ -283,5 +320,5 @@ export {
283
320
  createInternalIndexHttpClient
284
321
  };
285
322
 
286
- //# debugId=3E907135EB430D7E64756E2164756E21
323
+ //# debugId=53AE43019E152E4064756E2164756E21
287
324
  //# sourceMappingURL=index-http.js.map