@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/README.md +62 -6
- package/dist/index.d.ts +199 -29
- package/dist/index.js +186 -7
- package/dist/index.js.map +10 -9
- package/dist/streams/index.d.ts +199 -29
- package/dist/streams/index.js +186 -7
- package/dist/streams/index.js.map +10 -9
- package/dist/subgraphs/index.d.ts +173 -8
- package/dist/subgraphs/index.js +185 -7
- package/dist/subgraphs/index.js.map +7 -6
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
820
|
+
notTypes,
|
|
821
|
+
contractId,
|
|
822
|
+
sender,
|
|
823
|
+
recipient,
|
|
824
|
+
assetIdentifier
|
|
694
825
|
}) => {
|
|
695
|
-
return listEvents({
|
|
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
|
-
|
|
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=
|
|
1594
|
+
//# debugId=4712778CA4B6FFB664756E2164756E21
|
|
1416
1595
|
//# sourceMappingURL=index.js.map
|