@secondlayer/sdk 6.9.0 → 6.10.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 +7 -0
- package/dist/index.d.ts +67 -1
- package/dist/index.js +173 -12
- package/dist/index.js.map +9 -8
- package/dist/streams/index.d.ts +36 -1
- package/dist/streams/index.js +166 -12
- package/dist/streams/index.js.map +7 -6
- package/dist/subgraphs/index.d.ts +30 -0
- package/dist/subgraphs/index.js +166 -12
- package/dist/subgraphs/index.js.map +7 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -27,6 +27,13 @@ require an `sk-sl_` API key, created in the platform console at
|
|
|
27
27
|
https://secondlayer.tools/platform/api-keys. (Public Streams bulk dumps —
|
|
28
28
|
`client.dumps`, `events.replay` — need no key.)
|
|
29
29
|
|
|
30
|
+
**API keys.** Each `sk-sl_` key has a `product`. An **`account`** key (dashboard
|
|
31
|
+
default) grants both `streams:read` and `index:read` and is the only key that can
|
|
32
|
+
mint new keys; **`streams`** / **`index`** keys are scoped, read-only, and cannot
|
|
33
|
+
mint. Mint scoped keys programmatically with `sl.apiKeys.create({ product })`
|
|
34
|
+
(needs an account/owner key) — the returned `key` is shown once and inherits your
|
|
35
|
+
plan's tier.
|
|
36
|
+
|
|
30
37
|
## Mental model
|
|
31
38
|
|
|
32
39
|
- `sl.streams` reads raw ordered L1 events from Stacks Streams.
|
package/dist/index.d.ts
CHANGED
|
@@ -731,6 +731,7 @@ type StackingEnvelope = {
|
|
|
731
731
|
stacking: IndexStackingAction[]
|
|
732
732
|
next_cursor: string | null
|
|
733
733
|
tip: IndexTip
|
|
734
|
+
reorgs: never[]
|
|
734
735
|
/** Present only when the PoX-4 decoder is disabled, explaining an empty feed. */
|
|
735
736
|
notes?: string
|
|
736
737
|
};
|
|
@@ -1053,6 +1054,23 @@ type StreamsEventsStreamParams = {
|
|
|
1053
1054
|
maxEmptyPolls?: number
|
|
1054
1055
|
signal?: AbortSignal
|
|
1055
1056
|
};
|
|
1057
|
+
type StreamsEventsSubscribeParams = {
|
|
1058
|
+
/** Resume strictly after this cursor; omit to live-tail from the tip. */
|
|
1059
|
+
fromCursor?: string | null
|
|
1060
|
+
types?: readonly StreamsEventType[]
|
|
1061
|
+
notTypes?: readonly StreamsEventType[]
|
|
1062
|
+
contractId?: StreamsFilterValue
|
|
1063
|
+
sender?: StreamsFilterValue
|
|
1064
|
+
recipient?: StreamsFilterValue
|
|
1065
|
+
assetIdentifier?: string
|
|
1066
|
+
/** Abort to unsubscribe (the returned function does the same). */
|
|
1067
|
+
signal?: AbortSignal
|
|
1068
|
+
/** Called for each pushed event, in order. */
|
|
1069
|
+
onEvent: (event: StreamsEvent) => void | Promise<void>
|
|
1070
|
+
/** Called on a connection error; the subscription auto-reconnects from the
|
|
1071
|
+
* last delivered cursor unless the signal has aborted. */
|
|
1072
|
+
onError?: (err: unknown) => void
|
|
1073
|
+
};
|
|
1056
1074
|
/**
|
|
1057
1075
|
* The checkpoint the SDK computes for a batch. Persist `cursor` inside the same
|
|
1058
1076
|
* transaction as your projection writes, then resume from it via `fromCursor`.
|
|
@@ -1158,6 +1176,11 @@ type StreamsDumpsManifest = {
|
|
|
1158
1176
|
to_block: number
|
|
1159
1177
|
}
|
|
1160
1178
|
files: StreamsDumpFile[]
|
|
1179
|
+
/** ed25519 signature over the manifest's canonical bytes. Absent on legacy
|
|
1180
|
+
* unsigned manifests. Verified by `list()` when `verifyDumpsManifest` is on. */
|
|
1181
|
+
signature?: string
|
|
1182
|
+
/** Short id of the signing public key. */
|
|
1183
|
+
key_id?: string
|
|
1161
1184
|
};
|
|
1162
1185
|
type StreamsDumps = {
|
|
1163
1186
|
/** Fetch and parse the latest dumps manifest. */
|
|
@@ -1197,6 +1220,13 @@ type StreamsClient = {
|
|
|
1197
1220
|
* `maxEmptyPolls` stops it.
|
|
1198
1221
|
*/
|
|
1199
1222
|
stream(params?: StreamsEventsStreamParams): AsyncIterable<StreamsEvent>
|
|
1223
|
+
/**
|
|
1224
|
+
* Subscribe to the real-time SSE push surface. Calls `onEvent` for each new
|
|
1225
|
+
* canonical event as the server pushes it (chain cadence, not poll-bounded),
|
|
1226
|
+
* and verifies each frame's inline ed25519 signature when the client was
|
|
1227
|
+
* created with `verify`. Returns an unsubscribe function.
|
|
1228
|
+
*/
|
|
1229
|
+
subscribe(params: StreamsEventsSubscribeParams): () => void
|
|
1200
1230
|
}
|
|
1201
1231
|
blocks: {
|
|
1202
1232
|
events(heightOrHash: number | string): Promise<StreamsEventsListEnvelope>
|
|
@@ -1333,6 +1363,12 @@ type CreateStreamsClientOptions = {
|
|
|
1333
1363
|
verify?: boolean | {
|
|
1334
1364
|
publicKey: string
|
|
1335
1365
|
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Verify the bulk dumps manifest's ed25519 signature in `client.dumps.list()`
|
|
1368
|
+
* before trusting any file sha256 (default off). Uses the same key source as
|
|
1369
|
+
* `verify`. Default off until historical manifests carry signatures.
|
|
1370
|
+
*/
|
|
1371
|
+
verifyDumpsManifest?: boolean
|
|
1336
1372
|
};
|
|
1337
1373
|
declare function createStreamsClient(options: CreateStreamsClientOptions): StreamsClient;
|
|
1338
1374
|
declare class AuthError extends Error {
|
|
@@ -1739,10 +1775,40 @@ type WebhookHeaderInput = HeaderLookup | StandardWebhooksHeaders | Record<string
|
|
|
1739
1775
|
* ```
|
|
1740
1776
|
*/
|
|
1741
1777
|
declare function verifyWebhookSignature(rawBody: string, headers: WebhookHeaderInput, secret: string, toleranceSeconds?: number): boolean;
|
|
1778
|
+
/**
|
|
1779
|
+
* Verify the universal Secondlayer authenticity signature that every delivery
|
|
1780
|
+
* carries, regardless of body format (`raw`, `cloudevents`, `standard-webhooks`,
|
|
1781
|
+
* …). This is the format-agnostic alternative to {@link verifyWebhookSignature}:
|
|
1782
|
+
* instead of a per-subscription HMAC secret, it checks an ed25519 signature over
|
|
1783
|
+
* `${webhook-id}.${rawBody}` against Secondlayer's published public key — so one
|
|
1784
|
+
* key proves authenticity for any format.
|
|
1785
|
+
*
|
|
1786
|
+
* @param rawBody The raw request body string (never re-stringify the parsed
|
|
1787
|
+
* JSON — whitespace/key-order changes break the signature).
|
|
1788
|
+
* @param headers Request headers — plain object, Fetch `Headers`, or a
|
|
1789
|
+
* lookup callback. Reads `webhook-id` + `x-secondlayer-signature`.
|
|
1790
|
+
* @param publicKeyPem Secondlayer's published ed25519 public key (SPKI PEM).
|
|
1791
|
+
* @returns true when the signature header is present and verifies.
|
|
1792
|
+
*
|
|
1793
|
+
* @example
|
|
1794
|
+
* ```ts
|
|
1795
|
+
* import { verifySecondlayerSignature } from "@secondlayer/sdk";
|
|
1796
|
+
*
|
|
1797
|
+
* app.post("/webhook", async (c) => {
|
|
1798
|
+
* const raw = await c.req.text();
|
|
1799
|
+
* if (!verifySecondlayerSignature(raw, c.req.raw.headers, SECONDLAYER_PUBLIC_KEY)) {
|
|
1800
|
+
* return c.text("Invalid signature", 401);
|
|
1801
|
+
* }
|
|
1802
|
+
* // ... process raw ...
|
|
1803
|
+
* return c.body(null, 204);
|
|
1804
|
+
* });
|
|
1805
|
+
* ```
|
|
1806
|
+
*/
|
|
1807
|
+
declare function verifySecondlayerSignature(rawBody: string, headers: WebhookHeaderInput, publicKeyPem: string): boolean;
|
|
1742
1808
|
/** Make a cvToValue result JSON-serializable: Clarity (u)ints decode to bigint,
|
|
1743
1809
|
* which JSON.stringify can't handle — convert recursively to strings. */
|
|
1744
1810
|
declare function toJsonSafe(value: unknown): unknown;
|
|
1745
1811
|
/** Decode a hex-encoded Clarity value to JSON-safe JS (uints as strings,
|
|
1746
1812
|
* buffers as `0x…` hex, tuples as objects). Returns the input hex on failure. */
|
|
1747
1813
|
declare function decodeClarityValue(hex: string): unknown;
|
|
1748
|
-
export { verifyWebhookSignature, trigger, toJsonSafe, isStxTransfer, isStxMint, isStxLock, isStxBurn, isPrint, isNftTransfer, isNftMint, isNftBurn, isFtTransfer, isFtMint, isFtBurn, getSubgraph, decodeStxTransfer, decodeStxMint, decodeStxLock, decodeStxBurn, decodePrint, decodeNftTransfer, decodeNftMint, decodeNftBurn, decodeFtTransfer, decodeFtMint, decodeFtBurn, decodeClarityValue, createStreamsClient, VersionConflictError, ValidationError, UpdateSubscriptionRequest2 as UpdateSubscriptionRequest, TransactionsWalkParams, TransactionsListParams, TransactionsEnvelope, TransactionEnvelope, Subscriptions, SubscriptionSummary2 as SubscriptionSummary, SubscriptionStatus, SubscriptionRuntime, SubscriptionKind, SubscriptionFormat, SubscriptionDetail2 as SubscriptionDetail, Subgraphs, SubgraphSpecOptions3 as SubgraphSpecOptions, SubgraphSpecFormat2 as SubgraphSpecFormat, SubgraphOperationStatus, SubgraphAgentSchema3 as SubgraphAgentSchema, StreamsUsage, StreamsTip, StreamsSignatureError, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorgContext, StreamsReorg, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsDumpsManifest, StreamsDumps, StreamsDumpFile, StreamsClient, StreamsCanonicalBlock, StreamsBatchContext, StackingWalkParams, StackingListParams, StackingEnvelope, SecondLayerOptions, SecondLayer, ScopedKeyProduct, RotateSecretResponse2 as RotateSecretResponse, ReplayResult2 as ReplayResult, RateLimitError, Pox4CallsParams, NftTransfersWalkParams, NftTransfersListParams, NftTransfersEnvelope, NftTransferPayload, NftTransferEvent, NftTransfer, MempoolWalkParams, MempoolTransactionEnvelope, MempoolListParams, MempoolEnvelope, IndexUsage, IndexTransaction, IndexTip, IndexStackingAction, IndexPostCondition, IndexMempoolTransaction, IndexEventType, IndexEvent, IndexContractCall, IndexCanonicalBlock, IndexBlock, Index, FtTransfersWalkParams, FtTransfersListParams, FtTransfersEnvelope, FtTransferPayload, FtTransferEvent, FtTransfer, FetchLike2 as FetchLike, EventsWalkParams, EventsListParams, EventsEnvelope, DeliveryRow2 as DeliveryRow, 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, DeadRow2 as DeadRow, Datasets, DatasetRow, CursorListParams, CursorEnvelope, Cursor, CreateSubscriptionResponse2 as CreateSubscriptionResponse, CreateSubscriptionRequest2 as CreateSubscriptionRequest, CreateApiKeyResponse, CreateApiKeyParams, ContractsListParams, ContractsEnvelope, Contracts, ContractSummary, ContractConformance, ContractCallsWalkParams, ContractCallsListParams, ContractCallsEnvelope, ContextSnapshot, ContextAccount, ChainTriggerType, ChainTrigger, CanonicalWalkParams, CanonicalListParams, CanonicalEnvelope, CURSOR_SLUGS, BlocksWalkParams, BlocksListParams, BlocksEnvelope, BlockEnvelope, AuthError, ApiKeys, ApiError, ActiveSubgraphOperation };
|
|
1814
|
+
export { verifyWebhookSignature, verifySecondlayerSignature, trigger, toJsonSafe, isStxTransfer, isStxMint, isStxLock, isStxBurn, isPrint, isNftTransfer, isNftMint, isNftBurn, isFtTransfer, isFtMint, isFtBurn, getSubgraph, decodeStxTransfer, decodeStxMint, decodeStxLock, decodeStxBurn, decodePrint, decodeNftTransfer, decodeNftMint, decodeNftBurn, decodeFtTransfer, decodeFtMint, decodeFtBurn, decodeClarityValue, createStreamsClient, VersionConflictError, ValidationError, UpdateSubscriptionRequest2 as UpdateSubscriptionRequest, TransactionsWalkParams, TransactionsListParams, TransactionsEnvelope, TransactionEnvelope, Subscriptions, SubscriptionSummary2 as SubscriptionSummary, SubscriptionStatus, SubscriptionRuntime, SubscriptionKind, SubscriptionFormat, SubscriptionDetail2 as SubscriptionDetail, Subgraphs, SubgraphSpecOptions3 as SubgraphSpecOptions, SubgraphSpecFormat2 as SubgraphSpecFormat, SubgraphOperationStatus, SubgraphAgentSchema3 as SubgraphAgentSchema, StreamsUsage, StreamsTip, StreamsSignatureError, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorgContext, StreamsReorg, StreamsEventsSubscribeParams, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsDumpsManifest, StreamsDumps, StreamsDumpFile, StreamsClient, StreamsCanonicalBlock, StreamsBatchContext, StackingWalkParams, StackingListParams, StackingEnvelope, SecondLayerOptions, SecondLayer, ScopedKeyProduct, RotateSecretResponse2 as RotateSecretResponse, ReplayResult2 as ReplayResult, RateLimitError, Pox4CallsParams, NftTransfersWalkParams, NftTransfersListParams, NftTransfersEnvelope, NftTransferPayload, NftTransferEvent, NftTransfer, MempoolWalkParams, MempoolTransactionEnvelope, MempoolListParams, MempoolEnvelope, IndexUsage, IndexTransaction, IndexTip, IndexStackingAction, IndexPostCondition, IndexMempoolTransaction, IndexEventType, IndexEvent, IndexContractCall, IndexCanonicalBlock, IndexBlock, Index, FtTransfersWalkParams, FtTransfersListParams, FtTransfersEnvelope, FtTransferPayload, FtTransferEvent, FtTransfer, FetchLike2 as FetchLike, EventsWalkParams, EventsListParams, EventsEnvelope, DeliveryRow2 as DeliveryRow, 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, DeadRow2 as DeadRow, Datasets, DatasetRow, CursorListParams, CursorEnvelope, Cursor, CreateSubscriptionResponse2 as CreateSubscriptionResponse, CreateSubscriptionRequest2 as CreateSubscriptionRequest, CreateApiKeyResponse, CreateApiKeyParams, ContractsListParams, ContractsEnvelope, Contracts, ContractSummary, ContractConformance, ContractCallsWalkParams, ContractCallsListParams, ContractCallsEnvelope, ContextSnapshot, ContextAccount, ChainTriggerType, ChainTrigger, CanonicalWalkParams, CanonicalListParams, CanonicalEnvelope, CURSOR_SLUGS, BlocksWalkParams, BlocksListParams, BlocksEnvelope, BlockEnvelope, AuthError, ApiKeys, ApiError, ActiveSubgraphOperation };
|
package/dist/index.js
CHANGED
|
@@ -891,7 +891,7 @@ class Index extends BaseClient {
|
|
|
891
891
|
}
|
|
892
892
|
|
|
893
893
|
// src/streams/client.ts
|
|
894
|
-
import { ed25519 } from "@secondlayer/shared";
|
|
894
|
+
import { ed25519 as ed255192 } from "@secondlayer/shared";
|
|
895
895
|
|
|
896
896
|
// src/streams/errors.ts
|
|
897
897
|
class AuthError extends Error {
|
|
@@ -1078,6 +1078,7 @@ async function* streamStreamsEvents(opts) {
|
|
|
1078
1078
|
|
|
1079
1079
|
// src/streams/dumps.ts
|
|
1080
1080
|
import { createHash } from "node:crypto";
|
|
1081
|
+
import { verifyStreamsBulkManifestSignature } from "@secondlayer/shared/streams-bulk-manifest";
|
|
1081
1082
|
function createStreamsDumps(opts) {
|
|
1082
1083
|
const baseUrl = opts.baseUrl?.replace(/\/+$/, "");
|
|
1083
1084
|
function requireBaseUrl() {
|
|
@@ -1095,7 +1096,17 @@ function createStreamsDumps(opts) {
|
|
|
1095
1096
|
if (!res.ok) {
|
|
1096
1097
|
throw new StreamsServerError(`Could not fetch dumps manifest (${res.status}).`, res.status);
|
|
1097
1098
|
}
|
|
1098
|
-
|
|
1099
|
+
const manifest = await res.json();
|
|
1100
|
+
if (opts.verifyManifest) {
|
|
1101
|
+
if (!opts.loadPublicKeyPem) {
|
|
1102
|
+
throw new StreamsSignatureError("Manifest verification is on but no signing key source is configured.");
|
|
1103
|
+
}
|
|
1104
|
+
const publicKeyPem = await opts.loadPublicKeyPem();
|
|
1105
|
+
if (!verifyStreamsBulkManifestSignature(manifest, publicKeyPem)) {
|
|
1106
|
+
throw new StreamsSignatureError("Dumps manifest signature is missing or invalid.");
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return manifest;
|
|
1099
1110
|
}
|
|
1100
1111
|
async function download(file) {
|
|
1101
1112
|
const res = await opts.fetchImpl(fileUrl(file));
|
|
@@ -1112,6 +1123,135 @@ function createStreamsDumps(opts) {
|
|
|
1112
1123
|
return { list, fileUrl, download };
|
|
1113
1124
|
}
|
|
1114
1125
|
|
|
1126
|
+
// src/streams/subscribe.ts
|
|
1127
|
+
import { ed25519 } from "@secondlayer/shared";
|
|
1128
|
+
function subscribeStreamsEvents(opts) {
|
|
1129
|
+
const { params } = opts;
|
|
1130
|
+
const controller = new AbortController;
|
|
1131
|
+
const external = params.signal;
|
|
1132
|
+
if (external) {
|
|
1133
|
+
if (external.aborted)
|
|
1134
|
+
controller.abort();
|
|
1135
|
+
else
|
|
1136
|
+
external.addEventListener("abort", () => controller.abort(), {
|
|
1137
|
+
once: true
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
let cursor = params.fromCursor ?? null;
|
|
1141
|
+
const reconnectDelayMs = opts.reconnectDelayMs ?? 1000;
|
|
1142
|
+
const run = async () => {
|
|
1143
|
+
while (!controller.signal.aborted) {
|
|
1144
|
+
try {
|
|
1145
|
+
const url = `${opts.baseUrl}/v1/streams/events/stream${buildQuery({
|
|
1146
|
+
from_cursor: cursor ?? undefined,
|
|
1147
|
+
types: params.types,
|
|
1148
|
+
not_types: params.notTypes,
|
|
1149
|
+
contract_id: params.contractId,
|
|
1150
|
+
sender: params.sender,
|
|
1151
|
+
recipient: params.recipient,
|
|
1152
|
+
asset_identifier: params.assetIdentifier
|
|
1153
|
+
})}`;
|
|
1154
|
+
const res = await opts.fetchImpl(url, {
|
|
1155
|
+
headers: {
|
|
1156
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
1157
|
+
Accept: "text/event-stream"
|
|
1158
|
+
},
|
|
1159
|
+
signal: controller.signal
|
|
1160
|
+
});
|
|
1161
|
+
if (!res.ok) {
|
|
1162
|
+
throw new StreamsServerError(`Streams SSE returned ${res.status}.`, res.status);
|
|
1163
|
+
}
|
|
1164
|
+
if (!res.body) {
|
|
1165
|
+
throw new StreamsServerError("Streams SSE response has no body.", 0);
|
|
1166
|
+
}
|
|
1167
|
+
for await (const frame of parseSseFrames(res.body, controller.signal)) {
|
|
1168
|
+
if (frame.event === "ping" || !frame.data)
|
|
1169
|
+
continue;
|
|
1170
|
+
let parsed;
|
|
1171
|
+
try {
|
|
1172
|
+
parsed = JSON.parse(frame.data);
|
|
1173
|
+
} catch {
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (!parsed.event)
|
|
1177
|
+
continue;
|
|
1178
|
+
if (opts.verify) {
|
|
1179
|
+
const key = await opts.loadKey();
|
|
1180
|
+
if (!parsed.sig || !ed25519.verifyEd25519(JSON.stringify(parsed.event), parsed.sig, key.publicKey)) {
|
|
1181
|
+
throw new StreamsSignatureError("Streams SSE frame signature is missing or invalid.");
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
cursor = parsed.event.cursor ?? cursor;
|
|
1185
|
+
await params.onEvent(parsed.event);
|
|
1186
|
+
}
|
|
1187
|
+
} catch (err) {
|
|
1188
|
+
if (controller.signal.aborted)
|
|
1189
|
+
return;
|
|
1190
|
+
params.onError?.(err);
|
|
1191
|
+
await sleep(reconnectDelayMs, controller.signal);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
run();
|
|
1196
|
+
return () => controller.abort();
|
|
1197
|
+
}
|
|
1198
|
+
function sleep(ms, signal) {
|
|
1199
|
+
return new Promise((resolve) => {
|
|
1200
|
+
if (signal.aborted)
|
|
1201
|
+
return resolve();
|
|
1202
|
+
const onAbort = () => {
|
|
1203
|
+
clearTimeout(timer);
|
|
1204
|
+
resolve();
|
|
1205
|
+
};
|
|
1206
|
+
const timer = setTimeout(() => {
|
|
1207
|
+
signal.removeEventListener("abort", onAbort);
|
|
1208
|
+
resolve();
|
|
1209
|
+
}, ms);
|
|
1210
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
async function* parseSseFrames(body, signal) {
|
|
1214
|
+
const reader = body.getReader();
|
|
1215
|
+
const decoder = new TextDecoder;
|
|
1216
|
+
let buffer = "";
|
|
1217
|
+
try {
|
|
1218
|
+
while (!signal.aborted) {
|
|
1219
|
+
const { value, done } = await reader.read();
|
|
1220
|
+
if (done)
|
|
1221
|
+
break;
|
|
1222
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1223
|
+
let sep = buffer.indexOf(`
|
|
1224
|
+
|
|
1225
|
+
`);
|
|
1226
|
+
while (sep !== -1) {
|
|
1227
|
+
yield parseFrame(buffer.slice(0, sep));
|
|
1228
|
+
buffer = buffer.slice(sep + 2);
|
|
1229
|
+
sep = buffer.indexOf(`
|
|
1230
|
+
|
|
1231
|
+
`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
} finally {
|
|
1235
|
+
try {
|
|
1236
|
+
await reader.cancel();
|
|
1237
|
+
} catch {}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function parseFrame(raw) {
|
|
1241
|
+
let event;
|
|
1242
|
+
const data = [];
|
|
1243
|
+
for (const line of raw.split(`
|
|
1244
|
+
`)) {
|
|
1245
|
+
if (line.startsWith("data:")) {
|
|
1246
|
+
data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
1247
|
+
} else if (line.startsWith("event:")) {
|
|
1248
|
+
event = line.slice(line.startsWith("event: ") ? 7 : 6).trim();
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return { event, data: data.length > 0 ? data.join(`
|
|
1252
|
+
`) : undefined };
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1115
1255
|
// src/streams/client.ts
|
|
1116
1256
|
function cursorTuple(cursor) {
|
|
1117
1257
|
if (!cursor)
|
|
@@ -1171,10 +1311,6 @@ function createStreamsClient(options) {
|
|
|
1171
1311
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_STREAMS_BASE_URL);
|
|
1172
1312
|
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
1173
1313
|
const verify = options.verify ?? false;
|
|
1174
|
-
const dumps = createStreamsDumps({
|
|
1175
|
-
baseUrl: options.dumpsBaseUrl,
|
|
1176
|
-
fetchImpl
|
|
1177
|
-
});
|
|
1178
1314
|
let keyPromise = null;
|
|
1179
1315
|
function loadKey() {
|
|
1180
1316
|
if (keyPromise)
|
|
@@ -1182,8 +1318,9 @@ function createStreamsClient(options) {
|
|
|
1182
1318
|
keyPromise = (async () => {
|
|
1183
1319
|
if (typeof verify === "object") {
|
|
1184
1320
|
return {
|
|
1185
|
-
keyId:
|
|
1186
|
-
|
|
1321
|
+
keyId: ed255192.ed25519KeyId(verify.publicKey),
|
|
1322
|
+
publicKeyPem: verify.publicKey,
|
|
1323
|
+
publicKey: ed255192.loadEd25519PublicKey(verify.publicKey)
|
|
1187
1324
|
};
|
|
1188
1325
|
}
|
|
1189
1326
|
const res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
|
|
@@ -1195,12 +1332,19 @@ function createStreamsClient(options) {
|
|
|
1195
1332
|
throw new StreamsSignatureError("Signing key response missing key.");
|
|
1196
1333
|
}
|
|
1197
1334
|
return {
|
|
1198
|
-
keyId: body.key_id ??
|
|
1199
|
-
|
|
1335
|
+
keyId: body.key_id ?? ed255192.ed25519KeyId(body.public_key_pem),
|
|
1336
|
+
publicKeyPem: body.public_key_pem,
|
|
1337
|
+
publicKey: ed255192.loadEd25519PublicKey(body.public_key_pem)
|
|
1200
1338
|
};
|
|
1201
1339
|
})();
|
|
1202
1340
|
return keyPromise;
|
|
1203
1341
|
}
|
|
1342
|
+
const dumps = createStreamsDumps({
|
|
1343
|
+
baseUrl: options.dumpsBaseUrl,
|
|
1344
|
+
fetchImpl,
|
|
1345
|
+
verifyManifest: options.verifyDumpsManifest ?? false,
|
|
1346
|
+
loadPublicKeyPem: async () => (await loadKey()).publicKeyPem
|
|
1347
|
+
});
|
|
1204
1348
|
async function request(path) {
|
|
1205
1349
|
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
1206
1350
|
headers: { Authorization: `Bearer ${options.apiKey}` }
|
|
@@ -1225,7 +1369,7 @@ function createStreamsClient(options) {
|
|
|
1225
1369
|
throw new StreamsSignatureError(`Response signed with key '${responseKeyId}' not served by the signing-key endpoint.`);
|
|
1226
1370
|
}
|
|
1227
1371
|
}
|
|
1228
|
-
if (!
|
|
1372
|
+
if (!ed255192.verifyEd25519(text, signature, key.publicKey)) {
|
|
1229
1373
|
throw new StreamsSignatureError;
|
|
1230
1374
|
}
|
|
1231
1375
|
}
|
|
@@ -1310,6 +1454,16 @@ function createStreamsClient(options) {
|
|
|
1310
1454
|
fetchEvents
|
|
1311
1455
|
});
|
|
1312
1456
|
},
|
|
1457
|
+
subscribe(params) {
|
|
1458
|
+
return subscribeStreamsEvents({
|
|
1459
|
+
baseUrl,
|
|
1460
|
+
apiKey: options.apiKey,
|
|
1461
|
+
fetchImpl,
|
|
1462
|
+
verify: Boolean(verify),
|
|
1463
|
+
loadKey,
|
|
1464
|
+
params
|
|
1465
|
+
});
|
|
1466
|
+
},
|
|
1313
1467
|
async replay(params) {
|
|
1314
1468
|
const fromCursor = params.from === "genesis" ? null : params.from ?? null;
|
|
1315
1469
|
const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
|
|
@@ -1763,6 +1917,7 @@ import {
|
|
|
1763
1917
|
STREAMS_EVENT_TYPES
|
|
1764
1918
|
} from "@secondlayer/shared";
|
|
1765
1919
|
// src/webhooks.ts
|
|
1920
|
+
import { verifySecondlayerSignatureValues } from "@secondlayer/shared/crypto/secondlayer-webhook";
|
|
1766
1921
|
import {
|
|
1767
1922
|
verify
|
|
1768
1923
|
} from "@secondlayer/shared/crypto/standard-webhooks";
|
|
@@ -1805,8 +1960,14 @@ function verifyWebhookSignature(rawBody, headers, secret, toleranceSeconds = 300
|
|
|
1805
1960
|
"webhook-signature": signature
|
|
1806
1961
|
}, secret, { toleranceSeconds });
|
|
1807
1962
|
}
|
|
1963
|
+
function verifySecondlayerSignature(rawBody, headers, publicKeyPem) {
|
|
1964
|
+
const id = pickHeader(headers, "webhook-id");
|
|
1965
|
+
const signature = pickHeader(headers, "x-secondlayer-signature");
|
|
1966
|
+
return verifySecondlayerSignatureValues(rawBody, id, signature, publicKeyPem);
|
|
1967
|
+
}
|
|
1808
1968
|
export {
|
|
1809
1969
|
verifyWebhookSignature,
|
|
1970
|
+
verifySecondlayerSignature,
|
|
1810
1971
|
trigger,
|
|
1811
1972
|
toJsonSafe,
|
|
1812
1973
|
isStxTransfer,
|
|
@@ -1852,5 +2013,5 @@ export {
|
|
|
1852
2013
|
ApiError
|
|
1853
2014
|
};
|
|
1854
2015
|
|
|
1855
|
-
//# debugId=
|
|
2016
|
+
//# debugId=30831F6F187A1F5A64756E2164756E21
|
|
1856
2017
|
//# sourceMappingURL=index.js.map
|