1use crate::{GetPubkeyResponse, SignTxInputRequest, SignTxInputResponse};
2use bitcoin::consensus::{Decodable, Encodable};
3use bitcoin::hex::{DisplayHex, FromHex};
4use bitcoin::{Transaction, XOnlyPublicKey};
5use reqwest::Client;
6use std::fmt::{Display, Formatter};
7
8#[derive(Debug, Clone)]
9pub struct CatSignerClient {
10 pub base_url: String,
11 pub client: Client,
12}
13
14impl CatSignerClient {
15 pub fn new(base_url: String) -> Result<Self, Error> {
17 let client_builder = Client::builder();
18
19 Ok(Self::from_client(client_builder.build()?, base_url))
20 }
21
22 pub fn from_client(client: Client, base_url: String) -> Self {
24 Self { client, base_url }
25 }
26
27 pub async fn get_pubkey(&self) -> Result<XOnlyPublicKey, Error> {
29 let url = format!("{}/pubkey", self.base_url);
30 let resp = self.client.get(&url).send().await?;
31
32 let resp: GetPubkeyResponse = resp.error_for_status()?.json::<GetPubkeyResponse>().await?;
33 Ok(resp.pubkey)
34 }
35
36 pub async fn sign_tx_input(
41 &self,
42 tx: &Transaction,
43 input_index: usize,
44 ) -> Result<Transaction, Error> {
45 let url = format!("{}/sign-tx-input", self.base_url);
46
47 let mut tx_bytes: Vec<u8> = Vec::with_capacity(tx.total_size());
48 tx.consensus_encode(&mut tx_bytes)
49 .expect("consensus encode");
50
51 let payload = SignTxInputRequest {
52 tx: tx_bytes.to_lower_hex_string(),
53 index: input_index,
54 };
55
56 let resp = self
57 .client
58 .post(&url)
59 .json(&payload)
60 .send()
61 .await?
62 .error_for_status()?
63 .json::<SignTxInputResponse>()
64 .await?;
65
66 let hex_bytes = Vec::from_hex(&resp.tx)
67 .map_err(|_| Error::Other("Could not decode hex".to_string()))?;
68
69 let signed_tx = Transaction::consensus_decode(&mut hex_bytes.as_slice())
70 .map_err(|_| Error::Other("Could not decode tx".to_string()))?;
71
72 Ok(signed_tx)
73 }
74}
75
76#[derive(Debug)]
77pub enum Error {
78 Reqwest(reqwest::Error),
79 HttpResponse(u16),
81 Json(serde_json::Error),
83 InvalidResponse,
85 Other(String),
87}
88
89impl Display for Error {
90 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91 write!(f, "{:?}", self)
92 }
93}
94
95impl std::error::Error for Error {}
96
97macro_rules! impl_error {
98 ( $from:ty, $to:ident ) => {
99 impl_error!($from, $to, Error);
100 };
101 ( $from:ty, $to:ident, $impl_for:ty ) => {
102 impl std::convert::From<$from> for $impl_for {
103 fn from(err: $from) -> Self {
104 <$impl_for>::$to(err)
105 }
106 }
107 };
108}
109
110impl_error!(reqwest::Error, Reqwest, Error);
111impl_error!(serde_json::Error, Json, Error);