> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sundialprotocol.com/llms.txt
> Use this file to discover all available pages before exploring further.

# BTC Locker SDK: Build Bitcoin Staking Transactions

> TypeScript library for P2WSH Bitcoin staking on Sundial. Build deposit, claim, distribution, and withdrawal PSBTs programmatically in browser or Node.js.

`@sundial-protocol/btc-locker` lets you build Bitcoin staking PSBTs programmatically — the same library powering the Sundial dashboard. With it you can construct every transaction in the four-step BTC staking lifecycle: Deposit, Claim, Distribute, and Withdraw. The library works in both browser and Node.js environments, uses `@bitcoinerlab/secp256k1` for ECC operations, and requires asynchronous initialization via the `createBTCLocker()` factory. Version **2.0.5** supports Bitcoin mainnet and testnet3.

<Note>
  This package is hosted in a private GitHub Package Registry. You need an authorized GitHub token in your environment before installing. See the [installation steps](#installation) below.
</Note>

## Installation

<Steps>
  <Step title="Set up authentication">
    Add your GitHub token to your environment and configure `.npmrc`:

    ```bash theme={null}
    export NODE_AUTH_TOKEN=ghp_yourgithubtokenhere
    ```

    Create or update `.npmrc` in your project root:

    ```ini theme={null}
    @sundial-protocol:registry=https://npm.pkg.github.com
    //npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
    ```
  </Step>

  <Step title="Install the package">
    <CodeGroup>
      ```bash npm theme={null}
      npm install @sundial-protocol/btc-locker
      ```

      ```bash pnpm theme={null}
      pnpm add @sundial-protocol/btc-locker
      ```
    </CodeGroup>
  </Step>

  <Step title="Initialize the client">
    Always initialize through the `createBTCLocker()` factory — never call `new BTCLocker()` directly, as the async ECC initialization step must complete before any method is available:

    ```typescript theme={null}
    import { createBTCLocker } from '@sundial-protocol/btc-locker';

    const locker = await createBTCLocker('testnet'); // 'testnet' | 'bitcoin' | 'regtest'
    ```

    To use a specific API provider:

    ```typescript theme={null}
    import { createBTCLocker, BitcoinAPI } from '@sundial-protocol/btc-locker';

    const api = new BitcoinAPI('testnet', 'blockstream'); // 'mempool' | 'blockstream' | 'blockcypher'
    const locker = await createBTCLocker('testnet', api);
    ```
  </Step>
</Steps>

## Full Staking Walkthrough

The Sundial BTC staking flow uses four transactions: Deposit, Claim (provider), Distribute (provider), and Withdraw (user). The steps below walk you through the complete user-side flow.

### Step 1 — Generate a Key Pair

Generate a fresh secp256k1 key pair for this staking position. Store the private key securely — you need it to sign the withdrawal transaction after the lockup expires.

```typescript theme={null}
import { createBTCLocker, TimeUtils } from '@sundial-protocol/btc-locker';

const locker = await createBTCLocker('testnet');

const { privateKey, publicKey, address } = await locker.generateKeyPair();
console.log('Funding address:', address);
// Send testnet BTC to this address before proceeding
```

### Step 2 — Create Scripts

Create both scripts that govern the staking position. The timelock script releases funds back to you after the deadline. The escrow script enforces the provider's claim window.

```typescript theme={null}
// Lock for 30 days from now
const deadline = TimeUtils.addDuration(TimeUtils.DURATIONS.DAY * 30);

const timelockScript = await locker.createTimelockScript(
  deadline,
  publicKey,
);

const escrowScript = await locker.createEscrowScript(
  deadline,
  publicKey,          // "before" branch: user can spend before deadline only with provider co-sig
  providerXonlyPubkey // "after" branch: provider can claim yield after deadline
);

console.log('Timelock address:', timelockScript.address);
console.log('Escrow address:',   escrowScript.address);
```

### Step 3 — Create and Broadcast the Deposit Transaction

Fetch your UTxOs, decide how much to allocate to each script, and broadcast the deposit PSBT.

```typescript theme={null}
import { createBTCLocker, FeePriorities } from '@sundial-protocol/btc-locker';

const locker = await createBTCLocker('testnet');

// Decide the amounts you want to put in each script (in satoshis)
const timelockAmount = 750_000; // amount for the timelock output
const escrowAmount   = 250_000; // amount for the escrow output

// Build the deposit PSBT — the library selects UTxOs from sourceAddress automatically
const unsignedPsbt = await locker.createDepositTransaction({
  sourceAddress:   address,       // your funding address from Step 1
  timelockAddress: timelockScript.address,
  timelockAmount,
  escrowAddress:   escrowScript.address,
  escrowAmount,
  changeAddress:   address,
  priority:        FeePriorities.MEDIUM,
});

// Sign and broadcast
const signedHex = await locker.signTransaction(unsignedPsbt, privateKey);
const txId = await locker.submitTransaction(signedHex);
console.log('Deposit tx:', txId);
```

<Note>
  `createBTCLocker()` automatically initializes `@bitcoinerlab/secp256k1` for browser compatibility. Never call `bitcoin.initEccLib()` manually — the factory handles it.
</Note>

### Step 4 — Withdraw After Lockup Expires

After the deadline passes, your timelock UTxO becomes spendable. Build and broadcast the withdrawal transaction to reclaim your funds.

```typescript theme={null}
const unsignedPsbt = await locker.createWithdrawalTransaction({
  timelockRedeemScript: timelockScript.redeemScript,
  escrowRedeemScript:   escrowScript.redeemScript,
  destination:          yourBitcoinAddress,
  priority:             FeePriorities.MEDIUM,
});

// Pass spendAfterDeadline: true so the escrow finalizer selects the correct branch
const signedHex = await locker.signTransaction(unsignedPsbt, privateKey, {
  spendAfterDeadline: true,
});
const txId = await locker.submitTransaction(signedHex);
console.log('Withdrawal tx:', txId);
```

<Warning>
  Withdraw promptly after the deadline expires. If you wait too long, the provider may claim the escrow output using the "after-deadline" branch of the escrow script, and you may only recover the timelock portion.
</Warning>

## Using `calculateDepositAmounts` for Feasibility Checks

Before building the deposit transaction, use `calculateDepositAmounts` to verify that your chosen amounts are feasible given available inputs and to get a fee estimate. You supply both amounts explicitly — the split between timelock and escrow is your choice.

```typescript theme={null}
const result = await locker.calculateDepositAmounts({
  sourceAddress:        address,    // used to auto-fetch UTxOs if inputs not provided
  desiredTimelockAmount: 750_000,
  desiredEscrowAmount:   250_000,
  includeChange: true,
  feeRate:       10,                // sat/vB; omit to use the default
});

if (!result.feasible) {
  console.warn('Cannot proceed:', result.recommendation);
} else {
  console.log('Estimated fee:', result.estimatedFee, 'sats');
  console.log('Change:',        result.changeAmount,  'sats');
}
```

## Working with the Deposit-with-Script Helper

If you already have scripts prepared, use `createDepositTransactionWithScript` to build the PSBT from the script objects directly:

```typescript theme={null}
const unsignedPsbt = await locker.createDepositTransactionWithScript({
  sourceAddress:   address,
  timelockScript,            // ScriptInfo from createTimelockScript()
  timelockAmount:  750_000,
  escrowAddress:   escrowScript.address,
  escrowAmount:    250_000,
  changeAddress:   address,
});
```

## API Reference

<Accordion title="Factory and Initialization">
  **`createBTCLocker(network?, api?): Promise<BTCLocker>`**

  The recommended entry point. Initializes the ECC library and returns a fully ready `BTCLocker` instance.

  | Parameter | Type                                  | Default      | Description                          |
  | --------- | ------------------------------------- | ------------ | ------------------------------------ |
  | `network` | `'bitcoin' \| 'testnet' \| 'regtest'` | `'testnet'`  | Bitcoin network target               |
  | `api`     | `BitcoinAPI`                          | Auto-created | Optional pre-configured API instance |
</Accordion>

<Accordion title="Key Management">
  **`locker.generateKeyPair(): Promise<KeyPair>`**

  Generates a new random secp256k1 key pair and derives the P2WPKH address for the configured network.

  Returns `{ privateKey: string, publicKey: Buffer, address: string }`.
</Accordion>

<Accordion title="Script Construction">
  **`locker.createTimelockScript(locktime, publicKey): Promise<ScriptInfo>`**

  Builds a P2WSH script that requires `<sig>` and a past-deadline `CHECKLOCKTIMEVERIFY`. The user can spend after `locktime` with their signature alone.

  **`locker.createRelativeTimelockScript(sequence, publicKey): Promise<ScriptInfo>`**

  Same as above but uses `CHECKSEQUENCEVERIFY` for relative timelocks (e.g., 144 blocks ≈ 1 day).

  **`locker.createEscrowScript(deadline, userPubkey, providerXonlyPubkey): Promise<ScriptInfo>`**

  Builds a P2WSH escrow with two spending branches:

  * **Before deadline:** requires both user and provider signatures.
  * **After deadline:** provider can spend unilaterally to claim yield.
</Accordion>

<Accordion title="Transaction Construction">
  **`locker.createDepositTransaction(params): Promise<string>`**

  Builds a PSBT that funds both the timelock and escrow P2WSH addresses in a single transaction. Returns base64 PSBT string.

  Key parameters: `sourceAddress`, `timelockAddress`, `timelockAmount`, `escrowAddress`, `escrowAmount`, `changeAddress`, `priority`.

  **`locker.createDepositTransactionWithScript(params): Promise<string>`**

  Same as above but accepts a pre-created `ScriptInfo` object for the timelock output via a `timelockScript` parameter.

  **`locker.calculateDepositAmounts(params): Promise<DepositCalculationResult>`**

  Checks whether your chosen `desiredTimelockAmount` and `desiredEscrowAmount` are feasible given available inputs. Returns estimated fee, change amount, and a `feasible` flag. Does not enforce any fixed split — you control the amounts.

  **`locker.createClaimTransaction(params): Promise<string>`**

  (Provider use.) Builds a PSBT that spends the escrow output after the deadline, claiming the yield portion.

  **`locker.createDistributionTransaction(params): Promise<string>`**

  (Provider use.) Builds a PSBT that distributes yield proceeds back to stakers.

  **`locker.createWithdrawalTransaction(params): Promise<string>`**

  Builds a PSBT that reclaims funds from both the timelock and escrow scripts after the deadline expires. Requires `timelockRedeemScript`, `escrowRedeemScript`, and `destination`.
</Accordion>

<Accordion title="Signing and Broadcasting">
  **`locker.signTransaction(unsignedPsbt, privateKeys, options?): Promise<string>`**

  Signs a PSBT with one or more private keys and finalizes the witness stack. Returns signed transaction hex.

  | Parameter                    | Type                 | Description                                                 |
  | ---------------------------- | -------------------- | ----------------------------------------------------------- |
  | `unsignedPsbt`               | `string`             | Base64 PSBT                                                 |
  | `privateKeys`                | `string \| string[]` | One key per input, or a single key for all                  |
  | `options.spendAfterDeadline` | `boolean`            | Set `true` when spending the escrow's after-deadline branch |

  **`locker.submitTransaction(txHex): Promise<string>`**

  Broadcasts a signed transaction hex to the Bitcoin network via the configured API provider. Returns the transaction ID.
</Accordion>

<Accordion title="API Utilities">
  **`locker.api.getAddressUtxos(address): Promise<ApiUTXO[]>`**

  Fetches all UTxOs for a Bitcoin address. If the primary provider (mempool.space) is unavailable, automatically falls back to Blockstream.

  **`locker.api.getFeeEstimates(): Promise<FeeEstimates>`**

  Returns raw fee estimates (block-target → sat/vB map) from the configured provider. Use `FeeUtils.queryChainFeeRates(priority)` for a single prioritized rate.

  **`locker.api.broadcastTransaction(txHex): Promise<BroadcastResult>`**

  Broadcasts raw transaction hex and returns `{ txid }`.
</Accordion>

## Quick-Reference Table

| Method                                                     | Description                                                    |
| ---------------------------------------------------------- | -------------------------------------------------------------- |
| `createBTCLocker(network?, api?)`                          | Initialize the client (always use this, not `new BTCLocker()`) |
| `generateKeyPair()`                                        | Generate secp256k1 key pair                                    |
| `createTimelockScript(locktime, pubkey)`                   | Build P2WSH absolute timelock script                           |
| `createRelativeTimelockScript(sequence, pubkey)`           | Build P2WSH relative timelock script                           |
| `createEscrowScript(deadline, userPubkey, providerPubkey)` | Build P2WSH two-branch escrow script                           |
| `createDepositTransaction(params)`                         | Build deposit PSBT (timelock + escrow outputs)                 |
| `createDepositTransactionWithScript(params)`               | Build deposit PSBT from pre-created `ScriptInfo`               |
| `calculateDepositAmounts(params)`                          | Check feasibility for chosen timelock/escrow amounts           |
| `createClaimTransaction(params)`                           | Build claim PSBT (provider use)                                |
| `createDistributionTransaction(params)`                    | Build yield distribution PSBT (provider use)                   |
| `createWithdrawalTransaction(params)`                      | Build withdrawal PSBT                                          |
| `signTransaction(psbt, keys, options?)`                    | Sign a PSBT and finalize witness stack                         |
| `submitTransaction(txHex)`                                 | Broadcast to Bitcoin network                                   |
| `api.getAddressUtxos(address)`                             | Fetch UTxOs for a Bitcoin address                              |
| `api.getFeeEstimates()`                                    | Get raw Bitcoin fee rate map (block target → sat/vB)           |

## Error Types

The library throws typed errors for common failure modes:

| Error class       | When it is thrown                                            |
| ----------------- | ------------------------------------------------------------ |
| `BTCLockerError`  | General operation failures                                   |
| `ValidationError` | Invalid parameters (bad keys, amounts, addresses)            |
| `TimelockError`   | Timelock-specific failures (past deadline, invalid locktime) |

## Related Pages

<CardGroup cols={2}>
  <Card title="BTC Staking" icon="bitcoin" href="/components/btc-staking">
    Protocol-level overview of the four-transaction BTC staking lifecycle, P2WSH script design, and the timelock/escrow split rationale.
  </Card>

  <Card title="Midgard SDK" icon="code" href="/sdk/midgard-sdk">
    The L1-side SDK for building L2 deposits, withdrawals, and transaction orders on the Sundial L2.
  </Card>
</CardGroup>
