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}