Skip to main content

ic_cdk_bitcoin_canister/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub use ic_btc_interface::*;
4
5use candid::Principal;
6use ic_cdk::call::{Call, CallResult};
7
8const MAINNET_ID: Principal = Principal::from_slice(&[0, 0, 0, 0, 1, 160, 0, 4, 1, 1]); // "ghsi2-tqaaa-aaaan-aaaca-cai"
9const TESTNET_ID: Principal = Principal::from_slice(&[0, 0, 0, 0, 1, 160, 0, 1, 1, 1]); // "g4xu7-jiaaa-aaaan-aaaaq-cai"
10const REGTEST_ID: Principal = Principal::from_slice(&[0, 0, 0, 0, 1, 160, 0, 1, 1, 1]); // "g4xu7-jiaaa-aaaan-aaaaq-cai"
11
12// The cycles costs below are from the [API fees & Pricing](https://internetcomputer.org/docs/references/bitcoin-how-it-works#api-fees-and-pricing) documentation.
13// They are unlikely to change, so hardcoded here for simplicity.
14const GET_UTXO_MAINNET: u128 = 10_000_000_000;
15const GET_UTXO_TESTNET: u128 = 4_000_000_000;
16
17const GET_BALANCE_MAINNET: u128 = 100_000_000;
18const GET_BALANCE_TESTNET: u128 = 40_000_000;
19
20const GET_CURRENT_FEE_PERCENTILES_MAINNET: u128 = 100_000_000;
21const GET_CURRENT_FEE_PERCENTILES_TESTNET: u128 = 40_000_000;
22
23const GET_BLOCK_HEADERS_MAINNET: u128 = 10_000_000_000;
24const GET_BLOCK_HEADERS_TESTNET: u128 = 4_000_000_000;
25
26const SEND_TRANSACTION_SUBMISSION_MAINNET: u128 = 5_000_000_000;
27const SEND_TRANSACTION_SUBMISSION_TESTNET: u128 = 2_000_000_000;
28
29const SEND_TRANSACTION_PAYLOAD_MAINNET: u128 = 20_000_000;
30const SEND_TRANSACTION_PAYLOAD_TESTNET: u128 = 8_000_000;
31
32/// Gets the canister ID of the Bitcoin canister for the specified network.
33pub fn get_bitcoin_canister_id(network: Network) -> Principal {
34    match network {
35        Network::Mainnet => MAINNET_ID,
36        Network::Testnet => TESTNET_ID,
37        Network::Regtest => REGTEST_ID,
38    }
39}
40
41/// Gets all unspent transaction outputs (UTXOs) associated with the provided address.
42///
43/// **Bounded-wait call**
44///
45/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_get_utxos) for more details.
46pub async fn bitcoin_get_utxos(arg: &GetUtxosRequest) -> CallResult<GetUtxosResponse> {
47    let canister_id = get_bitcoin_canister_id(arg.network.into());
48    let cycles = cost_get_utxos(arg);
49    Ok(Call::bounded_wait(canister_id, "bitcoin_get_utxos")
50        .with_arg(arg)
51        .with_cycles(cycles)
52        .await?
53        .candid()?)
54}
55
56/// Gets the cycles cost for the [`bitcoin_get_utxos`] function.
57///
58/// # Note
59///
60/// [`bitcoin_get_utxos`] calls this function internally so it's not necessary to call this function directly.
61/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
62pub fn cost_get_utxos(arg: &GetUtxosRequest) -> u128 {
63    match Network::from(arg.network) {
64        Network::Mainnet => GET_UTXO_MAINNET,
65        Network::Testnet => GET_UTXO_TESTNET,
66        Network::Regtest => GET_UTXO_MAINNET,
67    }
68}
69
70/// Gets the current balance of a Bitcoin address in Satoshi.
71///
72/// **Bounded-wait call**
73///
74/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_get_balance) for more details.
75pub async fn bitcoin_get_balance(arg: &GetBalanceRequest) -> CallResult<Satoshi> {
76    let canister_id = get_bitcoin_canister_id(arg.network.into());
77    let cycles = cost_get_balance(arg);
78    Ok(Call::bounded_wait(canister_id, "bitcoin_get_balance")
79        .with_arg(arg)
80        .with_cycles(cycles)
81        .await?
82        .candid()?)
83}
84
85/// Gets the cycles cost for the [`bitcoin_get_balance`] function.
86///
87/// # Note
88///
89/// [`bitcoin_get_balance`] calls this function internally so it's not necessary to call this function directly.
90/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
91pub fn cost_get_balance(arg: &GetBalanceRequest) -> u128 {
92    match Network::from(arg.network) {
93        Network::Mainnet => GET_BALANCE_MAINNET,
94        Network::Testnet => GET_BALANCE_TESTNET,
95        Network::Regtest => GET_BALANCE_MAINNET,
96    }
97}
98
99/// Gets the Bitcoin transaction fee percentiles.
100///
101/// **Bounded-wait call**
102///
103/// The percentiles are measured in millisatoshi/byte (1000 millisatoshi = 1 satoshi),
104/// over the last 10,000 transactions in the specified network,
105/// i.e., over the transactions in the last approximately 4-10 blocks.
106pub async fn bitcoin_get_current_fee_percentiles(
107    arg: &GetCurrentFeePercentilesRequest,
108) -> CallResult<Vec<MillisatoshiPerByte>> {
109    let canister_id = get_bitcoin_canister_id(arg.network.into());
110    let cycles = cost_get_current_fee_percentiles(arg);
111    Ok(
112        Call::bounded_wait(canister_id, "bitcoin_get_current_fee_percentiles")
113            .with_arg(arg)
114            .with_cycles(cycles)
115            .await?
116            .candid()?,
117    )
118}
119
120/// Gets the cycles cost for the [`bitcoin_get_current_fee_percentiles`] function.
121///
122/// # Note
123///
124/// [`bitcoin_get_current_fee_percentiles`] calls this function internally so it's not necessary to call this function directly.
125/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
126pub fn cost_get_current_fee_percentiles(arg: &GetCurrentFeePercentilesRequest) -> u128 {
127    match Network::from(arg.network) {
128        Network::Mainnet => GET_CURRENT_FEE_PERCENTILES_MAINNET,
129        Network::Testnet => GET_CURRENT_FEE_PERCENTILES_TESTNET,
130        Network::Regtest => GET_CURRENT_FEE_PERCENTILES_MAINNET,
131    }
132}
133
134/// Gets the block headers in the provided range of block heights.
135///
136/// **Bounded-wait call**
137///
138/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_get_block_headers) for more details.
139pub async fn bitcoin_get_block_headers(
140    arg: &GetBlockHeadersRequest,
141) -> CallResult<GetBlockHeadersResponse> {
142    let canister_id = get_bitcoin_canister_id(arg.network.into());
143    let cycles = cost_get_block_headers(arg);
144    Ok(Call::bounded_wait(canister_id, "bitcoin_get_block_headers")
145        .with_arg(arg)
146        .with_cycles(cycles)
147        .await?
148        .candid()?)
149}
150
151/// Gets the cycles cost for the [`bitcoin_get_block_headers`] function.
152///
153/// # Note
154///
155/// [`bitcoin_get_block_headers`] calls this function internally so it's not necessary to call this function directly.
156/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
157pub fn cost_get_block_headers(arg: &GetBlockHeadersRequest) -> u128 {
158    match Network::from(arg.network) {
159        Network::Mainnet => GET_BLOCK_HEADERS_MAINNET,
160        Network::Testnet => GET_BLOCK_HEADERS_TESTNET,
161        Network::Regtest => GET_BLOCK_HEADERS_MAINNET,
162    }
163}
164
165/// Gets information about the current state of the Bitcoin blockchain as seen by the canister.
166///
167/// **Bounded-wait call**
168///
169/// This is a query-only function and does not require the `api_access` flag to be enabled,
170/// nor does it require the canister to be fully synchronized. It is primarily intended for
171/// monitoring purposes.
172///
173/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#get_blockchain_info) for more details.
174pub async fn get_blockchain_info(network: Network) -> CallResult<BlockchainInfo> {
175    let canister_id = get_bitcoin_canister_id(network);
176    Ok(Call::bounded_wait(canister_id, "get_blockchain_info")
177        .await?
178        .candid()?)
179}
180
181/// Sends a Bitcoin transaction to the Bitcoin network.
182///
183/// **Unbounded-wait call**
184///
185/// Check the [Bitcoin Canisters Interface Specification](https://github.com/dfinity/bitcoin-canister/blob/master/INTERFACE_SPECIFICATION.md#bitcoin_send_transaction) for more details.
186pub async fn bitcoin_send_transaction(arg: &SendTransactionRequest) -> CallResult<()> {
187    let canister_id = get_bitcoin_canister_id(arg.network.into());
188    let cycles = cost_send_transaction(arg);
189    Ok(
190        Call::unbounded_wait(canister_id, "bitcoin_send_transaction")
191            .with_arg(arg)
192            .with_cycles(cycles)
193            .await?
194            .candid()?,
195    )
196}
197
198/// Gets the cycles cost for the [`bitcoin_send_transaction`] function.
199///
200/// # Note
201///
202/// [`bitcoin_send_transaction`] calls this function internally so it's not necessary to call this function directly.
203/// When it is preferred to construct a [`Call`] manually, this function can be used to get the cycles cost.
204pub fn cost_send_transaction(arg: &SendTransactionRequest) -> u128 {
205    let (submission, payload) = match Network::from(arg.network) {
206        Network::Mainnet => (
207            SEND_TRANSACTION_SUBMISSION_MAINNET,
208            SEND_TRANSACTION_PAYLOAD_MAINNET,
209        ),
210        Network::Testnet => (
211            SEND_TRANSACTION_SUBMISSION_TESTNET,
212            SEND_TRANSACTION_PAYLOAD_TESTNET,
213        ),
214        Network::Regtest => (
215            SEND_TRANSACTION_SUBMISSION_MAINNET,
216            SEND_TRANSACTION_PAYLOAD_MAINNET,
217        ),
218    };
219    submission + payload * arg.transaction.len() as u128
220}