@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.
@@ -25,6 +25,19 @@ class VersionConflictError extends ApiError {
25
25
 
26
26
  // src/base.ts
27
27
  var DEFAULT_BASE_URL = "https://api.secondlayer.tools";
28
+ function buildQuery(params) {
29
+ const search = new URLSearchParams;
30
+ for (const [name, value] of Object.entries(params)) {
31
+ if (value === undefined || value === null)
32
+ continue;
33
+ const serialized = Array.isArray(value) ? value.join(",") : String(value);
34
+ if (serialized.length === 0)
35
+ continue;
36
+ search.set(name, serialized);
37
+ }
38
+ const query = search.toString();
39
+ return query ? `?${query}` : "";
40
+ }
28
41
 
29
42
  class BaseClient {
30
43
  baseUrl;
@@ -312,11 +325,6 @@ class Subgraphs extends BaseClient {
312
325
  }
313
326
  }
314
327
  // src/index-api/client.ts
315
- function appendSearchParam(params, name, value) {
316
- if (value === undefined || value === null)
317
- return;
318
- params.set(name, String(value));
319
- }
320
328
  function firstWalkFromHeight(params) {
321
329
  if (params.fromHeight !== undefined)
322
330
  return params.fromHeight;
@@ -346,31 +354,29 @@ class Index extends BaseClient {
346
354
  walk: (params = {}) => this.walkContractCalls(params)
347
355
  };
348
356
  async listFtTransfers(params = {}) {
349
- const searchParams = new URLSearchParams;
350
- appendSearchParam(searchParams, "cursor", params.cursor);
351
- appendSearchParam(searchParams, "from_cursor", params.fromCursor);
352
- appendSearchParam(searchParams, "limit", params.limit);
353
- appendSearchParam(searchParams, "contract_id", params.contractId);
354
- appendSearchParam(searchParams, "sender", params.sender);
355
- appendSearchParam(searchParams, "recipient", params.recipient);
356
- appendSearchParam(searchParams, "from_height", params.fromHeight);
357
- appendSearchParam(searchParams, "to_height", params.toHeight);
358
- const query = searchParams.toString();
359
- return this.request("GET", `/v1/index/ft-transfers${query ? `?${query}` : ""}`);
357
+ return this.request("GET", `/v1/index/ft-transfers${buildQuery({
358
+ cursor: params.cursor,
359
+ from_cursor: params.fromCursor,
360
+ limit: params.limit,
361
+ contract_id: params.contractId,
362
+ sender: params.sender,
363
+ recipient: params.recipient,
364
+ from_height: params.fromHeight,
365
+ to_height: params.toHeight
366
+ })}`);
360
367
  }
361
368
  async listNftTransfers(params = {}) {
362
- const searchParams = new URLSearchParams;
363
- appendSearchParam(searchParams, "cursor", params.cursor);
364
- appendSearchParam(searchParams, "from_cursor", params.fromCursor);
365
- appendSearchParam(searchParams, "limit", params.limit);
366
- appendSearchParam(searchParams, "contract_id", params.contractId);
367
- appendSearchParam(searchParams, "asset_identifier", params.assetIdentifier);
368
- appendSearchParam(searchParams, "sender", params.sender);
369
- appendSearchParam(searchParams, "recipient", params.recipient);
370
- appendSearchParam(searchParams, "from_height", params.fromHeight);
371
- appendSearchParam(searchParams, "to_height", params.toHeight);
372
- const query = searchParams.toString();
373
- return this.request("GET", `/v1/index/nft-transfers${query ? `?${query}` : ""}`);
369
+ return this.request("GET", `/v1/index/nft-transfers${buildQuery({
370
+ cursor: params.cursor,
371
+ from_cursor: params.fromCursor,
372
+ limit: params.limit,
373
+ contract_id: params.contractId,
374
+ asset_identifier: params.assetIdentifier,
375
+ sender: params.sender,
376
+ recipient: params.recipient,
377
+ from_height: params.fromHeight,
378
+ to_height: params.toHeight
379
+ })}`);
374
380
  }
375
381
  async* walkFtTransfers(params = {}) {
376
382
  const batchSize = params.batchSize ?? 200;
@@ -423,18 +429,18 @@ class Index extends BaseClient {
423
429
  }
424
430
  }
425
431
  async listEvents(params) {
426
- const searchParams = new URLSearchParams;
427
- appendSearchParam(searchParams, "event_type", params.eventType);
428
- appendSearchParam(searchParams, "cursor", params.cursor);
429
- appendSearchParam(searchParams, "from_cursor", params.fromCursor);
430
- appendSearchParam(searchParams, "limit", params.limit);
431
- appendSearchParam(searchParams, "contract_id", params.contractId);
432
- appendSearchParam(searchParams, "asset_identifier", params.assetIdentifier);
433
- appendSearchParam(searchParams, "sender", params.sender);
434
- appendSearchParam(searchParams, "recipient", params.recipient);
435
- appendSearchParam(searchParams, "from_height", params.fromHeight);
436
- appendSearchParam(searchParams, "to_height", params.toHeight);
437
- return this.request("GET", `/v1/index/events?${searchParams.toString()}`);
432
+ return this.request("GET", `/v1/index/events${buildQuery({
433
+ event_type: params.eventType,
434
+ cursor: params.cursor,
435
+ from_cursor: params.fromCursor,
436
+ limit: params.limit,
437
+ contract_id: params.contractId,
438
+ asset_identifier: params.assetIdentifier,
439
+ sender: params.sender,
440
+ recipient: params.recipient,
441
+ from_height: params.fromHeight,
442
+ to_height: params.toHeight
443
+ })}`);
438
444
  }
439
445
  async* walkEvents(params) {
440
446
  const batchSize = params.batchSize ?? 200;
@@ -462,17 +468,16 @@ class Index extends BaseClient {
462
468
  }
463
469
  }
464
470
  async listContractCalls(params = {}) {
465
- const searchParams = new URLSearchParams;
466
- appendSearchParam(searchParams, "cursor", params.cursor);
467
- appendSearchParam(searchParams, "from_cursor", params.fromCursor);
468
- appendSearchParam(searchParams, "limit", params.limit);
469
- appendSearchParam(searchParams, "contract_id", params.contractId);
470
- appendSearchParam(searchParams, "function_name", params.functionName);
471
- appendSearchParam(searchParams, "sender", params.sender);
472
- appendSearchParam(searchParams, "from_height", params.fromHeight);
473
- appendSearchParam(searchParams, "to_height", params.toHeight);
474
- const query = searchParams.toString();
475
- return this.request("GET", `/v1/index/contract-calls${query ? `?${query}` : ""}`);
471
+ return this.request("GET", `/v1/index/contract-calls${buildQuery({
472
+ cursor: params.cursor,
473
+ from_cursor: params.fromCursor,
474
+ limit: params.limit,
475
+ contract_id: params.contractId,
476
+ function_name: params.functionName,
477
+ sender: params.sender,
478
+ from_height: params.fromHeight,
479
+ to_height: params.toHeight
480
+ })}`);
476
481
  }
477
482
  async* walkContractCalls(params = {}) {
478
483
  const batchSize = params.batchSize ?? 200;
@@ -504,7 +509,74 @@ class Index extends BaseClient {
504
509
  // src/streams/client.ts
505
510
  import { ed25519 } from "@secondlayer/shared";
506
511
 
512
+ // src/streams/errors.ts
513
+ class AuthError extends Error {
514
+ status = 401;
515
+ constructor(message = "API key invalid or expired.") {
516
+ super(message);
517
+ this.name = "AuthError";
518
+ }
519
+ }
520
+
521
+ class RateLimitError extends Error {
522
+ retryAfter;
523
+ status = 429;
524
+ constructor(message = "Rate limited. Try again later.", retryAfter) {
525
+ super(message);
526
+ this.retryAfter = retryAfter;
527
+ this.name = "RateLimitError";
528
+ }
529
+ }
530
+
531
+ class ValidationError extends Error {
532
+ status;
533
+ body;
534
+ constructor(message, status, body) {
535
+ super(message);
536
+ this.status = status;
537
+ this.body = body;
538
+ this.name = "ValidationError";
539
+ }
540
+ }
541
+
542
+ class StreamsServerError extends Error {
543
+ status;
544
+ body;
545
+ constructor(message, status, body) {
546
+ super(message);
547
+ this.status = status;
548
+ this.body = body;
549
+ this.name = "StreamsServerError";
550
+ }
551
+ }
552
+
553
+ class StreamsSignatureError extends Error {
554
+ constructor(message = "Streams response signature verification failed.") {
555
+ super(message);
556
+ this.name = "StreamsSignatureError";
557
+ }
558
+ }
559
+
560
+ // src/streams/cursor.ts
561
+ var Cursor = {
562
+ atHeight(height) {
563
+ return `${height}:0`;
564
+ },
565
+ parse(cursor) {
566
+ const parts = cursor.split(":");
567
+ const blockHeight = Number(parts[0]);
568
+ const eventIndex = Number(parts[1]);
569
+ if (parts.length !== 2 || !Number.isInteger(blockHeight) || !Number.isInteger(eventIndex)) {
570
+ throw new ValidationError(`Invalid stream cursor "${cursor}"; expected "<block>:<index>" (e.g. "951475:3").`, 400);
571
+ }
572
+ return { blockHeight, eventIndex };
573
+ }
574
+ };
575
+
507
576
  // src/streams/consumer.ts
577
+ function reorgKey(reorg) {
578
+ return `${reorg.detected_at}|${reorg.fork_point_height}|${reorg.new_canonical_tip}`;
579
+ }
508
580
  async function defaultSleep(ms, signal) {
509
581
  if (signal?.aborted)
510
582
  return;
@@ -521,10 +593,12 @@ async function defaultSleep(ms, signal) {
521
593
  async function consumeStreamsEvents(opts) {
522
594
  const sleep = opts.sleep ?? defaultSleep;
523
595
  const mode = opts.mode ?? "tail";
596
+ const finalizedOnly = opts.finalizedOnly ?? false;
524
597
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
525
598
  const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
526
599
  const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
527
600
  let cursor = opts.fromCursor ?? null;
601
+ const handledReorgs = new Set;
528
602
  let pages = 0;
529
603
  let emptyPolls = 0;
530
604
  while (pages < maxPages && emptyPolls < maxEmptyPolls && !opts.signal?.aborted) {
@@ -532,20 +606,39 @@ async function consumeStreamsEvents(opts) {
532
606
  cursor,
533
607
  limit: opts.batchSize,
534
608
  types: opts.types,
609
+ notTypes: opts.notTypes,
535
610
  contractId: opts.contractId,
536
611
  sender: opts.sender,
537
612
  recipient: opts.recipient,
538
613
  assetIdentifier: opts.assetIdentifier
539
614
  });
540
615
  pages++;
541
- const returnedCursor = await opts.onBatch(envelope.events, envelope);
542
- const nextCursor = returnedCursor ?? envelope.next_cursor;
616
+ if (!finalizedOnly && opts.onReorg) {
617
+ const fresh = envelope.reorgs.filter((reorg) => !handledReorgs.has(reorgKey(reorg))).sort((a, b) => a.fork_point_height - b.fork_point_height);
618
+ if (fresh.length > 0) {
619
+ const forkPoint = Math.min(...fresh.map((reorg) => reorg.fork_point_height));
620
+ const rewind = Cursor.atHeight(forkPoint);
621
+ for (const reorg of fresh) {
622
+ await opts.onReorg(reorg, { cursor: rewind });
623
+ handledReorgs.add(reorgKey(reorg));
624
+ }
625
+ cursor = rewind;
626
+ emptyPolls = 0;
627
+ continue;
628
+ }
629
+ }
630
+ const emitted = finalizedOnly ? envelope.events.filter((event) => event.finalized) : envelope.events;
631
+ const checkpoint = finalizedOnly ? emitted.at(-1)?.cursor ?? cursor : envelope.next_cursor;
632
+ const returnedCursor = await opts.onBatch(emitted, envelope, {
633
+ cursor: checkpoint
634
+ });
635
+ const nextCursor = returnedCursor ?? checkpoint;
543
636
  if (nextCursor && nextCursor !== cursor) {
544
637
  cursor = nextCursor;
545
638
  emptyPolls = 0;
546
639
  continue;
547
640
  }
548
- if (envelope.events.length === 0) {
641
+ if (emitted.length === 0) {
549
642
  emptyPolls++;
550
643
  if (mode === "bounded") {
551
644
  return { cursor, pages, emptyPolls };
@@ -570,6 +663,7 @@ async function* streamStreamsEvents(opts) {
570
663
  cursor,
571
664
  limit: opts.batchSize,
572
665
  types: opts.types,
666
+ notTypes: opts.notTypes,
573
667
  contractId: opts.contractId,
574
668
  sender: opts.sender,
575
669
  recipient: opts.recipient,
@@ -600,56 +694,6 @@ async function* streamStreamsEvents(opts) {
600
694
 
601
695
  // src/streams/dumps.ts
602
696
  import { createHash } from "node:crypto";
603
-
604
- // src/streams/errors.ts
605
- class AuthError extends Error {
606
- status = 401;
607
- constructor(message = "API key invalid or expired.") {
608
- super(message);
609
- this.name = "AuthError";
610
- }
611
- }
612
-
613
- class RateLimitError extends Error {
614
- retryAfter;
615
- status = 429;
616
- constructor(message = "Rate limited. Try again later.", retryAfter) {
617
- super(message);
618
- this.retryAfter = retryAfter;
619
- this.name = "RateLimitError";
620
- }
621
- }
622
-
623
- class ValidationError extends Error {
624
- status;
625
- body;
626
- constructor(message, status, body) {
627
- super(message);
628
- this.status = status;
629
- this.body = body;
630
- this.name = "ValidationError";
631
- }
632
- }
633
-
634
- class StreamsServerError extends Error {
635
- status;
636
- body;
637
- constructor(message, status, body) {
638
- super(message);
639
- this.status = status;
640
- this.body = body;
641
- this.name = "StreamsServerError";
642
- }
643
- }
644
-
645
- class StreamsSignatureError extends Error {
646
- constructor(message = "Streams response signature verification failed.") {
647
- super(message);
648
- this.name = "StreamsSignatureError";
649
- }
650
- }
651
-
652
- // src/streams/dumps.ts
653
697
  function createStreamsDumps(opts) {
654
698
  const baseUrl = opts.baseUrl?.replace(/\/+$/, "");
655
699
  function requireBaseUrl() {
@@ -688,8 +732,12 @@ function createStreamsDumps(opts) {
688
732
  function cursorTuple(cursor) {
689
733
  if (!cursor)
690
734
  return [-1, -1];
691
- const [block, index] = cursor.split(":");
692
- return [Number(block), Number(index)];
735
+ const parts = cursor.split(":");
736
+ const [block, index] = parts.map(Number);
737
+ if (parts.length !== 2 || !Number.isInteger(block) || !Number.isInteger(index)) {
738
+ throw new ValidationError(`Invalid stream cursor "${cursor}"; expected "<block>:<index>" (e.g. "951475:3").`, 400);
739
+ }
740
+ return [block, index];
693
741
  }
694
742
  function maxCursor(a, b) {
695
743
  const [ah, ai] = cursorTuple(a);
@@ -700,11 +748,6 @@ var DEFAULT_STREAMS_BASE_URL = "https://api.secondlayer.tools";
700
748
  function normalizeBaseUrl(baseUrl) {
701
749
  return baseUrl.replace(/\/+$/, "");
702
750
  }
703
- function appendSearchParam2(params, name, value) {
704
- if (value === undefined || value === null)
705
- return;
706
- params.set(name, String(value));
707
- }
708
751
  async function responseBody(response) {
709
752
  const text = await response.text();
710
753
  if (text.length === 0)
@@ -748,13 +791,16 @@ function createStreamsClient(options) {
748
791
  baseUrl: options.dumpsBaseUrl,
749
792
  fetchImpl
750
793
  });
751
- let publicKeyPromise = null;
752
- function getPublicKey() {
753
- if (publicKeyPromise)
754
- return publicKeyPromise;
755
- publicKeyPromise = (async () => {
794
+ let keyPromise = null;
795
+ function loadKey() {
796
+ if (keyPromise)
797
+ return keyPromise;
798
+ keyPromise = (async () => {
756
799
  if (typeof verify === "object") {
757
- return ed25519.loadEd25519PublicKey(verify.publicKey);
800
+ return {
801
+ keyId: ed25519.ed25519KeyId(verify.publicKey),
802
+ publicKey: ed25519.loadEd25519PublicKey(verify.publicKey)
803
+ };
758
804
  }
759
805
  const res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
760
806
  if (!res.ok) {
@@ -764,9 +810,12 @@ function createStreamsClient(options) {
764
810
  if (!body.public_key_pem) {
765
811
  throw new StreamsSignatureError("Signing key response missing key.");
766
812
  }
767
- return ed25519.loadEd25519PublicKey(body.public_key_pem);
813
+ return {
814
+ keyId: body.key_id ?? ed25519.ed25519KeyId(body.public_key_pem),
815
+ publicKey: ed25519.loadEd25519PublicKey(body.public_key_pem)
816
+ };
768
817
  })();
769
- return publicKeyPromise;
818
+ return keyPromise;
770
819
  }
771
820
  async function request(path) {
772
821
  const response = await fetchImpl(`${baseUrl}${path}`, {
@@ -780,8 +829,19 @@ function createStreamsClient(options) {
780
829
  if (!signature) {
781
830
  throw new StreamsSignatureError("Response is missing X-Signature.");
782
831
  }
783
- const publicKey = await getPublicKey();
784
- if (!ed25519.verifyEd25519(text, signature, publicKey)) {
832
+ const responseKeyId = response.headers.get("X-Signature-KeyId");
833
+ let key = await loadKey();
834
+ if (responseKeyId && responseKeyId !== key.keyId) {
835
+ if (typeof verify === "object") {
836
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
837
+ }
838
+ keyPromise = null;
839
+ key = await loadKey();
840
+ if (responseKeyId !== key.keyId) {
841
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
842
+ }
843
+ }
844
+ if (!ed25519.verifyEd25519(text, signature, key.publicKey)) {
785
845
  throw new StreamsSignatureError;
786
846
  }
787
847
  }
@@ -791,6 +851,7 @@ function createStreamsClient(options) {
791
851
  cursor,
792
852
  limit,
793
853
  types,
854
+ notTypes,
794
855
  contractId,
795
856
  sender,
796
857
  recipient,
@@ -800,6 +861,7 @@ function createStreamsClient(options) {
800
861
  cursor,
801
862
  limit,
802
863
  types,
864
+ notTypes,
803
865
  contractId,
804
866
  sender,
805
867
  recipient,
@@ -807,20 +869,18 @@ function createStreamsClient(options) {
807
869
  });
808
870
  };
809
871
  async function listEvents(params = {}) {
810
- const searchParams = new URLSearchParams;
811
- appendSearchParam2(searchParams, "cursor", params.cursor);
812
- appendSearchParam2(searchParams, "from_height", params.fromHeight);
813
- appendSearchParam2(searchParams, "to_height", params.toHeight);
814
- appendSearchParam2(searchParams, "limit", params.limit);
815
- appendSearchParam2(searchParams, "contract_id", params.contractId);
816
- appendSearchParam2(searchParams, "sender", params.sender);
817
- appendSearchParam2(searchParams, "recipient", params.recipient);
818
- appendSearchParam2(searchParams, "asset_identifier", params.assetIdentifier);
819
- if (params.types?.length) {
820
- searchParams.set("types", params.types.join(","));
821
- }
822
- const query = searchParams.toString();
823
- return request(`/v1/streams/events${query ? `?${query}` : ""}`);
872
+ return request(`/v1/streams/events${buildQuery({
873
+ cursor: params.cursor,
874
+ from_height: params.fromHeight,
875
+ to_height: params.toHeight,
876
+ limit: params.limit,
877
+ contract_id: params.contractId,
878
+ sender: params.sender,
879
+ recipient: params.recipient,
880
+ asset_identifier: params.assetIdentifier,
881
+ types: params.types,
882
+ not_types: params.notTypes
883
+ })}`);
824
884
  }
825
885
  return {
826
886
  events: {
@@ -832,7 +892,9 @@ function createStreamsClient(options) {
832
892
  return consumeStreamsEvents({
833
893
  fromCursor: params.fromCursor,
834
894
  mode: params.mode,
895
+ finalizedOnly: params.finalizedOnly,
835
896
  types: params.types,
897
+ notTypes: params.notTypes,
836
898
  contractId: params.contractId,
837
899
  sender: params.sender,
838
900
  recipient: params.recipient,
@@ -840,6 +902,7 @@ function createStreamsClient(options) {
840
902
  batchSize: params.batchSize ?? 100,
841
903
  fetchEvents,
842
904
  onBatch: params.onBatch,
905
+ onReorg: params.onReorg,
843
906
  emptyBackoffMs: params.emptyBackoffMs,
844
907
  maxPages: params.maxPages,
845
908
  maxEmptyPolls: params.maxEmptyPolls,
@@ -850,6 +913,7 @@ function createStreamsClient(options) {
850
913
  return streamStreamsEvents({
851
914
  fromCursor: params.fromCursor,
852
915
  types: params.types,
916
+ notTypes: params.notTypes,
853
917
  contractId: params.contractId,
854
918
  sender: params.sender,
855
919
  recipient: params.recipient,
@@ -893,11 +957,10 @@ function createStreamsClient(options) {
893
957
  },
894
958
  reorgs: {
895
959
  list(params) {
896
- const searchParams = new URLSearchParams;
897
- appendSearchParam2(searchParams, "since", params.since);
898
- appendSearchParam2(searchParams, "limit", params.limit);
899
- const query = searchParams.toString();
900
- return request(`/v1/streams/reorgs${query ? `?${query}` : ""}`);
960
+ return request(`/v1/streams/reorgs${buildQuery({
961
+ since: params.since,
962
+ limit: params.limit
963
+ })}`);
901
964
  }
902
965
  },
903
966
  dumps,
@@ -984,5 +1047,5 @@ export {
984
1047
  Subgraphs
985
1048
  };
986
1049
 
987
- //# debugId=1092A8F57BF9E4DD64756E2164756E21
1050
+ //# debugId=CDC19E41AE11FF9164756E2164756E21
988
1051
  //# sourceMappingURL=index.js.map