@secondlayer/sdk 3.3.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,107 @@ const sl = new SecondLayer({
19
19
  });
20
20
  ```
21
21
 
22
+ ## Stacks Streams
23
+
24
+ Typed HTTP client.
25
+
26
+ `sk-sl_streams_status_public` is a public, non-secret Free-tier key used by the
27
+ Second Layer status page. Production apps should use their own Streams API key.
28
+
29
+ ```typescript
30
+ import { createStreamsClient } from "@secondlayer/sdk";
31
+
32
+ const client = createStreamsClient({
33
+ apiKey: process.env.SECONDLAYER_API_KEY!,
34
+ baseUrl: process.env.SECONDLAYER_API_URL,
35
+ });
36
+
37
+ const tip = await client.tip();
38
+ const page = await client.events.list({
39
+ types: ["ft_transfer"],
40
+ limit: 10,
41
+ });
42
+
43
+ console.log({ tip, firstCursor: page.events[0]?.cursor });
44
+ ```
45
+
46
+ Checkpointed consumer.
47
+
48
+ Use `client.events.consume` for indexers and ETL jobs. Write your database rows
49
+ inside `onBatch`, then return the cursor you committed. It exits when
50
+ `maxPages`, `maxEmptyPolls`, or `signal` stops it.
51
+
52
+ ```typescript
53
+ import { createStreamsClient } from "@secondlayer/sdk";
54
+
55
+ const client = createStreamsClient({
56
+ apiKey: process.env.SECONDLAYER_API_KEY!,
57
+ });
58
+
59
+ await client.events.consume({
60
+ types: ["ft_transfer"],
61
+ batchSize: 100,
62
+ maxPages: 1,
63
+ onBatch: async (events, envelope) => {
64
+ for (const event of events) {
65
+ console.log(event.cursor, event.tx_id);
66
+ }
67
+ return envelope.next_cursor;
68
+ },
69
+ });
70
+ ```
71
+
72
+ Live stream.
73
+
74
+ Use `client.events.stream` for live processors and watch-style apps. It follows
75
+ the tip indefinitely. Stop it with an `AbortSignal`.
76
+
77
+ ```typescript
78
+ import { createStreamsClient } from "@secondlayer/sdk";
79
+
80
+ const client = createStreamsClient({
81
+ apiKey: process.env.SECONDLAYER_API_KEY!,
82
+ });
83
+
84
+ const abort = new AbortController();
85
+ process.once("SIGINT", () => abort.abort());
86
+
87
+ for await (const event of client.events.stream({
88
+ types: ["ft_transfer"],
89
+ batchSize: 100,
90
+ signal: abort.signal,
91
+ })) {
92
+ console.log(event.cursor, event.tx_id);
93
+ }
94
+ ```
95
+
96
+ Decoder helper.
97
+
98
+ ```typescript
99
+ import {
100
+ createStreamsClient,
101
+ decodeFtTransfer,
102
+ isFtTransfer,
103
+ } from "@secondlayer/sdk";
104
+
105
+ const client = createStreamsClient({
106
+ apiKey: process.env.SECONDLAYER_API_KEY!,
107
+ });
108
+
109
+ for await (const event of client.events.stream({ types: ["ft_transfer"] })) {
110
+ if (!isFtTransfer(event)) continue;
111
+ const transfer = decodeFtTransfer(event);
112
+ console.log(transfer.decoded_payload);
113
+ break;
114
+ }
115
+ ```
116
+
117
+ Helper convention: each event helper is a pure function with no shared state.
118
+ Use `is<EventName>(event)` as the type guard and `decode<EventName>(event)` as
119
+ the decoder. Decoders throw when the event type or payload is malformed. Add new
120
+ helpers beside `src/streams/ft-transfer.ts` and export them through
121
+ `src/streams/index.ts`.
122
+
22
123
  ## Subgraphs
23
124
 
24
125
  Deploy and query subgraphs (custom indexers).
package/dist/index.d.ts CHANGED
@@ -102,6 +102,92 @@ declare class Subgraphs extends BaseClient {
102
102
  private createTableClient;
103
103
  }
104
104
  import { InferSubgraphClient as InferSubgraphClient2 } from "@secondlayer/subgraphs";
105
+ type IndexTip = {
106
+ block_height: number
107
+ lag_seconds: number
108
+ };
109
+ type FtTransfer = {
110
+ cursor: string
111
+ block_height: number
112
+ tx_id: string
113
+ tx_index: number
114
+ event_index: number
115
+ event_type: "ft_transfer"
116
+ contract_id: string
117
+ asset_identifier: string
118
+ sender: string
119
+ recipient: string
120
+ amount: string
121
+ };
122
+ type FtTransfersEnvelope = {
123
+ events: FtTransfer[]
124
+ next_cursor: string | null
125
+ tip: IndexTip
126
+ reorgs: never[]
127
+ };
128
+ type FtTransfersListParams = {
129
+ cursor?: string | null
130
+ fromCursor?: string | null
131
+ limit?: number
132
+ contractId?: string
133
+ sender?: string
134
+ recipient?: string
135
+ fromHeight?: number
136
+ toHeight?: number
137
+ };
138
+ type FtTransfersWalkParams = Omit<FtTransfersListParams, "limit"> & {
139
+ batchSize?: number
140
+ signal?: AbortSignal
141
+ };
142
+ type NftTransfer = {
143
+ cursor: string
144
+ block_height: number
145
+ tx_id: string
146
+ tx_index: number
147
+ event_index: number
148
+ event_type: "nft_transfer"
149
+ contract_id: string
150
+ asset_identifier: string
151
+ sender: string
152
+ recipient: string
153
+ value: string
154
+ };
155
+ type NftTransfersEnvelope = {
156
+ events: NftTransfer[]
157
+ next_cursor: string | null
158
+ tip: IndexTip
159
+ reorgs: never[]
160
+ };
161
+ type NftTransfersListParams = {
162
+ cursor?: string | null
163
+ fromCursor?: string | null
164
+ limit?: number
165
+ contractId?: string
166
+ assetIdentifier?: string
167
+ sender?: string
168
+ recipient?: string
169
+ fromHeight?: number
170
+ toHeight?: number
171
+ };
172
+ type NftTransfersWalkParams = Omit<NftTransfersListParams, "limit"> & {
173
+ batchSize?: number
174
+ signal?: AbortSignal
175
+ };
176
+ declare class Index extends BaseClient {
177
+ constructor(options?: Partial<SecondLayerOptions>);
178
+ readonly ftTransfers: {
179
+ list: (params?: FtTransfersListParams) => Promise<FtTransfersEnvelope>
180
+ walk: (params?: FtTransfersWalkParams) => AsyncIterable<FtTransfer>
181
+ };
182
+ readonly nftTransfers: {
183
+ list: (params?: NftTransfersListParams) => Promise<NftTransfersEnvelope>
184
+ walk: (params?: NftTransfersWalkParams) => AsyncIterable<NftTransfer>
185
+ };
186
+ private listFtTransfers;
187
+ private listNftTransfers;
188
+ private walkFtTransfers;
189
+ private walkNftTransfers;
190
+ }
105
191
  import { CreateSubscriptionRequest, CreateSubscriptionResponse, DeadRow, DeliveryRow, ReplayResult, RotateSecretResponse, SubscriptionDetail, SubscriptionSummary, UpdateSubscriptionRequest } from "@secondlayer/shared/schemas/subscriptions";
106
192
  import { CreateSubscriptionRequest as CreateSubscriptionRequest2, CreateSubscriptionResponse as CreateSubscriptionResponse2, DeadRow as DeadRow2, DeliveryRow as DeliveryRow2, ReplayResult as ReplayResult2, RotateSecretResponse as RotateSecretResponse2, SubscriptionDetail as SubscriptionDetail2, SubscriptionFormat, SubscriptionRuntime, SubscriptionStatus, SubscriptionSummary as SubscriptionSummary2, UpdateSubscriptionRequest as UpdateSubscriptionRequest2 } from "@secondlayer/shared/schemas/subscriptions";
107
193
  declare class Subscriptions extends BaseClient {
@@ -132,6 +218,7 @@ declare class Subscriptions extends BaseClient {
132
218
  }>;
133
219
  }
134
220
  declare class SecondLayer extends BaseClient {
221
+ readonly index: Index;
135
222
  readonly subgraphs: Subgraphs;
136
223
  readonly subscriptions: Subscriptions;
137
224
  constructor(options?: Partial<SecondLayerOptions>);
@@ -154,6 +241,188 @@ declare function getSubgraph<T extends {
154
241
  name: string
155
242
  schema: Record<string, unknown>
156
243
  }>(def: T, options?: Partial<SecondLayerOptions> | SecondLayer | Subgraphs): InferSubgraphClient2<T>;
244
+ declare const STREAMS_EVENT_TYPES: readonly ["stx_transfer", "stx_mint", "stx_burn", "stx_lock", "ft_transfer", "ft_mint", "ft_burn", "nft_transfer", "nft_mint", "nft_burn", "print"];
245
+ type StreamsEventType = (typeof STREAMS_EVENT_TYPES)[number];
246
+ type StreamsEventPayload = Record<string, unknown>;
247
+ type StreamsEvent = {
248
+ cursor: string
249
+ block_height: number
250
+ index_block_hash: string
251
+ burn_block_height: number
252
+ tx_id: string
253
+ tx_index: number
254
+ event_index: number
255
+ event_type: StreamsEventType
256
+ contract_id: string | null
257
+ payload: StreamsEventPayload
258
+ ts: string
259
+ };
260
+ type StreamsTip = {
261
+ block_height: number
262
+ index_block_hash: string
263
+ burn_block_height: number
264
+ lag_seconds: number
265
+ };
266
+ type StreamsReorg = {
267
+ detected_at: string
268
+ fork_point_height: number
269
+ orphaned_range: {
270
+ from: string
271
+ to: string
272
+ }
273
+ new_canonical_tip: string
274
+ };
275
+ type StreamsEventsEnvelope = {
276
+ events: StreamsEvent[]
277
+ next_cursor: string | null
278
+ tip: StreamsTip
279
+ reorgs: StreamsReorg[]
280
+ };
281
+ type StreamsEventsListParams = {
282
+ cursor?: string | null
283
+ fromHeight?: number
284
+ toHeight?: number
285
+ types?: readonly StreamsEventType[]
286
+ limit?: number
287
+ };
288
+ type StreamsEventsStreamParams = {
289
+ fromCursor?: string | null
290
+ types?: readonly StreamsEventType[]
291
+ batchSize?: number
292
+ emptyBackoffMs?: number
293
+ maxPages?: number
294
+ maxEmptyPolls?: number
295
+ signal?: AbortSignal
296
+ };
297
+ type StreamsEventsConsumeParams = {
298
+ fromCursor?: string | null
299
+ mode?: "tail" | "bounded"
300
+ types?: readonly StreamsEventType[]
301
+ batchSize?: number
302
+ onBatch: (events: StreamsEvent[], envelope: StreamsEventsEnvelope) => Promise<string | null | undefined> | string | null | undefined
303
+ emptyBackoffMs?: number
304
+ maxPages?: number
305
+ maxEmptyPolls?: number
306
+ signal?: AbortSignal
307
+ };
308
+ type StreamsEventsConsumeResult = {
309
+ cursor: string | null
310
+ pages: number
311
+ emptyPolls: number
312
+ };
313
+ type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
314
+ type StreamsClient = {
315
+ events: {
316
+ list(params?: StreamsEventsListParams): Promise<StreamsEventsEnvelope>
317
+ /**
318
+ * Pull pages from Streams and call `onBatch` after each page.
319
+ *
320
+ * Use `consume` for indexers and ETL jobs that own checkpointing. Return
321
+ * the checkpoint cursor from `onBatch`. Default `mode: "tail"` keeps
322
+ * polling when caught up; `mode: "bounded"` exits on the first empty page.
323
+ * The consumer also exits when `maxPages`, `maxEmptyPolls`, or `signal`
324
+ * stops it.
325
+ */
326
+ consume(params: StreamsEventsConsumeParams): Promise<StreamsEventsConsumeResult>
327
+ /**
328
+ * Follow Streams as an async iterator.
329
+ *
330
+ * Use `stream` for live processors and watch-style apps. It tails
331
+ * indefinitely by default and stops when its `AbortSignal`, `maxPages`, or
332
+ * `maxEmptyPolls` stops it.
333
+ */
334
+ stream(params?: StreamsEventsStreamParams): AsyncIterable<StreamsEvent>
335
+ }
336
+ tip(): Promise<StreamsTip>
337
+ };
338
+ type CreateStreamsClientOptions = {
339
+ apiKey: string
340
+ baseUrl?: string
341
+ fetchImpl?: FetchLike
342
+ };
343
+ declare function createStreamsClient(options: CreateStreamsClientOptions): StreamsClient;
344
+ declare class AuthError extends Error {
345
+ readonly status = 401;
346
+ constructor(message?: string);
347
+ }
348
+ declare class RateLimitError extends Error {
349
+ readonly retryAfter?: string;
350
+ readonly status = 429;
351
+ constructor(message?: string, retryAfter?: string);
352
+ }
353
+ declare class ValidationError extends Error {
354
+ readonly status: number;
355
+ readonly body?: unknown;
356
+ constructor(message: string, status: number, body?: unknown);
357
+ }
358
+ declare class StreamsServerError extends Error {
359
+ readonly status: number;
360
+ readonly body?: unknown;
361
+ constructor(message: string, status: number, body?: unknown);
362
+ }
363
+ type FtTransferPayload = {
364
+ asset_identifier: string
365
+ sender: string
366
+ recipient: string
367
+ amount: string
368
+ };
369
+ type FtTransferEvent = StreamsEvent & {
370
+ event_type: "ft_transfer"
371
+ payload: FtTransferPayload
372
+ };
373
+ type DecodedFtTransferPayload = {
374
+ asset_identifier: string
375
+ contract_id: string
376
+ token_name: string | null
377
+ sender: string
378
+ recipient: string
379
+ amount: string
380
+ };
381
+ type DecodedFtTransfer = {
382
+ cursor: string
383
+ block_height: number
384
+ tx_id: string
385
+ tx_index: number
386
+ event_index: number
387
+ event_type: "ft_transfer"
388
+ decoded_payload: DecodedFtTransferPayload
389
+ source_cursor: string
390
+ };
391
+ declare function isFtTransfer(event: StreamsEvent): event is FtTransferEvent;
392
+ declare function decodeFtTransfer(event: StreamsEvent): DecodedFtTransfer;
393
+ type NftTransferPayload = {
394
+ asset_identifier: string
395
+ sender: string
396
+ recipient: string
397
+ value: string | {
398
+ hex: string
399
+ }
400
+ };
401
+ type NftTransferEvent = StreamsEvent & {
402
+ event_type: "nft_transfer"
403
+ payload: NftTransferPayload
404
+ };
405
+ type DecodedNftTransferPayload = {
406
+ asset_identifier: string
407
+ contract_id: string
408
+ token_name: string | null
409
+ sender: string
410
+ recipient: string
411
+ value: string
412
+ };
413
+ type DecodedNftTransfer = {
414
+ cursor: string
415
+ block_height: number
416
+ tx_id: string
417
+ tx_index: number
418
+ event_index: number
419
+ event_type: "nft_transfer"
420
+ decoded_payload: DecodedNftTransferPayload
421
+ source_cursor: string
422
+ };
423
+ declare function isNftTransfer(event: StreamsEvent): event is NftTransferEvent;
424
+ declare function decodeNftTransfer(event: StreamsEvent): DecodedNftTransfer;
425
+ type DecodedEventRow = DecodedFtTransfer | DecodedNftTransfer;
157
426
  import { SubgraphAgentSchema as SubgraphAgentSchema3, SubgraphSpecFormat as SubgraphSpecFormat2, SubgraphSpecOptions as SubgraphSpecOptions3 } from "@secondlayer/shared/subgraphs/spec";
158
427
  /**
159
428
  * Error thrown by {@link SecondLayer} when an API request fails.
@@ -214,4 +483,4 @@ declare class VersionConflictError extends ApiError {
214
483
  * ```
