txlog_client/
lib.rs

1//! txlog client implementation
2//!
3//! provides functions to read and write bitcoin transactions from a txlog server
4
5use errors::TxLogErrors;
6use hex;
7use serde::{Deserialize, Serialize};
8
9mod errors;
10
11#[derive(Clone)]
12pub struct TxlogClient {
13    pub url: String,
14    secret: String,
15}
16
17#[derive(Serialize, Deserialize)]
18pub struct SubmitResponse {
19    pub success: bool,
20}
21
22#[derive(Serialize, Deserialize)]
23pub struct TxResponse {
24    pub txid: String,
25    pub status: u32,
26    pub created: String,
27    pub updated: String,
28}
29
30// create an instance of a txlog client
31pub fn new(url: String, secret: String) -> TxlogClient {
32    // TODO validate input
33    return TxlogClient { url, secret };
34}
35
36pub struct SubmitMetadata {
37    pub metadata: Option<String>,
38    pub merchant: Option<String>,
39    pub network: Option<String>,
40}
41
42impl TxlogClient {
43    // fetch a raw transaction
44    pub async fn rawtx(&self, txid: &String) -> Result<Vec<u8>, TxLogErrors> {
45        let client = reqwest::Client::new();
46        let url = format!("{}/tx/{}/raw", &self.url, txid);
47        let response = client
48            .get(&url)
49            .header("Authorization", format!("Bearer {}", self.secret))
50            .send()
51            .await?;
52
53        let body = match response.text().await {
54            Err(e) => return Err(TxLogErrors::BodyDecodeError(e)),
55            Ok(v) => v,
56        };
57
58        let decoded = match hex::decode(&body) {
59            Ok(v) => v,
60            Err(e) => return Err(TxLogErrors::HexDecode(e)),
61        };
62
63        return Ok(decoded);
64    }
65
66    // fetch transaction status
67    pub async fn tx(&self, txid: &String) -> Result<TxResponse, TxLogErrors> {
68        let client = reqwest::Client::new();
69        let url = format!("{}/tx/{}", &self.url, txid);
70        let response = client
71            .get(&url)
72            .header("Authorization", format!("Bearer {}", self.secret))
73            .send()
74            .await?;
75
76        let res = match response.json::<TxResponse>().await {
77            Ok(v) => v,
78            Err(e) => return Err(TxLogErrors::BodyDecodeError(e)),
79        };
80
81        Ok(res)
82    }
83
84    // submit a transaction
85    pub async fn submit(
86        &self,
87        rawtx: Vec<u8>,
88        params: Option<SubmitMetadata>,
89    ) -> Result<SubmitResponse, TxLogErrors> {
90        let client = reqwest::Client::new();
91        let url = format!("{}/tx", &self.url);
92        let mut query_params: Vec<(String, String)> = vec![];
93
94        match params {
95            Some(p) => {
96                match p.metadata {
97                    Some(v) => query_params.push(("metadata".to_string(), v)),
98                    None => (),
99                };
100                match p.merchant {
101                    Some(v) => query_params.push(("merchant".to_string(), v)),
102                    None => (),
103                };
104                match p.network {
105                    Some(v) => query_params.push(("network".to_string(), v)),
106                    None => (),
107                };
108            }
109            None => (),
110        }
111
112        let response = client
113            .post(&url)
114            .query(&query_params)
115            .header("Authorization", format!("Bearer {}", self.secret))
116            .header("Content-Type", "application/octet-stream")
117            .body(rawtx)
118            .send()
119            .await?;
120
121        let res = match response.json::<SubmitResponse>().await {
122            Ok(v) => v,
123            Err(e) => return Err(TxLogErrors::BodyDecodeError(e)),
124        };
125
126        if !res.success {
127            return Err(TxLogErrors::SubmitResponseFailed);
128        }
129
130        Ok(res)
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    macro_rules! aw {
137        ($e:expr) => {
138            tokio_test::block_on($e)
139        };
140    }
141
142    #[test]
143    fn test_rawtx() {
144        // Add variables to .env file
145        let secret = dotenv::var("TXLOG_SECRET").unwrap();
146        let url = dotenv::var("TXLOG_URL").unwrap();
147        let rawtx = dotenv::var("RAWTX").unwrap();
148        let txid = dotenv::var("TXID").unwrap();
149
150        let client = crate::new(url, secret);
151
152        let response = aw!(client.rawtx(&txid)).unwrap();
153
154        assert_eq!(response, hex::decode(&rawtx).unwrap());
155    }
156
157    #[test]
158    fn test_submit() {
159        // Add variables to .env file
160        let secret = dotenv::var("TXLOG_SECRET").unwrap();
161        let url = dotenv::var("TXLOG_URL").unwrap();
162        let rawtx = dotenv::var("RAWTX").unwrap();
163
164        let client = crate::new(url, secret);
165
166        let response = aw!(client.submit(hex::decode(&rawtx).unwrap(), None)).unwrap();
167
168        assert_eq!(response.success, true);
169    }
170
171    #[test]
172    fn test_tx() {
173        // Add variables to .env file
174        let secret = dotenv::var("TXLOG_SECRET").unwrap();
175        let url = dotenv::var("TXLOG_URL").unwrap();
176        let txid = dotenv::var("TXID").unwrap();
177
178        let client = crate::new(url, secret);
179
180        let response = aw!(client.tx(&txid)).unwrap();
181
182        assert_eq!(response.txid, txid);
183    }
184}