@secondlayer/sdk 5.7.0 → 5.9.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 +1 -1
- package/dist/index.d.ts +104 -1
- package/dist/index.js +177 -6
- package/dist/index.js.map +9 -8
- package/dist/streams/index.d.ts +104 -1
- package/dist/streams/index.js +152 -6
- package/dist/streams/index.js.map +8 -7
- package/dist/subgraphs/index.d.ts +85 -0
- package/dist/subgraphs/index.js +176 -6
- package/dist/subgraphs/index.js.map +8 -7
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ console.log({ tip, firstCursor: page.events[0]?.cursor });
|
|
|
50
50
|
import { createStreamsClient } from "@secondlayer/sdk";
|
|
51
51
|
|
|
52
52
|
const streams = createStreamsClient({
|
|
53
|
-
apiKey: process.env.
|
|
53
|
+
apiKey: process.env.SL_API_KEY!, // sk-sl_...
|
|
54
54
|
});
|
|
55
55
|
```
|
|
56
56
|
|
package/dist/index.d.ts
CHANGED
|
@@ -359,11 +359,21 @@ type StreamsEvent = {
|
|
|
359
359
|
contract_id: string | null
|
|
360
360
|
payload: StreamsEventPayload
|
|
361
361
|
ts: string
|
|
362
|
+
/**
|
|
363
|
+
* True when this event's block is past the finality boundary (immutable).
|
|
364
|
+
* Optional for back-compat; the API always sets it on Streams responses.
|
|
365
|
+
*/
|
|
366
|
+
finalized?: boolean
|
|
362
367
|
};
|
|
363
368
|
type StreamsTip = {
|
|
364
369
|
block_height: number
|
|
365
370
|
block_hash: string
|
|
366
371
|
burn_block_height: number
|
|
372
|
+
/**
|
|
373
|
+
* Highest Stacks block past the burn-confirmation finality boundary.
|
|
374
|
+
* Optional for back-compat; the API always sets it.
|
|
375
|
+
*/
|
|
376
|
+
finalized_height?: number
|
|
367
377
|
lag_seconds: number
|
|
368
378
|
};
|
|
369
379
|
type StreamsCanonicalBlock = {
|
|
@@ -403,12 +413,18 @@ type StreamsEventsListParams = {
|
|
|
403
413
|
toHeight?: number
|
|
404
414
|
types?: readonly StreamsEventType[]
|
|
405
415
|
contractId?: string
|
|
416
|
+
sender?: string
|
|
417
|
+
recipient?: string
|
|
418
|
+
assetIdentifier?: string
|
|
406
419
|
limit?: number
|
|
407
420
|
};
|
|
408
421
|
type StreamsEventsStreamParams = {
|
|
409
422
|
fromCursor?: string | null
|
|
410
423
|
types?: readonly StreamsEventType[]
|
|
411
424
|
contractId?: string
|
|
425
|
+
sender?: string
|
|
426
|
+
recipient?: string
|
|
427
|
+
assetIdentifier?: string
|
|
412
428
|
batchSize?: number
|
|
413
429
|
emptyBackoffMs?: number
|
|
414
430
|
maxPages?: number
|
|
@@ -420,6 +436,9 @@ type StreamsEventsConsumeParams = {
|
|
|
420
436
|
mode?: "tail" | "bounded"
|
|
421
437
|
types?: readonly StreamsEventType[]
|
|
422
438
|
contractId?: string
|
|
439
|
+
sender?: string
|
|
440
|
+
recipient?: string
|
|
441
|
+
assetIdentifier?: string
|
|
423
442
|
batchSize?: number
|
|
424
443
|
onBatch: (events: StreamsEvent[], envelope: StreamsEventsEnvelope) => Promise<string | null | undefined> | string | null | undefined
|
|
425
444
|
emptyBackoffMs?: number
|
|
@@ -432,7 +451,63 @@ type StreamsEventsConsumeResult = {
|
|
|
432
451
|
pages: number
|
|
433
452
|
emptyPolls: number
|
|
434
453
|
};
|
|
454
|
+
type StreamsEventsReplayParams = {
|
|
455
|
+
/** Start point: `"genesis"` (default) or a `<block>:<index>` cursor. */
|
|
456
|
+
from?: "genesis" | string
|
|
457
|
+
/**
|
|
458
|
+
* Called once per finalized dump file, in block order, before live tailing.
|
|
459
|
+
* Process the parquet with your own tooling (e.g. DuckDB) — the SDK does not
|
|
460
|
+
* decode parquet. Use `client.dumps.download(file)` to fetch + verify bytes.
|
|
461
|
+
*/
|
|
462
|
+
onDumpFile: (file: StreamsDumpFile) => Promise<void> | void
|
|
463
|
+
/** Called per live page after the dump phase, like `consume`. */
|
|
464
|
+
onBatch: (events: StreamsEvent[], envelope: StreamsEventsEnvelope) => Promise<string | null | undefined> | string | null | undefined
|
|
465
|
+
mode?: "tail" | "bounded"
|
|
466
|
+
batchSize?: number
|
|
467
|
+
emptyBackoffMs?: number
|
|
468
|
+
maxPages?: number
|
|
469
|
+
maxEmptyPolls?: number
|
|
470
|
+
signal?: AbortSignal
|
|
471
|
+
};
|
|
435
472
|
type FetchLike2 = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
473
|
+
/** One bulk parquet file in the dumps manifest. `path` is the object key under
|
|
474
|
+
* the dumps base URL. */
|
|
475
|
+
type StreamsDumpFile = {
|
|
476
|
+
path: string
|
|
477
|
+
from_block: number
|
|
478
|
+
to_block: number
|
|
479
|
+
min_cursor: string | null
|
|
480
|
+
max_cursor: string | null
|
|
481
|
+
row_count: number
|
|
482
|
+
byte_size: number
|
|
483
|
+
sha256: string
|
|
484
|
+
schema_version: number
|
|
485
|
+
created_at: string
|
|
486
|
+
};
|
|
487
|
+
type StreamsDumpsManifest = {
|
|
488
|
+
dataset: string
|
|
489
|
+
network: string
|
|
490
|
+
version: string
|
|
491
|
+
schema_version: number
|
|
492
|
+
generated_at: string
|
|
493
|
+
producer_version: string
|
|
494
|
+
finality_lag_blocks: number
|
|
495
|
+
/** Cursor at the end of the finalized bulk coverage — hand to live tailing. */
|
|
496
|
+
latest_finalized_cursor: string | null
|
|
497
|
+
coverage: {
|
|
498
|
+
from_block: number
|
|
499
|
+
to_block: number
|
|
500
|
+
}
|
|
501
|
+
files: StreamsDumpFile[]
|
|
502
|
+
};
|
|
503
|
+
type StreamsDumps = {
|
|
504
|
+
/** Fetch and parse the latest dumps manifest. */
|
|
505
|
+
list(): Promise<StreamsDumpsManifest>
|
|
506
|
+
/** Absolute URL for a manifest file. */
|
|
507
|
+
fileUrl(file: StreamsDumpFile): string
|
|
508
|
+
/** Download a parquet file and verify its sha256 against the manifest. */
|
|
509
|
+
download(file: StreamsDumpFile): Promise<Uint8Array>
|
|
510
|
+
};
|
|
436
511
|
type StreamsClient = {
|
|
437
512
|
events: {
|
|
438
513
|
list(params?: StreamsEventsListParams): Promise<StreamsEventsEnvelope>
|
|
@@ -448,6 +523,14 @@ type StreamsClient = {
|
|
|
448
523
|
*/
|
|
449
524
|
consume(params: StreamsEventsConsumeParams): Promise<StreamsEventsConsumeResult>
|
|
450
525
|
/**
|
|
526
|
+
* Backfill from bulk dumps, then continue live from the dump→live seam in
|
|
527
|
+
* one call. Iterates finalized dump files (via `onDumpFile`) in block
|
|
528
|
+
* order, then tails live from the manifest's `latest_finalized_cursor`
|
|
529
|
+
* (exclusive input → no gap or duplicate at the seam). Requires
|
|
530
|
+
* `dumpsBaseUrl`.
|
|
531
|
+
*/
|
|
532
|
+
replay(params: StreamsEventsReplayParams): Promise<StreamsEventsConsumeResult>
|
|
533
|
+
/**
|
|
451
534
|
* Follow Streams as an async iterator.
|
|
452
535
|
*
|
|
453
536
|
* Use `stream` for live processors and watch-style apps. It tails
|
|
@@ -462,6 +545,8 @@ type StreamsClient = {
|
|
|
462
545
|
reorgs: {
|
|
463
546
|
list(params: StreamsReorgsListParams): Promise<StreamsReorgsListEnvelope>
|
|
464
547
|
}
|
|
548
|
+
/** Bulk parquet dumps. Requires `dumpsBaseUrl` on the client. */
|
|
549
|
+
dumps: StreamsDumps
|
|
465
550
|
canonical(height: number): Promise<StreamsCanonicalBlock>
|
|
466
551
|
tip(): Promise<StreamsTip>
|
|
467
552
|
};
|
|
@@ -523,6 +608,20 @@ type CreateStreamsClientOptions = {
|
|
|
523
608
|
apiKey: string
|
|
524
609
|
baseUrl?: string
|
|
525
610
|
fetchImpl?: FetchLike2
|
|
611
|
+
/**
|
|
612
|
+
* Public base URL for bulk parquet dumps (the R2/CDN bucket root). Required
|
|
613
|
+
* to use `client.dumps`. See `GET /public/streams/dumps/manifest`.
|
|
614
|
+
*/
|
|
615
|
+
dumpsBaseUrl?: string
|
|
616
|
+
/**
|
|
617
|
+
* Verify the ed25519 `X-Signature` on every response (default off). Pass
|
|
618
|
+
* `true` to fetch the server's public key from
|
|
619
|
+
* `/public/streams/signing-key`, or `{ publicKey }` to pin a known PEM. A
|
|
620
|
+
* failed or missing signature throws `StreamsSignatureError`.
|
|
621
|
+
*/
|
|
622
|
+
verify?: boolean | {
|
|
623
|
+
publicKey: string
|
|
624
|
+
}
|
|
526
625
|
};
|
|
527
626
|
declare function createStreamsClient(options: CreateStreamsClientOptions): StreamsClient;
|
|
528
627
|
declare class AuthError extends Error {
|
|
@@ -544,6 +643,10 @@ declare class StreamsServerError extends Error {
|
|
|
544
643
|
readonly body?: unknown;
|
|
545
644
|
constructor(message: string, status: number, body?: unknown);
|
|
546
645
|
}
|
|
646
|
+
/** Thrown when response signature verification is enabled and fails. */
|
|
647
|
+
declare class StreamsSignatureError extends Error {
|
|
648
|
+
constructor(message?: string);
|
|
649
|
+
}
|
|
547
650
|
type FtTransferPayload = {
|
|
548
651
|
asset_identifier: string
|
|
549
652
|
sender: string
|
|
@@ -1054,4 +1157,4 @@ declare function toJsonSafe(value: unknown): unknown;
|
|
|
1054
1157
|
/** Decode a hex-encoded Clarity value to JSON-safe JS (uints as strings,
|
|
1055
1158
|
* buffers as `0x…` hex, tuples as objects). Returns the input hex on failure. */
|
|
1056
1159
|
declare function decodeClarityValue(hex: string): unknown;
|
|
1057
|
-
export { verifyWebhookSignature, 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, Subscriptions, SubscriptionSummary2 as SubscriptionSummary, SubscriptionStatus, SubscriptionRuntime, SubscriptionFormat, SubscriptionDetail2 as SubscriptionDetail, Subgraphs, SubgraphSpecOptions3 as SubgraphSpecOptions, SubgraphSpecFormat2 as SubgraphSpecFormat, SubgraphAgentSchema3 as SubgraphAgentSchema, StreamsTip, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorg, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsClient, StreamsCanonicalBlock, SecondLayerOptions, SecondLayer, RotateSecretResponse2 as RotateSecretResponse, ReplayResult2 as ReplayResult, RateLimitError, Pox4CallsParams, NftTransfersWalkParams, NftTransfersListParams, NftTransfersEnvelope, NftTransferPayload, NftTransferEvent, NftTransfer, IndexTip, IndexEventType, IndexEvent, IndexContractCall, 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, CreateSubscriptionResponse2 as CreateSubscriptionResponse, CreateSubscriptionRequest2 as CreateSubscriptionRequest, ContractCallsWalkParams, ContractCallsListParams, ContractCallsEnvelope, CURSOR_SLUGS, AuthError, ApiError };
|
|
1160
|
+
export { verifyWebhookSignature, 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, Subscriptions, SubscriptionSummary2 as SubscriptionSummary, SubscriptionStatus, SubscriptionRuntime, SubscriptionFormat, SubscriptionDetail2 as SubscriptionDetail, Subgraphs, SubgraphSpecOptions3 as SubgraphSpecOptions, SubgraphSpecFormat2 as SubgraphSpecFormat, SubgraphAgentSchema3 as SubgraphAgentSchema, StreamsTip, StreamsSignatureError, StreamsServerError, StreamsReorgsListParams, StreamsReorgsListEnvelope, StreamsReorg, StreamsEventsStreamParams, StreamsEventsListParams, StreamsEventsListEnvelope, StreamsEventsEnvelope, StreamsEventsConsumeResult, StreamsEventsConsumeParams, StreamsEventType, StreamsEventPayload, StreamsEvent, StreamsDumpsManifest, StreamsDumps, StreamsDumpFile, StreamsClient, StreamsCanonicalBlock, SecondLayerOptions, SecondLayer, RotateSecretResponse2 as RotateSecretResponse, ReplayResult2 as ReplayResult, RateLimitError, Pox4CallsParams, NftTransfersWalkParams, NftTransfersListParams, NftTransfersEnvelope, NftTransferPayload, NftTransferEvent, NftTransfer, IndexTip, IndexEventType, IndexEvent, IndexContractCall, 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, CreateSubscriptionResponse2 as CreateSubscriptionResponse, CreateSubscriptionRequest2 as CreateSubscriptionRequest, ContractCallsWalkParams, ContractCallsListParams, ContractCallsEnvelope, CURSOR_SLUGS, AuthError, ApiError };
|
package/dist/index.js
CHANGED
|
@@ -282,6 +282,31 @@ class Subgraphs extends BaseClient {
|
|
|
282
282
|
filters
|
|
283
283
|
});
|
|
284
284
|
return result.count;
|
|
285
|
+
},
|
|
286
|
+
subscribe(onRow, options = {}) {
|
|
287
|
+
const filters = options.where ? serializeWhere(options.where) : {};
|
|
288
|
+
const qs = new URLSearchParams;
|
|
289
|
+
for (const [k, v] of Object.entries(filters))
|
|
290
|
+
qs.set(k, String(v));
|
|
291
|
+
if (options.since != null)
|
|
292
|
+
qs.set("since", String(options.since));
|
|
293
|
+
const query = qs.toString();
|
|
294
|
+
const url = `${self.baseUrl}/api/subgraphs/${subgraphName}/${tableName}/stream${query ? `?${query}` : ""}`;
|
|
295
|
+
const ES = globalThis.EventSource;
|
|
296
|
+
if (!ES) {
|
|
297
|
+
throw new Error("subscribe() needs a global EventSource (available in browsers and Node >= 22).");
|
|
298
|
+
}
|
|
299
|
+
const es = new ES(url);
|
|
300
|
+
es.onmessage = (ev) => {
|
|
301
|
+
try {
|
|
302
|
+
onRow(JSON.parse(ev.data));
|
|
303
|
+
} catch {}
|
|
304
|
+
};
|
|
305
|
+
if (options.onError) {
|
|
306
|
+
const handler = options.onError;
|
|
307
|
+
es.onerror = (ev) => handler(ev);
|
|
308
|
+
}
|
|
309
|
+
return () => es.close();
|
|
285
310
|
}
|
|
286
311
|
};
|
|
287
312
|
}
|
|
@@ -476,6 +501,9 @@ class Index extends BaseClient {
|
|
|
476
501
|
}
|
|
477
502
|
}
|
|
478
503
|
|
|
504
|
+
// src/streams/client.ts
|
|
505
|
+
import { ed25519 } from "@secondlayer/shared";
|
|
506
|
+
|
|
479
507
|
// src/streams/consumer.ts
|
|
480
508
|
async function defaultSleep(ms, signal) {
|
|
481
509
|
if (signal?.aborted)
|
|
@@ -504,7 +532,10 @@ async function consumeStreamsEvents(opts) {
|
|
|
504
532
|
cursor,
|
|
505
533
|
limit: opts.batchSize,
|
|
506
534
|
types: opts.types,
|
|
507
|
-
contractId: opts.contractId
|
|
535
|
+
contractId: opts.contractId,
|
|
536
|
+
sender: opts.sender,
|
|
537
|
+
recipient: opts.recipient,
|
|
538
|
+
assetIdentifier: opts.assetIdentifier
|
|
508
539
|
});
|
|
509
540
|
pages++;
|
|
510
541
|
const returnedCursor = await opts.onBatch(envelope.events, envelope);
|
|
@@ -539,7 +570,10 @@ async function* streamStreamsEvents(opts) {
|
|
|
539
570
|
cursor,
|
|
540
571
|
limit: opts.batchSize,
|
|
541
572
|
types: opts.types,
|
|
542
|
-
contractId: opts.contractId
|
|
573
|
+
contractId: opts.contractId,
|
|
574
|
+
sender: opts.sender,
|
|
575
|
+
recipient: opts.recipient,
|
|
576
|
+
assetIdentifier: opts.assetIdentifier
|
|
543
577
|
});
|
|
544
578
|
pages++;
|
|
545
579
|
for (const event of envelope.events) {
|
|
@@ -564,6 +598,9 @@ async function* streamStreamsEvents(opts) {
|
|
|
564
598
|
}
|
|
565
599
|
}
|
|
566
600
|
|
|
601
|
+
// src/streams/dumps.ts
|
|
602
|
+
import { createHash } from "node:crypto";
|
|
603
|
+
|
|
567
604
|
// src/streams/errors.ts
|
|
568
605
|
class AuthError extends Error {
|
|
569
606
|
status = 401;
|
|
@@ -605,7 +642,60 @@ class StreamsServerError extends Error {
|
|
|
605
642
|
}
|
|
606
643
|
}
|
|
607
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
|
+
function createStreamsDumps(opts) {
|
|
654
|
+
const baseUrl = opts.baseUrl?.replace(/\/+$/, "");
|
|
655
|
+
function requireBaseUrl() {
|
|
656
|
+
if (!baseUrl) {
|
|
657
|
+
throw new StreamsServerError("Streams dumps require `dumpsBaseUrl` on createStreamsClient.", 0);
|
|
658
|
+
}
|
|
659
|
+
return baseUrl;
|
|
660
|
+
}
|
|
661
|
+
function fileUrl(file) {
|
|
662
|
+
return `${requireBaseUrl()}/${file.path.replace(/^\/+/, "")}`;
|
|
663
|
+
}
|
|
664
|
+
async function list() {
|
|
665
|
+
const url = `${requireBaseUrl()}/manifest/latest.json`;
|
|
666
|
+
const res = await opts.fetchImpl(url);
|
|
667
|
+
if (!res.ok) {
|
|
668
|
+
throw new StreamsServerError(`Could not fetch dumps manifest (${res.status}).`, res.status);
|
|
669
|
+
}
|
|
670
|
+
return await res.json();
|
|
671
|
+
}
|
|
672
|
+
async function download(file) {
|
|
673
|
+
const res = await opts.fetchImpl(fileUrl(file));
|
|
674
|
+
if (!res.ok) {
|
|
675
|
+
throw new StreamsServerError(`Could not download dump ${file.path} (${res.status}).`, res.status);
|
|
676
|
+
}
|
|
677
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
678
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
679
|
+
if (digest !== file.sha256) {
|
|
680
|
+
throw new StreamsSignatureError(`Dump ${file.path} sha256 mismatch (expected ${file.sha256}, got ${digest}).`);
|
|
681
|
+
}
|
|
682
|
+
return bytes;
|
|
683
|
+
}
|
|
684
|
+
return { list, fileUrl, download };
|
|
685
|
+
}
|
|
686
|
+
|
|
608
687
|
// src/streams/client.ts
|
|
688
|
+
function cursorTuple(cursor) {
|
|
689
|
+
if (!cursor)
|
|
690
|
+
return [-1, -1];
|
|
691
|
+
const [block, index] = cursor.split(":");
|
|
692
|
+
return [Number(block), Number(index)];
|
|
693
|
+
}
|
|
694
|
+
function maxCursor(a, b) {
|
|
695
|
+
const [ah, ai] = cursorTuple(a);
|
|
696
|
+
const [bh, bi] = cursorTuple(b);
|
|
697
|
+
return ah > bh || ah === bh && ai >= bi ? a : b;
|
|
698
|
+
}
|
|
609
699
|
var DEFAULT_STREAMS_BASE_URL = "https://api.secondlayer.tools";
|
|
610
700
|
function normalizeBaseUrl(baseUrl) {
|
|
611
701
|
return baseUrl.replace(/\/+$/, "");
|
|
@@ -653,21 +743,68 @@ async function mapStreamsError(response) {
|
|
|
653
743
|
function createStreamsClient(options) {
|
|
654
744
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_STREAMS_BASE_URL);
|
|
655
745
|
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
746
|
+
const verify = options.verify ?? false;
|
|
747
|
+
const dumps = createStreamsDumps({
|
|
748
|
+
baseUrl: options.dumpsBaseUrl,
|
|
749
|
+
fetchImpl
|
|
750
|
+
});
|
|
751
|
+
let publicKeyPromise = null;
|
|
752
|
+
function getPublicKey() {
|
|
753
|
+
if (publicKeyPromise)
|
|
754
|
+
return publicKeyPromise;
|
|
755
|
+
publicKeyPromise = (async () => {
|
|
756
|
+
if (typeof verify === "object") {
|
|
757
|
+
return ed25519.loadEd25519PublicKey(verify.publicKey);
|
|
758
|
+
}
|
|
759
|
+
const res = await fetchImpl(`${baseUrl}/public/streams/signing-key`);
|
|
760
|
+
if (!res.ok) {
|
|
761
|
+
throw new StreamsSignatureError(`Could not fetch signing key (${res.status}).`);
|
|
762
|
+
}
|
|
763
|
+
const body = await res.json();
|
|
764
|
+
if (!body.public_key_pem) {
|
|
765
|
+
throw new StreamsSignatureError("Signing key response missing key.");
|
|
766
|
+
}
|
|
767
|
+
return ed25519.loadEd25519PublicKey(body.public_key_pem);
|
|
768
|
+
})();
|
|
769
|
+
return publicKeyPromise;
|
|
770
|
+
}
|
|
656
771
|
async function request(path) {
|
|
657
772
|
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
658
773
|
headers: { Authorization: `Bearer ${options.apiKey}` }
|
|
659
774
|
});
|
|
660
775
|
if (!response.ok)
|
|
661
776
|
await mapStreamsError(response);
|
|
662
|
-
|
|
777
|
+
const text = await response.text();
|
|
778
|
+
if (verify) {
|
|
779
|
+
const signature = response.headers.get("X-Signature");
|
|
780
|
+
if (!signature) {
|
|
781
|
+
throw new StreamsSignatureError("Response is missing X-Signature.");
|
|
782
|
+
}
|
|
783
|
+
const publicKey = await getPublicKey();
|
|
784
|
+
if (!ed25519.verifyEd25519(text, signature, publicKey)) {
|
|
785
|
+
throw new StreamsSignatureError;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return JSON.parse(text);
|
|
663
789
|
}
|
|
664
790
|
const fetchEvents = async ({
|
|
665
791
|
cursor,
|
|
666
792
|
limit,
|
|
667
793
|
types,
|
|
668
|
-
contractId
|
|
794
|
+
contractId,
|
|
795
|
+
sender,
|
|
796
|
+
recipient,
|
|
797
|
+
assetIdentifier
|
|
669
798
|
}) => {
|
|
670
|
-
return listEvents({
|
|
799
|
+
return listEvents({
|
|
800
|
+
cursor,
|
|
801
|
+
limit,
|
|
802
|
+
types,
|
|
803
|
+
contractId,
|
|
804
|
+
sender,
|
|
805
|
+
recipient,
|
|
806
|
+
assetIdentifier
|
|
807
|
+
});
|
|
671
808
|
};
|
|
672
809
|
async function listEvents(params = {}) {
|
|
673
810
|
const searchParams = new URLSearchParams;
|
|
@@ -676,6 +813,9 @@ function createStreamsClient(options) {
|
|
|
676
813
|
appendSearchParam2(searchParams, "to_height", params.toHeight);
|
|
677
814
|
appendSearchParam2(searchParams, "limit", params.limit);
|
|
678
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);
|
|
679
819
|
if (params.types?.length) {
|
|
680
820
|
searchParams.set("types", params.types.join(","));
|
|
681
821
|
}
|
|
@@ -694,6 +834,9 @@ function createStreamsClient(options) {
|
|
|
694
834
|
mode: params.mode,
|
|
695
835
|
types: params.types,
|
|
696
836
|
contractId: params.contractId,
|
|
837
|
+
sender: params.sender,
|
|
838
|
+
recipient: params.recipient,
|
|
839
|
+
assetIdentifier: params.assetIdentifier,
|
|
697
840
|
batchSize: params.batchSize ?? 100,
|
|
698
841
|
fetchEvents,
|
|
699
842
|
onBatch: params.onBatch,
|
|
@@ -708,6 +851,9 @@ function createStreamsClient(options) {
|
|
|
708
851
|
fromCursor: params.fromCursor,
|
|
709
852
|
types: params.types,
|
|
710
853
|
contractId: params.contractId,
|
|
854
|
+
sender: params.sender,
|
|
855
|
+
recipient: params.recipient,
|
|
856
|
+
assetIdentifier: params.assetIdentifier,
|
|
711
857
|
batchSize: params.batchSize ?? 100,
|
|
712
858
|
emptyBackoffMs: params.emptyBackoffMs,
|
|
713
859
|
maxPages: params.maxPages,
|
|
@@ -715,6 +861,29 @@ function createStreamsClient(options) {
|
|
|
715
861
|
signal: params.signal,
|
|
716
862
|
fetchEvents
|
|
717
863
|
});
|
|
864
|
+
},
|
|
865
|
+
async replay(params) {
|
|
866
|
+
const fromCursor = params.from === "genesis" ? null : params.from ?? null;
|
|
867
|
+
const fromBlock = fromCursor ? cursorTuple(fromCursor)[0] : 0;
|
|
868
|
+
const manifest = await dumps.list();
|
|
869
|
+
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);
|
|
870
|
+
for (const file of files) {
|
|
871
|
+
if (params.signal?.aborted)
|
|
872
|
+
break;
|
|
873
|
+
await params.onDumpFile(file);
|
|
874
|
+
}
|
|
875
|
+
const seam = maxCursor(fromCursor, manifest.latest_finalized_cursor);
|
|
876
|
+
return consumeStreamsEvents({
|
|
877
|
+
fromCursor: seam,
|
|
878
|
+
mode: params.mode ?? "tail",
|
|
879
|
+
batchSize: params.batchSize ?? 100,
|
|
880
|
+
fetchEvents,
|
|
881
|
+
onBatch: params.onBatch,
|
|
882
|
+
emptyBackoffMs: params.emptyBackoffMs,
|
|
883
|
+
maxPages: params.maxPages,
|
|
884
|
+
maxEmptyPolls: params.maxEmptyPolls,
|
|
885
|
+
signal: params.signal
|
|
886
|
+
});
|
|
718
887
|
}
|
|
719
888
|
},
|
|
720
889
|
blocks: {
|
|
@@ -731,6 +900,7 @@ function createStreamsClient(options) {
|
|
|
731
900
|
return request(`/v1/streams/reorgs${query ? `?${query}` : ""}`);
|
|
732
901
|
}
|
|
733
902
|
},
|
|
903
|
+
dumps,
|
|
734
904
|
canonical(height) {
|
|
735
905
|
return request(`/v1/streams/canonical/${height}`);
|
|
736
906
|
},
|
|
@@ -1377,6 +1547,7 @@ export {
|
|
|
1377
1547
|
ValidationError,
|
|
1378
1548
|
Subscriptions,
|
|
1379
1549
|
Subgraphs,
|
|
1550
|
+
StreamsSignatureError,
|
|
1380
1551
|
StreamsServerError,
|
|
1381
1552
|
SecondLayer,
|
|
1382
1553
|
RateLimitError,
|
|
@@ -1387,5 +1558,5 @@ export {
|
|
|
1387
1558
|
ApiError
|
|
1388
1559
|
};
|
|
1389
1560
|
|
|
1390
|
-
//# debugId=
|
|
1561
|
+
//# debugId=54289A1292072EB864756E2164756E21
|
|
1391
1562
|
//# sourceMappingURL=index.js.map
|