@secondlayer/shared 6.8.1 → 6.10.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/db/index.d.ts +47 -1
  2. package/dist/src/db/index.js +42 -3
  3. package/dist/src/db/index.js.map +3 -3
  4. package/dist/src/db/queries/chain-reorgs.d.ts +36 -0
  5. package/dist/src/db/queries/chain-reorgs.js +41 -3
  6. package/dist/src/db/queries/chain-reorgs.js.map +3 -3
  7. package/dist/src/db/queries/contracts.d.ts +783 -0
  8. package/dist/src/db/queries/contracts.js +90 -0
  9. package/dist/src/db/queries/contracts.js.map +11 -0
  10. package/dist/src/db/queries/integrity.d.ts +36 -0
  11. package/dist/src/db/queries/subgraph-gaps.d.ts +36 -0
  12. package/dist/src/db/queries/subgraph-operations.d.ts +36 -0
  13. package/dist/src/db/queries/subgraphs.d.ts +58 -1
  14. package/dist/src/db/queries/subgraphs.js +390 -6
  15. package/dist/src/db/queries/subgraphs.js.map +9 -4
  16. package/dist/src/db/queries/subscriptions.d.ts +36 -0
  17. package/dist/src/db/schema.d.ts +40 -1
  18. package/dist/src/index.d.ts +55 -1
  19. package/dist/src/index.js +45 -4
  20. package/dist/src/index.js.map +4 -4
  21. package/dist/src/node/client.d.ts +2 -0
  22. package/dist/src/node/client.js +11 -1
  23. package/dist/src/node/client.js.map +3 -3
  24. package/dist/src/node/local-client.d.ts +36 -0
  25. package/dist/src/schemas/index.d.ts +8 -0
  26. package/dist/src/schemas/index.js +4 -2
  27. package/dist/src/schemas/index.js.map +3 -3
  28. package/dist/src/schemas/subgraphs.d.ts +8 -0
  29. package/dist/src/schemas/subgraphs.js +4 -2
  30. package/dist/src/schemas/subgraphs.js.map +3 -3
  31. package/migrations/0066_public_l2_decoded_events.ts +4 -2
  32. package/migrations/0067_product_usage_counters.ts +4 -2
  33. package/migrations/0081_subgraphs_database_url_enc.ts +16 -0
  34. package/migrations/0082_contracts_registry.ts +44 -0
  35. package/migrations/0083_burnchain_rewards.ts +63 -0
  36. package/package.json +6 -2
@@ -10,6 +10,14 @@ interface DeploySubgraphRequest {
10
10
  startBlock?: number;
11
11
  /** Original TypeScript source, persisted so chat can read/diff/edit later. */
12
12
  sourceCode?: string;
13
+ /**
14
+ * BYO data plane: a user-owned Postgres connection string. When set, the
15
+ * subgraph's schema, handler writes, and serving reads live in this DB instead
16
+ * of the managed one. Stored encrypted at rest, never returned.
17
+ */
18
+ databaseUrl?: string;
19
+ /** Validate the connection + print the DDL/grant plan without deploying. */
20
+ dryRun?: boolean;
13
21
  }
14
22
  declare const DeploySubgraphRequestSchema: z.ZodType<DeploySubgraphRequest>;
