1use std::ops::Deref;
4
5use candid::{CandidType, Principal};
6use ic_agent::{Agent, AgentError};
7use serde::Deserialize;
8
9use crate::{
10 call::{AsyncCall, SyncCall},
11 Canister,
12};
13
14#[derive(Debug)]
16pub struct BitcoinCanister<'agent> {
17 canister: Canister<'agent>,
18 network: BitcoinNetwork,
19}
20
21impl<'agent> Deref for BitcoinCanister<'agent> {
22 type Target = Canister<'agent>;
23 fn deref(&self) -> &Self::Target {
24 &self.canister
25 }
26}
27const MAINNET_ID: Principal =
28 Principal::from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, 0xa0, 0x00, 0x04, 0x01, 0x01]);
29const TESTNET_ID: Principal =
30 Principal::from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, 0xa0, 0x00, 0x01, 0x01, 0x01]);
31
32impl<'agent> BitcoinCanister<'agent> {
33 pub fn from_canister(canister: Canister<'agent>, network: BitcoinNetwork) -> Self {
35 Self { canister, network }
36 }
37 pub fn create(agent: &'agent Agent, canister_id: Principal, network: BitcoinNetwork) -> Self {
39 Self::from_canister(
40 Canister::builder()
41 .with_agent(agent)
42 .with_canister_id(canister_id)
43 .build()
44 .expect("all required fields should be set"),
45 network,
46 )
47 }
48 pub fn mainnet(agent: &'agent Agent) -> Self {
50 Self::for_network(agent, BitcoinNetwork::Mainnet).expect("valid network")
51 }
52 pub fn testnet(agent: &'agent Agent) -> Self {
54 Self::for_network(agent, BitcoinNetwork::Testnet).expect("valid network")
55 }
56 pub fn for_network(agent: &'agent Agent, network: BitcoinNetwork) -> Result<Self, AgentError> {
58 let canister_id = match network {
59 BitcoinNetwork::Mainnet => MAINNET_ID,
60 BitcoinNetwork::Testnet => TESTNET_ID,
61 BitcoinNetwork::Regtest => {
62 return Err(AgentError::MessageError(
63 "No applicable canister ID for regtest".to_string(),
64 ))
65 }
66 };
67 Ok(Self::create(agent, canister_id, network))
68 }
69
70 pub fn get_balance(
73 &self,
74 address: &str,
75 min_confirmations: Option<u32>,
76 ) -> impl 'agent + AsyncCall<Value = (u64,)> {
77 self.update("bitcoin_get_balance")
78 .with_arg(GetBalance {
79 address,
80 network: self.network,
81 min_confirmations,
82 })
83 .build()
84 }
85
86 pub fn get_balance_query(
89 &self,
90 address: &str,
91 min_confirmations: Option<u32>,
92 ) -> impl 'agent + SyncCall<Value = (u64,)> {
93 self.query("bitcoin_get_balance_query")
94 .with_arg(GetBalance {
95 address,
96 network: self.network,
97 min_confirmations,
98 })
99 .build()
100 }
101
102 pub fn get_utxos(
108 &self,
109 address: &str,
110 filter: Option<UtxosFilter>,
111 ) -> impl 'agent + AsyncCall<Value = (GetUtxosResponse,)> {
112 self.update("bitcoin_get_utxos")
113 .with_arg(GetUtxos {
114 address,
115 network: self.network,
116 filter,
117 })
118 .build()
119 }
120
121 pub fn get_utxos_query(
127 &self,
128 address: &str,
129 filter: Option<UtxosFilter>,
130 ) -> impl 'agent + SyncCall<Value = (GetUtxosResponse,)> {
131 self.query("bitcoin_get_utxos_query")
132 .with_arg(GetUtxos {
133 address,
134 network: self.network,
135 filter,
136 })
137 .build()
138 }
139
140 pub fn get_current_fee_percentiles(&self) -> impl 'agent + AsyncCall<Value = (Vec<u64>,)> {
143 #[derive(CandidType)]
144 struct In {
145 network: BitcoinNetwork,
146 }
147 self.update("bitcoin_get_current_fee_percentiles")
148 .with_arg(In {
149 network: self.network,
150 })
151 .build()
152 }
153 pub fn get_block_headers(
156 &self,
157 start_height: u32,
158 end_height: Option<u32>,
159 ) -> impl 'agent + AsyncCall<Value = (GetBlockHeadersResponse,)> {
160 #[derive(CandidType)]
161 struct In {
162 start_height: u32,
163 end_height: Option<u32>,
164 }
165 self.update("bitcoin_get_block_headers")
166 .with_arg(In {
167 start_height,
168 end_height,
169 })
170 .build()
171 }
172 pub fn send_transaction(&self, transaction: Vec<u8>) -> impl 'agent + AsyncCall<Value = ()> {
174 #[derive(CandidType, Deserialize)]
175 struct In {
176 network: BitcoinNetwork,
177 #[serde(with = "serde_bytes")]
178 transaction: Vec<u8>,
179 }
180 self.update("bitcoin_send_transaction")
181 .with_arg(In {
182 network: self.network,
183 transaction,
184 })
185 .build()
186 }
187}
188
189#[derive(Debug, CandidType)]
190struct GetBalance<'a> {
191 address: &'a str,
192 network: BitcoinNetwork,
193 min_confirmations: Option<u32>,
194}
195
196#[derive(Debug, CandidType)]
197struct GetUtxos<'a> {
198 address: &'a str,
199 network: BitcoinNetwork,
200 filter: Option<UtxosFilter>,
201}
202
203#[derive(Clone, Copy, Debug, CandidType, Deserialize, PartialEq, Eq)]
205pub enum BitcoinNetwork {
206 #[serde(rename = "mainnet")]
208 Mainnet,
209 #[serde(rename = "testnet")]
211 Testnet,
212 #[serde(rename = "regtest")]
216 Regtest,
217}
218
219#[derive(Debug, Clone, CandidType, Deserialize)]
221pub enum UtxosFilter {
222 #[serde(rename = "min_confirmations")]
224 MinConfirmations(u32),
225 #[serde(rename = "page")]
227 Page(#[serde(with = "serde_bytes")] Vec<u8>),
228}
229
230#[derive(Debug, Clone, CandidType, Deserialize)]
232pub struct UtxoOutpoint {
233 #[serde(with = "serde_bytes")]
235 pub txid: Vec<u8>,
236 pub vout: u32,
238}
239
240#[derive(Debug, Clone, CandidType, Deserialize)]
242pub struct Utxo {
243 pub outpoint: UtxoOutpoint,
245 pub value: u64,
247 pub height: u32,
249}
250
251#[derive(Debug, Clone, CandidType, Deserialize)]
253pub struct GetUtxosResponse {
254 pub utxos: Vec<Utxo>,
256 #[serde(with = "serde_bytes")]
258 pub tip_block_hash: Vec<u8>,
259 pub tip_height: u32,
261 pub next_page: Option<Vec<u8>>,
264}
265
266#[derive(Debug, Clone, CandidType, Deserialize)]
268pub struct GetBlockHeadersResponse {
269 pub tip_height: u32,
271 pub block_headers: Vec<Vec<u8>>,
273}