Burn-Block Timing
Why sBTC Pay measures time in Bitcoin burn blocks, not Stacks blocks, and why that matters after the Nakamoto upgrade.
Every time-sensitive piece of the contract — invoice expiry, refund window, subscription interval — is counted in burn blocks (Bitcoin blocks), not Stacks blocks. This page explains why, and why it matters specifically after the Stacks Nakamoto upgrade.
Two clocks, very different speeds
Stacks has two block heights you can reference from a Clarity contract:
stacks-block-height— increments every Stacks block. Post-Nakamoto: roughly every 5 seconds.burn-block-height— increments every Bitcoin block. Roughly every 10 minutes on mainnet, 5 minutes on testnet.
Before Nakamoto, these were tightly coupled (one Stacks block per Bitcoin block). After Nakamoto, Stacks blocks are fast — ~120× faster than Bitcoin blocks. That speed is great for dApp UX, but it's a trap for anything that measures calendar time.
The trap
Imagine a subscription with a "monthly" billing interval. You might naively write:
"A month is 30 × 24 × 60 / 5 = 8,640 Stacks blocks" (at 5 seconds per block)
Then you use stacks-block-height in your contract. Looks fine. Ships to mainnet. Works for a few months. Then, during a period of congestion or slow block production, the Stacks block time temporarily drops to, say, 3 seconds — and suddenly your "monthly" subscription bills every 18 days. Or, in an idle period when Stacks blocks slow to 8 seconds, it bills every 48 days.
Stacks block time is variable. Calendar time is not. Measuring calendar time in Stacks blocks is always wrong, and after Nakamoto the error is large.
The fix: count in Bitcoin blocks
Bitcoin's block time is tightly regulated by its difficulty adjustment. Over any meaningful window, Bitcoin blocks land at ~10-minute intervals. That makes burn-block-height a reliable clock for calendar-time logic.
So the contract defines:
- Daily: 144 burn blocks (24 × 60 / 10)
- Weekly: 1,008 burn blocks
- Monthly: 4,320 burn blocks (30 days)
These are approximate — Bitcoin blocks aren't exactly 10 minutes — but the error is small and bounded. Over a year, block-time drift is typically under 1–2%.
Testnet is faster
Where burn blocks are used
Every time reference in the sBTC Pay contract uses burn blocks:
- Invoice creation:
created-at= current burn block - Invoice expiry:
expires-at= created-at + expires-in-blocks - Subscription creation: next-payment-at = current burn block (due immediately)
- Subscription interval: next-payment-at = last-payment-at + interval-blocks
- Refund window: refund-deadline = first-payment-at + refund-window-blocks
What this means for integrators
If you ever need to compute "when will this be due?" in your own code, use the same approach:
- Read the relevant block height field (in burn blocks)
- Convert to wall-clock time using the network's average burn-block time (~600 sec mainnet, ~300 sec testnet)
The frontend already does this for you — dashboard and customer portal dates are derived exactly this way.
Don't convert with stacks-block-height