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