@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.
package/dist/index.js CHANGED
@@ -501,6 +501,9 @@ class Index extends BaseClient {
501
501
  }
502
502
  }
503
503
 
504
+ // src/streams/client.ts
505
+ import { ed25519 } from "@secondlayer/shared";
506
+
504
507
  // src/streams/consumer.ts
505
508
  async function defaultSleep(ms, signal) {
506
509
  if (signal?.aborted)
@@ -529,7 +532,11 @@ async function consumeStreamsEvents(opts) {
529
532
  cursor,
530
533
  limit: opts.batchSize,
531
534
  types: opts.types,
532
- contractId: opts.contractId
535
+ notTypes: opts.notTypes,
536
+ contractId: opts.contractId,
537
+ sender: opts.sender,
538
+ recipient: opts.recipient,
539
+ assetIdentifier: opts.assetIdentifier
533
540
  });
534
541
  pages++;
535
542
  const returnedCursor = await opts.onBatch(envelope.events, envelope);
@@ -564,7 +571,11 @@ async function* streamStreamsEvents(opts) {
564
571
  cursor,
565
572
  limit: opts.batchSize,
566
573
  types: opts.types,
567
- contractId: opts.contractId
574
+ notTypes: opts.notTypes,
575
+ contractId: opts.contractId,
576
+ sender: opts.sender,
577
+ recipient: opts.recipient,
578
+ assetIdentifier: opts.assetIdentifier
568
579
  });
569
580
  pages++;
570
581
  for (const event of envelope.events) {
@@ -589,6 +600,9 @@ async function* streamStreamsEvents(opts) {
589
600
  }
590
601
  }
591
602
 
603
+ // src/streams/dumps.ts
604
+ import { createHash } from "node:crypto";
605
+
592
606
  // src/streams/errors.ts
593
607
  class AuthError extends Error {
594
608
  status = 401;
@@ -630,7 +644,60 @@ class StreamsServerError extends Error {
630
644
  }
631
645
  }
632
646
 
647
+ class StreamsSignatureError extends Error {
648
+ constructor(message = "Streams response signature verification failed.") {
649
+ super(message);
650
+ this.name = "StreamsSignatureError";
651
+ }
652
+ }
653
+
654
+ // src/streams/dumps.ts
655
+ function createStreamsDumps(opts) {
656
+ const baseUrl = opts.baseUrl?.replace(/\/+$/, "");
657
+ function requireBaseUrl() {
658
+ if (!baseUrl) {
659
+ throw new StreamsServerError("Streams dumps require `dumpsBaseUrl` on createStreamsClient.", 0);
660
+ }
661
+ return baseUrl;
662
+ }
663
+ function fileUrl(file) {
664
+ return `${requireBaseUrl()}/${file.path.replace(/^\/+/, "")}`;
665
+ }
666
+ async function list() {
667
+ const url = `${requireBaseUrl()}/manifest/latest.json`;
668
+ const res = await opts.fetchImpl(url);
669
+ if (!res.ok) {
670
+ throw new StreamsServerError(`Could not fetch dumps manifest (${res.status}).`, res.status);
671
+ }
672
+ return await res.json();
673
+ }
674
+ async function download(file) {
675
+ const res = await opts.fetchImpl(fileUrl(file));
676
+ if (!res.ok) {
677
+ throw new StreamsServerError(`Could not download dump ${file.path} (${res.status}).`, res.status);
678
+ }
679
+ const bytes = new Uint8Array(await res.arrayBuffer());
680
+ const digest = createHash("sha256").update(bytes).digest("hex");
681
+ if (digest !== file.sha256) {
682
+ throw new StreamsSignatureError(`Dump ${file.path} sha256 mismatch (expected ${file.sha256}, got ${digest}).`);
683
+ }
684
+ return bytes;
685
+ }
686
+ return { list, fileUrl, download };
687
+ }
688
+
633
689
  // src/streams/client.ts
690
+ function cursorTuple(cursor) {
691
+ if (!cursor)
692
+ return [-1, -1];
693
+ const [block, index] = cursor.split(":");
694
+ return [Number(block), Number(index)];
695
+ }
696
+ function maxCursor(a, b) {
697
+ const [ah, ai] = cursorTuple(a);
698
+ const [bh, bi] = cursorTuple(b);
699
+ return ah > bh || ah === bh && ai >= bi ? a : b;
700
+ }
634
701
  var DEFAULT_STREAMS_BASE_URL = "https://api.secondlayer.tools";
