@secondlayer/sdk 5.8.0 → 6.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.
@@ -1,7 +1,76 @@
1
1
  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"];
2
2
  type StreamsEventType = (typeof STREAMS_EVENT_TYPES)[number];
3
- type StreamsEventPayload = Record<string, unknown>;
4
- type StreamsEvent = {
3
+ /** A Clarity value as Streams serves it: the canonical hex string, a typed
4
+ * object carrying that hex (`{ hex }`), or a decoded Clarity-JSON object.
5
+ * Decode helpers (`decodeNftTransfer`, etc.) resolve it to a concrete value. */
6
+ type StreamsClarityValue = string | {
7
+ hex: string
8
+ } | Record<string, unknown>;
9
+ type StxTransferPayload = {
10
+ sender: string
11
+ recipient: string
12
+ amount: string
13
+ memo?: string
14
+ };
15
+ type StxMintPayload = {
16
+ recipient: string
17
+ amount: string
18
+ };
19
+ type StxBurnPayload = {
20
+ sender: string
21
+ amount: string
22
+ };
23
+ type StxLockPayload = {
24
+ locked_address: string
25
+ locked_amount: string
26
+ unlock_height: string
27
+ };
28
+ type FtTransferPayload = {
29
+ asset_identifier: string
30
+ sender: string
31
+ recipient: string
32
+ amount: string
33
+ };
34
+ type FtMintPayload = {
35
+ asset_identifier: string
36
+ recipient: string
37
+ amount: string
38
+ };
39
+ type FtBurnPayload = {
40
+ asset_identifier: string
41
+ sender: string
42
+ amount: string
43
+ };
44
+ type NftTransferPayload = {
45
+ asset_identifier: string
46
+ sender: string
47
+ recipient: string
48
+ value: StreamsClarityValue
49
+ /** Canonical serialized hex of `value`, when the stream carries it. */
50
+ raw_value?: string
51
+ };
52
+ type NftMintPayload = {
53
+ asset_identifier: string
54
+ recipient: string
55
+ value: StreamsClarityValue
56
+ raw_value?: string
57
+ };
58
+ type NftBurnPayload = {
59
+ asset_identifier: string
60
+ sender: string
61
+ value: StreamsClarityValue
62
+ raw_value?: string
63
+ };
64
+ type PrintPayload = {
65
+ contract_id?: string | null
66
+ topic?: string
67
+ value?: unknown
68
+ raw_value?: string
69
+ };
70
+ /** Union of every Streams payload shape, discriminated by `event_type` on the
71
+ * parent `StreamsEvent`. */
72
+ type StreamsEventPayload = StxTransferPayload | StxMintPayload | StxBurnPayload | StxLockPayload | FtTransferPayload | FtMintPayload | FtBurnPayload | NftTransferPayload | NftMintPayload | NftBurnPayload | PrintPayload;
73
+ type StreamsEventBase = {
5
74
  cursor: string
6
75
  block_height: number
7
76
  block_hash: string
@@ -9,15 +78,33 @@ type StreamsEvent = {
9
78
  tx_id: string
10
79
  tx_index: number
11
80
  event_index: number
12
- event_type: StreamsEventType
13
81
  contract_id: string | null
14
- payload: StreamsEventPayload
15
82
  ts: string
16
- };
83
+ /**
84
+ * True when this event's block is past the finality boundary (immutable).
85
+ * Optional for back-compat; the API always sets it on Streams responses.
86
+ */
87
+ finalized?: boolean
88
+ };
89
+ type StreamsEventOf<
90
+ T extends StreamsEventType,
91
+ P
92
+ > = StreamsEventBase & {
93
+ event_type: T
94
+ payload: P
95
+ };
96
+ /** A raw Streams event. Discriminated on `event_type`, so `event.payload`
97
+ * narrows to the matching payload shape once the type is checked. */
98
+ type StreamsEvent = StreamsEventOf<"stx_transfer", StxTransferPayload> | StreamsEventOf<"stx_mint", StxMintPayload> | StreamsEventOf<"stx_burn", StxBurnPayload> | StreamsEventOf<"stx_lock", StxLockPayload> | StreamsEventOf<"ft_transfer", FtTransferPayload> | StreamsEventOf<"ft_mint", FtMintPayload> | StreamsEventOf<"ft_burn", FtBurnPayload> | StreamsEventOf<"nft_transfer", NftTransferPayload> | StreamsEventOf<"nft_mint", NftMintPayload> | StreamsEventOf<"nft_burn", NftBurnPayload> | StreamsEventOf<"print", PrintPayload>;
17
99
  type StreamsTip = {
18
100
  block_height: number
19
101
  block_hash: string
20
102
  burn_block_height: number
103
+ /**
104
+ * Highest Stacks block past the burn-confirmation finality boundary.
105
+ * Optional for back-compat; the API always sets it.
106
+ */
107
+ finalized_height?: number
21
108
  lag_seconds: number
22
109
  };
23
110
  type StreamsCanonicalBlock = {
@@ -51,18 +138,29 @@ type StreamsReorgsListEnvelope = {
51
138
  reorgs: StreamsReorg[]
52
139
  next_since: string | null
53
140
  };
141
+ /** A filter that matches a single value or any value in a list. */
142
+ type StreamsFilterValue = string | readonly string[];
54
143
  type StreamsEventsListParams = {
55
144
  cursor?: string | null
56
145
  fromHeight?: number
57
146
  toHeight?: number
58
147
  types?: readonly StreamsEventType[]
59
- contractId?: string
148
+ /** Event types to exclude (applied after `types`). */
149
+ notTypes?: readonly StreamsEventType[]
150
+ contractId?: StreamsFilterValue
151
+ sender?: StreamsFilterValue
152
+ recipient?: StreamsFilterValue
153
+ assetIdentifier?: string
60
154
  limit?: number
61
155
  };
62
156
  type StreamsEventsStreamParams = {
63
157
  fromCursor?: string | null
64
158
  types?: readonly StreamsEventType[]
65
- contractId?: string
159
+ notTypes?: readonly StreamsEventType[]
160
+ contractId?: StreamsFilterValue
161
+ sender?: StreamsFilterValue
162
+ recipient?: StreamsFilterValue
163
+ assetIdentifier?: string
66
164
  batchSize?: number
67
165
  emptyBackoffMs?: number
68
166
  maxPages?: number
@@ -73,7 +171,11 @@ type StreamsEventsConsumeParams = {
73
171
  fromCursor?: string | null
74
172
  mode?: "tail" | "bounded"
75
173
  types?: readonly StreamsEventType[]
76
- contractId?: string
174
+ notTypes?: readonly StreamsEventType[]
175
+ contractId?: StreamsFilterValue
176
+ sender?: StreamsFilterValue
177
+ recipient?: StreamsFilterValue
178
+ assetIdentifier?: string
77
179
  batchSize?: number
78
180
  onBatch: (events: StreamsEvent[], envelope: StreamsEventsEnvelope) => Promise<string | null | undefined> | string | null | undefined
79
181
  emptyBackoffMs?: number
@@ -86,7 +188,63 @@ type StreamsEventsConsumeResult = {
86
188
  pages: number
87
189
  emptyPolls: number
88
190
  };
191
+ type StreamsEventsReplayParams = {
192
+ /** Start point: `"genesis"` (default) or a `<block>:<index>` cursor. */
193
+ from?: "genesis" | string
194
+ /**
195
+ * Called once per finalized dump file, in block order, before live tailing.
196
+ * Process the parquet with your own tooling (e.g. DuckDB) — the SDK does not
197
+ * decode parquet. Use `client.dumps.download(file)` to fetch + verify bytes.
198
+ */
199
+ onDumpFile: (file: StreamsDumpFile) => Promise<void> | void
200
+ /** Called per live page after the dump phase, like `consume`. */
201
+ onBatch: (events: StreamsEvent[], envelope: StreamsEventsEnvelope) => Promise<string | null | undefined> | string | null | undefined
202
+ mode?: "tail" | "bounded"
203
+ batchSize?: number
204
+ emptyBackoffMs?: number
205
+ maxPages?: number
206
+ maxEmptyPolls?: number
207
+ signal?: AbortSignal
208
+ };
89
209
  type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
210
+ /** One bulk parquet file in the dumps manifest. `path` is the object key under
211
+ * the dumps base URL. */
212
+ type StreamsDumpFile = {
213
+ path: string
214
+ from_block: number
215
+ to_block: number
216
+ min_cursor: string | null
217
+ max_cursor: string | null
218
+ row_count: number
219
+ byte_size: number
220
+ sha256: string
221
+ schema_version: number
222
+ created_at: string
223
+ };
224
+ type StreamsDumpsManifest = {
225
+ dataset: string
226
+ network: string
227
+ version: string
228
+ schema_version: number
229
+ generated_at: string
230
+ producer_version: string
231
+ finality_lag_blocks: number
232
+ /** Cursor at the end of the finalized bulk coverage — hand to live tailing. */
233
+ latest_finalized_cursor: string | null
234
+ coverage: {
235
+ from_block: number
236
+ to_block: number
237
+ }
238
+ files: StreamsDumpFile[]
239
+ };
240
+ type StreamsDumps = {
241
+ /** Fetch and parse the latest dumps manifest. */
242
+ list(): Promise<StreamsDumpsManifest>
243
+ /** Absolute URL for a manifest file. */
244
+ fileUrl(file: StreamsDumpFile): string
245
+ /** Download a parquet file and verify its sha256 against the manifest. */
246
+ download(file: StreamsDumpFile): Promise<Uint8Array>
247
+ };
90
248
  type StreamsClient = {
91
249
  events: {
92
250
  list(params?: StreamsEventsListParams): Promise<StreamsEventsEnvelope>
@@ -102,6 +260,14 @@ type StreamsClient = {
102
260
  */
103
261
  consume(params: StreamsEventsConsumeParams): Promise<StreamsEventsConsumeResult>
104
262
  /**
263
+ * Backfill from bulk dumps, then continue live from the dump→live seam in
264
+ * one call. Iterates finalized dump files (via `onDumpFile`) in block
265
+ * order, then tails live from the manifest's `latest_finalized_cursor`
266
+ * (exclusive input → no gap or duplicate at the seam). Requires
267
+ * `dumpsBaseUrl`.
268
+ */
269
+ replay(params: StreamsEventsReplayParams): Promise<StreamsEventsConsumeResult>
270
+ /**
105
271
  * Follow Streams as an async iterator.
106
272
  *
107
273
  * Use `stream` for live processors and watch-style apps. It tails
@@ -116,6 +282,8 @@ type StreamsClient = {
116
282
  reorgs: {
117
283
  list(params: StreamsReorgsListParams): Promise<StreamsReorgsListEnvelope>
118
284
  }
285
+ /** Bulk parquet dumps. Requires `dumpsBaseUrl` on the client. */
286
+ dumps: StreamsDumps
119
287
  canonical(height: number): Promise<StreamsCanonicalBlock>
120
288
  tip(): Promise<StreamsTip>
121
289
  };
@@ -123,6 +291,20 @@ type CreateStreamsClientOptions = {
123
291
  apiKey: string
124
292
  baseUrl?: string
125
293
  fetchImpl?: FetchLike
294
+ /**
295
+ * Public base URL for bulk parquet dumps (the R2/CDN bucket root). Required
296
+ * to use `client.dumps`. See `GET /public/streams/dumps/manifest`.
297
+ */
298
+ dumpsBaseUrl?: string
299
+ /**
300
+ * Verify the ed25519 `X-Signature` on every response (default off). Pass
301
+ * `true` to fetch the server's public key from
302
+ * `/public/streams/signing-key`, or `{ publicKey }` to pin a known PEM. A
303
+ * failed or missing signature throws `StreamsSignatureError`.
304
+ */
305
+ verify?: boolean | {
306
+ publicKey: string
307
+ }
126
308
  };
127
309
  declare function createStreamsClient(options: CreateStreamsClientOptions): StreamsClient;
128
310
  declare class AuthError extends Error {
@@ -144,16 +326,13 @@ declare class StreamsServerError extends Error {
144
326
  readonly body?: unknown;
145
327
  constructor(message: string, status: number, body?: unknown);
146
328
  }
147
- type FtTransferPayload = {
148
- asset_identifier: string
149
- sender: string
150
- recipient: string
151
- amount: string
152
- };
153
- type FtTransferEvent = StreamsEvent & {
329
+ /** Thrown when response signature verification is enabled and fails. */
330
+ declare class StreamsSignatureError extends Error {
331
+ constructor(message?: string);
332
+ }
333
+ type FtTransferEvent = Extract<StreamsEvent, {
154
334
  event_type: "ft_transfer"
155
- payload: FtTransferPayload
156
- };
335
+ }>;
157
336
  type DecodedFtTransferPayload = {
158
337
  asset_identifier: string
159
338
  contract_id: string
@@ -174,18 +353,9 @@ type DecodedFtTransfer = {
174
353
  };
175
354
  declare function isFtTransfer(event: StreamsEvent): event is FtTransferEvent;
176
355
  declare function decodeFtTransfer(event: StreamsEvent): DecodedFtTransfer;
177
- type NftTransferPayload = {
178
- asset_identifier: string
179
- sender: string
180
- recipient: string
181
- value: string | {
182
- hex: string
183
- }
184
- };
185
- type NftTransferEvent = StreamsEvent & {
356
+ type NftTransferEvent = Extract<StreamsEvent, {
186
357
  event_type: "nft_transfer"
187
- payload: NftTransferPayload
188
- };
358
+ }>;
189
359
  type DecodedNftTransferPayload = {
190
360
  asset_identifier: string
191
361
  contract_id: string
@@ -410,4 +580,4 @@ type DecodedEventColumns = {
410
580
  payload?: unknown
411
581
  };
412
582
  type DecodedEventRow = DecodedFtTransfer | DecodedNftTransfer | DecodedStxTransfer | DecodedStxMint | DecodedStxBurn | DecodedStxLock | DecodedFtMint | DecodedFtBurn | DecodedNftMint | DecodedNftBurn | DecodedPrint;
413
- export { isStxTransfer, isStxMint, isStxLock, isStxBurn, isPrint, isNftTransfer, isNftMint, isNftBurn, isFtTransfer, isFtMint, isFtBurn, decodeStxTransfer, decodeStxMint, decodeStxLock, decodeStxBurn, decodePrint, decodeNftTransfer, decodeNftMint, decodeNftBurn, decodeFtTransfer, decodeFtMint, decodeFtBurn, createStreamsClient, ValidationError, StreamsTip, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorg, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsClient, StreamsCanonicalBlock, STREAMS_EVENT_TYPES, RateLimitError, NftTransferPayload, NftTransferEvent, FtTransferPayload, FtTransferEvent, FetchLike, DecodedStxTransferPayload, DecodedStxTransfer, DecodedStxMintPayload, DecodedStxMint, DecodedStxLockPayload, DecodedStxLock, DecodedStxBurnPayload, DecodedStxBurn, DecodedPrintValue, DecodedPrintPayload, DecodedPrint, DecodedNftTransferPayload, DecodedNftTransfer, DecodedNftMintPayload, DecodedNftMint, DecodedNftBurnPayload, DecodedNftBurn, DecodedFtTransferPayload, DecodedFtTransfer, DecodedFtMintPayload, DecodedFtMint, DecodedFtBurnPayload, DecodedFtBurn, DecodedEventRow, DecodedEventColumns, AuthError };
583
+ export { isStxTransfer, isStxMint, isStxLock, isStxBurn, isPrint, isNftTransfer, isNftMint, isNftBurn, isFtTransfer, isFtMint, isFtBurn, decodeStxTransfer, decodeStxMint, decodeStxLock, decodeStxBurn, decodePrint, decodeNftTransfer, decodeNftMint, decodeNftBurn, decodeFtTransfer, decodeFtMint, decodeFtBurn, createStreamsClient, ValidationError, StreamsTip, StreamsSignatureError, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorg, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsDumpsManifest, StreamsDumps, StreamsDumpFile, StreamsClient, StreamsCanonicalBlock, STREAMS_EVENT_TYPES, RateLimitError, NftTransferPayload, NftTransferEvent, FtTransferPayload, FtTransferEvent, FetchLike, DecodedStxTransferPayload, DecodedStxTransfer, DecodedStxMintPayload, DecodedStxMint, DecodedStxLockPayload, DecodedStxLock, DecodedStxBurnPayload, DecodedStxBurn, DecodedPrintValue, DecodedPrintPayload, DecodedPrint, DecodedNftTransferPayload, DecodedNftTransfer, DecodedNftMintPayload, DecodedNftMint, DecodedNftBurnPayload, DecodedNftBurn, DecodedFtTransferPayload, DecodedFtTransfer, DecodedFtMintPayload, DecodedFtMint, DecodedFtBurnPayload, DecodedFtBurn, DecodedEventRow, DecodedEventColumns, AuthError };
@@ -1,3 +1,6 @@
1
+ // src/streams/client.ts
2
+ import { ed25519 } from "@secondlayer/shared";
3
+
1
4
  // src/streams/consumer.ts
2
5
  async function defaultSleep(ms, signal) {
3
6
  if (signal?.aborted)
@@ -26,7 +29,11 @@ async function consumeStreamsEvents(opts) {
26
29
  cursor,
27
30
  limit: opts.batchSize,
28
31
  types: opts.types,
29
- contractId: opts.contractId
32
+ notTypes: opts.notTypes,
33
+ contractId: opts.contractId,
34
+ sender: opts.sender,
35
+ recipient: opts.recipient,
36
+ assetIdentifier: opts.assetIdentifier
30
37
  });
31
38
  pages++;
32
39
  const returnedCursor = await opts.onBatch(envelope.events, envelope);
@@ -61,7 +68,11 @@ async function* streamStreamsEvents(opts) {
61
68
  cursor,
62
69
  limit: opts.batchSize,
63
70
  types: opts.types,
64
- contractId: opts.contractId
71
+ notTypes: opts.notTypes,
72
+ contractId: opts.contractId,
73
+ sender: opts.sender,
74
+ recipient: opts.recipient,
75
+ assetIdentifier: opts.assetIdentifier
65
76
  });
66
77
  pages++;
67
78
  for (const event of envelope.events) {
@@ -86,6 +97,9 @@ async function* streamStreamsEvents(opts) {
86
97
  }
87
98
  }
88
99
 
100
+ // src/streams/dumps.ts
101
+ import { createHash } from "node:crypto";
102
+
89
103
  // src/streams/errors.ts
90
104
  class AuthError extends Error {
91
105
  status = 401;
@@ -127,7 +141,60 @@ class StreamsServerError extends Error {
127
141
  }
128
142
  }
129
143
 
144
+ class StreamsSignatureError extends Error {
145
+ constructor(message = "Streams response signature verification failed.") {
146
+ super(message);
147
+ this.name = "StreamsSignatureError";
148
+ }
149
+ }
150
+
151
+ // src/streams/dumps.ts
152
+ function createStreamsDumps(opts) {
153
+ const baseUrl = opts.baseUrl?.replace(/\/+$/, "");
154
+ function requireBaseUrl() {
155
+ if (!baseUrl) {
156
+ throw new StreamsServerError("Streams dumps require `dumpsBaseUrl` on createStreamsClient.", 0);
157
+ }
158
+ return baseUrl;
159
+ }
160
+ function fileUrl(file) {
161
+ return `${requireBaseUrl()}/${file.path.replace(/^\/+/, "")}`;
162
+ }
163
+ async function list() {
164
+ const url = `${requireBaseUrl()}/manifest/latest.json`;
165
+ const res = await opts.fetchImpl(url);
166
+ if (!res.ok) {
167
+ throw new StreamsServerError(`Could not fetch dumps manifest (${res.status}).`, res.status);
168
+ }
169
+ return await res.json();
170
+ }
171
+ async function download(file) {
172
+ const res = await opts.fetchImpl(fileUrl(file));
173
+ if (!res.ok) {
174
+ throw new StreamsServerError(`Could not download dump ${file.path} (${res.status}).`, res.status);
175
+ }
176
+ const bytes = new Uint8Array(await res.arrayBuffer());
177
+ const digest = createHash("sha256").update(bytes).digest("hex");
178
+ if (digest !== file.sha256) {
179
+ throw new StreamsSignatureError(`Dump ${file.path} sha256 mismatch (expected ${file.sha256}, got ${digest}).`);
180
+ }
181
+ return bytes;
182
+ }
183
+ return { list, fileUrl, download };
184
+ }
185
+
130
186
  // src/streams/client.ts
187
+ function cursorTuple(cursor) {
188
+ if (!cursor)
189
+ return [-1, -1];
190
+ const [block, index] = cursor.split(":");
191
+ return [Number(block), Number(index)];
192
+ }
193
+ function maxCursor(a, b) {
194
+ const [ah, ai] = cursorTuple(a);
195
+ const [bh, bi] = cursorTuple(b);
196
+ return ah > bh || ah === bh && ai >= bi ? a : b;
197
+ }
131
198
  var DEFAULT_STREAMS_BASE_URL = "https://api.secondlayer.tools";
132
199
  function normalizeBaseUrl(baseUrl) {
133
200
  return baseUrl.replace(/\/+$/, "");
@@ -137,6 +204,13 @@ function appendSearchParam(params, name, value) {
137
204
  return;
138
205
  params.set(name, String(value));
139
206
  }
207
+ function appendListParam(params, name, value) {
208
+ if (value === undefined || value === null)
209
+ return;
210
+ const joined = Array.isArray(value) ? value.join(",") : value;
211
+ if (joined.length > 0)
212
+ params.set(name, joined);
213
+ }
140
214
  async function responseBody(response) {
141
215
  const text = await response.text();
142
216
  if (text.length === 0)
@@ -175,21 +249,87 @@ async function mapStreamsError(response) {
175
249
  function createStreamsClient(options) {
176
250
  const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_STREAMS_BASE_URL);
177
251
  const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
252
+ const verify = options.verify ?? false;
253
+ const dumps = createStreamsDumps({
254
+ baseUrl: options.dumpsBaseUrl,
255
+ fetchImpl
256
+ });
257
+ let keyPromise = null;
258
+ function loadKey() {
259
+ if (keyPromise)
260
+ return keyPromise;
261
+ keyPromise = (async () => {
262
+ if (typeof verify === "object") {
263
+ return {
264
+ keyId: ed25519.ed25519KeyId(verify.publicKey),
265
+ publicKey: ed25519.loadEd25519PublicKey(verify.publicKey)
266
+ };
267
+ }
268
+ const res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
269
+ if (!res.ok) {
270
+ throw new StreamsSignatureError(`Could not fetch signing key (${res.status}).`);
271
+ }
272
+ const body = await res.json();
273
+ if (!body.public_key_pem) {
274
+ throw new StreamsSignatureError("Signing key response missing key.");
275
+ }
276
+ return {
277
+ keyId: body.key_id ?? ed25519.ed25519KeyId(body.public_key_pem),
278
+ publicKey: ed25519.loadEd25519PublicKey(body.public_key_pem)
279
+ };
280
+ })();
281
+ return keyPromise;
282
+ }
178
283
  async function request(path) {
179
284
  const response = await fetchImpl(`${baseUrl}${path}`, {
180
285
  headers: { Authorization: `Bearer ${options.apiKey}` }
181
286
  });
182
287
  if (!response.ok)
183
288
  await mapStreamsError(response);
184
- return await response.json();
289
+ const text = await response.text();
290
+ if (verify) {
291
+ const signature = response.headers.get("X-Signature");
292
+ if (!signature) {
293
+ throw new StreamsSignatureError("Response is missing X-Signature.");
294
+ }
295
+ const responseKeyId = response.headers.get("X-Signature-KeyId");
296
+ let key = await loadKey();
297
+ if (responseKeyId && responseKeyId !== key.keyId) {
298
+ if (typeof verify === "object") {
299
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
300
+ }
301
+ keyPromise = null;
302
+ key = await loadKey();
303
+ if (responseKeyId !== key.keyId) {
304
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
305
+ }
306
+ }
307
+ if (!ed25519.verifyEd25519(text, signature, key.publicKey)) {
308
+ throw new StreamsSignatureError;
309
+ }
310
+ }
311
+ return JSON.parse(text);
185
312
  }
186
313
  const fetchEvents = async ({
187
314
  cursor,
188
315
  limit,
189
316
  types,
190
- contractId
317
+ notTypes,
318
+ contractId,
319
+ sender,
320
+ recipient,
321
+ assetIdentifier
191
322
  }) => {
192
- return listEvents({ cursor, limit, types, contractId });
323
+ return listEvents({
324
+ cursor,
325
+ limit,
326
+ types,
327
+ notTypes,
328
+ contractId,
329
+ sender,
330
+ recipient,
331
+ assetIdentifier
332
+ });
193
333
  };
194
334
  async function listEvents(params = {}) {
195
335
  const searchParams = new URLSearchParams;
@@ -197,10 +337,16 @@ function createStreamsClient(options) {
197
337
  appendSearchParam(searchParams, "from_height", params.fromHeight);
198
338
  appendSearchParam(searchParams, "to_height", params.toHeight);
199
339
  appendSearchParam(searchParams, "limit", params.limit);
200
- appendSearchParam(searchParams, "contract_id", params.contractId);
340
+ appendListParam(searchParams, "contract_id", params.contractId);
341
+ appendListParam(searchParams, "sender", params.sender);
342
+ appendListParam(searchParams, "recipient", params.recipient);
343
+ appendSearchParam(searchParams, "asset_identifier", params.assetIdentifier);
201
344
  if (params.types?.length) {
202
345
  searchParams.set("types", params.types.join(","));
203
346
  }
347
+ if (params.notTypes?.length) {
348
+ searchParams.set("not_types", params.notTypes.join(","));
349
+ }
204
350
  const query = searchParams.toString();
205
351
  return request(`/v1/streams/events${query ? `?${query}` : ""}`);
206
352
  }
@@ -215,7 +361,11 @@ function createStreamsClient(options) {
215
361
  fromCursor: params.fromCursor,
216
362
  mode: params.mode,
217
363
  types: params.types,
364
+ notTypes: params.notTypes,
218
365
  contractId: params.contractId,
366
+ sender: params.sender,
367
+ recipient: params.recipient,
368
+ assetIdentifier: params.assetIdentifier,
219
369
  batchSize: params.batchSize ?? 100,
220
370
  fetchEvents,
221
371
  onBatch: params.onBatch,
@@ -229,7 +379,11 @@ function createStreamsClient(options) {
229
379
  return streamStreamsEvents({
230
380
  fromCursor: params.fromCursor,
231
381
  types: params.types,
382
+ notTypes: params.notTypes,
232
383
  contractId: params.contractId,
384
+ sender: params.sender,
385
+ recipient: params.recipient,
386
+ assetIdentifier: params.assetIdentifier,
233
387
  batchSize: params.batchSize ?? 100,
234
388
  emptyBackoffMs: params.emptyBackoffMs,
235
389
  maxPages: params.maxPages,
@@ -237,6 +391,29 @@ function createStreamsClient(options) {
237
391
  signal: params.signal,
238
392
  fetchEvents
239
393
  });
394
+ },
395
+ async replay(params) {
396
+ const fromCursor = params.from === "genesis" ? null : params.from ?? null;
397
+ const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
398
+ const manifest = await dumps.list();
399
+ const files = manifest.files.filter((file) => file.to_block >= fromBlock).sort((a, b) => a.from_block - b.from_block || a.to_block - b.to_block);
400
+ for (const file of files) {
401
+ if (params.signal?.aborted)
402
+ break;
403
+ await params.onDumpFile(file);
404
+ }
405
+ const seam = maxCursor(fromCursor, manifest.latest_finalized_cursor);
406
+ return consumeStreamsEvents({
407
+ fromCursor: seam,
408
+ mode: params.mode ?? "tail",
409
+ batchSize: params.batchSize ?? 100,
410
+ fetchEvents,
411
+ onBatch: params.onBatch,
412
+ emptyBackoffMs: params.emptyBackoffMs,
413
+ maxPages: params.maxPages,
414
+ maxEmptyPolls: params.maxEmptyPolls,
415
+ signal: params.signal
416
+ });
240
417
  }
241
418
  },
242
419
  blocks: {
@@ -253,6 +430,7 @@ function createStreamsClient(options) {
253
430
  return request(`/v1/streams/reorgs${query ? `?${query}` : ""}`);
254
431
  }
255
432
  },
433
+ dumps,
256
434
  canonical(height) {
257
435
  return request(`/v1/streams/canonical/${height}`);
258
436
  },
@@ -652,11 +830,12 @@ export {
652
830
  decodeFtBurn,
653
831
  createStreamsClient,
654
832
  ValidationError,
833
+ StreamsSignatureError,
655
834
  StreamsServerError,
656
835
  STREAMS_EVENT_TYPES,
657
836
  RateLimitError,
658
837
  AuthError
659
838
  };
660
839
 
661
- //# debugId=3A844A761118CEDE64756E2164756E21
840
+ //# debugId=E556011474C772DE64756E2164756E21
662
841
  //# sourceMappingURL=index.js.map