Skip to content

Commit 2134405

Browse files
Merge pull request #1188 from dfinity/kristofer/bitcoin_assets
add: Bitcoin asset support to basic_bitcoin example: Ordinals, BRC-20, Runes
2 parents e737b78 + 9cae974 commit 2134405

22 files changed

+1482
-140
lines changed

rust/basic_bitcoin/Cargo.lock

Lines changed: 4 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/basic_bitcoin/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ candid = "0.10.13"
1515
ic-cdk = "0.18.0"
1616
serde = "1.0.132"
1717
serde_bytes = "0.11.15"
18+
leb128 = "0.2.5"

rust/basic_bitcoin/README.md

Lines changed: 189 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
# Basic Bitcoin
22

3-
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.
3+
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.
4+
5+
This example also includes examples of how to work with Bitcoin assets such as Ordinals, Runes, and BRC-20 tokens.
6+
7+
## Table of contents
8+
9+
- [Architecture](#architecture)
10+
- [Prerequisites](#prerequisites)
11+
- [Building and deploying the smart contract](#building-and-deploying-the-smart-contract)
12+
- [Generating Bitcoin addresses](#generating-bitcoin-addresses)
13+
- [Receiving bitcoin](#receiving-bitcoin)
14+
- [Checking balance](#checking-balance)
15+
- [Sending bitcoin](#sending-bitcoin)
16+
- [Retrieving block headers](#retrieving-block-headers)
17+
- [Bitcoin assets](#bitcoin-assets)
18+
- [Inscribe an Ordinal](#inscribe-an-ordinal)
19+
- [Etch a Rune](#etch-a-rune)
20+
- [Deploy a BRC-20 token](#deploy-a-brc-20-token)
21+
- [Notes on implementation](#notes-on-implementation)
22+
- [Security considerations and best practices](#security-considerations-and-best-practices)
423

524
## Architecture
625

@@ -16,8 +35,8 @@ For background on the ICP<>BTC integration, refer to the [Learn Hub](https://lea
1635

1736
- [x] [Rust toolchain](https://www.rust-lang.org/tools/install)
1837
- [x] [Internet Computer SDK](https://internetcomputer.org/docs/building-apps/getting-started/install)
19-
- [x] [Local Bitcoin testnet (regtest)](https://internetcomputer.org/docs/build-on-btc/btc-dev-env#create-a-local-bitcoin-testnet-regtest-with-bitcoind)
20-
- [x] On macOS, an `llvm` version that supports the `wasm32-unknown-unknown` target is required. This is because the Rust `bitcoin` library relies on the `secp256k1-sys` crate, which requires `llvm` to build. The default `llvm` version provided by XCode does not meet this requirement. Instead, install the [Homebrew version](https://formulae.brew.sh/formula/llvm), using `brew install llvm`.
38+
- [x] [Local Bitcoin testnet (regtest)](https://internetcomputer.org/docs/build-on-btc/btc-dev-env#create-a-local-bitcoin-testnet-regtest-with-bitcoind)
39+
- [x] On macOS, an `llvm` version that supports the `wasm32-unknown-unknown` target is required. This is because the Rust `bitcoin` library relies on the `secp256k1-sys` crate, which requires `llvm` to build. The default `llvm` version provided by XCode does not meet this requirement. Instead, install the [Homebrew version](https://formulae.brew.sh/formula/llvm), using `brew install llvm`.
2140

2241
## Building and deploying the smart contract
2342

@@ -30,20 +49,24 @@ cd examples/rust/basic_bitcoin
3049

3150
### 2. Start the ICP execution environment
3251

52+
Open a terminal window (terminal 1) and run the following:
3353
```bash
34-
dfx start --clean --background
54+
dfx start --enable-bitcoin --bitcoin-node 127.0.0.1:18444
3555
```
56+
This starts a local canister execution environment with Bitcoin support enabled.
3657

3758
### 3. Start the Bitcoin testnet (regtest)
3859

39-
In a separate terminal window, run the following:
60+
Open another terminal window (terminal 2) and run the following to start the local Bitcoin testnet:
4061

4162
```bash
4263
bitcoind -conf=$(pwd)/bitcoin.conf -datadir=$(pwd)/bitcoin_data --port=18444
4364
```
4465

4566
### 4. Deploy the smart contract
4667

68+
Open a third terminal (terminal 3) and run the following to deploy the smart contract:
69+
4770
```bash
4871
dfx deploy basic_bitcoin --argument '(variant { regtest })'
4972
```
@@ -53,12 +76,11 @@ What this does:
5376
- `dfx deploy` tells the command line interface to `deploy` the smart contract.
5477
- `--argument '(variant { regtest })'` passes the argument `regtest` to initialize the smart contract, telling it to connect to the local Bitcoin regtest network.
5578

56-
57-
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.
79+
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).
5880

5981
> [!NOTE]
6082
> 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**.
61-
>
83+
>
6284
> Access the Candid UI of the example: https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=vvha6-7qaaa-aaaap-ahodq-cai
6385
6486
## Generating Bitcoin addresses
@@ -77,9 +99,9 @@ dfx canister call basic_bitcoin get_p2pkh_address
7799
# or get_p2wpkh_address, get_p2tr_key_path_only_address, get_p2tr_script_path_enabled_address
78100
```
79101

80-
## Receiving Bitcoin
102+
## Receiving bitcoin
81103

82-
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.
104+
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.
83105

84106
```bash
85107
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <bitcoin_address>
@@ -93,11 +115,11 @@ Check the balance of any Bitcoin address:
93115
dfx canister call basic_bitcoin get_balance '("<bitcoin_address>")'
94116
```
95117

96-
This uses `bitcoin_get_balance` and works for any supported address type. Requires at least one confirmation to be reflected.
118+
This uses `bitcoin_get_balance` and works for any supported address type. The balance requires at least one confirmation to be reflected.
97119

98-
## Sending Bitcoin
120+
## Sending bitcoin
99121

100-
You can send BTC using the following endpoints:
122+
You can send bitcoin using the following endpoints:
101123

102124
- `send_from_p2pkh_address`
103125
- `send_from_p2wpkh_address`
@@ -123,8 +145,8 @@ dfx canister call basic_bitcoin send_from_p2pkh_address '(record {
123145
```
124146

125147
> [!IMPORTANT]
126-
> 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.
127-
>
148+
> 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.
149+
>
128150
> ```bash
129151
> bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <bitcoin_address>
130152
> ```
@@ -141,17 +163,164 @@ dfx canister call basic_bitcoin get_block_headers '(10: nat32)'
141163
dfx canister call basic_bitcoin get_block_headers '(0: nat32, 11: nat32)'
142164
```
143165
144-
This calls `bitcoin_get_block_headers`, useful for validating blockchains or light client logic.
166+
This calls `bitcoin_get_block_headers`, which is useful for blockchain validation or light client logic.
167+
168+
## Bitcoin assets
169+
170+
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:
171+
172+
- **Ordinals**: Inscribe arbitrary data onto individual satoshis
173+
- **Runes**: Create fungible tokens using `OP_RETURN` outputs
174+
- **BRC-20**: Build fungible tokens on top of Ordinals using JSON
175+
176+
### Prerequisites for Bitcoin assets
177+
178+
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.
179+
180+
Install `ord` using a package manager. For example, on macOS:
181+
182+
```bash
183+
brew install ord
184+
```
185+
186+
For other platforms, see the [ord repository](https://github.com/ordinals/ord) for installation instructions.
187+
188+
> [!NOTE]
189+
> This repository includes a [default ord config file](./ord.yaml) that matches the also provided [bitcoin config file](./bitcoin.conf).
190+
191+
> [!IMPORTANT]
192+
> **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`:
193+
>
194+
> ```
195+
> acceptnonstdtxn=1
196+
> ```
197+
198+
## Inscribe an Ordinal
199+
200+
[Ordinals](https://ordinals.com) 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.
201+
202+
### Step-by-step process:
203+
204+
1. **Start the ord server** to index transactions:
205+
```bash
206+
ord --config-dir . server
207+
```
208+
209+
2. **Get a Taproot address** for funding the inscription:
210+
```bash
211+
dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()'
212+
```
213+
214+
3. **Fund the address** with sufficient bitcoin (100 blocks ensures spendability):
215+
```bash
216+
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
217+
```
218+
219+
4. **Create the inscription** with your desired text:
220+
```bash
221+
dfx canister call basic_bitcoin inscribe_ordinal '("Hello Bitcoin")'
222+
```
223+
224+
5. **Mine a block** to confirm the transactions:
225+
```bash
226+
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <p2tr_key_path_only_address>
227+
```
228+
229+
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/`.
230+
231+
## Etch a Rune
232+
233+
[Runes](https://docs.ordinals.com/runes.html) 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.
234+
235+
### Step-by-step process:
236+
237+
1. **Start the ord server** to track Rune balances:
238+
```bash
239+
ord --config-dir . server
240+
```
241+
242+
2. **Get a Taproot address** for the Rune etching:
243+
```bash
244+
dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()'
245+
```
246+
247+
3. **Fund the address** with bitcoin to pay for the etching:
248+
```bash
249+
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
250+
```
251+
252+
4. **Etch the Rune** with an uppercase name (maximum 28 characters):
253+
```bash
254+
dfx canister call basic_bitcoin etch_rune '("ICPRUNE")'
255+
```
256+
257+
5. **Mine a block** to confirm the etching:
258+
```bash
259+
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <p2tr_key_path_only_address>
260+
```
261+
262+
6. **Decode the Runestone** to verify the etching:
263+
```bash
264+
ord --config-dir . decode --txid <transaction_id>
265+
```
266+
267+
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.
268+
269+
## Deploy a BRC-20 token
270+
271+
[BRC-20](https://domo-2.gitbook.io/brc-20-experiment/) 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.
272+
273+
### Step-by-step process:
274+
275+
1. **Start the ord server** to index BRC-20 inscriptions:
276+
```bash
277+
ord --config-dir . server
278+
```
279+
280+
2. **Get a Taproot address** for the token deployment:
281+
```bash
282+
dfx canister call basic_bitcoin get_p2tr_key_path_only_address '()'
283+
```
284+
285+
3. **Fund the address** with bitcoin:
286+
```bash
287+
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 100 <p2tr_key_path_only_address>
288+
```
289+
290+
4. **Deploy the BRC-20 token** with a 4-character ticker:
291+
```bash
292+
dfx canister call basic_bitcoin inscribe_brc20 '("DEMO")'
293+
```
294+
295+
5. **Mine a block** to confirm the deployment:
296+
```bash
297+
bitcoin-cli -conf=$(pwd)/bitcoin.conf generatetoaddress 1 <p2tr_key_path_only_address>
298+
```
299+
300+
This creates a BRC-20 token with:
301+
- Ticker: "DEMO"
302+
- Max supply: 21_000_000 tokens
303+
- Mint limit: 1_000 tokens per mint
304+
305+
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.
306+
307+
To view the deployed BRC-20 token, use the local `ord` explorer at `http://127.0.0.1:80/`.
308+
145309

146310
## Notes on implementation
147311

148-
- Keys are derived using structured derivation paths according to BIP-32.
149-
- Key caching is used to avoid repeated calls to `get_ecdsa_public_key` and `get_schnorr_public_key`.
150-
- Transactions are assembled and signed manually, ensuring maximum flexibility in construction and fee estimation.
151-
- When _testing_ on mainnet, the [chain-key testing canister](https://github.com/dfinity/chainkey-testing-canister) can be used to save on costs for calling the threshold signing APIs for signing the BTC transactions.
312+
This example implements several important patterns for Bitcoin integration:
313+
314+
- **Derivation paths**: Keys are derived using structured derivation paths according to BIP-32, ensuring reproducible key generation.
315+
- **Key caching**: Optimization is used to avoid repeated calls to `get_ecdsa_public_key` and `get_schnorr_public_key`.
316+
- **Manual transaction construction**: Transactions are assembled and signed manually, ensuring maximum flexibility in construction and fee estimation.
317+
- **Cost optimization**: When testing on mainnet, the [chain-key testing canister](https://github.com/dfinity/chainkey-testing-canister) can be used to save on costs for calling the threshold signing APIs.
318+
- **Asset protocols**: Bitcoin assets (Ordinals, Runes, BRC-20) demonstrate advanced scripting capabilities and witness data usage.
152319

153320
## Security considerations and best practices
154321

322+
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.
323+
155324
If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/current/references/security/) for developing on the Internet Computer. This example may not implement all the best practices.
156325

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

rust/basic_bitcoin/basic_bitcoin.did

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ type get_block_headers_response = record {
4141
};
4242

4343
service : (network) -> {
44+
"etch_rune" : (ticker: text) -> (transaction_id);
45+
4446
"get_p2pkh_address" : () -> (bitcoin_address);
45-
47+
4648
"get_p2wpkh_address" : () -> (bitcoin_address);
4749

4850
"get_p2tr_script_path_enabled_address" : () -> (bitcoin_address);
@@ -57,6 +59,10 @@ service : (network) -> {
5759

5860
"get_current_fee_percentiles" : () -> (vec millisatoshi_per_vbyte);
5961

62+
"inscribe_brc20": (ticker: text) -> (transaction_id);
63+
64+
"inscribe_ordinal": (text: text) -> (transaction_id);
65+
6066
"send_from_p2pkh_address" : (
6167
record {
6268
destination_address : bitcoin_address;

rust/basic_bitcoin/bitcoin.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
regtest=1
2+
acceptnonstdtxn=1
23
txindex=1
34
rpcuser=ic-btc-integration
45
rpcpassword=QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E=
5-
rpcauth=ic-btc-integration:cdf2741387f3a12438f69092f0fdad8e$62081498c98bee09a0dce2b30671123fa561932992ce377585e8e08bb0c11dfa

rust/basic_bitcoin/ord.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
bitcoin_data_dir: ./bitcoin_data
2+
bitcoin_rpc_password: QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E=
3+
bitcoin_rpc_url: http://127.0.0.1:18443
4+
bitcoin_rpc_username: ic-btc-integration
5+
chain: regtest
6+
index_addresses: true
7+
index_runes: true
8+
index_sats: true
9+
index_transactions: true

0 commit comments

Comments
 (0)