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