215
484
  */
216
485
  declare function verifyWebhookSignature(rawBody: string, signatureHeader: string, secret: string, toleranceSeconds?: number): boolean;
217
- export { verifyWebhookSignature, getSubgraph, VersionConflictError, UpdateSubscriptionRequest2 as UpdateSubscriptionRequest, Subscriptions, SubscriptionSummary2 as SubscriptionSummary, SubscriptionStatus, SubscriptionRuntime, SubscriptionFormat, SubscriptionDetail2 as SubscriptionDetail, Subgraphs, SubgraphSpecOptions3 as SubgraphSpecOptions, SubgraphSpecFormat2 as SubgraphSpecFormat, SubgraphAgentSchema3 as SubgraphAgentSchema, SecondLayerOptions, SecondLayer, RotateSecretResponse2 as RotateSecretResponse, ReplayResult2 as ReplayResult, DeliveryRow2 as DeliveryRow, DeadRow2 as DeadRow, CreateSubscriptionResponse2 as CreateSubscriptionResponse, CreateSubscriptionRequest2 as CreateSubscriptionRequest, ApiError };
486
+ export { verifyWebhookSignature, isNftTransfer, isFtTransfer, getSubgraph, decodeNftTransfer, decodeFtTransfer, createStreamsClient, VersionConflictError, ValidationError, UpdateSubscriptionRequest2 as UpdateSubscriptionRequest, Subscriptions, SubscriptionSummary2 as SubscriptionSummary, SubscriptionStatus, SubscriptionRuntime, SubscriptionFormat, SubscriptionDetail2 as SubscriptionDetail, Subgraphs, SubgraphSpecOptions3 as SubgraphSpecOptions, SubgraphSpecFormat2 as SubgraphSpecFormat, SubgraphAgentSchema3 as SubgraphAgentSchema, StreamsTip, StreamsServerError, StreamsReorg, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsClient, SecondLayerOptions, SecondLayer, RotateSecretResponse2 as RotateSecretResponse, ReplayResult2 as ReplayResult, RateLimitError, NftTransfersWalkParams, NftTransfersListParams, NftTransfersEnvelope, NftTransferPayload, NftTransferEvent, NftTransfer, IndexTip, Index, FtTransfersWalkParams, FtTransfersListParams, FtTransfersEnvelope, FtTransferPayload, FtTransferEvent, FtTransfer, FetchLike, DeliveryRow2 as DeliveryRow, DecodedNftTransferPayload, DecodedNftTransfer, DecodedFtTransferPayload, DecodedFtTransfer, DecodedEventRow, DeadRow2 as DeadRow, CreateSubscriptionResponse2 as CreateSubscriptionResponse, CreateSubscriptionRequest2 as CreateSubscriptionRequest, AuthError, ApiError };