@secondlayer/sdk 6.13.0 → 6.15.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 +87 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +39 -1
- package/dist/index.js.map +6 -6
- package/dist/streams/index.d.ts +8 -0
- package/dist/streams/index.js.map +1 -1
- package/dist/subgraphs/index.d.ts +14 -2
- package/dist/subgraphs/index.js +39 -1
- package/dist/subgraphs/index.js.map +4 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -128,6 +128,31 @@ for await (const event of streams.events.stream({
|
|
|
128
128
|
}
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
Real-time push (`events.subscribe`).
|
|
132
|
+
|
|
133
|
+
Use `client.events.subscribe` for callback-style live delivery — it pushes each
|
|
134
|
+
event to `onEvent` as it lands. It's fetch-based (so it carries the Bearer key)
|
|
135
|
+
and works in browsers and Node 18+. It auto-reconnects from the last delivered
|
|
136
|
+
cursor on a dropped connection, and returns an unsubscribe function.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const unsubscribe = streams.events.subscribe({
|
|
140
|
+
types: ["ft_transfer"], // notTypes / contractId / sender / recipient / assetIdentifier also filter
|
|
141
|
+
// fromCursor: lastCursor, // resume strictly after this cursor; omit to tail from the tip
|
|
142
|
+
onEvent: async (event) => {
|
|
143
|
+
console.log(event.cursor, event.tx_id);
|
|
144
|
+
},
|
|
145
|
+
onError: (err) => console.error("reconnecting…", err),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// later
|
|
149
|
+
unsubscribe(); // or pass `signal` and abort it
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Each pushed frame is `{ event, sig, key_id }`. When the client was created with
|
|
153
|
+
`verify` (or `{ publicKey }`), the per-frame ed25519 signature is checked before
|
|
154
|
+
`onEvent` runs; a bad/missing signature throws `StreamsSignatureError`.
|
|
155
|
+
|
|
131
156
|
Bulk parquet dumps.
|
|
132
157
|
|
|
133
158
|
Finalized history is published as public parquet files. Set `dumpsBaseUrl`
|
|
@@ -135,6 +160,12 @@ Finalized history is published as public parquet files. Set `dumpsBaseUrl`
|
|
|
135
160
|
decode parquet; `download` hands you sha256-verified bytes to process with your
|
|
136
161
|
own tooling.
|
|
137
162
|
|
|
163
|
+
The bulk **manifest is ed25519-signed**, and the SDK verifies that signature
|
|
164
|
+
before it trusts any per-file sha256 listed in it. The `verifyDumpsManifest`
|
|
165
|
+
option **defaults to `true`** — `dumps.list()` and `events.replay()` enforce it,
|
|
166
|
+
so you don't trust the file hashes unless the manifest itself verifies. Opt out
|
|
167
|
+
with `verifyDumpsManifest: false`.
|
|
168
|
+
|
|
138
169
|
```typescript
|
|
139
170
|
const streams = createStreamsClient({
|
|
140
171
|
apiKey: process.env.SL_API_KEY!,
|
|
@@ -317,6 +348,30 @@ const gaps = await sl.subgraphs.gaps("my-subgraph");
|
|
|
317
348
|
const result = await sl.subgraphs.deploy({ name, sources, schema, handlerCode });
|
|
318
349
|
```
|
|
319
350
|
|
|
351
|
+
Stream rows live with the typed client — each table exposes `subscribe`
|
|
352
|
+
alongside `findMany`/`count`:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
const subgraph = sl.subgraphs.typed(myDefinition); // { transfers, ... }
|
|
356
|
+
|
|
357
|
+
const unsubscribe = subgraph.transfers.subscribe(
|
|
358
|
+
(row) => console.log(row),
|
|
359
|
+
{
|
|
360
|
+
where: { amount: { gte: "1000000" } }, // optional row filter
|
|
361
|
+
since: 180000, // optional: replay from this block_height, then tail
|
|
362
|
+
onError: (err) => console.error(err),
|
|
363
|
+
},
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// later
|
|
367
|
+
unsubscribe();
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`subscribe` is an SSE stream over the global `EventSource` (available in
|
|
371
|
+
browsers and Node ≥ 22; it throws if no `EventSource` is present). Frames are
|
|
372
|
+
unsigned rows. `since: <block_height>` replays matching rows from that height,
|
|
373
|
+
then tails the live edge; omit it to tail only.
|
|
374
|
+
|
|
320
375
|
## Subscriptions
|
|
321
376
|
|
|
322
377
|
Signed HTTP webhooks. Subscriptions are polymorphic — pick one kind:
|
|
@@ -396,6 +451,38 @@ const { data: dead } = await sl.subscriptions.dead(id);
|
|
|
396
451
|
await sl.subscriptions.requeueDead(id, outboxId);
|
|
397
452
|
```
|
|
398
453
|
|
|
454
|
+
### Verifying deliveries
|
|
455
|
+
|
|
456
|
+
Every delivery — any kind, any `format` — also carries a universal authenticity
|
|
457
|
+
signature you can verify with one published key, no per-subscription secret. The
|
|
458
|
+
headers are `webhook-id`, `x-secondlayer-signature`, and
|
|
459
|
+
`x-secondlayer-signature-keyid`; the signed content is `` `${webhook-id}.${rawBody}` ``
|
|
460
|
+
(ed25519). Fetch the public key from `GET /public/streams/signing-key`.
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { verifySecondlayerSignature } from "@secondlayer/sdk";
|
|
464
|
+
|
|
465
|
+
app.post("/webhook", async (c) => {
|
|
466
|
+
const raw = await c.req.text(); // raw body — never re-stringify the parsed JSON
|
|
467
|
+
if (!verifySecondlayerSignature(raw, c.req.raw.headers, SECONDLAYER_PUBLIC_KEY)) {
|
|
468
|
+
return c.text("Invalid signature", 401);
|
|
469
|
+
}
|
|
470
|
+
// ... trusted ...
|
|
471
|
+
return c.body(null, 204);
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
verifySecondlayerSignature(
|
|
477
|
+
rawBody: string,
|
|
478
|
+
headers: WebhookHeaderInput, // plain object, Fetch `Headers`, or a lookup fn
|
|
479
|
+
publicKeyPem: string,
|
|
480
|
+
): boolean;
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Prefer the per-subscription HMAC (Standard Webhooks) secret instead? Use
|
|
484
|
+
`verifyWebhookSignature(rawBody, headers, secret)` — raw body first.
|
|
485
|
+
|
|
399
486
|
## Error Handling
|
|
400
487
|
|
|
401
488
|
```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReindexResponse, SubgraphDetail, SubgraphGapsResponse, SubgraphQueryParams, SubgraphSummary } from "@secondlayer/shared/schemas";
|
|
1
|
+
import { ReindexResponse, SubgraphAggregateParams, SubgraphAggregateResponse, SubgraphDetail, SubgraphGapsResponse, SubgraphQueryParams, SubgraphSummary } from "@secondlayer/shared/schemas";
|
|
2
2
|
import { DeploySubgraphRequest, DeploySubgraphResponse } from "@secondlayer/shared/schemas/subgraphs";
|
|
3
3
|
import { SubgraphAgentSchema, SubgraphSpecOptions } from "@secondlayer/shared/subgraphs/spec";
|
|
4
4
|
import { InferSubgraphClient } from "@secondlayer/subgraphs";
|
|
@@ -110,6 +110,7 @@ declare class Subgraphs extends BaseClient {
|
|
|
110
110
|
queryTableCount(name: string, table: string, params?: SubgraphQueryParams): Promise<{
|
|
111
111
|
count: number
|
|
112
112
|
}>;
|
|
113
|
+
queryTableAggregate(name: string, table: string, params?: SubgraphAggregateParams): Promise<SubgraphAggregateResponse>;
|
|
113
114
|
/**
|
|
114
115
|
* Returns a typed client for a subgraph defined with `defineSubgraph()`.
|
|
115
116
|
* Row types are inferred from the subgraph's schema literal types.
|
|
@@ -1014,6 +1015,14 @@ type StreamsTip = {
|
|
|
1014
1015
|
*/
|
|
1015
1016
|
finalized_height?: number
|
|
1016
1017
|
lag_seconds: number
|
|
1018
|
+
/**
|
|
1019
|
+
* Oldest height still seekable on the live API for the caller's tier
|
|
1020
|
+
* (`tip - retention`). `null` = unlimited retention. Older reads must use the
|
|
1021
|
+
* cold dumps lane. Optional for back-compat.
|
|
1022
|
+
*/
|
|
1023
|
+
oldest_seekable_height?: number | null
|
|
1024
|
+
/** Oldest seekable cursor (`<oldest_seekable_height>:0`); `null` = unlimited. */
|
|
1025
|
+
oldest_cursor?: string | null
|
|
1017
1026
|
};
|
|
1018
1027
|
type StreamsCanonicalBlock = {
|
|
1019
1028
|
block_height: number
|
|
@@ -1274,7 +1283,7 @@ type StreamsUsage = {
|
|
|
1274
1283
|
events_this_month: number
|
|
1275
1284
|
}
|
|
1276
1285
|
};
|
|
1277
|
-
import { CreateSubscriptionRequest, CreateSubscriptionResponse, DeadRow, DeliveryRow, ReplayResult, RotateSecretResponse, SubscriptionDetail, SubscriptionSummary, UpdateSubscriptionRequest } from "@secondlayer/shared/schemas/subscriptions";
|
|
1286
|
+
import { CreateSubscriptionRequest, CreateSubscriptionResponse, DeadRow, DeliveryRow, ReplayResult, RotateSecretResponse, SubscriptionDetail, SubscriptionSummary, SubscriptionTestResult, UpdateSubscriptionRequest } from "@secondlayer/shared/schemas/subscriptions";
|
|
1278
1287
|
import { ChainTrigger, ChainTriggerType, CreateSubscriptionRequest as CreateSubscriptionRequest2, CreateSubscriptionResponse as CreateSubscriptionResponse2, DeadRow as DeadRow2, DeliveryRow as DeliveryRow2, ReplayResult as ReplayResult2, RotateSecretResponse as RotateSecretResponse2, SubscriptionDetail as SubscriptionDetail2, SubscriptionFormat, SubscriptionKind, SubscriptionRuntime, SubscriptionStatus, SubscriptionSummary as SubscriptionSummary2, UpdateSubscriptionRequest as UpdateSubscriptionRequest2 } from "@secondlayer/shared/schemas/subscriptions";
|
|
1279
1288
|
import { trigger } from "@secondlayer/shared/schemas/subscriptions";
|
|
1280
1289
|
declare class Subscriptions extends BaseClient {
|
|
@@ -1290,6 +1299,9 @@ declare class Subscriptions extends BaseClient {
|
|
|
1290
1299
|
ok: true
|
|
1291
1300
|
}>;
|
|
1292
1301
|
rotateSecret(id: string): Promise<RotateSecretResponse>;
|
|
1302
|
+
/** Send a one-off test webhook to the subscription's URL (built for its
|
|
1303
|
+
* format, SSRF-guarded). Logged as a delivery row, visible via recentDeliveries. */
|
|
1304
|
+
test(id: string): Promise<SubscriptionTestResult>;
|
|
1293
1305
|
recentDeliveries(id: string): Promise<{
|
|
1294
1306
|
data: DeliveryRow[]
|
|
1295
1307
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,26 @@ function buildSubgraphQueryString(params) {
|
|
|
194
194
|
const str = qs.toString();
|
|
195
195
|
return str ? `?${str}` : "";
|
|
196
196
|
}
|
|
197
|
+
function buildAggregateQueryString(params) {
|
|
198
|
+
const qs = new URLSearchParams;
|
|
199
|
+
if (params.filters) {
|
|
200
|
+
for (const [key, value] of Object.entries(params.filters)) {
|
|
201
|
+
qs.set(key, String(value));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (params.count)
|
|
205
|
+
qs.set("_count", "true");
|
|
206
|
+
if (params.countDistinct?.length)
|
|
207
|
+
qs.set("_countDistinct", params.countDistinct.join(","));
|
|
208
|
+
if (params.sum?.length)
|
|
209
|
+
qs.set("_sum", params.sum.join(","));
|
|
210
|
+
if (params.min?.length)
|
|
211
|
+
qs.set("_min", params.min.join(","));
|
|
212
|
+
if (params.max?.length)
|
|
213
|
+
qs.set("_max", params.max.join(","));
|
|
214
|
+
const str = qs.toString();
|
|
215
|
+
return str ? `?${str}` : "";
|
|
216
|
+
}
|
|
197
217
|
function buildSpecQueryString(options) {
|
|
198
218
|
const qs = new URLSearchParams;
|
|
199
219
|
if (options?.serverUrl)
|
|
@@ -264,6 +284,9 @@ class Subgraphs extends BaseClient {
|
|
|
264
284
|
async queryTableCount(name, table, params = {}) {
|
|
265
285
|
return this.request("GET", `/api/subgraphs/${name}/${table}/count${buildSubgraphQueryString(params)}`);
|
|
266
286
|
}
|
|
287
|
+
async queryTableAggregate(name, table, params = {}) {
|
|
288
|
+
return this.request("GET", `/api/subgraphs/${name}/${table}/aggregate${buildAggregateQueryString(params)}`);
|
|
289
|
+
}
|
|
267
290
|
typed(def) {
|
|
268
291
|
const result = {};
|
|
269
292
|
for (const tableName of Object.keys(def.schema)) {
|
|
@@ -302,6 +325,18 @@ class Subgraphs extends BaseClient {
|
|
|
302
325
|
});
|
|
303
326
|
return result.count;
|
|
304
327
|
},
|
|
328
|
+
async aggregate(spec) {
|
|
329
|
+
const filters = spec.where ? serializeWhere(spec.where) : undefined;
|
|
330
|
+
const result = await self.queryTableAggregate(subgraphName, tableName, {
|
|
331
|
+
filters,
|
|
332
|
+
count: spec.count,
|
|
333
|
+
countDistinct: spec.countDistinct,
|
|
334
|
+
sum: spec.sum,
|
|
335
|
+
min: spec.min,
|
|
336
|
+
max: spec.max
|
|
337
|
+
});
|
|
338
|
+
return result;
|
|
339
|
+
},
|
|
305
340
|
subscribe(onRow, options = {}) {
|
|
306
341
|
const filters = options.where ? serializeWhere(options.where) : {};
|
|
307
342
|
const qs = new URLSearchParams;
|
|
@@ -1542,6 +1577,9 @@ class Subscriptions extends BaseClient {
|
|
|
1542
1577
|
async rotateSecret(id) {
|
|
1543
1578
|
return this.request("POST", `/api/subscriptions/${id}/rotate-secret`);
|
|
1544
1579
|
}
|
|
1580
|
+
async test(id) {
|
|
1581
|
+
return this.request("POST", `/api/subscriptions/${id}/test`);
|
|
1582
|
+
}
|
|
1545
1583
|
async recentDeliveries(id) {
|
|
1546
1584
|
return this.request("GET", `/api/subscriptions/${id}/deliveries`);
|
|
1547
1585
|
}
|
|
@@ -2090,5 +2128,5 @@ export {
|
|
|
2090
2128
|
ApiError
|
|
2091
2129
|
};
|
|
2092
2130
|
|
|
2093
|
-
//# debugId=
|
|
2131
|
+
//# debugId=4980F60DADD3DFA064756E2164756E21
|
|
2094
2132
|
//# sourceMappingURL=index.js.map
|