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 {
200 ChainId::new(self.chain_id.load(Ordering::Relaxed))
201 }
202
203 pub async fn ensure_chain_id(&self) -> AptosResult<ChainId> {
219 let id = self.chain_id.load(Ordering::Relaxed);
220 if id > 0 {
221 return Ok(ChainId::new(id));
222 }
223 let response = self.fullnode.get_ledger_info().await?;
225 let info = response.into_inner();
226 self.chain_id.store(info.chain_id, Ordering::Relaxed);
227 Ok(ChainId::new(info.chain_id))
228 }
229
230 pub async fn get_sequence_number(&self, address: AccountAddress) -> AptosResult<u64> {
239 self.fullnode.get_sequence_number(address).await
240 }
241
242 pub async fn get_balance(&self, address: AccountAddress) -> AptosResult<u64> {
249 self.fullnode.get_account_balance(address).await
250 }
251
252 pub async fn account_exists(&self, address: AccountAddress) -> AptosResult<bool> {
259 match self.fullnode.get_account(address).await {
260 Ok(_) => Ok(true),
261 Err(AptosError::Api {
262 status_code: 404, ..
263 }) => Ok(false),
264 Err(e) => Err(e),
265 }
266 }
267
268 pub async fn build_transaction<A: Account>(
280 &self,
281 sender: &A,
282 payload: TransactionPayload,
283 ) -> AptosResult<RawTransaction> {
284 let (sequence_number, gas_estimation, chain_id) = tokio::join!(
286 self.get_sequence_number(sender.address()),
287 self.fullnode.estimate_gas_price(),
288 self.ensure_chain_id()
289 );
290 let sequence_number = sequence_number?;
291 let gas_estimation = gas_estimation?;
292 let chain_id = chain_id?;
293
294 TransactionBuilder::new()
295 .sender(sender.address())
296 .sequence_number(sequence_number)
297 .payload(payload)
298 .gas_unit_price(gas_estimation.data.recommended())
299 .chain_id(chain_id)
300 .expiration_from_now(600)
301 .build()
302 }
303
304 #[cfg(feature = "ed25519")]
312 pub async fn sign_and_submit<A: Account>(
313 &self,
314 account: &A,
315 payload: TransactionPayload,
316 ) -> AptosResult<AptosResponse<PendingTransaction>> {
317 let raw_txn = self.build_transaction(account, payload).await?;
318 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
319 self.fullnode.submit_transaction(&signed).await
320 }
321
322 #[cfg(feature = "ed25519")]
330 pub async fn sign_submit_and_wait<A: Account>(
331 &self,
332 account: &A,
333 payload: TransactionPayload,
334 timeout: Option<Duration>,
335 ) -> AptosResult<AptosResponse<serde_json::Value>> {
336 let raw_txn = self.build_transaction(account, payload).await?;
337 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
338 self.fullnode.submit_and_wait(&signed, timeout).await
339 }
340
341 pub async fn build_orderless_transaction<A: Account>(
362 &self,
363 sender: &A,
364 payload: TransactionPayload,
365 nonce: Option<u64>,
366 ) -> AptosResult<RawTransaction> {
367 let (gas_estimation, chain_id) =
370 tokio::join!(self.fullnode.estimate_gas_price(), self.ensure_chain_id());
371 let gas_estimation = gas_estimation?;
372 let chain_id = chain_id?;
373
374 let nonce = nonce.unwrap_or_else(|| rand::RngCore::next_u64(&mut rand::rngs::OsRng));
375 let orderless_payload = payload.into_orderless(nonce)?;
376
377 TransactionBuilder::new()
378 .sender(sender.address())
379 .sequence_number(u64::MAX)
380 .payload(orderless_payload)
381 .gas_unit_price(gas_estimation.data.recommended())
382 .chain_id(chain_id)
383 .expiration_from_now(60)
384 .build()
385 }
386
387 #[cfg(feature = "ed25519")]
398 pub async fn sign_and_submit_orderless<A: Account>(
399 &self,
400 account: &A,
401 payload: TransactionPayload,
402 nonce: Option<u64>,
403 ) -> AptosResult<AptosResponse<PendingTransaction>> {
404 let raw_txn = self
405 .build_orderless_transaction(account, payload, nonce)
406 .await?;
407 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
408 self.fullnode.submit_transaction(&signed).await
409 }
410
411 #[cfg(feature = "ed25519")]
422 pub async fn sign_submit_and_wait_orderless<A: Account>(
423 &self,
424 account: &A,
425 payload: TransactionPayload,
426 nonce: Option<u64>,
427 timeout: Option<Duration>,
428 ) -> AptosResult<AptosResponse<serde_json::Value>> {
429 let raw_txn = self
430 .build_orderless_transaction(account, payload, nonce)
431 .await?;
432 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
433 self.fullnode.submit_and_wait(&signed, timeout).await
434 }
435
436 pub async fn submit_transaction(
443 &self,
444 signed_txn: &SignedTransaction,
445 ) -> AptosResult<AptosResponse<PendingTransaction>> {
446 self.fullnode.submit_transaction(signed_txn).await
447 }
448
449 pub async fn submit_and_wait(
457 &self,
458 signed_txn: &SignedTransaction,
459 timeout: Option<Duration>,
460 ) -> AptosResult<AptosResponse<serde_json::Value>> {
461 self.fullnode.submit_and_wait(signed_txn, timeout).await
462 }
463
464 pub async fn simulate_transaction(
474 &self,
475 signed_txn: &SignedTransaction,
476 ) -> AptosResult<AptosResponse<Vec<serde_json::Value>>> {
477 self.fullnode.simulate_transaction(signed_txn).await
478 }
479
480 #[cfg(feature = "ed25519")]
501 pub async fn simulate<A: Account>(
502 &self,
503 account: &A,
504 payload: TransactionPayload,
505 ) -> AptosResult<crate::transaction::SimulationResult> {
506 use crate::transaction::SignedTransaction;
507
508 let raw_txn = self.build_transaction(account, payload).await?;
509
510 let auth = build_zero_signed_authenticator(account)?;
518 let signed = SignedTransaction::new(raw_txn, auth);
519
520 let response = self.fullnode.simulate_transaction(&signed).await?;
521 crate::transaction::SimulationResult::from_response(response.into_inner())
522 }
523
524 pub async fn simulate_signed(
538 &self,
539 signed_txn: &SignedTransaction,
540 ) -> AptosResult<SimulationResult> {
541 let response = self.fullnode.simulate_transaction(signed_txn).await?;
542 SimulationResult::from_response(response.into_inner())
543 }
544
545 pub async fn simulate_signed_with_options(
558 &self,
559 signed_txn: &SignedTransaction,
560 options: SimulateQueryOptions,
561 ) -> AptosResult<SimulationResult> {
562 let response = self
563 .fullnode
564 .simulate_transaction_with_options(signed_txn, Some(options))
565 .await?;
566 SimulationResult::from_response(response.into_inner())
567 }
568
569 pub async fn simulate_multi_agent(
590 &self,
591 multi_agent: &MultiAgentRawTransaction,
592 options: impl Into<Option<SimulateQueryOptions>>,
593 ) -> AptosResult<SimulationResult> {
594 let signed = build_simulation_signed_multi_agent(multi_agent);
595 match options.into() {
596 None => self.simulate_signed(&signed).await,
597 Some(opts) => self.simulate_signed_with_options(&signed, opts).await,
598 }
599 }
600
601 pub async fn simulate_fee_payer(
622 &self,
623 fee_payer_txn: &FeePayerRawTransaction,
624 options: impl Into<Option<SimulateQueryOptions>>,
625 ) -> AptosResult<SimulationResult> {
626 let signed = build_simulation_signed_fee_payer(fee_payer_txn);
627 match options.into() {
628 None => self.simulate_signed(&signed).await,
629 Some(opts) => self.simulate_signed_with_options(&signed, opts).await,
630 }
631 }
632
633 #[cfg(feature = "ed25519")]
649 pub async fn estimate_gas<A: Account>(
650 &self,
651 account: &A,
652 payload: TransactionPayload,
653 ) -> AptosResult<u64> {
654 let result = self.simulate(account, payload).await?;
655 if result.success() {
656 Ok(result.safe_gas_estimate())
657 } else {
658 Err(AptosError::SimulationFailed(
659 result
660 .error_message()
661 .unwrap_or_else(|| result.vm_status().to_string()),
662 ))
663 }
664 }
665
666 #[cfg(feature = "ed25519")]
684 pub async fn simulate_and_submit<A: Account>(
685 &self,
686 account: &A,
687 payload: TransactionPayload,
688 ) -> AptosResult<AptosResponse<PendingTransaction>> {
689 let raw_txn = self.build_transaction(account, payload).await?;
692 let sim_auth = build_zero_signed_authenticator(account)?;
693 let sim_signed = SignedTransaction::new(raw_txn.clone(), sim_auth);
694 let sim_response = self.fullnode.simulate_transaction(&sim_signed).await?;
695 let sim_result =
696 crate::transaction::SimulationResult::from_response(sim_response.into_inner())?;
697
698 if sim_result.failed() {
699 return Err(AptosError::SimulationFailed(
700 sim_result
701 .error_message()
702 .unwrap_or_else(|| sim_result.vm_status().to_string()),
703 ));
704 }
705
706 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
707 self.fullnode.submit_transaction(&signed).await
708 }
709
710 #[cfg(feature = "ed25519")]
721 pub async fn simulate_submit_and_wait<A: Account>(
722 &self,
723 account: &A,
724 payload: TransactionPayload,
725 timeout: Option<Duration>,
726 ) -> AptosResult<AptosResponse<serde_json::Value>> {
727 let raw_txn = self.build_transaction(account, payload).await?;
730 let sim_auth = build_zero_signed_authenticator(account)?;
731 let sim_signed = SignedTransaction::new(raw_txn.clone(), sim_auth);
732 let sim_response = self.fullnode.simulate_transaction(&sim_signed).await?;
733 let sim_result =
734 crate::transaction::SimulationResult::from_response(sim_response.into_inner())?;
735
736 if sim_result.failed() {
737 return Err(AptosError::SimulationFailed(
738 sim_result
739 .error_message()
740 .unwrap_or_else(|| sim_result.vm_status().to_string()),
741 ));
742 }
743
744 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
745 self.fullnode.submit_and_wait(&signed, timeout).await
746 }
747
748 #[cfg(feature = "ed25519")]
758 pub async fn transfer_apt<A: Account>(
759 &self,
760 sender: &A,
761 recipient: AccountAddress,
762 amount: u64,
763 ) -> AptosResult<AptosResponse<serde_json::Value>> {
764 let payload = EntryFunction::apt_transfer(recipient, amount)?;
765 self.sign_submit_and_wait(sender, payload.into(), None)
766 .await
767 }
768
769 #[cfg(feature = "ed25519")]
777 pub async fn transfer_coin<A: Account>(
778 &self,
779 sender: &A,
780 recipient: AccountAddress,
781 coin_type: TypeTag,
782 amount: u64,
783 ) -> AptosResult<AptosResponse<serde_json::Value>> {
784 let payload = EntryFunction::coin_transfer(coin_type, recipient, amount)?;
785 self.sign_submit_and_wait(sender, payload.into(), None)
786 .await
787 }
788
789 #[cfg(feature = "ed25519")]
804 pub async fn transfer_fungible_asset<A: Account>(
805 &self,
806 sender: &A,
807 metadata: AccountAddress,
808 recipient: AccountAddress,
809 amount: u64,
810 ) -> AptosResult<AptosResponse<serde_json::Value>> {
811 let payload = crate::transaction::InputEntryFunctionData::transfer_fungible_asset(
812 metadata, recipient, amount,
813 )?;
814 self.sign_submit_and_wait(sender, payload, None).await
815 }
816
817 #[cfg(feature = "ed25519")]
827 pub async fn transfer_object<A: Account>(
828 &self,
829 owner: &A,
830 object: AccountAddress,
831 to: AccountAddress,
832 ) -> AptosResult<AptosResponse<serde_json::Value>> {
833 let payload = crate::transaction::InputEntryFunctionData::transfer_object(object, to)?;
834 self.sign_submit_and_wait(owner, payload, None).await
835 }
836
837 #[cfg(feature = "ed25519")]
846 pub async fn transfer_digital_asset<A: Account>(
847 &self,
848 owner: &A,
849 token: AccountAddress,
850 to: AccountAddress,
851 ) -> AptosResult<AptosResponse<serde_json::Value>> {
852 let payload =
853 crate::transaction::InputEntryFunctionData::transfer_digital_asset(token, to)?;
854 self.sign_submit_and_wait(owner, payload, None).await
855 }
856
857 #[cfg(feature = "ed25519")]
877 pub async fn rotate_auth_key<A: Account, B: Account>(
878 &self,
879 current: &A,
880 new_account: &B,
881 timeout: Option<Duration>,
882 ) -> AptosResult<AptosResponse<serde_json::Value>> {
883 let sequence_number = self.get_sequence_number(current.address()).await?;
886 let payload =
887 crate::account::build_rotate_auth_key_payload(current, new_account, sequence_number)?;
888
889 let (gas_estimation, chain_id) =
890 tokio::join!(self.fullnode.estimate_gas_price(), self.ensure_chain_id());
891 let gas_estimation = gas_estimation?;
892 let chain_id = chain_id?;
893
894 let raw_txn = TransactionBuilder::new()
895 .sender(current.address())
896 .sequence_number(sequence_number)
897 .payload(payload)
898 .gas_unit_price(gas_estimation.data.recommended())
899 .chain_id(chain_id)
900 .expiration_from_now(600)
901 .build()?;
902 let signed = crate::transaction::builder::sign_transaction(&raw_txn, current)?;
903 self.fullnode.submit_and_wait(&signed, timeout).await
904 }
905
906 pub async fn get_table_item(
920 &self,
921 handle: &str,
922 key_type: &str,
923 value_type: &str,
924 key: serde_json::Value,
925 ) -> AptosResult<serde_json::Value> {
926 let response = self
927 .fullnode
928 .get_table_item(handle, key_type, value_type, key)
929 .await?;
930 Ok(response.into_inner())
931 }
932
933 pub async fn view(
944 &self,
945 function: &str,
946 type_args: Vec<String>,
947 args: Vec<serde_json::Value>,
948 ) -> AptosResult<Vec<serde_json::Value>> {
949 let response = self.fullnode.view(function, type_args, args).await?;
950 Ok(response.into_inner())
951 }
952
953 pub async fn view_bcs<T: serde::de::DeserializeOwned>(
993 &self,
994 function: &str,
995 type_args: Vec<String>,
996 args: Vec<Vec<u8>>,
997 ) -> AptosResult<T> {
998 let response = self.fullnode.view_bcs(function, type_args, args).await?;
999 let bytes = response.into_inner();
1000 aptos_bcs::from_bytes(&bytes).map_err(|e| AptosError::Bcs(e.to_string()))
1001 }
1002
1003 pub async fn view_bcs_raw(
1012 &self,
1013 function: &str,
1014 type_args: Vec<String>,
1015 args: Vec<Vec<u8>>,
1016 ) -> AptosResult<Vec<u8>> {
1017 let response = self.fullnode.view_bcs(function, type_args, args).await?;
1018 Ok(response.into_inner())
1019 }
1020
1021 #[cfg(feature = "faucet")]
1041 pub async fn fund_account(
1042 &self,
1043 address: AccountAddress,
1044 amount: u64,
1045 ) -> AptosResult<Vec<String>> {
1046 const MAX_FAUCET_ATTEMPTS: u32 = 16;
1050
1051 let faucet = self
1052 .faucet
1053 .as_ref()
1054 .ok_or_else(|| AptosError::FeatureNotEnabled("faucet".into()))?;
1055
1056 let starting_balance = self.get_balance(address).await.unwrap_or(0);
1058 let target_balance = starting_balance.saturating_add(amount);
1059
1060 let mut all_hashes: Vec<String> = Vec::new();
1061 let mut current_balance = starting_balance;
1062 let mut attempts = 0u32;
1063
1064 while current_balance < target_balance && attempts < MAX_FAUCET_ATTEMPTS {
1065 attempts += 1;
1066 let still_needed = target_balance.saturating_sub(current_balance);
1067 let txn_hashes = faucet.fund(address, still_needed).await?;
1068
1069 let hashes: Vec<HashValue> = txn_hashes
1071 .iter()
1072 .filter_map(|hash_str| {
1073 let hash_str_clean = hash_str.strip_prefix("0x").unwrap_or(hash_str);
1074 HashValue::from_hex(hash_str_clean).ok()
1075 })
1076 .collect();
1077
1078 let wait_futures: Vec<_> = hashes
1080 .iter()
1081 .map(|hash| {
1082 self.fullnode
1083 .wait_for_transaction(hash, Some(Duration::from_mins(1)))
1084 })
1085 .collect();
1086 let results = futures::future::join_all(wait_futures).await;
1087 for result in results {
1088 result?;
1089 }
1090
1091 all_hashes.extend(txn_hashes);
1092
1093 let new_balance = self.get_balance(address).await.unwrap_or(current_balance);
1095 if new_balance <= current_balance {
1096 return Err(AptosError::api(
1097 400,
1098 format!(
1099 "faucet returned successful response but balance did not increase (\
1100 attempts={attempts}, balance={new_balance}, requested top-up={amount})"
1101 ),
1102 ));
1103 }
1104 current_balance = new_balance;
1105 }
1106
1107 if current_balance < target_balance {
1108 return Err(AptosError::api(
1109 429,
1110 format!(
1111 "faucet could not deliver {amount} octas in {attempts} attempts \
1112 (starting balance={starting_balance}, current balance={current_balance})"
1113 ),
1114 ));
1115 }
1116
1117 Ok(all_hashes)
1118 }
1119
1120 #[cfg(all(feature = "faucet", feature = "ed25519"))]
1121 pub async fn create_funded_account(
1127 &self,
1128 amount: u64,
1129 ) -> AptosResult<crate::account::Ed25519Account> {
1130 let account = crate::account::Ed25519Account::generate();
1131 self.fund_account(account.address(), amount).await?;
1132 Ok(account)
1133 }
1134
1135 pub fn batch(&self) -> crate::transaction::BatchOperations<'_> {
1154 crate::transaction::BatchOperations::new(&self.fullnode, &self.chain_id)
1155 }
1156
1157 #[cfg(feature = "ed25519")]
1176 pub async fn submit_batch<A: Account>(
1177 &self,
1178 account: &A,
1179 payloads: Vec<TransactionPayload>,
1180 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
1181 self.batch().submit(account, payloads).await
1182 }
1183
1184 #[cfg(feature = "ed25519")]
1201 pub async fn submit_batch_and_wait<A: Account>(
1202 &self,
1203 account: &A,
1204 payloads: Vec<TransactionPayload>,
1205 timeout: Option<Duration>,
1206 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
1207 self.batch()
1208 .submit_and_wait(account, payloads, timeout)
1209 .await
1210 }
1211
1212 #[cfg(feature = "ed25519")]
1234 pub async fn batch_transfer_apt<A: Account>(
1235 &self,
1236 sender: &A,
1237 transfers: Vec<(AccountAddress, u64)>,
1238 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
1239 self.batch().transfer_apt(sender, transfers).await
1240 }
1241}
1242
1243#[cfg(feature = "ed25519")]
1252fn build_zero_signed_authenticator<A: Account>(
1274 account: &A,
1275) -> AptosResult<crate::transaction::TransactionAuthenticator> {
1276 use crate::crypto::{
1277 ED25519_SCHEME, MULTI_ED25519_SCHEME, MULTI_KEY_SCHEME, SINGLE_KEY_SCHEME,
1278 };
1279 use crate::transaction::TransactionAuthenticator;
1280 use crate::transaction::authenticator::{
1281 AccountAuthenticator, Ed25519PublicKey, Ed25519Signature,
1282 };
1283
1284 let pubkey_bytes = account.public_key_bytes();
1285 let scheme = account.signature_scheme();
1286
1287 match scheme {
1288 s if s == ED25519_SCHEME => {
1291 let pubkey_arr: [u8; 32] = pubkey_bytes.as_slice().try_into().map_err(|_| {
1292 crate::error::AptosError::transaction(
1293 "simulate(): Ed25519 account exposed a non-32-byte public key",
1294 )
1295 })?;
1296 Ok(TransactionAuthenticator::Ed25519 {
1297 public_key: Ed25519PublicKey(pubkey_arr),
1298 signature: Ed25519Signature([0u8; 64]),
1299 })
1300 }
1301
1302 s if s == MULTI_ED25519_SCHEME => {
1307 const PK_LEN: usize = 32;
1308 const SIG_LEN: usize = 64;
1309 if pubkey_bytes.is_empty() || !(pubkey_bytes.len() - 1).is_multiple_of(PK_LEN) {
1310 return Err(crate::error::AptosError::transaction(
1311 "simulate(): MultiEd25519 public_key_bytes has invalid length",
1312 ));
1313 }
1314 let threshold = *pubkey_bytes.last().unwrap() as usize;
1315 if threshold == 0 {
1316 return Err(crate::error::AptosError::transaction(
1317 "simulate(): MultiEd25519 threshold cannot be zero",
1318 ));
1319 }
1320 let mut bitmap = [0u8; 4];
1322 for i in 0..threshold {
1323 let byte = i / 8;
1324 let bit = i % 8;
1325 bitmap[byte] |= 0b1000_0000_u8 >> bit;
1326 }
1327 let mut signature = Vec::with_capacity(threshold * SIG_LEN + 4);
1328 signature.extend(std::iter::repeat_n(0u8, threshold * SIG_LEN));
1329 signature.extend_from_slice(&bitmap);
1330 Ok(TransactionAuthenticator::MultiEd25519 {
1331 public_key: pubkey_bytes,
1332 signature,
1333 })
1334 }
1335
1336 s if s == SINGLE_KEY_SCHEME => {
1340 let zero_sig = zero_any_signature_for_pubkey(&pubkey_bytes).ok_or_else(|| {
1341 crate::error::AptosError::transaction(
1342 "simulate(): unsupported AnyPublicKey variant in SingleKey account",
1343 )
1344 })?;
1345 Ok(TransactionAuthenticator::single_sender(
1346 AccountAuthenticator::single_key(pubkey_bytes, zero_sig),
1347 ))
1348 }
1349
1350 s if s == MULTI_KEY_SCHEME => {
1355 let (variants, threshold) = parse_multi_key_pubkey(&pubkey_bytes)?;
1356 if threshold == 0 || (threshold as usize) > variants.len() {
1357 return Err(crate::error::AptosError::transaction(
1358 "simulate(): invalid MultiKey threshold",
1359 ));
1360 }
1361 let mut sig_bytes = Vec::with_capacity(1 + threshold as usize * 66 + 1 + 4);
1362 sig_bytes.push(threshold); for variant in variants.iter().take(threshold as usize) {
1364 let zero_sig = zero_any_signature_for_variant(*variant).ok_or_else(|| {
1365 crate::error::AptosError::transaction(
1366 "simulate(): unsupported AnyPublicKey variant in MultiKey account",
1367 )
1368 })?;
1369 sig_bytes.extend_from_slice(&zero_sig);
1370 }
1371 sig_bytes.push(4);
1373 let mut bitmap = [0u8; 4];
1374 for i in 0..threshold as usize {
1375 let byte = i / 8;
1376 let bit = i % 8;
1377 bitmap[byte] |= 0b1000_0000_u8 >> bit;
1378 }
1379 sig_bytes.extend_from_slice(&bitmap);
1380 Ok(TransactionAuthenticator::single_sender(
1381 AccountAuthenticator::multi_key(pubkey_bytes, sig_bytes),
1382 ))
1383 }
1384
1385 _ => Err(crate::error::AptosError::transaction(format!(
1386 "simulate(): unsupported signature scheme {scheme}; \
1387 use simulate_signed() with a hand-built zero-signed transaction"
1388 ))),
1389 }
1390}
1391
1392#[cfg(feature = "ed25519")]
1396fn zero_any_signature_for_pubkey(any_public_key_bcs: &[u8]) -> Option<Vec<u8>> {
1397 let variant = *any_public_key_bcs.first()?;
1398 zero_any_signature_for_variant(variant)
1399}
1400
1401#[cfg(feature = "ed25519")]
1403fn zero_any_signature_for_variant(variant: u8) -> Option<Vec<u8>> {
1404 match variant {
1413 0..=2 => {
1414 let mut out = Vec::with_capacity(1 + 1 + 64);
1415 out.push(variant);
1416 out.push(64);
1417 out.extend(std::iter::repeat_n(0u8, 64));
1418 Some(out)
1419 }
1420 _ => None,
1421 }
1422}
1423
1424#[cfg(feature = "ed25519")]
1428fn parse_multi_key_pubkey(bytes: &[u8]) -> AptosResult<(Vec<u8>, u8)> {
1429 if bytes.is_empty() {
1430 return Err(crate::error::AptosError::transaction(
1431 "simulate(): MultiKey public_key_bytes is empty",
1432 ));
1433 }
1434 let num_keys = bytes[0] as usize;
1435 let mut offset = 1;
1436 let mut variants = Vec::with_capacity(num_keys);
1437 for _ in 0..num_keys {
1438 if offset >= bytes.len() {
1439 return Err(crate::error::AptosError::transaction(
1440 "simulate(): MultiKey public_key truncated at variant tag",
1441 ));
1442 }
1443 let variant = bytes[offset];
1444 variants.push(variant);
1445 offset += 1;
1446 let (len, len_bytes) = decode_uleb128_internal(&bytes[offset..])?;
1448 offset += len_bytes;
1449 offset = offset.checked_add(len).ok_or_else(|| {
1450 crate::error::AptosError::transaction("simulate(): MultiKey public_key overflow")
1451 })?;
1452 if offset > bytes.len() {
1453 return Err(crate::error::AptosError::transaction(
1454 "simulate(): MultiKey public_key truncated at key bytes",
1455 ));
1456 }
1457 }
1458 if offset >= bytes.len() {
1459 return Err(crate::error::AptosError::transaction(
1460 "simulate(): MultiKey public_key missing threshold byte",
1461 ));
1462 }
1463 let threshold = bytes[offset];
1464 Ok((variants, threshold))
1465}
1466
1467#[cfg(feature = "ed25519")]
1469fn decode_uleb128_internal(bytes: &[u8]) -> AptosResult<(usize, usize)> {
1470 let mut value: usize = 0;
1471 let mut shift = 0;
1472 for (i, &b) in bytes.iter().enumerate() {
1473 value |= ((b & 0x7F) as usize) << shift;
1474 if (b & 0x80) == 0 {
1475 return Ok((value, i + 1));
1476 }
1477 shift += 7;
1478 if shift >= 64 {
1479 break;
1480 }
1481 }
1482 Err(crate::error::AptosError::transaction(
1483 "simulate(): malformed ULEB128 in public key",
1484 ))
1485}
1486
1487#[cfg(test)]
1488mod tests {
1489 use super::*;
1490 use crate::transaction::authenticator::{
1491 Ed25519PublicKey, Ed25519Signature, TransactionAuthenticator,
1492 };
1493 use crate::transaction::payload::{EntryFunction, TransactionPayload};
1494 use crate::transaction::simulation::SimulateQueryOptions;
1495 use crate::transaction::types::{
1496 FeePayerRawTransaction, MultiAgentRawTransaction, RawTransaction, SignedTransaction,
1497 };
1498 use crate::types::ChainId;
1499 use wiremock::{
1500 Mock, MockServer, ResponseTemplate,
1501 matchers::{method, path, path_regex},
1502 };
1503
1504 #[test]
1505 fn test_aptos_client_creation() {
1506 let aptos = Aptos::testnet();
1507 assert!(aptos.is_ok());
1508 }
1509
1510 #[test]
1511 fn test_chain_id() {
1512 let aptos = Aptos::testnet().unwrap();
1513 assert_eq!(aptos.chain_id(), ChainId::testnet());
1514
1515 let aptos = Aptos::mainnet().unwrap();
1516 assert_eq!(aptos.chain_id(), ChainId::mainnet());
1517 }
1518
1519 #[test]
1520 fn test_ans_accessor() {
1521 let aptos = Aptos::mainnet().unwrap();
1524 assert!(aptos.ans().router_address().is_ok());
1525 }
1526
1527 fn create_mock_aptos(server: &MockServer) -> Aptos {
1528 let url = format!("{}/v1", server.uri());
1529 let config = AptosConfig::custom(&url).unwrap().without_retry();
1530 Aptos::new(config).unwrap()
1531 }
1532
1533 fn create_minimal_signed_transaction() -> SignedTransaction {
1534 let raw = RawTransaction::new(
1535 AccountAddress::ONE,
1536 0,
1537 TransactionPayload::EntryFunction(
1538 EntryFunction::apt_transfer(AccountAddress::ONE, 0).unwrap(),
1539 ),
1540 100_000,
1541 100,
1542 std::time::SystemTime::now()
1543 .duration_since(std::time::UNIX_EPOCH)
1544 .unwrap()
1545 .as_secs()
1546 .saturating_add(600),
1547 ChainId::testnet(),
1548 );
1549 SignedTransaction::new(
1550 raw,
1551 TransactionAuthenticator::Ed25519 {
1552 public_key: Ed25519PublicKey([0u8; 32]),
1553 signature: Ed25519Signature([0u8; 64]),
1554 },
1555 )
1556 }
1557
1558 fn simulate_response_json() -> serde_json::Value {
1559 serde_json::json!([{
1560 "success": true,
1561 "vm_status": "Executed successfully",
1562 "gas_used": "1500",
1563 "max_gas_amount": "200000",
1564 "gas_unit_price": "100",
1565 "hash": "0xabc",
1566 "changes": [],
1567 "events": []
1568 }])
1569 }
1570
1571 #[tokio::test]
1572 async fn test_get_sequence_number() {
1573 let server = MockServer::start().await;
1574
1575 Mock::given(method("GET"))
1576 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+"))
1577 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1578 "sequence_number": "42",
1579 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1580 })))
1581 .expect(1)
1582 .mount(&server)
1583 .await;
1584
1585 let aptos = create_mock_aptos(&server);
1586 let seq = aptos
1587 .get_sequence_number(AccountAddress::ONE)
1588 .await
1589 .unwrap();
1590 assert_eq!(seq, 42);
1591 }
1592
1593 #[tokio::test]
1594 async fn test_get_balance() {
1595 let server = MockServer::start().await;
1596
1597 Mock::given(method("POST"))
1599 .and(path("/v1/view"))
1600 .respond_with(
1601 ResponseTemplate::new(200).set_body_json(serde_json::json!(["5000000000"])),
1602 )
1603 .expect(1)
1604 .mount(&server)
1605 .await;
1606
1607 let aptos = create_mock_aptos(&server);
1608 let balance = aptos.get_balance(AccountAddress::ONE).await.unwrap();
1609 assert_eq!(balance, 5_000_000_000);
1610 }
1611
1612 #[tokio::test]
1613 async fn test_get_resources_via_fullnode() {
1614 let server = MockServer::start().await;
1615
1616 Mock::given(method("GET"))
1617 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+/resources"))
1618 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
1619 {"type": "0x1::account::Account", "data": {}},
1620 {"type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": {}}
1621 ])))
1622 .expect(1)
1623 .mount(&server)
1624 .await;
1625
1626 let aptos = create_mock_aptos(&server);
1627 let resources = aptos
1628 .fullnode()
1629 .get_account_resources(AccountAddress::ONE)
1630 .await
1631 .unwrap();
1632 assert_eq!(resources.data.len(), 2);
1633 }
1634
1635 #[tokio::test]
1636 async fn test_ledger_info() {
1637 let server = MockServer::start().await;
1638
1639 Mock::given(method("GET"))
1640 .and(path("/v1"))
1641 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1642 "chain_id": 2,
1643 "epoch": "100",
1644 "ledger_version": "12345",
1645 "oldest_ledger_version": "0",
1646 "ledger_timestamp": "1000000",
1647 "node_role": "full_node",
1648 "oldest_block_height": "0",
1649 "block_height": "5000"
1650 })))
1651 .expect(1)
1652 .mount(&server)
1653 .await;
1654
1655 let aptos = create_mock_aptos(&server);
1656 let info = aptos.ledger_info().await.unwrap();
1657 assert_eq!(info.version().unwrap(), 12345);
1658 }
1659
1660 #[tokio::test]
1661 async fn test_config_builder() {
1662 let config = AptosConfig::testnet().with_timeout(Duration::from_mins(1));
1663
1664 let aptos = Aptos::new(config).unwrap();
1665 assert_eq!(aptos.chain_id(), ChainId::testnet());
1666 }
1667
1668 #[tokio::test]
1669 async fn test_fullnode_accessor() {
1670 let server = MockServer::start().await;
1671 let aptos = create_mock_aptos(&server);
1672
1673 let fullnode = aptos.fullnode();
1675 assert!(fullnode.base_url().as_str().contains(&server.uri()));
1676 }
1677
1678 #[cfg(feature = "ed25519")]
1679 #[tokio::test]
1680 async fn test_build_transaction() {
1681 let server = MockServer::start().await;
1682
1683 Mock::given(method("GET"))
1685 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+"))
1686 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1687 "sequence_number": "0",
1688 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1689 })))
1690 .expect(1)
1691 .mount(&server)
1692 .await;
1693
1694 Mock::given(method("GET"))
1696 .and(path("/v1/estimate_gas_price"))
1697 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1698 "gas_estimate": 100
1699 })))
1700 .expect(1)
1701 .mount(&server)
1702 .await;
1703
1704 Mock::given(method("GET"))
1706 .and(path("/v1"))
1707 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1708 "chain_id": 4,
1709 "epoch": "1",
1710 "ledger_version": "100",
1711 "oldest_ledger_version": "0",
1712 "ledger_timestamp": "1000000",
1713 "node_role": "full_node",
1714 "oldest_block_height": "0",
1715 "block_height": "50"
1716 })))
1717 .expect(1)
1718 .mount(&server)
1719 .await;
1720
1721 let aptos = create_mock_aptos(&server);
1722 let account = crate::account::Ed25519Account::generate();
1723 let recipient = AccountAddress::from_hex("0x123").unwrap();
1724 let payload = crate::transaction::EntryFunction::apt_transfer(recipient, 1000).unwrap();
1725
1726 let raw_txn = aptos
1727 .build_transaction(&account, payload.into())
1728 .await
1729 .unwrap();
1730 assert_eq!(raw_txn.sender, account.address());
1731 assert_eq!(raw_txn.sequence_number, 0);
1732 }
1733
1734 #[cfg(feature = "indexer")]
1735 #[tokio::test]
1736 async fn test_indexer_accessor() {
1737 let aptos = Aptos::testnet().unwrap();
1738 let indexer = aptos.indexer();
1739 assert!(indexer.is_some());
1740 }
1741
1742 #[tokio::test]
1743 async fn test_account_exists_true() {
1744 let server = MockServer::start().await;
1745
1746 Mock::given(method("GET"))
1747 .and(path_regex(r"^/v1/accounts/0x[0-9a-f]+$"))
1748 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1749 "sequence_number": "10",
1750 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1751 })))
1752 .expect(1)
1753 .mount(&server)
1754 .await;
1755
1756 let aptos = create_mock_aptos(&server);
1757 let exists = aptos.account_exists(AccountAddress::ONE).await.unwrap();
1758 assert!(exists);
1759 }
1760
1761 #[tokio::test]
1762 async fn test_account_exists_false() {
1763 let server = MockServer::start().await;
1764
1765 Mock::given(method("GET"))
1766 .and(path_regex(r"^/v1/accounts/0x[0-9a-f]+$"))
1767 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
1768 "message": "Account not found",
1769 "error_code": "account_not_found"
1770 })))
1771 .expect(1)
1772 .mount(&server)
1773 .await;
1774
1775 let aptos = create_mock_aptos(&server);
1776 let exists = aptos.account_exists(AccountAddress::ONE).await.unwrap();
1777 assert!(!exists);
1778 }
1779
1780 #[tokio::test]
1781 async fn test_view_function() {
1782 let server = MockServer::start().await;
1783
1784 Mock::given(method("POST"))
1785 .and(path("/v1/view"))
1786 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!(["1000000"])))
1787 .expect(1)
1788 .mount(&server)
1789 .await;
1790
1791 let aptos = create_mock_aptos(&server);
1792 let result: Vec<serde_json::Value> = aptos
1793 .view(
1794 "0x1::coin::balance",
1795 vec!["0x1::aptos_coin::AptosCoin".to_string()],
1796 vec![serde_json::json!("0x1")],
1797 )
1798 .await
1799 .unwrap();
1800
1801 assert_eq!(result.len(), 1);
1802 assert_eq!(result[0].as_str().unwrap(), "1000000");
1803 }
1804
1805 #[tokio::test]
1806 async fn test_chain_id_from_config() {
1807 let aptos = Aptos::mainnet().unwrap();
1808 assert_eq!(aptos.chain_id(), ChainId::mainnet());
1809
1810 let aptos = Aptos::devnet().unwrap();
1816 assert_eq!(aptos.chain_id(), ChainId::new(0));
1817 }
1818
1819 #[tokio::test]
1820 async fn test_custom_config() {
1821 let server = MockServer::start().await;
1822 let url = format!("{}/v1", server.uri());
1823 let config = AptosConfig::custom(&url).unwrap();
1824 let aptos = Aptos::new(config).unwrap();
1825
1826 assert_eq!(aptos.chain_id(), ChainId::new(0));
1828 }
1829
1830 #[cfg(feature = "ed25519")]
1839 #[test]
1840 fn test_zero_signed_authenticator_ed25519() {
1841 use crate::account::Ed25519Account;
1842 use crate::transaction::TransactionAuthenticator;
1843
1844 let account = Ed25519Account::generate();
1845 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1846 match auth {
1847 TransactionAuthenticator::Ed25519 {
1848 public_key,
1849 signature,
1850 } => {
1851 assert_eq!(public_key.0, account.public_key().to_bytes());
1852 assert_eq!(signature.0, [0u8; 64]);
1853 }
1854 other => panic!("expected TransactionAuthenticator::Ed25519, got {other:?}"),
1855 }
1856 }
1857
1858 #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
1859 #[test]
1860 fn test_zero_signed_authenticator_single_key_secp256k1() {
1861 use crate::account::Secp256k1Account;
1862 use crate::transaction::TransactionAuthenticator;
1863 use crate::transaction::authenticator::AccountAuthenticator;
1864
1865 let account = Secp256k1Account::generate();
1866 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1867 let TransactionAuthenticator::SingleSender { sender } = auth else {
1870 panic!("expected SingleSender, got {auth:?}");
1871 };
1872 let AccountAuthenticator::SingleKey {
1873 public_key,
1874 signature,
1875 } = sender
1876 else {
1877 panic!("expected AccountAuthenticator::SingleKey");
1878 };
1879 assert_eq!(public_key, account.public_key_bytes());
1881 assert_eq!(signature.len(), 1 + 1 + 64);
1884 assert_eq!(signature[0], 0x01, "variant tag must match secp256k1");
1885 assert_eq!(signature[1], 64, "ULEB128(64)");
1886 assert!(signature[2..].iter().all(|b| *b == 0), "all-zero signature");
1887 }
1888
1889 #[cfg(feature = "ed25519")]
1890 #[test]
1891 fn test_zero_signed_authenticator_single_key_ed25519() {
1892 use crate::account::Ed25519SingleKeyAccount;
1893 use crate::transaction::TransactionAuthenticator;
1894 use crate::transaction::authenticator::AccountAuthenticator;
1895
1896 let account = Ed25519SingleKeyAccount::generate();
1901 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1902 let TransactionAuthenticator::SingleSender { sender } = auth else {
1903 panic!("expected SingleSender for Ed25519SingleKey, got {auth:?}");
1904 };
1905 let AccountAuthenticator::SingleKey {
1906 public_key,
1907 signature,
1908 } = sender
1909 else {
1910 panic!("expected AccountAuthenticator::SingleKey");
1911 };
1912 assert_eq!(public_key, account.public_key_bytes());
1913 assert_eq!(signature[0], 0x00, "AnySignature::Ed25519 variant");
1914 assert_eq!(signature[1], 64, "ULEB128(64)");
1915 assert!(signature[2..].iter().all(|b| *b == 0));
1916 }
1917
1918 #[cfg(feature = "ed25519")]
1919 #[test]
1920 fn test_zero_signed_authenticator_multi_ed25519() {
1921 use crate::account::MultiEd25519Account;
1922 use crate::crypto::Ed25519PrivateKey;
1923 use crate::transaction::TransactionAuthenticator;
1924
1925 let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
1927 let account = MultiEd25519Account::new(keys, 2).unwrap();
1928 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1929 let TransactionAuthenticator::MultiEd25519 {
1930 public_key,
1931 signature,
1932 } = auth
1933 else {
1934 panic!("expected MultiEd25519, got {auth:?}");
1935 };
1936 assert_eq!(public_key, account.public_key_bytes());
1937 assert_eq!(signature.len(), 2 * 64 + 4);
1939 assert!(signature[..128].iter().all(|b| *b == 0));
1940 assert_eq!(signature[128], 0b1100_0000, "bits 0 and 1 set (MSB-first)");
1941 assert_eq!(&signature[129..], &[0u8, 0u8, 0u8]);
1942 }
1943
1944 #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
1945 #[test]
1946 fn test_zero_signed_authenticator_multi_key() {
1947 use crate::account::{AnyPrivateKey, MultiKeyAccount};
1948 use crate::crypto::{Ed25519PrivateKey, Secp256k1PrivateKey};
1949 use crate::transaction::TransactionAuthenticator;
1950 use crate::transaction::authenticator::AccountAuthenticator;
1951
1952 let keys = vec![
1953 AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
1954 AnyPrivateKey::secp256k1(Secp256k1PrivateKey::generate()),
1955 AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
1956 ];
1957 let account = MultiKeyAccount::new(keys, 2).unwrap();
1958 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1959 let TransactionAuthenticator::SingleSender { sender } = auth else {
1960 panic!("expected SingleSender for MultiKey, got {auth:?}");
1961 };
1962 let AccountAuthenticator::MultiKey {
1963 public_key,
1964 signature,
1965 } = sender
1966 else {
1967 panic!("expected AccountAuthenticator::MultiKey");
1968 };
1969 assert_eq!(public_key, account.public_key_bytes());
1970 assert_eq!(signature[0], 2, "num_sigs ULEB128");
1976 assert_eq!(signature[1], 0x00, "first AnySignature variant (Ed25519)");
1977 assert_eq!(
1978 signature[1 + 1 + 1 + 64],
1979 0x01,
1980 "second AnySignature variant (Secp256k1)"
1981 );
1982 }
1983
1984 #[tokio::test]
1985 async fn test_simulate_signed_with_options() {
1986 let server = MockServer::start().await;
1987
1988 Mock::given(method("POST"))
1989 .and(path("/v1/transactions/simulate"))
1990 .and(|req: &wiremock::Request| {
1991 req.url
1992 .query()
1993 .is_some_and(|q| q.contains("estimate_gas_unit_price=true"))
1994 })
1995 .respond_with(
1996 ResponseTemplate::new(200).set_body_json(serde_json::json!([{
1997 "success": true,
1998 "vm_status": "Executed successfully",
1999 "gas_used": "1500",
2000 "max_gas_amount": "200000",
2001 "gas_unit_price": "100",
2002 "hash": "0xabc",
2003 "changes": [],
2004 "events": []
2005 }])),
2006 )
2007 .expect(1)
2008 .mount(&server)
2009 .await;
2010
2011 let raw = RawTransaction::new(
2012 AccountAddress::ONE,
2013 0,
2014 TransactionPayload::EntryFunction(
2015 EntryFunction::apt_transfer(AccountAddress::ONE, 0).unwrap(),
2016 ),
2017 100_000,
2018 100,
2019 std::time::SystemTime::now()
2020 .duration_since(std::time::UNIX_EPOCH)
2021 .unwrap()
2022 .as_secs()
2023 .saturating_add(600),
2024 ChainId::testnet(),
2025 );
2026 let signed = SignedTransaction::new(
2027 raw,
2028 TransactionAuthenticator::Ed25519 {
2029 public_key: Ed25519PublicKey([0u8; 32]),
2030 signature: Ed25519Signature([0u8; 64]),
2031 },
2032 );
2033
2034 let aptos = create_mock_aptos(&server);
2035 let options = SimulateQueryOptions::new().estimate_gas_unit_price(true);
2036 let result = aptos
2037 .simulate_signed_with_options(&signed, options)
2038 .await
2039 .unwrap();
2040
2041 assert!(result.success());
2042 assert_eq!(result.gas_used(), 1500);
2043 assert_eq!(result.gas_unit_price(), 100);
2044 }
2045
2046 #[tokio::test]
2047 async fn test_simulate_signed_without_options() {
2048 let server = MockServer::start().await;
2049
2050 Mock::given(method("POST"))
2051 .and(path("/v1/transactions/simulate"))
2052 .and(|req: &wiremock::Request| {
2053 req.url.query().is_none_or(|q| {
2054 !q.contains("estimate_gas_unit_price=")
2055 && !q.contains("estimate_max_gas_amount=")
2056 && !q.contains("estimate_prioritized_gas_unit_price=")
2057 })
2058 })
2059 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2060 .expect(1)
2061 .mount(&server)
2062 .await;
2063
2064 let aptos = create_mock_aptos(&server);
2065 let signed = create_minimal_signed_transaction();
2066 let result = aptos.simulate_signed(&signed).await.unwrap();
2067 assert!(result.success());
2068 }
2069
2070 #[tokio::test]
2071 async fn test_simulate_multi_agent_without_options() {
2072 let server = MockServer::start().await;
2073
2074 Mock::given(method("POST"))
2075 .and(path("/v1/transactions/simulate"))
2076 .and(|req: &wiremock::Request| {
2077 req.url.query().is_none_or(|q| {
2078 !q.contains("estimate_gas_unit_price=")
2079 && !q.contains("estimate_max_gas_amount=")
2080 && !q.contains("estimate_prioritized_gas_unit_price=")
2081 })
2082 })
2083 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2084 .expect(1)
2085 .mount(&server)
2086 .await;
2087
2088 let aptos = create_mock_aptos(&server);
2089 let multi_agent = MultiAgentRawTransaction::new(
2090 create_minimal_signed_transaction().raw_txn,
2091 vec![AccountAddress::from_hex("0x2").unwrap()],
2092 );
2093 let result = aptos
2094 .simulate_multi_agent(&multi_agent, None)
2095 .await
2096 .unwrap();
2097 assert!(result.success());
2098 }
2099
2100 #[tokio::test]
2101 async fn test_simulate_multi_agent_with_options() {
2102 let server = MockServer::start().await;
2103
2104 Mock::given(method("POST"))
2105 .and(path("/v1/transactions/simulate"))
2106 .and(|req: &wiremock::Request| {
2107 req.url
2108 .query()
2109 .is_some_and(|q| q.contains("estimate_max_gas_amount=true"))
2110 })
2111 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2112 .expect(1)
2113 .mount(&server)
2114 .await;
2115
2116 let aptos = create_mock_aptos(&server);
2117 let multi_agent = MultiAgentRawTransaction::new(
2118 create_minimal_signed_transaction().raw_txn,
2119 vec![AccountAddress::from_hex("0x2").unwrap()],
2120 );
2121 let options = SimulateQueryOptions::new().estimate_max_gas_amount(true);
2122 let result = aptos
2123 .simulate_multi_agent(&multi_agent, Some(options))
2124 .await
2125 .unwrap();
2126 assert!(result.success());
2127 }
2128
2129 #[tokio::test]
2130 async fn test_simulate_fee_payer_without_options() {
2131 let server = MockServer::start().await;
2132
2133 Mock::given(method("POST"))
2134 .and(path("/v1/transactions/simulate"))
2135 .and(|req: &wiremock::Request| {
2136 req.url.query().is_none_or(|q| {
2137 !q.contains("estimate_gas_unit_price=")
2138 && !q.contains("estimate_max_gas_amount=")
2139 && !q.contains("estimate_prioritized_gas_unit_price=")
2140 })
2141 })
2142 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2143 .expect(1)
2144 .mount(&server)
2145 .await;
2146
2147 let aptos = create_mock_aptos(&server);
2148 let fee_payer_txn = FeePayerRawTransaction::new_simple(
2149 create_minimal_signed_transaction().raw_txn,
2150 AccountAddress::THREE,
2151 );
2152 let result = aptos
2153 .simulate_fee_payer(&fee_payer_txn, None)
2154 .await
2155 .unwrap();
2156 assert!(result.success());
2157 }
2158
2159 #[tokio::test]
2160 async fn test_simulate_fee_payer_with_options() {
2161 let server = MockServer::start().await;
2162
2163 Mock::given(method("POST"))
2164 .and(path("/v1/transactions/simulate"))
2165 .and(|req: &wiremock::Request| {
2166 req.url
2167 .query()
2168 .is_some_and(|q| q.contains("estimate_prioritized_gas_unit_price=true"))
2169 })
2170 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
2171 .expect(1)
2172 .mount(&server)
2173 .await;
2174
2175 let aptos = create_mock_aptos(&server);
2176 let fee_payer_txn = FeePayerRawTransaction::new_simple(
2177 create_minimal_signed_transaction().raw_txn,
2178 AccountAddress::THREE,
2179 );
2180 let options = SimulateQueryOptions::new().estimate_prioritized_gas_unit_price(true);
2181 let result = aptos
2182 .simulate_fee_payer(&fee_payer_txn, options)
2183 .await
2184 .unwrap();
2185 assert!(result.success());
2186 }
2187}