use crate::{GetPubkeyResponse, SignTxInputRequest, SignTxInputResponse};
use bitcoin::consensus::{Decodable, Encodable};
use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::{Transaction, XOnlyPublicKey};
use reqwest::Client;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone)]
pub struct CatSignerClient {
pub base_url: String,
pub client: Client,
}
impl CatSignerClient {
pub fn new(base_url: String) -> Result<Self, Error> {
let client_builder = Client::builder();
Ok(Self::from_client(client_builder.build()?, base_url))
}
pub fn from_client(client: Client, base_url: String) -> Self {
Self { client, base_url }
}
pub async fn get_pubkey(&self) -> Result<XOnlyPublicKey, Error> {
let url = format!("{}/pubkey", self.base_url);
let resp = self.client.get(&url).send().await?;
let resp: GetPubkeyResponse = resp.error_for_status()?.json::<GetPubkeyResponse>().await?;
Ok(resp.pubkey)
}
pub async fn sign_tx_input(
&self,
tx: &Transaction,
input_index: usize,
) -> Result<Transaction, Error> {
let url = format!("{}/sign-tx-input", self.base_url);
let mut tx_bytes: Vec<u8> = Vec::with_capacity(tx.total_size());
tx.consensus_encode(&mut tx_bytes)
.expect("consensus encode");
let payload = SignTxInputRequest {
tx: tx_bytes.to_lower_hex_string(),
index: input_index,
};
let resp = self
.client
.post(&url)
.json(&payload)
.send()
.await?
.error_for_status()?
.json::<SignTxInputResponse>()
.await?;
let hex_bytes = Vec::from_hex(&resp.tx)
.map_err(|_| Error::Other("Could not decode hex".to_string()))?;
let signed_tx = Transaction::consensus_decode(&mut hex_bytes.as_slice())
.map_err(|_| Error::Other("Could not decode tx".to_string()))?;
Ok(signed_tx)
}
}
#[derive(Debug)]
pub enum Error {
Reqwest(reqwest::Error),
HttpResponse(u16),
Json(serde_json::Error),
InvalidResponse,
Other(String),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::error::Error for Error {}
macro_rules! impl_error {
( $from:ty, $to:ident ) => {
impl_error!($from, $to, Error);
};
( $from:ty, $to:ident, $impl_for:ty ) => {
impl std::convert::From<$from> for $impl_for {
fn from(err: $from) -> Self {
<$impl_for>::$to(err)
}
}
};
}
impl_error!(reqwest::Error, Reqwest, Error);
impl_error!(serde_json::Error, Json, Error);