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
27const WAITING_TRANSACTION_DELAY_TIME: u64 = 500;
29
30#[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 return Ok(txn);
410 }
411 Err(e) => {
412 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 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 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 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 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}