@secondlayer/sdk 5.9.0 → 6.1.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,205 @@
1
1
  // src/streams/client.ts
2
2
  import { ed25519 } from "@secondlayer/shared";
3
3
 
4
+ // src/errors.ts
5
+ class ApiError extends Error {
6
+ status;
7
+ body;
8
+ code;
9
+ constructor(status, message, body, code) {
10
+ super(message);
11
+ this.status = status;
12
+ this.body = body;
13
+ this.code = code;
14
+ this.name = "ApiError";
15
+ }
16
+ }
17
+
18
+ class VersionConflictError extends ApiError {
19
+ currentVersion;
20
+ expectedVersion;
21
+ constructor(currentVersion, expectedVersion, message = `Version conflict: expected ${expectedVersion}, current ${currentVersion}`) {
22
+ super(409, message, { currentVersion, expectedVersion });
23
+ this.currentVersion = currentVersion;
24
+ this.expectedVersion = expectedVersion;
25
+ this.name = "VersionConflictError";
26
+ }
27
+ }
28
+
29
+ // src/base.ts
30
+ var DEFAULT_BASE_URL = "https://api.secondlayer.tools";
31
+ function buildQuery(params) {
32
+ const search = new URLSearchParams;
33
+ for (const [name, value] of Object.entries(params)) {
34
+ if (value === undefined || value === null)
35
+ continue;
36
+ const serialized = Array.isArray(value) ? value.join(",") : String(value);
37
+ if (serialized.length === 0)
38
+ continue;
39
+ search.set(name, serialized);
40
+ }
41
+ const query = search.toString();
42
+ return query ? `?${query}` : "";
43
+ }
44
+
45
+ class BaseClient {
46
+ baseUrl;
47
+ apiKey;
48
+ origin;
49
+ constructor(options = {}) {
50
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
51
+ this.apiKey = options.apiKey;
52
+ this.origin = options.origin ?? "cli";
53
+ }
54
+ static authHeaders(apiKey) {
55
+ const headers = {
56
+ "Content-Type": "application/json"
57
+ };
58
+ if (apiKey) {
59
+ headers.Authorization = `Bearer ${apiKey}`;
60
+ }
61
+ return headers;
62
+ }
63
+ async request(method, path, body) {
64
+ const response = await this.fetchResponse(method, path, body);
65
+ if (response.status === 204) {
66
+ return;
67
+ }
68
+ return response.json();
69
+ }
70
+ async requestText(method, path, body) {
71
+ const response = await this.fetchResponse(method, path, body);
72
+ return response.text();
73
+ }
74
+ async fetchResponse(method, path, body) {
75
+ const url = `${this.baseUrl}${path}`;
76
+ const headers = BaseClient.authHeaders(this.apiKey);
77
+ headers["x-sl-origin"] = this.origin;
78
+ let serializedBody;
79
+ if (body !== undefined && body !== null) {
80
+ try {
81
+ serializedBody = JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value);
82
+ } catch (err) {
83
+ const detail = err instanceof Error ? err.message : String(err);
84
+ throw new ApiError(0, `Failed to serialize request body: ${detail}`);
85
+ }
86
+ }
87
+ let response;
88
+ try {
89
+ response = await fetch(url, {
90
+ method,
91
+ headers,
92
+ body: serializedBody
93
+ });
94
+ } catch {
95
+ throw new ApiError(0, `Cannot reach API at ${this.baseUrl}. Check your connection or try again.`);
96
+ }
97
+ if (!response.ok) {
98
+ if (response.status === 401) {
99
+ throw new ApiError(401, "API key invalid or expired.");
100
+ }
101
+ if (response.status === 429) {
102
+ const retryAfter = response.headers.get("Retry-After");
103
+ const msg = retryAfter ? `Rate limited. Wait ${retryAfter} seconds.` : "Rate limited. Try again later.";
104
+ throw new ApiError(429, msg);
105
+ }
106
+ if (response.status >= 500) {
107
+ throw new ApiError(response.status, `Server error. Try again or check status at ${this.baseUrl}/health`);
108
+ }
109
+ const errorBody = await response.text();
110
+ let message = `HTTP ${response.status}`;
111
+ let parsedBody = errorBody;
112
+ let code;
113
+ try {
114
+ const json = JSON.parse(errorBody);
115
+ parsedBody = json;
116
+ const err = json.error ?? json.message;
117
+ if (typeof err === "string") {
118
+ message = err;
119
+ } else if (err && typeof err === "object") {
120
+ message = JSON.stringify(err);
121
+ }
122
+ if (typeof json.code === "string") {
123
+ code = json.code;
124
+ }
125
+ } catch {
126
+ if (errorBody)
127
+ message = errorBody;
128
+ }
129
+ throw new ApiError(response.status, message, parsedBody, code);
130
+ }
131
+ return response;
132
+ }
133
+ }
134
+
135
+ // src/streams/errors.ts
136
+ class AuthError extends Error {
137
+ status = 401;
138
+ constructor(message = "API key invalid or expired.") {
139
+ super(message);
140
+ this.name = "AuthError";
141
+ }
142
+ }
143
+
144
+ class RateLimitError extends Error {
145
+ retryAfter;
146
+ status = 429;
147
+ constructor(message = "Rate limited. Try again later.", retryAfter) {
148
+ super(message);
149
+ this.retryAfter = retryAfter;
150
+ this.name = "RateLimitError";
151
+ }
152
+ }
153
+
154
+ class ValidationError extends Error {
155
+ status;
156
+ body;
157
+ constructor(message, status, body) {
158
+ super(message);
159
+ this.status = status;
160
+ this.body = body;
161
+ this.name = "ValidationError";
162
+ }
163
+ }
164
+
165
+ class StreamsServerError extends Error {
166
+ status;
167
+ body;
168
+ constructor(message, status, body) {
169
+ super(message);
170
+ this.status = status;
171
+ this.body = body;
172
+ this.name = "StreamsServerError";
173
+ }
174
+ }
175
+
176
+ class StreamsSignatureError extends Error {
177
+ constructor(message = "Streams response signature verification failed.") {
178
+ super(message);
179
+ this.name = "StreamsSignatureError";
180
+ }
181
+ }
182
+
183
+ // src/streams/cursor.ts
184
+ var Cursor = {
185
+ atHeight(height) {
186
+ return `${height}:0`;
187
+ },
188
+ parse(cursor) {
189
+ const parts = cursor.split(":");
190
+ const blockHeight = Number(parts[0]);
191
+ const eventIndex = Number(parts[1]);
192
+ if (parts.length !== 2 || !Number.isInteger(blockHeight) || !Number.isInteger(eventIndex)) {
193
+ throw new ValidationError(`Invalid stream cursor "${cursor}"; expected "<block>:<index>" (e.g. "951475:3").`, 400);
194
+ }
195
+ return { blockHeight, eventIndex };
196
+ }
197
+ };
198
+
4
199
  // src/streams/consumer.ts
