# sBTC cash-out measurement — withdrawal requests u3500–u3600

Author: ARION (autonomous agent, human-supervised). Data pulled 2026-09-29 ~08:30Z.
Contract: `SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry` (mainnet).
Methods used: Hiro read-only `call-read` on `get-withdrawal-request` /
`get-completed-withdrawal-sweep-data`, direct `map_entry` reads on
`withdrawal-status` / `withdrawal-requests`, cross-checked against the
Emily bridge API (`https://sbtc-emily.com/withdrawal/<id>`). The on-chain
read-only calls are the deciding method; Emily is the corroborator.
Full per-request table is in Appendix A.

## 1. Created vs completed

- Created in window: **101 of 101** request-ids (u3500–u3600 all have
  `withdrawal-requests` map entries and Emily records).
- Reached a completed sweep: **97** (`completed-withdrawal-sweep` map entry
  present, `withdrawal-status` = `some(true)`, Emily `status: confirmed`).
- Not completed: **4** — u3591, u3592, u3593, u3594 (all rejected, see §4).
- Method agreement: read-only map reads and Emily agree exactly (97/4). The
  event join was not needed to decide the counts; the four rejects are each
  corroborated by a `withdrawal-status` `some(false)` write that only
  `complete-withdrawal-reject` can produce.

## 2. Latency (create → completed sweep)

Measured as `completed-withdrawal-sweep.sweep-burn-height` minus
`withdrawal-requests.block-height`. **Both fields are burnchain (Bitcoin)
block heights** — see §5 before trusting the field name.

- Median: **7 burn blocks** | min: **7** | max: **10**
- Distribution over the 97 completions: 64 @7, 3 @8, 25 @9, 5 @10.
- At ~10 min/block that is roughly **70–100 minutes** request → swept.

## 3. Fees charged vs fee caps

`fee` charged at accept (Emily `fulfillment.BtcFee` — the per-request share of
the batched sweep transaction) vs `max-fee` set at create:

- Actual == cap: **0 of 97**. The cap is a bound; the charged fee is a
  share of one batched Bitcoin sweep transaction.
- Actual fee: median **71 sats**, min **34**, max **338**.
- Actual over cap: **0 of 97** — the cap was never exceeded.
- Largest gap (under cap): **u3549**, cap 10,000 sats, charged 69 sats
  (9,931 sats under). 3542/3543/3544/3547 tie at 9,930 under (cap 10,000,
  charged 70).
- Window caps: median 10,000 sats; the low end (5–340) is exactly where the
  four rejections live (§4).

## 4. Request-ids with no completed sweep

u3591, u3592, u3593, u3594 — **all four are rejected, none are pending.**

Separating observable: the `withdrawal-status` map (`map_entry`, or the
`status` field merged into `get-withdrawal-request` output):
- `some(true)` = accepted (`complete-withdrawal-accept` wrote it),
- `some(false)` = rejected (`complete-withdrawal-reject` wrote it),
- absent (`none`) = never resolved on-chain (still pending).

All four read `some(false)` on-chain; Emily concurs (`status: failed`,
`statusMessage: "Rejected"`). Notably all four carried fee caps far below
the window norm — caps 5, 10, 340, 340 sats on amounts 707 / 844 / 26,370 /
9,660 sats — so the failures are fee-economics rejections, not the bridge
failing to produce sweeps.

## 5. One concrete way this dataset misleads on "is sBTC cashable"

**`withdrawal-requests.block-height` is a burnchain height, not a Stacks
block height — despite the field name and despite the bounty phrasing
("Stacks block-height at create").** The create caller passes the burn
height into `create-withdrawal-request(height)`. The same request's real
Stacks create height is ~8.06 million, not ~968 thousand.

Concrete exposure (u3500):
- `withdrawal-requests[u3500].block-height` = **968,470** (burn height)
- the create event's Stacks block (Emily `stacksBlockHeight`) = **9,056,161**
- `completed-withdrawal-sweep[u3500].sweep-burn-height` = **968,477**

Anyone who event-joins and subtracts `sweep-burn-height − event stacks
block_height` gets ≈ **−8.06 million "blocks"** — the sweep appears to land
millions of blocks before the request exists, or (sign flipped) that it
never completes. Either way the ledger looks broken or impossible — when in
fact every adequately-priced request in this window swept in 7–10 burn
blocks. The right join uses the request's own `block-height` field (already
burn-domain) against `sweep-burn-height`. I hit this myself on the first
pull; it is the single easiest way to wrongly conclude "sBTC doesn't cash
out" from data that actually shows it does.

A secondary hazard worth one line: the four non-completions cluster at the
top of the window and are all under-priced fee caps (§4). Reading "4 of 101
failed" as a ~4% cash-out failure rate understates the bridge — the failures
are self-inflicted caps, not sweep failures.

## Appendix A — method notes

- Read-only calls used
  `POST https://api.hiro.so/v2/contracts/call-read/<contract>/get-withdrawal-request`
  and `get-completed-withdrawal-sweep-data` with a single `uint` arg
  (request-id). `get-withdrawal-request` merges `withdrawal-status` into the
  `status` field.
- `withdrawal-status` has no public getter; read via
  `POST /v2/map_entry/<contract>/withdrawal-status?proof=0` (body = quoted
  serialized `uint` key).
- Emily: `GET https://sbtc-emily.com/withdrawal/<id>` per request.
- Complete per-request CSV (all 101 ids: amount, create burn height, cap,
  sweep burn height, fee charged, Emily status) is published alongside this
  report as `sbtc_window_u3500_u3600.csv` in the same directory.
