@secondlayer/sdk 6.12.0 → 6.14.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 +156 -0
- package/dist/index.d.ts +93 -2
- package/dist/index.js +81 -1
- package/dist/index.js.map +7 -6
- package/dist/streams/index.d.ts +8 -0
- package/dist/streams/index.js.map +1 -1
- package/dist/subgraphs/index.d.ts +12 -1
- package/dist/subgraphs/index.js +4 -1
- package/dist/subgraphs/index.js.map +3 -3
- 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!,
|
|
@@ -215,6 +246,75 @@ for await (const transfer of sl.index.ftTransfers.walk({
|
|
|
215
246
|
}
|
|
216
247
|
```
|
|
217
248
|
|
|
249
|
+
## Transaction-inclusion proofs
|
|
250
|
+
|
|
251
|
+
Verify — **without trusting Second Layer** — that a transaction is included in a
|
|
252
|
+
Stacks (Nakamoto) block, and that ≥70% of the reward cycle's signer weight
|
|
253
|
+
attested to that block. `verifyTransactionProof` recomputes everything
|
|
254
|
+
client-side and trusts nothing the API returned.
|
|
255
|
+
|
|
256
|
+
> Verification uses Node's crypto via `@secondlayer/shared` — Node/server-side use.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { verifyTransactionProof, fetchRewardSet } from "@secondlayer/sdk";
|
|
260
|
+
|
|
261
|
+
const proof = await fetch(
|
|
262
|
+
`https://api.secondlayer.tools/v1/index/transactions/${txid}/proof`,
|
|
263
|
+
).then((r) => r.json());
|
|
264
|
+
|
|
265
|
+
const result = verifyTransactionProof(proof); // anchored + consensus (embedded set)
|
|
266
|
+
// result.ok, result.level === "consensus", result.signerWeightBps
|
|
267
|
+
|
|
268
|
+
// Fully trustless — resolve the reward set from your own node:
|
|
269
|
+
const rewardSet = await fetchRewardSet({
|
|
270
|
+
nodeUrl: "https://your-stacks-node:20443",
|
|
271
|
+
cycle: proof.consensus.reward_cycle,
|
|
272
|
+
});
|
|
273
|
+
const trustless = verifyTransactionProof(proof, { rewardSet }); // rewardSetSource: "provided"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Two trust levels:
|
|
277
|
+
|
|
278
|
+
- **Anchored** — recompute the txid from `raw_tx`, fold `tx_merkle_path` up to the
|
|
279
|
+
header's `tx_merkle_root`, and recompute `block_hash` + `index_block_hash` from
|
|
280
|
+
`raw_header`. The tx is in a header any node can corroborate.
|
|
281
|
+
- **Consensus** — additionally recover the header's signer signatures and confirm
|
|
282
|
+
≥70% of the reward cycle's signer weight signed the block. Fully trustless when
|
|
283
|
+
you pass a `rewardSet` resolved yourself via `fetchRewardSet`
|
|
284
|
+
(`rewardSetSource: "provided"`); otherwise it uses the proof's embedded set
|
|
285
|
+
(`rewardSetSource: "embedded"`).
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
verifyTransactionProof(
|
|
289
|
+
proof: TransactionProof,
|
|
290
|
+
opts?: { rewardSet?: RewardSet },
|
|
291
|
+
): TransactionProofVerifyResult;
|
|
292
|
+
|
|
293
|
+
fetchRewardSet(opts: {
|
|
294
|
+
nodeUrl: string; // your own stacks-node
|
|
295
|
+
cycle: number; // reward cycle — proof.consensus.reward_cycle
|
|
296
|
+
fetchImpl?: typeof fetch;
|
|
297
|
+
}): Promise<RewardSet | null>; // reads /v3/stacker_set/{cycle}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`verifyTransactionProof` returns a `TransactionProofVerifyResult`:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
{
|
|
304
|
+
level: "anchored" | "consensus";
|
|
305
|
+
txidMatches: boolean;
|
|
306
|
+
includedInHeader: boolean;
|
|
307
|
+
headerSelfConsistent: boolean;
|
|
308
|
+
signerWeightBps?: number; // consensus only
|
|
309
|
+
thresholdMet?: boolean; // consensus only — ≥70% (7000 bps)
|
|
310
|
+
rewardSetSource?: "provided" | "embedded";
|
|
311
|
+
ok: boolean;
|
|
312
|
+
errors: string[];
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Exported types: `TransactionProof`, `TransactionProofVerifyResult`, `RewardSet`.
|
|
317
|
+
|
|
218
318
|
## Stacks Subgraphs
|
|
219
319
|
|
|
220
320
|
Deploy and query app-specific L3 tables.
|
|
@@ -248,6 +348,30 @@ const gaps = await sl.subgraphs.gaps("my-subgraph");
|
|
|
248
348
|
const result = await sl.subgraphs.deploy({ name, sources, schema, handlerCode });
|
|
249
349
|
```
|
|
250
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
|
+
|
|
251
375
|
## Subscriptions
|
|
252
376
|
|
|
253
377
|
Signed HTTP webhooks. Subscriptions are polymorphic — pick one kind:
|
|
@@ -327,6 +451,38 @@ const { data: dead } = await sl.subscriptions.dead(id);
|
|
|
327
451
|
await sl.subscriptions.requeueDead(id, outboxId);
|
|
328
452
|
```
|
|
329
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
|
+
|
|
330
486
|
## Error Handling
|
|
331
487
|
|
|
332
488
|
```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -1014,6 +1014,14 @@ type StreamsTip = {
|
|
|
1014
1014
|
*/
|
|
1015
1015
|
finalized_height?: number
|
|
1016
1016
|
lag_seconds: number
|
|
1017
|
+
/**
|
|
1018
|
+
* Oldest height still seekable on the live API for the caller's tier
|
|
1019
|
+
* (`tip - retention`). `null` = unlimited retention. Older reads must use the
|
|
1020
|
+
* cold dumps lane. Optional for back-compat.
|
|
1021
|
+
*/
|
|
1022
|
+
oldest_seekable_height?: number | null
|
|
1023
|
+
/** Oldest seekable cursor (`<oldest_seekable_height>:0`); `null` = unlimited. */
|
|
1024
|
+
oldest_cursor?: string | null
|
|
1017
1025
|
};
|
|
1018
1026
|
type StreamsCanonicalBlock = {
|
|
1019
1027
|
block_height: number
|
|
@@ -1274,7 +1282,7 @@ type StreamsUsage = {
|
|
|
1274
1282
|
events_this_month: number
|
|
1275
1283
|
}
|
|
1276
1284
|
};
|
|
1277
|
-
import { CreateSubscriptionRequest, CreateSubscriptionResponse, DeadRow, DeliveryRow, ReplayResult, RotateSecretResponse, SubscriptionDetail, SubscriptionSummary, UpdateSubscriptionRequest } from "@secondlayer/shared/schemas/subscriptions";
|
|
1285
|
+
import { CreateSubscriptionRequest, CreateSubscriptionResponse, DeadRow, DeliveryRow, ReplayResult, RotateSecretResponse, SubscriptionDetail, SubscriptionSummary, SubscriptionTestResult, UpdateSubscriptionRequest } from "@secondlayer/shared/schemas/subscriptions";
|
|
1278
1286
|
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
1287
|
import { trigger } from "@secondlayer/shared/schemas/subscriptions";
|
|
1280
1288
|
declare class Subscriptions extends BaseClient {
|
|
@@ -1290,6 +1298,9 @@ declare class Subscriptions extends BaseClient {
|
|
|
1290
1298
|
ok: true
|
|
1291
1299
|
}>;
|
|
1292
1300
|
rotateSecret(id: string): Promise<RotateSecretResponse>;
|
|
1301
|
+
/** Send a one-off test webhook to the subscription's URL (built for its
|
|
1302
|
+
* format, SSRF-guarded). Logged as a delivery row, visible via recentDeliveries. */
|
|
1303
|
+
test(id: string): Promise<SubscriptionTestResult>;
|
|
1293
1304
|
recentDeliveries(id: string): Promise<{
|
|
1294
1305
|
data: DeliveryRow[]
|
|
1295
1306
|
}>;
|
|
@@ -1828,10 +1839,90 @@ declare function verifyWebhookSignature(rawBody: string, headers: WebhookHeaderI
|
|
|
1828
1839
|
* ```
|
|
1829
1840
|
*/
|
|
1830
1841
|
declare function verifySecondlayerSignature(rawBody: string, headers: WebhookHeaderInput, publicKeyPem: string): boolean;
|
|
1842
|
+
import { RewardSet } from "@secondlayer/shared/node/consensus";
|
|
1843
|
+
import { MerkleProofStep } from "@secondlayer/shared/node/nakamoto";
|
|
1844
|
+
/**
|
|
1845
|
+
* Trustless transaction-inclusion proof verification.
|
|
1846
|
+
*
|
|
1847
|
+
* Given a proof from `GET /v1/index/transactions/:txid/proof`, the consumer
|
|
1848
|
+
* re-derives everything itself — it does NOT trust any value Secondlayer
|
|
1849
|
+
* computed. Anchored level: (1) recompute the txid from the raw tx bytes, (2)
|
|
1850
|
+
* fold it up the merkle path to the header's `tx_merkle_root`, (3) recompute the
|
|
1851
|
+
* header's `block_hash` and `index_block_hash` from the raw header — "this tx is
|
|
1852
|
+
* included in a header any node can corroborate". Consensus level (when the proof
|
|
1853
|
+
* carries a `consensus` field, or a `rewardSet` is passed): additionally recover
|
|
1854
|
+
* the header's signer signatures and confirm ≥70% of reward-set signer weight
|
|
1855
|
+
* signed the block.
|
|
1856
|
+
*
|
|
1857
|
+
* Note: uses Node's crypto via `@secondlayer/shared` (same as the Streams
|
|
1858
|
+
* signature verify); intended for Node/server verification.
|
|
1859
|
+
*/
|
|
1860
|
+
interface TransactionProof {
|
|
1861
|
+
txid: string;
|
|
1862
|
+
index_block_hash: string;
|
|
1863
|
+
block_height: number;
|
|
1864
|
+
tx_index: number;
|
|
1865
|
+
/** Raw consensus-serialized transaction bytes (hex). */
|
|
1866
|
+
raw_tx: string;
|
|
1867
|
+
/** Raw Nakamoto block-header bytes (hex) — parsed + re-hashed by the verifier. */
|
|
1868
|
+
raw_header: string;
|
|
1869
|
+
/** Authentication path from the tx leaf to `tx_merkle_root`. */
|
|
1870
|
+
tx_merkle_path: MerkleProofStep[];
|
|
1871
|
+
/** Present when consensus-level verification is available: the reward cycle and
|
|
1872
|
+
* its signer set, against which the header's signer signatures are checked. */
|
|
1873
|
+
consensus?: {
|
|
1874
|
+
reward_cycle: number
|
|
1875
|
+
reward_set: RewardSet
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
interface TransactionProofVerifyResult {
|
|
1879
|
+
/** Highest level actually verified. "consensus" requires the proof's
|
|
1880
|
+
* `consensus` field and a met signer-weight threshold. */
|
|
1881
|
+
level: "anchored" | "consensus";
|
|
1882
|
+
/** Recomputed txid === proof.txid. */
|
|
1883
|
+
txidMatches: boolean;
|
|
1884
|
+
/** Merkle path folds the txid to the header's tx_merkle_root. */
|
|
1885
|
+
includedInHeader: boolean;
|
|
1886
|
+
/** Recomputed block_hash + index_block_hash match the header / proof. */
|
|
1887
|
+
headerSelfConsistent: boolean;
|
|
1888
|
+
/** Basis points (0–10000) of reward-set signer weight that signed the block.
|
|
1889
|
+
* Only set when the proof carries a `consensus` field. */
|
|
1890
|
+
signerWeightBps?: number;
|
|
1891
|
+
/** ≥70% of signer weight signed. Only set with a `consensus` field. */
|
|
1892
|
+
thresholdMet?: boolean;
|
|
1893
|
+
/** Which reward set the signer check used: "provided" (caller-resolved →
|
|
1894
|
+
* fully trustless) or "embedded" (the one Secondlayer put in the proof). */
|
|
1895
|
+
rewardSetSource?: "provided" | "embedded";
|
|
1896
|
+
/** All applicable checks passed (incl. the threshold when consensus is present). */
|
|
1897
|
+
ok: boolean;
|
|
1898
|
+
errors: string[];
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Resolve a reward set directly from a stacks-node (`/v3/stacker_set/{cycle}`),
|
|
1902
|
+
* so a caller can verify the consensus layer against a node IT trusts rather than
|
|
1903
|
+
* the reward set Secondlayer embedded in the proof. Pass the result as
|
|
1904
|
+
* `verifyTransactionProof(proof, { rewardSet })`.
|
|
1905
|
+
*/
|
|
1906
|
+
declare function fetchRewardSet(opts: {
|
|
1907
|
+
nodeUrl: string
|
|
1908
|
+
cycle: number
|
|
1909
|
+
fetchImpl?: typeof fetch
|
|
1910
|
+
}): Promise<RewardSet | null>;
|
|
1911
|
+
/**
|
|
1912
|
+
* Verify a transaction-inclusion proof. Every check is recomputed client-side,
|
|
1913
|
+
* so a `true` result does not rely on trusting Secondlayer. Pass
|
|
1914
|
+
* `{ rewardSet }` (resolved via {@link fetchRewardSet} from your own node) to
|
|
1915
|
+
* verify the consensus layer against a reward set you trust rather than the one
|
|
1916
|
+
* embedded in the proof.
|
|
1917
|
+
*/
|
|
1918
|
+
declare function verifyTransactionProof(proof: TransactionProof, opts?: {
|
|
1919
|
+
rewardSet?: RewardSet
|
|
1920
|
+
}): TransactionProofVerifyResult;
|
|
1921
|
+
import { RewardSet as RewardSet2 } from "@secondlayer/shared/node/consensus";
|
|
1831
1922
|
/** Make a cvToValue result JSON-serializable: Clarity (u)ints decode to bigint,
|
|
1832
1923
|
* which JSON.stringify can't handle — convert recursively to strings. */
|
|
1833
1924
|
declare function toJsonSafe(value: unknown): unknown;
|
|
1834
1925
|
/** Decode a hex-encoded Clarity value to JSON-safe JS (uints as strings,
|
|
1835
1926
|
* buffers as `0x…` hex, tuples as objects). Returns the input hex on failure. */
|
|
1836
1927
|
declare function decodeClarityValue(hex: string): unknown;
|
|
1837
|
-
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, IndexReorg, 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 };
|
|
1928
|
+
export { verifyWebhookSignature, verifyTransactionProof, verifySecondlayerSignature, trigger, toJsonSafe, isStxTransfer, isStxMint, isStxLock, isStxBurn, isPrint, isNftTransfer, isNftMint, isNftBurn, isFtTransfer, isFtMint, isFtBurn, getSubgraph, fetchRewardSet, decodeStxTransfer, decodeStxMint, decodeStxLock, decodeStxBurn, decodePrint, decodeNftTransfer, decodeNftMint, decodeNftBurn, decodeFtTransfer, decodeFtMint, decodeFtBurn, decodeClarityValue, createStreamsClient, VersionConflictError, ValidationError, UpdateSubscriptionRequest2 as UpdateSubscriptionRequest, TransactionsWalkParams, TransactionsListParams, TransactionsEnvelope, TransactionProofVerifyResult, TransactionProof, 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, RewardSet2 as RewardSet, ReplayResult2 as ReplayResult, RateLimitError, Pox4CallsParams, NftTransfersWalkParams, NftTransfersListParams, NftTransfersEnvelope, NftTransferPayload, NftTransferEvent, NftTransfer, MempoolWalkParams, MempoolTransactionEnvelope, MempoolListParams, MempoolEnvelope, IndexUsage, IndexTransaction, IndexTip, IndexStackingAction, IndexReorg, 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
|
@@ -1542,6 +1542,9 @@ class Subscriptions extends BaseClient {
|
|
|
1542
1542
|
async rotateSecret(id) {
|
|
1543
1543
|
return this.request("POST", `/api/subscriptions/${id}/rotate-secret`);
|
|
1544
1544
|
}
|
|
1545
|
+
async test(id) {
|
|
1546
|
+
return this.request("POST", `/api/subscriptions/${id}/test`);
|
|
1547
|
+
}
|
|
1545
1548
|
async recentDeliveries(id) {
|
|
1546
1549
|
return this.request("GET", `/api/subscriptions/${id}/deliveries`);
|
|
1547
1550
|
}
|
|
@@ -1965,8 +1968,84 @@ function verifySecondlayerSignature(rawBody, headers, publicKeyPem) {
|
|
|
1965
1968
|
const signature = pickHeader(headers, "x-secondlayer-signature");
|
|
1966
1969
|
return verifySecondlayerSignatureValues(rawBody, id, signature, publicKeyPem);
|
|
1967
1970
|
}
|
|
1971
|
+
// src/proofs.ts
|
|
1972
|
+
import {
|
|
1973
|
+
verifySignerSignatures
|
|
1974
|
+
} from "@secondlayer/shared/node/consensus";
|
|
1975
|
+
import {
|
|
1976
|
+
nakamotoBlockHash,
|
|
1977
|
+
nakamotoBlockId,
|
|
1978
|
+
parseNakamotoBlockHeader,
|
|
1979
|
+
stacksTxid,
|
|
1980
|
+
verifyTxMerkleProof
|
|
1981
|
+
} from "@secondlayer/shared/node/nakamoto";
|
|
1982
|
+
var strip = (h) => h.startsWith("0x") ? h.slice(2) : h;
|
|
1983
|
+
var bytes = (h) => Uint8Array.from(Buffer.from(strip(h), "hex"));
|
|
1984
|
+
async function fetchRewardSet(opts) {
|
|
1985
|
+
const f = opts.fetchImpl ?? fetch;
|
|
1986
|
+
const res = await f(`${opts.nodeUrl.replace(/\/+$/, "")}/v3/stacker_set/${opts.cycle}`);
|
|
1987
|
+
if (res.status === 404)
|
|
1988
|
+
return null;
|
|
1989
|
+
if (!res.ok) {
|
|
1990
|
+
throw new Error(`/v3/stacker_set/${opts.cycle} returned ${res.status}`);
|
|
1991
|
+
}
|
|
1992
|
+
const body = await res.json();
|
|
1993
|
+
const signers = body.stacker_set.signers.map((s) => ({
|
|
1994
|
+
signing_key: strip(s.signing_key),
|
|
1995
|
+
weight: s.weight
|
|
1996
|
+
}));
|
|
1997
|
+
return {
|
|
1998
|
+
signers,
|
|
1999
|
+
total_weight: signers.reduce((sum, s) => sum + s.weight, 0)
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
function verifyTransactionProof(proof, opts) {
|
|
2003
|
+
const errors = [];
|
|
2004
|
+
const computedTxid = stacksTxid(bytes(proof.raw_tx));
|
|
2005
|
+
const txidMatches = computedTxid === strip(proof.txid);
|
|
2006
|
+
if (!txidMatches)
|
|
2007
|
+
errors.push("txid does not match raw_tx");
|
|
2008
|
+
const header = parseNakamotoBlockHeader(bytes(proof.raw_header));
|
|
2009
|
+
const includedInHeader = verifyTxMerkleProof(computedTxid, proof.tx_merkle_path, header.txMerkleRoot);
|
|
2010
|
+
if (!includedInHeader)
|
|
2011
|
+
errors.push("merkle path does not reach tx_merkle_root");
|
|
2012
|
+
const blockHash = nakamotoBlockHash(header);
|
|
2013
|
+
const indexBlockHash = nakamotoBlockId(blockHash, header.consensusHash);
|
|
2014
|
+
const headerSelfConsistent = indexBlockHash === strip(proof.index_block_hash);
|
|
2015
|
+
if (!headerSelfConsistent) {
|
|
2016
|
+
errors.push("recomputed index_block_hash does not match proof");
|
|
2017
|
+
}
|
|
2018
|
+
const anchoredOk = txidMatches && includedInHeader && headerSelfConsistent;
|
|
2019
|
+
const rewardSet = opts?.rewardSet ?? proof.consensus?.reward_set;
|
|
2020
|
+
let level = "anchored";
|
|
2021
|
+
let signerWeightBps;
|
|
2022
|
+
let thresholdMet;
|
|
2023
|
+
let rewardSetSource;
|
|
2024
|
+
if (rewardSet) {
|
|
2025
|
+
const v = verifySignerSignatures(blockHash, header.signerSignatures, rewardSet);
|
|
2026
|
+
signerWeightBps = v.totalWeight > 0 ? Math.round(v.signedWeight / v.totalWeight * 1e4) : 0;
|
|
2027
|
+
thresholdMet = v.thresholdMet;
|
|
2028
|
+
rewardSetSource = opts?.rewardSet ? "provided" : "embedded";
|
|
2029
|
+
if (!thresholdMet)
|
|
2030
|
+
errors.push("signer weight below the 70% threshold");
|
|
2031
|
+
if (anchoredOk && thresholdMet)
|
|
2032
|
+
level = "consensus";
|
|
2033
|
+
}
|
|
2034
|
+
return {
|
|
2035
|
+
level,
|
|
2036
|
+
txidMatches,
|
|
2037
|
+
includedInHeader,
|
|
2038
|
+
headerSelfConsistent,
|
|
2039
|
+
signerWeightBps,
|
|
2040
|
+
thresholdMet,
|
|
2041
|
+
rewardSetSource,
|
|
2042
|
+
ok: anchoredOk && (rewardSet ? thresholdMet === true : true),
|
|
2043
|
+
errors
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
1968
2046
|
export {
|
|
1969
2047
|
verifyWebhookSignature,
|
|
2048
|
+
verifyTransactionProof,
|
|
1970
2049
|
verifySecondlayerSignature,
|
|
1971
2050
|
trigger,
|
|
1972
2051
|
toJsonSafe,
|
|
@@ -1982,6 +2061,7 @@ export {
|
|
|
1982
2061
|
isFtMint,
|
|
1983
2062
|
isFtBurn,
|
|
1984
2063
|
getSubgraph,
|
|
2064
|
+
fetchRewardSet,
|
|
1985
2065
|
decodeStxTransfer,
|
|
1986
2066
|
decodeStxMint,
|
|
1987
2067
|
decodeStxLock,
|
|
@@ -2013,5 +2093,5 @@ export {
|
|
|
2013
2093
|
ApiError
|
|
2014
2094
|
};
|
|
2015
2095
|
|
|
2016
|
-
//# debugId=
|
|
2096
|
+
//# debugId=9DCF0337E6524B2364756E2164756E21
|
|
2017
2097
|
//# sourceMappingURL=index.js.map
|