200
+ function reorgKey(reorg) {
201
+ return `${reorg.detected_at}|${reorg.fork_point_height}|${reorg.new_canonical_tip}`;
202
+ }
5
203
  async function defaultSleep(ms, signal) {
6
204
  if (signal?.aborted)
7
205
  return;
@@ -18,10 +216,12 @@ async function defaultSleep(ms, signal) {
18
216
  async function consumeStreamsEvents(opts) {
19
217
  const sleep = opts.sleep ?? defaultSleep;
20
218
  const mode = opts.mode ?? "tail";
219
+ const finalizedOnly = opts.finalizedOnly ?? false;
21
220
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
22
221
  const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
23
222
  const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
24
223
  let cursor = opts.fromCursor ?? null;
224
+ const handledReorgs = new Set;
25
225
  let pages = 0;
26
226
  let emptyPolls = 0;
27
227
  while (pages < maxPages && emptyPolls < maxEmptyPolls && !opts.signal?.aborted) {
@@ -29,20 +229,39 @@ async function consumeStreamsEvents(opts) {
29
229
  cursor,
30
230
  limit: opts.batchSize,
31
231
  types: opts.types,
232
+ notTypes: opts.notTypes,
32
233
  contractId: opts.contractId,
33
234
  sender: opts.sender,
34
235
  recipient: opts.recipient,
35
236
  assetIdentifier: opts.assetIdentifier
36
237
  });
37
238
  pages++;
38
- const returnedCursor = await opts.onBatch(envelope.events, envelope);
39
- const nextCursor = returnedCursor ?? envelope.next_cursor;
239
+ if (!finalizedOnly && opts.onReorg) {
240
+ const fresh = envelope.reorgs.filter((reorg) => !handledReorgs.has(reorgKey(reorg))).sort((a, b) => a.fork_point_height - b.fork_point_height);
241
+ if (fresh.length > 0) {
242
+ const forkPoint = Math.min(...fresh.map((reorg) => reorg.fork_point_height));
243
+ const rewind = Cursor.atHeight(forkPoint);
244
+ for (const reorg of fresh) {
245
+ await opts.onReorg(reorg, { cursor: rewind });
246
+ handledReorgs.add(reorgKey(reorg));
247
+ }
248
+ cursor = rewind;
249
+ emptyPolls = 0;
250
+ continue;
251
+ }
252
+ }
253
+ const emitted = finalizedOnly ? envelope.events.filter((event) => event.finalized) : envelope.events;
254
+ const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
255
+ const returnedCursor = await opts.onBatch(emitted, envelope, {
256
+ cursor: checkpoint
257
+ });
258
+ const nextCursor = returnedCursor ?? checkpoint;
40
259
  if (nextCursor && nextCursor !== cursor) {
41
260
  cursor = nextCursor;
42
261
  emptyPolls = 0;
43
262
  continue;
44
263
  }
45
- if (envelope.events.length === 0) {
264
+ if (emitted.length === 0) {
46
265
  emptyPolls++;
47
266
  if (mode === "bounded") {
48
267
  return { cursor, pages, emptyPolls };
@@ -67,6 +286,7 @@ async function* streamStreamsEvents(opts) {
67
286
  cursor,
68
287
  limit: opts.batchSize,
69
288
  types: opts.types,
289
+ notTypes: opts.notTypes,
70
290
  contractId: opts.contractId,
71
291
  sender: opts.sender,
72
292
  recipient: opts.recipient,
@@ -97,56 +317,6 @@ async function* streamStreamsEvents(opts) {
97
317
 
98
318
  // src/streams/dumps.ts
99
319
  import { createHash } from "node:crypto";
100
-
101
- // src/streams/errors.ts
102
- class AuthError extends Error {
103
- status = 401;
104
- constructor(message = "API key invalid or expired.") {
105
- super(message);
106
- this.name = "AuthError";
107
- }
108
- }
109
-
110
- class RateLimitError extends Error {
111
- retryAfter;
112
- status = 429;
113
- constructor(message = "Rate limited. Try again later.", retryAfter) {
114
- super(message);
115
- this.retryAfter = retryAfter;
116
- this.name = "RateLimitError";
117
- }
118
- }
119
-
120
- class ValidationError extends Error {
121
- status;
122
- body;
123
- constructor(message, status, body) {
124
- super(message);
125
- this.status = status;
126
- this.body = body;
127
- this.name = "ValidationError";
128
- }
129
- }
130
-
131
- class StreamsServerError extends Error {
132
- status;
133
- body;
134
- constructor(message, status, body) {
135
- super(message);
136
- this.status = status;
137
- this.body = body;
138
- this.name = "StreamsServerError";
139
- }
140
- }
141
-
142
- class StreamsSignatureError extends Error {
143
- constructor(message = "Streams response signature verification failed.") {
144
- super(message);
145
- this.name = "StreamsSignatureError";
146
- }
147
- }
148
-
149
- // src/streams/dumps.ts
150
320
  function createStreamsDumps(opts) {
151
321
  const baseUrl = opts.baseUrl?.replace(/\/+$/, "");
152
322
  function requireBaseUrl() {
@@ -185,8 +355,12 @@ function createStreamsDumps(opts) {
185
355
  function cursorTuple(cursor) {
186
356
  if (!cursor)
187
357
  return [-1, -1];
188
- const [block, index] = cursor.split(":");
189
- return [Number(block), Number(index)];
358
+ const parts = cursor.split(":");
359
+ const [block, index] = parts.map(Number);
360
+ if (parts.length !== 2 || !Number.isInteger(block) || !Number.isInteger(index)) {
361
+ throw new ValidationError(`Invalid stream cursor "${cursor}"; expected "<block>:<index>" (e.g. "951475:3").`, 400);
362
+ }
363
+ return [block, index];
190
364
  }
191
365
  function maxCursor(a, b) {
192
366
  const [ah, ai] = cursorTuple(a);
@@ -197,11 +371,6 @@ var DEFAULT_STREAMS_BASE_URL = "https://api.secondlayer.tools";
197
371
  function normalizeBaseUrl(baseUrl) {
198
372
  return baseUrl.replace(/\/+$/, "");
199
373
  }
200
- function appendSearchParam(params, name, value) {
201
- if (value === undefined || value === null)
202
- return;
203
- params.set(name, String(value));
204
- }
205
374
  async function responseBody(response) {
206
375
  const text = await response.text();
207
376
  if (text.length === 0)
@@ -245,13 +414,16 @@ function createStreamsClient(options) {
245
414
  baseUrl: options.dumpsBaseUrl,
246
415
  fetchImpl
247
416
  });
248
- let publicKeyPromise = null;
249
- function getPublicKey() {
250
- if (publicKeyPromise)
251
- return publicKeyPromise;
252
- publicKeyPromise = (async () => {
417
+ let keyPromise = null;
418
+ function loadKey() {
419
+ if (keyPromise)
420
+ return keyPromise;
421
+ keyPromise = (async () => {
253
422
  if (typeof verify === "object") {
254
- return ed25519.loadEd25519PublicKey(verify.publicKey);
423
+ return {
424
+ keyId: ed25519.ed25519KeyId(verify.publicKey),
425
+ publicKey: ed25519.loadEd25519PublicKey(verify.publicKey)
426
+ };
255
427
  }
256
428
  const res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
257
429
  if (!res.ok) {
@@ -261,9 +433,12 @@ function createStreamsClient(options) {
261
433
  if (!body.public_key_pem) {
262
434
  throw new StreamsSignatureError("Signing key response missing key.");
263
435
  }
264
- return ed25519.loadEd25519PublicKey(body.public_key_pem);
436
+ return {
437
+ keyId: body.key_id ?? ed25519.ed25519KeyId(body.public_key_pem),
438
+ publicKey: ed25519.loadEd25519PublicKey(body.public_key_pem)
439
+ };
265
440
  })();
266
- return publicKeyPromise;
441
+ return keyPromise;
267
442
  }
268
443
  async function request(path) {
269
444
  const response = await fetchImpl(`${baseUrl}${path}`, {
@@ -277,8 +452,19 @@ function createStreamsClient(options) {
277
452
  if (!signature) {
278
453
  throw new StreamsSignatureError("Response is missing X-Signature.");
279
454
  }
280
- const publicKey = await getPublicKey();
281
- if (!ed25519.verifyEd25519(text, signature, publicKey)) {
455
+ const responseKeyId = response.headers.get("X-Signature-KeyId");
456
+ let key = await loadKey();
457
+ if (responseKeyId && responseKeyId !== key.keyId) {
458
+ if (typeof verify === "object") {
459
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
460
+ }
461
+ keyPromise = null;
462
+ key = await loadKey();
463
+ if (responseKeyId !== key.keyId) {
464
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
465
+ }
466
+ }
467
+ if (!ed25519.verifyEd25519(text, signature, key.publicKey)) {
282
468
  throw new StreamsSignatureError;
283
469
  }
284
470
  }
@@ -288,6 +474,7 @@ function createStreamsClient(options) {
288
474
  cursor,
289
475
  limit,
290
476
  types,
477
+ notTypes,
291
478
  contractId,
292
479
  sender,
293
480
  recipient,
@@ -297,6 +484,7 @@ function createStreamsClient(options) {
297
484
  cursor,
298
485
  limit,
299
486
  types,
487
+ notTypes,
300
488
  contractId,
301
489
  sender,
302
490
  recipient,
@@ -304,20 +492,18 @@ function createStreamsClient(options) {
304
492
  });
305
493
  };
306
494
  async function listEvents(params = {}) {
307
- const searchParams = new URLSearchParams;
308
- appendSearchParam(searchParams, "cursor", params.cursor);
309
- appendSearchParam(searchParams, "from_height", params.fromHeight);
310
- appendSearchParam(searchParams, "to_height", params.toHeight);
311
- appendSearchParam(searchParams, "limit", params.limit);
312
- appendSearchParam(searchParams, "contract_id", params.contractId);
313
- appendSearchParam(searchParams, "sender", params.sender);
314
- appendSearchParam(searchParams, "recipient", params.recipient);
315
- appendSearchParam(searchParams, "asset_identifier", params.assetIdentifier);
316
- if (params.types?.length) {
317
- searchParams.set("types", params.types.join(","));
318
- }
319
- const query = searchParams.toString();
320
- return request(`/v1/streams/events${query ? `?${query}` : ""}`);
495
+ return request(`/v1/streams/events${buildQuery({
496
+ cursor: params.cursor,
497
+ from_height: params.fromHeight,
498
+ to_height: params.toHeight,
499
+ limit: params.limit,
500
+ contract_id: params.contractId,
501
+ sender: params.sender,
502
+ recipient: params.recipient,
503
+ asset_identifier: params.assetIdentifier,
504
+ types: params.types,
505
+ not_types: params.notTypes
506
+ })}`);
321
507
  }
322
508
  return {
323
509
  events: {
@@ -329,7 +515,9 @@ function createStreamsClient(options) {
329
515
  return consumeStreamsEvents({
330
516
  fromCursor: params.fromCursor,
331
517
  mode: params.mode,
518
+ finalizedOnly: params.finalizedOnly,
332
519
  types: params.types,
520
+ notTypes: params.notTypes,
333
521
  contractId: params.contractId,
334
522
  sender: params.sender,
335
523
  recipient: params.recipient,
@@ -337,6 +525,7 @@ function createStreamsClient(options) {
337
525
  batchSize: params.batchSize ?? 100,
338
526
  fetchEvents,
339
527
  onBatch: params.onBatch,
528
+ onReorg: params.onReorg,
340
529
  emptyBackoffMs: params.emptyBackoffMs,
341
530
  maxPages: params.maxPages,
342
531
  maxEmptyPolls: params.maxEmptyPolls,
@@ -347,6 +536,7 @@ function createStreamsClient(options) {
347
536
  return streamStreamsEvents({
348
537
  fromCursor: params.fromCursor,
349
538
  types: params.types,
539
+ notTypes: params.notTypes,
350
540
  contractId: params.contractId,
351
541
  sender: params.sender,
352
542
  recipient: params.recipient,
@@ -390,11 +580,10 @@ function createStreamsClient(options) {
390
580
  },
391
581
  reorgs: {
392
582
  list(params) {
393
- const searchParams = new URLSearchParams;
394
- appendSearchParam(searchParams, "since", params.since);
395
- appendSearchParam(searchParams, "limit", params.limit);
396
- const query = searchParams.toString();
397
- return request(`/v1/streams/reorgs${query ? `?${query}` : ""}`);
583
+ return request(`/v1/streams/reorgs${buildQuery({
584
+ since: params.since,
585
+ limit: params.limit
586
+ })}`);
398
587
  }
399
588
  },
400
589
  dumps,
@@ -406,127 +595,8 @@ function createStreamsClient(options) {
406
595
  }
407
596
  };
408
597
  }
409
- // src/streams/ft-transfer.ts
410
- function requireString(payload, field) {
411
- const value = payload[field];
412
- if (typeof value !== "string" || value.length === 0) {
413
- throw new Error(`ft_transfer payload missing ${field}`);
414
- }
415
- return value;
416
- }
417
- function parseAssetIdentifier(assetIdentifier) {
418
- const [contractId, tokenName] = assetIdentifier.split("::");
419
- if (!contractId) {
420
- throw new Error("ft_transfer payload has malformed asset_identifier");
421
- }
422
- return {
423
- contract_id: contractId,
424
- token_name: tokenName && tokenName.length > 0 ? tokenName : null
425
- };
426
- }
427
- function isFtTransfer(event) {
428
- return event.event_type === "ft_transfer";
429
- }
430
- function decodeFtTransfer(event) {
431
- if (!isFtTransfer(event)) {
432
- throw new Error(`Expected ft_transfer event, got ${event.event_type}`);
433
- }
434
- const payload = event.payload;
435
- const assetIdentifier = requireString(payload, "asset_identifier");
436
- const sender = requireString(payload, "sender");
437
- const recipient = requireString(payload, "recipient");
438
- const amount = requireString(payload, "amount");
439
- if (!/^(0|[1-9]\d*)$/.test(amount)) {
440
- throw new Error("ft_transfer payload has malformed amount");
441
- }
442
- const { contract_id, token_name } = parseAssetIdentifier(assetIdentifier);
443
- return {
444
- cursor: event.cursor,
445
- block_height: event.block_height,
446
- tx_id: event.tx_id,
447
- tx_index: event.tx_index,
448
- event_index: event.event_index,
449
- event_type: event.event_type,
450
- decoded_payload: {
451
- asset_identifier: assetIdentifier,
452
- contract_id: event.contract_id ?? contract_id,
453
- token_name,
454
- sender,
455
- recipient,
456
- amount
457
- },
458
- source_cursor: event.cursor
459
- };
460
- }
461
- // src/streams/nft-transfer.ts
462
- function requireString2(payload, field) {
463
- const value = payload[field];
464
- if (typeof value !== "string" || value.length === 0) {
465
- throw new Error(`nft_transfer payload missing ${field}`);
466
- }
467
- return value;
468
- }
469
- function requireHexValue(payload) {
470
- const rawValue = payload.raw_value;
471
- if (typeof rawValue === "string") {
472
- if (!/^0x[0-9a-fA-F]*$/.test(rawValue)) {
473
- throw new Error("nft_transfer payload has malformed value");
474
- }
475
- return rawValue;
476
- }
477
- const value = payload.value;
478
- const hex = typeof value === "string" ? value : value && typeof value === "object" && typeof value.hex === "string" ? value.hex : null;
479
- if (!hex) {
480
- throw new Error("nft_transfer payload missing value");
481
- }
482
- if (!/^0x[0-9a-fA-F]*$/.test(hex)) {
483
- throw new Error("nft_transfer payload has malformed value");
484
- }
485
- return hex;
486
- }
487
- function parseAssetIdentifier2(assetIdentifier) {
488
- const [contractId, tokenName] = assetIdentifier.split("::");
489
- if (!contractId) {
490
- throw new Error("nft_transfer payload has malformed asset_identifier");
491
- }
492
- return {
493
- contract_id: contractId,
494
- token_name: tokenName && tokenName.length > 0 ? tokenName : null
495
- };
496
- }
497
- function isNftTransfer(event) {
498
- return event.event_type === "nft_transfer";
499
- }
500
- function decodeNftTransfer(event) {
501
- if (!isNftTransfer(event)) {
502
- throw new Error(`Expected nft_transfer event, got ${event.event_type}`);
503
- }
504
- const payload = event.payload;
505
- const assetIdentifier = requireString2(payload, "asset_identifier");
506
- const sender = requireString2(payload, "sender");
507
- const recipient = requireString2(payload, "recipient");
508
- const value = requireHexValue(payload);
509
- const { contract_id, token_name } = parseAssetIdentifier2(assetIdentifier);
510
- return {
511
- cursor: event.cursor,
512
- block_height: event.block_height,
513
- tx_id: event.tx_id,
514
- tx_index: event.tx_index,
515
- event_index: event.event_index,
516
- event_type: event.event_type,
517
- decoded_payload: {
518
- asset_identifier: assetIdentifier,
519
- contract_id: event.contract_id ?? contract_id,
520
- token_name,
521
- sender,
522
- recipient,
523
- value
524
- },
525
- source_cursor: event.cursor
526
- };
527
- }
528
598
  // src/streams/_payload.ts
529
- function requireString3(payload, field, eventType) {
599
+ function requireString(payload, field, eventType) {
530
600
  const value = payload[field];
531
601
  if (typeof value !== "string" || value.length === 0) {
532
602
  throw new Error(`${eventType} payload missing ${field}`);
@@ -537,7 +607,7 @@ function optionalString(value) {
537
607
  return typeof value === "string" && value.length > 0 ? value : null;
538
608
  }
539
609
  function requireAmountField(payload, field, eventType) {
540
- const amount = requireString3(payload, field, eventType);
610
+ const amount = requireString(payload, field, eventType);
541
611
  if (!/^(0|[1-9]\d*)$/.test(amount)) {
542
612
  throw new Error(`${eventType} payload has malformed ${field}`);
543
613
  }
@@ -546,7 +616,7 @@ function requireAmountField(payload, field, eventType) {
546
616
  function requireAmount(payload, eventType) {
547
617
  return requireAmountField(payload, "amount", eventType);
548
618
  }
549
- function parseAssetIdentifier3(assetIdentifier, eventType) {
619
+ function parseAssetIdentifier(assetIdentifier, eventType) {
550
620
  const [contractId, tokenName] = assetIdentifier.split("::");
551
621
  if (!contractId) {
552
622
  throw new Error(`${eventType} payload has malformed asset_identifier`);
@@ -556,7 +626,7 @@ function parseAssetIdentifier3(assetIdentifier, eventType) {
556
626
  token_name: tokenName && tokenName.length > 0 ? tokenName : null
557
627
  };
558
628
  }
559
- function requireHexValue2(payload, eventType) {
629
+ function requireHexValue(payload, eventType) {
560
630
  const rawValue = payload.raw_value;
561
631
  if (typeof rawValue === "string") {
562
632
  if (!/^0x[0-9a-fA-F]*$/.test(rawValue)) {
@@ -587,6 +657,52 @@ function decodedRow(event, eventType, decoded_payload) {
587
657
  };
588
658
  }
589
659
 
660
+ // src/streams/ft-transfer.ts
661
+ function isFtTransfer(event) {
662
+ return event.event_type === "ft_transfer";
663
+ }
664
+ function decodeFtTransfer(event) {
665
+ if (!isFtTransfer(event)) {
666
+ throw new Error(`Expected ft_transfer event, got ${event.event_type}`);
667
+ }
668
+ const payload = event.payload;
669
+ const assetIdentifier = requireString(payload, "asset_identifier", "ft_transfer");
670
+ const sender = requireString(payload, "sender", "ft_transfer");
671
+ const recipient = requireString(payload, "recipient", "ft_transfer");
672
+ const amount = requireAmount(payload, "ft_transfer");
673
+ const { contract_id, token_name } = parseAssetIdentifier(assetIdentifier, "ft_transfer");
674
+ return decodedRow(event, "ft_transfer", {
675
+ asset_identifier: assetIdentifier,
676
+ contract_id: event.contract_id ?? contract_id,
677
+ token_name,
678
+ sender,
679
+ recipient,
680
+ amount
681
+ });
682
+ }
683
+ // src/streams/nft-transfer.ts
684
+ function isNftTransfer(event) {
685
+ return event.event_type === "nft_transfer";
686
+ }
687
+ function decodeNftTransfer(event) {
688
+ if (!isNftTransfer(event)) {
689
+ throw new Error(`Expected nft_transfer event, got ${event.event_type}`);
690
+ }
691
+ const payload = event.payload;
692
+ const assetIdentifier = requireString(payload, "asset_identifier", "nft_transfer");
693
+ const sender = requireString(payload, "sender", "nft_transfer");
694
+ const recipient = requireString(payload, "recipient", "nft_transfer");
695
+ const value = requireHexValue(payload, "nft_transfer");
696
+ const { contract_id, token_name } = parseAssetIdentifier(assetIdentifier, "nft_transfer");
697
+ return decodedRow(event, "nft_transfer", {
698
+ asset_identifier: assetIdentifier,
699
+ contract_id: event.contract_id ?? contract_id,
700
+ token_name,
701
+ sender,
702
+ recipient,
703
+ value
704
+ });
705
+ }
590
706
  // src/streams/stx-events.ts
591
707
  function isStxTransfer(event) {
592
708
  return event.event_type === "stx_transfer";
@@ -597,8 +713,8 @@ function decodeStxTransfer(event) {
597
713
  }
598
714
  const payload = event.payload;
599
715
  return decodedRow(event, "stx_transfer", {
600
- sender: requireString3(payload, "sender", "stx_transfer"),
601
- recipient: requireString3(payload, "recipient", "stx_transfer"),
716
+ sender: requireString(payload, "sender", "stx_transfer"),
717
+ recipient: requireString(payload, "recipient", "stx_transfer"),
602
718
  amount: requireAmount(payload, "stx_transfer"),
603
719
  memo: optionalString(payload.memo)
604
720
  });
@@ -612,7 +728,7 @@ function decodeStxMint(event) {
612
728
  }
613
729
  const payload = event.payload;
614
730
  return decodedRow(event, "stx_mint", {
615
- recipient: requireString3(payload, "recipient", "stx_mint"),
731
+ recipient: requireString(payload, "recipient", "stx_mint"),
616
732
  amount: requireAmount(payload, "stx_mint")
617
733
  });
618
734
  }
@@ -625,7 +741,7 @@ function decodeStxBurn(event) {
625
741
  }
626
742
  const payload = event.payload;
627
743
  return decodedRow(event, "stx_burn", {
628
- sender: requireString3(payload, "sender", "stx_burn"),
744
+ sender: requireString(payload, "sender", "stx_burn"),
629
745
  amount: requireAmount(payload, "stx_burn")
630
746
  });
631
747
  }
@@ -638,15 +754,15 @@ function decodeStxLock(event) {
638
754
  }
639
755
  const payload = event.payload;
640
756
  return decodedRow(event, "stx_lock", {
641
- sender: requireString3(payload, "locked_address", "stx_lock"),
757
+ sender: requireString(payload, "locked_address", "stx_lock"),
642
758
  amount: requireAmountField(payload, "locked_amount", "stx_lock"),
643
759
  payload: { unlock_height: optionalString(payload.unlock_height) }
644
760
  });
645
761
  }
646
762
  // src/streams/token-mint-burn.ts
647
763
  function assetFields(event, eventType) {
648
- const assetIdentifier = requireString3(event.payload, "asset_identifier", eventType);
649
- const { contract_id, token_name } = parseAssetIdentifier3(assetIdentifier, eventType);
764
+ const assetIdentifier = requireString(event.payload, "asset_identifier", eventType);
765
+ const { contract_id, token_name } = parseAssetIdentifier(assetIdentifier, eventType);
650
766
  return {
651
767
  asset_identifier: assetIdentifier,
652
768
  contract_id: event.contract_id ?? contract_id,
@@ -662,7 +778,7 @@ function decodeFtMint(event) {
662
778
  }
663
779
  return decodedRow(event, "ft_mint", {
664
780
  ...assetFields(event, "ft_mint"),
665
- recipient: requireString3(event.payload, "recipient", "ft_mint"),
781
+ recipient: requireString(event.payload, "recipient", "ft_mint"),
666
782
  amount: requireAmount(event.payload, "ft_mint")
667
783
  });
668
784
  }
@@ -675,7 +791,7 @@ function decodeFtBurn(event) {
675
791
  }
676
792
  return decodedRow(event, "ft_burn", {
677
793
  ...assetFields(event, "ft_burn"),
678
- sender: requireString3(event.payload, "sender", "ft_burn"),
794
+ sender: requireString(event.payload, "sender", "ft_burn"),
679
795
  amount: requireAmount(event.payload, "ft_burn")
680
796
  });
681
797
  }
@@ -688,8 +804,8 @@ function decodeNftMint(event) {
688
804
  }
689
805
  return decodedRow(event, "nft_mint", {
690
806
  ...assetFields(event, "nft_mint"),
691
- recipient: requireString3(event.payload, "recipient", "nft_mint"),
692
- value: requireHexValue2(event.payload, "nft_mint")
807
+ recipient: requireString(event.payload, "recipient", "nft_mint"),
808
+ value: requireHexValue(event.payload, "nft_mint")
693
809
  });
694
810
  }
695
811
  function isNftBurn(event) {
@@ -701,8 +817,8 @@ function decodeNftBurn(event) {
701
817
  }
702
818
  return decodedRow(event, "nft_burn", {
703
819
  ...assetFields(event, "nft_burn"),
704
- sender: requireString3(event.payload, "sender", "nft_burn"),
705
- value: requireHexValue2(event.payload, "nft_burn")
820
+ sender: requireString(event.payload, "sender", "nft_burn"),
821
+ value: requireHexValue(event.payload, "nft_burn")
706
822
  });
707
823
  }
708
824
  // src/clarity.ts
@@ -801,8 +917,9 @@ export {
801
917
  StreamsServerError,
802
918
  STREAMS_EVENT_TYPES,
803
919
  RateLimitError,
920
+ Cursor,
804
921
  AuthError
805
922
  };
806
923
 
807
- //# debugId=AC24B4EAD103932F64756E2164756E21
924
+ //# debugId=CA2A679BA194AF3B64756E2164756E21
808
925
  //# sourceMappingURL=index.js.map