aptos_network_sdk/
lib.rs

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