Using Bitcoin regtest
Before using the local Bitcoin regtest instance, you will need to:
Clone the
@dfinity/examples
repo:git clone https://github.com/dfinity/examples.git
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.
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 ofdfx
.
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 thebitcoind
process if it is running, and then delete the data folder you created.
rm -r data
mkdir data