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 #[derive(CandidType)]
78 struct In<'a> {
79 address: &'a str,
80 network: BitcoinNetwork,
81 min_confirmations: Option<u32>,
82 }
83 self.update("bitcoin_get_balance")
84 .with_arg(GetBalance {
85 address,
86 network: self.network,
87 min_confirmations,
88 })
89 .build()
90 }
91
92 pub fn get_balance_query(
95 &self,
96 address: &str,
97 min_confirmations: Option<u32>,
98 ) -> impl 'agent + SyncCall<Value = (u64,)> {
99 self.query("bitcoin_get_balance_query")
100 .with_arg(GetBalance {
101 address,
102 network: self.network,
103 min_confirmations,
104 })
105 .build()
106 }
107
108 pub fn get_utxos(
114 &self,
115 address: &str,
116 filter: Option<UtxosFilter>,
117 ) -> impl 'agent + AsyncCall<Value = (GetUtxosResponse,)> {
118 self.update("bitcoin_get_utxos")
119 .with_arg(GetUtxos {
120 address,
121 network: self.network,
122 filter,
123 })
124 .build()
125 }
126
127 pub fn get_utxos_query(
133 &self,
134 address: &str,
135 filter: Option<UtxosFilter>,
136 ) -> impl 'agent + SyncCall<Value = (GetUtxosResponse,)> {
137 self.query("bitcoin_get_utxos_query")
138 .with_arg(GetUtxos {
139 address,
140 network: self.network,
141 filter,
142 })
143 .build()
144 }
145
146 pub fn get_current_fee_percentiles(&self) -> impl 'agent + AsyncCall<Value = (Vec<u64>,)> {
149 #[derive(CandidType)]
150 struct In {
151 network: BitcoinNetwork,
152 }
153 self.update("bitcoin_get_current_fee_percentiles")
154 .with_arg(In {
155 network: self.network,
156 })
157 .build()
158 }
159 pub fn get_block_headers(
162 &self,
163 start_height: u32,
164 end_height: Option<u32>,
165 ) -> impl 'agent + AsyncCall<Value = (GetBlockHeadersResponse,)> {
166 #[derive(CandidType)]
167 struct In {
168 start_height: u32,
169 end_height: Option<u32>,
170 }
171 self.update("bitcoin_get_block_headers")
172 .with_arg(In {
173 start_height,
174 end_height,
175 })
176 .build()
177 }
178 pub fn send_transaction(&self, transaction: Vec<u8>) -> impl 'agent + AsyncCall<Value = ()> {
180 #[derive(CandidType, Deserialize)]
181 struct In {
182 network: BitcoinNetwork,
183 #[serde(with = "serde_bytes")]
184 transaction: Vec<u8>,
185 }
186 self.update("bitcoin_send_transaction")
187 .with_arg(In {
188 network: self.network,
189 transaction,
190 })
191 .build()
192 }
193}
194
195#[derive(Debug, CandidType)]
196struct GetBalance<'a> {
197 address: &'a str,
198 network: BitcoinNetwork,
199 min_confirmations: Option<u32>,
200}
201
202#[derive(Debug, CandidType)]
203struct GetUtxos<'a> {
204 address: &'a str,
205 network: BitcoinNetwork,
206 filter: Option<UtxosFilter>,
207}
208
209#[derive(Clone, Copy, Debug, CandidType, Deserialize, PartialEq, Eq)]
211pub enum BitcoinNetwork {
212 #[serde(rename = "mainnet")]
214 Mainnet,
215 #[serde(rename = "testnet")]
217 Testnet,
218 #[serde(rename = "regtest")]
222 Regtest,
223}
224
225#[derive(Debug, Clone, CandidType, Deserialize)]
227pub enum UtxosFilter {
228 #[serde(rename = "min_confirmations")]
230 MinConfirmations(u32),
231 #[serde(rename = "page")]
233 Page(#[serde(with = "serde_bytes")] Vec<u8>),
234}
235
236#[derive(Debug, Clone, CandidType, Deserialize)]
238pub struct UtxoOutpoint {
239 #[serde(with = "serde_bytes")]
241 pub txid: Vec<u8>,
242 pub vout: u32,
244}
245
246#[derive(Debug, Clone, CandidType, Deserialize)]
248pub struct Utxo {
249 pub outpoint: UtxoOutpoint,
251 pub value: u64,
253 pub height: u32,
255}
256
257#[derive(Debug, Clone, CandidType, Deserialize)]
259pub struct GetUtxosResponse {
260 pub utxos: Vec<Utxo>,
262 #[serde(with = "serde_bytes")]
264 pub tip_block_hash: Vec<u8>,
265 pub tip_height: u32,
267 pub next_page: Option<Vec<u8>>,
270}
271
272#[derive(Debug, Clone, CandidType, Deserialize)]
274pub struct GetBlockHeadersResponse {
275 pub tip_height: u32,
277 pub block_headers: Vec<Vec<u8>>,
279}