elrond_rust/
rest.rs

1//! An HTTP client for interaction with the Elrond network. 
2
3use serde_json::{Map, Value};
4use super::{SignedTransaction, ElrondCurrencyAmount, ElrondAddress, Result, ElrondClientError};
5
6/// Internal helper for type of outgoing request
7enum RequestType {
8    GET,
9    POST
10}
11
12/// Client manages interactions with the Elrond network
13pub struct Client {
14    endpoint: String
15}
16
17impl Client {
18    /// Internal helper for submitting requests to the network
19    fn request(&self, path: &str, request_type: RequestType, data: Option<Value>) -> Result<Value> {
20        let full_path = format!("{}/{}", self.endpoint, path);
21        let resp = match (request_type, data) {
22            (RequestType::GET, Some(json)) => {
23                ureq::get(&full_path).send_json(json)
24            },
25            (RequestType::GET, None) => {
26                ureq::get(&full_path).call()
27            },
28            (RequestType::POST, Some(json)) => {
29                ureq::post(&full_path).send_json(json)
30            },
31            (RequestType::POST, None) => {
32                ureq::post(&full_path).call()
33            }
34        };
35        if resp.ok() {
36            let response = resp.into_json().map_err(|_| {
37                ElrondClientError::new("could not decode response to JSON")
38            })?;
39            Ok(response)
40        } else {
41            let status = &resp.status();
42            let error_response = resp.into_string().map_err(|_|{
43                ElrondClientError::new("could not decode error response to string")
44            })?;
45            Err(ElrondClientError::new(
46                &format!(
47                    "error code {}, data='{}'",
48                    status,
49                    error_response
50                )
51            ))
52        }
53    }
54}
55
56/// Unpackage 'data' object from Elrond API response
57fn parse_response_data(response: &Value) -> Result<&Map<String, Value>> {
58    response
59        .as_object()
60        .ok_or(
61            ElrondClientError::new("response is not a JSON object")
62        )?
63        .get("data")
64        .ok_or(
65            ElrondClientError::new("response does not contain 'data' field")
66        )?
67        .as_object()
68        .ok_or(
69            ElrondClientError::new("'data' is not a JSON object")
70        )
71}
72
73
74impl Client {
75    /// Create a new client that will work on Elrond MainNet
76    pub fn new() -> Self {
77        Self { endpoint: "https://api.elrond.com".to_string() }
78    }
79
80    /// Get the current nonce associated with an address
81    pub fn get_address_nonce(&self, addr_str: &str) -> Result<u64> {
82        let address = ElrondAddress::new(addr_str)?;
83        let path = format!("address/{}/nonce", address.to_string());
84        let response = self.request(&path, RequestType::GET, None)?;
85        let nonce = parse_response_data(&response)?
86            .get("nonce")
87            .ok_or(
88                ElrondClientError::new("response does not contain 'nonce' field")
89            )?
90            .as_u64()
91            .ok_or(
92                ElrondClientError::new("'nonce' is not a number")
93            )?;
94        Ok(nonce)
95    }
96
97    /// Post signed transaction to Elrond network and return hash of transaction
98    pub fn post_signed_transaction(&self, signed_tx: SignedTransaction) -> Result<String>{
99        let path = "transaction/send";
100        let serialized_tx = signed_tx.serialize()?;
101        // this unwrap is safe, just serialized it...
102        let json_tx = serde_json::from_str(&serialized_tx).unwrap();
103        let response = self.request(path, RequestType::POST, Some(json_tx))?;
104        let tx_hash = parse_response_data(&response)?
105            .get("txHash")
106            .ok_or(ElrondClientError::new("response does not contain 'txHash' field"))?
107            .as_str()
108            .ok_or(ElrondClientError::new("tx hash is not a string"))?;
109        Ok(tx_hash.to_string())
110    }
111
112    /// Get the balance associated with an Elrond address
113    pub fn get_address_balance(&self, addr_str: &str) -> Result<ElrondCurrencyAmount> {
114        let address = ElrondAddress::new(addr_str)?;
115        let path = format!("address/{}/balance", address.to_string());
116        let response = self.request(&path, RequestType::GET, None)?;
117        let balance = parse_response_data(&response)?
118            .get("balance")
119            .ok_or(
120                ElrondClientError::new("response does not contain 'balance' field")
121            )?
122            .as_str()
123            .ok_or(
124                ElrondClientError::new("'balance' is not a number")
125            )?;
126        Ok(ElrondCurrencyAmount::from_blockchain_precision(balance)?)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::Client;
133    use super::super::account::Account;
134    
135    #[test]
136    pub fn get_address_nonce(){
137        let address = "erd16jats393r8rnut88yhvu5wvxxje57qzlj3tqk7n6jnf7f6cxs4uqfeh65k";
138        let client = Client::new();
139        let response = client.get_address_nonce(address).unwrap();
140        println!("{:?}", response);
141    }
142
143    #[test]
144    pub fn get_newly_generated_address_nonce() {
145        let account = Account::generate().unwrap();
146        let address = account.address.to_string();
147        println!("{}", address);
148        let client = Client::new();
149        let nonce = client.get_address_nonce(&address).unwrap();
150        println!("{:?}", nonce);
151        assert_eq!(nonce, 0);
152    }
153
154    #[test]
155    pub fn get_address_balance() {
156        let client = Client::new();
157        let address = "erd18gx50mf0xvz3c3xljm0s5pkz0zugsprltl86ux6f0sz94gqeua3q7l77wd";
158        let balance = client.get_address_balance(address).unwrap();
159        assert_eq!(balance.to_string(), "0.0001");
160    }
161
162}
163
164
165