stxer 0.6.1 → 0.8.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/CHANGELOG.md ADDED
@@ -0,0 +1,118 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `stxer` SDK are documented here. The format
4
+ loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
5
+ versioning is [SemVer](https://semver.org/).
6
+
7
+ ## 0.8.0
8
+
9
+ Pairs with **stxer-api ≥ schema-v2** (the simulator build that ships
10
+ `AdvanceBlocks` + `/tip` + the typed 409 / 410 status codes). Older
11
+ deployments respond `404` for `getSimulationTip` and reject
12
+ `AdvanceBlocks` step variants.
13
+
14
+ ### Added
15
+
16
+ - **`addAdvanceBlocks(request)` on `SimulationBuilder`** plus the
17
+ `{ AdvanceBlocks: AdvanceBlocksRequest }` variant on
18
+ `SimulationStepInput`. Synthesizes bitcoin / stacks blocks on top of
19
+ the simulation's pinned parent tip — used to model burn-block /
20
+ tenure boundaries (PoX cycles, locked-STX unlock, time-locked
21
+ redemptions, bridge contract finalization). See the
22
+ [`AdvanceBlocks` section in the README](./README.md#advanceblocks-burn-block-scenarios)
23
+ and the worked examples under `src/sample/`.
24
+ - **`getSimulationTip(sessionId, options?)`** — reads the current tip
25
+ of a simulation session. Returns the latest synthetic tip when at
26
+ least one `AdvanceBlocks` step has run (`synthetic: true`, includes
27
+ `vrf_seed` and `tenure_change`); otherwise returns the parent
28
+ metadata pinned at session start.
29
+ - **Typed `SimulationError`** thrown by every `simulation-api.ts`
30
+ fetch wrapper. Carries `.status` (HTTP code), `.marker` (typed
31
+ `'simulation_busy' | 'simulation_outdated' | null`), and `.body`
32
+ (raw upstream message). Branch on `.marker` to distinguish a
33
+ retry-the-same-request 409 from a start-a-new-session 410. Pre-0.8.0
34
+ threw plain `Error`; the message format
35
+ (`${operation} (HTTP ${status}): ${body}`) is preserved so log
36
+ scrapers keep working.
37
+ - **`src/sim-helpers.ts`** — session-bound wrappers: `callContract`,
38
+ `getStxBalance`, `getFtBalance`, `getNonce`, `readDataVar`. Uses
39
+ `simulationBatchReads` underneath so demos can lean on typed reads
40
+ without hand-rolling the batch envelope.
41
+ - **`src/transaction.ts`** — `setSender`, `buildUnsignedContractCallHex`,
42
+ `ftPrincipal`, `ContractCallTxArgs`. Extracted from `simulation.ts`
43
+ and the old `sample/_helpers.ts` so consumers can build unsigned tx
44
+ hex without instantiating `SimulationBuilder`.
45
+ - **`src/bitcoin.ts`** — SPV / merkle-proof / forged-tx primitives used
46
+ by the bridge demos: `sha256`, `sha256d`, `hexToBytes`, `bytesToHex`,
47
+ `p2wpkhScript`, `p2pkhScript`, `opReturnScript`, `forgeBitcoinTx`,
48
+ `buildBitcoinHeader`, `singleTxMerkleRoot`, `merkleProof`,
49
+ `verifyMerkleProof`. Pairs with `addAdvanceBlocks`'
50
+ `burn_header_hashes` override for sBTC / Brotocol bridge scenarios.
51
+ - **`addTenureExtend(cause?)`** now accepts an optional
52
+ `TenureExtendCause` (`'Extended' | 'ExtendedRuntime' | ...`) so
53
+ callers can reset a single SIP-034 cost dimension.
54
+ - **Six new vitest demos** under `src/sample/*-vitest.test.ts`:
55
+ `locked-stx`, `sbtc-deposit`, `brotocol-pegin`, `hbtc-vault`,
56
+ `zest-borrow`, `granite-leverage`. **The `src/sample` directory is
57
+ intentionally excluded from the npm tarball** to keep package size
58
+ down — browse them on
59
+ [GitHub](https://github.com/stxer/stxer-sdk/tree/master/src/sample).
60
+
61
+ ### Changed (breaking)
62
+
63
+ - **`addTenureExtend()` zero-arg now emits the modern wire shape**
64
+ (`{ TenureExtend: { cause: 'Extended' } }`) instead of the legacy
65
+ `{ TenureExtend: [] }`. The TypeScript signature is back-compat —
66
+ zero-arg call sites keep compiling — but anyone snapshotting JSON
67
+ request bodies will see the new shape. The server still parses the
68
+ legacy `[]` form for raw consumers.
69
+ - **`SimulationStepResult.TenureExtend` summaries always carry
70
+ `{ cause }`.** Previously typed as `[] | { cause }`; the server
71
+ normalizes legacy `[]` inputs to `{ cause: 'Extended' }` at parse
72
+ time, so the summary echo can never carry `[]`. The narrower type
73
+ is now reflected on `TenureExtendStepSummary.TenureExtend`.
74
+ - **HTTP responses now propagate as 4xx where they used to be masked
75
+ as 200.** The stxer-api proxy was forwarding upstream
76
+ `simulation_busy:` / `simulation_outdated:` / 4xx bodies with status
77
+ 200; the proxy now forwards the upstream status verbatim. SDK
78
+ callers are unaffected (the fetch wrappers already threw on `!ok`),
79
+ but raw-curl consumers reading 200 as success will now see
80
+ exceptions on those paths. The body content is unchanged.
81
+ - **`BatchReadsResult.tip` renamed to `BatchReadsResult.index_block_hash`**
82
+ to match the wire field name.
83
+ - **Numeric u64 / u128 fields are typed as `U64` / `U128`** (`number |
84
+ string`) instead of plain `number` to avoid silent precision loss
85
+ above 2^53. Affected fields: `TransactionReceipt.stx_burned`
86
+ (u128), `ExecutionCost.{read_count,read_length,write_count,
87
+ write_length,runtime}` (u64), `AdvancedBlockSummary.{stacks_height,
88
+ block_time,burn_block_time}` (u64), `SimulationTipResponse.
89
+ {stacks_height,block_time,burn_block_time}` (u64),
90
+ `SimulationMetadata.block_height` (u64), `SidecarTip.
91
+ {block_height,block_time,burn_block_time,tenure_height}` (u64),
92
+ `TenureCost.*` (u64), `AdvanceBlocksRequest.bitcoin_interval_secs`
93
+ and `AdvanceBlocksStepEcho.bitcoin_interval_secs` (u64),
94
+ `CreateSimulationRequest.block_height` /
95
+ `InstantSimulationRequest.block_height` (u64). Use `BigInt(x)` to
96
+ normalize before arithmetic.
97
+ - **`ClarityVersion` (the SDK string-literal type) renamed to
98
+ `ClarityVersionName`** to disambiguate from the numeric enum
99
+ re-exported by `@stacks/transactions`. The old name is preserved as
100
+ a deprecated alias for one major; use `ClarityVersionName` for
101
+ `ClarityAbi.clarity_version` matches and `@stacks/transactions`'s
102
+ numeric `ClarityVersion` enum (`ClarityVersion.Clarity4` → `4`) for
103
+ builder calls.
104
+
105
+ ### Server-side note
106
+
107
+ This release is paired with a server-side schema bump (`v=2`).
108
+ Simulation sessions created against an older simulator are rejected
109
+ with **HTTP 410 `simulation_outdated:`** after the cutover; SDK callers
110
+ see this as `SimulationError` with `marker: 'simulation_outdated'` and
111
+ should start a new session. Sim ids are random per-session and never
112
+ expected to persist across releases by design.
113
+
114
+ ## 0.7.0
115
+
116
+ See `git log` for prior releases. 0.7.0 was the last release without
117
+ `AdvanceBlocks` / `getSimulationTip` and treated all upstream errors as
118
+ plain `Error`.
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
- # stxer SDK
1
+ # STXER SDK
2
2
 
3
3
  A powerful SDK for Stacks blockchain that provides transaction simulation, batch operations, contract AST parsing, and chain tip information.
4
4
 
5
+ Pairs with the [stxer-api](https://api.stxer.xyz) (mainnet) and
6
+ [testnet-api](https://testnet-api.stxer.xyz). See
7
+ [`CHANGELOG.md`](./CHANGELOG.md) for release notes.
8
+
5
9
  ## Installation
6
10
 
7
11
  ```bash
@@ -10,6 +14,30 @@ npm install stxer
10
14
  yarn add stxer
11
15
  ```
12
16
 
17
+ ## When to use what
18
+
19
+ Pick the right tool for the task — these are not interchangeable:
20
+
21
+ | Goal | Use |
22
+ |---|---|
23
+ | Wallet preview / pre-broadcast safety check (single tx, no session, **no debug tracing**) | [`instantSimulation`](#instant-simulation) |
24
+ | Quick scripted multi-step simulation, browsable in the [stxer debug UI](https://stxer.xyz) | [`SimulationBuilder`](#1-transaction-simulation-v2-api) |
25
+ | CI / Vitest contract test with custom assertions on per-step results | [`createSimulationSession`](#session-based-simulation) + [`submitSimulationSteps`](#session-based-simulation) + [`sim-helpers`](#sim-helpers-callcontract-getstxbalance-getftbalance-getnonce-readdatavar) |
26
+ | Burn-block / tenure scenarios (PoX cycles, locked-STX unlock, time-locked redemptions) | [`addAdvanceBlocks`](#advanceblocks-burn-block-scenarios) + [`getSimulationTip`](#advanceblocks-burn-block-scenarios) |
27
+ | Bridge / SPV peg-in (sBTC, Brotocol) | raw API + [`bitcoin.ts`](#bitcoin-spv--bridge-primitives) + [`transaction.ts`](#transaction-builders) — see the bridge demos in [`src/sample`](https://github.com/stxer/stxer-sdk/tree/master/src/sample) |
28
+ | Bulk read against current chain state | [`batchRead`](#batch-operations) (sidecar) or [`simulationBatchReads`](#session-based-simulation) (against a session's forked state) |
29
+ | Read-only contract calls with ABI decoding | [`callReadonly`](#clarity-api) / [`readVariable`](#clarity-api) / [`readMap`](#clarity-api) |
30
+
31
+ `instantSimulation` is intentionally transient — it has **no debug
32
+ tracing** and the simulation isn't viewable in the stxer UI. Use
33
+ sessions when you need either of those.
34
+
35
+ The bridge / `AdvanceBlocks` patterns deliberately bypass
36
+ `SimulationBuilder` because they need typed access to per-step results
37
+ (builder only returns the final `simulation_id`). The decision table
38
+ above is the canonical mapping; the in-tree bridge vitest demos are
39
+ the canonical worked examples.
40
+
13
41
  ## Features
14
42
 
15
43
  ### 1. Transaction Simulation (V2 API)
@@ -42,7 +70,7 @@ const simulationId = await SimulationBuilder.new({
42
70
  contract_name: 'my-contract',
43
71
  source_code: '(define-public (hello) (ok "world"))',
44
72
  deployer: 'SP...', // Optional: overrides default sender
45
- clarity_version: 4, // Optional: Clarity1, Clarity2, Clarity3, or Clarity4
73
+ clarity_version: 4, // 1 / 2 / 3 / 4 — or `ClarityVersion.Clarity4` from `@stacks/transactions`
46
74
  })
47
75
  .run();
48
76
 
@@ -51,10 +79,19 @@ const simulationId = await SimulationBuilder.new({
51
79
 
52
80
  #### Advanced V2 Step Types
53
81
 
54
- The SDK supports all V2 simulation step types:
82
+ The SDK supports all V2 simulation step types. Three of them — `Transaction`, `Eval`, and `EvalReadonly` (a `Reads` sub-type) — all execute Clarity but differ on side-effects, cost, and what they emit. Pick by **what you need to come out of the step**:
83
+
84
+ | Step | Side effects | Receipt + events | Fee / nonce | Post-conditions | When to use |
85
+ |---|---|---|---|---|---|
86
+ | `Transaction` | full tx execution | ✅ `TransactionReceipt` | ✅ | ✅ | Simulating a real user action; need a receipt; want PCs enforced |
87
+ | `Eval` | **write access** | ❌ | ❌ | ❌ | Stub state mid-session, run code with side-effects without tx ceremony |
88
+ | `EvalReadonly` (in `Reads`) | none — analyzer-enforced read-only | ❌ | ❌ | ❌ | Read-only contract calls / projections / custom expressions that need sender-context |
89
+
90
+ Cost order, cheapest first: `EvalReadonly` < `Eval` < `Transaction`. Use the cheapest variant that gives you what you need — running a `(var-get counter)` projection through `Eval` is wasteful when an `EvalReadonly` (or a plain `DataVar` read) gets the same answer for less.
55
91
 
56
92
  ```typescript
57
- import { SimulationBuilder, ClarityVersion } from 'stxer';
93
+ import { SimulationBuilder } from 'stxer';
94
+ import { ClarityVersion } from '@stacks/transactions';
58
95
 
59
96
  const simulationId = await SimulationBuilder.new()
60
97
  .withSender('SP...')
@@ -77,11 +114,11 @@ const simulationId = await SimulationBuilder.new()
77
114
  clarity_version: ClarityVersion.Clarity4,
78
115
  })
79
116
 
80
- // === Eval Steps ===
81
- // Read state
82
- .addEvalCode('SP...contract', '(var-get counter)')
83
-
84
- // Modify state
117
+ // === Eval Step (write access, no tx ceremony) ===
118
+ // Use Eval when you need to mutate contract state WITHOUT a real tx —
119
+ // no fee, no nonce, no post-conditions, no receipt; just the Clarity
120
+ // value or err. For pure reads, prefer the `Reads` batch below
121
+ // (`EvalReadonly` is cheaper and analyzer-enforced read-only).
85
122
  .addEvalCode('SP...contract', '(var-set counter u100)')
86
123
 
87
124
  // === SetContractCode Step ===
@@ -93,22 +130,61 @@ const simulationId = await SimulationBuilder.new()
93
130
  })
94
131
 
95
132
  // === Reads Batch Step ===
96
- // Batch multiple read operations in a single step
133
+ // Batch multiple read operations into one step. `EvalReadonly` is the
134
+ // sender-context read variant — analyzer-enforced no writes; use it
135
+ // for read-only contract calls / projections / custom expressions
136
+ // that need a specific sender principal in scope.
97
137
  .addReads([
98
138
  { DataVar: ['SP...contract', 'counter'] },
99
139
  { DataVar: ['SP...contract', 'enabled'] },
100
140
  { StxBalance: 'SP...' },
101
141
  { Nonce: 'SP...' },
102
142
  { EvalReadonly: ['SP...', '', 'SP...contract', '(get-counter)'] },
143
+ {
144
+ EvalReadonly: [
145
+ 'SP...',
146
+ '',
147
+ 'SP...oracle',
148
+ "(contract-call? .oracle get-value 'STX-USD)",
149
+ ],
150
+ },
103
151
  ])
104
152
 
105
153
  // === TenureExtend Step ===
106
- // Extend tenure (resets execution cost)
107
- .addTenureExtend()
154
+ // Extend tenure (resets execution cost). Defaults to 'Extended'
155
+ // (full reset); pass a SIP-034 cause to reset only one dimension.
156
+ .addTenureExtend('ExtendedRuntime')
157
+
158
+ // === AdvanceBlocks Step ===
159
+ // Synthesize bitcoin and stacks blocks on top of the pinned parent
160
+ // tip — used to model burn-block / tenure boundaries (bridge
161
+ // contracts, time-locked redemptions, locked-STX unlock).
162
+ .addAdvanceBlocks({ bitcoin_blocks: 1, stacks_blocks_per_bitcoin: 1 })
108
163
 
109
164
  .run();
110
165
  ```
111
166
 
167
+ #### Schema cutover (v0.8.0)
168
+
169
+ The 0.8.0 release coincides with a server-side schema bump for
170
+ simulation sessions. **Existing simulation session ids started before
171
+ the upgrade return `HTTP 410 Gone`** — start a fresh session via
172
+ `SimulationBuilder.run()` / `createSimulationSession()` to get a
173
+ schema-v=2 id. Sim ids are random per-call and not persisted across
174
+ releases by design, so this only affects in-flight callers.
175
+
176
+ The fetch wrappers in `simulation-api` now throw a typed
177
+ `SimulationError` (with `status` and `marker` fields) on non-2xx
178
+ responses; `marker` is `'simulation_busy'` (HTTP 409),
179
+ `'simulation_outdated'` (HTTP 410), or `null`. Pre-0.8.0 the SDK
180
+ threw a plain `Error` whose message embedded the body — message format
181
+ is unchanged so existing log scrapers keep working.
182
+
183
+ `addTenureExtend(cause?)` now accepts an optional `TenureExtendCause`.
184
+ The on-the-wire request shape is `{ TenureExtend: { cause } }`; the
185
+ legacy `{ TenureExtend: [] }` shape stays parseable on the server but
186
+ the SDK builder no longer emits it.
187
+
112
188
  #### Reads Batch Sub-Types
113
189
 
114
190
  The `Reads` step supports multiple read operation types:
@@ -170,6 +246,7 @@ import {
170
246
  createSimulationSession,
171
247
  submitSimulationSteps,
172
248
  getSimulationResult,
249
+ getSimulationTip,
173
250
  simulationBatchReads
174
251
  } from 'stxer';
175
252
 
@@ -188,18 +265,25 @@ const stepResults = await submitSimulationSteps(sessionId, {
188
265
  { DataVar: ['SP...contract', 'my-var'] },
189
266
  { StxBalance: 'SP...' }
190
267
  ]},
191
- { TenureExtend: [] }
268
+ { TenureExtend: { cause: 'Extended' } },
269
+ { AdvanceBlocks: { bitcoin_blocks: 1, stacks_blocks_per_bitcoin: 1 } }
192
270
  ]
193
271
  });
194
272
 
195
273
  console.log(`Executed ${stepResults.steps.length} steps`);
196
274
 
197
- // 3. Get full simulation results
275
+ // 3. Inspect the current tip — `synthetic` flips to `true` once
276
+ // `AdvanceBlocks` has run; `vrf_seed` and `tenure_change` are
277
+ // populated only on synthetic tips.
278
+ const tip = await getSimulationTip(sessionId);
279
+ console.log(tip.synthetic, tip.stacks_height, tip.burn_height);
280
+
281
+ // 4. Get full simulation results
198
282
  const result = await getSimulationResult(sessionId);
199
283
  console.log(result.metadata);
200
284
  console.log(result.steps);
201
285
 
202
- // 4. Batch reads from simulation state
286
+ // 5. Batch reads from simulation state
203
287
  const reads = await simulationBatchReads(sessionId, {
204
288
  vars: [['SP...contract', 'my-var']],
205
289
  maps: [['SP...contract', 'my-map', '0x...']],
@@ -220,6 +304,40 @@ const sessionId = await createSimulationSession(
220
304
  );
221
305
  ```
222
306
 
307
+ #### TransactionReceipt: failure signals & events
308
+
309
+ When a step lands as `{ Transaction: { Ok: receipt } }`, the engine ran the transaction to completion — but that does **not** mean the contract logic succeeded. There are four failure signals on or around a receipt; check them in this order:
310
+
311
+ | Signal | Where | Meaning |
312
+ |---|---|---|
313
+ | Outer `Err` | `Result.Transaction = { Err: string }` | Engine refused the tx (deserialization, etc.). No receipt is produced. |
314
+ | `post_condition_aborted: true` | on the receipt | Execution ran, a post-condition tripped, state was rolled back. |
315
+ | `vm_error: string` (without PC abort) | on the receipt | Clarity VM raised a runtime error (overflow, unwrap on `none`, etc.) or static analysis failed. |
316
+ | `(err uX)` inside `result` | on the receipt | Contract returned a Clarity error response. Not signalled by any flag — decode `result` to detect (response::err prefix is `08`). |
317
+
318
+ **`post_condition_aborted` and `vm_error` are not independent.** When a post-condition trips, the upstream sets `post_condition_aborted: true` *and* writes the PC abort reason into `vm_error` as a side effect — so `vm_error` is also non-null in that case. The reverse is not true: `vm_error` alone (with `post_condition_aborted: false`) means a VM/runtime/analysis failure that is *not* a PC abort. A clean way to narrow:
319
+
320
+ ```typescript
321
+ if (receipt.post_condition_aborted) {
322
+ // PC abort. receipt.vm_error holds the abort reason.
323
+ } else if (receipt.vm_error != null) {
324
+ // Runtime / analysis failure (not a PC abort).
325
+ } else if (receipt.result.startsWith('08')) {
326
+ // Contract returned (err uX). Decode receipt.result for the value.
327
+ } else {
328
+ // Clean success.
329
+ }
330
+ ```
331
+
332
+ **Events are JSON-encoded strings, not objects.** `receipt.events` is `string[]`; each entry is a JSON-encoded event payload. Call `JSON.parse(receipt.events[i])` to inspect — iterating without parsing yields opaque strings.
333
+
334
+ ```typescript
335
+ for (const ev of receipt.events) {
336
+ const event = JSON.parse(ev) as { type: string; contract_event?: { ... } };
337
+ if (event.type === 'contract_event') { /* handle print/log */ }
338
+ }
339
+ ```
340
+
223
341
  ### 3. Get Chain Tip
224
342
 
225
343
  Fetch the current chain tip information:
@@ -264,10 +382,10 @@ The SDK provides two approaches for efficient batch reading from the Stacks bloc
264
382
  #### Direct Batch Reading
265
383
 
266
384
  ```typescript
267
- import { batchRead } from 'stxer';
385
+ import { batchRead, type BatchReadsResult } from 'stxer';
268
386
 
269
387
  // Batch read variables and maps
270
- const result = await batchRead({
388
+ const result: BatchReadsResult = await batchRead({
271
389
  variables: [{
272
390
  contract: contractPrincipalCV(...),
273
391
  variableName: 'my-var'
@@ -283,6 +401,14 @@ const result = await batchRead({
283
401
  functionArgs: [/* clarity values */]
284
402
  }]
285
403
  });
404
+
405
+ // Result shape — `index_block_hash` matches the upstream wire field
406
+ // (the SDK previously aliased this as `tip`; renamed to remove the
407
+ // indirection).
408
+ result.index_block_hash; // string — the block the batch ran against
409
+ result.vars; // (ClarityValue | Error)[]
410
+ result.maps; // (ClarityValue | Error)[]
411
+ result.readonly; // (ClarityValue | Error)[]
286
412
  ```
287
413
 
288
414
  #### BatchProcessor for Queue-based Operations
@@ -416,64 +542,183 @@ const ast = await getContractAST({
416
542
  });
417
543
  ```
418
544
 
545
+ ## Samples
546
+
547
+ Runnable end-to-end examples live in
548
+ [`src/sample/`](https://github.com/stxer/stxer-sdk/tree/master/src/sample)
549
+ on GitHub. **The `src/sample/` directory is intentionally excluded from
550
+ the published npm tarball** to keep the package size down — clone the
551
+ repo to run them locally. Samples track `master` and may evolve faster
552
+ than published SDK versions; if you pin a specific SDK version, browse
553
+ the matching git tag.
554
+
555
+ ### Core flows
556
+
557
+ | Sample | What it demonstrates |
558
+ |---|---|
559
+ | [`counter.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/counter.ts) | Full Simulation v2 lifecycle without `SimulationBuilder` — every `SimulationStepInput` variant (Transaction / Eval / SetContractCode / Reads / TenureExtend), narrowing on `SimulationStepResult`, and `SimulationStepSummary`. |
560
+ | [`instant.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/instant.ts) | `instantSimulation` with all 7 `ReadStep` variants. |
561
+ | [`failure-modes.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/failure-modes.ts) | The four failure signals on a `TransactionReceipt`, with bug-zoo triggers for each. |
562
+ | [`batch-categories.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/batch-categories.ts) | Every `simulationBatchReads` category and the sidecar `batchRead`. |
563
+ | [`contract-vitest.test.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/contract-vitest.test.ts) | Vitest suite that drives a Clarity contract through Simulation v2 — copy-pasteable CI test template. Pinned fork point so assertions stay deterministic. |
564
+ | [`verify-types.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/verify-types.ts) | Runtime type-drift detector. Hits every endpoint, dereferences every documented field, asserts each value's runtime type matches the declared SDK type. Run it before publishing your client after upstream changes. |
565
+ | [`read.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/read.ts) | `batchRead`, `BatchProcessor`, and the high-level `clarity-api` helpers (`callReadonly`, `readVariable`, `readMap`). |
566
+
567
+ ### `AdvanceBlocks` + bridge / time-locked scenarios (0.8.0)
568
+
569
+ The vitest demos below are the canonical reference for the new
570
+ `addAdvanceBlocks` / `getSimulationTip` / `bitcoin.ts` / `sim-helpers`
571
+ patterns. All target real **mainnet** contracts at pinned settled
572
+ blocks — every assertion is exact.
573
+
574
+ | Sample | What it demonstrates |
575
+ |---|---|
576
+ | [`locked-stx-vitest.test.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/locked-stx-vitest.test.ts) | PoX-4 locked-STX unlock: read `(stx-account ...)`, attempt pre-unlock transfer (`err u1`), `addAdvanceBlocks` past `unlock_height`, transfer the now-released amount. Includes the parent-burn-probe trick (advance with `bitcoin_blocks: 0` first to learn the parent burn height before computing N). |
577
+ | [`sbtc-deposit-vitest.test.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/sbtc-deposit-vitest.test.ts) | sBTC bridge deposit, end-to-end. Uses `forgeBitcoinTx` + `AdvanceBlocks { burn_header_hashes: ... }` so `(get-burn-block-info? header-hash …)` resolves to a value we control. |
578
+ | [`brotocol-pegin-vitest.test.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/brotocol-pegin-vitest.test.ts) | Brotocol BTC peg-in. Forges a Bitcoin tx + 2-tx Merkle proof + 80-byte block header; calls `finalize-peg-in-0`; asserts mint and replay rejection. The full `bitcoin.ts` SPV pipeline. |
579
+ | [`hbtc-vault-vitest.test.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/hbtc-vault-vitest.test.ts) | hBTC yield-bearing vault: deposit, request-redeem, `AdvanceBlocks +1 day` to cross the rewarder window, `log-reward`, fund-claim, `AdvanceBlocks +3 days` to clear the cooldown, redeem, transfer. Exact yield arithmetic. |
580
+ | [`zest-borrow-vitest.test.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/zest-borrow-vitest.test.ts) | Zest V2 lend / borrow / repay with a forged Pyth v4 price update. Patches `pyth-pnau-decoder-v3` via `SetContractCode` to bypass the Wormhole quorum / Merkle / data-source checks; `AdvanceBlocks +1` so storage's freshness gate has a synthetic-tip block_time. |
581
+ | [`granite-leverage-vitest.test.ts`](https://github.com/stxer/stxer-sdk/blob/master/src/sample/granite-leverage-vitest.test.ts) | Granite leverage open with a forged Pyth v3 price update. Same patch-decoder pattern as Zest, applied through Granite's gl-api → gl-oracle → pyth-oracle-v3 chain. |
582
+
583
+ Run any of them locally by cloning the SDK repo and:
584
+
585
+ ```bash
586
+ pnpm install
587
+ pnpm sample:counter # or sample:instant / failure-modes / batch-categories / verify-types
588
+ pnpm sample:vitest # run the Vitest sample suite (counter + 6 bridge / locked-STX demos)
589
+ ```
590
+
419
591
  ## API Reference
420
592
 
421
593
  ### Constants
422
594
 
423
- - `STXER_API_MAINNET` - Mainnet API endpoint (https://api.stxer.xyz)
424
- - `STXER_API_TESTNET` - Testnet API endpoint (https://testnet-api.stxer.xyz)
425
- - `DEFAULT_STXER_API` - Default API endpoint (same as STXER_API_MAINNET)
595
+ - `STXER_API_MAINNET` Mainnet API endpoint (`https://api.stxer.xyz`)
596
+ - `STXER_API_TESTNET` Testnet API endpoint (`https://testnet-api.stxer.xyz`)
597
+ - `DEFAULT_STXER_API` Default API endpoint (same as `STXER_API_MAINNET`)
426
598
 
427
- ### Simulation Builder (High-Level)
599
+ ### Simulation Builder (high-level)
428
600
 
429
- - `SimulationBuilder.new(options)` - Create a new simulation builder
430
- - `builder.useBlockHeight(height)` - Set block height for simulation
431
- - `builder.withSender(address)` - Set default sender address
601
+ - `SimulationBuilder.new(options)` Create a new simulation builder
602
+ - `builder.useBlockHeight(height)` Pin the parent block to fork at
603
+ - `builder.withSender(address)` Default sender address for unsigned txs
432
604
 
433
605
  **Transaction Steps:**
434
- - `builder.addContractCall(params)` - Add a contract call step
435
- - `builder.addSTXTransfer(params)` - Add an STX transfer step
436
- - `builder.addContractDeploy(params)` - Add a contract deployment step
606
+ - `builder.addContractCall(params)` Add a contract call step
607
+ - `builder.addSTXTransfer(params)` Add an STX transfer step
608
+ - `builder.addContractDeploy(params)` Add a contract deployment step
437
609
 
438
610
  **V2 Step Types:**
439
- - `builder.addEvalCode(contractId, code)` - Add arbitrary code evaluation (with state modification)
440
- - `builder.addSetContractCode(params)` - Directly set contract code without transaction
441
- - `builder.addReads(reads[])` - Batch read operations in a single step
442
- - `builder.addTenureExtend()` - Extend tenure (resets execution cost)
611
+ - `builder.addEvalCode(contractId, code)` Arbitrary code evaluation with write access
612
+ - `builder.addSetContractCode(params)` Replace contract code without a transaction
613
+ - `builder.addReads(reads[])` Batch read operations interleaved with other steps
614
+ - `builder.addTenureExtend(cause?)` Reset execution cost. Default `'Extended'` resets all dimensions; pass an explicit `TenureExtendCause` to reset only one SIP-034 dimension
615
+ - `builder.addAdvanceBlocks(request)` — *(0.8.0)* Synthesize bitcoin / stacks blocks on the fork. See the [`AdvanceBlocks` section](#advanceblocks-burn-block-scenarios)
443
616
 
444
617
  **Execution:**
445
- - `builder.run()` - Execute the simulation and return simulation ID
446
-
447
- ### Programmatic Simulation APIs (Low-Level)
448
-
449
- **Instant Simulation:**
450
- - `instantSimulation(request, options?)` - Simulate a single transaction without session
451
-
452
- **Session Management:**
453
- - `createSimulationSession(options?, apiOptions?)` - Create a new simulation session
454
- - `submitSimulationSteps(sessionId, request, options?)` - Submit steps to a session
455
- - `getSimulationResult(sessionId, options?)` - Get full simulation results
456
- - `simulationBatchReads(sessionId, request, options?)` - Batch reads from simulation state
457
-
458
- ### Chain Tip
459
-
460
- - `getTip(options?)` - Fetch current chain tip information
618
+ - `builder.run()` Submit the session and return the `simulation_id`. Note: returns only the id; if you need typed per-step results, use the low-level API + `sim-helpers` instead
619
+
620
+ ### Programmatic simulation APIs (low-level)
621
+
622
+ **Instant simulation:**
623
+ - `instantSimulation(request, options?)` Simulate a single transaction without a session. Transient: **no debug tracing**, not viewable in the stxer UI
624
+
625
+ **Session management:**
626
+ - `createSimulationSession(options?, apiOptions?)` Create a new session, returns `id: string`
627
+ - `submitSimulationSteps(sessionId, request, options?)` Submit one or more steps; returns typed per-step results
628
+ - `getSimulationResult(sessionId, options?)` Get full session results (metadata + step summaries)
629
+ - `getSimulationTip(sessionId, options?)` *(0.8.0)* Read the current synthetic tip after `AdvanceBlocks` steps. See the [`AdvanceBlocks` section](#advanceblocks-burn-block-scenarios)
630
+ - `simulationBatchReads(sessionId, request, options?)` — Batch-read the session's forked state without consuming a step slot
631
+
632
+ **Errors:**
633
+ - `class SimulationError extends Error` — *(0.8.0)* Typed error thrown by every fetch wrapper. Fields:
634
+ - `.status: number` — HTTP status (409 retry, 410 start-new-session, 400 validation, 404 not found, ...)
635
+ - `.marker: 'simulation_busy' | 'simulation_outdated' | null` — server-side classification
636
+ - `.body: string` — raw upstream message
637
+ - `.message: string` — formatted as `"${operation} (HTTP ${status}): ${body}"`
638
+ - `type SimulationErrorMarker` — *(0.8.0)* Exported union for `.marker`
639
+
640
+ ### Sim helpers (`callContract`, `getStxBalance`, `getFtBalance`, `getNonce`, `readDataVar`)
641
+
642
+ *New in 0.8.0.* Session-bound wrappers around `simulationBatchReads`
643
+ that produce typed reads without hand-rolling the batch envelope. Useful
644
+ for vitest-style tests that interleave reads with mutating steps.
645
+
646
+ - `callContract(sessionId, args, options?)` — Build an unsigned contract-call tx, submit it as one step, decode the result. Returns `{ result, vmError, pcAborted, events, txid, executionCost }` for direct `expect(...)` assertions
647
+ - `getStxBalance(sessionId, principal, options?): Promise<bigint>`
648
+ - `getFtBalance(sessionId, contractAndToken, principal, options?): Promise<bigint>`
649
+ - `getNonce(sessionId, principal, options?): Promise<bigint>` — session-bound (do not confuse with the Hiro-API `getOnChainNonce` in `src/sample/_helpers.ts`)
650
+ - `readDataVar(sessionId, contractId, varName, options?)` — Returns the deserialized Clarity value
651
+ - `parseSimulationEvent(eventJson)` — Parse one entry from `TransactionReceipt.events[]` into a typed `SimulationEvent`
652
+
653
+ ### Transaction builders
654
+
655
+ *New in 0.8.0.* Build unsigned transaction hex without `SimulationBuilder`
656
+ — useful when you want to control sender via the simulator's
657
+ no-signature trust model.
658
+
659
+ - `setSender(tx, sender)` — Rewrite an unsigned `StacksTransactionWire`'s spending condition so the simulator treats `sender` as the origin. Mutates and returns `tx`
660
+ - `buildUnsignedContractCallHex(args)` — `args: { sender, contract, functionName, functionArgs, fee?, nonce? }` → hex string
661
+ - `ftPrincipal(contractAndToken: 'SP....contract::token-name'): ClarityValue` — Build a `(contract-of (use-trait <ft-trait>))` literal for FT contract calls
662
+
663
+ ### Bitcoin SPV / bridge primitives
664
+
665
+ *New in 0.8.0.* Forge Bitcoin transactions and SPV proofs for bridge
666
+ peg-in scenarios. Pairs with `addAdvanceBlocks`'
667
+ `burn_header_hashes` override so on-chain
668
+ `(get-burn-block-info? header-hash …)` resolves to a value you control.
669
+
670
+ - `forgeBitcoinTx(opts)` — Build a Bitcoin tx with chosen inputs / outputs / OP_RETURN. Returns `{ rawTx, txid, inputs, outputs }`
671
+ - `buildBitcoinHeader(opts)` — 80-byte BTC block header with chosen prev-hash / merkle root / time / bits / nonce. Returns `{ rawHeader, hash }` where `hash` is the double-sha256 (display order, ready for `burn_header_hashes`)
672
+ - `singleTxMerkleRoot(txid: Uint8Array): Uint8Array` — 1-tx merkle root (the txid itself)
673
+ - `merkleProof(txids, txIndex): { hashes: Uint8Array[], treeDepth: number }`
674
+ - `verifyMerkleProof(txid, proof, root): boolean`
675
+ - `p2wpkhScript(pubKeyHash20)` / `p2pkhScript(pubKeyHash20)` / `opReturnScript(data)` — scriptPubKey builders
676
+ - `sha256(data)` / `sha256d(data)` / `hexToBytes(s)` / `bytesToHex(b)`
677
+
678
+ ### `AdvanceBlocks` (burn-block scenarios)
679
+
680
+ *New in 0.8.0.* Synthesize bitcoin / stacks blocks on top of the
681
+ simulation's pinned parent tip — used to model burn-block / tenure
682
+ boundaries (PoX cycles, locked-STX unlock, time-locked redemptions,
683
+ bridge contract finalization).
684
+
685
+ - `builder.addAdvanceBlocks(request)` — builder method
686
+ - `submitSimulationSteps(id, { steps: [{ AdvanceBlocks: ... }] }, opts)` — raw step
687
+ - `getSimulationTip(id, opts?)` — read the current synthetic tip; `synthetic: true` after at least one `AdvanceBlocks` step has run
688
+ - `AdvanceBlocksRequest` (key fields):
689
+ - `bitcoin_blocks: number` — burn blocks to synthesize
690
+ - `stacks_blocks_per_bitcoin: number`
691
+ - `bitcoin_interval_secs?: U64` — burn-block-time delta; defaults to 600s
692
+ - `burn_header_hashes?: Record<string, string>` — per-burn-index hash override (32-byte hex), keyed by 0-based index
693
+ - `pox_addrs?: Record<string, [PoxAddrInput[], U128]>` — per-burn-index PoX-payout override
694
+ - `vrf_seeds?: Record<string, string>` — per-burn-index VRF-seed override
695
+
696
+ For the canonical worked examples — including the parent-burn-probe
697
+ trick for computing N, the `burn_header_hashes` + Merkle-proof bridge
698
+ pattern, and the `SetContractCode` + `AdvanceBlocks` Pyth-decoder
699
+ pattern — see the
700
+ [bridge / time-locked vitest demos](#advanceblocks--bridge--time-locked-scenarios-080)
701
+ on GitHub.
702
+
703
+ ### Chain tip
704
+
705
+ - `getTip(options?)` — Global mainnet chain tip (sidecar). Distinct from `getSimulationTip` (per-session synthetic tip)
461
706
 
462
707
  ### Contract AST
463
708
 
464
- - `getContractAST({ contractId, stxerApi? })` - Fetch on-chain contract AST
465
- - `parseContract({ sourceCode, contractId, clarityVersion?, epoch?, stxerApi? })` - Parse source code to AST
709
+ - `getContractAST({ contractId, stxerApi? })` Fetch on-chain contract AST
710
+ - `parseContract({ sourceCode, contractId, clarityVersion?, epoch?, stxerApi? })` Parse source code to AST. `clarityVersion` is `ClarityVersionName` (`'Clarity1' | ... | 'Clarity4'`) — distinct from `@stacks/transactions`'s numeric `ClarityVersion` enum
466
711
 
467
- ### Batch Operations
712
+ ### Batch operations
468
713
 
469
- - `batchRead(reads, options?)` - Execute batch read operations
470
- - `new BatchProcessor({ stxerAPIEndpoint?, batchDelayMs })` - Create a batch processor
714
+ - `batchRead(reads, options?)` Execute batch read operations against current chain state via the sidecar
715
+ - `new BatchProcessor({ stxerAPIEndpoint?, batchDelayMs })` — Queue-based batch processor for high-throughput read pipelines
471
716
 
472
717
  ### Clarity API
473
718
 
474
- - `callReadonly(params)` - Call a read-only contract function
475
- - `readVariable(params)` - Read a contract variable
476
- - `readMap(params)` - Read from a contract map
719
+ - `callReadonly(params)` Call a read-only contract function
720
+ - `readVariable(params)` Read a contract variable
721
+ - `readMap(params)` Read from a contract map
477
722
 
478
723
  ## Support
479
724
 
package/dist/ast.d.ts CHANGED
@@ -20,7 +20,7 @@ export declare function getContractAST(options: GetContractAstOptions): Promise<
20
20
  export interface ParseContractOptions extends AstOptions {
21
21
  sourceCode: string;
22
22
  contractId: string;
23
- clarityVersion?: '1' | '2' | '3' | '4';
23
+ clarityVersion?: '1' | '2' | '3' | '4' | '5';
24
24
  epoch?: ClarityEpoch;
25
25
  }
26
26
  /**
@@ -23,7 +23,8 @@ export interface BatchReads {
23
23
  index_block_hash?: string;
24
24
  }
25
25
  export interface BatchReadsResult {
26
- tip: string;
26
+ /** Index block hash the batch ran against. Matches the wire field name. */
27
+ index_block_hash: string;
27
28
  vars: (ClarityValue | Error)[];
28
29
  maps: (ClarityValue | Error)[];
29
30
  readonly: (ClarityValue | Error)[];