1use crate::account::Account;
6use crate::api::{AptosResponse, FullnodeClient, PendingTransaction};
7use crate::config::AptosConfig;
8use crate::error::{AptosError, AptosResult};
9use crate::transaction::{
10 FeePayerRawTransaction, MultiAgentRawTransaction, RawTransaction, SignedTransaction,
11 SimulateQueryOptions, SimulationResult, TransactionBuilder, TransactionPayload,
12 build_simulation_signed_fee_payer, build_simulation_signed_multi_agent,
13};
14use crate::types::{AccountAddress, ChainId};
15use std::sync::Arc;
16use std::sync::atomic::{AtomicU8, Ordering};
17use std::time::Duration;
18
19#[cfg(feature = "ed25519")]
20use crate::transaction::EntryFunction;
21#[cfg(feature = "ed25519")]
22use crate::types::TypeTag;
23
24#[cfg(feature = "faucet")]
25use crate::api::FaucetClient;
26#[cfg(feature = "faucet")]
27use crate::types::HashValue;
28
29#[cfg(feature = "indexer")]
30use crate::api::IndexerClient;
31
32#[derive(Debug)]
55pub struct Aptos {
56 config: AptosConfig,
57 fullnode: Arc<FullnodeClient>,
58 chain_id: AtomicU8,
62 #[cfg(feature = "faucet")]
63 faucet: Option<FaucetClient>,
64 #[cfg(feature = "indexer")]
65 indexer: Option<IndexerClient>,
66}
67
68impl Aptos {
69 pub fn new(config: AptosConfig) -> AptosResult<Self> {
75 let fullnode = Arc::new(FullnodeClient::new(config.clone())?);
76
77 #[cfg(feature = "faucet")]
78 let faucet = FaucetClient::new(&config).ok();
79
80 #[cfg(feature = "indexer")]
81 let indexer = IndexerClient::new(&config).ok();
82
83 let chain_id = AtomicU8::new(config.chain_id().id());
84
85 Ok(Self {
86 config,
87 fullnode,
88 chain_id,
89 #[cfg(feature = "faucet")]
90 faucet,
91 #[cfg(feature = "indexer")]
92 indexer,
93 })
94 }
95
96 pub fn testnet() -> AptosResult<Self> {
102 Self::new(AptosConfig::testnet())
103 }
104
105 pub fn devnet() -> AptosResult<Self> {
111 Self::new(AptosConfig::devnet())
112 }
113
114 pub fn mainnet() -> AptosResult<Self> {
120 Self::new(AptosConfig::mainnet())
121 }
122
123 pub fn local() -> AptosResult<Self> {
129 Self::new(AptosConfig::local())
130 }
131
132 pub fn config(&self) -> &AptosConfig {
134 &self.config
135 }
136
137 pub fn fullnode(&self) -> &FullnodeClient {
139 &self.fullnode
140 }
141
142 #[cfg(feature = "faucet")]
144 pub fn faucet(&self) -> Option<&FaucetClient> {
145 self.faucet.as_ref()
146 }
147
148 #[cfg(feature = "indexer")]
150 pub fn indexer(&self) -> Option<&IndexerClient> {
151 self.indexer.as_ref()
152 }
153
154 pub fn ans(&self) -> crate::api::AnsClient {
161 crate::api::AnsClient::new((*self.fullnode).clone())
162 }
163
164 pub async fn ledger_info(&self) -> AptosResult<crate::api::response::LedgerInfo> {
176 let response = self.fullnode.get_ledger_info().await?;
177 let info = response.into_inner();
178
179 if self.chain_id.load(Ordering::Relaxed) == 0 && info.chain_id > 0 {
185 self.chain_id.store(info.chain_id, Ordering::Relaxed);
186 }
187
188 Ok(info)
189 }
190
191 pub fn chain_id(&self) -> ChainId {
203 ChainId::new(self.chain_id.load(Ordering::Relaxed))
204 }
205
206 pub async fn ensure_chain_id(&self) -> AptosResult<ChainId> {
223 let id = self.chain_id.load(Ordering::Relaxed);
224 if id > 0 {
225 return Ok(ChainId::new(id));
226 }
227 let response = self.fullnode.get_ledger_info().await?;
229 let info = response.into_inner();
230 self.chain_id.store(info.chain_id, Ordering::Relaxed);
231 Ok(ChainId::new(info.chain_id))
232 }
233
234 pub async fn get_sequence_number(&self, address: AccountAddress) -> AptosResult<u64> {
243 self.fullnode.get_sequence_number(address).await
244 }
245
246 pub async fn get_balance(&self, address: AccountAddress) -> AptosResult<u64> {
253 self.fullnode.get_account_balance(address).await
254 }
255
256 pub async fn account_exists(&self, address: AccountAddress) -> AptosResult<bool> {
263 match self.fullnode.get_account(address).await {
264 Ok(_) => Ok(true),
265 Err(AptosError::Api {
266 status_code: 404, ..
267 }) => Ok(false),
268 Err(e) => Err(e),
269 }
270 }
271
272 pub async fn build_transaction<A: Account>(
284 &self,
285 sender: &A,
286 payload: TransactionPayload,
287 ) -> AptosResult<RawTransaction> {
288 let (sequence_number, gas_estimation, chain_id) = tokio::join!(
290 self.get_sequence_number(sender.address()),
291 self.fullnode.estimate_gas_price(),
292 self.ensure_chain_id()
293 );
294 let sequence_number = sequence_number?;
295 let gas_estimation = gas_estimation?;
296 let chain_id = chain_id?;
297
298 TransactionBuilder::new()
299 .sender(sender.address())
300 .sequence_number(sequence_number)
301 .payload(payload)
302 .gas_unit_price(gas_estimation.data.recommended())
303 .chain_id(chain_id)
304 .expiration_from_now(600)
305 .build()
306 }
307
308 #[cfg(feature = "ed25519")]
316 pub async fn sign_and_submit<A: Account>(
317 &self,
318 account: &A,
319 payload: TransactionPayload,
320 ) -> AptosResult<AptosResponse<PendingTransaction>> {
321 let raw_txn = self.build_transaction(account, payload).await?;
322 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
323 self.fullnode.submit_transaction(&signed).await
324 }
325
326 #[cfg(feature = "ed25519")]
334 pub async fn sign_submit_and_wait<A: Account>(
335 &self,
336 account: &A,
337 payload: TransactionPayload,
338 timeout: Option<Duration>,
339 ) -> AptosResult<AptosResponse<serde_json::Value>> {
340 let raw_txn = self.build_transaction(account, payload).await?;
341 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
342 self.fullnode.submit_and_wait(&signed, timeout).await
343 }
344
345 pub async fn build_orderless_transaction<A: Account>(
366 &self,
367 sender: &A,
368 payload: TransactionPayload,
369 nonce: Option<u64>,
370 ) -> AptosResult<RawTransaction> {
371 let (gas_estimation, chain_id) =
374 tokio::join!(self.fullnode.estimate_gas_price(), self.ensure_chain_id());
375 let gas_estimation = gas_estimation?;
376 let chain_id = chain_id?;
377
378 let nonce = nonce.unwrap_or_else(|| rand::RngCore::next_u64(&mut rand::rngs::OsRng));
379 let orderless_payload = payload.into_orderless(nonce)?;
380
381 TransactionBuilder::new()
382 .sender(sender.address())
383 .sequence_number(u64::MAX)
384 .payload(orderless_payload)
385 .gas_unit_price(gas_estimation.data.recommended())
386 .chain_id(chain_id)
387 .expiration_from_now(60)
388 .build()
389 }
390
391 #[cfg(feature = "ed25519")]
402 pub async fn sign_and_submit_orderless<A: Account>(
403 &self,
404 account: &A,
405 payload: TransactionPayload,
406 nonce: Option<u64>,
407 ) -> AptosResult<AptosResponse<PendingTransaction>> {
408 let raw_txn = self
409 .build_orderless_transaction(account, payload, nonce)
410 .await?;
411 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
412 self.fullnode.submit_transaction(&signed).await
413 }
414
415 #[cfg(feature = "ed25519")]
426 pub async fn sign_submit_and_wait_orderless<A: Account>(
427 &self,
428 account: &A,
429 payload: TransactionPayload,
430 nonce: Option<u64>,
431 timeout: Option<Duration>,
432 ) -> AptosResult<AptosResponse<serde_json::Value>> {
433 let raw_txn = self
434 .build_orderless_transaction(account, payload, nonce)
435 .await?;
436 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
437 self.fullnode.submit_and_wait(&signed, timeout).await
438 }
439
440 pub async fn submit_transaction(
447 &self,
448 signed_txn: &SignedTransaction,
449 ) -> AptosResult<AptosResponse<PendingTransaction>> {
450 self.fullnode.submit_transaction(signed_txn).await
451 }
452
453 pub async fn submit_and_wait(
461 &self,
462 signed_txn: &SignedTransaction,
463 timeout: Option<Duration>,
464 ) -> AptosResult<AptosResponse<serde_json::Value>> {
465 self.fullnode.submit_and_wait(signed_txn, timeout).await
466 }
467
468 pub async fn simulate_transaction(
478 &self,
479 signed_txn: &SignedTransaction,
480 ) -> AptosResult<AptosResponse<Vec<serde_json::Value>>> {
481 self.fullnode.simulate_transaction(signed_txn).await
482 }
483
484 #[cfg(feature = "ed25519")]
505 pub async fn simulate<A: Account>(
506 &self,
507 account: &A,
508 payload: TransactionPayload,
509 ) -> AptosResult<crate::transaction::SimulationResult> {
510 use crate::transaction::SignedTransaction;
511
512 let raw_txn = self.build_transaction(account, payload).await?;
513
514 let auth = build_zero_signed_authenticator(account)?;
522 let signed = SignedTransaction::new(raw_txn, auth);
523
524 let response = self.fullnode.simulate_transaction(&signed).await?;
525 crate::transaction::SimulationResult::from_response(response.into_inner())
526 }
527
528 pub async fn simulate_signed(
542 &self,
543 signed_txn: &SignedTransaction,
544 ) -> AptosResult<SimulationResult> {
545 let response = self.fullnode.simulate_transaction(signed_txn).await?;
546 SimulationResult::from_response(response.into_inner())
547 }
548
549 pub async fn simulate_signed_with_options(
562 &self,
563 signed_txn: &SignedTransaction,
564 options: SimulateQueryOptions,
565 ) -> AptosResult<SimulationResult> {
566 let response = self
567 .fullnode
568 .simulate_transaction_with_options(signed_txn, Some(options))
569 .await?;
570 SimulationResult::from_response(response.into_inner())
571 }
572
573 pub async fn simulate_multi_agent(
594 &self,
595 multi_agent: &MultiAgentRawTransaction,
596 options: impl Into<Option<SimulateQueryOptions>>,
597 ) -> AptosResult<SimulationResult> {
598 let signed = build_simulation_signed_multi_agent(multi_agent);
599 match options.into() {
600 None => self.simulate_signed(&signed).await,
601 Some(opts) => self.simulate_signed_with_options(&signed, opts).await,
602 }
603 }
604
605 pub async fn simulate_fee_payer(
626 &self,
627 fee_payer_txn: &FeePayerRawTransaction,
628 options: impl Into<Option<SimulateQueryOptions>>,
629 ) -> AptosResult<SimulationResult> {
630 let signed = build_simulation_signed_fee_payer(fee_payer_txn);
631 match options.into() {
632 None => self.simulate_signed(&signed).await,
633 Some(opts) => self.simulate_signed_with_options(&signed, opts).await,
634 }
635 }
636
637 #[cfg(feature = "ed25519")]
653 pub async fn estimate_gas<A: Account>(
654 &self,
655 account: &A,
656 payload: TransactionPayload,
657 ) -> AptosResult<u64> {
658 let result = self.simulate(account, payload).await?;
659 if result.success() {
660 Ok(result.safe_gas_estimate())
661 } else {
662 Err(AptosError::SimulationFailed(
663 result
664 .error_message()
665 .unwrap_or_else(|| result.vm_status().to_string()),
666 ))
667 }
668 }
669
670 #[cfg(feature = "ed25519")]
688 pub async fn simulate_and_submit<A: Account>(
689 &self,
690 account: &A,
691 payload: TransactionPayload,
692 ) -> AptosResult<AptosResponse<PendingTransaction>> {
693 let raw_txn = self.build_transaction(account, payload).await?;
696 let sim_auth = build_zero_signed_authenticator(account)?;
697 let sim_signed = SignedTransaction::new(raw_txn.clone(), sim_auth);
698 let sim_response = self.fullnode.simulate_transaction(&sim_signed).await?;
699 let sim_result =
700 crate::transaction::SimulationResult::from_response(sim_response.into_inner())?;
701
702 if sim_result.failed() {
703 return Err(AptosError::SimulationFailed(
704 sim_result
705 .error_message()
706 .unwrap_or_else(|| sim_result.vm_status().to_string()),
707 ));
708 }
709
710 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
711 self.fullnode.submit_transaction(&signed).await
712 }
713
714 #[cfg(feature = "ed25519")]
725 pub async fn simulate_submit_and_wait<A: Account>(
726 &self,
727 account: &A,
728 payload: TransactionPayload,
729 timeout: Option<Duration>,
730 ) -> AptosResult<AptosResponse<serde_json::Value>> {
731 let raw_txn = self.build_transaction(account, payload).await?;
734 let sim_auth = build_zero_signed_authenticator(account)?;
735 let sim_signed = SignedTransaction::new(raw_txn.clone(), sim_auth);
736 let sim_response = self.fullnode.simulate_transaction(&sim_signed).await?;
737 let sim_result =
738 crate::transaction::SimulationResult::from_response(sim_response.into_inner())?;
739
740 if sim_result.failed() {
741 return Err(AptosError::SimulationFailed(
742 sim_result
743 .error_message()
744 .unwrap_or_else(|| sim_result.vm_status().to_string()),
745 ));
746 }
747
748 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
749 self.fullnode.submit_and_wait(&signed, timeout).await
750 }
751
752 #[cfg(feature = "ed25519")]
762 pub async fn transfer_apt<A: Account>(
763 &self,
764 sender: &A,
765 recipient: AccountAddress,
766 amount: u64,
767 ) -> AptosResult<AptosResponse<serde_json::Value>> {
768 let payload = EntryFunction::apt_transfer(recipient, amount)?;
769 self.sign_submit_and_wait(sender, payload.into(), None)
770 .await
771 }
772
773 #[cfg(feature = "ed25519")]
781 pub async fn transfer_coin<A: Account>(
782 &self,
783 sender: &A,
784 recipient: AccountAddress,
785 coin_type: TypeTag,
786 amount: u64,
787 ) -> AptosResult<AptosResponse<serde_json::Value>> {
788 let payload = EntryFunction::coin_transfer(coin_type, recipient, amount)?;
789 self.sign_submit_and_wait(sender, payload.into(), None)
790 .await
791 }
792
793 #[cfg(feature = "ed25519")]
808 pub async fn transfer_fungible_asset<A: Account>(
809 &self,
810 sender: &A,
811 metadata: AccountAddress,
812 recipient: AccountAddress,
813 amount: u64,
814 ) -> AptosResult<AptosResponse<serde_json::Value>> {
815 let payload = crate::transaction::InputEntryFunctionData::transfer_fungible_asset(
816 metadata, recipient, amount,
817 )?;
818 self.sign_submit_and_wait(sender, payload, None).await
819 }
820
821 #[cfg(feature = "ed25519")]
831 pub async fn transfer_object<A: Account>(
832 &self,
833 owner: &A,
834 object: AccountAddress,
835 to: AccountAddress,
836 ) -> AptosResult<AptosResponse<serde_json::Value>> {
837 let payload = crate::transaction::InputEntryFunctionData::transfer_object(object, to)?;
838 self.sign_submit_and_wait(owner, payload, None).await
839 }
840
841 #[cfg(feature = "ed25519")]
850 pub async fn transfer_digital_asset<A: Account>(
851 &self,
852 owner: &A,
853 token: AccountAddress,
854 to: AccountAddress,
855 ) -> AptosResult<AptosResponse<serde_json::Value>> {
856 let payload =
857 crate::transaction::InputEntryFunctionData::transfer_digital_asset(token, to)?;
858 self.sign_submit_and_wait(owner, payload, None).await
859 }
860
861 #[cfg(feature = "ed25519")]
881 pub async fn rotate_auth_key<A: Account, B: Account>(
882 &self,
883 current: &A,
884 new_account: &B,
885 timeout: Option<Duration>,
886 ) -> AptosResult<AptosResponse<serde_json::Value>> {
887 let sequence_number = self.get_sequence_number(current.address()).await?;
890 let payload =
891 crate::account::build_rotate_auth_key_payload(current, new_account, sequence_number)?;
892
893 let (gas_estimation, chain_id) =
894 tokio::join!(self.fullnode.estimate_gas_price(), self.ensure_chain_id());
895 let gas_estimation = gas_estimation?;
896 let chain_id = chain_id?;
897
898 let raw_txn = TransactionBuilder::new()
899 .sender(current.address())
900 .sequence_number(sequence_number)
901 .payload(payload)
902 .gas_unit_price(gas_estimation.data.recommended())
903 .chain_id(chain_id)
904 .expiration_from_now(600)
905 .build()?;
906 let signed = crate::transaction::builder::sign_transaction(&raw_txn, current)?;
907 self.fullnode.submit_and_wait(&signed, timeout).await
908 }
909
910 pub async fn get_table_item(
924 &self,
925 handle: &str,
926 key_type: &str,
927 value_type: &str,
928 key: serde_json::Value,
929 ) -> AptosResult<serde_json::Value> {
930 let response = self
931 .fullnode
932 .get_table_item(handle, key_type, value_type, key)
933 .await?;
934 Ok(response.into_inner())
935 }
936
937 pub async fn view(
948 &self,
949 function: &str,
950 type_args: Vec<String>,
951 args: Vec<serde_json::Value>,
952 ) -> AptosResult<Vec<serde_json::Value>> {
953 let response = self.fullnode.view(function, type_args, args).await?;
954 Ok(response.into_inner())
955 }
956
957 pub async fn view_bcs<T: serde::de::DeserializeOwned>(
997 &self,
998 function: &str,
999 type_args: Vec<String>,
1000 args: Vec<Vec<u8>>,
1001 ) -> AptosResult<T> {
1002 let response = self.fullnode.view_bcs(function, type_args, args).await?;
1003 let bytes = response.into_inner();
1004 aptos_bcs::from_bytes(&bytes).map_err(|e| AptosError::Bcs(e.to_string()))
1005 }
1006
1007 pub async fn view_bcs_raw(
1016 &self,
1017 function: &str,
1018 type_args: Vec<String>,
1019 args: Vec<Vec<u8>>,
1020 ) -> AptosResult<Vec<u8>> {
1021 let response = self.fullnode.view_bcs(function, type_args, args).await?;
1022 Ok(response.into_inner())
1023 }
1024
1025 #[cfg(feature = "faucet")]
1045 pub async fn fund_account(
1046 &self,
1047 address: AccountAddress,
1048 amount: u64,
1049 ) -> AptosResult<Vec<String>> {
1050 const MAX_FAUCET_ATTEMPTS: u32 = 16;
1054
1055 let faucet = self
1056 .faucet
1057 .as_ref()
1058 .ok_or_else(|| AptosError::FeatureNotEnabled("faucet".into()))?;
1059
1060 let starting_balance = self.get_balance(address).await.unwrap_or(0);
1062 let target_balance = starting_balance.saturating_add(amount);
1063
1064 let mut all_hashes: Vec<String> = Vec::new();
1065 let mut current_balance = starting_balance;
1066 let mut attempts = 0u32;
1067
1068 while current_balance < target_balance && attempts < MAX_FAUCET_ATTEMPTS {
1069 attempts += 1;
1070 let still_needed = target_balance.saturating_sub(current_balance);
1071 let txn_hashes = faucet.fund(address, still_needed).await?;
1072
1073 let hashes: Vec<HashValue> = txn_hashes
1075 .iter()
1076 .filter_map(|hash_str| {
1077 let hash_str_clean = hash_str.strip_prefix("0x").unwrap_or(hash_str);
1078 HashValue::from_hex(hash_str_clean).ok()
1079 })
1080 .collect();
1081
1082 let wait_futures: Vec<_> = hashes
1084 .iter()
1085 .map(|hash| {
1086 self.fullnode
1087 .wait_for_transaction(hash, Some(Duration::from_mins(1)))
1088 })
1089 .collect();
1090 let results = futures::future::join_all(wait_futures).await;
1091 for result in results {
1092 result?;
1093 }
1094
1095 all_hashes.extend(txn_hashes);
1096
1097 let new_balance = self.get_balance(address).await.unwrap_or(current_balance);
1099 if new_balance <= current_balance {
1100 return Err(AptosError::api(
1101 400,
1102 format!(
1103 "faucet returned successful response but balance did not increase (\
1104 attempts={attempts}, balance={new_balance}, requested top-up={amount})"
1105 ),
1106 ));
1107 }
1108 current_balance = new_balance;
1109 }
1110
1111 if current_balance < target_balance {
1112 return Err(AptosError::api(
1113 429,
1114 format!(
1115 "faucet could not deliver {amount} octas in {attempts} attempts \
1116 (starting balance={starting_balance}, current balance={current_balance})"
1117 ),
1118 ));
1119 }
1120
1121 Ok(all_hashes)
1122 }
1123
1124 #[cfg(all(feature = "faucet", feature = "ed25519"))]
1125 pub async fn create_funded_account(
1131 &self,
1132 amount: u64,
1133 ) -> AptosResult<crate::account::Ed25519Account> {
1134 let account = crate::account::Ed25519Account::generate();
1135 self.fund_account(account.address(), amount).await?;
1136 Ok(account)
1137 }
1138
1139 pub fn batch(&self) -> crate::transaction::BatchOperations<'_> {
1158 crate::transaction::BatchOperations::new(&self.fullnode, &self.chain_id)
1159 }
1160
1161 #[cfg(feature = "ed25519")]
1180 pub async fn submit_batch<A: Account>(
1181 &self,
1182 account: &A,
1183 payloads: Vec<TransactionPayload>,
1184 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
1185 self.batch().submit(account, payloads).await
1186 }
1187
1188 #[cfg(feature = "ed25519")]
1205 pub async fn submit_batch_and_wait<A: Account>(
1206 &self,
1207 account: &A,
1208 payloads: Vec<TransactionPayload>,
1209 timeout: Option<Duration>,
1210 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
1211 self.batch()
1212 .submit_and_wait(account, payloads, timeout)
1213 .await
1214 }
1215
1216 #[cfg(feature = "ed25519")]
1238 pub async fn batch_transfer_apt<A: Account>(
1239 &self,
1240 sender: &A,
1241 transfers: Vec<(AccountAddress, u64)>,
1242 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
1243 self.batch().transfer_apt(sender, transfers).await
1244 }
1245}
1246
1247#[cfg(feature = "ed25519")]
1256fn build_zero_signed_authenticator<A: Account>(
1278 account: &A,
1279) -> AptosResult<crate::transaction::TransactionAuthenticator> {
1280 use crate::crypto::{
1281 ED25519_SCHEME, MULTI_ED25519_SCHEME, MULTI_KEY_SCHEME, SINGLE_KEY_SCHEME,
1282 };
1283 use crate::transaction::TransactionAuthenticator;
1284 use crate::transaction::authenticator::{
1285 AccountAuthenticator, Ed25519PublicKey, Ed25519Signature,
1286 };
1287
1288 let pubkey_bytes = account.public_key_bytes();
1289 let scheme = account.signature_scheme();
1290
1291 match scheme {
1292 s if s == ED25519_SCHEME => {
1295 let pubkey_arr: [u8; 32] = pubkey_bytes.as_slice().try_into().map_err(|_| {
1296 crate::error::AptosError::transaction(
1297 "simulate(): Ed25519 account exposed a non-32-byte public key",
1298 )
1299 })?;
1300 Ok(TransactionAuthenticator::Ed25519 {
1301 public_key: Ed25519PublicKey(pubkey_arr),
1302 signature: Ed25519Signature([0u8; 64]),
1303 })
1304 }
1305
1306 s if s == MULTI_ED25519_SCHEME => {
1311 const PK_LEN: usize = 32;
1312 const SIG_LEN: usize = 64;
1313 if pubkey_bytes.is_empty() || !(pubkey_bytes.len() - 1).is_multiple_of(PK_LEN) {
1314 return Err(crate::error::AptosError::transaction(
1315 "simulate(): MultiEd25519 public_key_bytes has invalid length",
1316 ));
1317 }
1318 let threshold = *pubkey_bytes.last().unwrap() as usize;
1319 if threshold == 0 {
1320 return Err(crate::error::AptosError::transaction(
1321 "simulate(): MultiEd25519 threshold cannot be zero",
1322 ));
1323 }
1324 let mut bitmap = [0u8; 4];
1326 for i in 0..threshold {
1327 let byte = i / 8;
1328 let bit = i % 8;
1329 bitmap[byte] |= 0b1000_0000_u8 >> bit;
1330 }
1331 let mut signature = Vec::with_capacity(threshold * SIG_LEN + 4);
1332 signature.extend(std::iter::repeat_n(0u8, threshold * SIG_LEN));
1333 signature.extend_from_slice(&bitmap);
1334 Ok(TransactionAuthenticator::MultiEd25519 {
1335 public_key: pubkey_bytes,
1336 signature,
1337 })
1338 }
1339
1340 s if s == SINGLE_KEY_SCHEME => {
1344 let zero_sig = zero_any_signature_for_pubkey(&pubkey_bytes).ok_or_else(|| {
1345 crate::error::AptosError::transaction(
1346 "simulate(): unsupported AnyPublicKey variant in SingleKey account",
1347 )
1348 })?;
1349 Ok(TransactionAuthenticator::single_sender(
1350 AccountAuthenticator::single_key(pubkey_bytes, zero_sig),
1351 ))
1352 }
1353
1354 s if s == MULTI_KEY_SCHEME => {
1359 let (variants, threshold) = parse_multi_key_pubkey(&pubkey_bytes)?;
1360 if threshold == 0 || (threshold as usize) > variants.len() {
1361 return Err(crate::error::AptosError::transaction(
1362 "simulate(): invalid MultiKey threshold",
1363 ));
1364 }
1365 let mut sig_bytes = Vec::with_capacity(1 + threshold as usize * 66 + 1 + 4);
1366 sig_bytes.push(threshold); for variant in variants.iter().take(threshold as usize) {
1368 let zero_sig = zero_any_signature_for_variant(*variant).ok_or_else(|| {
1369 crate::error::AptosError::transaction(
1370 "simulate(): unsupported AnyPublicKey variant in MultiKey account",
1371 )
1372 })?;
1373 sig_bytes.extend_from_slice(&zero_sig);
1374 }
1375 sig_bytes.push(4);
1377 let mut bitmap = [0u8; 4];
1378 for i in 0..threshold as usize {
1379 let byte = i / 8;
1380 let bit = i % 8;
1381 bitmap[byte] |= 0b1000_0000_u8 >> bit;
1382 }
1383 sig_bytes.extend_from_slice(&bitmap);
1384 Ok(TransactionAuthenticator::single_sender(
1385 AccountAuthenticator::multi_key(pubkey_bytes, sig_bytes),
1386 ))
1387 }
1388
1389 _ => Err(crate::error::AptosError::transaction(format!(
1390 "simulate(): unsupported signature scheme {scheme}; \
1391 use simulate_signed() with a hand-built zero-signed transaction"
1392 ))),
1393 }
1394}
1395
1396#[cfg(feature = "ed25519")]
1400fn zero_any_signature_for_pubkey(any_public_key_bcs: &[u8]) -> Option<Vec<u8>> {
1401 let variant = *any_public_key_bcs.first()?;
1402 zero_any_signature_for_variant(variant)
1403}
1404
1405#[cfg(feature = "ed25519")]
1407fn zero_any_signature_for_variant(variant: u8) -> Option<Vec<u8>> {
1408 match variant {
1417 0..=2 => {
1418 let mut out = Vec::with_capacity(1 + 1 + 64);
1419 out.push(variant);
1420 out.push(64);
1421 out.extend(std::iter::repeat_n(0u8, 64));
1422 Some(out)
1423 }
1424 _ => None,
1425 }
1426}
1427
1428#[cfg(feature = "ed25519")]
1432fn parse_multi_key_pubkey(bytes: &[u8]) -> AptosResult<(Vec<u8>, u8)> {
1433 if bytes.is_empty() {
1434 return Err(crate::error::AptosError::transaction(
1435 "simulate(): MultiKey public_key_bytes is empty",
1436 ));
1437 }
1438 let num_keys = bytes[0] as usize;
1439 let mut offset = 1;
1440 let mut variants = Vec::with_capacity(num_keys);
1441 for _ in 0..num_keys {
1442 if offset >= bytes.len() {
1443 return Err(crate::error::AptosError::transaction(
1444 "simulate(): MultiKey public_key truncated at variant tag",
1445 ));
1446 }
1447 let variant = bytes[offset];
1448 variants.push(variant);
1449 offset += 1;
1450 let (len, len_bytes) = decode_uleb128_internal(&bytes[offset..])?;
1452 offset += len_bytes;
1453 offset = offset.checked_add(len).ok_or_else(|| {
1454 crate::error::AptosError::transaction("simulate(): MultiKey public_key overflow")
1455 })?;
1456 if offset > bytes.len() {
1457 return Err(crate::error::AptosError::transaction(
1458 "simulate(): MultiKey public_key truncated at key bytes",
1459 ));
1460 }
1461 }
1462 if offset >= bytes.len() {
1463 return Err(crate::error::AptosError::transaction(
1464 "simulate(): MultiKey public_key missing threshold byte",
1465 ));
1466 }
1467 let threshold = bytes[offset];
1468 Ok((variants, threshold))
1469}
1470
1471#[cfg(feature = "ed25519")]
1473fn decode_uleb128_internal(bytes: &[u8]) -> AptosResult<(usize, usize)> {
1474 let mut value: usize = 0;
1475 let mut shift = 0;
1476 for (i, &b) in bytes.iter().enumerate() {
1477 value |= ((b & 0x7F) as usize) << shift;
1478 if (b & 0x80) == 0 {
1479 return Ok((value, i + 1));
1480 }
1481 shift += 7;
1482 if shift >= 64 {
1483 break;
1484 }
1485 }
1486 Err(crate::error::AptosError::transaction(
1487 "simulate(): malformed ULEB128 in public key",
1488 ))
1489}
1490
1491#[cfg(test)]
1492mod tests {
1493 use super::*;
1494 use crate::transaction::authenticator::{
1495 Ed25519PublicKey, Ed25519Signature, TransactionAuthenticator,
1496 };
1497 use crate::transaction::payload::{EntryFunction, TransactionPayload};
1498 use crate::transaction::simulation::SimulateQueryOptions;
1499 use crate::transaction::types::{
1500 FeePayerRawTransaction, MultiAgentRawTransaction, RawTransaction, SignedTransaction,
1501 };
1502 use crate::types::ChainId;
1503 use wiremock::{
1504 Mock, MockServer, ResponseTemplate,
1505 matchers::{method, path, path_regex},
1506 };
1507
1508 #[test]
1509 fn test_aptos_client_creation() {
1510 let aptos = Aptos::testnet();
1511 assert!(aptos.is_ok());
1512 }
1513
1514 #[test]
1515 fn test_chain_id() {
1516 let aptos = Aptos::testnet().unwrap();
1517 assert_eq!(aptos.chain_id(), ChainId::testnet());
1518
1519 let aptos = Aptos::mainnet().unwrap();
1520 assert_eq!(aptos.chain_id(), ChainId::mainnet());
1521 }
1522
1523 #[test]
1524 fn test_ans_accessor() {
1525 let aptos = Aptos::mainnet().unwrap();
1528 assert!(aptos.ans().router_address().is_ok());
1529 }
1530
1531 fn create_mock_aptos(server: &MockServer) -> Aptos {
1532 let url = format!("{}/v1", server.uri());
1533 let config = AptosConfig::custom(&url).unwrap().without_retry();
1534 Aptos::new(config).unwrap()
1535 }
1536
1537 fn create_minimal_signed_transaction() -> SignedTransaction {
1538 let raw = RawTransaction::new(
1539 AccountAddress::ONE,
1540 0,
1541 TransactionPayload::EntryFunction(
1542 EntryFunction::apt_transfer(AccountAddress::ONE, 0).unwrap(),
1543 ),
1544 100_000,
1545 100,
1546 std::time::SystemTime::now()
1547 .duration_since(std::time::UNIX_EPOCH)
1548 .unwrap()
1549 .as_secs()
1550 .saturating_add(600),
1551 ChainId::testnet(),
1552 );
1553 SignedTransaction::new(
1554 raw,
1555 TransactionAuthenticator::Ed25519 {
1556 public_key: Ed25519PublicKey([0u8; 32]),
1557 signature: Ed25519Signature([0u8; 64]),
1558 },
1559 )
1560 }
1561
1562 fn simulate_response_json() -> serde_json::Value {
1563 serde_json::json!([{
1564 "success": true,
1565 "vm_status": "Executed successfully",
1566 "gas_used": "1500",
1567 "max_gas_amount": "200000",
1568 "gas_unit_price": "100",
1569 "hash": "0xabc",
1570 "changes": [],
1571 "events": []
1572 }])
1573 }
1574
1575 #[tokio::test]
1576 async fn test_get_sequence_number() {
1577 let server = MockServer::start().await;
1578
1579 Mock::given(method("GET"))
1580 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+"))
1581 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1582 "sequence_number": "42",
1583 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1584 })))
1585 .expect(1)
1586 .mount(&server)
1587 .await;
1588
1589 let aptos = create_mock_aptos(&server);
1590 let seq = aptos
1591 .get_sequence_number(AccountAddress::ONE)
1592 .await
1593 .unwrap();
1594 assert_eq!(seq, 42);
1595 }
1596
1597 #[tokio::test]
1598 async fn test_get_balance() {
1599 let server = MockServer::start().await;
1600
1601 Mock::given(method("POST"))
1603 .and(path("/v1/view"))
1604 .respond_with(
1605 ResponseTemplate::new(200).set_body_json(serde_json::json!(["5000000000"])),
1606 )
1607 .expect(1)
1608 .mount(&server)
1609 .await;
1610
1611 let aptos = create_mock_aptos(&server);
1612 let balance = aptos.get_balance(AccountAddress::ONE).await.unwrap();
1613 assert_eq!(balance, 5_000_000_000);
1614 }
1615
1616 #[tokio::test]
1617 async fn test_get_resources_via_fullnode() {
1618 let server = MockServer::start().await;
1619
1620 Mock::given(method("GET"))
1621 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+/resources"))
1622 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
1623 {"type": "0x1::account::Account", "data": {}},
1624 {"type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": {}}
1625 ])))
1626 .expect(1)
1627 .mount(&server)
1628 .await;
1629
1630 let aptos = create_mock_aptos(&server);
1631 let resources = aptos
1632 .fullnode()
1633 .get_account_resources(AccountAddress::ONE)
1634 .await
1635 .unwrap();
1636 assert_eq!(resources.data.len(), 2);
1637 }
1638
1639 #[tokio::test]
1640 async fn test_ledger_info() {
1641 let server = MockServer::start().await;
1642
1643 Mock::given(method("GET"))
1644 .and(path("/v1"))
1645 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1646 "chain_id": 2,
1647 "epoch": "100",
1648 "ledger_version": "12345",
1649 "oldest_ledger_version": "0",
1650 "ledger_timestamp": "1000000",
1651 "node_role": "full_node",
1652 "oldest_block_height": "0",
1653 "block_height": "5000"
1654 })))
1655 .expect(1)
1656 .mount(&server)
1657 .await;
1658
1659 let aptos = create_mock_aptos(&server);
1660 let info = aptos.ledger_info().await.unwrap();
1661 assert_eq!(info.version().unwrap(), 12345);
1662 }
1663
1664 #[tokio::test]
1665 async fn test_config_builder() {
1666 let config = AptosConfig::testnet().with_timeout(Duration::from_mins(1));
1667
1668 let aptos = Aptos::new(config).unwrap();
1669 assert_eq!(aptos.chain_id(), ChainId::testnet());
1670 }
1671
1672 #[tokio::test]
1673 async fn test_fullnode_accessor() {
1674 let server = MockServer::start().await;
1675 let aptos = create_mock_aptos(&server);
1676
1677 let fullnode = aptos.fullnode();
1679 assert!(fullnode.base_url().as_str().contains(&server.uri()));
1680 }
1681
1682 #[cfg(feature = "ed25519")]
1683 #[tokio::test]
1684 async fn test_build_transaction() {
1685 let server = MockServer::start().await;
1686
1687 Mock::given(method("GET"))
1689 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+"))
1690 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1691 "sequence_number": "0",
1692 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1693 })))
1694 .expect(1)
1695 .mount(&server)
1696 .await;
1697
1698 Mock::given(method("GET"))
1700 .and(path("/v1/estimate_gas_price"))
1701 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1702 "gas_estimate": 100
1703 })))
1704 .expect(1)
1705 .mount(&server)
1706 .await;
1707
1708 Mock::given(method("GET"))
1710 .and(path("/v1"))
1711 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1712 "chain_id": 4,
1713 "epoch": "1",
1714 "ledger_version": "100",
1715 "oldest_ledger_version": "0",
1716 "ledger_timestamp": "1000000",
1717 "node_role": "full_node",
1718 "oldest_block_height": "0",
1719 "block_height": "50"
1720 })))
1721 .expect(1)
1722 .mount(&server)
1723 .await;
1724
1725 let aptos = create_mock_aptos(&server);
1726 let account = crate::account::Ed25519Account::generate();
1727 let recipient = AccountAddress::from_hex("0x123").unwrap();
1728 let payload = crate::transaction::EntryFunction::apt_transfer(recipient, 1000).unwrap();
1729
1730 let raw_txn = aptos
1731 .build_transaction(&account, payload.into())
1732 .await
1733 .unwrap();
1734 assert_eq!(raw_txn.sender, account.address());
1735 assert_eq!(raw_txn.sequence_number, 0);
1736 }
1737
1738 #[cfg(feature = "indexer")]
1739 #[tokio::test]
1740 async fn test_indexer_accessor() {
1741 let aptos = Aptos::testnet().unwrap();
1742 let indexer = aptos.indexer();
1743 assert!(indexer.is_some());
1744 }
1745
1746 #[tokio::test]
1747 async fn test_account_exists_true() {
1748 let server = MockServer::start().await;
1749
1750 Mock::given(method("GET"))
1751 .and(path_regex(r"^/v1/accounts/0x[0-9a-f]+$"))
1752 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1753 "sequence_number": "10",
1754 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1755 })))
1756 .expect(1)
1757 .mount(&server)
1758 .await;
1759
1760 let aptos = create_mock_aptos(&server);
1761 let exists = aptos.account_exists(AccountAddress::ONE).await.unwrap();
1762 assert!(exists);
1763 }
1764
1765 #[tokio::test]
1766 async fn test_account_exists_false() {
1767 let server = MockServer::start().await;
1768
1769 Mock::given(method("GET"))
1770 .and(path_regex(r"^/v1/accounts/0x[0-9a-f]+$"))
1771 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
1772 "message": "Account not found",
1773 "error_code": "account_not_found"
1774 })))
1775 .expect(1)
1776 .mount(&server)
1777 .await;
1778
1779 let aptos = create_mock_aptos(&server);
1780 let exists = aptos.account_exists(AccountAddress::ONE).await.unwrap();
1781 assert!(!exists);
1782 }
1783
1784 #[tokio::test]
1785 async fn test_view_function() {
1786 let server = MockServer::start().await;
1787
1788 Mock::given(method("POST"))
1789 .and(path("/v1/view"))
1790 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!(["1000000"])))
1791 .expect(1)
1792 .mount(&server)
1793 .await;
1794
1795 let aptos = create_mock_aptos(&server);
1796 let result: Vec<serde_json::Value> = aptos
1797 .view(
1798 "0x1::coin::balance",
1799 vec!["0x1::aptos_coin::AptosCoin".to_string()],
1800 vec![serde_json::json!("0x1")],
1801 )
1802 .await
1803 .unwrap();
1804
1805 assert_eq!(result.len(), 1);
1806 assert_eq!(result[0].as_str().unwrap(), "1000000");
1807 }
1808
1809 #[tokio::test]
1810 async fn test_chain_id_from_config() {
1811 let aptos = Aptos::mainnet().unwrap();
1812 assert_eq!(aptos.chain_id(), ChainId::mainnet());
1813
1814 let aptos = Aptos::devnet().unwrap();
1820 assert_eq!(aptos.chain_id(), ChainId::new(0));
1821 }
1822
1823 #[tokio::test]
1824 async fn test_custom_config() {
1825 let server = MockServer::start().await;
1826 let url = format!("{}/v1", server.uri());
1827 let config = AptosConfig::custom(&url).unwrap();
1828 let aptos = Aptos::new(config).unwrap();
1829
1830 assert_eq!(aptos.chain_id(), ChainId::new(0));
1832 }
1833
1834 #[cfg(feature = "ed25519")]
1843 #[test]
1844 fn test_zero_signed_authenticator_ed25519() {
1845 use crate::account::Ed25519Account;
1846 use crate::transaction::TransactionAuthenticator;
1847
1848 let account = Ed25519Account::generate();
1849 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1850 match auth {
1851 TransactionAuthenticator::Ed25519 {
1852 public_key,
1853 signature,
1854 } => {
1855 assert_eq!(public_key.0, account.public_key().to_bytes());
1856 assert_eq!(signature.0, [0u8; 64]);
1857 }
1858 other => panic!("expected TransactionAuthenticator::Ed25519, got {other:?}"),
1859 }
1860 }
1861
1862 #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
1863 #[test]
1864 fn test_zero_signed_authenticator_single_key_secp256k1() {
1865 use crate::account::Secp256k1Account;
1866 use crate::transaction::TransactionAuthenticator;
1867 use crate::transaction::authenticator::AccountAuthenticator;
1868
1869 let account = Secp256k1Account::generate();
1870 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1871 let TransactionAuthenticator::SingleSender { sender } = auth else {
1874 panic!("expected SingleSender, got {auth:?}");
1875 };
1876 let AccountAuthenticator::SingleKey {
1877 public_key,
1878 signature,
1879 } = sender
1880 else {
1881 panic!("expected AccountAuthenticator::SingleKey");
1882 };
1883 assert_eq!(public_key, account.public_key_bytes());
1885 assert_eq!(signature.len(), 1 + 1 + 64);
1888 assert_eq!(signature[0], 0x01, "variant tag must match secp256k1");
1889 assert_eq!(signature[1], 64, "ULEB128(64)");
1890 assert!(signature[2..].iter().all(|b| *b == 0), "all-zero signature");
1891 }
1892
1893 #[cfg(feature = "ed25519")]
1894 #[test]
1895 fn test_zero_signed_authenticator_single_key_ed25519() {
1896 use crate::account::Ed25519SingleKeyAccount;
1897 use crate::transaction::TransactionAuthenticator;
1898 use crate::transaction::authenticator::AccountAuthenticator;
1899
1900 let account = Ed25519SingleKeyAccount::generate();
1905 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1906 let TransactionAuthenticator::SingleSender { sender } = auth else {
1907 panic!("expected SingleSender for Ed25519SingleKey, got {auth:?}");
1908 };
1909 let AccountAuthenticator::SingleKey {
1910 public_key,
1911 signature,
1912 } = sender
1913 else {
1914 panic!("expected AccountAuthenticator::SingleKey");
1915 };
1916 assert_eq!(public_key, account.public_key_bytes());
1917 assert_eq!(signature[0], 0x00, "AnySignature::Ed25519 variant");
1918 assert_eq!(signature[1], 64, "ULEB128(64)");
1919 assert!(signature[2..].iter().all(|b| *b == 0));
1920 }
1921
1922 #[cfg(feature = "ed25519")]
1923 #[test]
1924 fn test_zero_signed_authenticator_multi_ed25519() {
1925 use crate::account::MultiEd25519Account;
1926 use crate::crypto::Ed25519PrivateKey;
1927 use crate::transaction::TransactionAuthenticator;
1928
1929 let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
1931 let account = MultiEd25519Account::new(keys, 2).unwrap();
1932 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1933 let TransactionAuthenticator::MultiEd25519 {
1934 public_key,
1935 signature,
1936 } = auth
1937 else {
1938 panic!("expected MultiEd25519, got {auth:?}");
1939 };
1940 assert_eq!(public_key, account.public_key_bytes());
1941 assert_eq!(signature.len(), 2 * 64 + 4);
1943 assert!(signature[..128].iter().all(|b| *b == 0));
1944 assert_eq!(signature[128], 0b1100_0000, "bits 0 and 1 set (MSB-first)");
1945 assert_eq!(&signature[129..], &[0u8, 0u8, 0u8]);
1946 }
1947
1948 #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
1949 #[test]
1950 fn test_zero_signed_authenticator_multi_key() {
1951 use crate::account::{AnyPrivateKey, MultiKeyAccount};
1952 use crate::crypto::{Ed25519PrivateKey, Secp256k1PrivateKey};
1953 use crate::transaction::TransactionAuthenticator;
1954 use crate::transaction::authenticator::AccountAuthenticator;
1955
1956 let keys = vec![
1957 AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
1958 AnyPrivateKey::secp256k1(Secp256k1PrivateKey::generate()),
1959 AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
1960 ];
1961 let account = MultiKeyAccount::new(keys, 2).unwrap();
1962 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1963 let TransactionAuthenticator::SingleSender { sender } = auth else {
1964 panic!("expected SingleSender for MultiKey, got {auth:?}");
1965 };
1966 let AccountAuthenticator::MultiKey {
1967 public_key,
1968 signature,
1969 } = sender
1970 else {
1971 panic!("expected AccountAuthenticator::MultiKey");
1972 };
1973 assert_eq!(public_key, account.public_key_bytes());
1974 assert_eq!(signature[0], 2, "num_sigs ULEB128");
1980 assert_eq!(signature[1], 0x00, "first AnySignature variant (Ed25519)");
1981 assert_eq!(
1982 signature[1 + 1 + 1 + 64],
1983 0x01,
1984 "second AnySignature variant (Secp256k1)"
1985 );
1986 }
1987
1988 #[tokio::test]
1989 async fn test_simulate_signed_with_options() {
1990 let server = MockServer::start().await;
1991
1992 Mock::given(method("POST"))
1993 .and(path("/v1/transactions/simulate"))
1994 .and(|req: &wiremock::Request| {
1995 req.url
1996 .query()
1997 .is_some_and(|q| q.contains("estimate_gas_unit_price=true"))
1998 })
1999 .respond_with(
2000 ResponseTemplate::new(200).set_body_json(serde_json::json!([{
2001 "success": true,
2002 "vm_status": "Executed successfully",
2003 "gas_used": "1500",
2004 "max_gas_amount": "200000",
2005 "gas_unit_price": "100",
2006 "hash": "0xabc",
2007 "changes": [],
2008 "events": []
2009 }])),
2010 )
2011 .expect(1)
2012 .mount(&server)
2013 .await;
2014
2015 let raw = RawTransaction::new(
2016 AccountAddress::ONE,
2017 0,
2018 TransactionPayload::EntryFunction(
2019 EntryFunction::apt_transfer(AccountAddress::ONE, 0).unwrap(),
2020 ),
2021 100_000,
2022 100,
2023 std::time::SystemTime::now()
2024 .duration_since(std::time::UNIX_EPOCH)
2025 .unwrap()
2026 .as_secs()
2027 .saturating_add(600),
2028 ChainId::testnet(),
2029 );
2030 let signed = SignedTransaction::new(
2031 raw,
2032 TransactionAuthenticator::Ed25519 {
2033 public_key: Ed25519PublicKey([0u8; 32]),
2034 signature: Ed25519Signature([0u8; 64]),
2035 },
2036 );
2037
2038 let aptos = create_mock_aptos(&server);
2039 let options = SimulateQueryOptions::new().estimate_gas_unit_price(true);
2040 let result = aptos
2041 .simulate_signed_with_options(&signed, options)
2042 .await
2043 .unwrap();
2044
2045 assert!(result.success());
2046 assert_eq!(result.gas_used(), 1500);
2047 assert_eq!(result.gas_unit_price(), 100);
2048 }
2049
2050 #[tokio::test]
2051 async fn test_simulate_signed_without_options() {
2052 let server = MockServer::start().await;
2053
2054 Mock::given(method("POST"))
2055 .and(path("/v1/transactions/simulate"))
2056 .and(|req: &wiremock::Request| {
2057 req.url.query().is_none_or(|q| {
2058 !q.contains("estimate_gas_unit_price=")
2059 && !q.contains("estimate_max_gas_amount=")
2060 && !q.contains("estimate_prioritized_gas_unit_price=")
2061 })
2062 })
2063 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2064 .expect(1)
2065 .mount(&server)
2066 .await;
2067
2068 let aptos = create_mock_aptos(&server);
2069 let signed = create_minimal_signed_transaction();
2070 let result = aptos.simulate_signed(&signed).await.unwrap();
2071 assert!(result.success());
2072 }
2073
2074 #[tokio::test]
2075 async fn test_simulate_multi_agent_without_options() {
2076 let server = MockServer::start().await;
2077
2078 Mock::given(method("POST"))
2079 .and(path("/v1/transactions/simulate"))
2080 .and(|req: &wiremock::Request| {
2081 req.url.query().is_none_or(|q| {
2082 !q.contains("estimate_gas_unit_price=")
2083 && !q.contains("estimate_max_gas_amount=")
2084 && !q.contains("estimate_prioritized_gas_unit_price=")
2085 })
2086 })
2087 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2088 .expect(1)
2089 .mount(&server)
2090 .await;
2091
2092 let aptos = create_mock_aptos(&server);
2093 let multi_agent = MultiAgentRawTransaction::new(
2094 create_minimal_signed_transaction().raw_txn,
2095 vec![AccountAddress::from_hex("0x2").unwrap()],
2096 );
2097 let result = aptos
2098 .simulate_multi_agent(&multi_agent, None)
2099 .await
2100 .unwrap();
2101 assert!(result.success());
2102 }
2103
2104 #[tokio::test]
2105 async fn test_simulate_multi_agent_with_options() {
2106 let server = MockServer::start().await;
2107
2108 Mock::given(method("POST"))
2109 .and(path("/v1/transactions/simulate"))
2110 .and(|req: &wiremock::Request| {
2111 req.url
2112 .query()
2113 .is_some_and(|q| q.contains("estimate_max_gas_amount=true"))
2114 })
2115 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2116 .expect(1)
2117 .mount(&server)
2118 .await;
2119
2120 let aptos = create_mock_aptos(&server);
2121 let multi_agent = MultiAgentRawTransaction::new(
2122 create_minimal_signed_transaction().raw_txn,
2123 vec![AccountAddress::from_hex("0x2").unwrap()],
2124 );
2125 let options = SimulateQueryOptions::new().estimate_max_gas_amount(true);
2126 let result = aptos
2127 .simulate_multi_agent(&multi_agent, Some(options))
2128 .await
2129 .unwrap();
2130 assert!(result.success());
2131 }
2132
2133 #[tokio::test]
2134 async fn test_simulate_fee_payer_without_options() {
2135 let server = MockServer::start().await;
2136
2137 Mock::given(method("POST"))
2138 .and(path("/v1/transactions/simulate"))
2139 .and(|req: &wiremock::Request| {
2140 req.url.query().is_none_or(|q| {
2141 !q.contains("estimate_gas_unit_price=")
2142 && !q.contains("estimate_max_gas_amount=")
2143 && !q.contains("estimate_prioritized_gas_unit_price=")
2144 })
2145 })
2146 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2147 .expect(1)
2148 .mount(&server)
2149 .await;
2150
2151 let aptos = create_mock_aptos(&server);
2152 let fee_payer_txn = FeePayerRawTransaction::new_simple(
2153 create_minimal_signed_transaction().raw_txn,
2154 AccountAddress::THREE,
2155 );
2156 let result = aptos
2157 .simulate_fee_payer(&fee_payer_txn, None)
2158 .await
2159 .unwrap();
2160 assert!(result.success());
2161 }
2162
2163 #[tokio::test]
2164 async fn test_simulate_fee_payer_with_options() {
2165 let server = MockServer::start().await;
2166
2167 Mock::given(method("POST"))
2168 .and(path("/v1/transactions/simulate"))
2169 .and(|req: &wiremock::Request| {
2170 req.url
2171 .query()
2172 .is_some_and(|q| q.contains("estimate_prioritized_gas_unit_price=true"))
2173 })
2174 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2175 .expect(1)
2176 .mount(&server)
2177 .await;
2178
2179 let aptos = create_mock_aptos(&server);
2180 let fee_payer_txn = FeePayerRawTransaction::new_simple(
2181 create_minimal_signed_transaction().raw_txn,
2182 AccountAddress::THREE,
2183 );
2184 let options = SimulateQueryOptions::new().estimate_prioritized_gas_unit_price(true);
2185 let result = aptos
2186 .simulate_fee_payer(&fee_payer_txn, options)
2187 .await
2188 .unwrap();
2189 assert!(result.success());
2190 }
2191
2192 const TEST_TXN_HASH: &str =
2199 "0x0000000000000000000000000000000000000000000000000000000000000001";
2200
2201 async fn mount_seq_number(server: &MockServer, seq: u64) {
2202 Mock::given(method("GET"))
2203 .and(path_regex(r"^/v1/accounts/0x[0-9a-f]+$"))
2204 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2205 "sequence_number": seq.to_string(),
2206 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
2207 })))
2208 .mount(server)
2209 .await;
2210 }
2211
2212 async fn mount_gas_price(server: &MockServer) {
2213 Mock::given(method("GET"))
2214 .and(path("/v1/estimate_gas_price"))
2215 .respond_with(
2216 ResponseTemplate::new(200)
2217 .set_body_json(serde_json::json!({ "gas_estimate": 100 })),
2218 )
2219 .mount(server)
2220 .await;
2221 }
2222
2223 async fn mount_ledger(server: &MockServer, chain_id: u8) {
2224 Mock::given(method("GET"))
2225 .and(path("/v1"))
2226 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2227 "chain_id": chain_id,
2228 "epoch": "1",
2229 "ledger_version": "100",
2230 "oldest_ledger_version": "0",
2231 "ledger_timestamp": "1000000",
2232 "node_role": "full_node",
2233 "oldest_block_height": "0",
2234 "block_height": "50"
2235 })))
2236 .mount(server)
2237 .await;
2238 }
2239
2240 fn pending_txn_json() -> serde_json::Value {
2241 serde_json::json!({
2242 "hash": TEST_TXN_HASH,
2243 "sender": "0x1",
2244 "sequence_number": "0",
2245 "max_gas_amount": "200000",
2246 "gas_unit_price": "100",
2247 "expiration_timestamp_secs": "1000000000"
2248 })
2249 }
2250
2251 fn committed_txn_json() -> serde_json::Value {
2252 serde_json::json!({
2253 "type": "user_transaction",
2254 "version": "12345",
2255 "hash": TEST_TXN_HASH,
2256 "success": true,
2257 "vm_status": "Executed successfully"
2258 })
2259 }
2260
2261 async fn mount_submit(server: &MockServer) {
2262 Mock::given(method("POST"))
2263 .and(path("/v1/transactions"))
2264 .respond_with(ResponseTemplate::new(202).set_body_json(pending_txn_json()))
2265 .mount(server)
2266 .await;
2267 }
2268
2269 async fn mount_wait(server: &MockServer) {
2270 Mock::given(method("GET"))
2271 .and(path_regex(r"^/v1/transactions/by_hash/0x[0-9a-f]+$"))
2272 .respond_with(ResponseTemplate::new(200).set_body_json(committed_txn_json()))
2273 .mount(server)
2274 .await;
2275 }
2276
2277 async fn mount_simulate(server: &MockServer) {
2278 Mock::given(method("POST"))
2279 .and(path("/v1/transactions/simulate"))
2280 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2281 .mount(server)
2282 .await;
2283 }
2284
2285 async fn mount_build_flow(server: &MockServer, seq: u64, chain_id: u8) {
2288 mount_seq_number(server, seq).await;
2289 mount_gas_price(server).await;
2290 mount_ledger(server, chain_id).await;
2291 }
2292
2293 #[tokio::test]
2298 async fn test_ensure_chain_id_known_no_request() {
2299 let server = MockServer::start().await;
2303 let aptos = Aptos::testnet().unwrap();
2304 let _ = &server;
2306 let id = aptos.ensure_chain_id().await.unwrap();
2307 assert_eq!(id, ChainId::testnet());
2308 }
2309
2310 #[tokio::test]
2311 async fn test_ensure_chain_id_fetches_from_node() {
2312 let server = MockServer::start().await;
2313 mount_ledger(&server, 7).await;
2314
2315 let aptos = create_mock_aptos(&server);
2316 assert_eq!(aptos.chain_id(), ChainId::new(0));
2318
2319 let id = aptos.ensure_chain_id().await.unwrap();
2320 assert_eq!(id, ChainId::new(7));
2321 assert_eq!(aptos.chain_id(), ChainId::new(7));
2323 }
2324
2325 #[tokio::test]
2326 async fn test_ledger_info_populates_chain_id() {
2327 let server = MockServer::start().await;
2328 mount_ledger(&server, 9).await;
2329
2330 let aptos = create_mock_aptos(&server);
2331 assert_eq!(aptos.chain_id(), ChainId::new(0));
2332 let info = aptos.ledger_info().await.unwrap();
2333 assert_eq!(info.chain_id, 9);
2334 assert_eq!(aptos.chain_id(), ChainId::new(9));
2336 }
2337
2338 #[tokio::test]
2343 async fn test_submit_transaction() {
2344 let server = MockServer::start().await;
2345 mount_submit(&server).await;
2346
2347 let aptos = create_mock_aptos(&server);
2348 let signed = create_minimal_signed_transaction();
2349 let pending = aptos.submit_transaction(&signed).await.unwrap();
2350 assert_eq!(pending.into_inner().hash().to_string(), TEST_TXN_HASH);
2351 }
2352
2353 #[tokio::test]
2354 async fn test_submit_and_wait() {
2355 let server = MockServer::start().await;
2356 mount_submit(&server).await;
2357 mount_wait(&server).await;
2358
2359 let aptos = create_mock_aptos(&server);
2360 let signed = create_minimal_signed_transaction();
2361 let response = aptos.submit_and_wait(&signed, None).await.unwrap();
2362 assert_eq!(
2363 response
2364 .into_inner()
2365 .get("success")
2366 .and_then(serde_json::Value::as_bool),
2367 Some(true)
2368 );
2369 }
2370
2371 #[tokio::test]
2372 async fn test_simulate_transaction_raw() {
2373 let server = MockServer::start().await;
2374 mount_simulate(&server).await;
2375
2376 let aptos = create_mock_aptos(&server);
2377 let signed = create_minimal_signed_transaction();
2378 let response = aptos.simulate_transaction(&signed).await.unwrap();
2379 let data = response.into_inner();
2380 assert_eq!(data.len(), 1);
2381 assert_eq!(
2382 data[0].get("success").and_then(serde_json::Value::as_bool),
2383 Some(true)
2384 );
2385 }
2386
2387 #[cfg(feature = "ed25519")]
2392 #[tokio::test]
2393 async fn test_build_orderless_transaction() {
2394 let server = MockServer::start().await;
2395 mount_gas_price(&server).await;
2396 mount_ledger(&server, 4).await;
2397
2398 let aptos = create_mock_aptos(&server);
2399 let account = crate::account::Ed25519Account::generate();
2400 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2401 .unwrap()
2402 .into();
2403
2404 let raw_txn = aptos
2405 .build_orderless_transaction(&account, payload, Some(99))
2406 .await
2407 .unwrap();
2408 assert_eq!(raw_txn.sequence_number, u64::MAX);
2410 assert_eq!(raw_txn.sender, account.address());
2411 assert_eq!(raw_txn.chain_id, ChainId::new(4));
2412 }
2413
2414 #[cfg(feature = "ed25519")]
2419 #[tokio::test]
2420 async fn test_sign_and_submit() {
2421 let server = MockServer::start().await;
2422 mount_build_flow(&server, 0, 4).await;
2423 mount_submit(&server).await;
2424
2425 let aptos = create_mock_aptos(&server);
2426 let account = crate::account::Ed25519Account::generate();
2427 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2428 .unwrap()
2429 .into();
2430
2431 let pending = aptos.sign_and_submit(&account, payload).await.unwrap();
2432 assert_eq!(pending.into_inner().hash().to_string(), TEST_TXN_HASH);
2433 }
2434
2435 #[cfg(feature = "ed25519")]
2436 #[tokio::test]
2437 async fn test_sign_submit_and_wait() {
2438 let server = MockServer::start().await;
2439 mount_build_flow(&server, 0, 4).await;
2440 mount_submit(&server).await;
2441 mount_wait(&server).await;
2442
2443 let aptos = create_mock_aptos(&server);
2444 let account = crate::account::Ed25519Account::generate();
2445 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2446 .unwrap()
2447 .into();
2448
2449 let response = aptos
2450 .sign_submit_and_wait(&account, payload, None)
2451 .await
2452 .unwrap();
2453 assert_eq!(
2454 response
2455 .into_inner()
2456 .get("success")
2457 .and_then(serde_json::Value::as_bool),
2458 Some(true)
2459 );
2460 }
2461
2462 #[cfg(feature = "ed25519")]
2463 #[tokio::test]
2464 async fn test_sign_and_submit_orderless() {
2465 let server = MockServer::start().await;
2466 mount_gas_price(&server).await;
2467 mount_ledger(&server, 4).await;
2468 mount_submit(&server).await;
2469
2470 let aptos = create_mock_aptos(&server);
2471 let account = crate::account::Ed25519Account::generate();
2472 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2473 .unwrap()
2474 .into();
2475
2476 let pending = aptos
2477 .sign_and_submit_orderless(&account, payload, Some(7))
2478 .await
2479 .unwrap();
2480 assert_eq!(pending.into_inner().hash().to_string(), TEST_TXN_HASH);
2481 }
2482
2483 #[cfg(feature = "ed25519")]
2488 #[tokio::test]
2489 async fn test_simulate_with_account() {
2490 let server = MockServer::start().await;
2491 mount_build_flow(&server, 0, 4).await;
2492 mount_simulate(&server).await;
2493
2494 let aptos = create_mock_aptos(&server);
2495 let account = crate::account::Ed25519Account::generate();
2496 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2497 .unwrap()
2498 .into();
2499
2500 let result = aptos.simulate(&account, payload).await.unwrap();
2501 assert!(result.success());
2502 assert_eq!(result.gas_used(), 1500);
2503 }
2504
2505 #[cfg(feature = "ed25519")]
2506 #[tokio::test]
2507 async fn test_estimate_gas() {
2508 let server = MockServer::start().await;
2509 mount_build_flow(&server, 0, 4).await;
2510 mount_simulate(&server).await;
2511
2512 let aptos = create_mock_aptos(&server);
2513 let account = crate::account::Ed25519Account::generate();
2514 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2515 .unwrap()
2516 .into();
2517
2518 let gas = aptos.estimate_gas(&account, payload).await.unwrap();
2519 assert_eq!(gas, 1800);
2521 }
2522
2523 #[cfg(feature = "ed25519")]
2524 #[tokio::test]
2525 async fn test_estimate_gas_simulation_failed() {
2526 let server = MockServer::start().await;
2527 mount_build_flow(&server, 0, 4).await;
2528 Mock::given(method("POST"))
2530 .and(path("/v1/transactions/simulate"))
2531 .respond_with(
2532 ResponseTemplate::new(200).set_body_json(serde_json::json!([{
2533 "success": false,
2534 "vm_status": "Move abort in 0x1::coin",
2535 "gas_used": "10",
2536 "max_gas_amount": "200000",
2537 "gas_unit_price": "100",
2538 "hash": "0xabc",
2539 "changes": [],
2540 "events": []
2541 }])),
2542 )
2543 .mount(&server)
2544 .await;
2545
2546 let aptos = create_mock_aptos(&server);
2547 let account = crate::account::Ed25519Account::generate();
2548 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2549 .unwrap()
2550 .into();
2551
2552 let err = aptos.estimate_gas(&account, payload).await.unwrap_err();
2553 assert!(matches!(err, AptosError::SimulationFailed(_)));
2554 }
2555
2556 #[cfg(feature = "ed25519")]
2557 #[tokio::test]
2558 async fn test_simulate_and_submit() {
2559 let server = MockServer::start().await;
2560 mount_build_flow(&server, 0, 4).await;
2561 mount_simulate(&server).await;
2562 mount_submit(&server).await;
2563
2564 let aptos = create_mock_aptos(&server);
2565 let account = crate::account::Ed25519Account::generate();
2566 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2567 .unwrap()
2568 .into();
2569
2570 let pending = aptos.simulate_and_submit(&account, payload).await.unwrap();
2571 assert_eq!(pending.into_inner().hash().to_string(), TEST_TXN_HASH);
2572 }
2573
2574 #[cfg(feature = "ed25519")]
2575 #[tokio::test]
2576 async fn test_simulate_submit_and_wait() {
2577 let server = MockServer::start().await;
2578 mount_build_flow(&server, 0, 4).await;
2579 mount_simulate(&server).await;
2580 mount_submit(&server).await;
2581 mount_wait(&server).await;
2582
2583 let aptos = create_mock_aptos(&server);
2584 let account = crate::account::Ed25519Account::generate();
2585 let payload = crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2586 .unwrap()
2587 .into();
2588
2589 let response = aptos
2590 .simulate_submit_and_wait(&account, payload, None)
2591 .await
2592 .unwrap();
2593 assert_eq!(
2594 response
2595 .into_inner()
2596 .get("success")
2597 .and_then(serde_json::Value::as_bool),
2598 Some(true)
2599 );
2600 }
2601
2602 #[cfg(feature = "ed25519")]
2607 #[tokio::test]
2608 async fn test_transfer_apt() {
2609 let server = MockServer::start().await;
2610 mount_build_flow(&server, 0, 4).await;
2611 mount_submit(&server).await;
2612 mount_wait(&server).await;
2613
2614 let aptos = create_mock_aptos(&server);
2615 let sender = crate::account::Ed25519Account::generate();
2616 let recipient = AccountAddress::from_hex("0x123").unwrap();
2617 let response = aptos.transfer_apt(&sender, recipient, 1000).await.unwrap();
2618 assert_eq!(
2619 response
2620 .into_inner()
2621 .get("success")
2622 .and_then(serde_json::Value::as_bool),
2623 Some(true)
2624 );
2625 }
2626
2627 #[cfg(feature = "ed25519")]
2628 #[tokio::test]
2629 async fn test_transfer_coin() {
2630 let server = MockServer::start().await;
2631 mount_build_flow(&server, 0, 4).await;
2632 mount_submit(&server).await;
2633 mount_wait(&server).await;
2634
2635 let aptos = create_mock_aptos(&server);
2636 let sender = crate::account::Ed25519Account::generate();
2637 let recipient = AccountAddress::from_hex("0x123").unwrap();
2638 let coin_type = TypeTag::aptos_coin();
2639 let response = aptos
2640 .transfer_coin(&sender, recipient, coin_type, 1000)
2641 .await
2642 .unwrap();
2643 assert_eq!(
2644 response
2645 .into_inner()
2646 .get("success")
2647 .and_then(serde_json::Value::as_bool),
2648 Some(true)
2649 );
2650 }
2651
2652 #[cfg(feature = "ed25519")]
2653 #[tokio::test]
2654 async fn test_transfer_fungible_asset() {
2655 let server = MockServer::start().await;
2656 mount_build_flow(&server, 0, 4).await;
2657 mount_submit(&server).await;
2658 mount_wait(&server).await;
2659
2660 let aptos = create_mock_aptos(&server);
2661 let sender = crate::account::Ed25519Account::generate();
2662 let metadata = AccountAddress::from_hex("0xa").unwrap();
2663 let recipient = AccountAddress::from_hex("0x123").unwrap();
2664 let response = aptos
2665 .transfer_fungible_asset(&sender, metadata, recipient, 1000)
2666 .await
2667 .unwrap();
2668 assert_eq!(
2669 response
2670 .into_inner()
2671 .get("success")
2672 .and_then(serde_json::Value::as_bool),
2673 Some(true)
2674 );
2675 }
2676
2677 #[cfg(feature = "ed25519")]
2678 #[tokio::test]
2679 async fn test_transfer_object() {
2680 let server = MockServer::start().await;
2681 mount_build_flow(&server, 0, 4).await;
2682 mount_submit(&server).await;
2683 mount_wait(&server).await;
2684
2685 let aptos = create_mock_aptos(&server);
2686 let owner = crate::account::Ed25519Account::generate();
2687 let object = AccountAddress::from_hex("0xb").unwrap();
2688 let to = AccountAddress::from_hex("0x123").unwrap();
2689 let response = aptos.transfer_object(&owner, object, to).await.unwrap();
2690 assert_eq!(
2691 response
2692 .into_inner()
2693 .get("success")
2694 .and_then(serde_json::Value::as_bool),
2695 Some(true)
2696 );
2697 }
2698
2699 #[cfg(feature = "ed25519")]
2700 #[tokio::test]
2701 async fn test_transfer_digital_asset() {
2702 let server = MockServer::start().await;
2703 mount_build_flow(&server, 0, 4).await;
2704 mount_submit(&server).await;
2705 mount_wait(&server).await;
2706
2707 let aptos = create_mock_aptos(&server);
2708 let owner = crate::account::Ed25519Account::generate();
2709 let token = AccountAddress::from_hex("0xc").unwrap();
2710 let to = AccountAddress::from_hex("0x123").unwrap();
2711 let response = aptos
2712 .transfer_digital_asset(&owner, token, to)
2713 .await
2714 .unwrap();
2715 assert_eq!(
2716 response
2717 .into_inner()
2718 .get("success")
2719 .and_then(serde_json::Value::as_bool),
2720 Some(true)
2721 );
2722 }
2723
2724 #[tokio::test]
2729 async fn test_get_table_item() {
2730 let server = MockServer::start().await;
2731 Mock::given(method("POST"))
2732 .and(path_regex(r"^/v1/tables/.+/item$"))
2733 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!("42")))
2734 .expect(1)
2735 .mount(&server)
2736 .await;
2737
2738 let aptos = create_mock_aptos(&server);
2739 let value = aptos
2740 .get_table_item("0xabc", "address", "u64", serde_json::json!("0x2"))
2741 .await
2742 .unwrap();
2743 assert_eq!(value.as_str(), Some("42"));
2744 }
2745
2746 #[tokio::test]
2747 async fn test_view_bcs_typed() {
2748 let server = MockServer::start().await;
2749 let body = aptos_bcs::to_bytes(&1_000_000u64).unwrap();
2751 Mock::given(method("POST"))
2752 .and(path("/v1/view"))
2753 .respond_with(ResponseTemplate::new(200).set_body_bytes(body))
2754 .expect(1)
2755 .mount(&server)
2756 .await;
2757
2758 let aptos = create_mock_aptos(&server);
2759 let args = vec![aptos_bcs::to_bytes(&AccountAddress::ONE).unwrap()];
2760 let balance: u64 = aptos
2761 .view_bcs("0x1::coin::balance", vec![], args)
2762 .await
2763 .unwrap();
2764 assert_eq!(balance, 1_000_000);
2765 }
2766
2767 #[tokio::test]
2768 async fn test_view_bcs_raw() {
2769 let server = MockServer::start().await;
2770 let body = aptos_bcs::to_bytes(&7u64).unwrap();
2771 Mock::given(method("POST"))
2772 .and(path("/v1/view"))
2773 .respond_with(ResponseTemplate::new(200).set_body_bytes(body.clone()))
2774 .expect(1)
2775 .mount(&server)
2776 .await;
2777
2778 let aptos = create_mock_aptos(&server);
2779 let args = vec![aptos_bcs::to_bytes(&AccountAddress::ONE).unwrap()];
2780 let raw = aptos
2781 .view_bcs_raw("0x1::coin::balance", vec![], args)
2782 .await
2783 .unwrap();
2784 assert_eq!(raw, body);
2785 }
2786
2787 #[cfg(feature = "ed25519")]
2792 #[tokio::test]
2793 async fn test_submit_batch() {
2794 let server = MockServer::start().await;
2795 mount_build_flow(&server, 0, 4).await;
2796 mount_submit(&server).await;
2797
2798 let aptos = create_mock_aptos(&server);
2799 let account = crate::account::Ed25519Account::generate();
2800 let payloads: Vec<TransactionPayload> = vec![
2801 crate::transaction::EntryFunction::apt_transfer(AccountAddress::ONE, 1000)
2802 .unwrap()
2803 .into(),
2804 crate::transaction::EntryFunction::apt_transfer(
2805 AccountAddress::from_hex("0x2").unwrap(),
2806 2000,
2807 )
2808 .unwrap()
2809 .into(),
2810 ];
2811
2812 let results = aptos.submit_batch(&account, payloads).await.unwrap();
2813 assert_eq!(results.len(), 2);
2814 }
2815
2816 #[cfg(feature = "ed25519")]
2817 #[tokio::test]
2818 async fn test_batch_transfer_apt() {
2819 let server = MockServer::start().await;
2820 mount_build_flow(&server, 0, 4).await;
2821 mount_submit(&server).await;
2822 mount_wait(&server).await;
2823
2824 let aptos = create_mock_aptos(&server);
2825 let sender = crate::account::Ed25519Account::generate();
2826 let transfers = vec![
2827 (AccountAddress::from_hex("0x123").unwrap(), 1000u64),
2828 (AccountAddress::from_hex("0x456").unwrap(), 2000u64),
2829 ];
2830
2831 let results = aptos.batch_transfer_apt(&sender, transfers).await.unwrap();
2832 assert_eq!(results.len(), 2);
2833 }
2834}