taurus_api/
taurus.rs

1use crate::config::Wallet;
2use anyhow::bail;
3use reqwest::blocking::Client;
4use serde::{Deserialize, Serialize};
5use serde_aux::prelude::*;
6use std::time::Duration;
7
8#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
9pub struct NodeInfo {
10    pub name: String,
11    pub version: String,
12    pub runtime_environment: String,
13    pub id: String,
14    pub commit: String,
15}
16
17#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
18#[serde(rename_all = "camelCase")]
19pub struct Score {
20    id: String,
21    provider: String,
22    #[serde(rename(deserialize = "type"))]
23    score_type: String,
24    score: String,
25    update_date: String,
26}
27
28#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
29#[serde(rename_all = "camelCase")]
30pub struct CurrencyInfo {
31    name: String,
32    symbol: String,
33    blockchain: String,
34    decimals: String,
35    contract_address: Option<String>,
36    is_u_t_x_o_based: Option<bool>,
37    enabled: bool,
38    id: String,
39    display_name: String,
40    #[serde(rename(deserialize = "type"))]
41    currency_type: String,
42}
43
44#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
45#[serde(rename_all = "camelCase")]
46pub struct WalletInfo {
47    pub id: String,
48    pub balance: Balance,
49    pub currency: String,
50    pub coin: String,
51    pub name: String,
52    pub container: Option<String>,
53    pub account_path: String,
54    pub is_omnibus: Option<bool>,
55    pub creation_date: String,
56    pub update_date: String,
57    pub blockchain: String,
58    pub currency_info: CurrencyInfo,
59}
60
61#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
62#[serde(rename_all = "camelCase")]
63pub struct Attributes {
64    key: String,
65    value: String,
66    id: String,
67    content_type: String,
68    owner: String,
69    #[serde(rename(deserialize = "type"))]
70    attribute_type: String,
71    subtype: String,
72    isfile: bool,
73}
74
75#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
76#[serde(rename_all = "camelCase")]
77pub struct Balance {
78    #[serde(deserialize_with = "deserialize_number_from_string")]
79    pub total_confirmed: u128,
80    #[serde(deserialize_with = "deserialize_number_from_string")]
81    pub total_unconfirmed: u128,
82    #[serde(deserialize_with = "deserialize_number_from_string")]
83    pub available_confirmed: u128,
84    #[serde(deserialize_with = "deserialize_number_from_string")]
85    pub available_unconfirmed: u128,
86    #[serde(deserialize_with = "deserialize_number_from_string")]
87    pub reserved_confirmed: u128,
88    #[serde(deserialize_with = "deserialize_number_from_string")]
89    pub reserved_unconfirmed: u128,
90}
91
92#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
93#[serde(rename_all = "camelCase")]
94pub struct Addresses {
95    pub id: String,
96    pub wallet_id: String,
97    pub address_path: String,
98    pub address: String,
99    pub label: String,
100    pub signature: String,
101}
102
103#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
104pub struct WalletResponse {
105    pub result: Option<Vec<WalletInfo>>,
106    pub total_items: Option<String>,
107}
108
109#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
110pub struct AddressesResponse {
111    pub result: Option<Vec<Addresses>>,
112    pub total_items: Option<String>,
113}
114
115#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
116pub struct Token {
117    pub result: String,
118}
119
120#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
121pub struct TokenParams {
122    pub email: String,
123    pub password: String,
124    pub totp: Option<String>,
125    pub username: Option<String>,
126}
127
128#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
129#[serde(rename_all = "camelCase")]
130pub struct AccountInfo {
131    pub sequence: String,
132    pub account_number: String,
133}
134
135#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
136#[serde(rename_all = "camelCase")]
137pub struct RequestParams {
138    pub chain_id: String,
139    pub signers: Vec<u16>,
140    pub broadcast_kind: String,
141    pub fee_denom: String,
142    pub gas_limit: String,
143    pub fee: String,
144    pub accounts_info: Vec<AccountInfo>,
145    pub messages: Vec<crate::payload::Message>,
146}
147
148#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
149#[serde(rename_all = "camelCase")]
150pub struct ValueParams {
151    pub primitive: String,
152}
153
154#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
155#[serde(rename_all = "camelCase")]
156pub struct EthArgsParams {
157    pub name: String,
158    #[serde(rename(serialize = "type"))]
159    pub attribute_type: String,
160    pub value: ValueParams,
161}
162
163#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
164#[serde(rename_all = "camelCase")]
165pub struct EthParams {
166    pub function_signature: String,
167    pub args: Vec<EthArgsParams>,
168}
169
170#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
171#[serde(rename_all = "camelCase")]
172pub struct CallParams {
173    pub blockchain: String,
174    pub eth: EthParams,
175}
176
177#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
178#[serde(rename_all = "camelCase")]
179pub struct ApproveParams {
180    pub from_address_id: String,
181    pub to_whitelisted_address_id: String,
182    pub contract_type: String,
183    pub call: CallParams,
184}
185
186#[derive(Serialize, Clone, Debug, Eq, PartialEq, Default)]
187#[serde(rename_all = "camelCase")]
188pub struct WhitelistParams {
189    pub blockchain: Option<String>,
190    pub label: String,
191    pub address: String,
192    pub address_type: String,
193    pub contract_type: Option<String>,
194}
195
196#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, Default)]
197#[serde(rename_all = "camelCase")]
198pub struct SignedRequests {
199    pub id: String,
200    pub signed_request: String,
201    pub status: String,
202    pub creation_date: String,
203    pub update_date: String,
204}
205
206#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, Default)]
207#[serde(rename_all = "camelCase")]
208pub struct Trails {
209    pub user_id: String,
210    pub external_user_id: String,
211    pub action: String,
212    pub date: Option<String>,
213    pub request_status: String,
214}
215
216#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, Default)]
217#[serde(rename_all = "camelCase")]
218pub struct Payload {
219    pub column: String,
220    pub key: String,
221    #[serde(rename(deserialize = "type"))]
222    pub payload_type: String,
223    pub value: serde_json::Value,
224}
225
226#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, Default)]
227#[serde(rename_all = "camelCase")]
228pub struct Metadata {
229    pub hash: String,
230    pub payload: Vec<Payload>,
231}
232
233#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, Default)]
234#[serde(rename_all = "camelCase")]
235pub struct RequestInfos {
236    pub id: String,
237    pub tenant_id: String,
238    pub currency: String,
239    pub envelope: String,
240    pub status: String,
241    #[serde(rename(deserialize = "type"))]
242    pub type_request: String,
243    pub signed_requests: Option<Vec<SignedRequests>>,
244    pub trails: Vec<Trails>,
245    pub metadata: Option<Metadata>,
246}
247
248#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, Default)]
249#[serde(rename_all = "camelCase")]
250pub struct RequestResponse {
251    pub result: RequestInfos,
252}
253
254#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, Default)]
255#[serde(rename_all = "camelCase")]
256pub struct WhitelistInfos {
257    pub id: String,
258}
259
260#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, Default)]
261#[serde(rename_all = "camelCase")]
262pub struct WhitelistResponse {
263    pub result: WhitelistInfos,
264}
265
266pub struct Taurus {
267    address: String,
268    client: Client,
269    token: Option<String>,
270}
271
272impl Taurus {
273    pub fn new(cfg: &crate::config::Taurus) -> Result<Self, anyhow::Error> {
274        let client = Client::builder()
275            .timeout(Duration::from_secs(120))
276            .build()?;
277
278        let mut taurus = Taurus {
279            address: cfg.api_url.clone(),
280            client,
281            token: None,
282        };
283
284        taurus.login(cfg.mail.as_str(), cfg.passwd.as_str())?;
285
286        Ok(taurus)
287    }
288
289    pub fn login(&mut self, email: &str, password: &str) -> Result<(), anyhow::Error> {
290        let token = self.token(TokenParams {
291            email: email.to_string(),
292            password: password.to_string(),
293            ..Default::default()
294        })?;
295
296        log::info!("Token generated");
297
298        self.token = Some(format!("Bearer {}", token.result));
299
300        Ok(())
301    }
302
303    fn get<T: serde::de::DeserializeOwned + Clone>(
304        &self,
305        endpoint: &str,
306    ) -> Result<T, anyhow::Error> {
307        log::debug!("GET {}", endpoint);
308        let mut request_builder = self.client.get(format!("{}{}", self.address, endpoint));
309
310        if let Some(bearer) = self.token.clone() {
311            request_builder = request_builder.header("Authorization", bearer);
312        }
313        let request = request_builder.send()?;
314
315        let data = &request.text()?;
316        log::trace!("-> payload\n{}", data);
317
318        let output = serde_json::from_str::<T>(data);
319
320        Ok(output.unwrap())
321    }
322
323    fn post<T: serde::de::DeserializeOwned + Clone, U: serde::ser::Serialize + Clone>(
324        &self,
325        endpoint: &str,
326        data: &U,
327    ) -> Result<T, anyhow::Error> {
328        log::debug!("POST {}", endpoint);
329        let body = serde_json::to_string(data)?;
330        log::debug!("\t Body {}", body);
331        let mut request_builder = self
332            .client
333            .post(format!("{}{}", self.address, endpoint))
334            .body(body)
335            .header("Content-Type", "application/json");
336        if let Some(bearer) = self.token.clone() {
337            request_builder = request_builder.header("Authorization", bearer);
338        }
339
340        let request = request_builder.send()?;
341
342        let data = &request.text()?;
343        log::trace!("-> payload\n{}", data);
344
345        let output = serde_json::from_str::<T>(data);
346
347        Ok(output.unwrap())
348    }
349
350    fn token(&self, params: TokenParams) -> Result<Token, anyhow::Error> {
351        self.post("/api/rest/v1/authentication/token", &params)
352    }
353
354    pub fn addresses(&self) -> Result<AddressesResponse, anyhow::Error> {
355        self.get("/api/rest/v1/addresses")
356    }
357
358    pub fn addresses_by_address(&self, wallet: Wallet) -> Result<Addresses, anyhow::Error> {
359        let addresses = self.addresses()?;
360
361        if addresses.result.is_none() {
362            bail!("no matching addresses");
363        }
364
365        let addresses = addresses.result.unwrap();
366        let pos = addresses.iter().position(|x| x.address == wallet.address);
367
368        if pos.is_none() {
369            bail!("no matching addresses");
370        }
371
372        Ok(addresses[pos.unwrap()].clone())
373    }
374
375    pub fn request(&self, params: RequestParams) -> Result<RequestResponse, anyhow::Error> {
376        self.post(
377            "/api/rest/v1/requests/outgoing/cosmos/generic_request",
378            &params,
379        )
380    }
381
382    pub fn add_contract_whitelist(
383        &self,
384        params: WhitelistParams,
385    ) -> Result<WhitelistResponse, anyhow::Error> {
386        self.post("/api/rest/v1/whitelists/addresses", &params)
387    }
388
389    pub fn add_addr_whitelist(
390        &self,
391        params: WhitelistParams,
392    ) -> Result<WhitelistResponse, anyhow::Error> {
393        self.post("/api/rest/v1/whitelists/addresses", &params)
394    }
395
396    pub fn ethereum_approve(
397        &self,
398        params: ApproveParams,
399    ) -> Result<RequestResponse, anyhow::Error> {
400        self.post("/api/rest/v1/requests/outgoing/contracts/call", &params)
401    }
402
403    pub fn request_by_id(&self, id: u64) -> Result<RequestResponse, anyhow::Error> {
404        self.get(format!("/api/rest/v1/requests/{}", id).as_str())
405    }
406}