aptos_network_sdk/
lib.rs

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