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
12const 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
17const WAITING_TRANSACTION_DELAY_TIME: u64 = 500;
19
20#[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 return Ok(txn);
385 }
386 Err(e) => {
387 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 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 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 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 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}