@secondlayer/sdk 9.0.2 → 10.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.
@@ -16,20 +16,50 @@ interface SecondLayerOptions {
16
16
  dumpsBaseUrl?: string;
17
17
  /** Deploy origin label sent as `x-sl-origin` (telemetry). Defaults to `cli`. */
18
18
  origin?: "cli" | "mcp" | "session";
19
+ /** Check the ed25519 signature on every Streams read. Omit for lenient
20
+ * (verify when signed, pass through unsigned self-host responses), `true`
21
+ * or `{ publicKey }` for strict, `false` for off. Reaches `sl.streams`. */
22
+ verify?: boolean | {
23
+ publicKey: string;
24
+ };
25
+ /** Check the dumps manifest signature before trusting any file hash
26
+ * (default on). Reaches `sl.streams.dumps`. */
27
+ verifyDumpsManifest?: boolean;
28
+ /** How long one request may take, headers and body, before it fails with a
29
+ * retryable `ApiError` (default 30 000 ms). A hung socket then trips the
30
+ * retry policy instead of stalling a walk or consume loop forever. `0`
31
+ * disables the timeout. */
32
+ requestTimeoutMs?: number;
19
33
  }
34
+ /** Per-call options every `request*` method accepts. */
35
+ type RequestOptions = {
36
+ /** Cancels the in-flight request (and its body read). The rejection is the
37
+ * signal's reason, an `AbortError` by default, never an `ApiError`. */
38
+ signal?: AbortSignal;
39
+ };
20
40
  declare abstract class BaseClient {
21
41
  protected baseUrl: string;
22
42
  protected apiKey?: string;
23
43
  protected origin: "cli" | "mcp" | "session";
24
44
  protected fetchImpl: FetchLike;
45
+ protected requestTimeoutMs: number;
25
46
  constructor(options?: Partial<SecondLayerOptions>);
26
47
  static authHeaders(apiKey?: string): Record<string, string>;
27
- protected request<T>(method: string, path: string, body?: unknown): Promise<T>;
48
+ protected request<T>(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
28
49
  /** Like `request`, but maps a 404 to `null` instead of throwing — the one
29
50
  * place that owns the "absent resource" rule for `get*` accessors. */
30
- protected requestOrNull<T>(method: string, path: string, body?: unknown): Promise<T | null>;
31
- protected requestText(method: string, path: string, body?: unknown): Promise<string>;
32
- private fetchResponse;
51
+ protected requestOrNull<T>(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<T | null>;
52
+ protected requestText(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<string>;
53
+ /** Run one request under the caller's signal plus the per-request timeout.
54
+ * One combined signal covers the fetch and the body read, so a socket that
55
+ * stalls mid-body times out the same way one that never answers does. A
56
+ * timeout rejects with a retryable `ApiError`; a caller abort rejects with
57
+ * the signal's reason so loops can tell "stop" from "try again". */
58
+ private withRequestBudget;
59
+ /** Issue the HTTP request and map non-2xx statuses onto the error family.
60
+ * `signal` is the combined caller-plus-timeout signal from `request*`;
61
+ * subclasses that stream a body call this directly and read it themselves. */
62
+ protected fetchResponse(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<Response>;
33
63
  }
34
64
  interface SubgraphSource {
35
65
  name: string;
@@ -92,8 +122,10 @@ declare class Subgraphs extends BaseClient {
92
122
  markdown(name: string, options?: SubgraphSpecOptions): Promise<string>;
93
123
  /**
94
124
  * Reindex always drops and rebuilds the whole subgraph, so it takes no
95
- * block range — the API rejects one with `REINDEX_RANGE_NOT_SUPPORTED`.
96
- * Use {@link backfill} to process a specific range.
125
+ * block range; the API rejects one with `REINDEX_RANGE_NOT_SUPPORTED`.
126
+ * Use {@link backfill} to process a specific range. While a reindex or
127
+ * backfill is already running the API answers 409 with code
128
+ * `OPERATION_IN_PROGRESS`; poll {@link operations} until it finishes.
97
129
  */
98
130
  reindex(name: string): Promise<ReindexResponse>;
99
131
  stop(name: string): Promise<{
@@ -101,6 +133,8 @@ declare class Subgraphs extends BaseClient {
101
133
  operationId?: string;
102
134
  status?: string;
103
135
  }>;
136
+ /** Process one block range. 409 `OPERATION_IN_PROGRESS` while another
137
+ * reindex or backfill runs on this subgraph. */
104
138
  backfill(name: string, options: {
105
139
  fromBlock: number;
106
140
  toBlock: number;
@@ -130,6 +164,9 @@ declare class Subgraphs extends BaseClient {
130
164
  /** Status of a single operation (poll the `operationId` returned by
131
165
  * reindex/backfill/stop until `status` is terminal). */
132
166
  getOperation(name: string, operationId: string): Promise<SubgraphOperationStatus>;
167
+ /** Create or update a subgraph. A deploy that needs a rebuild queues one;
168
+ * if a reindex or backfill is already running the API answers 409 with
169
+ * code `OPERATION_IN_PROGRESS` (an `ApiError`, not a dedicated class). */
133
170
  deploy(data: DeploySubgraphRequest): Promise<DeploySubgraphResponse>;
134
171
  getSource(name: string): Promise<SubgraphSource>;
135
172
  /**
@@ -160,6 +197,13 @@ declare class Subgraphs extends BaseClient {
160
197
  name: string;
161
198
  schema: Record<string, unknown>;
162
199
  }>(def: T): InferSubgraphClient<T>;
200
+ /**
201
+ * `columns` is the table's declared column set from `defineSubgraph()`.
202
+ * Filters and orderBy always accept the canonical system names
203
+ * `_id` / `_blockHeight` / `_txId` / `_createdAt`; the unprefixed
204
+ * shorthands (`id`, `blockHeight`, ...) mean the system column only when
205
+ * the table declares no column of that name.
206
+ */
163
207
  private createTableClient;
164
208
  }
165
209
  import { InferSubgraphClient as InferSubgraphClient2 } from "@secondlayer/subgraphs";
@@ -367,8 +411,11 @@ interface ConsumerSink<Tx = unknown> {
367
411
  * same `rewindCursor` (the lowest fork point) but its own
368
412
  * `forkPointHeight` — scope the undo by `forkPointHeight` only (contract
369
413
  * invariant #11), and expect re-application after a crash (#10).
414
+ *
415
+ * `rewindCursor` is `null` only for a fork at genesis: clear the
416
+ * checkpoint so the next run starts from the first event.
370
417
  */
371
- rollback(forkPointHeight: number, rewindCursor: string): Promise<void>;
418
+ rollback(forkPointHeight: number, rewindCursor: string | null): Promise<void>;
372
419
  }
373
420
  /** `ctx` gains `tx` exactly when a sink is attached; without one the shape
374
421
  * is unchanged (an intersection with `unknown` is an identity, so
@@ -516,11 +563,41 @@ type StreamsEventsSubscribeParams = {
516
563
  filters?: StreamsFilterMap;
517
564
  /** Abort to unsubscribe (the returned function does the same). */
518
565
  signal?: AbortSignal;
519
- /** Called for each pushed event, in order. */
566
+ /**
567
+ * Called for each pushed event, in order. The resume cursor advances only
568
+ * after this resolves, so a handler that throws sees the same event again
569
+ * on reconnect (at-least-once). Key durable writes by `cursor`.
570
+ */
520
571
  onEvent: (event: StreamsEvent) => void | Promise<void>;
521
- /** Called on a connection error; the subscription auto-reconnects from the
522
- * last delivered cursor unless the signal has aborted. */
572
+ /**
573
+ * Called on every failure. Transport errors (dropped socket, 5xx, 429, a
574
+ * stale connection, a throwing handler) reconnect from the last handled
575
+ * cursor with exponential backoff. Errors that a retry cannot fix (401,
576
+ * 4xx, a bad signature) end the subscription and reject its `done`.
577
+ */
523
578
  onError?: (err: unknown) => void;
579
+ /**
580
+ * First reconnect pause in ms (default 1000). Doubles per consecutive
581
+ * failure up to 30 s with jitter, resets once a frame arrives, and never
582
+ * undercuts a `Retry-After`.
583
+ */
584
+ reconnectDelayMs?: number;
585
+ /**
586
+ * Reconnect when no frame (pings included) arrives for this long (default
587
+ * 60000, three server heartbeats). Catches a half-open socket that never
588
+ * errors; set it above your instance's heartbeat interval.
589
+ */
590
+ staleAfterMs?: number;
591
+ };
592
+ /**
593
+ * Handle for a live subscription: call it to unsubscribe. `done` resolves
594
+ * after unsubscribe (or the signal aborting) and rejects with the error that
595
+ * ended the loop when a retry could not fix it, so a worker can `await`
596
+ * it instead of polling for silence.
597
+ */
598
+ type StreamsSubscription = {
599
+ (): void;
600
+ done: Promise<void>;
524
601
  };
525
602
  /**
526
603
  * The checkpoint the SDK computes for a batch. Persist `cursor` inside the same
@@ -585,8 +662,11 @@ type StreamsBatchContext = ConsumerBatchContext;
585
662
  * The checkpoint for a reorg rollback. Persist `cursor` (the rewind position)
586
663
  * inside the same transaction as your rollback so the two commit atomically.
587
664
  */
665
+ /** `cursor` is the rewind position the loop resumes from after `onReorg`:
666
+ * the foot of the fork point, or `null` for a fork at genesis. Persist it
667
+ * in the same transaction as the rollback. */
588
668
  type StreamsReorgContext = {
589
- cursor: string;
669
+ cursor: string | null;
590
670
  };
591
671
  type StreamsEventsConsumeParams<
592
672
  TTx = never,
@@ -654,12 +734,23 @@ type StreamsEventsConsumeParams<
654
734
  onBatch?: (events: D extends true ? IndexEvent[] : StreamsEvent[], envelope: StreamsEventsEnvelope, ctx: StreamsBatchContext & WithSinkTx<TTx>) => void | string | null | undefined | Promise<void> | Promise<string | null | undefined>;
655
735
  /**
656
736
  * Roll your projection back to `reorg.fork_point_height`, persisting
657
- * `ctx.cursor` in the same transaction. Called once per *new* reorg
658
- * (deduped in-memory, fork-ascending) before the SDK rewinds and re-reads the
659
- * now-canonical events. Omit it to ignore reorgs (events stay canonical, but
660
- * stale rows from an orphaned fork are left in place).
737
+ * `ctx.cursor` in the same transaction. Called once per *new* reorg at or
738
+ * below the checkpoint (deduped in-memory, fork-ascending) before the SDK
739
+ * rewinds and re-reads the now-canonical events. A reorg whose fork sits
740
+ * above the checkpoint is not a rollback (nothing past it was written) and
741
+ * is skipped. Omit it to ignore reorgs (events stay canonical, but stale
742
+ * rows from an orphaned fork are left in place).
743
+ *
744
+ * With `onReorg` or a `sink` attached, every empty page also polls
745
+ * `reorgs.list` (one extra request per idle poll) so a fork that lands
746
+ * while the consumer idles at the tip is rolled back too.
661
747
  */
662
748
  onReorg?: (reorg: StreamsReorg, ctx: StreamsReorgContext) => Promise<void> | void;
749
+ /** Deepest rewind one reorg may make below the checkpoint before the loop
750
+ * refuses with `ValidationError`. Default 1000 blocks. The fork point is
751
+ * server supplied and drives a delete on every declared table, so raise
752
+ * this only for a source you trust. */
753
+ maxRollbackDepth?: number;
663
754
  /** Page-fetch retries after the first failure (429/5xx/network only —
664
755
  * 4xx and handler throws always propagate). Default 3; `0` disables. */
665
756
  retryCount?: number;
@@ -722,8 +813,16 @@ type StreamsEventsReplayParams = {
722
813
  * Called once per finalized dump file, in block order, before live tailing.
723
814
  * Process the parquet with your own tooling (e.g. DuckDB) — the SDK does not
724
815
  * decode parquet. Use `client.dumps.download(file)` to fetch + verify bytes.
816
+ *
817
+ * Delivery is file-granular and at-least-once: a file whose range straddles
818
+ * `from` is handed over whole. `ctx.from` is that cursor (`null` from
819
+ * genesis); skip rows at or below it, or key rows by `cursor` so a re-run
820
+ * is an idempotent no-op. Files ending at or below `from` are not handed
821
+ * over at all.
725
822
  */
726
- onDumpFile: (file: StreamsDumpFile) => Promise<void> | void;
823
+ onDumpFile: (file: StreamsDumpFile, ctx: {
824
+ from: string | null;
825
+ }) => Promise<void> | void;
727
826
  /** Called per live page after the dump phase, like `consume`. */
728
827
  onBatch: (events: StreamsEvent[], envelope: StreamsEventsEnvelope) => Promise<string | null | undefined> | string | null | undefined;
729
828
  /**
@@ -858,9 +957,11 @@ type StreamsClient = {
858
957
  * Subscribe to the real-time SSE push surface. Calls `onEvent` for each new
859
958
  * canonical event as the server pushes it (chain cadence, not poll-bounded),
860
959
  * and verifies each frame's inline ed25519 signature when the client was
861
- * created with `verify`. Returns an unsubscribe function.
960
+ * created with `verify`. Returns an unsubscribe function whose `done`
961
+ * settles when the loop ends. Not reorg-aware: an event delivered then
962
+ * orphaned is never retracted. Durable writers use `consume()`.
862
963
  */
863
- subscribe(params: StreamsEventsSubscribeParams): () => void;
964
+ subscribe(params: StreamsEventsSubscribeParams): StreamsSubscription;
864
965
  };
865
966
  blocks: {
866
967
  events(heightOrHash: number | string): Promise<StreamsEventsListEnvelope>;
@@ -939,9 +1040,18 @@ type IndexConsumeOptions<
939
1040
  * `consumerHealth().record`. */
940
1041
  onProgress?: (ctx: ConsumerBatchContext) => void;
941
1042
  onBatch: (items: TItem[], envelope: TEnvelope, ctx: ConsumerBatchContext & WithSinkTx<TTx>) => void | string | null | undefined | Promise<void> | Promise<string | null | undefined>;
1043
+ /** Called once per new reorg at or below the checkpoint, before the loop
1044
+ * rewinds to `ctx.cursor` (`null` = pre-genesis) and re-reads. A reorg
1045
+ * whose fork sits ABOVE the checkpoint is not a rollback: nothing past
1046
+ * it was written, so it is noted and the page is read as normal. */
942
1047
  onReorg?: (reorg: IndexReorg, ctx: {
943
- cursor: string;
1048
+ cursor: string | null;
944
1049
  }) => Promise<void> | void;
1050
+ /** Deepest rewind one reorg may make below the checkpoint before the loop
1051
+ * refuses with `ValidationError`. Default 1000 blocks. The fork point is
1052
+ * server supplied and drives a delete on every declared table, so raise
1053
+ * this only for a source you trust. */
1054
+ maxRollbackDepth?: number;
945
1055
  sleep?: Sleep;
946
1056
  emptyBackoffMs?: number;
947
1057
  maxPages?: number;
@@ -1037,10 +1147,20 @@ type FtTransfersListParams = {
1037
1147
  fromHeight?: number;
1038
1148
  toHeight?: number;
1039
1149
  };
1040
- type FtTransfersWalkParams = Omit<FtTransfersListParams, "limit"> & {
1150
+ /** Options every `walk*` feed shares on top of its list filters. Page fetches
1151
+ * retry with the same `retryCount`/`retryDelay`/`onError` vocabulary as
1152
+ * `consume()`; the tradeoff is that a walk can pause for the retry delays
1153
+ * instead of failing fast on the first 429. */
1154
+ type WalkOptions = PageRetryOptions & {
1155
+ /** Rows per page request. Default 200, max `INDEX_MAX_PAGE_SIZE` (1000). */
1041
1156
  batchSize?: number;
1157
+ /** Stops the walk. The iterator always rejects with the signal's reason (an
1158
+ * `AbortError` by default), whether the abort lands during a page request
1159
+ * (which is cancelled) or between yields, so a caller can tell a finished
1160
+ * walk from a stopped one. */
1042
1161
  signal?: AbortSignal;
1043
1162
  };
1163
+ type FtTransfersWalkParams = Omit<FtTransfersListParams, "limit"> & WalkOptions;
1044
1164
  type NftTransfer = {
1045
1165
  cursor: string;
1046
1166
  block_height: number;
@@ -1078,10 +1198,7 @@ type NftTransfersListParams = {
1078
1198
  fromHeight?: number;
1079
1199
  toHeight?: number;
1080
1200
  };
1081
- type NftTransfersWalkParams = Omit<NftTransfersListParams, "limit"> & {
1082
- batchSize?: number;
1083
- signal?: AbortSignal;
1084
- };
1201
+ type NftTransfersWalkParams = Omit<NftTransfersListParams, "limit"> & WalkOptions;
1085
1202
  type IndexEventBase = {
1086
1203
  cursor: string;
1087
1204
  block_height: number;
@@ -1236,10 +1353,7 @@ type EventsListParams<T extends IndexEventType = IndexEventType> = {
1236
1353
  */
1237
1354
  fields?: readonly (keyof IndexEventOf<T> & string)[];
1238
1355
  };
1239
- type EventsWalkParams<T extends IndexEventType = IndexEventType> = Omit<EventsListParams<T>, "limit"> & {
1240
- batchSize?: number;
1241
- signal?: AbortSignal;
1242
- };
1356
+ type EventsWalkParams<T extends IndexEventType = IndexEventType> = Omit<EventsListParams<T>, "limit"> & WalkOptions;
1243
1357
  type EventsConsumeParams<
1244
1358
  T extends IndexEventType = IndexEventType,
1245
1359
  TTx = never
@@ -1279,10 +1393,7 @@ type ContractCallsListParams = {
1279
1393
  * Mutually exclusive with contractId. */
1280
1394
  trait?: string;
1281
1395
  };
1282
- type ContractCallsWalkParams = Omit<ContractCallsListParams, "limit"> & {
1283
- batchSize?: number;
1284
- signal?: AbortSignal;
1285
- };
1396
+ type ContractCallsWalkParams = Omit<ContractCallsListParams, "limit"> & WalkOptions;
1286
1397
  type ContractCallsConsumeParams<TTx = never> = Omit<ContractCallsListParams, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexContractCall, ContractCallsEnvelope, TTx>;
1287
1398
  /** One canonical block in the sync map. Lean by design — block + parent hash
1288
1399
  * for chain linkage, burn anchor for Bitcoin confirmations. Use `blocks` for
@@ -1307,10 +1418,7 @@ type CanonicalListParams = {
1307
1418
  fromHeight?: number;
1308
1419
  toHeight?: number;
1309
1420
  };
1310
- type CanonicalWalkParams = Omit<CanonicalListParams, "limit"> & {
1311
- batchSize?: number;
1312
- signal?: AbortSignal;
1313
- };
1421
+ type CanonicalWalkParams = Omit<CanonicalListParams, "limit"> & WalkOptions;
1314
1422
  /** A block resource. Metadata is intentionally thin — only chain-linkage and
1315
1423
  * burn-anchor fields are persisted (no miner / tx_count / signer). */
1316
1424
  type IndexBlock = {
@@ -1339,10 +1447,7 @@ type BlocksListParams = {
1339
1447
  fromHeight?: number;
1340
1448
  toHeight?: number;
1341
1449
  };
1342
- type BlocksWalkParams = Omit<BlocksListParams, "limit"> & {
1343
- batchSize?: number;
1344
- signal?: AbortSignal;
1345
- };
1450
+ type BlocksWalkParams = Omit<BlocksListParams, "limit"> & WalkOptions;
1346
1451
  type IndexPostCondition = {
1347
1452
  type: "stx";
1348
1453
  principal: string;
@@ -1448,10 +1553,7 @@ type TransactionsListParams = {
1448
1553
  fromHeight?: number;
1449
1554
  toHeight?: number;
1450
1555
  };
1451
- type TransactionsWalkParams = Omit<TransactionsListParams, "limit"> & {
1452
- batchSize?: number;
1453
- signal?: AbortSignal;
1454
- };
1556
+ type TransactionsWalkParams = Omit<TransactionsListParams, "limit"> & WalkOptions;
1455
1557
  /** A decoded PoX-4 stacking action (one per stacking contract call). */
1456
1558
  type IndexStackingAction = {
1457
1559
  cursor: string;
@@ -1495,10 +1597,7 @@ type StackingListParams = {
1495
1597
  fromHeight?: number;
1496
1598
  toHeight?: number;
1497
1599
  };
1498
- type StackingWalkParams = Omit<StackingListParams, "limit"> & {
1499
- batchSize?: number;
1500
- signal?: AbortSignal;
1501
- };
1600
+ type StackingWalkParams = Omit<StackingListParams, "limit"> & WalkOptions;
1502
1601
  /** A pending (unconfirmed) transaction. Like a transaction document but
1503
1602
  * pre-chain — no block_height/tx_index/result/events — with `received_at` and
1504
1603
  * a sequence cursor instead of a block position. */
@@ -1554,10 +1653,7 @@ type MempoolListParams = {
1554
1653
  /** Filter to pending calls to a single function (composes with `contractId`). */
1555
1654
  functionName?: string;
1556
1655
  };
1557
- type MempoolWalkParams = Omit<MempoolListParams, "limit"> & {
1558
- batchSize?: number;
1559
- signal?: AbortSignal;
1560
- };
1656
+ type MempoolWalkParams = Omit<MempoolListParams, "limit"> & WalkOptions;
1561
1657
  /**
1562
1658
  * Empirical per-topic print payload schema for a contract, inferred from
1563
1659
  * sampled on-chain events. `topics` is sorted by count desc; `sampled` is true
@@ -1749,10 +1845,7 @@ type SbtcDepositsListParams = {
1749
1845
  type SbtcDepositAlwaysFields = "cursor" | "block_height";
1750
1846
  /** A deposit row narrowed to the requested columns plus the always-present ones. */
1751
1847
  type SbtcDepositFields<F extends keyof IndexSbtcDeposit & string> = Pick<IndexSbtcDeposit, (F | SbtcDepositAlwaysFields) & keyof IndexSbtcDeposit>;
1752
- type SbtcDepositsWalkParams = Omit<SbtcDepositsListParams, "limit"> & {
1753
- batchSize?: number;
1754
- signal?: AbortSignal;
1755
- };
1848
+ type SbtcDepositsWalkParams = Omit<SbtcDepositsListParams, "limit"> & WalkOptions;
1756
1849
  /** SbtcWithdrawal columns that survive any projection. */
1757
1850
  type SbtcWithdrawalAlwaysFields = "cursor" | "request_id";
1758
1851
  /** A SbtcWithdrawal row narrowed to the requested columns plus the always-present ones. */
@@ -1777,10 +1870,7 @@ type SbtcWithdrawalsListParams = {
1777
1870
  fromHeight?: number;
1778
1871
  toHeight?: number;
1779
1872
  };
1780
- type SbtcWithdrawalsWalkParams = Omit<SbtcWithdrawalsListParams, "limit"> & {
1781
- batchSize?: number;
1782
- signal?: AbortSignal;
1783
- };
1873
+ type SbtcWithdrawalsWalkParams = Omit<SbtcWithdrawalsListParams, "limit"> & WalkOptions;
1784
1874
  type SbtcEventsListParams = {
1785
1875
  cursor?: string | null;
1786
1876
  fromCursor?: string | null;
@@ -1793,10 +1883,7 @@ type SbtcEventsListParams = {
1793
1883
  fromHeight?: number;
1794
1884
  toHeight?: number;
1795
1885
  };
1796
- type SbtcEventsWalkParams = Omit<SbtcEventsListParams, "limit"> & {
1797
- batchSize?: number;
1798
- signal?: AbortSignal;
1799
- };
1886
+ type SbtcEventsWalkParams = Omit<SbtcEventsListParams, "limit"> & WalkOptions;
1800
1887
  type SbtcEventsConsumeParams<TTx = never> = Omit<SbtcEventsListParams, "cursor" | "fromCursor" | "limit"> & IndexConsumeOptions<IndexSbtcEvent, SbtcEventsEnvelope, TTx>;
1801
1888
  type SbtcDepositsConsumeParams<TTx = never> = Omit<SbtcDepositsListParams, "cursor" | "fromCursor" | "limit" | "fields"> & IndexConsumeOptions<IndexSbtcDeposit, SbtcDepositsEnvelope, TTx>;
1802
1889
  /** `index.sbtc` — the decoded sBTC peg surface (deposits, withdrawals, raw
@@ -1887,10 +1974,7 @@ type PoxCyclesListParams = {
1887
1974
  cursor?: number | null;
1888
1975
  limit?: number;
1889
1976
  };
1890
- type PoxCyclesWalkParams = Omit<PoxCyclesListParams, "limit"> & {
1891
- batchSize?: number;
1892
- signal?: AbortSignal;
1893
- };
1977
+ type PoxCyclesWalkParams = Omit<PoxCyclesListParams, "limit"> & WalkOptions;
1894
1978
  /** `index.pox` — PoX reward-cycle aggregates. */
1895
1979
  interface PoxResource {
1896
1980
  cycles: {
@@ -1965,10 +2049,7 @@ type Pox5EventsListParams = {
1965
2049
  fromHeight?: number;
1966
2050
  toHeight?: number;
1967
2051
  };
1968
- type Pox5EventsWalkParams = Omit<Pox5EventsListParams, "limit"> & {
1969
- batchSize?: number;
1970
- signal?: AbortSignal;
1971
- };
2052
+ type Pox5EventsWalkParams = Omit<Pox5EventsListParams, "limit"> & WalkOptions;
1972
2053
  /** `index.pox5` — decoded PoX-5 print events, the staking primitive from the
1973
2054
  * epoch 4.0 hard fork onward (PoX-4's `index.stacking` stream ends there). */
1974
2055
  interface Pox5Resource {
@@ -2173,9 +2254,13 @@ declare class Index extends BaseClient {
2173
2254
  private listNftTransfers;
2174
2255
  /** Shared keyset-pagination loop for every `walk*` feed: seed the cursor from
2175
2256
  * `cursor`/`fromCursor` (and `fromHeight` 0 on the first page), then page until
2176
- * the cursor stops advancing or a short page signals the tail. `list` merges the
2177
- * resource's filter params; `itemsOf` selects the envelope's item array. PoX
2178
- * cycles use a numeric cursor and are walked separately. */
2257
+ * the server stops advancing the cursor (`next_cursor` null or unchanged). A
2258
+ * short page is not a tail signal: the server clamps oversized limits without
2259
+ * saying so, so trusting page length truncated backfills. The cost is one
2260
+ * extra empty fetch at the end of feeds that never return a null cursor.
2261
+ * `list` merges the resource's filter params; `itemsOf` selects the envelope's
2262
+ * item array. Each page fetch retries under `fetchPageWithRetry`; the cursor
2263
+ * is a string keyset for every feed except PoX cycles, which use a number. */
2179
2264
  private keysetWalk;
2180
2265
  private walkFtTransfers;
2181
2266
  private walkNftTransfers;
@@ -2262,22 +2347,43 @@ interface ActiveSubgraphOperation {
2262
2347
  status: SubgraphOperationStatus["status"];
2263
2348
  progress: number | null;
2264
2349
  }
2350
+ /** Why one snapshot field could not be read. Serialized from the SDK error
2351
+ * family so a snapshot stays plain data. */
2352
+ interface ContextFieldError {
2353
+ message: string;
2354
+ /** Stable code from the API envelope (`UNAUTHORIZED`, `INDEX_NOT_READY`)
2355
+ * or the SDK (`REQUEST_TIMEOUT`); absent for a bare transport failure. */
2356
+ code?: string;
2357
+ /** HTTP status; `0` when the API could not be reached. */
2358
+ status?: number;
2359
+ /** Whether the same read can succeed on a retry (429, 5xx, network). */
2360
+ retryable: boolean;
2361
+ }
2362
+ /** One snapshot field: the value, or `null` plus the error that produced it.
2363
+ * `value: null` with no `error` means the read succeeded and found nothing. */
2364
+ interface ContextField<T> {
2365
+ value: T | null;
2366
+ error?: ContextFieldError;
2367
+ }
2265
2368
  /**
2266
- * A point-in-time orientation snapshot for an agent: who you are, the live tips,
2267
- * and what you own. Each field is `null` when it couldn't be read (e.g. no key,
2268
- * or a free-tier key for a Build+ surface) so the snapshot never throws.
2369
+ * A point-in-time orientation snapshot for an agent: the live tips and what
2370
+ * this instance holds. Every field is a {@link ContextField}, so a missing
2371
+ * value says why it is missing (API unreachable, token rejected, index still
2372
+ * catching up) instead of a bare `null`. `context()` itself never throws.
2269
2373
  */
2270
2374
  interface ContextSnapshot {
2271
- account: ContextAccount | null;
2272
- streamsTip: StreamsTip | null;
2273
- indexTip: IndexTip | null;
2274
- subgraphs: SubgraphSummary2[] | null;
2275
- subscriptions: {
2375
+ /** Token identity from `/api/accounts/me`. A self-hosted instance has no
2376
+ * account system: the read 404s and the field carries that error. */
2377
+ account: ContextField<ContextAccount>;
2378
+ streamsTip: ContextField<StreamsTip>;
2379
+ indexTip: ContextField<IndexTip>;
2380
+ subgraphs: ContextField<SubgraphSummary2[]>;
2381
+ subscriptions: ContextField<{
2276
2382
  count: number;
2277
2383
  byStatus: Record<string, number>;
2278
- } | null;
2384
+ }>;
2279
2385
  /** In-flight reindex operations (bounded to subgraphs reporting `reindexing`). */
2280
- activeOperations: ActiveSubgraphOperation[] | null;
2386
+ activeOperations: ContextField<ActiveSubgraphOperation[]>;
2281
2387
  }
2282
2388
  declare class SecondLayer extends BaseClient {
2283
2389
  readonly streams: StreamsClient;
@@ -2287,14 +2393,8 @@ declare class SecondLayer extends BaseClient {
2287
2393
  readonly subscriptions: Subscriptions;
2288
2394
  constructor(options?: Partial<SecondLayerOptions>);
2289
2395
  /**
2290
- * Assemble a {@link ContextSnapshot} — the same orientation an MCP agent reads
2291
- * from `secondlayer://context`, but available to any SDK/CLI consumer. Reads
2292
- * run concurrently and degrade to `null` per field on failure.
2293
- */
2294
- /**
2295
2396
  * Up to 10 public reads in one round trip (`POST /v1/batch`). Each item
2296
- * keeps its own auth/quota/x402 semantics; forwarded credentials (API key,
2297
- * PAYMENT-BALANCE/SESSION) apply to every item.
2397
+ * is authorized on its own; the client's bearer token applies to every item.
2298
2398
  */
2299
2399
  batch(requests: Array<{
2300
2400
  path: string;
@@ -2306,6 +2406,12 @@ declare class SecondLayer extends BaseClient {
2306
2406
  body: unknown;
2307
2407
  }>;
2308
2408
  }>;
2409
+ /**
2410
+ * Assemble a {@link ContextSnapshot}: the same orientation an MCP agent
2411
+ * reads from `secondlayer://context`, available to any SDK/CLI consumer.
2412
+ * Reads run concurrently; a failed read lands as `{ value: null, error }`
2413
+ * on its field rather than rejecting the whole snapshot.
2414
+ */
2309
2415
  context(): Promise<ContextSnapshot>;
2310
2416
  }
2311
2417
  /**