Skip to main content

Using Bitcoin regtest

Bitcoin

Before using the local Bitcoin regtest instance, you will need to:

This page will demonstrate how to use the local Bitcoin regtest instance using the basic_bitcoin example project written in Rust. Alternatively, you can use the Motoko variation of this project.

If you are using macOS, an llvm version that supports the wasm32-unknown-unknown target is required. This is because the Rust bitcoin library relies on secp256k1-sys, which requires llvm to build. The default llvm version provided by XCode does not meet this requirement. Instead, install the Homebrew version, using brew install llvm.

The basic_bitcoin example provides a simple smart contract that implements methods for sending and receiving bitcoin. It supports P2PKH (legacy), P2PWKH (SegWit), and P2TR (Taproot) address types.

Navigate into the rust/basic_bitcoin subdirectory of the examples repo:

cd examples/rust/basic_bitcoin

When you set up your developer environment, if you created the subdirectory for your bitcoin_data files in another project's directory, you either need to make them again or copy them into this project's folder.

Then start the local Bitcoin regtest network:

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

Next, deploy the project to your local development environment with the dfx deploy command and specify the regtest network as an init argument for the project:

dfx start --clean --enable-bitcoin --background // If dfx is not already running
dfx deploy basic_bitcoin --argument '(variant { regtest })'

Make calls to the local Bitcoin regtest

Generating a Bitcoin address

The Bitcoin network uses different types of addresses (e.g., P2PKH, P2SH), most of which can be generated from an ECDSA public key. The basic_bitcoin example implements a function for generating a P2PKH address using the ecdsa_public_key API endpoint.

You can call this function from the command line:

dfx canister call basic_bitcoin get_p2pkh_address

Receiving BTC

A key difference between working with a local Bitcoin regtest and the Bitcoin testnet or mainnet is how you receive BTC. In order to receive BTC on your local Bitcoin testnet, you need to manually mine blocks. BTC is issued as a reward for each block mined.

Coinbase maturity

One caveat of using a local Bitcoin regtest and receiving block rewards is that the rewards are subject to the Coinbase maturity rule, which states that in order for you to spend rewarded BTC, you will first need to mine 100 blocks.

In the same directory as bitcoind, you can issue the following command to mine blocks:

bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress <number-of-blocks> <btc-address>

After mining a block, its hash will be returned. In the dfx logs, you will see a log entry confirming that dfx has ingested the newly mined block. Syncing the first bitcoin block can take up to 30 seconds. Subsequent blocks sync nearly instantly.

Then check your BTC balance:

dfx canister call basic_bitcoin get_balance '("<btc-address>")'

The BTC you mine is valid only in your local Bitcoin regtest and cannot be spent or used elsewhere.

Sending BTC

You can send BTC using the send_from_${type} function of the basic_bitcoin smart contract, where ${type} is one of the following:

  • p2pkh_address

  • p2tr_key_only_address

  • p2tr_address_key_path

  • p2tr_address_script_path

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

This command creates a transaction and sends it to your local Bitcoin regtest. Now, you need to mine a block so that the transaction you just sent becomes part of the blockchain:

bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 mtbZzVBwLnDmhH4pE9QynWAgh6H3aC1E6M

View the full details of the generatetoaddress command

Getting a block’s information

To get information about a specific block, use the getblock command:

bitcoin-cli -conf=$(pwd)/bitcoin.conf -regtest getblock <block-hash> <verbosity>

This command requires the block’s hash value and an optional verbosity level:

  • 0: Hex-encoded data
  • 1: JSON objects
  • 2: JSON objects with transaction data

View the full details of the getblock command

Getting raw transaction data

To get raw information about a transaction, use the getrawtransaction command:

bitcoin-cli -conf=$(pwd)/bitcoin.conf -regtest getrawtransaction <tx-id> <verbosity>

This command requires the transaction’s ID, an optional verbosity level, and block hash value.

View the full details of the getrawtransaction command.

Troubleshooting

Sending transactions

If you're trying to send a transaction and the transaction isn't being mined, try sending the same transaction using bitcoin-cli, as it can reveal helpful errors:

bitcoin-cli -conf=$(pwd)/bitcoin.conf sendrawtransaction <tx-in-hex>

Resetting the state

It's often useful to delete the entire local Bitcoin state and start from scratch. To do this:

  • Step 1: Run the following commands in the directory of your dfx project to delete the local state of dfx.

dfx stop
rm -rf .dfx

Running rm -rf .dfx will permanently delete all the ICP smart contracts you have created locally.

  • Step 2: In the folder where you're running bitcoind, stop the bitcoind process if it is running, and then delete the data folder you created.

rm -r data
mkdir data