|
| 1 | +--- |
| 2 | +description: >- |
| 3 | + How to check a bond's Bitcoin lock address before you fund it, what you need |
| 4 | + to spend it later, and how to reclaim your BTC once the timelock passes. |
| 5 | +--- |
| 6 | + |
| 7 | +# Verifying and Reclaiming Your Locked Bitcoin |
| 8 | + |
| 9 | +On a native BTC bond your Bitcoin stays in an output you control, guarded by a timelock. This page covers the two moments that matter for your funds: checking the lock address before you send anything to it, and spending the output back to yourself once the timelock passes. For leaving before the timelock, see [Ending or Changing a Bond Position](ending-or-changing-a-bond-position.md). |
| 10 | + |
| 11 | +## Before you fund: check the lock address |
| 12 | + |
| 13 | +The lock address is deterministic. It is built from four inputs: |
| 14 | + |
| 15 | +* your Stacks address, |
| 16 | +* the output's unlock height, at or above the bond's minimum, |
| 17 | +* your `staker-unlock-bytes`, by default `<your Bitcoin public key> OP_CHECKSIG`, |
| 18 | +* the bond's `early-unlock-bytes`, published on-chain when the bond is set up. |
| 19 | + |
| 20 | +Check the address against the contract before funding. The contract's `construct-lockup-output-script` builds the same output from the same inputs. If it disagrees with the address your wallet or tool shows, do not fund. The code is under [Cross-check the script before funding](verifying-and-reclaiming-your-locked-bitcoin.md#cross-check-the-script-before-funding). |
| 21 | + |
| 22 | +{% hint style="danger" %} |
| 23 | +**Check before you send.** BTC sent to an address that does not match the contract's script cannot be registered for the bond, and whether you can spend it depends entirely on what that address actually encodes. |
| 24 | +{% endhint %} |
| 25 | + |
| 26 | +## What you need to spend it later |
| 27 | + |
| 28 | +Spending the output needs the lock script, the output itself, and your Bitcoin key. Save the lock script when you register, and keep the lock transaction ID: it identifies the output to spend. If you lose the lock script, you can rebuild it from its four inputs: |
| 29 | + |
| 30 | +| Input | Where to find it again | |
| 31 | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | |
| 32 | +| Stacks address | Your Stacks wallet | |
| 33 | +| Unlock height | The arguments of your `register-for-bond` transaction | |
| 34 | +| `staker-unlock-bytes` | The same transaction. With the default unlock bytes, rebuild them from the public key of the Bitcoin key you registered with | |
| 35 | +| `early-unlock-bytes` | The bond's on-chain record | |
| 36 | + |
| 37 | +The contract keeps none of these in its own state, which is why the registration transaction matters. |
| 38 | + |
| 39 | +## Reclaim after the timelock |
| 40 | + |
| 41 | +Once Bitcoin passes the output's unlock height, you spend it alone through the timelock branch. There is no co-signer, no Stacks transaction and no deadline: the output stays spendable by you from then on. |
| 42 | + |
| 43 | +Your STX leg unlocks separately, at the end of the bond term on Stacks. See the [bond timeline](https://docs.stacks.co/learn/bitcoin-staking#the-bond-timeline). |
| 44 | + |
| 45 | +## Building a reclaim |
| 46 | + |
| 47 | +For wallet, custodian, and app developers. The examples use [`@stacks/bitcoin-staking`](https://www.npmjs.com/package/@stacks/bitcoin-staking) 7.6.0. |
| 48 | + |
| 49 | +### Cross-check the script before funding |
| 50 | + |
| 51 | +```ts |
| 52 | +import { buildLockOutputScript, fetchConstructLockupOutputScript } from '@stacks/bitcoin-staking'; |
| 53 | +import { bytesToHex } from '@stacks/common'; |
| 54 | + |
| 55 | +const inputs = { stxAddress: staker, unlockHeight, unlockBytes, earlyUnlockBytes: bond.earlyUnlockBytes }; |
| 56 | + |
| 57 | +const local = buildLockOutputScript(inputs); |
| 58 | +const onchain = await fetchConstructLockupOutputScript({ ...inputs, network: 'mainnet' }); |
| 59 | +if (bytesToHex(local) !== bytesToHex(onchain)) { |
| 60 | + throw new Error('Lock script mismatch: do not fund'); |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +### Rebuild the lock script if you did not keep it |
| 65 | + |
| 66 | +```ts |
| 67 | +import { buildLockScript, buildUnlockScript, fetchBond } from '@stacks/bitcoin-staking'; |
| 68 | + |
| 69 | +const bond = await fetchBond({ bondIndex, network: 'mainnet' }); |
| 70 | +const lockScript = buildLockScript({ |
| 71 | + stxAddress: staker, |
| 72 | + unlockHeight, // from your register-for-bond transaction |
| 73 | + unlockBytes: buildUnlockScript(stakerBtcPublicKey), |
| 74 | + earlyUnlockBytes: bond.earlyUnlockBytes, |
| 75 | +}); |
| 76 | +``` |
| 77 | + |
| 78 | +### Build, sign and finalize the spend |
| 79 | + |
| 80 | +```ts |
| 81 | +import { buildReclaim, finalizeReclaim } from '@stacks/bitcoin-staking'; |
| 82 | + |
| 83 | +const tx = buildReclaim({ |
| 84 | + path: 'locktime', |
| 85 | + utxo, // the lockup UTXO: { txid, vout, value } |
| 86 | + lockScript, |
| 87 | + output: { address: destinationAddress, feeSats }, |
| 88 | + network: 'mainnet', |
| 89 | +}); |
| 90 | + |
| 91 | +// Sign input 0 with your key, or send tx.toPSBT() to a hardware or browser wallet |
| 92 | +tx.signIdx(stakerPrivateKey, 0); |
| 93 | + |
| 94 | +const { txHex, txid } = finalizeReclaim({ path: 'locktime', tx }); |
| 95 | +// Broadcast txHex to Bitcoin |
| 96 | +``` |
| 97 | + |
| 98 | +`buildReclaim` sets the transaction's `nLockTime` to the unlock height decoded from the lock script, so the spend can be mined from the block after that height. The signature commits to the outputs and fee, so set them before signing. |
| 99 | + |
| 100 | +### Relock straight into the next bond |
| 101 | + |
| 102 | +To renew without other BTC, set `output.address` to the next bond's lock address instead of your own. The same spend moves your BTC from the old lock into the new one. Once it confirms, register for the next bond with a proof of the new output. See [Renewing a native BTC bond](ending-or-changing-a-bond-position.md#renewing-a-native-btc-bond). |
0 commit comments