Skip to main content
@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.
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 below.

Installation

1

Set up authentication

Add your GitHub token to your environment and configure .npmrc:
Create or update .npmrc in your project root:
2

Install the package

3

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:
To use a specific API provider:

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.

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.

Step 3 — Create and Broadcast the Deposit Transaction

Fetch your UTxOs, decide how much to allocate to each script, and broadcast the deposit PSBT.
createBTCLocker() automatically initializes @bitcoinerlab/secp256k1 for browser compatibility. Never call bitcoin.initEccLib() manually — the factory handles it.

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.
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.

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.

Working with the Deposit-with-Script Helper

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

API Reference

createBTCLocker(network?, api?): Promise<BTCLocker>The recommended entry point. Initializes the ECC library and returns a fully ready BTCLocker instance.
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 }.
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.
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.
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.locker.submitTransaction(txHex): Promise<string>Broadcasts a signed transaction hex to the Bitcoin network via the configured API provider. Returns the transaction ID.
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 }.

Quick-Reference Table

Error Types

The library throws typed errors for common failure modes:

BTC Staking

Protocol-level overview of the four-transaction BTC staking lifecycle, P2WSH script design, and the timelock/escrow split rationale.

Midgard SDK

The L1-side SDK for building L2 deposits, withdrawals, and transaction orders on the Sundial L2.