ic_cdk/
bitcoin_canister.rs

1//! This module provides functionality for making inter-canister calls to the [Bitcoin canisters][1].
2//!
3//! The Bitcoin canisters allow for interactions with the Bitcoin network from within the Internet Computer.
4//! This module includes functions and types that facilitate these interactions, adhering to the
5//! [Bitcoin Canisters Interface Specification][2].
6//!
7//! # Bounded-wait vs. Unbounded-wait
8//!
9//! Interacting with the Bitcoin canisters involves making inter-canister calls,
10//! which can be either [bounded-wait](crate::call::Call::bounded_wait) or [unbounded-wait](crate::call::Call::unbounded_wait).
11//!
12//! Most of the functions in this module use the bounded-wait calls because they only read state.
13//! The only function that uses the unbounded-wait call is [`bitcoin_send_transaction`].
14//!
15//! If the default behavior is not suitable for a particular use case, the [`Call`] struct can be used directly to make the call.
16//!
17//! For example, [`bitcoin_get_utxos`] makes an bounded-wait call. If an unbounded-wait call is preferred, the call can be made as follows:
18//! ```rust, no_run
19//! # use ic_cdk::bitcoin_canister::{cost_get_utxos, get_bitcoin_canister_id, GetUtxosRequest, GetUtxosResponse};
20//! # use ic_cdk::call::Call;
21//! # async fn example() -> ic_cdk::call::CallResult<GetUtxosResponse> {
22//! let arg = GetUtxosRequest::default();
23//! let canister_id = get_bitcoin_canister_id(&arg.network);
24//! let cycles = cost_get_utxos(&arg);
25//! let res: GetUtxosResponse = Call::unbounded_wait(canister_id, "bitcoin_get_utxos")
26//!     .with_arg(&arg)
27//!     .with_cycles(cycles)
28//!     .await?
29//!     .candid()?;
30//! # Ok(res)
31//! # }
32//! ```
33//!
34//! ## Cycle Cost
35//!
36//! All the Bitcoin canister methods require cycles to be attached to the call.
37//! The helper functions in this module automatically calculate the required cycles and attach them to the call.
38//!
39//! For completeness, this module also provides functions to calculate the cycle cost:
40//! - [`cost_get_utxos`]
41//! - [`cost_get_balance`]
42//! - [`cost_get_current_fee_percentiles`]
43//! - [`cost_get_block_headers`]
44//! - [`cost_send_transaction`]
45//!
46//! # Bitcoin Canister ID
47//!
48//! The Bitcoin canister ID is determined by the network.
49//! The helper functions in this module automatically determine the canister ID based on the `network` field in the request.
50//!
51//! For completeness, the [`get_bitcoin_canister_id`] function can be used to get the canister ID manually.
52//!
53//! [1]: https://github.com/dfinity/bitcoin-canister
54//! [2]: https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md
55
56use crate::call::{Call, CallResult};
57use candid::{CandidType, Principal};
58use serde::{Deserialize, Serialize};
59
60const MAINNET_ID: Principal = Principal::from_slice(&[0, 0, 0, 0, 1, 160, 0, 4, 1, 1]); // "ghsi2-tqaaa-aaaan-aaaca-cai"
61const TESTNET_ID: Principal = Principal::from_slice(&[0, 0, 0, 0, 1, 160, 0, 1, 1, 1]); // "g4xu7-jiaaa-aaaan-aaaaq-cai"
62const REGTEST_ID: Principal = Principal::from_slice(&[0, 0, 0, 0, 1, 160, 0, 1, 1, 1]); // "g4xu7-jiaaa-aaaan-aaaaq-cai"
63
64// The cycles costs below are from the [API fees & Pricing](https://internetcomputer.org/docs/references/bitcoin-how-it-works#api-fees-and-pricing) documentation.
65// They are unlikely to change, so hardcoded here for simplicity.
66const GET_UTXO_MAINNET: u128 = 10_000_000_000;
67const GET_UTXO_TESTNET: u128 = 4_000_000_000;
68
69const GET_BALANCE_MAINNET: u128 = 100_000_000;
70const GET_BALANCE_TESTNET: u128 = 40_000_000;
71
72const GET_CURRENT_FEE_PERCENTILES_MAINNET: u128 = 100_000_000;
73const GET_CURRENT_FEE_PERCENTILES_TESTNET: u128 = 40_000_000;
74
75const GET_BLOCK_HEADERS_MAINNET: u128 = 10_000_000_000;
76const GET_BLOCK_HEADERS_TESTNET: u128 = 4_000_000_000;
77
78const SEND_TRANSACTION_SUBMISSION_MAINNET: u128 = 5_000_000_000;
79const SEND_TRANSACTION_SUBMISSION_TESTNET: u128 = 2_000_000_000;
80
81const SEND_TRANSACTION_PAYLOAD_MAINNET: u128 = 20_000_000;
82const SEND_TRANSACTION_PAYLOAD_TESTNET: u128 = 8_000_000;
83
84/// Gets the canister ID of the Bitcoin canister for the specified network.
85pub fn get_bitcoin_canister_id(network: &Network) -> Principal {
86    match network {
87        Network::Mainnet => MAINNET_ID,
88        Network::Testnet => TESTNET_ID,
89        Network::Regtest => REGTEST_ID,
90    }
91}
92
93/// Bitcoin Network.
94#[derive(
95    CandidType,
96    Serialize,
97    Deserialize,
98    Debug,
99    PartialEq,
100    Eq,
101    PartialOrd,
102    Ord,
103    Hash,
104    Clone,
105    Copy,
106    Default,
107)]
108pub enum Network {
109    /// The Bitcoin mainnet.
110    #[default]
111    #[serde(rename = "mainnet")]
112    Mainnet,
113    /// The Bitcoin testnet.
114    #[serde(rename = "testnet")]
115    Testnet,
116    /// The Bitcoin regtest, used for local testing purposes.
117    #[serde(rename = "regtest")]
118    Regtest,
119}
120
121/// Satoshi.
122///
123/// The smallest unit of Bitcoin, equal to 0.00000001 BTC.
124pub type Satoshi = u64;
125
126/// Bitcoin Address.
127///
128/// Please check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_get_utxos) for supported address formats.
129pub type Address = String;
130
131/// Block Hash.
132pub type BlockHash = Vec<u8>;
133
134/// Block Height.
135pub type BlockHeight = u32;
136
137/// Outpoint.
138#[derive(
139    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
140)]
141pub struct Outpoint {
142    /// Transaction ID (TxID).
143    ///
144    /// The hash of the transaction that created the UTXO.
145    pub txid: Vec<u8>,
146    /// Output Index (vout).
147    ///
148    /// The index of the specific output within that transaction (since a transaction can have multiple outputs).
149    pub vout: u32,
150}
151
152/// Unspent Transaction Output (UTXO).
153#[derive(
154    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
155)]
156pub struct Utxo {
157    /// The outpoint of the UTXO.
158    pub outpoint: Outpoint,
159    /// The value of the UTXO in satoshis.
160    pub value: Satoshi,
161    /// The block height at which the UTXO was created.
162    pub height: BlockHeight,
163}
164
165/// Filter to restrict the set of returned UTXOs.
166#[derive(
167    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone,
168)]
169pub enum UtxosFilter {
170    /// Filter by minimum number of confirmations.
171    #[serde(rename = "min_confirmations")]
172    MinConfirmations(u32),
173    /// Filter by a page reference.
174    #[serde(rename = "page")]
175    Page(Vec<u8>),
176}
177
178/// Argument type of [`bitcoin_get_utxos`].
179#[derive(
180    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
181)]
182pub struct GetUtxosRequest {
183    /// The Bitcoin network.
184    pub network: Network,
185    /// The Bitcoin address.
186    pub address: Address,
187    /// An optional filter to restrict the set of returned UTXOs.
188    pub filter: Option<UtxosFilter>,
189}
190
191/// Result type of [`bitcoin_get_utxos`].
192#[derive(
193    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
194)]
195pub struct GetUtxosResponse {
196    /// List of UTXOs.
197    pub utxos: Vec<Utxo>,
198    /// Hash of the tip block.
199    pub tip_block_hash: BlockHash,
200    /// Height of the tip height.
201    pub tip_height: u32,
202    /// Page reference when the response needs to be paginated.
203    ///
204    /// To be used in [`UtxosFilter::Page`].
205    pub next_page: Option<Vec<u8>>,
206}
207
208/// Gets all unspent transaction outputs (UTXOs) associated with the provided address.
209///
210/// **Bounded-wait call**
211///
212/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_get_utxos) for more details.
213pub async fn bitcoin_get_utxos(arg: &GetUtxosRequest) -> CallResult<GetUtxosResponse> {
214    let canister_id = get_bitcoin_canister_id(&arg.network);
215    let cycles = cost_get_utxos(arg);
216    Ok(Call::bounded_wait(canister_id, "bitcoin_get_utxos")
217        .with_arg(arg)
218        .with_cycles(cycles)
219        .await?
220        .candid()?)
221}
222
223/// Gets the cycles cost for the [`bitcoin_get_utxos`] function.
224///
225/// # Note
226///
227/// [`bitcoin_get_utxos`] calls this function internally so it's not necessary to call this function directly.
228/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
229pub fn cost_get_utxos(arg: &GetUtxosRequest) -> u128 {
230    match arg.network {
231        Network::Mainnet => GET_UTXO_MAINNET,
232        Network::Testnet => GET_UTXO_TESTNET,
233        Network::Regtest => GET_UTXO_MAINNET,
234    }
235}
236
237/// Argument type of [`bitcoin_get_balance`].
238#[derive(
239    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
240)]
241pub struct GetBalanceRequest {
242    /// The Bitcoin network.
243    pub network: Network,
244    /// The Bitcoin address.
245    pub address: Address,
246    /// Minimum number of confirmations.
247    ///
248    /// There is an upper bound of 144. Typically set to a value around 6 in practice.
249    pub min_confirmations: Option<u32>,
250}
251
252/// Gets the current balance of a Bitcoin address in Satoshi.
253///
254/// **Bounded-wait call**
255///
256/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_get_balance) for more details.
257pub async fn bitcoin_get_balance(arg: &GetBalanceRequest) -> CallResult<Satoshi> {
258    let canister_id = get_bitcoin_canister_id(&arg.network);
259    let cycles = cost_get_balance(arg);
260    Ok(Call::bounded_wait(canister_id, "bitcoin_get_balance")
261        .with_arg(arg)
262        .with_cycles(cycles)
263        .await?
264        .candid()?)
265}
266
267/// Gets the cycles cost for the [`bitcoin_get_balance`] function.
268///
269/// # Note
270///
271/// [`bitcoin_get_balance`] calls this function internally so it's not necessary to call this function directly.
272/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
273pub fn cost_get_balance(arg: &GetBalanceRequest) -> u128 {
274    match arg.network {
275        Network::Mainnet => GET_BALANCE_MAINNET,
276        Network::Testnet => GET_BALANCE_TESTNET,
277        Network::Regtest => GET_BALANCE_MAINNET,
278    }
279}
280
281/// Argument type of the [`bitcoin_get_current_fee_percentiles`] function.
282#[derive(
283    CandidType,
284    Serialize,
285    Deserialize,
286    Debug,
287    PartialEq,
288    Eq,
289    PartialOrd,
290    Ord,
291    Hash,
292    Clone,
293    Copy,
294    Default,
295)]
296pub struct GetCurrentFeePercentilesRequest {
297    /// The Bitcoin network.
298    pub network: Network,
299}
300
301/// Unit of Bitcoin transaction fee.
302///
303/// This is the element in the [`bitcoin_get_current_fee_percentiles`] response.
304pub type MillisatoshiPerByte = u64;
305
306/// Gets the Bitcoin transaction fee percentiles.
307///
308/// **Bounded-wait call**
309///
310/// The percentiles are measured in millisatoshi/byte (1000 millisatoshi = 1 satoshi),
311/// over the last 10,000 transactions in the specified network,
312/// i.e., over the transactions in the last approximately 4-10 blocks.
313pub async fn bitcoin_get_current_fee_percentiles(
314    arg: &GetCurrentFeePercentilesRequest,
315) -> CallResult<Vec<MillisatoshiPerByte>> {
316    let canister_id = get_bitcoin_canister_id(&arg.network);
317    let cycles = cost_get_current_fee_percentiles(arg);
318    Ok(
319        Call::bounded_wait(canister_id, "bitcoin_get_current_fee_percentiles")
320            .with_arg(arg)
321            .with_cycles(cycles)
322            .await?
323            .candid()?,
324    )
325}
326
327/// Gets the cycles cost for the [`bitcoin_get_current_fee_percentiles`] function.
328///
329/// # Note
330///
331/// [`bitcoin_get_current_fee_percentiles`] calls this function internally so it's not necessary to call this function directly.
332/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
333pub fn cost_get_current_fee_percentiles(arg: &GetCurrentFeePercentilesRequest) -> u128 {
334    match arg.network {
335        Network::Mainnet => GET_CURRENT_FEE_PERCENTILES_MAINNET,
336        Network::Testnet => GET_CURRENT_FEE_PERCENTILES_TESTNET,
337        Network::Regtest => GET_CURRENT_FEE_PERCENTILES_MAINNET,
338    }
339}
340
341/// Argument type of the [`bitcoin_get_block_headers`] function.
342#[derive(
343    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
344)]
345pub struct GetBlockHeadersRequest {
346    /// The starting block height for the request.
347    pub start_height: BlockHeight,
348    /// The ending block height for the request, or `None` for the current tip.
349    pub end_height: Option<BlockHeight>,
350    /// The Bitcoin network.
351    pub network: Network,
352}
353
354/// Block Header.
355pub type BlockHeader = Vec<u8>;
356
357/// Response type of the [`bitcoin_get_block_headers`] function.
358#[derive(
359    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
360)]
361pub struct GetBlockHeadersResponse {
362    /// The tip of the blockchain when this request was filled.
363    pub tip_height: BlockHeight,
364    /// The requested block headers.
365    pub block_headers: Vec<BlockHeader>,
366}
367
368/// Gets the block headers in the provided range of block heights.
369///
370/// **Bounded-wait call**
371///
372/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_get_block_headers) for more details.
373pub async fn bitcoin_get_block_headers(
374    arg: &GetBlockHeadersRequest,
375) -> CallResult<GetBlockHeadersResponse> {
376    let canister_id = get_bitcoin_canister_id(&arg.network);
377    let cycles = cost_get_block_headers(arg);
378    Ok(Call::bounded_wait(canister_id, "bitcoin_get_block_headers")
379        .with_arg(arg)
380        .with_cycles(cycles)
381        .await?
382        .candid()?)
383}
384
385/// Gets the cycles cost for the [`bitcoin_get_block_headers`] function.
386///
387/// # Note
388///
389/// [`bitcoin_get_block_headers`] calls this function internally so it's not necessary to call this function directly.
390/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
391pub fn cost_get_block_headers(arg: &GetBlockHeadersRequest) -> u128 {
392    match arg.network {
393        Network::Mainnet => GET_BLOCK_HEADERS_MAINNET,
394        Network::Testnet => GET_BLOCK_HEADERS_TESTNET,
395        Network::Regtest => GET_BLOCK_HEADERS_MAINNET,
396    }
397}
398
399/// Argument type of the [`bitcoin_send_transaction`] function.
400#[derive(
401    CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
402)]
403pub struct SendTransactionRequest {
404    /// The Bitcoin network.
405    pub network: Network,
406    /// The Bitcoin transaction.
407    pub transaction: Vec<u8>,
408}
409
410/// Sends a Bitcoin transaction to the Bitcoin network.
411///
412/// **Unbounded-wait call**
413///
414/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_send_transaction) for more details.
415pub async fn bitcoin_send_transaction(arg: &SendTransactionRequest) -> CallResult<()> {
416    let canister_id = get_bitcoin_canister_id(&arg.network);
417    let cycles = cost_send_transaction(arg);
418    Ok(
419        Call::unbounded_wait(canister_id, "bitcoin_send_transaction")
420            .with_arg(arg)
421            .with_cycles(cycles)
422            .await?
423            .candid()?,
424    )
425}
426
427/// Gets the cycles cost for the [`bitcoin_send_transaction`] function.
428///
429/// # Note
430///
431/// [`bitcoin_send_transaction`] calls this function internally so it's not necessary to call this function directly.
432/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
433pub fn cost_send_transaction(arg: &SendTransactionRequest) -> u128 {
434    let (submission, payload) = match arg.network {
435        Network::Mainnet => (
436            SEND_TRANSACTION_SUBMISSION_MAINNET,
437            SEND_TRANSACTION_PAYLOAD_MAINNET,
438        ),
439        Network::Testnet => (
440            SEND_TRANSACTION_SUBMISSION_TESTNET,
441            SEND_TRANSACTION_PAYLOAD_TESTNET,
442        ),
443        Network::Regtest => (
444            SEND_TRANSACTION_SUBMISSION_MAINNET,
445            SEND_TRANSACTION_PAYLOAD_MAINNET,
446        ),
447    };
448    submission + payload * arg.transaction.len() as u128
449}