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", ¶ms)
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 ¶ms,
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", ¶ms)
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", ¶ms)
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", ¶ms)
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}