aptos_network_sdk/
lib.rs

1pub mod contract;
2pub mod trade;
3pub mod types;
4pub mod wallet;
5use crate::types::*;
6use reqwest::Client;
7use serde_json::Value;
8use std::time::Duration;
9
10/// aptos rpc url
11const APTOS_MAINNET_URL: &str = "https://fullnode.mainnet.aptoslabs.com/v1";
12const APTOS_TESTNET_URL: &str = "https://fullnode.testnet.aptoslabs.com/v1";
13const APTOS_DEVNET_URL: &str = "https://fullnode.devnet.aptoslabs.com/v1";
14
15/// waiting transaction delay time
16const WAITING_TRANSACTION_DELAY_TIME: u64 = 500;
17
18/// client type
19#[derive(Debug, Clone)]
20pub enum AptosClientType {
21    Mainnet,
22    Testnet,
23    Devnet,
24}
25
26#[derive(Debug, Clone)]
27pub struct AptosClient {
28    client: Client,
29    base_url: String,
30}
31
32impl AptosClient {
33    pub fn new(network: AptosClientType) -> Self {
34        let base_url = match network {
35            AptosClientType::Mainnet => APTOS_MAINNET_URL.to_string(),
36            AptosClientType::Testnet => APTOS_TESTNET_URL.to_string(),
37            AptosClientType::Devnet => APTOS_DEVNET_URL.to_string(),
38        };
39        AptosClient {
40            client: Client::new(),
41            base_url,
42        }
43    }
44
45    /// get account info
46    pub async fn get_account_info(&self, address: &str) -> Result<AccountInfo, String> {
47        let url: String = format!("{}/accounts/{}", self.base_url, address);
48        let response = self.client.get(&url).send().await.unwrap();
49        if !response.status().is_success() {
50            let error_msg = response.text().await.unwrap();
51            return Err(format!("api error: {}", error_msg).to_string());
52        }
53
54        let account_info: AccountInfo = response.json().await.unwrap();
55        Ok(account_info)
56    }
57
58    /// get account resources vec
59    pub async fn get_account_resource_vec(&self, address: &str) -> Result<Vec<Resource>, String> {
60        let url = format!("{}/accounts/{}/resources", self.base_url, address);
61        let response = self.client.get(&url).send().await.unwrap();
62        if !response.status().is_success() {
63            let error_msg = response.text().await.unwrap();
64            return Err(format!("api error: {}", error_msg).to_string());
65        }
66        let resources: Vec<Resource> = response.json().await.unwrap();
67        Ok(resources)
68    }
69
70    /// get account resource
71    pub async fn get_account_resource(
72        &self,
73        address: &str,
74        resource_type: &str,
75    ) -> Result<Option<Resource>, String> {
76        let url = format!(
77            "{}/accounts/{}/resource/{}",
78            self.base_url, address, resource_type
79        );
80        let response = self.client.get(&url).send().await.unwrap();
81
82        if response.status() == 404 {
83            return Ok(None);
84        }
85
86        if !response.status().is_success() {
87            let error_msg = response.text().await.unwrap();
88            return Err(format!("api error: {}", error_msg).to_string());
89        }
90
91        let resource: Resource = response.json().await.unwrap();
92        Ok(Some(resource))
93    }
94
95    /// get account module vec
96    pub async fn get_account_module_vec(&self, address: &str) -> Result<Vec<Module>, String> {
97        let url = format!("{}/accounts/{}/modules", self.base_url, address);
98        let response = self.client.get(&url).send().await.unwrap();
99        if !response.status().is_success() {
100            let error_msg = response.text().await.unwrap();
101            return Err(format!("api error: {}", error_msg).to_string());
102        }
103        let modules: Vec<Module> = response.json().await.unwrap();
104        Ok(modules)
105    }
106
107    /// get account module
108    pub async fn get_account_module(
109        &self,
110        address: &str,
111        module_name: &str,
112    ) -> Result<Option<Module>, String> {
113        let url = format!(
114            "{}/accounts/{}/module/{}",
115            self.base_url, address, module_name
116        );
117        let response = self.client.get(&url).send().await.unwrap();
118        if response.status() == 404 {
119            return Ok(None);
120        }
121        if !response.status().is_success() {
122            let error_msg = response.text().await.unwrap();
123            return Err(format!("api error: {}", error_msg).to_string());
124        }
125        let module: Module = response.json().await.unwrap();
126        Ok(Some(module))
127    }
128
129    /// submit transaction
130    pub async fn submit_transaction(&self, txn_payload: &Value) -> Result<Transaction, String> {
131        let url = format!("{}/transactions", self.base_url);
132        let response = self
133            .client
134            .post(&url)
135            .header("Content-Type", "application/json")
136            .json(txn_payload)
137            .send()
138            .await
139            .unwrap();
140        if !response.status().is_success() {
141            let error_msg = response.text().await.unwrap();
142            return Err(format!("transaction submit failed: {}", error_msg).to_string());
143        }
144        let transaction: Transaction = response.json().await.unwrap();
145        Ok(transaction)
146    }
147
148    /// get transaction info
149    pub async fn get_transaction_info(&self, txn_hash: &str) -> Result<Transaction, String> {
150        let url = format!("{}/transactions/{}", self.base_url, txn_hash);
151        let response = self.client.get(&url).send().await.unwrap();
152        if !response.status().is_success() {
153            let error_msg = response.text().await.unwrap();
154            return Err(format!("api error: {}", error_msg).to_string());
155        }
156        let transaction: Transaction = response.json().await.unwrap();
157        Ok(transaction)
158    }
159
160    /// get transaction by version
161    pub async fn get_transaction_by_version(&self, version: u64) -> Result<Transaction, String> {
162        let url = format!("{}/transactions/by_version/{}", self.base_url, version);
163        let response = self.client.get(&url).send().await.unwrap();
164        if !response.status().is_success() {
165            let error_msg = response.text().await.unwrap();
166            return Err(format!("api error: {}", error_msg).to_string());
167        }
168        let transaction: Transaction = response.json().await.unwrap();
169        Ok(transaction)
170    }
171
172    /// get account transaction vec
173    pub async fn get_account_transaction_vec(
174        &self,
175        address: &str,
176        limit: Option<u64>,
177        start: Option<u64>,
178    ) -> Result<Vec<Transaction>, String> {
179        let limit = limit.unwrap_or(25);
180        let mut url = format!(
181            "{}/accounts/{}/transactions?limit={}",
182            self.base_url, address, limit
183        );
184        if let Some(start) = start {
185            url.push_str(&format!("&start={}", start));
186        }
187        let response = self.client.get(&url).send().await.unwrap();
188        if !response.status().is_success() {
189            let error_msg = response.text().await.unwrap();
190            return Err(format!("api error: {}", error_msg).to_string());
191        }
192        let transactions: Vec<Transaction> = response.json().await.unwrap();
193        Ok(transactions)
194    }
195
196    /// get chain info
197    pub async fn get_chain_info(&self) -> Result<ChainInfo, String> {
198        let url = format!("{}/", self.base_url);
199        let response = self.client.get(&url).send().await.unwrap();
200        if !response.status().is_success() {
201            let error_msg = response.text().await.unwrap();
202            return Err(format!("api error: {}", error_msg).to_string());
203        }
204        let ledger_info: ChainInfo = response.json().await.unwrap();
205        Ok(ledger_info)
206    }
207
208    /// get block by height
209    pub async fn get_block_by_height(&self, height: u64) -> Result<Block, String> {
210        let url = format!("{}/blocks/by_height/{}", self.base_url, height);
211        let response = self.client.get(&url).send().await.unwrap();
212        if !response.status().is_success() {
213            let error_msg = response.text().await.unwrap();
214            return Err(format!("api error: {}", error_msg).to_string());
215        }
216        let block: Block = response.json().await.unwrap();
217        Ok(block)
218    }
219
220    /// get block by version
221    pub async fn get_block_by_version(&self, version: u64) -> Result<Block, String> {
222        let url = format!("{}/blocks/by_version/{}", self.base_url, version);
223        let response = self.client.get(&url).send().await.unwrap();
224        if !response.status().is_success() {
225            let error_msg = response.text().await.unwrap();
226            return Err(format!("api error: {}", error_msg).to_string());
227        }
228        let block: Block = response.json().await.unwrap();
229        Ok(block)
230    }
231
232    /// get account event vec
233    pub async fn get_account_event_vec(
234        &self,
235        address: &str,
236        event_type: &str,
237        limit: Option<u64>,
238        start: Option<u64>,
239    ) -> Result<Vec<Event>, String> {
240        let limit = limit.unwrap_or(25);
241        let mut url = format!(
242            "{}/accounts/{}/events/{}?limit={}",
243            self.base_url, address, event_type, limit
244        );
245        if let Some(start) = start {
246            url.push_str(&format!("&start={}", start));
247        }
248        let response = self.client.get(&url).send().await.unwrap();
249        if !response.status().is_success() {
250            let error_msg = response.text().await.unwrap();
251            return Err(format!("api error: {}", error_msg).to_string());
252        }
253        let events: Vec<Event> = response.json().await.unwrap();
254        Ok(events)
255    }
256
257    /// get table item
258    pub async fn get_table_item(
259        &self,
260        table_handle: &str,
261        key_type: &str,
262        value_type: &str,
263        key: &Value,
264    ) -> Result<Value, String> {
265        let url = format!("{}/tables/{}/item", self.base_url, table_handle);
266        let request = TableRequest {
267            key_type: key_type.to_string(),
268            value_type: value_type.to_string(),
269            key: key.clone(),
270        };
271        let response = self
272            .client
273            .post(&url)
274            .header("Content-Type", "application/json")
275            .json(&request)
276            .send()
277            .await
278            .unwrap();
279        if !response.status().is_success() {
280            let error_msg = response.text().await.unwrap();
281            return Err(format!("api error: {}", error_msg).to_string());
282        }
283        let value: Value = response.json().await.unwrap();
284        Ok(value)
285    }
286
287    /// view function
288    pub async fn view(&self, view_request: &ViewRequest) -> Result<Vec<Value>, String> {
289        let url = format!("{}/view", self.base_url);
290        let response = self
291            .client
292            .post(&url)
293            .header("Content-Type", "application/json")
294            .json(view_request)
295            .send()
296            .await
297            .unwrap();
298        if !response.status().is_success() {
299            let error_msg = response.text().await.unwrap();
300            return Err(format!("api error: {}", error_msg).to_string());
301        }
302        let result: Vec<Value> = response.json().await.unwrap();
303        Ok(result)
304    }
305
306    /// estimate gas price
307    pub async fn estimate_gas_price(&self) -> Result<u64, String> {
308        let url = format!("{}/estimate_gas_price", self.base_url);
309        let response = self.client.get(&url).send().await.unwrap();
310        if !response.status().is_success() {
311            let error_msg = response.text().await.unwrap();
312            return Err(format!("api error: {}", error_msg).to_string());
313        }
314        let gas_estimation: GasEstimation = response.json().await.unwrap();
315        Ok(gas_estimation.gas_estimate * 2000)
316    }
317    
318    /// get account balance
319    pub async fn get_account_balance(&self, address: &str) -> Result<u64, String> {
320        let resources = self.get_account_resource_vec(address).await.unwrap();
321        for resource in resources {
322            if resource.r#type == "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>" {
323                if let Some(data) = resource.data.as_object() {
324                    if let Some(coin) = data.get("coin") {
325                        if let Some(value) = coin.get("value") {
326                            return if let Some(balance) = value.as_str() {
327                                Ok(balance.parse().unwrap_or(0))
328                            } else if let Some(balance) = value.as_u64() {
329                                Ok(balance)
330                            } else {
331                                Ok(0)
332                            };
333                        }
334                    }
335                }
336            }
337        }
338        Ok(0)
339    }
340    /// get token balance
341    pub async fn get_token_balance(&self, address: &str, token_type: &str) -> Result<u64, String> {
342        let resource_type = format!("0x1::coin::CoinStore<{}>", token_type);
343        if let Some(resource) = self
344            .get_account_resource(address, &resource_type)
345            .await
346            .unwrap()
347        {
348            if let Some(data) = resource.data.as_object() {
349                if let Some(coin) = data.get("coin") {
350                    if let Some(value) = coin.get("value") {
351                        return if let Some(balance) = value.as_str() {
352                            Ok(balance.parse().unwrap_or(0))
353                        } else if let Some(balance) = value.as_u64() {
354                            Ok(balance)
355                        } else {
356                            Ok(0)
357                        };
358                    }
359                }
360            }
361        }
362        Ok(0)
363    }
364    /// waiting transaction
365    pub async fn waiting_transaction(
366        &self,
367        txn_hash: &str,
368        timeout_secs: u64,
369    ) -> Result<Transaction, String> {
370        let start = std::time::Instant::now();
371        let timeout = Duration::from_secs(timeout_secs);
372        while start.elapsed() < timeout {
373            match self.get_transaction_info(txn_hash).await {
374                Ok(txn) => {
375                    // transaction completed
376                    return Ok(txn);
377                }
378                Err(e) => {
379                    // during transaction processing, delay accessing the transaction status again.
380                    tokio::time::sleep(Duration::from_millis(WAITING_TRANSACTION_DELAY_TIME)).await;
381                }
382            }
383        }
384        Err(format!(
385            "Transaction timeout tx:{:?}\ntime:{:?}",
386            txn_hash, timeout_secs
387        )
388        .to_string())
389    }
390    /// determine whether the transaction is successful
391    pub async fn is_transaction_successful(&self, txn_hash: &str) -> Result<bool, String> {
392        match self.get_transaction_info(txn_hash).await {
393            Ok(t) => Ok(t.success),
394            Err(e) => Err(e),
395        }
396    }
397    /// get apt balance by account
398    pub async fn get_apt_balance_by_account(&self, address: &str) -> Result<f64, String> {
399        match self.get_account_balance(address).await {
400            Ok(balance) => Ok(balance as f64 / 100_000_000.0),
401            Err(e) => Err(e),
402        }
403    }
404    /// get account sequence number
405    pub async fn get_account_sequence_number(&self, address: &str) -> Result<u64, String> {
406        match self.get_account_info(address).await {
407            Ok(info) => Ok(info.sequence_number.parse::<u64>().unwrap()),
408            Err(e) => Err(e),
409        }
410    }
411    /// account exists
412    pub async fn account_exists(&self, address: &str) -> Result<bool, String> {
413        match self.get_account_info(address).await {
414            Ok(_) => Ok(true),
415            Err(e) => {
416                if e.to_string().contains("Account not found") {
417                    Ok(false)
418                } else {
419                    Err(e)
420                }
421            }
422        }
423    }
424}