15
23
  interface DeploySubgraphResponse {
@@ -24,11 +24,13 @@ var DeploySubgraphRequestSchema = z.object({
24
24
  schema: z.record(z.string(), z.unknown()),
25
25
  handlerCode: z.string().max(1048576, "handler code exceeds 1MB limit"),
26
26
  startBlock: z.number().int().nonnegative().optional(),
27
- sourceCode: z.string().max(1048576, "source code exceeds 1MB limit").optional()
27
+ sourceCode: z.string().max(1048576, "source code exceeds 1MB limit").optional(),
28
+ databaseUrl: z.string().url().refine((u) => u.startsWith("postgres://") || u.startsWith("postgresql://"), "must be a postgres:// connection string").optional(),
29
+ dryRun: z.boolean().optional()
28
30
  });
29
31
  export {
30
32
  DeploySubgraphRequestSchema
31
33
  };
32
34
 
33
- //# debugId=AA56811DA75420EF64756E2164756E21
35
+ //# debugId=914B1C03F7A3693864756E2164756E21
34
36
  //# sourceMappingURL=subgraphs.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/schemas/subgraphs.ts"],
4
4
  "sourcesContent": [
5
- "import { z } from \"zod/v4\";\n\n// ── Deploy Subgraph Request ─────────────────────────────────────────────────\n\nexport interface DeploySubgraphRequest {\n\tname: string;\n\tversion?: string;\n\tdescription?: string;\n\tsources: Record<string, Record<string, unknown>>;\n\tschema: Record<string, unknown>;\n\thandlerCode: string;\n\t/** Override the definition's startBlock for this deploy only. */\n\tstartBlock?: number;\n\t/** Original TypeScript source, persisted so chat can read/diff/edit later. */\n\tsourceCode?: string;\n}\n\nexport const DeploySubgraphRequestSchema: z.ZodType<DeploySubgraphRequest> =\n\tz.object({\n\t\tname: z\n\t\t\t.string()\n\t\t\t.regex(/^[a-z0-9-]+$/, \"lowercase alphanumeric + hyphens only\")\n\t\t\t.max(63),\n\t\tversion: z.string().optional(),\n\t\tdescription: z.string().optional(),\n\t\tsources: z\n\t\t\t.record(z.string(), z.record(z.string(), z.unknown()))\n\t\t\t.refine(\n\t\t\t\t(s) => Object.keys(s).length > 0,\n\t\t\t\t\"Must have at least one source\",\n\t\t\t),\n\t\tschema: z.record(z.string(), z.unknown()),\n\t\thandlerCode: z.string().max(1_048_576, \"handler code exceeds 1MB limit\"),\n\t\tstartBlock: z.number().int().nonnegative().optional(),\n\t\tsourceCode: z\n\t\t\t.string()\n\t\t\t.max(1_048_576, \"source code exceeds 1MB limit\")\n\t\t\t.optional(),\n\t});\n\nexport interface DeploySubgraphResponse {\n\taction: \"created\" | \"unchanged\" | \"handler_updated\" | \"updated\" | \"reindexed\";\n\tsubgraphId: string;\n\tversion: string;\n\tmessage: string;\n\toperationId?: string;\n\treindexStarted?: boolean;\n\tdiff?: {\n\t\taddedTables: string[];\n\t\tremovedTables: string[];\n\t\taddedColumns: Record<string, string[]>;\n\t\tbreakingChanges: string[];\n\t};\n}\n\n// Subgraph API response types\n\nexport interface SubgraphSummary {\n\tname: string;\n\tversion: string;\n\tstatus: string;\n\tlastProcessedBlock: number;\n\ttotalProcessed: number;\n\ttotalErrors: number;\n\ttables: string[];\n\tchainTip: number;\n\tsourceChainTip?: number;\n\ttargetBlock?: number;\n\tprogress: number;\n\tblocksRemaining?: number;\n\tsyncMode?: \"sync\" | \"reindex\";\n\tresourceWarning?: SubgraphResourceWarning;\n\tgapCount: number;\n\tintegrity: \"complete\" | \"gaps_detected\";\n\tcreatedAt: string;\n}\n\nexport interface SubgraphGapRange {\n\tstart: number;\n\tend: number;\n\tsize: number;\n\treason: string;\n}\n\nexport interface SubgraphSyncInfo {\n\tstatus: \"synced\" | \"catching_up\" | \"reindexing\" | \"error\";\n\tmode?: \"sync\" | \"reindex\";\n\tstartBlock: number;\n\tlastProcessedBlock: number;\n\t/**\n\t * Backward-compatible denominator for progress displays. During reindexing,\n\t * this is the reindex target block rather than the live source chain tip.\n\t */\n\tchainTip: number;\n\tsourceChainTip?: number;\n\ttargetBlock?: number;\n\tblocksRemaining: number;\n\tprocessedBlocks?: number;\n\ttotalBlocks?: number;\n\tprogress: number;\n\tresourceWarning?: SubgraphResourceWarning;\n\tgaps: {\n\t\tcount: number;\n\t\ttotalMissingBlocks: number;\n\t\tranges: SubgraphGapRange[];\n\t};\n\tintegrity: \"complete\" | \"gaps_detected\";\n}\n\nexport interface SubgraphResourceWarning {\n\tcode: string;\n\tmessage: string;\n\tplan?: string;\n\tblockRange: number;\n\tprocessorMemoryMb: number;\n\trecommendedPlan: \"launch\";\n}\n\nexport interface SubgraphDetail {\n\tname: string;\n\tversion: string;\n\tschemaHash?: string;\n\tstatus: string;\n\tlastProcessedBlock: number;\n\tdescription?: string;\n\tsources?: Record<string, unknown>;\n\tdefinition?: Record<string, unknown>;\n\thealth: {\n\t\ttotalProcessed: number;\n\t\ttotalErrors: number;\n\t\terrorRate: number;\n\t\tlastError: string | null;\n\t\tlastErrorAt: string | null;\n\t};\n\tsync: SubgraphSyncInfo;\n\ttables: Record<\n\t\tstring,\n\t\t{\n\t\t\tendpoint: string;\n\t\t\tcolumns: Record<\n\t\t\t\tstring,\n\t\t\t\t{\n\t\t\t\t\ttype: string;\n\t\t\t\t\tnullable?: boolean;\n\t\t\t\t\tindexed?: boolean;\n\t\t\t\t\tsearchable?: boolean;\n\t\t\t\t\tdefault?: string | number | boolean;\n\t\t\t\t}\n\t\t\t>;\n\t\t\trowCount: number;\n\t\t\texample: string;\n\t\t\tindexes?: string[][];\n\t\t\tuniqueKeys?: string[][];\n\t\t}\n\t>;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface SubgraphGapEntry {\n\tstart: number;\n\tend: number;\n\tsize: number;\n\treason: string;\n\tdetectedAt: string;\n\tresolvedAt: string | null;\n}\n\nexport interface SubgraphGapsResponse {\n\tdata: SubgraphGapEntry[];\n\tmeta: {\n\t\ttotal: number;\n\t\ttotalMissingBlocks: number;\n\t\tlimit: number;\n\t\toffset: number;\n\t};\n}\n\nexport interface ReindexResponse {\n\tmessage: string;\n\tfromBlock: number;\n\ttoBlock: number | string;\n\toperationId?: string;\n\tstatus?: \"queued\" | \"running\" | \"cancel_requested\";\n}\n\nexport interface SubgraphQueryParams {\n\tsort?: string;\n\torder?: string;\n\tlimit?: number;\n\toffset?: number;\n\tfields?: string;\n\tfilters?: Record<string, string>;\n}\n"
5
+ "import { z } from \"zod/v4\";\n\n// ── Deploy Subgraph Request ─────────────────────────────────────────────────\n\nexport interface DeploySubgraphRequest {\n\tname: string;\n\tversion?: string;\n\tdescription?: string;\n\tsources: Record<string, Record<string, unknown>>;\n\tschema: Record<string, unknown>;\n\thandlerCode: string;\n\t/** Override the definition's startBlock for this deploy only. */\n\tstartBlock?: number;\n\t/** Original TypeScript source, persisted so chat can read/diff/edit later. */\n\tsourceCode?: string;\n\t/**\n\t * BYO data plane: a user-owned Postgres connection string. When set, the\n\t * subgraph's schema, handler writes, and serving reads live in this DB instead\n\t * of the managed one. Stored encrypted at rest, never returned.\n\t */\n\tdatabaseUrl?: string;\n\t/** Validate the connection + print the DDL/grant plan without deploying. */\n\tdryRun?: boolean;\n}\n\nexport const DeploySubgraphRequestSchema: z.ZodType<DeploySubgraphRequest> =\n\tz.object({\n\t\tname: z\n\t\t\t.string()\n\t\t\t.regex(/^[a-z0-9-]+$/, \"lowercase alphanumeric + hyphens only\")\n\t\t\t.max(63),\n\t\tversion: z.string().optional(),\n\t\tdescription: z.string().optional(),\n\t\tsources: z\n\t\t\t.record(z.string(), z.record(z.string(), z.unknown()))\n\t\t\t.refine(\n\t\t\t\t(s) => Object.keys(s).length > 0,\n\t\t\t\t\"Must have at least one source\",\n\t\t\t),\n\t\tschema: z.record(z.string(), z.unknown()),\n\t\thandlerCode: z.string().max(1_048_576, \"handler code exceeds 1MB limit\"),\n\t\tstartBlock: z.number().int().nonnegative().optional(),\n\t\tsourceCode: z\n\t\t\t.string()\n\t\t\t.max(1_048_576, \"source code exceeds 1MB limit\")\n\t\t\t.optional(),\n\t\tdatabaseUrl: z\n\t\t\t.string()\n\t\t\t.url()\n\t\t\t.refine(\n\t\t\t\t(u) => u.startsWith(\"postgres://\") || u.startsWith(\"postgresql://\"),\n\t\t\t\t\"must be a postgres:// connection string\",\n\t\t\t)\n\t\t\t.optional(),\n\t\tdryRun: z.boolean().optional(),\n\t});\n\nexport interface DeploySubgraphResponse {\n\taction: \"created\" | \"unchanged\" | \"handler_updated\" | \"updated\" | \"reindexed\";\n\tsubgraphId: string;\n\tversion: string;\n\tmessage: string;\n\toperationId?: string;\n\treindexStarted?: boolean;\n\tdiff?: {\n\t\taddedTables: string[];\n\t\tremovedTables: string[];\n\t\taddedColumns: Record<string, string[]>;\n\t\tbreakingChanges: string[];\n\t};\n}\n\n// Subgraph API response types\n\nexport interface SubgraphSummary {\n\tname: string;\n\tversion: string;\n\tstatus: string;\n\tlastProcessedBlock: number;\n\ttotalProcessed: number;\n\ttotalErrors: number;\n\ttables: string[];\n\tchainTip: number;\n\tsourceChainTip?: number;\n\ttargetBlock?: number;\n\tprogress: number;\n\tblocksRemaining?: number;\n\tsyncMode?: \"sync\" | \"reindex\";\n\tresourceWarning?: SubgraphResourceWarning;\n\tgapCount: number;\n\tintegrity: \"complete\" | \"gaps_detected\";\n\tcreatedAt: string;\n}\n\nexport interface SubgraphGapRange {\n\tstart: number;\n\tend: number;\n\tsize: number;\n\treason: string;\n}\n\nexport interface SubgraphSyncInfo {\n\tstatus: \"synced\" | \"catching_up\" | \"reindexing\" | \"error\";\n\tmode?: \"sync\" | \"reindex\";\n\tstartBlock: number;\n\tlastProcessedBlock: number;\n\t/**\n\t * Backward-compatible denominator for progress displays. During reindexing,\n\t * this is the reindex target block rather than the live source chain tip.\n\t */\n\tchainTip: number;\n\tsourceChainTip?: number;\n\ttargetBlock?: number;\n\tblocksRemaining: number;\n\tprocessedBlocks?: number;\n\ttotalBlocks?: number;\n\tprogress: number;\n\tresourceWarning?: SubgraphResourceWarning;\n\tgaps: {\n\t\tcount: number;\n\t\ttotalMissingBlocks: number;\n\t\tranges: SubgraphGapRange[];\n\t};\n\tintegrity: \"complete\" | \"gaps_detected\";\n}\n\nexport interface SubgraphResourceWarning {\n\tcode: string;\n\tmessage: string;\n\tplan?: string;\n\tblockRange: number;\n\tprocessorMemoryMb: number;\n\trecommendedPlan: \"launch\";\n}\n\nexport interface SubgraphDetail {\n\tname: string;\n\tversion: string;\n\tschemaHash?: string;\n\tstatus: string;\n\tlastProcessedBlock: number;\n\tdescription?: string;\n\tsources?: Record<string, unknown>;\n\tdefinition?: Record<string, unknown>;\n\thealth: {\n\t\ttotalProcessed: number;\n\t\ttotalErrors: number;\n\t\terrorRate: number;\n\t\tlastError: string | null;\n\t\tlastErrorAt: string | null;\n\t};\n\tsync: SubgraphSyncInfo;\n\ttables: Record<\n\t\tstring,\n\t\t{\n\t\t\tendpoint: string;\n\t\t\tcolumns: Record<\n\t\t\t\tstring,\n\t\t\t\t{\n\t\t\t\t\ttype: string;\n\t\t\t\t\tnullable?: boolean;\n\t\t\t\t\tindexed?: boolean;\n\t\t\t\t\tsearchable?: boolean;\n\t\t\t\t\tdefault?: string | number | boolean;\n\t\t\t\t}\n\t\t\t>;\n\t\t\trowCount: number;\n\t\t\texample: string;\n\t\t\tindexes?: string[][];\n\t\t\tuniqueKeys?: string[][];\n\t\t}\n\t>;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface SubgraphGapEntry {\n\tstart: number;\n\tend: number;\n\tsize: number;\n\treason: string;\n\tdetectedAt: string;\n\tresolvedAt: string | null;\n}\n\nexport interface SubgraphGapsResponse {\n\tdata: SubgraphGapEntry[];\n\tmeta: {\n\t\ttotal: number;\n\t\ttotalMissingBlocks: number;\n\t\tlimit: number;\n\t\toffset: number;\n\t};\n}\n\nexport interface ReindexResponse {\n\tmessage: string;\n\tfromBlock: number;\n\ttoBlock: number | string;\n\toperationId?: string;\n\tstatus?: \"queued\" | \"running\" | \"cancel_requested\";\n}\n\nexport interface SubgraphQueryParams {\n\tsort?: string;\n\torder?: string;\n\tlimit?: number;\n\toffset?: number;\n\tfields?: string;\n\tfilters?: Record<string, string>;\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAiBO,IAAM,8BACZ,EAAE,OAAO;AAAA,EACR,MAAM,EACJ,OAAO,EACP,MAAM,gBAAgB,uCAAuC,EAC7D,IAAI,EAAE;AAAA,EACR,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,SAAS,EACP,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,EACpD,OACA,CAAC,MAAM,OAAO,KAAK,CAAC,EAAE,SAAS,GAC/B,+BACD;AAAA,EACD,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EACxC,aAAa,EAAE,OAAO,EAAE,IAAI,SAAW,gCAAgC;AAAA,EACvE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;AAAA,EACpD,YAAY,EACV,OAAO,EACP,IAAI,SAAW,+BAA+B,EAC9C,SAAS;AACZ,CAAC;",
8
- "debugId": "AA56811DA75420EF64756E2164756E21",
7
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAyBO,IAAM,8BACZ,EAAE,OAAO;AAAA,EACR,MAAM,EACJ,OAAO,EACP,MAAM,gBAAgB,uCAAuC,EAC7D,IAAI,EAAE;AAAA,EACR,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,SAAS,EACP,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,EACpD,OACA,CAAC,MAAM,OAAO,KAAK,CAAC,EAAE,SAAS,GAC/B,+BACD;AAAA,EACD,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EACxC,aAAa,EAAE,OAAO,EAAE,IAAI,SAAW,gCAAgC;AAAA,EACvE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;AAAA,EACpD,YAAY,EACV,OAAO,EACP,IAAI,SAAW,+BAA+B,EAC9C,SAAS;AAAA,EACX,aAAa,EACX,OAAO,EACP,IAAI,EACJ,OACA,CAAC,MAAM,EAAE,WAAW,aAAa,KAAK,EAAE,WAAW,eAAe,GAClE,yCACD,EACC,SAAS;AAAA,EACX,QAAQ,EAAE,QAAQ,EAAE,SAAS;AAC9B,CAAC;",
8
+ "debugId": "914B1C03F7A3693864756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,6 +1,7 @@
1
1
  import { type Kysely, sql } from "kysely";
2
2
 
3
- export async function up(db: Kysely<unknown>): Promise<void> {
3
+ // biome-ignore lint/suspicious/noExplicitAny: migration DDL is intentionally schema-dynamic
4
+ export async function up(db: Kysely<any>): Promise<void> {
4
5
  await sql`
5
6
  ALTER TABLE decoded_events
6
7
  ADD COLUMN microblock_hash TEXT,
@@ -41,7 +42,8 @@ export async function up(db: Kysely<unknown>): Promise<void> {
41
42
  `.execute(db);
42
43
  }
43
44
 
44
- export async function down(db: Kysely<unknown>): Promise<void> {
45
+ // biome-ignore lint/suspicious/noExplicitAny: migration DDL is intentionally schema-dynamic
46
+ export async function down(db: Kysely<any>): Promise<void> {
45
47
  await sql`DROP INDEX IF EXISTS decoded_events_recipient_height_event_idx`.execute(
46
48
  db,
47
49
  );
@@ -1,6 +1,7 @@
1
1
  import { type Kysely, sql } from "kysely";
2
2
 
3
- export async function up(db: Kysely<unknown>): Promise<void> {
3
+ // biome-ignore lint/suspicious/noExplicitAny: migration DDL is intentionally schema-dynamic
4
+ export async function up(db: Kysely<any>): Promise<void> {
4
5
  await sql`SET lock_timeout = '30s'`.execute(db);
5
6
  await sql`
6
7
  ALTER TABLE usage_daily
@@ -9,7 +10,8 @@ export async function up(db: Kysely<unknown>): Promise<void> {
9
10
  `.execute(db);
10
11
  }
11
12
 
12
- export async function down(db: Kysely<unknown>): Promise<void> {
13
+ // biome-ignore lint/suspicious/noExplicitAny: migration DDL is intentionally schema-dynamic
14
+ export async function down(db: Kysely<any>): Promise<void> {
13
15
  await sql`
14
16
  ALTER TABLE usage_daily
15
17
  DROP COLUMN IF EXISTS index_decoded_events_returned,
@@ -0,0 +1,16 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ // BYO data plane (subgraphs): per-subgraph user-owned Postgres connection
4
+ // string, stored as an AES-GCM envelope (crypto/secrets.ts) in a single bytea
5
+ // column on the subgraph row — same convention as subscriptions.signing_secret_enc
6
+ // and the old tenants.target_database_url_enc. Nullable; null = managed (handler
7
+ // writes + serving use the target DB, unchanged).
8
+ export async function up(db: Kysely<unknown>): Promise<void> {
9
+ await sql`ALTER TABLE subgraphs ADD COLUMN database_url_enc BYTEA`.execute(
10
+ db,
11
+ );
12
+ }
13
+
14
+ export async function down(db: Kysely<unknown>): Promise<void> {
15
+ await sql`ALTER TABLE subgraphs DROP COLUMN database_url_enc`.execute(db);
16
+ }
@@ -0,0 +1,44 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ // Contract registry for trait-based discovery ("find all SIP-010 tokens"). One
4
+ // row per deployed contract with its fetched ABI, declared traits (parsed from
5
+ // Clarity source), and statically-inferred SIP standards. `canonical` mirrors
6
+ // chain reorgs. The partial index on transactions makes the deploy backfill
7
+ // (WHERE type='smart_contract') an index scan, not a full-table seq scan.
8
+ export async function up(db: Kysely<unknown>): Promise<void> {
9
+ await sql`
10
+ CREATE TABLE contracts (
11
+ contract_id TEXT PRIMARY KEY,
12
+ deployer TEXT NOT NULL,
13
+ block_height BIGINT NOT NULL,
14
+ canonical BOOLEAN NOT NULL DEFAULT TRUE,
15
+ abi JSONB,
16
+ declared_traits TEXT[] NOT NULL DEFAULT '{}',
17
+ inferred_standards TEXT[] NOT NULL DEFAULT '{}',
18
+ abi_status TEXT NOT NULL DEFAULT 'pending',
19
+ abi_fetched_at TIMESTAMPTZ,
20
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
21
+ )
22
+ `.execute(db);
23
+
24
+ // Discovery query path: filter by inferred standard / declared trait.
25
+ await sql`CREATE INDEX contracts_inferred_standards_idx ON contracts USING gin (inferred_standards)`.execute(
26
+ db,
27
+ );
28
+ await sql`CREATE INDEX contracts_declared_traits_idx ON contracts USING gin (declared_traits)`.execute(
29
+ db,
30
+ );
31
+ // As-of-block trait resolution (B4) reads by block_height for canonical rows.
32
+ await sql`CREATE INDEX contracts_block_height_idx ON contracts (block_height) WHERE canonical`.execute(
33
+ db,
34
+ );
35
+ // Deploy backfill: avoid seq-scanning the whole transactions table.
36
+ await sql`CREATE INDEX transactions_smart_contract_idx ON transactions (contract_id) WHERE type = 'smart_contract'`.execute(
37
+ db,
38
+ );
39
+ }
40
+
41
+ export async function down(db: Kysely<unknown>): Promise<void> {
42
+ await sql`DROP INDEX IF EXISTS transactions_smart_contract_idx`.execute(db);
43
+ await sql`DROP TABLE IF EXISTS contracts`.execute(db);
44
+ }
@@ -0,0 +1,63 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ // Burnchain (Bitcoin) PoX reward data, sourced from the stacks-node
4
+ // /new_burn_block event observer payload (reward_recipients / reward_slot_holders),
5
+ // which the indexer previously discarded. Two views per burn block:
6
+ // - burn_block_rewards: actual BTC payouts (one row per reward slot, ≤2/block).
7
+ // Populated only during a reward cycle's reward phase.
8
+ // - burn_block_reward_slots: reward-set membership (eligible BTC addresses).
9
+ // Both are keyed by (burn_block_height, index) via `cursor`; the handler does
10
+ // delete-by-height-then-insert (replace-per-height), which makes redelivery and
11
+ // shallow burnchain reorgs idempotent. `canonical` is reserved for a future
12
+ // mark-non-canonical reorg path; v1 keeps every row canonical.
13
+ export async function up(db: Kysely<unknown>): Promise<void> {
14
+ await sql`SET lock_timeout = '30s'`.execute(db);
15
+
16
+ await sql`
17
+ CREATE TABLE IF NOT EXISTS burn_block_rewards (
18
+ cursor TEXT PRIMARY KEY,
19
+ burn_block_height BIGINT NOT NULL,
20
+ burn_block_hash TEXT NOT NULL,
21
+ reward_index INTEGER NOT NULL,
22
+ recipient_btc TEXT NOT NULL,
23
+ amount_sats TEXT NOT NULL,
24
+ burn_amount TEXT NOT NULL DEFAULT '0',
25
+ canonical BOOLEAN NOT NULL DEFAULT true,
26
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
27
+ )
28
+ `.execute(db);
29
+
30
+ await sql`CREATE INDEX IF NOT EXISTS burn_block_rewards_canonical_height_idx ON burn_block_rewards (canonical, burn_block_height)`.execute(
31
+ db,
32
+ );
33
+ await sql`CREATE INDEX IF NOT EXISTS burn_block_rewards_recipient_height_idx ON burn_block_rewards (recipient_btc, burn_block_height)`.execute(
34
+ db,
35
+ );
36
+ await sql`CREATE INDEX IF NOT EXISTS burn_block_rewards_hash_idx ON burn_block_rewards (burn_block_hash)`.execute(
37
+ db,
38
+ );
39
+
40
+ await sql`
41
+ CREATE TABLE IF NOT EXISTS burn_block_reward_slots (
42
+ cursor TEXT PRIMARY KEY,
43
+ burn_block_height BIGINT NOT NULL,
44
+ burn_block_hash TEXT NOT NULL,
45
+ slot_index INTEGER NOT NULL,
46
+ holder_btc TEXT NOT NULL,
47
+ canonical BOOLEAN NOT NULL DEFAULT true,
48
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
49
+ )
50
+ `.execute(db);
51
+
52
+ await sql`CREATE INDEX IF NOT EXISTS burn_block_reward_slots_canonical_height_idx ON burn_block_reward_slots (canonical, burn_block_height)`.execute(
53
+ db,
54
+ );
55
+ await sql`CREATE INDEX IF NOT EXISTS burn_block_reward_slots_holder_height_idx ON burn_block_reward_slots (holder_btc, burn_block_height)`.execute(
56
+ db,
57
+ );
58
+ }
59
+
60
+ export async function down(db: Kysely<unknown>): Promise<void> {
61
+ await sql`DROP TABLE IF EXISTS burn_block_reward_slots`.execute(db);
62
+ await sql`DROP TABLE IF EXISTS burn_block_rewards`.execute(db);
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/shared",
3
- "version": "6.8.1",
3
+ "version": "6.10.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,6 +28,10 @@
28
28
  "types": "./dist/src/db/queries/chain-reorgs.d.ts",
29
29
  "import": "./dist/src/db/queries/chain-reorgs.js"
30
30
  },
31
+ "./db/queries/contracts": {
32
+ "types": "./dist/src/db/queries/contracts.d.ts",
33
+ "import": "./dist/src/db/queries/contracts.js"
34
+ },
31
35
  "./db/queries/subgraphs": {
32
36
  "types": "./dist/src/db/queries/subgraphs.d.ts",
33
37
  "import": "./dist/src/db/queries/subgraphs.js"
@@ -127,7 +131,7 @@
127
131
  "prepublishOnly": "bun run build"
128
132
  },
129
133
  "dependencies": {
130
- "@secondlayer/stacks": "^2.2.1",
134
+ "@secondlayer/stacks": "^2.3.0",
131
135
  "kysely": "0.28.15",
132
136
  "kysely-postgres-js": "3.0.0",
133
137
  "postgres": "^3.4.6",