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.
This example also includes how to work with Bitcoin assets such as Ordinals, Runes, and BRC-20 tokens.
This example integrates with the Internet Computer's built-in:
For background on the ICP<>BTC integration, refer to the Learn Hub.
This example can be deployed directly to the Internet Computer using ICP Ninja, where it connects to Bitcoin testnet4. Note: Canisters deployed via ICP Ninja remain live for 50 minutes after signing in with your Internet Identity.
- Rust toolchain
- Internet Computer SDK
- Local Bitcoin testnet (regtest)
- On macOS, an
llvmversion that supports thewasm32-unknown-unknowntarget is required. The Rustbitcoinlibrary relies on thesecp256k1-syscrate, which requiresllvmto build. The defaultllvmversion provided by XCode does not meet this requirement. Install the Homebrew version usingbrew install llvm.
git clone https://github.com/dfinity/examples
cd examples/rust/basic_bitcoinOpen a terminal window (terminal 1) and run the following:
dfx start --enable-bitcoin --bitcoin-node 127.0.0.1:18444This starts a local canister execution environment with Bitcoin support enabled.
Open another terminal window (terminal 2) and run the following to start the local Bitcoin regtest network:
bitcoind -conf=$(pwd)/bitcoin.conf -datadir=$(pwd)/bitcoin_data --port=18444Open a third terminal (terminal 3) and run the following to deploy the smart contract:
dfx deploy basic_bitcoin --argument '(variant { regtest })'What this does:
dfx deploytells the command line interface todeploythe smart contract.--argument '(variant { regtest })'passes the argumentregtestto 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 the Candid UI (the link you see in the terminal).
The example demonstrates how to generate and use the following address types:
- P2PKH (Legacy) using ECDSA and
sign_with_ecdsa - P2WPKH (SegWit v0) using ECDSA and
sign_with_ecdsa - P2TR (Taproot, key-path-only) using Schnorr keys and
sign_with_schnorr - P2TR (Taproot, script-path-enabled) commits to a script allowing both key path and script path spending Use the Candid UI or CLI to generate:
dfx canister call basic_bitcoin get_p2pkh_address
# or: get_p2wpkh_address, get_p2tr_key_path_only_address, get_p2tr_script_path_enabled_addressUse the bitcoin-cli to mine a Bitcoin block and send the block reward in the form of local testnet bitcoin to one of the smart contract addresses.
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <bitcoin_address>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. The balance requires at least one confirmation to be reflected.
You can send bitcoin using the following endpoints: Endpoints:
send_from_p2pkh_addresssend_from_p2wpkh_addresssend_from_p2tr_key_path_only_addresssend_from_p2tr_script_path_enabled_address_key_spendsend_from_p2tr_script_path_enabled_address_script_spend
Each endpoint internally:
- Estimates fees
- Looks up spendable UTXOs
- Builds a transaction to the target address
- Signs using ECDSA or Schnorr, depending on address type
- Broadcasts the transaction using
bitcoin_send_transaction
Example:
dfx canister call basic_bitcoin send_from_p2pkh_address '(record {
destination_address = "bcrt1qg8qknn6f3txqg97gt8ca0ctya0vw7ep6d02qmt";
amount_in_satoshi = 4321;
})'Important
Newly mined bitcoin, like those you created with the above bitcoin-cli command, cannot be spent until 100 additional blocks have been added to the chain. To make your bitcoin spendable, create 100 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 100 <bitcoin_address>The function returns the transaction ID. When interacting with the contract deployed on IC mainnet, you can track testnet transactions on mempool.space.
You can query the current state of the Bitcoin blockchain:
dfx canister call basic_bitcoin get_blockchain_infoThis calls get_blockchain_info on the Bitcoin canister and returns the tip height, block hash, timestamp, difficulty, and total UTXO count. It is useful for monitoring the state of the Bitcoin network from your smart contract.
You can query historical block headers:
dfx canister call basic_bitcoin get_block_headers '(10: nat32, null)'
# or a range:
dfx canister call basic_bitcoin get_block_headers '(10: nat32, opt (11: nat32))'This calls bitcoin_get_block_headers, which is useful for blockchain validation or light client logic.
Bitcoin's scripting capabilities enable various digital assets beyond simple transfers. This example demonstrates how to create and interact with three major Bitcoin asset protocols from an ICP smart contract:
- Ordinals: Inscribe arbitrary data onto individual satoshis
- Runes: Create fungible tokens using
OP_RETURNoutputs - BRC-20: Build fungible tokens on top of Ordinals using JSON
All Bitcoin assets rely on off-chain indexing since the Bitcoin protocol doesn't natively support querying these assets. The ord CLI tool is the standard indexer for Bitcoin assets like Ordinals and Runes.
Install ord using a package manager. For example, on macOS:
brew install ordFor other platforms, see the ord repository for installation instructions.
Note
This repository includes a default ord config file that matches the also provided bitcoin config file.
Important
Bitcoin Configuration: To work with Bitcoin assets, make sure bitcoind is configured to accept non-standard transactions by including this setting in your bitcoin.conf:
acceptnonstdtxn=1
Ordinals is a protocol that allows inscribing arbitrary data (text, images, etc.) onto individual satoshis, creating unique digital artifacts on Bitcoin. Each inscription is permanently stored in the Bitcoin blockchain using a two-transaction commit/reveal process.
-
Start the ord server to index transactions:
ord --config-dir . server -
Get a Taproot address for funding the inscription:
dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()' -
Fund the address with sufficient bitcoin (100 blocks ensures spendability):
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
-
Create the inscription with your desired text:
dfx canister call basic_bitcoin inscribe_ordinal '("Hello Bitcoin")' -
Mine a block to confirm the transactions:
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <p2tr_key_path_only_address>
The function returns the reveal transaction ID. Your inscription is now permanently stored on Bitcoin and can be viewed using ord or other Ordinals explorers. The default address of the local ord server is http://127.0.0.1:80/.
Runes is a fungible token protocol that embeds token metadata directly into Bitcoin transactions using OP_RETURN outputs. Unlike Ordinals, Runes are created in a single transaction and support standard fungible token operations.
-
Start the ord server to track Rune balances:
ord --config-dir . server -
Get a Taproot address for the Rune etching:
dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()' -
Fund the address with bitcoin to pay for the etching:
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
-
Etch the Rune with an uppercase name (maximum 28 characters):
dfx canister call basic_bitcoin etch_rune '("ICPRUNE")' -
Mine a block to confirm the etching:
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <p2tr_key_path_only_address>
-
Decode the Runestone to verify the etching:
ord --config-dir . decode --txid <transaction_id>
The Rune is now etched with 1_000_000 tokens minted to your address. The tokens can be transferred using standard Bitcoin transactions with Runestone data.
BRC-20 is a token standard built on top of Ordinals that uses structured JSON payloads to create fungible tokens. BRC-20 tokens follow the same inscription process as Ordinals but with standardized JSON formats.
-
Start the ord server to index BRC-20 inscriptions:
ord --config-dir . server -
Get a Taproot address for the token deployment:
dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()' -
Fund the address with bitcoin:
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
-
Deploy the BRC-20 token with a 4-character ticker:
dfx canister call basic_bitcoin inscribe_brc20 '("DEMO")' -
Mine a block to confirm the deployment:
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <p2tr_key_path_only_address>
This creates a BRC-20 token with:
- Ticker: "DEMO"
- Max supply: 21_000_000 tokens
- Mint limit: 1_000 tokens per mint
The deployment inscription contains JSON metadata that BRC-20 indexers use to track token balances and transfers. Additional mint and transfer operations require separate inscriptions following the BRC-20 protocol.
To view the deployed BRC-20 token, use the local ord explorer at http://127.0.0.1:80/.
This example implements several important patterns for Bitcoin integration:
- Derivation paths: Keys are derived using structured derivation paths according to BIP-32, ensuring reproducible key generation.
- Key caching: Optimization is used to avoid repeated calls to
get_ecdsa_public_keyandget_schnorr_public_key. - Manual transaction construction: Transactions are assembled and signed manually, ensuring maximum flexibility in construction and fee estimation.
- Cost optimization: When testing on mainnet, the chain-key testing canister can be used to save on costs for calling the threshold signing APIs.
- Asset protocols: Bitcoin assets (Ordinals, Runes, BRC-20) demonstrate advanced scripting capabilities and witness data usage.
This example is provided for educational purposes and is not production-ready. It is important to consider security implications when developing applications that interact with Bitcoin or other cryptocurrencies. The code has not been audited and may contain vulnerabilities or security issues.
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:
- Certify query responses if they are relevant for security, since the app e.g. offers a method to read balances.
- Use a decentralized governance system like SNS to make a smart contract have a decentralized controller, since decentralized control may be essential for smart contracts holding bitcoins on behalf of users.
Last updated: July 2025