Skip to main content

Overview

This guide shows you how to sign Bitcoin transactions using Dynamic’s Node SDK. Bitcoin uses PSBTs (Partially Signed Bitcoin Transactions) for signing. You create a PSBT, pass it to signTransaction() as a base64 string, and receive the signed PSBT back.

Prerequisites

Step 1: Install Dependencies

These examples use bitcoinjs-lib v7, where satoshi amounts are bigint values (for example 50_000n), not numbers.

Step 2: Create and Sign a PSBT

The approach for signing transactions depends on how you created your wallet: If you created your wallet with backUpToDynamic: true, you can sign directly without retrieving key shares. walletMetadata is the object returned by createWalletAccount (or importPrivateKey), and password is the one you set at creation:

Signing on Testnet

For testnet, use bitcoin.networks.testnet for the PSBT and BitcoinNetwork.TESTNET for network. Build the PSBT with the wallet’s testnet address (tb1q...), which you get from deriveAccountAddress with network: BitcoinNetwork.TESTNET, and keep passing the walletMetadata from createWalletAccount unchanged:
A missing or incorrect network can make signing fail with No inputs found that belong to the account address. See Working with testnet.

With Manual Backup

If you created your wallet with backUpToDynamic: false, you must retrieve and provide external key shares:
Password Handling Notes:
  • If your wallet was created without a password, omit the password parameter
  • If your wallet was created with a password, you must provide it for all operations
  • Wallets created with backUpToDynamic: true always have a password, because one is required at creation

Step 3: Finalize and Broadcast

After signing, finalize the PSBT and extract the raw transaction for broadcasting:

Complete Example: Send Bitcoin

Here’s a complete example of creating, signing, and broadcasting a Bitcoin transaction:

Taproot Transaction Signing

For Taproot addresses, the signing process is the same, but uses Schnorr signatures internally:
Taproot transaction signing requires @dynamic-labs-wallet/node-btc 1.1.59 or later. In earlier versions it fails with Uint8Array expected.

Multi-Input Transactions

The SDK only signs inputs that belong to the sender address. Other inputs remain unsigned. To co-sign a P2WSH multisig input, see Co-signing P2WSH Multisig Inputs:

Co-signing P2WSH Multisig Inputs

By default signTransaction signs only inputs that belong to the wallet’s own address. To co-sign a P2WSH multisig, such as a 2-of-3 that holds this wallet’s key next to keys held elsewhere, pass the witness script in allowedWitnessScripts. This requires @dynamic-labs-wallet/node-btc 1.1.59 or later and a Native SegWit wallet. allowedWitnessScripts is an array of hex-encoded witness scripts. An input is signed only when all of these hold:
  • Its witness script is in allowedWitnessScripts.
  • The SHA-256 of the witness script matches the input’s scriptPubKey.
  • The script is a standard m-of-n multisig.
  • The wallet’s own compressed public key is one of the keys in the script.
Omitting allowedWitnessScripts, or passing an empty array, disables P2WSH signing. The wallet’s publicKeyHex is the uncompressed 65-byte key (it starts with 04). A multisig script needs the compressed 33-byte form, so compress it before building the script:
The returned PSBT is not finalized. It carries this wallet’s partial signature. Merge the signatures from the other co-signers, then finalize and broadcast as in Step 3:
allowedWitnessScripts is your application’s consent to co-sign that script. Build the list from scripts you constructed and trust, never from scripts taken from the PSBT or from user input. Pass allowedSighash as well to restrict the signature hash types the wallet will sign.
If you pass signingIndexes for an input whose witness script is not allowed, or does not contain the wallet’s key, signing fails with No inputs found that belong to the account address.

Error Handling

Next Steps

Last modified on November 2, 2026