635
702
  function normalizeBaseUrl(baseUrl) {
636
703
  return baseUrl.replace(/\/+$/, "");
@@ -640,6 +707,13 @@ function appendSearchParam2(params, name, value) {
640
707
  return;
641
708
  params.set(name, String(value));
642
709
  }
710
+ function appendListParam(params, name, value) {
711
+ if (value === undefined || value === null)
712
+ return;
713
+ const joined = Array.isArray(value) ? value.join(",") : value;
714
+ if (joined.length > 0)
715
+ params.set(name, joined);
716
+ }
643
717
  async function responseBody(response) {
644
718
  const text = await response.text();
645
719
  if (text.length === 0)
@@ -678,21 +752,87 @@ async function mapStreamsError(response) {
678
752
  function createStreamsClient(options) {
679
753
  const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_STREAMS_BASE_URL);
680
754
  const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
755
+ const verify = options.verify ?? false;
756
+ const dumps = createStreamsDumps({
757
+ baseUrl: options.dumpsBaseUrl,
758
+ fetchImpl
759
+ });
760
+ let keyPromise = null;
761
+ function loadKey() {
762
+ if (keyPromise)
763
+ return keyPromise;
764
+ keyPromise = (async () => {
765
+ if (typeof verify === "object") {
766
+ return {
767
+ keyId: ed25519.ed25519KeyId(verify.publicKey),
768
+ publicKey: ed25519.loadEd25519PublicKey(verify.publicKey)
769
+ };
770
+ }
771
+ const res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
772
+ if (!res.ok) {
773
+ throw new StreamsSignatureError(`Could not fetch signing key (${res.status}).`);
774
+ }
775
+ const body = await res.json();
776
+ if (!body.public_key_pem) {
777
+ throw new StreamsSignatureError("Signing key response missing key.");
778
+ }
779
+ return {
780
+ keyId: body.key_id ?? ed25519.ed25519KeyId(body.public_key_pem),
781
+ publicKey: ed25519.loadEd25519PublicKey(body.public_key_pem)
782
+ };
783
+ })();
784
+ return keyPromise;
785
+ }
681
786
  async function request(path) {
682
787
  const response = await fetchImpl(`${baseUrl}${path}`, {
683
788
  headers: { Authorization: `Bearer ${options.apiKey}` }
684
789
  });
685
790
  if (!response.ok)
686
791
  await mapStreamsError(response);
687
- return await response.json();
792
+ const text = await response.text();
793
+ if (verify) {
794
+ const signature = response.headers.get("X-Signature");
795
+ if (!signature) {
796
+ throw new StreamsSignatureError("Response is missing X-Signature.");
797
+ }
798
+ const responseKeyId = response.headers.get("X-Signature-KeyId");
799
+ let key = await loadKey();
800
+ if (responseKeyId && responseKeyId !== key.keyId) {
801
+ if (typeof verify === "object") {
802
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}', expected pinned key '${key.keyId}'.`);
803
+ }
804
+ keyPromise = null;
805
+ key = await loadKey();
806
+ if (responseKeyId !== key.keyId) {
807
+ throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
808
+ }
809
+ }
810
+ if (!ed25519.verifyEd25519(text, signature, key.publicKey)) {
811
+ throw new StreamsSignatureError;
812
+ }
813
+ }
814
+ return JSON.parse(text);
688
815
  }
689
816
  const fetchEvents = async ({
690
817
  cursor,
691
818
  limit,
692
819
  types,
693
- contractId
820
+ notTypes,
821
+ contractId,
822
+ sender,
823
+ recipient,
824
+ assetIdentifier
694
825
  }) => {
695
- return listEvents({ cursor, limit, types, contractId });
826
+ return listEvents({
827
+ cursor,
828
+ limit,
829
+ types,
830
+ notTypes,
831
+ contractId,
832
+ sender,
833
+ recipient,
834
+ assetIdentifier
835
+ });
696
836
  };
697
837
  async function listEvents(params = {}) {
698
838
  const searchParams = new URLSearchParams;
@@ -700,10 +840,16 @@ function createStreamsClient(options) {
700
840
  appendSearchParam2(searchParams, "from_height", params.fromHeight);
701
841
  appendSearchParam2(searchParams, "to_height", params.toHeight);
702
842
  appendSearchParam2(searchParams, "limit", params.limit);
703
- appendSearchParam2(searchParams, "contract_id", params.contractId);
843
+ appendListParam(searchParams, "contract_id", params.contractId);
844
+ appendListParam(searchParams, "sender", params.sender);
845
+ appendListParam(searchParams, "recipient", params.recipient);
846
+ appendSearchParam2(searchParams, "asset_identifier", params.assetIdentifier);
704
847
  if (params.types?.length) {
705
848
  searchParams.set("types", params.types.join(","));
706
849
  }
850
+ if (params.notTypes?.length) {
851
+ searchParams.set("not_types", params.notTypes.join(","));
852
+ }
707
853
  const query = searchParams.toString();
708
854
  return request(`/v1/streams/events${query ? `?${query}` : ""}`);
709
855
  }
@@ -718,7 +864,11 @@ function createStreamsClient(options) {
718
864
  fromCursor: params.fromCursor,
719
865
  mode: params.mode,
720
866
  types: params.types,
867
+ notTypes: params.notTypes,
721
868
  contractId: params.contractId,
869
+ sender: params.sender,
870
+ recipient: params.recipient,
871
+ assetIdentifier: params.assetIdentifier,
722
872
  batchSize: params.batchSize ?? 100,
723
873
  fetchEvents,
724
874
  onBatch: params.onBatch,
@@ -732,7 +882,11 @@ function createStreamsClient(options) {
732
882
  return streamStreamsEvents({
733
883
  fromCursor: params.fromCursor,
734
884
  types: params.types,
885
+ notTypes: params.notTypes,
735
886
  contractId: params.contractId,
887
+ sender: params.sender,
888
+ recipient: params.recipient,
889
+ assetIdentifier: params.assetIdentifier,
736
890
  batchSize: params.batchSize ?? 100,
737
891
  emptyBackoffMs: params.emptyBackoffMs,
738
892
  maxPages: params.maxPages,
@@ -740,6 +894,29 @@ function createStreamsClient(options) {
740
894
  signal: params.signal,
741
895
  fetchEvents
742
896
  });
897
+ },
898
+ async replay(params) {
899
+ const fromCursor = params.from === "genesis" ? null : params.from ?? null;
900
+ const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
901
+ const manifest = await dumps.list();
902
+ 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);
903
+ for (const file of files) {
904
+ if (params.signal?.aborted)
905
+ break;
906
+ await params.onDumpFile(file);
907
+ }
908
+ const seam = maxCursor(fromCursor, manifest.latest_finalized_cursor);
909
+ return consumeStreamsEvents({
910
+ fromCursor: seam,
911
+ mode: params.mode ?? "tail",
912
+ batchSize: params.batchSize ?? 100,
913
+ fetchEvents,
914
+ onBatch: params.onBatch,
915
+ emptyBackoffMs: params.emptyBackoffMs,
916
+ maxPages: params.maxPages,
917
+ maxEmptyPolls: params.maxEmptyPolls,
918
+ signal: params.signal
919
+ });
743
920
  }
744
921
  },
745
922
  blocks: {
@@ -756,6 +933,7 @@ function createStreamsClient(options) {
756
933
  return request(`/v1/streams/reorgs${query ? `?${query}` : ""}`);
757
934
  }
758
935
  },
936
+ dumps,
759
937
  canonical(height) {
760
938
  return request(`/v1/streams/canonical/${height}`);
761
939
  },
@@ -1402,6 +1580,7 @@ export {
1402
1580
  ValidationError,
1403
1581
  Subscriptions,
1404
1582
  Subgraphs,
1583
+ StreamsSignatureError,
1405
1584
  StreamsServerError,
1406
1585
  SecondLayer,
1407
1586
  RateLimitError,
@@ -1412,5 +1591,5 @@ export {
1412
1591
  ApiError
1413
1592
  };
1414
1593
 
1415
- //# debugId=45667C0DA2C290D964756E2164756E21
1594
+ //# debugId=4712778CA4B6FFB664756E2164756E21
1416
1595
  //# sourceMappingURL=index.js.map