1#![forbid(unsafe_code)]
4
5pub mod config;
6pub mod dto;
7pub mod error;
8#[doc(hidden)]
9pub mod protobuf;
10pub mod provider;
11
12#[cfg(test)]
13mod tests;
14
15pub use crate::error::AElfError;
16
17#[cfg(feature = "native-http")]
18use crate::config::ClientConfig;
19use crate::dto::{
20 BlockDto, CalculateTransactionFeeInput, CalculateTransactionFeeOutput, ChainStatusDto,
21 CreateRawTransactionInput, CreateRawTransactionOutput, ExecuteRawTransactionDto, MerklePathDto,
22 NetworkInfoOutput, PeerDto, SendRawTransactionInput, SendRawTransactionOutput,
23 SendTransactionOutput, TaskQueueInfoDto, TransactionPoolStatusOutput, TransactionResultDto,
24};
25use crate::protobuf::RawBytesMessage;
26#[cfg(feature = "native-http")]
27use crate::provider::HttpProvider;
28use crate::provider::Provider;
29use aelf_crypto::{
30 address_from_public_key, address_to_pb, base58_to_chain_id, decode_address, pb_to_address,
31 sha256_bytes, sign_transaction, Wallet,
32};
33use aelf_proto::aelf::{Address, Hash, Transaction};
34use base64::Engine;
35use http::Method;
36use prost::Message;
37use std::fmt;
38use std::sync::Arc;
39use zeroize::Zeroize;
40
41const API_BASE: &str = "api/blockChain";
42const NET_API_BASE: &str = "api/net";
43const READONLY_PRIVATE_KEY: &str =
44 "0000000000000000000000000000000000000000000000000000000000000001";
45
46fn strip_transaction_id_quotes(value: &str) -> &str {
47 let trimmed = value.trim();
48 trimmed
49 .strip_prefix('"')
50 .and_then(|unquoted| unquoted.strip_suffix('"'))
51 .unwrap_or(trimmed)
52}
53
54fn is_valid_transaction_id(value: &str) -> bool {
55 let candidate = strip_transaction_id_quotes(value);
56 let candidate = candidate
57 .strip_prefix("0x")
58 .or_else(|| candidate.strip_prefix("0X"))
59 .unwrap_or(candidate);
60 candidate.len() == 64 && candidate.chars().all(|char| char.is_ascii_hexdigit())
61}
62
63fn validate_transaction_id(
64 transaction_id: impl Into<String>,
65 raw_response: &str,
66) -> Result<String, AElfError> {
67 let transaction_id = transaction_id.into();
68 let transaction_id = strip_transaction_id_quotes(&transaction_id).to_owned();
69 if transaction_id.is_empty() {
70 return Err(AElfError::UnexpectedResponse(
71 "empty sendTransaction response".to_owned(),
72 ));
73 }
74 if is_valid_transaction_id(&transaction_id) {
75 Ok(transaction_id)
76 } else {
77 Err(AElfError::UnexpectedResponse(format!(
78 "sendTransaction returned a non-transaction id payload: {raw_response}"
79 )))
80 }
81}
82
83#[derive(Clone)]
85pub struct AElfClient {
86 provider: Arc<dyn Provider>,
87}
88
89impl AElfClient {
90 #[cfg(feature = "native-http")]
92 pub fn new(config: ClientConfig) -> Result<Self, AElfError> {
93 Self::with_provider(HttpProvider::new(config)?)
94 }
95
96 pub fn with_provider<P>(provider: P) -> Result<Self, AElfError>
98 where
99 P: Provider + 'static,
100 {
101 Ok(Self {
102 provider: Arc::new(provider),
103 })
104 }
105
106 pub fn block(&self) -> BlockService {
108 BlockService {
109 client: self.clone(),
110 }
111 }
112
113 pub fn chain(&self) -> ChainService {
115 ChainService {
116 client: self.clone(),
117 }
118 }
119
120 pub fn net(&self) -> NetService {
122 NetService {
123 client: self.clone(),
124 }
125 }
126
127 pub fn tx(&self) -> TransactionService {
129 TransactionService {
130 client: self.clone(),
131 }
132 }
133
134 pub fn utils(&self) -> ClientUtilsService {
136 ClientUtilsService {
137 client: self.clone(),
138 }
139 }
140
141 pub fn transaction_builder(&self) -> TransactionBuilder {
143 TransactionBuilder::new(self.clone())
144 }
145
146 async fn get_json<T>(&self, path: &str, query: &[(&str, String)]) -> Result<T, AElfError>
147 where
148 T: serde::de::DeserializeOwned,
149 {
150 let value = self
151 .provider
152 .request_json(Method::GET, path, query, None)
153 .await?;
154 serde_json::from_value(value).map_err(AElfError::Json)
155 }
156
157 async fn post_json<T>(&self, path: &str, body: serde_json::Value) -> Result<T, AElfError>
158 where
159 T: serde::de::DeserializeOwned,
160 {
161 let value = self
162 .provider
163 .request_json(Method::POST, path, &[], Some(body))
164 .await?;
165 serde_json::from_value(value).map_err(AElfError::Json)
166 }
167
168 async fn get_text(&self, path: &str, query: &[(&str, String)]) -> Result<String, AElfError> {
169 self.provider
170 .request_text(Method::GET, path, query, None)
171 .await
172 }
173
174 async fn post_text(&self, path: &str, body: serde_json::Value) -> Result<String, AElfError> {
175 self.provider
176 .request_text(Method::POST, path, &[], Some(body))
177 .await
178 }
179}
180
181#[derive(Clone)]
182pub struct BlockService {
183 client: AElfClient,
184}
185
186impl BlockService {
187 pub async fn get_block_height(&self) -> Result<i64, AElfError> {
188 let text = self
189 .client
190 .get_text(&format!("{API_BASE}/blockHeight"), &[])
191 .await?;
192 text.trim_matches('"')
193 .parse::<i64>()
194 .map_err(|err| AElfError::InvalidConfig(format!("invalid block height: {err}")))
195 }
196
197 pub async fn get_block_by_hash(
198 &self,
199 block_hash: &str,
200 include_transactions: bool,
201 ) -> Result<BlockDto, AElfError> {
202 self.client
203 .get_json(
204 &format!("{API_BASE}/block"),
205 &[
206 ("blockHash", block_hash.to_owned()),
207 ("includeTransactions", include_transactions.to_string()),
208 ],
209 )
210 .await
211 }
212
213 pub async fn get_block_by_height(
214 &self,
215 block_height: i64,
216 include_transactions: bool,
217 ) -> Result<BlockDto, AElfError> {
218 self.client
219 .get_json(
220 &format!("{API_BASE}/blockByHeight"),
221 &[
222 ("blockHeight", block_height.to_string()),
223 ("includeTransactions", include_transactions.to_string()),
224 ],
225 )
226 .await
227 }
228}
229
230#[derive(Clone)]
231pub struct ChainService {
232 client: AElfClient,
233}
234
235impl ChainService {
236 pub async fn get_chain_status(&self) -> Result<ChainStatusDto, AElfError> {
237 self.client
238 .get_json(&format!("{API_BASE}/chainStatus"), &[])
239 .await
240 }
241
242 pub async fn get_contract_file_descriptor_set(
243 &self,
244 address: &str,
245 ) -> Result<Vec<u8>, AElfError> {
246 let text = self
247 .client
248 .get_text(
249 &format!("{API_BASE}/contractFileDescriptorSet"),
250 &[("address", address.to_owned())],
251 )
252 .await?;
253 Ok(base64::engine::general_purpose::STANDARD.decode(text.trim_matches('"'))?)
254 }
255
256 pub async fn get_task_queue_status(&self) -> Result<Vec<TaskQueueInfoDto>, AElfError> {
257 self.client
258 .get_json(&format!("{API_BASE}/taskQueueStatus"), &[])
259 .await
260 }
261
262 pub async fn get_chain_id(&self) -> Result<i32, AElfError> {
263 let status = self.get_chain_status().await?;
264 base58_to_chain_id(&status.chain_id).map_err(AElfError::Crypto)
265 }
266}
267
268#[derive(Clone)]
269pub struct NetService {
270 client: AElfClient,
271}
272
273impl NetService {
274 pub async fn add_peer(&self, address: &str) -> Result<bool, AElfError> {
275 self.client
276 .post_json(
277 &format!("{NET_API_BASE}/peer"),
278 serde_json::json!({ "Address": address }),
279 )
280 .await
281 }
282
283 pub async fn remove_peer(&self, address: &str) -> Result<bool, AElfError> {
284 let text = self
285 .client
286 .provider
287 .request_text(
288 Method::DELETE,
289 &format!("{NET_API_BASE}/peer"),
290 &[("address", address.to_owned())],
291 None,
292 )
293 .await?;
294 serde_json::from_str(&text).or_else(|_| Ok(text.trim().eq_ignore_ascii_case("true")))
295 }
296
297 pub async fn get_peers(&self, with_metrics: bool) -> Result<Vec<PeerDto>, AElfError> {
298 self.client
299 .get_json(
300 &format!("{NET_API_BASE}/peers"),
301 &[("withMetrics", with_metrics.to_string())],
302 )
303 .await
304 }
305
306 pub async fn get_network_info(&self) -> Result<NetworkInfoOutput, AElfError> {
307 self.client
308 .get_json(&format!("{NET_API_BASE}/networkInfo"), &[])
309 .await
310 }
311}
312
313#[derive(Clone)]
314pub struct TransactionService {
315 client: AElfClient,
316}
317
318impl TransactionService {
319 pub async fn get_transaction_pool_status(
320 &self,
321 ) -> Result<TransactionPoolStatusOutput, AElfError> {
322 self.client
323 .get_json(&format!("{API_BASE}/transactionPoolStatus"), &[])
324 .await
325 }
326
327 pub async fn execute_transaction(&self, raw_transaction: &str) -> Result<String, AElfError> {
328 self.client
329 .post_text(
330 &format!("{API_BASE}/executeTransaction"),
331 serde_json::json!({ "RawTransaction": raw_transaction }),
332 )
333 .await
334 }
335
336 pub async fn execute_raw_transaction(
337 &self,
338 input: &ExecuteRawTransactionDto,
339 ) -> Result<String, AElfError> {
340 self.client
341 .post_text(
342 &format!("{API_BASE}/executeRawTransaction"),
343 serde_json::json!({
344 "RawTransaction": input.raw_transaction,
345 "Signature": input.signature,
346 }),
347 )
348 .await
349 }
350
351 pub async fn create_raw_transaction(
352 &self,
353 input: &CreateRawTransactionInput,
354 ) -> Result<CreateRawTransactionOutput, AElfError> {
355 self.client
356 .post_json(
357 &format!("{API_BASE}/rawTransaction"),
358 serde_json::to_value(input)?,
359 )
360 .await
361 }
362
363 pub async fn send_raw_transaction(
364 &self,
365 input: &SendRawTransactionInput,
366 ) -> Result<SendRawTransactionOutput, AElfError> {
367 self.client
368 .post_json(
369 &format!("{API_BASE}/sendRawTransaction"),
370 serde_json::to_value(input)?,
371 )
372 .await
373 }
374
375 pub async fn send_transaction(
376 &self,
377 raw_transaction: &str,
378 ) -> Result<SendTransactionOutput, AElfError> {
379 let text = self
380 .client
381 .post_text(
382 &format!("{API_BASE}/sendTransaction"),
383 serde_json::json!({ "RawTransaction": raw_transaction }),
384 )
385 .await?;
386 if let Ok(output) = serde_json::from_str::<SendTransactionOutput>(&text) {
387 return Ok(SendTransactionOutput {
388 transaction_id: validate_transaction_id(output.transaction_id, &text)?,
389 });
390 }
391
392 if let Ok(transaction_id) = serde_json::from_str::<String>(&text) {
393 return Ok(SendTransactionOutput {
394 transaction_id: validate_transaction_id(transaction_id, &text)?,
395 });
396 }
397
398 Ok(SendTransactionOutput {
399 transaction_id: validate_transaction_id(&text, &text)?,
400 })
401 }
402
403 pub async fn send_transactions(
404 &self,
405 raw_transactions: &str,
406 ) -> Result<Vec<String>, AElfError> {
407 self.client
408 .post_json(
409 &format!("{API_BASE}/sendTransactions"),
410 serde_json::json!({ "RawTransactions": raw_transactions }),
411 )
412 .await
413 }
414
415 pub async fn get_transaction_result(
416 &self,
417 transaction_id: &str,
418 ) -> Result<TransactionResultDto, AElfError> {
419 self.client
420 .get_json(
421 &format!("{API_BASE}/transactionResult"),
422 &[("transactionId", transaction_id.to_owned())],
423 )
424 .await
425 }
426
427 pub async fn get_transaction_results(
428 &self,
429 block_hash: &str,
430 offset: i64,
431 limit: i64,
432 ) -> Result<Vec<TransactionResultDto>, AElfError> {
433 self.client
434 .get_json(
435 &format!("{API_BASE}/transactionResults"),
436 &[
437 ("blockHash", block_hash.to_owned()),
438 ("offset", offset.to_string()),
439 ("limit", limit.to_string()),
440 ],
441 )
442 .await
443 }
444
445 pub async fn get_merkle_path_by_transaction_id(
446 &self,
447 transaction_id: &str,
448 ) -> Result<MerklePathDto, AElfError> {
449 self.client
450 .get_json(
451 &format!("{API_BASE}/merklePathByTransactionId"),
452 &[("transactionId", transaction_id.to_owned())],
453 )
454 .await
455 }
456
457 pub async fn calculate_transaction_fee(
458 &self,
459 input: &CalculateTransactionFeeInput,
460 ) -> Result<CalculateTransactionFeeOutput, AElfError> {
461 self.client
462 .post_json(
463 &format!("{API_BASE}/calculateTransactionFee"),
464 serde_json::to_value(input)?,
465 )
466 .await
467 }
468}
469
470#[derive(Clone)]
471pub struct ClientUtilsService {
472 client: AElfClient,
473}
474
475impl ClientUtilsService {
476 pub async fn is_connected(&self) -> bool {
477 self.client.chain().get_chain_status().await.is_ok()
478 }
479
480 pub fn get_address_from_pub_key(&self, public_key_hex: &str) -> Result<String, AElfError> {
481 let public_key = hex::decode(public_key_hex)?;
482 Ok(address_from_public_key(&public_key))
483 }
484
485 pub fn get_address_from_private_key(&self, private_key_hex: &str) -> Result<String, AElfError> {
486 let wallet = Wallet::from_private_key(private_key_hex)?;
487 Ok(wallet.address().to_owned())
488 }
489
490 pub fn generate_key_pair_info(&self) -> Result<KeyPairInfo, AElfError> {
491 let wallet = Wallet::create()?;
492 Ok(KeyPairInfo::from_wallet(&wallet))
493 }
494
495 pub async fn get_genesis_contract_address(&self) -> Result<String, AElfError> {
496 let status = self.client.chain().get_chain_status().await?;
497 Ok(status.genesis_contract_address)
498 }
499
500 pub async fn get_contract_address_by_name(
501 &self,
502 contract_name: &str,
503 ) -> Result<String, AElfError> {
504 let readonly_wallet = Wallet::from_private_key(READONLY_PRIVATE_KEY)?;
505 let genesis_address = self.get_genesis_contract_address().await?;
506 let request = Hash {
507 value: sha256_bytes(contract_name.as_bytes()).to_vec(),
508 };
509 let transaction = self
510 .generate_transaction(
511 readonly_wallet.address(),
512 &genesis_address,
513 "GetContractAddressByName",
514 &request,
515 )
516 .await?;
517 let signed = self.sign_transaction(&readonly_wallet, transaction)?;
518 let raw = hex::encode(signed.encode_to_vec());
519 let response = self.client.tx().execute_transaction(&raw).await?;
520 let bytes = hex::decode(response.trim_matches('"'))?;
521 let address = Address::decode(bytes.as_slice())?;
522 Ok(pb_to_address(&address))
523 }
524
525 pub async fn get_formatted_address(&self, address: &str) -> Result<String, AElfError> {
526 let readonly_wallet = Wallet::from_private_key(READONLY_PRIVATE_KEY)?;
527 let token_address = self
528 .get_contract_address_by_name("AElf.ContractNames.Token")
529 .await?;
530 let transaction = self
531 .generate_transaction(
532 readonly_wallet.address(),
533 &token_address,
534 "GetPrimaryTokenSymbol",
535 &pbjson_types::Empty {},
536 )
537 .await?;
538 let signed = self.sign_transaction(&readonly_wallet, transaction)?;
539 let raw = hex::encode(signed.encode_to_vec());
540 let response = self.client.tx().execute_transaction(&raw).await?;
541 let bytes = hex::decode(response.trim_matches('"'))?;
542 let symbol = pbjson_types::StringValue::decode(bytes.as_slice())?;
543 let status = self.client.chain().get_chain_status().await?;
544 Ok(format!("{}_{}_{}", symbol.value, address, status.chain_id))
545 }
546
547 pub async fn generate_transaction<M>(
548 &self,
549 from: &str,
550 to: &str,
551 method_name: &str,
552 input: &M,
553 ) -> Result<Transaction, AElfError>
554 where
555 M: Message,
556 {
557 let chain_status = self.client.chain().get_chain_status().await?;
558 let best_chain_hash = chain_status.best_chain_hash.trim_start_matches("0x");
559 let hash_bytes = hex::decode(best_chain_hash)?;
560 let prefix = hash_bytes
561 .get(..4)
562 .ok_or_else(|| AElfError::request("best chain hash is too short", None))?;
563 Ok(Transaction {
564 from: Some(address_to_pb(from)?),
565 to: Some(address_to_pb(to)?),
566 ref_block_number: chain_status.best_chain_height,
567 ref_block_prefix: prefix.to_vec(),
568 method_name: method_name.to_owned(),
569 params: input.encode_to_vec(),
570 signature: Vec::new(),
571 })
572 }
573
574 pub fn sign_transaction(
575 &self,
576 wallet: &Wallet,
577 mut transaction: Transaction,
578 ) -> Result<Transaction, AElfError> {
579 transaction.signature = sign_transaction(wallet, &transaction)?;
580 Ok(transaction)
581 }
582
583 pub fn decode_address(&self, address: &str) -> Result<Vec<u8>, AElfError> {
584 decode_address(address).map_err(AElfError::Crypto)
585 }
586}
587
588#[derive(Clone, PartialEq, Eq)]
592pub struct KeyPairInfo {
593 pub private_key: String,
594 pub public_key: String,
595 pub address: String,
596}
597
598impl KeyPairInfo {
599 pub fn from_wallet(wallet: &Wallet) -> Self {
601 Self {
602 private_key: wallet.private_key().to_owned(),
603 public_key: wallet.public_key().to_owned(),
604 address: wallet.address().to_owned(),
605 }
606 }
607}
608
609impl fmt::Debug for KeyPairInfo {
610 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611 f.debug_struct("KeyPairInfo")
612 .field("private_key", &"<redacted>")
613 .field("public_key", &self.public_key)
614 .field("address", &self.address)
615 .finish()
616 }
617}
618
619impl Drop for KeyPairInfo {
620 fn drop(&mut self) {
621 self.private_key.zeroize();
622 }
623}
624
625#[derive(Clone)]
627pub struct TransactionBuilder {
628 client: AElfClient,
629 wallet: Option<Wallet>,
630 contract_address: Option<String>,
631 system_contract_name: Option<String>,
632 method_name: Option<String>,
633 params: Option<Vec<u8>>,
634}
635
636impl TransactionBuilder {
637 pub fn new(client: AElfClient) -> Self {
639 Self {
640 client,
641 wallet: None,
642 contract_address: None,
643 system_contract_name: None,
644 method_name: None,
645 params: None,
646 }
647 }
648
649 pub fn with_wallet(mut self, wallet: Wallet) -> Self {
651 self.wallet = Some(wallet);
652 self
653 }
654
655 pub fn with_contract(mut self, address: impl Into<String>) -> Self {
657 self.contract_address = Some(address.into());
658 self
659 }
660
661 pub fn with_system_contract(mut self, name: impl Into<String>) -> Self {
663 self.system_contract_name = Some(name.into());
664 self
665 }
666
667 pub fn with_method(mut self, method_name: impl Into<String>) -> Self {
669 self.method_name = Some(method_name.into());
670 self
671 }
672
673 pub fn with_message<M: Message>(mut self, message: &M) -> Self {
675 self.params = Some(message.encode_to_vec());
676 self
677 }
678
679 pub async fn build_unsigned(self) -> Result<Transaction, AElfError> {
681 let wallet = self.wallet.ok_or(AElfError::MissingField("wallet"))?;
682 let contract_address = match (self.contract_address, self.system_contract_name) {
683 (Some(address), _) => address,
684 (None, Some(name)) => {
685 self.client
686 .utils()
687 .get_contract_address_by_name(&name)
688 .await?
689 }
690 (None, None) => return Err(AElfError::MissingField("contract address")),
691 };
692 let method_name = self
693 .method_name
694 .ok_or(AElfError::MissingField("method name"))?;
695 let params = self.params.unwrap_or_default();
696 self.client
697 .utils()
698 .generate_transaction(
699 wallet.address(),
700 &contract_address,
701 &method_name,
702 &RawBytesMessage::new(params),
703 )
704 .await
705 }
706
707 pub async fn build_signed(self) -> Result<Transaction, AElfError> {
709 let wallet = self
710 .wallet
711 .clone()
712 .ok_or(AElfError::MissingField("wallet"))?;
713 let unsigned = self.clone().build_unsigned().await?;
714 self.client.utils().sign_transaction(&wallet, unsigned)
715 }
716}