@secondlayer/stacks 5.1.2 → 5.1.3
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 +16 -333
- package/dist/src/filters/index.d.cts +3 -3
- package/dist/src/filters/index.d.ts +3 -3
- package/dist/src/index.d.cts +2 -2
- package/dist/src/index.d.ts +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A viem-style SDK for the Stacks blockchain. One package, zero polyfills, full tree-shaking.
|
|
4
4
|
|
|
5
|
+
Docs: [stacks.secondlayer.tools](https://stacks.secondlayer.tools)
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -24,24 +26,6 @@ const balance = await client.getBalance({
|
|
|
24
26
|
});
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
## Clarinet simnet
|
|
28
|
-
|
|
29
|
-
Same client as HTTP, against an in-process VM (`@stacks/clarinet-sdk` is an optional peer of this entry).
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
import { initSimnet } from "@stacks/clarinet-sdk";
|
|
33
|
-
import { createPublicClient } from "@secondlayer/stacks";
|
|
34
|
-
import { simnet, simnetChain } from "@secondlayer/stacks/simnet";
|
|
35
|
-
|
|
36
|
-
const session = await initSimnet("./Clarinet.toml");
|
|
37
|
-
const client = createPublicClient({
|
|
38
|
-
chain: simnetChain,
|
|
39
|
-
transport: simnet(session),
|
|
40
|
-
});
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Then `getContract` as on mainnet. No `/extended`; watches throw; fees use `'min'`.
|
|
44
|
-
|
|
45
29
|
## Exports
|
|
46
30
|
|
|
47
31
|
| Module | Description |
|
|
@@ -66,7 +50,7 @@ These ship and work but get no further investment — they may be removed in a
|
|
|
66
50
|
future major. Prefer Hiro's maintained `@stacks/*` ecosystem for wallet-side
|
|
67
51
|
work; Secondlayer is a data-infrastructure company, not a wallet SDK vendor.
|
|
68
52
|
|
|
69
|
-
Nonce coordination
|
|
53
|
+
Nonce coordination is the exception — it is supported and maintained:
|
|
70
54
|
it's mempool-aware (built on Secondlayer's data plane) and solves a real
|
|
71
55
|
multi-broadcast gap, which is why it lives here rather than being deferred to
|
|
72
56
|
`@stacks/*`.
|
|
@@ -75,7 +59,7 @@ multi-broadcast gap, which is why it lives here rather than being deferred to
|
|
|
75
59
|
|---|---|
|
|
76
60
|
| `@secondlayer/stacks/connect` | Wallet connection — browser extensions + `setProvider` |
|
|
77
61
|
| `@secondlayer/stacks/connect/walletconnect` | WalletConnect v2 — native relay, QR, modal |
|
|
78
|
-
| `@secondlayer/stacks/subscriptions` | `watchBlocks`, `watchMempool`, `watchTransaction` |
|
|
62
|
+
| `@secondlayer/stacks/subscriptions` | WebSocket watch helpers (`watchBlocks`, `watchMempool`, `watchTransaction`) — not a Webhooks product alias |
|
|
79
63
|
| `@secondlayer/stacks/bns` | BNS name registration, resolution, zonefiles |
|
|
80
64
|
| `@secondlayer/stacks/pox` | PoX stacking — solo and delegated |
|
|
81
65
|
| `@secondlayer/stacks/stackingdao` | StackingDAO liquid staking (STX/stSTX) |
|
|
@@ -89,316 +73,15 @@ Give your agent Stacks reads through [`@secondlayer/mcp`](https://www.npmjs.com/
|
|
|
89
73
|
| `@secondlayer/stacks/tools` | **Deprecated.** AI SDK `tool()` set for Stacks reads. Bare exports read `STACKS_NETWORK` (or `STACKS_CHAIN`) and `STACKS_NODE_RPC_URL`, `SL_API_URL` or `STACKS_RPC_URL`; `createStacksTools(client)` binds your own client |
|
|
90
74
|
| `@secondlayer/stacks/tools/btc` | **Deprecated.** AI SDK `tool()` set for Bitcoin reads via mempool.space (`BTC_MEMPOOL_URL` overrides the host) |
|
|
91
75
|
|
|
92
|
-
##
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
const txid = await client.callContract({ contract, functionName: "mint" });
|
|
107
|
-
const receipt = await client.waitForTransactionReceipt({ txid, confirmations: 2 });
|
|
108
|
-
receipt.result; // decoded ClarityValue
|
|
109
|
-
|
|
110
|
-
// or in one step:
|
|
111
|
-
const { receipt } = await sendTransaction(client, { transaction, wait: 2 });
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Status reads are pluggable, like nonce sources: the default reads `/extended/v1/tx` on your transport host; `indexTxSource()` reads `/v1/index/transactions` on your Secondlayer instance, which returns the chain tip in the same response, so N-confirmation waits cost one request per poll. The index only knows mined transactions, so with this source the dropped-grace window defaults to the full `timeout` instead of 30s. Without `baseUrl` the transport URL is assumed to be your instance: a Hiro transport throws up front, and a host that answers `/v1/index` with a non-JSON 404 (a bare stacks-node) throws on the first poll instead of waiting out the timeout. A `baseUrl` other than the transport host reuses the transport's retry and timeout policy only; the transport's `apiKey` and `fetchOptions` stay with the transport host. Rejection reasons are typed too: `BroadcastError.reason` is a literal union of all 26 stacks-node rejection strings (with `reasonData` and `txid` attached).
|
|
115
|
-
|
|
116
|
-
## Errors
|
|
117
|
-
|
|
118
|
-
The HTTP transport throws a typed `HttpRequestError` (`.status`, `.url`, `.method` attached) on any non-2xx response instead of handing back the error body as if it were a successful result, and it retries `429`s, not just `5xx`/network errors. One `timeout` covers each attempt end to end, headers and body, so a stalled response rejects with `TimeoutError` (`.url`, `.method`, `.timeout`, `.attempt`) instead of hanging. Pass `signal` on any request to cancel from your side; a caller abort is never retried. Broadcasts are sent once (`retryCount: 0`): re-posting a transaction the node may already hold would surface as a nonce conflict, so a timed-out broadcast checks whether the node knows the tx before failing. `MalformedResponseError` throws from `getBalance`, `getAccountInfo`, `getNonce`, `getMapEntry`, `getBlockHeight`, `getBurnBlockHeight`, and `readContract` when the node's response is missing an expected field, instead of failing with an opaque native error (`BigInt(undefined)` and friends). `readContract` (and `getContract().read`) throws `ReadContractError` when the node answers `okay: false`; the message is the node's cause. Both are exported from `@secondlayer/stacks`. Every error carries a stable `code` (`HTTP_REQUEST_ERROR`, `TIMEOUT_ERROR`, `MALFORMED_RESPONSE_ERROR`, `READ_CONTRACT_ERROR`, ...) that also appears in `toJSON()`, so a handler can branch on it without an `instanceof` chain and without parsing a message that may be reworded.
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
import { BaseError, HttpRequestError, MalformedResponseError, ReadContractError, TimeoutError } from "@secondlayer/stacks";
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
await client.getBalance({ address });
|
|
125
|
-
} catch (e) {
|
|
126
|
-
if (e instanceof HttpRequestError) e.status; // non-2xx from the node/API
|
|
127
|
-
if (e instanceof TimeoutError) e.url; // headers or body did not arrive in time
|
|
128
|
-
if (e instanceof MalformedResponseError) { /* response shape didn't match */ }
|
|
129
|
-
if (e instanceof ReadContractError) e.shortMessage; // call-read okay: false, node cause
|
|
130
|
-
if (e instanceof BaseError) e.code; // "TIMEOUT_ERROR", stable across releases and minification (derived from `name`, not the class)
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Bitcoin addresses from the same mnemonic
|
|
135
|
-
|
|
136
|
-
Derive the paired BTC account (what Leather/Xverse show next to your Stacks address) with no extra dependencies — BIP84 native segwit or BIP86 taproot, network-aware:
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
import { mnemonicToBitcoinKeys } from "@secondlayer/stacks/accounts";
|
|
140
|
-
|
|
141
|
-
const btc = mnemonicToBitcoinKeys(mnemonic, { type: "p2tr" });
|
|
142
|
-
btc.address; // bc1p… (path m/86'/0'/0'/0/0)
|
|
143
|
-
|
|
144
|
-
mnemonicToBitcoinKeys(mnemonic, { type: "p2wpkh", network: "testnet" }).address; // tb1q…
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Pure derivation — no Bitcoin transaction building or signing. The pubkey→address helpers (`publicKeyToP2wpkhAddress`, `publicKeyToP2trAddress`, `taprootTweakPubkey`) are exported from `@secondlayer/stacks/bitcoin`, validated against the BIP84/86/341 test vectors.
|
|
148
|
-
|
|
149
|
-
The sBTC extension uses the same machinery to derive the **signers' deposit address** straight from the on-chain registry — network-aware, so testnet gives `tb1p…` instead of a wrong-network address:
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
const client = createPublicClient({ chain: mainnet, transport: http() }).extend(sbtc());
|
|
153
|
-
await client.sbtc.getSignersAddress(); // bc1p… (derived from get-current-aggregate-pubkey)
|
|
154
|
-
await client.sbtc.getSignersPublicKey(); // 33-byte aggregate key
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## PoX-5 Bitcoin Staking (SIP-045)
|
|
158
|
-
|
|
159
|
-
Epoch 4.0 activated at Bitcoin block 960,230 (2026-07-30). This module is pinned against the **final `pox-5` contract from stacks-core 4.0.0** — bonds, staking, L1 lockup scripts, signer grants, cycle math.
|
|
160
|
-
|
|
161
|
-
Activation gating is chain-reported, not hardcoded: `client.pox5.isActive()` / `getActivation()` read the node's `/v2/pox` `contract_versions` — no heights baked in, correct on any network, so integrations built today ship safely before the fork.
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
import { createWalletClient, http } from "@secondlayer/stacks";
|
|
165
|
-
import { mainnet } from "@secondlayer/stacks/chains";
|
|
166
|
-
import { Pc } from "@secondlayer/stacks/postconditions";
|
|
167
|
-
import { pox5 } from "@secondlayer/stacks/pox5";
|
|
168
|
-
|
|
169
|
-
const client = createWalletClient({ chain: mainnet, transport: http(), account })
|
|
170
|
-
.extend(pox5());
|
|
171
|
-
|
|
172
|
-
if (await client.pox5.isActive()) {
|
|
173
|
-
const txid = await client.pox5.stake({
|
|
174
|
-
signerManager: "SP….signer-mgr",
|
|
175
|
-
amountUstx: 100_000_000_000n,
|
|
176
|
-
numCycles: 12,
|
|
177
|
-
startBurnHeight: 960_231,
|
|
178
|
-
fee: "low",
|
|
179
|
-
postConditions: [
|
|
180
|
-
Pc.principal(account.address).willSendEq(100_000_000_000n).ustxToLock(),
|
|
181
|
-
],
|
|
182
|
-
});
|
|
183
|
-
await client.waitForTransactionReceipt({ txid });
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Every wallet action (`setupBond`, `registerForBond`, `stake`, `unstake`, `unstakeSbtc`, `claimRewards`, `grantSignerKey`, …) inherits fee tiers, nonce management, and typed errors from the client. `client.pox5.getStakerState(staker)` returns a staker's whole position (staker info, bond membership, custodied sBTC, current cycle) in one call (four parallel reads under the hood; Stacks nodes have no batch RPC, and `multicall` caps reads in flight at 8 by default via `concurrency`). Individual reads return typed JS values decoded via the module's committed pox-5 ABI (bigints, camelCase tuples, `null` for absent optionals), and every action's arguments are checked against that ABI at compile time.
|
|
188
|
-
|
|
189
|
-
### Off-chain L1 tooling — works before activation
|
|
190
|
-
|
|
191
|
-
- **Lockup scripts** — `buildLockupScript` / `buildLockupAddress` construct the CLTV + early-exit witness script and the network-aware P2WSH address a staker sends their L1 BTC to.
|
|
192
|
-
- **Signer grants** — `computeSignerGrantHash` (SIP-018 structured data) and `signSignerGrant` (65-byte RSV signature, the layout `grant-signer-key` expects), plus local `verifySignerGrant`.
|
|
193
|
-
- **Cycle math** — `burnHeightToRewardCycle`, `bondPeriodToBurnHeight`, `bondPhaseAtHeight`, … anchored on chain-reported `/v2/pox` parameters, nothing hardcoded.
|
|
194
|
-
|
|
195
|
-
The trust story: every wallet action is pinned against the boot contract interface in Clarinet simnet tests, and the script/grant-hash ports are byte-compared against the actual pox-5 contract read-onlys — not a hand-transcribed ABI.
|
|
196
|
-
|
|
197
|
-
## Nonce management
|
|
198
|
-
|
|
199
|
-
Stacks' `/v2/accounts` returns only the confirmed nonce — it ignores the mempool. Broadcasting several transactions from one account before the first confirms makes them reuse the same nonce, so every one after the first is rejected (`ConflictingNonceInMempool`). The usual workaround is tracking nonces by hand.
|
|
200
|
-
|
|
201
|
-
Attach a nonce manager and the SDK hands out sequential nonces across rapid broadcasts:
|
|
202
|
-
|
|
203
|
-
```ts
|
|
204
|
-
import { createWalletClient, http, createNonceManager } from "@secondlayer/stacks";
|
|
205
|
-
import { mainnet } from "@secondlayer/stacks/chains";
|
|
206
|
-
import { privateKeyToAccount } from "@secondlayer/stacks/accounts";
|
|
207
|
-
|
|
208
|
-
const client = createWalletClient({
|
|
209
|
-
chain: mainnet,
|
|
210
|
-
transport: http(), // any node — no Secondlayer dependency
|
|
211
|
-
account: privateKeyToAccount(process.env.KEY!),
|
|
212
|
-
nonceManager: createNonceManager(), // jsonRpcSource + in-memory store
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// 20 back-to-back transfers → nonces n, n+1, …, n+19 — no collisions
|
|
216
|
-
await Promise.all(
|
|
217
|
-
recipients.map((to) => client.transferStx({ to, amount: 1000n })),
|
|
218
|
-
);
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Passing an explicit `nonce` always bypasses the manager. The defaults are node-agnostic and in-memory, with zero external dependencies. A send that fails before the node accepts it (fee estimate outage, `FeeTooLow`, transport error) hands its nonce back, so the next send reuses it instead of leaving a gap the mempool cannot chain past. Custom stores opt in by implementing `release(key, nonce)`; the bundled memory, Redis and Postgres stores already do.
|
|
222
|
-
|
|
223
|
-
### Multiple processes / smart wallets
|
|
224
|
-
|
|
225
|
-
The in-memory store is single-process. Backends that sign from one key across multiple workers (smart-wallet-as-a-service) need a shared, durable store. The reservation is atomic in the datastore, so it doubles as the cross-process lock and survives restarts:
|
|
226
|
-
|
|
227
|
-
```ts
|
|
228
|
-
import { createNonceManager, redisStore } from "@secondlayer/stacks";
|
|
229
|
-
|
|
230
|
-
const nonceManager = createNonceManager({
|
|
231
|
-
store: redisStore({ redis: new Bun.RedisClient(process.env.REDIS_URL!) }),
|
|
232
|
-
});
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
`postgresStore({ sql })` works the same way. Bring your own client — no global `Bun` reference, so the store stays runtime-agnostic.
|
|
236
|
-
|
|
237
|
-
### Mempool-aware sources (optional)
|
|
238
|
-
|
|
239
|
-
By default the floor is the node's confirmed nonce. To make it mempool-aware, and to auto-fill the freed nonce of a dropped transaction, swap the source. `indexSource` reads your Secondlayer instance's mempool through the client transport (retries, timeout and `Authorization: Bearer` included), `hiroNonceSource` reads Hiro's, or bring your own pending feed with `mempoolAwareSource`:
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
import {
|
|
243
|
-
createNonceManager,
|
|
244
|
-
indexSource,
|
|
245
|
-
startNonceReconciler,
|
|
246
|
-
} from "@secondlayer/stacks";
|
|
247
|
-
|
|
248
|
-
// indexSource({ baseUrl?, apiKey? }) | hiroNonceSource({ baseUrl }) | mempoolAwareSource({ getPending })
|
|
249
|
-
// baseUrl defaults to the transport URL; pass it when the transport is not your instance.
|
|
250
|
-
// A missing URL, a Hiro transport, or a host without /v1/index rejects instead of
|
|
251
|
-
// silently falling back to the confirmed nonce; a 5xx or timeout still degrades.
|
|
252
|
-
const source = indexSource();
|
|
253
|
-
const nonceManager = createNonceManager({ source });
|
|
254
|
-
|
|
255
|
-
// Optional: periodically heal silently-dropped txs (run in ONE process)
|
|
256
|
-
const reconciler = startNonceReconciler(nonceManager, {
|
|
257
|
-
client,
|
|
258
|
-
addresses: [account.address],
|
|
259
|
-
source,
|
|
260
|
-
});
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Everything here is opt-in. With no `source`/`store`, the manager depends only on your node.
|
|
264
|
-
|
|
265
|
-
## Bitcoin SPV (SIP-044)
|
|
266
|
-
|
|
267
|
-
**Prove a Bitcoin payment happened — inside a Stacks contract, with no oracle.**
|
|
268
|
-
|
|
269
|
-
SIP-044 ("Clarity 6", live since Stacks Epoch 4.0 activated 2026-07-30) lets a contract natively
|
|
270
|
-
verify that a Bitcoin transaction was mined — *SPV* (Simplified Payment
|
|
271
|
-
Verification), the proof technique Bitcoin light clients use. A contract can check
|
|
272
|
-
"BTC tx T paid Z sats to address Y in a confirmed block" without trusting an
|
|
273
|
-
indexer or oracle.
|
|
274
|
-
|
|
275
|
-
The catch: the built-ins (`get-bitcoin-tx-output?`, `verify-merkle-proof`) run
|
|
276
|
-
only *inside* a contract and demand precisely-shaped proof data — the right merkle
|
|
277
|
-
proof, internal byte order, witness stripped. This module does that off-chain prep
|
|
278
|
-
so you never run a node by hand or reverse a hash, and ships a reference contract
|
|
279
|
-
(`spv-adapter`, in `contracts/`) wired to the built-ins.
|
|
280
|
-
|
|
281
|
-
**What it unlocks** (it trust-minimizes *verification*, not *custody*):
|
|
282
|
-
|
|
283
|
-
- **BTC-settled escrow / OTC** — release sBTC, an NFT, or a loan only when a real BTC payment is proven on-chain.
|
|
284
|
-
- **BTC-L1 collateral** — prove a borrower's Bitcoin UTXO exists on L1 instead of trusting a price/existence oracle (Zest / Granite-style lending).
|
|
285
|
-
- **Atomic BTC ↔ sBTC / Runes swaps** — native SPV is uncapped, so multi-output Runes/BTC txs that blew past the old `clarity-bitcoin` limits now verify.
|
|
286
|
-
- **Trust-minimized sBTC** — deposits *proven* on-chain rather than only *asserted* by the signer set (aligns with SIP-028).
|
|
287
|
-
|
|
288
|
-
It's also the Bitcoin half of the [Secondlayer](https://secondlayer.tools) indexer:
|
|
289
|
-
our index already surfaces the `bitcoin_txid` on every sBTC deposit/withdrawal —
|
|
290
|
-
this module turns that correlation into an on-chain-verifiable proof.
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
import { createPublicClient, http } from "@secondlayer/stacks";
|
|
294
|
-
import { mainnet } from "@secondlayer/stacks/chains";
|
|
295
|
-
import {
|
|
296
|
-
buildTxProof,
|
|
297
|
-
bitcoinRpcSource,
|
|
298
|
-
esploraSource,
|
|
299
|
-
fallbackProofSource,
|
|
300
|
-
verifyBitcoinPayment,
|
|
301
|
-
} from "@secondlayer/stacks/bitcoin";
|
|
302
|
-
|
|
303
|
-
const client = createPublicClient({ chain: mainnet, transport: http() });
|
|
304
|
-
|
|
305
|
-
// Trustless by default: the integrator's own node first, hosted fallback second.
|
|
306
|
-
const source = fallbackProofSource([
|
|
307
|
-
bitcoinRpcSource({ url: "http://127.0.0.1:8332", auth: { username: "u", password: "p" } }),
|
|
308
|
-
esploraSource({ url: "https://blockstream.info/api" }),
|
|
309
|
-
]);
|
|
310
|
-
|
|
311
|
-
// "release only when a real BTC payment to <addr> for <amount> is proven on-chain"
|
|
312
|
-
const result = await verifyBitcoinPayment(client, {
|
|
313
|
-
txid: "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",
|
|
314
|
-
source,
|
|
315
|
-
vout: 0,
|
|
316
|
-
expect: { address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", amount: 5_000_000_000n },
|
|
317
|
-
});
|
|
318
|
-
// → { verified, mined, output, proof }
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
On mainnet `contract` is optional — it resolves the reference adapter,
|
|
322
|
-
`SP2M1DE95TS0QBM4K893X6ST49FFJ53CCX9CYWNVY.spv-adapter`, live since Epoch 4.0
|
|
323
|
-
activated (2026-07-30) and proven via a `was-tx-mined` golden-proof smoke test.
|
|
324
|
-
Stacks testnet has no Epoch 4.0, so the built-ins don't exist there: pass an
|
|
325
|
-
explicit `contract`, or point it at your own verifier.
|
|
326
|
-
|
|
327
|
-
Lower-level pieces are exported too: `parseBitcoinTx` / `buildMerkleProof` /
|
|
328
|
-
`merkleRoot` (proof construction), `encodeMerkleProofArgs` / `decodeTxOutput` /
|
|
329
|
-
`parseOutputScript` (Clarity codecs), and `bitcoinVerifier` / `isClarity6Active`
|
|
330
|
-
(the contract binding + activation gate).
|
|
331
|
-
|
|
332
|
-
The off-chain surface (proof construction, codecs, sources) works today against
|
|
333
|
-
live Bitcoin data. The on-chain verification calls the SIP-044 native built-ins,
|
|
334
|
-
live on mainnet since Clarity 6 / Epoch 4.0 activated at Bitcoin block 960,230
|
|
335
|
-
(2026-07-30), exported as `EPOCH_4_ACTIVATION_BURN_HEIGHT_MAINNET`.
|
|
336
|
-
|
|
337
|
-
### Run the on-chain side locally — no node
|
|
338
|
-
|
|
339
|
-
Clarinet ≥ 3.21 boots simnet at Epoch 4.0, so the `spv-adapter` reference
|
|
340
|
-
contract (`contracts/spv-adapter.clar`) both type-checks and *executes* the
|
|
341
|
-
built-ins today:
|
|
342
|
-
|
|
343
|
-
```bash
|
|
344
|
-
cd contracts && clarinet console # in-memory simnet @ Epoch 4.0
|
|
345
|
-
# then: (contract-call? .spv-adapter get-tx-output 0x<rawtx> u0)
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
The SDK↔contract round-trip is covered in CI — it asserts the bytes this module
|
|
349
|
-
encodes are exactly what the built-ins accept:
|
|
350
|
-
|
|
351
|
-
```bash
|
|
352
|
-
bun test packages/stacks/src/bitcoin/__tests__/onchain.simnet.test.ts # 7 pass
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
See `contracts/README.md` for the full local recipe.
|
|
356
|
-
**SPV trust-minimizes *verification*, not *custody*.**
|
|
357
|
-
|
|
358
|
-
## WalletConnect v2
|
|
359
|
-
|
|
360
|
-
Native WC v2 implementation — X25519 ECDH, AES-256-GCM envelope encryption, Ed25519 JWT relay auth. Zero cost if you don't import it, tree-shakes completely.
|
|
361
|
-
|
|
362
|
-
```ts
|
|
363
|
-
import { connect, setProvider } from "@secondlayer/stacks/connect";
|
|
364
|
-
import { WalletConnectProvider, showModal } from "@secondlayer/stacks/connect/walletconnect";
|
|
365
|
-
|
|
366
|
-
const wc = new WalletConnectProvider({
|
|
367
|
-
projectId: "your-reown-project-id", // from cloud.reown.com
|
|
368
|
-
metadata: { name: "My App", description: "...", url: "https://myapp.com", icons: [] },
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// Restore existing session or pair new one
|
|
372
|
-
if (!wc.restore()) {
|
|
373
|
-
const { uri, approval } = await wc.pair();
|
|
374
|
-
showModal({ wcUri: uri, onClose: () => {} });
|
|
375
|
-
await approval;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
setProvider(wc);
|
|
379
|
-
const { addresses } = await connect();
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
The built-in modal shows browser extension wallets alongside the WC QR code — users pick whichever they prefer.
|
|
383
|
-
|
|
384
|
-
## Bundle Size
|
|
385
|
-
|
|
386
|
-
Measured with `bun build --minify --target=browser`, gzipped. Compared against stacks.js v7.3.1 + @stacks/connect v8.2.4.
|
|
387
|
-
|
|
388
|
-
```
|
|
389
|
-
@secondlayer/stacks stacks.js + connect
|
|
390
|
-
─────────────────── ───────────────────
|
|
391
|
-
SDK (gzipped) 23.8 KB 189 KB
|
|
392
|
-
+ Connect +7.9 KB +347 KB
|
|
393
|
-
+ WalletConnect v2 +25.5 KB (included)
|
|
394
|
-
─────────────────── ───────────────────
|
|
395
|
-
Total 46.1 KB 536 KB ← 11.6x
|
|
396
|
-
Dependencies 8 294 ← 37x
|
|
397
|
-
node_modules 7 MB 351 MB ← 50x
|
|
398
|
-
Polyfills needed none Buffer, crypto
|
|
399
|
-
Packages to install 1 5+
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
8 runtime deps, all `@noble`/`@scure`: `@noble/hashes`, `@noble/secp256k1`, `@noble/curves`, `@noble/ciphers`, `@scure/base`, `@scure/bip32`, `@scure/bip39`, `@scure/btc-signer`. `ai` and `zod` are optional peers, pulled in only by the deprecated `/tools` entries.
|
|
403
|
-
|
|
404
|
-
Connect and WalletConnect are separate entry points — import only what you use. An app that just reads contracts pays 23.8 KB. Full wallet connection + WC v2 pays 46.1 KB. The equivalent stacks.js setup is 536 KB regardless.
|
|
76
|
+
## Guides
|
|
77
|
+
|
|
78
|
+
- [Clarinet simnet](https://stacks.secondlayer.tools/guide/simnet)
|
|
79
|
+
- [Fee tiers](https://stacks.secondlayer.tools/guide/fees)
|
|
80
|
+
- [Wait for confirmation](https://stacks.secondlayer.tools/guide/confirmation)
|
|
81
|
+
- [Errors](https://stacks.secondlayer.tools/guide/errors)
|
|
82
|
+
- [Bitcoin addresses](https://stacks.secondlayer.tools/guide/bitcoin-addresses)
|
|
83
|
+
- [PoX-5 Bitcoin Staking](https://stacks.secondlayer.tools/guide/pox5)
|
|
84
|
+
- [Nonce management](https://stacks.secondlayer.tools/guide/nonces)
|
|
85
|
+
- [Bitcoin SPV](https://stacks.secondlayer.tools/guide/bitcoin-spv)
|
|
86
|
+
- [WalletConnect v2](https://stacks.secondlayer.tools/guide/walletconnect)
|
|
87
|
+
- [Bundle size](https://stacks.secondlayer.tools/)
|
|
@@ -189,7 +189,7 @@ interface StxLockSpec {
|
|
|
189
189
|
minAmount?: bigint;
|
|
190
190
|
}
|
|
191
191
|
/** Scope to contracts conforming to a trait/standard (e.g. "sip-010") instead
|
|
192
|
-
* of a fixed contract. Index + Subgraphs +
|
|
192
|
+
* of a fixed contract. Index + Subgraphs + Webhooks; Streams has no
|
|
193
193
|
* trait resolution — `toStreamsParams()` throws if set. */
|
|
194
194
|
type TraitScope = {
|
|
195
195
|
trait?: string
|
|
@@ -300,7 +300,7 @@ type SpecFor<T extends ChainEventFilterType> = Extract<ChainEventFilterSpec, {
|
|
|
300
300
|
type: T
|
|
301
301
|
}>;
|
|
302
302
|
/** The members expressible as subgraph sources (everything but the
|
|
303
|
-
*
|
|
303
|
+
* Webhooks-only sBTC lifecycle types). Instantiating
|
|
304
304
|
* `ChainEventFilter<SubgraphMemberType>` keeps `toSubgraphSource` present:
|
|
305
305
|
* the conditional in `ProjectionsFor` evaluates over this whole union
|
|
306
306
|
* (non-distributive at an instantiated site), and every member qualifies. */
|
|
@@ -310,7 +310,7 @@ type SubgraphMemberType = Exclude<ChainEventFilterType, `sbtc_${string}`>;
|
|
|
310
310
|
type SubgraphSourceSpec = Extract<ChainEventFilterSpec, {
|
|
311
311
|
type: SubgraphMemberType
|
|
312
312
|
}>;
|
|
313
|
-
/** Wire shape of a chain trigger (
|
|
313
|
+
/** Wire shape of a chain trigger (Webhooks), derived per member from
|
|
314
314
|
* the spec: same fields, with `bigint` amounts stringified (uint128 exceeds
|
|
315
315
|
* JS safe integers) and the type-only `abi`/`prints` decorations dropped.
|
|
316
316
|
* Structurally assignable to the SDK's `ChainTrigger` union. */
|
|
@@ -189,7 +189,7 @@ interface StxLockSpec {
|
|
|
189
189
|
minAmount?: bigint;
|
|
190
190
|
}
|
|
191
191
|
/** Scope to contracts conforming to a trait/standard (e.g. "sip-010") instead
|
|
192
|
-
* of a fixed contract. Index + Subgraphs +
|
|
192
|
+
* of a fixed contract. Index + Subgraphs + Webhooks; Streams has no
|
|
193
193
|
* trait resolution — `toStreamsParams()` throws if set. */
|
|
194
194
|
type TraitScope = {
|
|
195
195
|
trait?: string
|
|
@@ -300,7 +300,7 @@ type SpecFor<T extends ChainEventFilterType> = Extract<ChainEventFilterSpec, {
|
|
|
300
300
|
type: T
|
|
301
301
|
}>;
|
|
302
302
|
/** The members expressible as subgraph sources (everything but the
|
|
303
|
-
*
|
|
303
|
+
* Webhooks-only sBTC lifecycle types). Instantiating
|
|
304
304
|
* `ChainEventFilter<SubgraphMemberType>` keeps `toSubgraphSource` present:
|
|
305
305
|
* the conditional in `ProjectionsFor` evaluates over this whole union
|
|
306
306
|
* (non-distributive at an instantiated site), and every member qualifies. */
|
|
@@ -310,7 +310,7 @@ type SubgraphMemberType = Exclude<ChainEventFilterType, `sbtc_${string}`>;
|
|
|
310
310
|
type SubgraphSourceSpec = Extract<ChainEventFilterSpec, {
|
|
311
311
|
type: SubgraphMemberType
|
|
312
312
|
}>;
|
|
313
|
-
/** Wire shape of a chain trigger (
|
|
313
|
+
/** Wire shape of a chain trigger (Webhooks), derived per member from
|
|
314
314
|
* the spec: same fields, with `bigint` amounts stringified (uint128 exceeds
|
|
315
315
|
* JS safe integers) and the type-only `abi`/`prints` decorations dropped.
|
|
316
316
|
* Structurally assignable to the SDK's `ChainTrigger` union. */
|
package/dist/src/index.d.cts
CHANGED
|
@@ -187,7 +187,7 @@ interface StxLockSpec {
|
|
|
187
187
|
minAmount?: bigint;
|
|
188
188
|
}
|
|
189
189
|
/** Scope to contracts conforming to a trait/standard (e.g. "sip-010") instead
|
|
190
|
-
* of a fixed contract. Index + Subgraphs +
|
|
190
|
+
* of a fixed contract. Index + Subgraphs + Webhooks; Streams has no
|
|
191
191
|
* trait resolution — `toStreamsParams()` throws if set. */
|
|
192
192
|
type TraitScope = {
|
|
193
193
|
trait?: string
|
|
@@ -297,7 +297,7 @@ type ChainEventFilterSpec = StxTransferSpec | StxMintSpec | StxBurnSpec | StxLoc
|
|
|
297
297
|
type SpecFor<T extends ChainEventFilterType> = Extract<ChainEventFilterSpec, {
|
|
298
298
|
type: T
|
|
299
299
|
}>;
|
|
300
|
-
/** Wire shape of a chain trigger (
|
|
300
|
+
/** Wire shape of a chain trigger (Webhooks), derived per member from
|
|
301
301
|
* the spec: same fields, with `bigint` amounts stringified (uint128 exceeds
|
|
302
302
|
* JS safe integers) and the type-only `abi`/`prints` decorations dropped.
|
|
303
303
|
* Structurally assignable to the SDK's `ChainTrigger` union. */
|
package/dist/src/index.d.ts
CHANGED
|
@@ -187,7 +187,7 @@ interface StxLockSpec {
|
|
|
187
187
|
minAmount?: bigint;
|
|
188
188
|
}
|
|
189
189
|
/** Scope to contracts conforming to a trait/standard (e.g. "sip-010") instead
|
|
190
|
-
* of a fixed contract. Index + Subgraphs +
|
|
190
|
+
* of a fixed contract. Index + Subgraphs + Webhooks; Streams has no
|
|
191
191
|
* trait resolution — `toStreamsParams()` throws if set. */
|
|
192
192
|
type TraitScope = {
|
|
193
193
|
trait?: string
|
|
@@ -297,7 +297,7 @@ type ChainEventFilterSpec = StxTransferSpec | StxMintSpec | StxBurnSpec | StxLoc
|
|
|
297
297
|
type SpecFor<T extends ChainEventFilterType> = Extract<ChainEventFilterSpec, {
|
|
298
298
|
type: T
|
|
299
299
|
}>;
|
|
300
|
-
/** Wire shape of a chain trigger (
|
|
300
|
+
/** Wire shape of a chain trigger (Webhooks), derived per member from
|
|
301
301
|
* the spec: same fields, with `bigint` amounts stringified (uint128 exceeds
|
|
302
302
|
* JS safe integers) and the type-only `abi`/`prints` decorations dropped.
|
|
303
303
|
* Structurally assignable to the SDK's `ChainTrigger` union. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@secondlayer/stacks",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.3",
|
|
4
4
|
"description": "Typed Stacks client for TypeScript: read contracts, decode Clarity, build and send transactions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"url": "git+https://github.com/ryanwaits/secondlayer.git",
|
|
9
9
|
"directory": "packages/stacks"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://
|
|
11
|
+
"homepage": "https://stacks.secondlayer.tools",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"main": "./dist/src/index.cjs",
|
|
14
14
|
"module": "./dist/src/index.js",
|