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 async fn ledger_info(&self) -> AptosResult<crate::api::response::LedgerInfo> {
166 let response = self.fullnode.get_ledger_info().await?;
167 let info = response.into_inner();
168
169 if self.chain_id.load(Ordering::Relaxed) == 0 && info.chain_id > 0 {
175 self.chain_id.store(info.chain_id, Ordering::Relaxed);
176 }
177
178 Ok(info)
179 }
180
181 pub fn chain_id(&self) -> ChainId {
190 ChainId::new(self.chain_id.load(Ordering::Relaxed))
191 }
192
193 pub async fn ensure_chain_id(&self) -> AptosResult<ChainId> {
209 let id = self.chain_id.load(Ordering::Relaxed);
210 if id > 0 {
211 return Ok(ChainId::new(id));
212 }
213 let response = self.fullnode.get_ledger_info().await?;
215 let info = response.into_inner();
216 self.chain_id.store(info.chain_id, Ordering::Relaxed);
217 Ok(ChainId::new(info.chain_id))
218 }
219
220 pub async fn get_sequence_number(&self, address: AccountAddress) -> AptosResult<u64> {
229 self.fullnode.get_sequence_number(address).await
230 }
231
232 pub async fn get_balance(&self, address: AccountAddress) -> AptosResult<u64> {
239 self.fullnode.get_account_balance(address).await
240 }
241
242 pub async fn account_exists(&self, address: AccountAddress) -> AptosResult<bool> {
249 match self.fullnode.get_account(address).await {
250 Ok(_) => Ok(true),
251 Err(AptosError::Api {
252 status_code: 404, ..
253 }) => Ok(false),
254 Err(e) => Err(e),
255 }
256 }
257
258 pub async fn build_transaction<A: Account>(
270 &self,
271 sender: &A,
272 payload: TransactionPayload,
273 ) -> AptosResult<RawTransaction> {
274 let (sequence_number, gas_estimation, chain_id) = tokio::join!(
276 self.get_sequence_number(sender.address()),
277 self.fullnode.estimate_gas_price(),
278 self.ensure_chain_id()
279 );
280 let sequence_number = sequence_number?;
281 let gas_estimation = gas_estimation?;
282 let chain_id = chain_id?;
283
284 TransactionBuilder::new()
285 .sender(sender.address())
286 .sequence_number(sequence_number)
287 .payload(payload)
288 .gas_unit_price(gas_estimation.data.recommended())
289 .chain_id(chain_id)
290 .expiration_from_now(600)
291 .build()
292 }
293
294 #[cfg(feature = "ed25519")]
302 pub async fn sign_and_submit<A: Account>(
303 &self,
304 account: &A,
305 payload: TransactionPayload,
306 ) -> AptosResult<AptosResponse<PendingTransaction>> {
307 let raw_txn = self.build_transaction(account, payload).await?;
308 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
309 self.fullnode.submit_transaction(&signed).await
310 }
311
312 #[cfg(feature = "ed25519")]
320 pub async fn sign_submit_and_wait<A: Account>(
321 &self,
322 account: &A,
323 payload: TransactionPayload,
324 timeout: Option<Duration>,
325 ) -> AptosResult<AptosResponse<serde_json::Value>> {
326 let raw_txn = self.build_transaction(account, payload).await?;
327 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
328 self.fullnode.submit_and_wait(&signed, timeout).await
329 }
330
331 pub async fn submit_transaction(
338 &self,
339 signed_txn: &SignedTransaction,
340 ) -> AptosResult<AptosResponse<PendingTransaction>> {
341 self.fullnode.submit_transaction(signed_txn).await
342 }
343
344 pub async fn submit_and_wait(
352 &self,
353 signed_txn: &SignedTransaction,
354 timeout: Option<Duration>,
355 ) -> AptosResult<AptosResponse<serde_json::Value>> {
356 self.fullnode.submit_and_wait(signed_txn, timeout).await
357 }
358
359 pub async fn simulate_transaction(
369 &self,
370 signed_txn: &SignedTransaction,
371 ) -> AptosResult<AptosResponse<Vec<serde_json::Value>>> {
372 self.fullnode.simulate_transaction(signed_txn).await
373 }
374
375 #[cfg(feature = "ed25519")]
396 pub async fn simulate<A: Account>(
397 &self,
398 account: &A,
399 payload: TransactionPayload,
400 ) -> AptosResult<crate::transaction::SimulationResult> {
401 use crate::transaction::SignedTransaction;
402
403 let raw_txn = self.build_transaction(account, payload).await?;
404
405 let auth = build_zero_signed_authenticator(account)?;
413 let signed = SignedTransaction::new(raw_txn, auth);
414
415 let response = self.fullnode.simulate_transaction(&signed).await?;
416 crate::transaction::SimulationResult::from_response(response.into_inner())
417 }
418
419 pub async fn simulate_signed(
433 &self,
434 signed_txn: &SignedTransaction,
435 ) -> AptosResult<SimulationResult> {
436 let response = self.fullnode.simulate_transaction(signed_txn).await?;
437 SimulationResult::from_response(response.into_inner())
438 }
439
440 pub async fn simulate_signed_with_options(
453 &self,
454 signed_txn: &SignedTransaction,
455 options: SimulateQueryOptions,
456 ) -> AptosResult<SimulationResult> {
457 let response = self
458 .fullnode
459 .simulate_transaction_with_options(signed_txn, Some(options))
460 .await?;
461 SimulationResult::from_response(response.into_inner())
462 }
463
464 pub async fn simulate_multi_agent(
485 &self,
486 multi_agent: &MultiAgentRawTransaction,
487 options: impl Into<Option<SimulateQueryOptions>>,
488 ) -> AptosResult<SimulationResult> {
489 let signed = build_simulation_signed_multi_agent(multi_agent);
490 match options.into() {
491 None => self.simulate_signed(&signed).await,
492 Some(opts) => self.simulate_signed_with_options(&signed, opts).await,
493 }
494 }
495
496 pub async fn simulate_fee_payer(
517 &self,
518 fee_payer_txn: &FeePayerRawTransaction,
519 options: impl Into<Option<SimulateQueryOptions>>,
520 ) -> AptosResult<SimulationResult> {
521 let signed = build_simulation_signed_fee_payer(fee_payer_txn);
522 match options.into() {
523 None => self.simulate_signed(&signed).await,
524 Some(opts) => self.simulate_signed_with_options(&signed, opts).await,
525 }
526 }
527
528 #[cfg(feature = "ed25519")]
544 pub async fn estimate_gas<A: Account>(
545 &self,
546 account: &A,
547 payload: TransactionPayload,
548 ) -> AptosResult<u64> {
549 let result = self.simulate(account, payload).await?;
550 if result.success() {
551 Ok(result.safe_gas_estimate())
552 } else {
553 Err(AptosError::SimulationFailed(
554 result
555 .error_message()
556 .unwrap_or_else(|| result.vm_status().to_string()),
557 ))
558 }
559 }
560
561 #[cfg(feature = "ed25519")]
579 pub async fn simulate_and_submit<A: Account>(
580 &self,
581 account: &A,
582 payload: TransactionPayload,
583 ) -> AptosResult<AptosResponse<PendingTransaction>> {
584 let raw_txn = self.build_transaction(account, payload).await?;
587 let sim_auth = build_zero_signed_authenticator(account)?;
588 let sim_signed = SignedTransaction::new(raw_txn.clone(), sim_auth);
589 let sim_response = self.fullnode.simulate_transaction(&sim_signed).await?;
590 let sim_result =
591 crate::transaction::SimulationResult::from_response(sim_response.into_inner())?;
592
593 if sim_result.failed() {
594 return Err(AptosError::SimulationFailed(
595 sim_result
596 .error_message()
597 .unwrap_or_else(|| sim_result.vm_status().to_string()),
598 ));
599 }
600
601 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
602 self.fullnode.submit_transaction(&signed).await
603 }
604
605 #[cfg(feature = "ed25519")]
616 pub async fn simulate_submit_and_wait<A: Account>(
617 &self,
618 account: &A,
619 payload: TransactionPayload,
620 timeout: Option<Duration>,
621 ) -> AptosResult<AptosResponse<serde_json::Value>> {
622 let raw_txn = self.build_transaction(account, payload).await?;
625 let sim_auth = build_zero_signed_authenticator(account)?;
626 let sim_signed = SignedTransaction::new(raw_txn.clone(), sim_auth);
627 let sim_response = self.fullnode.simulate_transaction(&sim_signed).await?;
628 let sim_result =
629 crate::transaction::SimulationResult::from_response(sim_response.into_inner())?;
630
631 if sim_result.failed() {
632 return Err(AptosError::SimulationFailed(
633 sim_result
634 .error_message()
635 .unwrap_or_else(|| sim_result.vm_status().to_string()),
636 ));
637 }
638
639 let signed = crate::transaction::builder::sign_transaction(&raw_txn, account)?;
640 self.fullnode.submit_and_wait(&signed, timeout).await
641 }
642
643 #[cfg(feature = "ed25519")]
653 pub async fn transfer_apt<A: Account>(
654 &self,
655 sender: &A,
656 recipient: AccountAddress,
657 amount: u64,
658 ) -> AptosResult<AptosResponse<serde_json::Value>> {
659 let payload = EntryFunction::apt_transfer(recipient, amount)?;
660 self.sign_submit_and_wait(sender, payload.into(), None)
661 .await
662 }
663
664 #[cfg(feature = "ed25519")]
672 pub async fn transfer_coin<A: Account>(
673 &self,
674 sender: &A,
675 recipient: AccountAddress,
676 coin_type: TypeTag,
677 amount: u64,
678 ) -> AptosResult<AptosResponse<serde_json::Value>> {
679 let payload = EntryFunction::coin_transfer(coin_type, recipient, amount)?;
680 self.sign_submit_and_wait(sender, payload.into(), None)
681 .await
682 }
683
684 pub async fn view(
695 &self,
696 function: &str,
697 type_args: Vec<String>,
698 args: Vec<serde_json::Value>,
699 ) -> AptosResult<Vec<serde_json::Value>> {
700 let response = self.fullnode.view(function, type_args, args).await?;
701 Ok(response.into_inner())
702 }
703
704 pub async fn view_bcs<T: serde::de::DeserializeOwned>(
744 &self,
745 function: &str,
746 type_args: Vec<String>,
747 args: Vec<Vec<u8>>,
748 ) -> AptosResult<T> {
749 let response = self.fullnode.view_bcs(function, type_args, args).await?;
750 let bytes = response.into_inner();
751 aptos_bcs::from_bytes(&bytes).map_err(|e| AptosError::Bcs(e.to_string()))
752 }
753
754 pub async fn view_bcs_raw(
763 &self,
764 function: &str,
765 type_args: Vec<String>,
766 args: Vec<Vec<u8>>,
767 ) -> AptosResult<Vec<u8>> {
768 let response = self.fullnode.view_bcs(function, type_args, args).await?;
769 Ok(response.into_inner())
770 }
771
772 #[cfg(feature = "faucet")]
792 pub async fn fund_account(
793 &self,
794 address: AccountAddress,
795 amount: u64,
796 ) -> AptosResult<Vec<String>> {
797 const MAX_FAUCET_ATTEMPTS: u32 = 16;
801
802 let faucet = self
803 .faucet
804 .as_ref()
805 .ok_or_else(|| AptosError::FeatureNotEnabled("faucet".into()))?;
806
807 let starting_balance = self.get_balance(address).await.unwrap_or(0);
809 let target_balance = starting_balance.saturating_add(amount);
810
811 let mut all_hashes: Vec<String> = Vec::new();
812 let mut current_balance = starting_balance;
813 let mut attempts = 0u32;
814
815 while current_balance < target_balance && attempts < MAX_FAUCET_ATTEMPTS {
816 attempts += 1;
817 let still_needed = target_balance.saturating_sub(current_balance);
818 let txn_hashes = faucet.fund(address, still_needed).await?;
819
820 let hashes: Vec<HashValue> = txn_hashes
822 .iter()
823 .filter_map(|hash_str| {
824 let hash_str_clean = hash_str.strip_prefix("0x").unwrap_or(hash_str);
825 HashValue::from_hex(hash_str_clean).ok()
826 })
827 .collect();
828
829 let wait_futures: Vec<_> = hashes
831 .iter()
832 .map(|hash| {
833 self.fullnode
834 .wait_for_transaction(hash, Some(Duration::from_mins(1)))
835 })
836 .collect();
837 let results = futures::future::join_all(wait_futures).await;
838 for result in results {
839 result?;
840 }
841
842 all_hashes.extend(txn_hashes);
843
844 let new_balance = self.get_balance(address).await.unwrap_or(current_balance);
846 if new_balance <= current_balance {
847 return Err(AptosError::api(
848 400,
849 format!(
850 "faucet returned successful response but balance did not increase (\
851 attempts={attempts}, balance={new_balance}, requested top-up={amount})"
852 ),
853 ));
854 }
855 current_balance = new_balance;
856 }
857
858 if current_balance < target_balance {
859 return Err(AptosError::api(
860 429,
861 format!(
862 "faucet could not deliver {amount} octas in {attempts} attempts \
863 (starting balance={starting_balance}, current balance={current_balance})"
864 ),
865 ));
866 }
867
868 Ok(all_hashes)
869 }
870
871 #[cfg(all(feature = "faucet", feature = "ed25519"))]
872 pub async fn create_funded_account(
878 &self,
879 amount: u64,
880 ) -> AptosResult<crate::account::Ed25519Account> {
881 let account = crate::account::Ed25519Account::generate();
882 self.fund_account(account.address(), amount).await?;
883 Ok(account)
884 }
885
886 pub fn batch(&self) -> crate::transaction::BatchOperations<'_> {
905 crate::transaction::BatchOperations::new(&self.fullnode, &self.chain_id)
906 }
907
908 #[cfg(feature = "ed25519")]
927 pub async fn submit_batch<A: Account>(
928 &self,
929 account: &A,
930 payloads: Vec<TransactionPayload>,
931 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
932 self.batch().submit(account, payloads).await
933 }
934
935 #[cfg(feature = "ed25519")]
952 pub async fn submit_batch_and_wait<A: Account>(
953 &self,
954 account: &A,
955 payloads: Vec<TransactionPayload>,
956 timeout: Option<Duration>,
957 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
958 self.batch()
959 .submit_and_wait(account, payloads, timeout)
960 .await
961 }
962
963 #[cfg(feature = "ed25519")]
985 pub async fn batch_transfer_apt<A: Account>(
986 &self,
987 sender: &A,
988 transfers: Vec<(AccountAddress, u64)>,
989 ) -> AptosResult<Vec<crate::transaction::BatchTransactionResult>> {
990 self.batch().transfer_apt(sender, transfers).await
991 }
992}
993
994#[cfg(feature = "ed25519")]
1003fn build_zero_signed_authenticator<A: Account>(
1025 account: &A,
1026) -> AptosResult<crate::transaction::TransactionAuthenticator> {
1027 use crate::crypto::{
1028 ED25519_SCHEME, MULTI_ED25519_SCHEME, MULTI_KEY_SCHEME, SINGLE_KEY_SCHEME,
1029 };
1030 use crate::transaction::TransactionAuthenticator;
1031 use crate::transaction::authenticator::{
1032 AccountAuthenticator, Ed25519PublicKey, Ed25519Signature,
1033 };
1034
1035 let pubkey_bytes = account.public_key_bytes();
1036 let scheme = account.signature_scheme();
1037
1038 match scheme {
1039 s if s == ED25519_SCHEME => {
1042 let pubkey_arr: [u8; 32] = pubkey_bytes.as_slice().try_into().map_err(|_| {
1043 crate::error::AptosError::transaction(
1044 "simulate(): Ed25519 account exposed a non-32-byte public key",
1045 )
1046 })?;
1047 Ok(TransactionAuthenticator::Ed25519 {
1048 public_key: Ed25519PublicKey(pubkey_arr),
1049 signature: Ed25519Signature([0u8; 64]),
1050 })
1051 }
1052
1053 s if s == MULTI_ED25519_SCHEME => {
1058 const PK_LEN: usize = 32;
1059 const SIG_LEN: usize = 64;
1060 if pubkey_bytes.is_empty() || !(pubkey_bytes.len() - 1).is_multiple_of(PK_LEN) {
1061 return Err(crate::error::AptosError::transaction(
1062 "simulate(): MultiEd25519 public_key_bytes has invalid length",
1063 ));
1064 }
1065 let threshold = *pubkey_bytes.last().unwrap() as usize;
1066 if threshold == 0 {
1067 return Err(crate::error::AptosError::transaction(
1068 "simulate(): MultiEd25519 threshold cannot be zero",
1069 ));
1070 }
1071 let mut bitmap = [0u8; 4];
1073 for i in 0..threshold {
1074 let byte = i / 8;
1075 let bit = i % 8;
1076 bitmap[byte] |= 0b1000_0000_u8 >> bit;
1077 }
1078 let mut signature = Vec::with_capacity(threshold * SIG_LEN + 4);
1079 signature.extend(std::iter::repeat_n(0u8, threshold * SIG_LEN));
1080 signature.extend_from_slice(&bitmap);
1081 Ok(TransactionAuthenticator::MultiEd25519 {
1082 public_key: pubkey_bytes,
1083 signature,
1084 })
1085 }
1086
1087 s if s == SINGLE_KEY_SCHEME => {
1091 let zero_sig = zero_any_signature_for_pubkey(&pubkey_bytes).ok_or_else(|| {
1092 crate::error::AptosError::transaction(
1093 "simulate(): unsupported AnyPublicKey variant in SingleKey account",
1094 )
1095 })?;
1096 Ok(TransactionAuthenticator::single_sender(
1097 AccountAuthenticator::single_key(pubkey_bytes, zero_sig),
1098 ))
1099 }
1100
1101 s if s == MULTI_KEY_SCHEME => {
1106 let (variants, threshold) = parse_multi_key_pubkey(&pubkey_bytes)?;
1107 if threshold == 0 || (threshold as usize) > variants.len() {
1108 return Err(crate::error::AptosError::transaction(
1109 "simulate(): invalid MultiKey threshold",
1110 ));
1111 }
1112 let mut sig_bytes = Vec::with_capacity(1 + threshold as usize * 66 + 1 + 4);
1113 sig_bytes.push(threshold); for variant in variants.iter().take(threshold as usize) {
1115 let zero_sig = zero_any_signature_for_variant(*variant).ok_or_else(|| {
1116 crate::error::AptosError::transaction(
1117 "simulate(): unsupported AnyPublicKey variant in MultiKey account",
1118 )
1119 })?;
1120 sig_bytes.extend_from_slice(&zero_sig);
1121 }
1122 sig_bytes.push(4);
1124 let mut bitmap = [0u8; 4];
1125 for i in 0..threshold as usize {
1126 let byte = i / 8;
1127 let bit = i % 8;
1128 bitmap[byte] |= 0b1000_0000_u8 >> bit;
1129 }
1130 sig_bytes.extend_from_slice(&bitmap);
1131 Ok(TransactionAuthenticator::single_sender(
1132 AccountAuthenticator::multi_key(pubkey_bytes, sig_bytes),
1133 ))
1134 }
1135
1136 _ => Err(crate::error::AptosError::transaction(format!(
1137 "simulate(): unsupported signature scheme {scheme}; \
1138 use simulate_signed() with a hand-built zero-signed transaction"
1139 ))),
1140 }
1141}
1142
1143#[cfg(feature = "ed25519")]
1147fn zero_any_signature_for_pubkey(any_public_key_bcs: &[u8]) -> Option<Vec<u8>> {
1148 let variant = *any_public_key_bcs.first()?;
1149 zero_any_signature_for_variant(variant)
1150}
1151
1152#[cfg(feature = "ed25519")]
1154fn zero_any_signature_for_variant(variant: u8) -> Option<Vec<u8>> {
1155 match variant {
1164 0..=2 => {
1165 let mut out = Vec::with_capacity(1 + 1 + 64);
1166 out.push(variant);
1167 out.push(64);
1168 out.extend(std::iter::repeat_n(0u8, 64));
1169 Some(out)
1170 }
1171 _ => None,
1172 }
1173}
1174
1175#[cfg(feature = "ed25519")]
1179fn parse_multi_key_pubkey(bytes: &[u8]) -> AptosResult<(Vec<u8>, u8)> {
1180 if bytes.is_empty() {
1181 return Err(crate::error::AptosError::transaction(
1182 "simulate(): MultiKey public_key_bytes is empty",
1183 ));
1184 }
1185 let num_keys = bytes[0] as usize;
1186 let mut offset = 1;
1187 let mut variants = Vec::with_capacity(num_keys);
1188 for _ in 0..num_keys {
1189 if offset >= bytes.len() {
1190 return Err(crate::error::AptosError::transaction(
1191 "simulate(): MultiKey public_key truncated at variant tag",
1192 ));
1193 }
1194 let variant = bytes[offset];
1195 variants.push(variant);
1196 offset += 1;
1197 let (len, len_bytes) = decode_uleb128_internal(&bytes[offset..])?;
1199 offset += len_bytes;
1200 offset = offset.checked_add(len).ok_or_else(|| {
1201 crate::error::AptosError::transaction("simulate(): MultiKey public_key overflow")
1202 })?;
1203 if offset > bytes.len() {
1204 return Err(crate::error::AptosError::transaction(
1205 "simulate(): MultiKey public_key truncated at key bytes",
1206 ));
1207 }
1208 }
1209 if offset >= bytes.len() {
1210 return Err(crate::error::AptosError::transaction(
1211 "simulate(): MultiKey public_key missing threshold byte",
1212 ));
1213 }
1214 let threshold = bytes[offset];
1215 Ok((variants, threshold))
1216}
1217
1218#[cfg(feature = "ed25519")]
1220fn decode_uleb128_internal(bytes: &[u8]) -> AptosResult<(usize, usize)> {
1221 let mut value: usize = 0;
1222 let mut shift = 0;
1223 for (i, &b) in bytes.iter().enumerate() {
1224 value |= ((b & 0x7F) as usize) << shift;
1225 if (b & 0x80) == 0 {
1226 return Ok((value, i + 1));
1227 }
1228 shift += 7;
1229 if shift >= 64 {
1230 break;
1231 }
1232 }
1233 Err(crate::error::AptosError::transaction(
1234 "simulate(): malformed ULEB128 in public key",
1235 ))
1236}
1237
1238#[cfg(test)]
1239mod tests {
1240 use super::*;
1241 use crate::transaction::authenticator::{
1242 Ed25519PublicKey, Ed25519Signature, TransactionAuthenticator,
1243 };
1244 use crate::transaction::payload::{EntryFunction, TransactionPayload};
1245 use crate::transaction::simulation::SimulateQueryOptions;
1246 use crate::transaction::types::{
1247 FeePayerRawTransaction, MultiAgentRawTransaction, RawTransaction, SignedTransaction,
1248 };
1249 use crate::types::ChainId;
1250 use wiremock::{
1251 Mock, MockServer, ResponseTemplate,
1252 matchers::{method, path, path_regex},
1253 };
1254
1255 #[test]
1256 fn test_aptos_client_creation() {
1257 let aptos = Aptos::testnet();
1258 assert!(aptos.is_ok());
1259 }
1260
1261 #[test]
1262 fn test_chain_id() {
1263 let aptos = Aptos::testnet().unwrap();
1264 assert_eq!(aptos.chain_id(), ChainId::testnet());
1265
1266 let aptos = Aptos::mainnet().unwrap();
1267 assert_eq!(aptos.chain_id(), ChainId::mainnet());
1268 }
1269
1270 fn create_mock_aptos(server: &MockServer) -> Aptos {
1271 let url = format!("{}/v1", server.uri());
1272 let config = AptosConfig::custom(&url).unwrap().without_retry();
1273 Aptos::new(config).unwrap()
1274 }
1275
1276 fn create_minimal_signed_transaction() -> SignedTransaction {
1277 let raw = RawTransaction::new(
1278 AccountAddress::ONE,
1279 0,
1280 TransactionPayload::EntryFunction(
1281 EntryFunction::apt_transfer(AccountAddress::ONE, 0).unwrap(),
1282 ),
1283 100_000,
1284 100,
1285 std::time::SystemTime::now()
1286 .duration_since(std::time::UNIX_EPOCH)
1287 .unwrap()
1288 .as_secs()
1289 .saturating_add(600),
1290 ChainId::testnet(),
1291 );
1292 SignedTransaction::new(
1293 raw,
1294 TransactionAuthenticator::Ed25519 {
1295 public_key: Ed25519PublicKey([0u8; 32]),
1296 signature: Ed25519Signature([0u8; 64]),
1297 },
1298 )
1299 }
1300
1301 fn simulate_response_json() -> serde_json::Value {
1302 serde_json::json!([{
1303 "success": true,
1304 "vm_status": "Executed successfully",
1305 "gas_used": "1500",
1306 "max_gas_amount": "200000",
1307 "gas_unit_price": "100",
1308 "hash": "0xabc",
1309 "changes": [],
1310 "events": []
1311 }])
1312 }
1313
1314 #[tokio::test]
1315 async fn test_get_sequence_number() {
1316 let server = MockServer::start().await;
1317
1318 Mock::given(method("GET"))
1319 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+"))
1320 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1321 "sequence_number": "42",
1322 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1323 })))
1324 .expect(1)
1325 .mount(&server)
1326 .await;
1327
1328 let aptos = create_mock_aptos(&server);
1329 let seq = aptos
1330 .get_sequence_number(AccountAddress::ONE)
1331 .await
1332 .unwrap();
1333 assert_eq!(seq, 42);
1334 }
1335
1336 #[tokio::test]
1337 async fn test_get_balance() {
1338 let server = MockServer::start().await;
1339
1340 Mock::given(method("POST"))
1342 .and(path("/v1/view"))
1343 .respond_with(
1344 ResponseTemplate::new(200).set_body_json(serde_json::json!(["5000000000"])),
1345 )
1346 .expect(1)
1347 .mount(&server)
1348 .await;
1349
1350 let aptos = create_mock_aptos(&server);
1351 let balance = aptos.get_balance(AccountAddress::ONE).await.unwrap();
1352 assert_eq!(balance, 5_000_000_000);
1353 }
1354
1355 #[tokio::test]
1356 async fn test_get_resources_via_fullnode() {
1357 let server = MockServer::start().await;
1358
1359 Mock::given(method("GET"))
1360 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+/resources"))
1361 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
1362 {"type": "0x1::account::Account", "data": {}},
1363 {"type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": {}}
1364 ])))
1365 .expect(1)
1366 .mount(&server)
1367 .await;
1368
1369 let aptos = create_mock_aptos(&server);
1370 let resources = aptos
1371 .fullnode()
1372 .get_account_resources(AccountAddress::ONE)
1373 .await
1374 .unwrap();
1375 assert_eq!(resources.data.len(), 2);
1376 }
1377
1378 #[tokio::test]
1379 async fn test_ledger_info() {
1380 let server = MockServer::start().await;
1381
1382 Mock::given(method("GET"))
1383 .and(path("/v1"))
1384 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1385 "chain_id": 2,
1386 "epoch": "100",
1387 "ledger_version": "12345",
1388 "oldest_ledger_version": "0",
1389 "ledger_timestamp": "1000000",
1390 "node_role": "full_node",
1391 "oldest_block_height": "0",
1392 "block_height": "5000"
1393 })))
1394 .expect(1)
1395 .mount(&server)
1396 .await;
1397
1398 let aptos = create_mock_aptos(&server);
1399 let info = aptos.ledger_info().await.unwrap();
1400 assert_eq!(info.version().unwrap(), 12345);
1401 }
1402
1403 #[tokio::test]
1404 async fn test_config_builder() {
1405 let config = AptosConfig::testnet().with_timeout(Duration::from_mins(1));
1406
1407 let aptos = Aptos::new(config).unwrap();
1408 assert_eq!(aptos.chain_id(), ChainId::testnet());
1409 }
1410
1411 #[tokio::test]
1412 async fn test_fullnode_accessor() {
1413 let server = MockServer::start().await;
1414 let aptos = create_mock_aptos(&server);
1415
1416 let fullnode = aptos.fullnode();
1418 assert!(fullnode.base_url().as_str().contains(&server.uri()));
1419 }
1420
1421 #[cfg(feature = "ed25519")]
1422 #[tokio::test]
1423 async fn test_build_transaction() {
1424 let server = MockServer::start().await;
1425
1426 Mock::given(method("GET"))
1428 .and(path_regex(r"/v1/accounts/0x[0-9a-f]+"))
1429 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1430 "sequence_number": "0",
1431 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1432 })))
1433 .expect(1)
1434 .mount(&server)
1435 .await;
1436
1437 Mock::given(method("GET"))
1439 .and(path("/v1/estimate_gas_price"))
1440 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1441 "gas_estimate": 100
1442 })))
1443 .expect(1)
1444 .mount(&server)
1445 .await;
1446
1447 Mock::given(method("GET"))
1449 .and(path("/v1"))
1450 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1451 "chain_id": 4,
1452 "epoch": "1",
1453 "ledger_version": "100",
1454 "oldest_ledger_version": "0",
1455 "ledger_timestamp": "1000000",
1456 "node_role": "full_node",
1457 "oldest_block_height": "0",
1458 "block_height": "50"
1459 })))
1460 .expect(1)
1461 .mount(&server)
1462 .await;
1463
1464 let aptos = create_mock_aptos(&server);
1465 let account = crate::account::Ed25519Account::generate();
1466 let recipient = AccountAddress::from_hex("0x123").unwrap();
1467 let payload = crate::transaction::EntryFunction::apt_transfer(recipient, 1000).unwrap();
1468
1469 let raw_txn = aptos
1470 .build_transaction(&account, payload.into())
1471 .await
1472 .unwrap();
1473 assert_eq!(raw_txn.sender, account.address());
1474 assert_eq!(raw_txn.sequence_number, 0);
1475 }
1476
1477 #[cfg(feature = "indexer")]
1478 #[tokio::test]
1479 async fn test_indexer_accessor() {
1480 let aptos = Aptos::testnet().unwrap();
1481 let indexer = aptos.indexer();
1482 assert!(indexer.is_some());
1483 }
1484
1485 #[tokio::test]
1486 async fn test_account_exists_true() {
1487 let server = MockServer::start().await;
1488
1489 Mock::given(method("GET"))
1490 .and(path_regex(r"^/v1/accounts/0x[0-9a-f]+$"))
1491 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1492 "sequence_number": "10",
1493 "authentication_key": "0x0000000000000000000000000000000000000000000000000000000000000001"
1494 })))
1495 .expect(1)
1496 .mount(&server)
1497 .await;
1498
1499 let aptos = create_mock_aptos(&server);
1500 let exists = aptos.account_exists(AccountAddress::ONE).await.unwrap();
1501 assert!(exists);
1502 }
1503
1504 #[tokio::test]
1505 async fn test_account_exists_false() {
1506 let server = MockServer::start().await;
1507
1508 Mock::given(method("GET"))
1509 .and(path_regex(r"^/v1/accounts/0x[0-9a-f]+$"))
1510 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
1511 "message": "Account not found",
1512 "error_code": "account_not_found"
1513 })))
1514 .expect(1)
1515 .mount(&server)
1516 .await;
1517
1518 let aptos = create_mock_aptos(&server);
1519 let exists = aptos.account_exists(AccountAddress::ONE).await.unwrap();
1520 assert!(!exists);
1521 }
1522
1523 #[tokio::test]
1524 async fn test_view_function() {
1525 let server = MockServer::start().await;
1526
1527 Mock::given(method("POST"))
1528 .and(path("/v1/view"))
1529 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!(["1000000"])))
1530 .expect(1)
1531 .mount(&server)
1532 .await;
1533
1534 let aptos = create_mock_aptos(&server);
1535 let result: Vec<serde_json::Value> = aptos
1536 .view(
1537 "0x1::coin::balance",
1538 vec!["0x1::aptos_coin::AptosCoin".to_string()],
1539 vec![serde_json::json!("0x1")],
1540 )
1541 .await
1542 .unwrap();
1543
1544 assert_eq!(result.len(), 1);
1545 assert_eq!(result[0].as_str().unwrap(), "1000000");
1546 }
1547
1548 #[tokio::test]
1549 async fn test_chain_id_from_config() {
1550 let aptos = Aptos::mainnet().unwrap();
1551 assert_eq!(aptos.chain_id(), ChainId::mainnet());
1552
1553 let aptos = Aptos::devnet().unwrap();
1559 assert_eq!(aptos.chain_id(), ChainId::new(0));
1560 }
1561
1562 #[tokio::test]
1563 async fn test_custom_config() {
1564 let server = MockServer::start().await;
1565 let url = format!("{}/v1", server.uri());
1566 let config = AptosConfig::custom(&url).unwrap();
1567 let aptos = Aptos::new(config).unwrap();
1568
1569 assert_eq!(aptos.chain_id(), ChainId::new(0));
1571 }
1572
1573 #[cfg(feature = "ed25519")]
1582 #[test]
1583 fn test_zero_signed_authenticator_ed25519() {
1584 use crate::account::Ed25519Account;
1585 use crate::transaction::TransactionAuthenticator;
1586
1587 let account = Ed25519Account::generate();
1588 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1589 match auth {
1590 TransactionAuthenticator::Ed25519 {
1591 public_key,
1592 signature,
1593 } => {
1594 assert_eq!(public_key.0, account.public_key().to_bytes());
1595 assert_eq!(signature.0, [0u8; 64]);
1596 }
1597 other => panic!("expected TransactionAuthenticator::Ed25519, got {other:?}"),
1598 }
1599 }
1600
1601 #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
1602 #[test]
1603 fn test_zero_signed_authenticator_single_key_secp256k1() {
1604 use crate::account::Secp256k1Account;
1605 use crate::transaction::TransactionAuthenticator;
1606 use crate::transaction::authenticator::AccountAuthenticator;
1607
1608 let account = Secp256k1Account::generate();
1609 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1610 let TransactionAuthenticator::SingleSender { sender } = auth else {
1613 panic!("expected SingleSender, got {auth:?}");
1614 };
1615 let AccountAuthenticator::SingleKey {
1616 public_key,
1617 signature,
1618 } = sender
1619 else {
1620 panic!("expected AccountAuthenticator::SingleKey");
1621 };
1622 assert_eq!(public_key, account.public_key_bytes());
1624 assert_eq!(signature.len(), 1 + 1 + 64);
1627 assert_eq!(signature[0], 0x01, "variant tag must match secp256k1");
1628 assert_eq!(signature[1], 64, "ULEB128(64)");
1629 assert!(signature[2..].iter().all(|b| *b == 0), "all-zero signature");
1630 }
1631
1632 #[cfg(feature = "ed25519")]
1633 #[test]
1634 fn test_zero_signed_authenticator_single_key_ed25519() {
1635 use crate::account::Ed25519SingleKeyAccount;
1636 use crate::transaction::TransactionAuthenticator;
1637 use crate::transaction::authenticator::AccountAuthenticator;
1638
1639 let account = Ed25519SingleKeyAccount::generate();
1644 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1645 let TransactionAuthenticator::SingleSender { sender } = auth else {
1646 panic!("expected SingleSender for Ed25519SingleKey, got {auth:?}");
1647 };
1648 let AccountAuthenticator::SingleKey {
1649 public_key,
1650 signature,
1651 } = sender
1652 else {
1653 panic!("expected AccountAuthenticator::SingleKey");
1654 };
1655 assert_eq!(public_key, account.public_key_bytes());
1656 assert_eq!(signature[0], 0x00, "AnySignature::Ed25519 variant");
1657 assert_eq!(signature[1], 64, "ULEB128(64)");
1658 assert!(signature[2..].iter().all(|b| *b == 0));
1659 }
1660
1661 #[cfg(feature = "ed25519")]
1662 #[test]
1663 fn test_zero_signed_authenticator_multi_ed25519() {
1664 use crate::account::MultiEd25519Account;
1665 use crate::crypto::Ed25519PrivateKey;
1666 use crate::transaction::TransactionAuthenticator;
1667
1668 let keys: Vec<_> = (0..3).map(|_| Ed25519PrivateKey::generate()).collect();
1670 let account = MultiEd25519Account::new(keys, 2).unwrap();
1671 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1672 let TransactionAuthenticator::MultiEd25519 {
1673 public_key,
1674 signature,
1675 } = auth
1676 else {
1677 panic!("expected MultiEd25519, got {auth:?}");
1678 };
1679 assert_eq!(public_key, account.public_key_bytes());
1680 assert_eq!(signature.len(), 2 * 64 + 4);
1682 assert!(signature[..128].iter().all(|b| *b == 0));
1683 assert_eq!(signature[128], 0b1100_0000, "bits 0 and 1 set (MSB-first)");
1684 assert_eq!(&signature[129..], &[0u8, 0u8, 0u8]);
1685 }
1686
1687 #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
1688 #[test]
1689 fn test_zero_signed_authenticator_multi_key() {
1690 use crate::account::{AnyPrivateKey, MultiKeyAccount};
1691 use crate::crypto::{Ed25519PrivateKey, Secp256k1PrivateKey};
1692 use crate::transaction::TransactionAuthenticator;
1693 use crate::transaction::authenticator::AccountAuthenticator;
1694
1695 let keys = vec![
1696 AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
1697 AnyPrivateKey::secp256k1(Secp256k1PrivateKey::generate()),
1698 AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
1699 ];
1700 let account = MultiKeyAccount::new(keys, 2).unwrap();
1701 let auth = super::build_zero_signed_authenticator(&account).unwrap();
1702 let TransactionAuthenticator::SingleSender { sender } = auth else {
1703 panic!("expected SingleSender for MultiKey, got {auth:?}");
1704 };
1705 let AccountAuthenticator::MultiKey {
1706 public_key,
1707 signature,
1708 } = sender
1709 else {
1710 panic!("expected AccountAuthenticator::MultiKey");
1711 };
1712 assert_eq!(public_key, account.public_key_bytes());
1713 assert_eq!(signature[0], 2, "num_sigs ULEB128");
1719 assert_eq!(signature[1], 0x00, "first AnySignature variant (Ed25519)");
1720 assert_eq!(
1721 signature[1 + 1 + 1 + 64],
1722 0x01,
1723 "second AnySignature variant (Secp256k1)"
1724 );
1725 }
1726
1727 #[tokio::test]
1728 async fn test_simulate_signed_with_options() {
1729 let server = MockServer::start().await;
1730
1731 Mock::given(method("POST"))
1732 .and(path("/v1/transactions/simulate"))
1733 .and(|req: &wiremock::Request| {
1734 req.url
1735 .query()
1736 .is_some_and(|q| q.contains("estimate_gas_unit_price=true"))
1737 })
1738 .respond_with(
1739 ResponseTemplate::new(200).set_body_json(serde_json::json!([{
1740 "success": true,
1741 "vm_status": "Executed successfully",
1742 "gas_used": "1500",
1743 "max_gas_amount": "200000",
1744 "gas_unit_price": "100",
1745 "hash": "0xabc",
1746 "changes": [],
1747 "events": []
1748 }])),
1749 )
1750 .expect(1)
1751 .mount(&server)
1752 .await;
1753
1754 let raw = RawTransaction::new(
1755 AccountAddress::ONE,
1756 0,
1757 TransactionPayload::EntryFunction(
1758 EntryFunction::apt_transfer(AccountAddress::ONE, 0).unwrap(),
1759 ),
1760 100_000,
1761 100,
1762 std::time::SystemTime::now()
1763 .duration_since(std::time::UNIX_EPOCH)
1764 .unwrap()
1765 .as_secs()
1766 .saturating_add(600),
1767 ChainId::testnet(),
1768 );
1769 let signed = SignedTransaction::new(
1770 raw,
1771 TransactionAuthenticator::Ed25519 {
1772 public_key: Ed25519PublicKey([0u8; 32]),
1773 signature: Ed25519Signature([0u8; 64]),
1774 },
1775 );
1776
1777 let aptos = create_mock_aptos(&server);
1778 let options = SimulateQueryOptions::new().estimate_gas_unit_price(true);
1779 let result = aptos
1780 .simulate_signed_with_options(&signed, options)
1781 .await
1782 .unwrap();
1783
1784 assert!(result.success());
1785 assert_eq!(result.gas_used(), 1500);
1786 assert_eq!(result.gas_unit_price(), 100);
1787 }
1788
1789 #[tokio::test]
1790 async fn test_simulate_signed_without_options() {
1791 let server = MockServer::start().await;
1792
1793 Mock::given(method("POST"))
1794 .and(path("/v1/transactions/simulate"))
1795 .and(|req: &wiremock::Request| {
1796 req.url.query().is_none_or(|q| {
1797 !q.contains("estimate_gas_unit_price=")
1798 && !q.contains("estimate_max_gas_amount=")
1799 && !q.contains("estimate_prioritized_gas_unit_price=")
1800 })
1801 })
1802 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
1803 .expect(1)
1804 .mount(&server)
1805 .await;
1806
1807 let aptos = create_mock_aptos(&server);
1808 let signed = create_minimal_signed_transaction();
1809 let result = aptos.simulate_signed(&signed).await.unwrap();
1810 assert!(result.success());
1811 }
1812
1813 #[tokio::test]
1814 async fn test_simulate_multi_agent_without_options() {
1815 let server = MockServer::start().await;
1816
1817 Mock::given(method("POST"))
1818 .and(path("/v1/transactions/simulate"))
1819 .and(|req: &wiremock::Request| {
1820 req.url.query().is_none_or(|q| {
1821 !q.contains("estimate_gas_unit_price=")
1822 && !q.contains("estimate_max_gas_amount=")
1823 && !q.contains("estimate_prioritized_gas_unit_price=")
1824 })
1825 })
1826 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
1827 .expect(1)
1828 .mount(&server)
1829 .await;
1830
1831 let aptos = create_mock_aptos(&server);
1832 let multi_agent = MultiAgentRawTransaction::new(
1833 create_minimal_signed_transaction().raw_txn,
1834 vec![AccountAddress::from_hex("0x2").unwrap()],
1835 );
1836 let result = aptos
1837 .simulate_multi_agent(&multi_agent, None)
1838 .await
1839 .unwrap();
1840 assert!(result.success());
1841 }
1842
1843 #[tokio::test]
1844 async fn test_simulate_multi_agent_with_options() {
1845 let server = MockServer::start().await;
1846
1847 Mock::given(method("POST"))
1848 .and(path("/v1/transactions/simulate"))
1849 .and(|req: &wiremock::Request| {
1850 req.url
1851 .query()
1852 .is_some_and(|q| q.contains("estimate_max_gas_amount=true"))
1853 })
1854 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
1855 .expect(1)
1856 .mount(&server)
1857 .await;
1858
1859 let aptos = create_mock_aptos(&server);
1860 let multi_agent = MultiAgentRawTransaction::new(
1861 create_minimal_signed_transaction().raw_txn,
1862 vec![AccountAddress::from_hex("0x2").unwrap()],
1863 );
1864 let options = SimulateQueryOptions::new().estimate_max_gas_amount(true);
1865 let result = aptos
1866 .simulate_multi_agent(&multi_agent, Some(options))
1867 .await
1868 .unwrap();
1869 assert!(result.success());
1870 }
1871
1872 #[tokio::test]
1873 async fn test_simulate_fee_payer_without_options() {
1874 let server = MockServer::start().await;
1875
1876 Mock::given(method("POST"))
1877 .and(path("/v1/transactions/simulate"))
1878 .and(|req: &wiremock::Request| {
1879 req.url.query().is_none_or(|q| {
1880 !q.contains("estimate_gas_unit_price=")
1881 && !q.contains("estimate_max_gas_amount=")
1882 && !q.contains("estimate_prioritized_gas_unit_price=")
1883 })
1884 })
1885 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
1886 .expect(1)
1887 .mount(&server)
1888 .await;
1889
1890 let aptos = create_mock_aptos(&server);
1891 let fee_payer_txn = FeePayerRawTransaction::new_simple(
1892 create_minimal_signed_transaction().raw_txn,
1893 AccountAddress::THREE,
1894 );
1895 let result = aptos
1896 .simulate_fee_payer(&fee_payer_txn, None)
1897 .await
1898 .unwrap();
1899 assert!(result.success());
1900 }
1901
1902 #[tokio::test]
1903 async fn test_simulate_fee_payer_with_options() {
1904 let server = MockServer::start().await;
1905
1906 Mock::given(method("POST"))
1907 .and(path("/v1/transactions/simulate"))
1908 .and(|req: &wiremock::Request| {
1909 req.url
1910 .query()
1911 .is_some_and(|q| q.contains("estimate_prioritized_gas_unit_price=true"))
1912 })
1913 .respond_with(ResponseTemplate::new(200).set_body_json(simulate_response_json()))
1914 .expect(1)
1915 .mount(&server)
1916 .await;
1917
1918 let aptos = create_mock_aptos(&server);
1919 let fee_payer_txn = FeePayerRawTransaction::new_simple(
1920 create_minimal_signed_transaction().raw_txn,
1921 AccountAddress::THREE,
1922 );
1923 let options = SimulateQueryOptions::new().estimate_prioritized_gas_unit_price(true);
1924 let result = aptos
1925 .simulate_fee_payer(&fee_payer_txn, options)
1926 .await
1927 .unwrap();
1928 assert!(result.success());
1929 }
1930}