@secondlayer/sdk 6.11.0 → 6.13.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 +69 -0
- package/dist/index.d.ts +108 -7
- package/dist/index.js +78 -1
- package/dist/index.js.map +6 -5
- package/dist/subgraphs/index.d.ts +27 -6
- package/dist/subgraphs/index.js.map +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -215,6 +215,75 @@ for await (const transfer of sl.index.ftTransfers.walk({
|
|
|
215
215
|
}
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
+
## Transaction-inclusion proofs
|
|
219
|
+
|
|
220
|
+
Verify — **without trusting Second Layer** — that a transaction is included in a
|
|
221
|
+
Stacks (Nakamoto) block, and that ≥70% of the reward cycle's signer weight
|
|
222
|
+
attested to that block. `verifyTransactionProof` recomputes everything
|
|
223
|
+
client-side and trusts nothing the API returned.
|
|
224
|
+
|
|
225
|
+
> Verification uses Node's crypto via `@secondlayer/shared` — Node/server-side use.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { verifyTransactionProof, fetchRewardSet } from "@secondlayer/sdk";
|
|
229
|
+
|
|
230
|
+
const proof = await fetch(
|
|
231
|
+
`https://api.secondlayer.tools/v1/index/transactions/${txid}/proof`,
|
|
232
|
+
).then((r) => r.json());
|
|
233
|
+
|
|
234
|
+
const result = verifyTransactionProof(proof); // anchored + consensus (embedded set)
|
|
235
|
+
// result.ok, result.level === "consensus", result.signerWeightBps
|
|
236
|
+
|
|
237
|
+
// Fully trustless — resolve the reward set from your own node:
|
|
238
|
+
const rewardSet = await fetchRewardSet({
|
|
239
|
+
nodeUrl: "https://your-stacks-node:20443",
|
|
240
|
+
cycle: proof.consensus.reward_cycle,
|
|
241
|
+
});
|
|
242
|
+
const trustless = verifyTransactionProof(proof, { rewardSet }); // rewardSetSource: "provided"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Two trust levels:
|
|
246
|
+
|
|
247
|
+
- **Anchored** — recompute the txid from `raw_tx`, fold `tx_merkle_path` up to the
|
|
248
|
+
header's `tx_merkle_root`, and recompute `block_hash` + `index_block_hash` from
|
|
249
|
+
`raw_header`. The tx is in a header any node can corroborate.
|
|
250
|
+
- **Consensus** — additionally recover the header's signer signatures and confirm
|
|
251
|
+
≥70% of the reward cycle's signer weight signed the block. Fully trustless when
|
|
252
|
+
you pass a `rewardSet` resolved yourself via `fetchRewardSet`
|
|
253
|
+
(`rewardSetSource: "provided"`); otherwise it uses the proof's embedded set
|
|
254
|
+
(`rewardSetSource: "embedded"`).
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
verifyTransactionProof(
|
|
258
|
+
proof: TransactionProof,
|
|
259
|
+
opts?: { rewardSet?: RewardSet },
|
|
260
|
+
): TransactionProofVerifyResult;
|
|
261
|
+
|
|
262
|
+
fetchRewardSet(opts: {
|
|
263
|
+
nodeUrl: string; // your own stacks-node
|
|
264
|
+
cycle: number; // reward cycle — proof.consensus.reward_cycle
|
|
265
|
+
fetchImpl?: typeof fetch;
|
|
266
|
+
}): Promise<RewardSet | null>; // reads /v3/stacker_set/{cycle}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`verifyTransactionProof` returns a `TransactionProofVerifyResult`:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
{
|
|
273
|
+
level: "anchored" | "consensus";
|
|
274
|
+
txidMatches: boolean;
|
|
275
|
+
includedInHeader: boolean;
|
|
276
|
+
headerSelfConsistent: boolean;
|
|
277
|
+
signerWeightBps?: number; // consensus only
|
|
278
|
+
thresholdMet?: boolean; // consensus only — ≥70% (7000 bps)
|
|
279
|
+
rewardSetSource?: "provided" | "embedded";
|
|
280
|
+
ok: boolean;
|
|
281
|
+
errors: string[];
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Exported types: `TransactionProof`, `TransactionProofVerifyResult`, `RewardSet`.
|
|
286
|
+
|
|
218
287
|
## Stacks Subgraphs
|
|
219
288
|
|
|
220
289
|
Deploy and query app-specific L3 tables.
|
package/dist/index.d.ts
CHANGED
|
@@ -337,6 +337,27 @@ type IndexTip = {
|
|
|
337
337
|
block_height: number
|
|
338
338
|
lag_seconds: number
|
|
339
339
|
};
|
|
340
|
+
/**
|
|
341
|
+
* A chain reorg overlapping a returned page's height range. Height-keyed feeds
|
|
342
|
+
* (`/transactions`, `/contract-calls`, `/stacking`) populate this so a consumer
|
|
343
|
+
* can reconcile: roll back the rows whose `block_height:tx_index` cursor falls in
|
|
344
|
+
* `orphaned_range`, then re-fetch from `new_canonical_tip`. Empty when the page
|
|
345
|
+
* spans no reorg.
|
|
346
|
+
*/
|
|
347
|
+
type IndexReorg = {
|
|
348
|
+
id: string
|
|
349
|
+
detected_at: string
|
|
350
|
+
fork_point_height: number
|
|
351
|
+
old_index_block_hash: string | null
|
|
352
|
+
new_index_block_hash: string | null
|
|
353
|
+
/** Orphaned cursor span `<block_height>:<tx_index>`, inclusive. */
|
|
354
|
+
orphaned_range: {
|
|
355
|
+
from: string
|
|
356
|
+
to: string
|
|
357
|
+
}
|
|
358
|
+
/** New canonical tip cursor to resume from. */
|
|
359
|
+
new_canonical_tip: string
|
|
360
|
+
};
|
|
340
361
|
type IndexUsage = {
|
|
341
362
|
product: "index"
|
|
342
363
|
tier: string
|
|
@@ -365,7 +386,7 @@ type FtTransfersEnvelope = {
|
|
|
365
386
|
events: FtTransfer[]
|
|
366
387
|
next_cursor: string | null
|
|
367
388
|
tip: IndexTip
|
|
368
|
-
reorgs:
|
|
389
|
+
reorgs: IndexReorg[]
|
|
369
390
|
};
|
|
370
391
|
type FtTransfersListParams = {
|
|
371
392
|
cursor?: string | null
|
|
@@ -398,7 +419,7 @@ type NftTransfersEnvelope = {
|
|
|
398
419
|
events: NftTransfer[]
|
|
399
420
|
next_cursor: string | null
|
|
400
421
|
tip: IndexTip
|
|
401
|
-
reorgs:
|
|
422
|
+
reorgs: IndexReorg[]
|
|
402
423
|
};
|
|
403
424
|
type NftTransfersListParams = {
|
|
404
425
|
cursor?: string | null
|
|
@@ -502,7 +523,7 @@ type EventsEnvelope = {
|
|
|
502
523
|
events: IndexEvent[]
|
|
503
524
|
next_cursor: string | null
|
|
504
525
|
tip: IndexTip
|
|
505
|
-
reorgs:
|
|
526
|
+
reorgs: IndexReorg[]
|
|
506
527
|
};
|
|
507
528
|
type EventsListParams = {
|
|
508
529
|
/** Required. One of the decoded event types. */
|
|
@@ -539,7 +560,7 @@ type ContractCallsEnvelope = {
|
|
|
539
560
|
contract_calls: IndexContractCall[]
|
|
540
561
|
next_cursor: string | null
|
|
541
562
|
tip: IndexTip
|
|
542
|
-
reorgs:
|
|
563
|
+
reorgs: IndexReorg[]
|
|
543
564
|
};
|
|
544
565
|
type ContractCallsListParams = {
|
|
545
566
|
cursor?: string | null
|
|
@@ -682,7 +703,7 @@ type TransactionsEnvelope = {
|
|
|
682
703
|
transactions: IndexTransaction[]
|
|
683
704
|
next_cursor: string | null
|
|
684
705
|
tip: IndexTip
|
|
685
|
-
reorgs:
|
|
706
|
+
reorgs: IndexReorg[]
|
|
686
707
|
};
|
|
687
708
|
type TransactionEnvelope = {
|
|
688
709
|
transaction: IndexTransaction
|
|
@@ -731,7 +752,7 @@ type StackingEnvelope = {
|
|
|
731
752
|
stacking: IndexStackingAction[]
|
|
732
753
|
next_cursor: string | null
|
|
733
754
|
tip: IndexTip
|
|
734
|
-
reorgs:
|
|
755
|
+
reorgs: IndexReorg[]
|
|
735
756
|
/** Present only when the PoX-4 decoder is disabled, explaining an empty feed. */
|
|
736
757
|
notes?: string
|
|
737
758
|
};
|
|
@@ -1807,10 +1828,90 @@ declare function verifyWebhookSignature(rawBody: string, headers: WebhookHeaderI
|
|
|
1807
1828
|
* ```
|
|
1808
1829
|
*/
|
|
1809
1830
|
declare function verifySecondlayerSignature(rawBody: string, headers: WebhookHeaderInput, publicKeyPem: string): boolean;
|
|
1831
|
+
import { RewardSet } from "@secondlayer/shared/node/consensus";
|
|
1832
|
+
import { MerkleProofStep } from "@secondlayer/shared/node/nakamoto";
|
|
1833
|
+
/**
|
|
1834
|
+
* Trustless transaction-inclusion proof verification.
|
|
1835
|
+
*
|
|
1836
|
+
* Given a proof from `GET /v1/index/transactions/:txid/proof`, the consumer
|
|
1837
|
+
* re-derives everything itself — it does NOT trust any value Secondlayer
|
|
1838
|
+
* computed. Anchored level: (1) recompute the txid from the raw tx bytes, (2)
|
|
1839
|
+
* fold it up the merkle path to the header's `tx_merkle_root`, (3) recompute the
|
|
1840
|
+
* header's `block_hash` and `index_block_hash` from the raw header — "this tx is
|
|
1841
|
+
* included in a header any node can corroborate". Consensus level (when the proof
|
|
1842
|
+
* carries a `consensus` field, or a `rewardSet` is passed): additionally recover
|
|
1843
|
+
* the header's signer signatures and confirm ≥70% of reward-set signer weight
|
|
1844
|
+
* signed the block.
|
|
1845
|
+
*
|
|
1846
|
+
* Note: uses Node's crypto via `@secondlayer/shared` (same as the Streams
|
|
1847
|
+
* signature verify); intended for Node/server verification.
|
|
1848
|
+
*/
|
|
1849
|
+
interface TransactionProof {
|
|
1850
|
+
txid: string;
|
|
1851
|
+
index_block_hash: string;
|
|
1852
|
+
block_height: number;
|
|
1853
|
+
tx_index: number;
|
|
1854
|
+
/** Raw consensus-serialized transaction bytes (hex). */
|
|
1855
|
+
raw_tx: string;
|
|
1856
|
+
/** Raw Nakamoto block-header bytes (hex) — parsed + re-hashed by the verifier. */
|
|
1857
|
+
raw_header: string;
|
|
1858
|
+
/** Authentication path from the tx leaf to `tx_merkle_root`. */
|
|
1859
|
+
tx_merkle_path: MerkleProofStep[];
|
|
1860
|
+
/** Present when consensus-level verification is available: the reward cycle and
|
|
1861
|
+
* its signer set, against which the header's signer signatures are checked. */
|
|
1862
|
+
consensus?: {
|
|
1863
|
+
reward_cycle: number
|
|
1864
|
+
reward_set: RewardSet
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
interface TransactionProofVerifyResult {
|
|
1868
|
+
/** Highest level actually verified. "consensus" requires the proof's
|
|
1869
|
+
* `consensus` field and a met signer-weight threshold. */
|
|
1870
|
+
level: "anchored" | "consensus";
|
|
1871
|
+
/** Recomputed txid === proof.txid. */
|
|
1872
|
+
txidMatches: boolean;
|
|
1873
|
+
/** Merkle path folds the txid to the header's tx_merkle_root. */
|
|
1874
|
+
includedInHeader: boolean;
|
|
1875
|
+
/** Recomputed block_hash + index_block_hash match the header / proof. */
|
|
1876
|
+
headerSelfConsistent: boolean;
|
|
1877
|
+
/** Basis points (0–10000) of reward-set signer weight that signed the block.
|
|
1878
|
+
* Only set when the proof carries a `consensus` field. */
|
|
1879
|
+
signerWeightBps?: number;
|
|
1880
|
+
/** ≥70% of signer weight signed. Only set with a `consensus` field. */
|
|
1881
|
+
thresholdMet?: boolean;
|
|
1882
|
+
/** Which reward set the signer check used: "provided" (caller-resolved →
|
|
1883
|
+
* fully trustless) or "embedded" (the one Secondlayer put in the proof). */
|
|
1884
|
+
rewardSetSource?: "provided" | "embedded";
|
|
1885
|
+
/** All applicable checks passed (incl. the threshold when consensus is present). */
|
|
1886
|
+
ok: boolean;
|
|
1887
|
+
errors: string[];
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Resolve a reward set directly from a stacks-node (`/v3/stacker_set/{cycle}`),
|
|
1891
|
+
* so a caller can verify the consensus layer against a node IT trusts rather than
|
|
1892
|
+
* the reward set Secondlayer embedded in the proof. Pass the result as
|
|
1893
|
+
* `verifyTransactionProof(proof, { rewardSet })`.
|
|
1894
|
+
*/
|
|
1895
|
+
declare function fetchRewardSet(opts: {
|
|
1896
|
+
nodeUrl: string
|
|
1897
|
+
cycle: number
|
|
1898
|
+
fetchImpl?: typeof fetch
|
|
1899
|
+
}): Promise<RewardSet | null>;
|
|
1900
|
+
/**
|
|
1901
|
+
* Verify a transaction-inclusion proof. Every check is recomputed client-side,
|
|
1902
|
+
* so a `true` result does not rely on trusting Secondlayer. Pass
|
|
1903
|
+
* `{ rewardSet }` (resolved via {@link fetchRewardSet} from your own node) to
|
|
1904
|
+
* verify the consensus layer against a reward set you trust rather than the one
|
|
1905
|
+
* embedded in the proof.
|
|
1906
|
+
*/
|
|
1907
|
+
declare function verifyTransactionProof(proof: TransactionProof, opts?: {
|
|
1908
|
+
rewardSet?: RewardSet
|
|
1909
|
+
}): TransactionProofVerifyResult;
|
|
1910
|
+
import { RewardSet as RewardSet2 } from "@secondlayer/shared/node/consensus";
|
|
1810
1911
|
/** Make a cvToValue result JSON-serializable: Clarity (u)ints decode to bigint,
|
|
1811
1912
|
* which JSON.stringify can't handle — convert recursively to strings. */
|
|
1812
1913
|
declare function toJsonSafe(value: unknown): unknown;
|
|
1813
1914
|
/** Decode a hex-encoded Clarity value to JSON-safe JS (uints as strings,
|
|
1814
1915
|
* buffers as `0x…` hex, tuples as objects). Returns the input hex on failure. */
|
|
1815
1916
|
declare function decodeClarityValue(hex: string): unknown;
|
|
1816
|
-
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 };
|
|
1917
|
+
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
|
@@ -1965,8 +1965,84 @@ function verifySecondlayerSignature(rawBody, headers, publicKeyPem) {
|
|
|
1965
1965
|
const signature = pickHeader(headers, "x-secondlayer-signature");
|
|
1966
1966
|
return verifySecondlayerSignatureValues(rawBody, id, signature, publicKeyPem);
|
|
1967
1967
|
}
|
|
1968
|
+
// src/proofs.ts
|
|
1969
|
+
import {
|
|
1970
|
+
verifySignerSignatures
|
|
1971
|
+
} from "@secondlayer/shared/node/consensus";
|
|
1972
|
+
import {
|
|
1973
|
+
nakamotoBlockHash,
|
|
1974
|
+
nakamotoBlockId,
|
|
1975
|
+
parseNakamotoBlockHeader,
|
|
1976
|
+
stacksTxid,
|
|
1977
|
+
verifyTxMerkleProof
|
|
1978
|
+
} from "@secondlayer/shared/node/nakamoto";
|
|
1979
|
+
var strip = (h) => h.startsWith("0x") ? h.slice(2) : h;
|
|
1980
|
+
var bytes = (h) => Uint8Array.from(Buffer.from(strip(h), "hex"));
|
|
1981
|
+
async function fetchRewardSet(opts) {
|
|
1982
|
+
const f = opts.fetchImpl ?? fetch;
|
|
1983
|
+
const res = await f(`${opts.nodeUrl.replace(/\/+$/, "")}/v3/stacker_set/${opts.cycle}`);
|
|
1984
|
+
if (res.status === 404)
|
|
1985
|
+
return null;
|
|
1986
|
+
if (!res.ok) {
|
|
1987
|
+
throw new Error(`/v3/stacker_set/${opts.cycle} returned ${res.status}`);
|
|
1988
|
+
}
|
|
1989
|
+
const body = await res.json();
|
|
1990
|
+
const signers = body.stacker_set.signers.map((s) => ({
|
|
1991
|
+
signing_key: strip(s.signing_key),
|
|
1992
|
+
weight: s.weight
|
|
1993
|
+
}));
|
|
1994
|
+
return {
|
|
1995
|
+
signers,
|
|
1996
|
+
total_weight: signers.reduce((sum, s) => sum + s.weight, 0)
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
function verifyTransactionProof(proof, opts) {
|
|
2000
|
+
const errors = [];
|
|
2001
|
+
const computedTxid = stacksTxid(bytes(proof.raw_tx));
|
|
2002
|
+
const txidMatches = computedTxid === strip(proof.txid);
|
|
2003
|
+
if (!txidMatches)
|
|
2004
|
+
errors.push("txid does not match raw_tx");
|
|
2005
|
+
const header = parseNakamotoBlockHeader(bytes(proof.raw_header));
|
|
2006
|
+
const includedInHeader = verifyTxMerkleProof(computedTxid, proof.tx_merkle_path, header.txMerkleRoot);
|
|
2007
|
+
if (!includedInHeader)
|
|
2008
|
+
errors.push("merkle path does not reach tx_merkle_root");
|
|
2009
|
+
const blockHash = nakamotoBlockHash(header);
|
|
2010
|
+
const indexBlockHash = nakamotoBlockId(blockHash, header.consensusHash);
|
|
2011
|
+
const headerSelfConsistent = indexBlockHash === strip(proof.index_block_hash);
|
|
2012
|
+
if (!headerSelfConsistent) {
|
|
2013
|
+
errors.push("recomputed index_block_hash does not match proof");
|
|
2014
|
+
}
|
|
2015
|
+
const anchoredOk = txidMatches && includedInHeader && headerSelfConsistent;
|
|
2016
|
+
const rewardSet = opts?.rewardSet ?? proof.consensus?.reward_set;
|
|
2017
|
+
let level = "anchored";
|
|
2018
|
+
let signerWeightBps;
|
|
2019
|
+
let thresholdMet;
|
|
2020
|
+
let rewardSetSource;
|
|
2021
|
+
if (rewardSet) {
|
|
2022
|
+
const v = verifySignerSignatures(blockHash, header.signerSignatures, rewardSet);
|
|
2023
|
+
signerWeightBps = v.totalWeight > 0 ? Math.round(v.signedWeight / v.totalWeight * 1e4) : 0;
|
|
2024
|
+
thresholdMet = v.thresholdMet;
|
|
2025
|
+
rewardSetSource = opts?.rewardSet ? "provided" : "embedded";
|
|
2026
|
+
if (!thresholdMet)
|
|
2027
|
+
errors.push("signer weight below the 70% threshold");
|
|
2028
|
+
if (anchoredOk && thresholdMet)
|
|
2029
|
+
level = "consensus";
|
|
2030
|
+
}
|
|
2031
|
+
return {
|
|
2032
|
+
level,
|
|
2033
|
+
txidMatches,
|
|
2034
|
+
includedInHeader,
|
|
2035
|
+
headerSelfConsistent,
|
|
2036
|
+
signerWeightBps,
|
|
2037
|
+
thresholdMet,
|
|
2038
|
+
rewardSetSource,
|
|
2039
|
+
ok: anchoredOk && (rewardSet ? thresholdMet === true : true),
|
|
2040
|
+
errors
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
1968
2043
|
export {
|
|
1969
2044
|
verifyWebhookSignature,
|
|
2045
|
+
verifyTransactionProof,
|
|
1970
2046
|
verifySecondlayerSignature,
|
|
1971
2047
|
trigger,
|
|
1972
2048
|
toJsonSafe,
|
|
@@ -1982,6 +2058,7 @@ export {
|
|
|
1982
2058
|
isFtMint,
|
|
1983
2059
|
isFtBurn,
|
|
1984
2060
|
getSubgraph,
|
|
2061
|
+
fetchRewardSet,
|
|
1985
2062
|
decodeStxTransfer,
|
|
1986
2063
|
decodeStxMint,
|
|
1987
2064
|
decodeStxLock,
|
|
@@ -2013,5 +2090,5 @@ export {
|
|
|
2013
2090
|
ApiError
|
|
2014
2091
|
};
|
|
2015
2092
|
|
|
2016
|
-
//# debugId=
|
|
2093
|
+
//# debugId=5E490BEDE2791EFC64756E2164756E21
|
|
2017
2094
|
//# sourceMappingURL=index.js.map
|