Skip to content

Latest commit

 

History

History

README.md

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.

This example also includes how to work with Bitcoin assets such as Ordinals, Runes, and BRC-20 tokens.

Table of contents

Architecture

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

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

Deploying from ICP Ninja

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.

Image

Building and deploying the smart contract locally

1. Prerequisites

2. Clone the examples repo

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

3. Start the ICP execution environment

Open a terminal window (terminal 1) and run the following:

dfx start --enable-bitcoin --bitcoin-node 127.0.0.1:18444

This starts a local canister execution environment with Bitcoin support enabled.

4. Start Bitcoin regtest

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=18444

5. Deploy the smart contract

Open 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 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 the Candid UI (the link you see in the terminal).

Generating Bitcoin addresses

The 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 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_address

Receiving bitcoin

Use 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>

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. The balance requires at least one confirmation to be reflected.

Sending bitcoin

You can send bitcoin using the following endpoints: 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 = "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.

Retrieving blockchain info

You can query the current state of the Bitcoin blockchain:

dfx canister call basic_bitcoin get_blockchain_info

This 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.

Retrieving block headers

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 assets

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_RETURN outputs
  • BRC-20: Build fungible tokens on top of Ordinals using JSON

Prerequisites for Bitcoin assets

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 ord

For 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

Inscribe an Ordinal

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.

Step-by-step process:

  1. Start the ord server to index transactions:

    ord --config-dir . server
  2. Get a Taproot address for funding the inscription:

    dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()'
  3. Fund the address with sufficient bitcoin (100 blocks ensures spendability):

    bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
  4. Create the inscription with your desired text:

    dfx canister call basic_bitcoin inscribe_ordinal '("Hello Bitcoin")'
  5. 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/.

Etch a Rune

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.

Step-by-step process:

  1. Start the ord server to track Rune balances:

    ord --config-dir . server
  2. Get a Taproot address for the Rune etching:

    dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()'
  3. Fund the address with bitcoin to pay for the etching:

    bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
  4. Etch the Rune with an uppercase name (maximum 28 characters):

    dfx canister call basic_bitcoin etch_rune '("ICPRUNE")'
  5. Mine a block to confirm the etching:

    bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <p2tr_key_path_only_address>
  6. 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.

Deploy a BRC-20 token

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.

Step-by-step process:

  1. Start the ord server to index BRC-20 inscriptions:

    ord --config-dir . server
  2. Get a Taproot address for the token deployment:

    dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()'
  3. Fund the address with bitcoin:

    bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
  4. Deploy the BRC-20 token with a 4-character ticker:

    dfx canister call basic_bitcoin inscribe_brc20 '("DEMO")'
  5. 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/.

Notes on implementation

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_key and get_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.

Security considerations and best practices

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:


Last updated: July 2025