aptos_network_sdk/
lib.rs

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