use std::ops::Deref;
use candid::{CandidType, Principal};
use ic_agent::{Agent, AgentError};
use serde::Deserialize;
use crate::{
call::{AsyncCall, SyncCall},
Canister,
};
#[derive(Debug)]
pub struct BitcoinCanister<'agent> {
canister: Canister<'agent>,
network: BitcoinNetwork,
}
impl<'agent> Deref for BitcoinCanister<'agent> {
type Target = Canister<'agent>;
fn deref(&self) -> &Self::Target {
&self.canister
}
}
const MAINNET_ID: Principal =
Principal::from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, 0xa0, 0x00, 0x04, 0x01, 0x01]);
const TESTNET_ID: Principal =
Principal::from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, 0xa0, 0x00, 0x01, 0x01, 0x01]);
impl<'agent> BitcoinCanister<'agent> {
pub fn from_canister(canister: Canister<'agent>, network: BitcoinNetwork) -> Self {
Self { canister, network }
}
pub fn create(agent: &'agent Agent, canister_id: Principal, network: BitcoinNetwork) -> Self {
Self::from_canister(
Canister::builder()
.with_agent(agent)
.with_canister_id(canister_id)
.build()
.expect("all required fields should be set"),
network,
)
}
pub fn mainnet(agent: &'agent Agent) -> Self {
Self::for_network(agent, BitcoinNetwork::Mainnet).expect("valid network")
}
pub fn testnet(agent: &'agent Agent) -> Self {
Self::for_network(agent, BitcoinNetwork::Testnet).expect("valid network")
}
pub fn for_network(agent: &'agent Agent, network: BitcoinNetwork) -> Result<Self, AgentError> {
let canister_id = match network {
BitcoinNetwork::Mainnet => MAINNET_ID,
BitcoinNetwork::Testnet => TESTNET_ID,
BitcoinNetwork::Regtest => {
return Err(AgentError::MessageError(
"No applicable canister ID for regtest".to_string(),
))
}
};
Ok(Self::create(agent, canister_id, network))
}
pub fn get_balance(
&self,
address: &str,
min_confirmations: Option<u32>,
) -> impl 'agent + AsyncCall<Value = (u64,)> {
self.update("bitcoin_get_balance")
.with_arg(GetBalance {
address,
network: self.network,
min_confirmations,
})
.build()
}
pub fn get_balance_query(
&self,
address: &str,
min_confirmations: Option<u32>,
) -> impl 'agent + SyncCall<Value = (u64,)> {
self.query("bitcoin_get_balance_query")
.with_arg(GetBalance {
address,
network: self.network,
min_confirmations,
})
.build()
}
pub fn get_utxos(
&self,
address: &str,
filter: Option<UtxosFilter>,
) -> impl 'agent + AsyncCall<Value = (GetUtxosResponse,)> {
self.update("bitcoin_get_utxos")
.with_arg(GetUtxos {
address,
network: self.network,
filter,
})
.build()
}
pub fn get_utxos_query(
&self,
address: &str,
filter: Option<UtxosFilter>,
) -> impl 'agent + SyncCall<Value = (GetUtxosResponse,)> {
self.query("bitcoin_get_utxos_query")
.with_arg(GetUtxos {
address,
network: self.network,
filter,
})
.build()
}
pub fn get_current_fee_percentiles(&self) -> impl 'agent + AsyncCall<Value = (Vec<u64>,)> {
#[derive(CandidType)]
struct In {
network: BitcoinNetwork,
}
self.update("bitcoin_get_current_fee_percentiles")
.with_arg(In {
network: self.network,
})
.build()
}
pub fn get_block_headers(
&self,
start_height: u32,
end_height: Option<u32>,
) -> impl 'agent + AsyncCall<Value = (GetBlockHeadersResponse,)> {
#[derive(CandidType)]
struct In {
start_height: u32,
end_height: Option<u32>,
}
self.update("bitcoin_get_block_headers")
.with_arg(In {
start_height,
end_height,
})
.build()
}
pub fn send_transaction(&self, transaction: Vec<u8>) -> impl 'agent + AsyncCall<Value = ()> {
#[derive(CandidType, Deserialize)]
struct In {
network: BitcoinNetwork,
#[serde(with = "serde_bytes")]
transaction: Vec<u8>,
}
self.update("bitcoin_send_transaction")
.with_arg(In {
network: self.network,
transaction,
})
.build()
}
}
#[derive(Debug, CandidType)]
struct GetBalance<'a> {
address: &'a str,
network: BitcoinNetwork,
min_confirmations: Option<u32>,
}
#[derive(Debug, CandidType)]
struct GetUtxos<'a> {
address: &'a str,
network: BitcoinNetwork,
filter: Option<UtxosFilter>,
}
#[derive(Clone, Copy, Debug, CandidType, Deserialize, PartialEq, Eq)]
pub enum BitcoinNetwork {
#[serde(rename = "mainnet")]
Mainnet,
#[serde(rename = "testnet")]
Testnet,
#[serde(rename = "regtest")]
Regtest,
}
#[derive(Debug, Clone, CandidType, Deserialize)]
pub enum UtxosFilter {
#[serde(rename = "min_confirmations")]
MinConfirmations(u32),
#[serde(rename = "page")]
Page(#[serde(with = "serde_bytes")] Vec<u8>),
}
#[derive(Debug, Clone, CandidType, Deserialize)]
pub struct UtxoOutpoint {
#[serde(with = "serde_bytes")]
pub txid: Vec<u8>,
pub vout: u32,
}
#[derive(Debug, Clone, CandidType, Deserialize)]
pub struct Utxo {
pub outpoint: UtxoOutpoint,
pub value: u64,
pub height: u32,
}
#[derive(Debug, Clone, CandidType, Deserialize)]
pub struct GetUtxosResponse {
pub utxos: Vec<Utxo>,
#[serde(with = "serde_bytes")]
pub tip_block_hash: Vec<u8>,
pub tip_height: u32,
pub next_page: Option<Vec<u8>>,
}
#[derive(Debug, Clone, CandidType, Deserialize)]
pub struct GetBlockHeadersResponse {
pub tip_height: u32,
pub block_headers: Vec<Vec<u8>>,
}