Skip to main content

Basic Bitcoin

This example demonstrates how to deploy a smart contract on the Internet Computer that can receive and send Bitcoin, including support for legacy (P2PKH), SegWit (P2WPKH), and Taproot (P2TR) address types.

Architecture

This example integrates with the Internet Computer's built-in:

For background on the ICP <-> BTC integration, refer to the Learn Hub.

Prerequisites

Step 1: Building and deploying the smart contract

Clone the examples repo

git clone https://github.com/dfinity/examples
cd examples/rust/basic_bitcoin

Start the ICP local development environment

dfx start --clean --background

Start the local Bitcoin testnet (regtest)

In a separate terminal window, run the following:

bitcoind -conf=$(pwd)/bitcoin.conf -datadir=$(pwd)/bitcoin_data --port=18444

Deploy the smart contract

dfx deploy basic_bitcoin --argument '(variant { regtest })'

What this does:

  • dfx deploy tells the command line interface to deploy the smart contract.
  • --argument '(variant { regtest })' passes the argument regtest to initialize the smart contract, telling it to connect to the local Bitcoin regtest network.

Your smart contract is live and ready to use! You can interact with it using either the command line or using the Candid UI, which is the link you see in the terminal.

[!NOTE] You can also interact with a pre-deployed version of the basic_bitcoin example running on the IC mainnet and configured to interact with Bitcoin testnet4.

Access the Candid UI of the example: https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=vvha6-7qaaa-aaaap-ahodq-cai

2. Supported Bitcoin address types

This example demonstrates how to generate and use the following address types:

  1. P2PKH (Legacy) using ECDSA and sign_with_ecdsa
  2. P2WPKH (SegWit v0) using ECDSA and sign_with_ecdsa
  3. P2TR (Taproot, key-path-only) using Schnorr keys and sign_with_schnorr
  4. P2TR (Taproot, script-path-enabled) commits to a script allowing both key path and script path spending

Use the Candid UI or command line to generate these addresses with:

dfx canister call basic_bitcoin get_p2pkh_address
# or get_p2wpkh_address, get_p2tr_key_path_only_address, get_p2tr_script_path_enabled_address

3. Receiving bitcoin

Use the bitcoin-cli to mine a Bitcoin block and send the block reward in the form of local testnet BTC to one of the smart contract addresses.

bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <bitcoin_address>

4. Checking balance

Check the balance of any Bitcoin address:

dfx canister call basic_bitcoin get_balance '("<bitcoin_address>")'

This uses bitcoin_get_balance and works for any supported address type. Requires at least one confirmation to be reflected.

5. Sending bitcoin

You can send BTC using the following endpoints:

  • send_from_p2pkh_address
  • send_from_p2wpkh_address
  • send_from_p2tr_key_path_only_address
  • send_from_p2tr_script_path_enabled_address_key_spend
  • send_from_p2tr_script_path_enabled_address_script_spend

Each endpoint internally:

  1. Estimates fees
  2. Looks up spendable UTXOs
  3. Builds a transaction to the target address
  4. Signs using ECDSA or Schnorr, depending on address type
  5. Broadcasts the transaction using bitcoin_send_transaction

Example:

dfx canister call basic_bitcoin send_from_p2pkh_address '(record {
destination_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt";
amount_in_satoshi = 4321;
})'

[!IMPORTANT] Newly created bitcoin, like those you created with the above bitcoin-cli command cannot be spent until 10 additional blocks have been added to the chain. To make your bitcoin spendable, create 10 additional blocks. Choose one of the smart contract addresses as receiver of the block reward or use any valid bitcoin dummy address.

bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 10 <bitcoin_address>

The function returns the transaction ID, which you can track on mempool.space testnet4.

6. Retrieving block headers

You can query historical block headers:

dfx canister call basic_bitcoin get_block_headers '(10: nat32)'
# or a range:
dfx canister call basic_bitcoin get_block_headers '(0: nat32, 11: nat32)'

This calls bitcoin_get_block_headers, useful for validating blockchains or light client logic.

Notes on implementation

  • Keys are derived using structured derivation paths according to BIP-32.
  • Key caching is used to avoid repeated calls to get_ecdsa_public_key and get_schnorr_public_key.
  • Transactions are assembled and signed manually, ensuring maximum flexibility in construction and fee estimation.

Conclusion

In this tutorial, you were able to:

  • Deploy a smart contract locally that can receive & send bitcoin.
  • Connect the smart contract to the local Bitcoin testnet.
  • Send the smart contract some local testnet BTC.
  • Check the local testnet BTC balance of the smart contract.
  • Use the smart contract to send local testnet BTC to another local testnet BTC address.

The steps to develop Bitcoin dapps locally are extensively documented in this tutorial.

Note that for testing on mainnet, the chain-key testing canister can be used to save on costs for calling the threshold signing APIs for signing the BTC transactions.

Security considerations and best practices

If you base your application on this example, we recommend you familiarize yourself with and adhere to the security best practices for developing on the Internet Computer. This example may not implement all the best practices.

For example, the following aspects are particularly relevant for this app:


Last updated: May 2025