From aa98ce12312a47c7d2234ff7d99a17238965ecc1 Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Mon, 10 Nov 2025 01:40:47 +0800 Subject: [PATCH 1/3] Enhance AptosClientBuilder and AptosNetwork for improved functionality - Updated `AptosClientBuilder` to accept optional headers and return a result in the `build` method, allowing for better error handling. - Modified `AptosNetwork` struct to include an optional `chain_id` and updated related methods for flexibility. - Introduced `LedgerInfo` struct to represent ledger information from the RPC API, enhancing data handling capabilities. - Updated various tests to accommodate the new async structure and improved error handling. - Added `anyhow` dependency for better error management across the SDK. --- Cargo.lock | 1 + .../src/api_types/ledger_info.rs | 82 +++++ .../aptos-rust-sdk-types/src/api_types/mod.rs | 1 + .../src/api_types/numbers.rs | 17 + crates/aptos-rust-sdk/src/client/builder.rs | 130 ++++++-- crates/aptos-rust-sdk/src/client/config.rs | 47 ++- crates/aptos-rust-sdk/src/client/rest_api.rs | 24 +- .../src/tests/client/rest_api.rs | 4 +- crates/examples/Cargo.toml | 5 +- crates/examples/src/lib.rs | 83 +++-- crates/examples/src/view_function_example.rs | 307 +++++++++--------- 11 files changed, 463 insertions(+), 238 deletions(-) create mode 100644 crates/aptos-rust-sdk-types/src/api_types/ledger_info.rs diff --git a/Cargo.lock b/Cargo.lock index befca6f..c1e648c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -956,6 +956,7 @@ dependencies = [ name = "examples" version = "0.1.0" dependencies = [ + "anyhow", "aptos-bcs", "aptos-crypto", "aptos-rust-sdk", diff --git a/crates/aptos-rust-sdk-types/src/api_types/ledger_info.rs b/crates/aptos-rust-sdk-types/src/api_types/ledger_info.rs new file mode 100644 index 0000000..de286f4 --- /dev/null +++ b/crates/aptos-rust-sdk-types/src/api_types/ledger_info.rs @@ -0,0 +1,82 @@ +use crate::api_types::chain_id::ChainId; +use crate::api_types::numbers::U64; +use serde::{Deserialize, Serialize}; + +/// Ledger information returned from the RPC API +/// This represents the JSON response from the `/v1` endpoint +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct LedgerInfo { + /// The chain ID of the network + #[serde(rename = "chain_id")] + pub chain_id: u8, + + /// The current epoch number + #[serde(rename = "epoch")] + pub epoch: U64, + + /// The current ledger version + #[serde(rename = "ledger_version")] + pub ledger_version: U64, + + /// The oldest ledger version available + #[serde(rename = "oldest_ledger_version")] + pub oldest_ledger_version: U64, + + /// The ledger timestamp in microseconds + #[serde(rename = "ledger_timestamp")] + pub ledger_timestamp: U64, + + /// The role of the node (e.g., "full_node") + #[serde(rename = "node_role")] + pub node_role: String, + + /// The oldest block height available + #[serde(rename = "oldest_block_height")] + pub oldest_block_height: U64, + + /// The current block height + #[serde(rename = "block_height")] + pub block_height: U64, + + /// The git hash of the node software + #[serde(rename = "git_hash")] + pub git_hash: String, +} + +impl LedgerInfo { + /// Get the chain ID as a ChainId enum + pub fn chain_id(&self) -> ChainId { + ChainId::from_u8(self.chain_id) + } + + /// Get the epoch as u64 + pub fn epoch_u64(&self) -> u64 { + self.epoch.as_u64() + } + + /// Get the ledger version as u64 + pub fn ledger_version_u64(&self) -> u64 { + self.ledger_version.as_u64() + } + + /// Get the oldest ledger version as u64 + pub fn oldest_ledger_version_u64(&self) -> u64 { + self.oldest_ledger_version.as_u64() + } + + /// Get the ledger timestamp as u64 + pub fn ledger_timestamp_u64(&self) -> u64 { + self.ledger_timestamp.as_u64() + } + + /// Get the oldest block height as u64 + pub fn oldest_block_height_u64(&self) -> u64 { + self.oldest_block_height.as_u64() + } + + /// Get the block height as u64 + pub fn block_height_u64(&self) -> u64 { + self.block_height.as_u64() + } +} + diff --git a/crates/aptos-rust-sdk-types/src/api_types/mod.rs b/crates/aptos-rust-sdk-types/src/api_types/mod.rs index 8403455..cef9a82 100644 --- a/crates/aptos-rust-sdk-types/src/api_types/mod.rs +++ b/crates/aptos-rust-sdk-types/src/api_types/mod.rs @@ -4,6 +4,7 @@ pub mod chain_id; pub mod event; pub mod hash; pub mod identifier; +pub mod ledger_info; pub mod module_id; pub mod move_types; pub mod numbers; diff --git a/crates/aptos-rust-sdk-types/src/api_types/numbers.rs b/crates/aptos-rust-sdk-types/src/api_types/numbers.rs index 911231a..fbbdcef 100644 --- a/crates/aptos-rust-sdk-types/src/api_types/numbers.rs +++ b/crates/aptos-rust-sdk-types/src/api_types/numbers.rs @@ -25,3 +25,20 @@ impl<'de> Deserialize<'de> for U64 { .map_err(|err| D::Error::custom(err.to_string())) } } + +impl U64 { + /// Get the inner u64 value + pub fn as_u64(&self) -> u64 { + self.0 + } + + /// Convert into u64 + pub fn into_u64(self) -> u64 { + self.0 + } + + /// Create a new U64 from a u64 + pub fn new(value: u64) -> Self { + U64(value) + } +} diff --git a/crates/aptos-rust-sdk/src/client/builder.rs b/crates/aptos-rust-sdk/src/client/builder.rs index 11a46a1..3131b41 100644 --- a/crates/aptos-rust-sdk/src/client/builder.rs +++ b/crates/aptos-rust-sdk/src/client/builder.rs @@ -1,6 +1,6 @@ use crate::client::config::AptosNetwork; use crate::client::rest_api::AptosFullnodeClient; -use aptos_rust_sdk_types::headers::X_APTOS_CLIENT; +use aptos_rust_sdk_types::{api_types::ledger_info::LedgerInfo, headers::X_APTOS_CLIENT}; use aptos_rust_sdk_types::AptosResult; use reqwest::{ header::{self, HeaderMap, HeaderName, HeaderValue}, @@ -23,25 +23,22 @@ pub struct AptosClientBuilder { impl AptosClientBuilder { /// A hidden constructor, please use `AptosClient::builder()` to create - pub fn new(network: AptosNetwork) -> Self { - let mut headers = HeaderMap::new(); + pub fn new(network: AptosNetwork, headers: Option<&HeaderMap>) -> Self { + let mut headers = headers + .map(|h| h.clone()) + .unwrap_or_else(HeaderMap::new); + headers.insert( X_APTOS_CLIENT, HeaderValue::from_static(X_APTOS_SDK_HEADER_VALUE), ); - let mut client_builder = Self { + Self { rest_api_client_builder: ReqwestClient::builder(), network, timeout: Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECONDS), // Default to 5 seconds headers, - }; - - // TODO: This seems like a bit of a hack here and needs to be documented - if let Ok(key) = env::var("X_API_KEY") { - client_builder = client_builder.api_key(&key).unwrap(); } - client_builder } pub fn network(mut self, network: AptosNetwork) -> Self { @@ -70,16 +67,107 @@ impl AptosClientBuilder { Ok(self) } - pub fn build(self) -> AptosFullnodeClient { - AptosFullnodeClient { - network: self.network, - rest_client: self - .rest_api_client_builder - .default_headers(self.headers) - .timeout(self.timeout) - .cookie_store(true) - .build() - .unwrap(), - } + pub async fn build(self) -> Result { + let rest_client = self + .rest_api_client_builder + .default_headers(self.headers) + .timeout(self.timeout) + .cookie_store(true) + .build()?; + + // Fetch chain_id from RPC if not set + let network = if self.network.chain_id().is_some() { + self.network + } else { + let url = self.network.rest_url().join("v1")?; + let ledger_info: LedgerInfo = rest_client + .get(url) + .send() + .await? + .json() + .await?; + let chain_id = ledger_info.chain_id(); + self.network.with_chain_id(Some(chain_id)) + }; + + Ok(AptosFullnodeClient { + network, + rest_client, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use aptos_rust_sdk_types::api_types::chain_id::ChainId; + use url::Url; + + #[tokio::test] + async fn test_build_with_invalid_url() { + // Test with an invalid URL that cannot be parsed + // This should fail when trying to create the network + let invalid_url = match Url::parse("not-a-valid-url") { + Ok(url) => url, + Err(_) => { + // If URL parsing fails, we can't even create the network + // This is expected behavior - invalid URLs should fail at network creation + return; + } + }; + + // If we somehow get here, try to create a network with invalid URL + let network = AptosNetwork::new("invalid", invalid_url, None, None); + let builder = AptosClientBuilder::new(network, None); + + // Building should fail when trying to join the path + let result = builder.build().await; + assert!(result.is_err(), "Building with invalid URL should fail"); + } + + #[tokio::test] + async fn test_build_with_unreachable_url() { + // Test with a valid URL format but unreachable server + // Use a non-existent domain to ensure connection failure + let unreachable_url = Url::parse("http://this-domain-does-not-exist-12345.invalid:8080").unwrap(); + // Ensure chain_id is None so it tries to fetch from server + let network = AptosNetwork::new("unreachable", unreachable_url, None, None); + let builder = AptosClientBuilder::new(network, None); + + // Building should fail when trying to fetch chain_id from unreachable server + let result = builder.build().await; + assert!(result.is_err(), "Building with unreachable URL should fail"); + + // Check that the error is related to connection failure or DNS resolution + let error_msg = format!("{}", result.unwrap_err()); + assert!( + error_msg.contains("connection") || + error_msg.contains("refused") || + error_msg.contains("timeout") || + error_msg.contains("failed") || + error_msg.contains("error sending request") || + error_msg.contains("error decoding response body") || + error_msg.contains("dns") || + error_msg.contains("resolve"), + "Error should be related to connection/DNS failure, got: {}", + error_msg + ); + } + + #[tokio::test] + async fn test_build_with_unreachable_url_no_chain_id() { + // Test fetching chain_id from server when it's not set initially + // Use mainnet URL but without chain_id to test fetching chain_id from server + let base_url = AptosNetwork::mainnet().rest_url().clone(); + // Ensure chain_id is None so it tries to fetch from server + let network = AptosNetwork::new("test", base_url, None, None); + let builder = AptosClientBuilder::new(network, None); + + // Build should succeed and fetch chain_id from the server + let client = builder.build().await.expect("Should successfully build client"); + + // Verify that chain_id was fetched from the server + let chain_id = client.network.chain_id().expect("Chain id should be fetched from server"); + assert_eq!(chain_id, ChainId::Mainnet, "Chain id should match mainnet"); } } diff --git a/crates/aptos-rust-sdk/src/client/config.rs b/crates/aptos-rust-sdk/src/client/config.rs index 1f271bb..a2b6a59 100644 --- a/crates/aptos-rust-sdk/src/client/config.rs +++ b/crates/aptos-rust-sdk/src/client/config.rs @@ -1,3 +1,4 @@ +use aptos_rust_sdk_types::api_types::chain_id::ChainId; use url::Url; const MAINNET_REST_URL: &str = "https://api.mainnet.aptoslabs.com"; @@ -15,15 +16,17 @@ const LOCAL_INDEXER_URL: &str = "http://127.0.0.1:8090"; pub struct AptosNetwork { name: &'static str, rest_url: Url, - indexer_url: Url, + indexer_url: Option, + chain_id: Option, } impl AptosNetwork { - pub const fn new(name: &'static str, rest_url: Url, indexer_url: Url) -> AptosNetwork { + pub const fn new(name: &'static str, rest_url: Url, indexer_url: Option, chain_id: Option) -> AptosNetwork { AptosNetwork { name, rest_url, indexer_url, + chain_id, } } @@ -31,7 +34,8 @@ impl AptosNetwork { Self::new( "mainnet", Url::parse(MAINNET_REST_URL).unwrap(), - Url::parse(MAINNET_INDEXER_URL).unwrap(), + Some(Url::parse(MAINNET_INDEXER_URL).unwrap()), + Some(ChainId::Mainnet), ) } @@ -39,7 +43,8 @@ impl AptosNetwork { Self::new( "testnet", Url::parse(TESTNET_REST_URL).unwrap(), - Url::parse(TESTNET_INDEXER_URL).unwrap(), + Some(Url::parse(TESTNET_INDEXER_URL).unwrap()), + Some(ChainId::Testnet), ) } @@ -47,7 +52,8 @@ impl AptosNetwork { Self::new( "devnet", Url::parse(DEVNET_REST_URL).unwrap(), - Url::parse(DEVNET_INDEXER_URL).unwrap(), + Some(Url::parse(DEVNET_INDEXER_URL).unwrap()), + None, ) } @@ -55,7 +61,8 @@ impl AptosNetwork { Self::new( "localnet", Url::parse(LOCAL_REST_URL).unwrap(), - Url::parse(LOCAL_INDEXER_URL).unwrap(), + Some(Url::parse(LOCAL_INDEXER_URL).unwrap()), + Some(ChainId::Localnet), ) } @@ -67,7 +74,31 @@ impl AptosNetwork { &self.rest_url } - pub fn indexer_url(&self) -> &Url { - &self.indexer_url + pub fn indexer_url(&self) -> Option<&Url> { + self.indexer_url.as_ref() + } + + pub fn chain_id(&self) -> Option { + self.chain_id + } + + pub fn with_name(mut self, name: &'static str) -> Self { + self.name = name; + self + } + + pub fn with_rest_url(mut self, rest_url: Url) -> Self { + self.rest_url = rest_url; + self + } + + pub fn with_indexer_url(mut self, indexer_url: Option) -> Self { + self.indexer_url = indexer_url; + self + } + + pub fn with_chain_id(mut self, chain_id: Option) -> Self { + self.chain_id = chain_id; + self } } diff --git a/crates/aptos-rust-sdk/src/client/rest_api.rs b/crates/aptos-rust-sdk/src/client/rest_api.rs index c864ecf..7037019 100644 --- a/crates/aptos-rust-sdk/src/client/rest_api.rs +++ b/crates/aptos-rust-sdk/src/client/rest_api.rs @@ -24,7 +24,7 @@ pub struct AptosFullnodeClient { impl AptosFullnodeClient { /// Create a builder for the `AptosClient` pub fn builder(network: AptosNetwork) -> AptosClientBuilder { - AptosClientBuilder::new(network) + AptosClientBuilder::new(network, None) } /// Retrieve the network information for the client @@ -156,8 +156,6 @@ impl AptosFullnodeClient { .send() .await?; - println!("{:?}", response); - let parsable_response = ParsableResponse(response); parsable_response.parse_response().await } @@ -180,8 +178,8 @@ mod view_function_tests { if std::env::var("SKIP_NETWORK_TESTS").is_ok() { return; } - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + let client = builder.build().await.unwrap(); // Test view function with struct type argument (this is supported) let view_request = ViewRequest { @@ -220,8 +218,8 @@ mod view_function_tests { if std::env::var("SKIP_NETWORK_TESTS").is_ok() { return; } - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + let client = builder.build().await.unwrap(); // Test view function with no type arguments let view_request = ViewRequest { @@ -252,8 +250,8 @@ mod view_function_tests { if std::env::var("SKIP_NETWORK_TESTS").is_ok() { return; } - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + let client = builder.build().await.unwrap(); // Test view function with address argument let view_request = ViewRequest { @@ -286,8 +284,8 @@ mod view_function_tests { if std::env::var("SKIP_NETWORK_TESTS").is_ok() { return; } - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + let client = builder.build().await.unwrap(); // Test view function to check if account exists let view_request = ViewRequest { @@ -314,8 +312,8 @@ mod view_function_tests { if std::env::var("SKIP_NETWORK_TESTS").is_ok() { return; } - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + let client = builder.build().await.unwrap(); // Test with invalid function name let view_request = ViewRequest { diff --git a/crates/aptos-rust-sdk/src/tests/client/rest_api.rs b/crates/aptos-rust-sdk/src/tests/client/rest_api.rs index 5721346..37dff34 100644 --- a/crates/aptos-rust-sdk/src/tests/client/rest_api.rs +++ b/crates/aptos-rust-sdk/src/tests/client/rest_api.rs @@ -7,7 +7,7 @@ async fn test_rest_client() { return; } // TODO: Test against local testnet - let aptos_client = AptosFullnodeClient::builder(AptosNetwork::localnet()).build(); + let aptos_client = AptosFullnodeClient::builder(AptosNetwork::localnet()).build().await.unwrap(); let state = aptos_client .get_state() .await @@ -22,7 +22,7 @@ async fn test_get_by_version() { return; } // TODO: Test against local testnet - let aptos_client = AptosFullnodeClient::builder(AptosNetwork::localnet()).build(); + let aptos_client = AptosFullnodeClient::builder(AptosNetwork::localnet()).build().await.unwrap(); // Retrieve latest blockchain state let state = aptos_client diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index 7931daa..bf6d634 100644 --- a/crates/examples/Cargo.toml +++ b/crates/examples/Cargo.toml @@ -13,6 +13,7 @@ rand.workspace = true ed25519-dalek.workspace = true serde.workspace = true serde_json.workspace = true +anyhow.workspace = true [dev-dependencies] tokio = { workspace = true } @@ -20,7 +21,3 @@ tokio = { workspace = true } [[bin]] name = "type_parsing_example" path = "src/type_parsing_example.rs" - -[[bin]] -name = "view_function_example" -path = "src/view_function_example.rs" diff --git a/crates/examples/src/lib.rs b/crates/examples/src/lib.rs index 39ca5ed..c495d60 100644 --- a/crates/examples/src/lib.rs +++ b/crates/examples/src/lib.rs @@ -20,11 +20,11 @@ mod tests { use std::vec; #[tokio::test] - async fn submit_transaction() { - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); + async fn submit_transaction() -> Result<(), anyhow::Error> { + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + let client = builder.build().await?; - let state = client.get_state().await.unwrap(); + let state = client.get_state().await?; let mut seed = [0u8; 32]; let seed_bytes = @@ -32,26 +32,24 @@ mod tests { .unwrap(); // Remove the 0x prefix seed[..seed_bytes.len()].copy_from_slice(&seed_bytes); - let key = Ed25519PrivateKey::try_from(seed_bytes.as_slice()).unwrap(); + let key = Ed25519PrivateKey::try_from(seed_bytes.as_slice())?; let auth_key = AuthenticationKey::ed25519(&Ed25519PublicKey::from(&key)); let sender = auth_key.account_address(); println!("Sender: {:?}", sender); let resource = client .get_account_resources(sender.to_string()) - .await - .unwrap() + .await? .into_inner(); let sequence_number = resource .iter() .find(|r| r.type_ == "0x1::account::Account") - .unwrap() + .ok_or_else(|| anyhow::anyhow!("missing account resource"))? .data .get("sequence_number") - .unwrap() + .ok_or_else(|| anyhow::anyhow!("missing sequence number"))? .as_str() - .unwrap() - .parse::() - .unwrap(); + .ok_or_else(|| anyhow::anyhow!("missing sequence number"))? + .parse::()?; let payload = TransactionPayload::EntryFunction(EntryFunction::new( ModuleId::new(AccountAddress::ONE, "aptos_account".to_string()), "transfer".to_string(), @@ -73,7 +71,7 @@ mod tests { chain_id, ); - let message = raw_txn.generate_signing_message().unwrap(); + let message = raw_txn.generate_signing_message()?; let signature = key.sign_message(&message); @@ -82,7 +80,7 @@ mod tests { raw_txn.clone(), TransactionAuthenticator::single_sender(AccountAuthenticator::no_authenticator()), )) - .await; + .await?; println!("Simulate Transaction: {:?}", simulate_transaction); @@ -94,22 +92,22 @@ mod tests { .await; println!("Transaction: {:?}", transaction); + Ok(()) } #[tokio::test] - async fn submit_feepayer_transaction() { - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); + async fn submit_feepayer_transaction() -> Result<(), anyhow::Error> { + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + let client = builder.build().await?; - let state = client.get_state().await.unwrap(); + let state = client.get_state().await?; let mut seed = [0u8; 32]; let seed_bytes = - hex::decode("4aeeeb3f286caa91984d4a16d424786c7aa26947050b00e84ab7033f2aab0c2d") - .unwrap(); // Remove the 0x prefix + hex::decode("4aeeeb3f286caa91984d4a16d424786c7aa26947050b00e84ab7033f2aab0c2d")?; // Remove the 0x prefix seed[..seed_bytes.len()].copy_from_slice(&seed_bytes); - let fee_payer_key = Ed25519PrivateKey::try_from(seed_bytes.as_slice()).unwrap(); + let fee_payer_key = Ed25519PrivateKey::try_from(seed_bytes.as_slice())?; let fee_payer_address = AuthenticationKey::ed25519(&Ed25519PublicKey::from(&fee_payer_key)).account_address(); println!("Feepayer Address: {:?}", fee_payer_address.to_string()); @@ -122,8 +120,7 @@ mod tests { ModuleId::new( AccountAddress::from_str( "0x94bd6fa34dba07f935ea2288ba36d74aa5dda6ae541137844cc2f0af8b6b73f3", - ) - .unwrap(), + )?, "create_object".to_string(), ), "create".to_string(), @@ -152,7 +149,7 @@ mod tests { fee_payer_address, ); - let message = raw_txn_with_data.generate_signing_message().unwrap(); + let message = raw_txn_with_data.generate_signing_message()?; let txn_sender_signature = txn_sender_key.sign_message(&message); @@ -169,7 +166,7 @@ mod tests { AccountAuthenticator::no_authenticator(), ), )) - .await; + .await?; println!("Simulate Transaction: {:?}", simulate_transaction); let transaction = client .submit_transaction(SignedTransaction::new( @@ -188,22 +185,23 @@ mod tests { ), ), )) - .await; + .await?; println!("Transaction: {:?}", transaction); + Ok(()) } #[tokio::test] - async fn submit_multi_agent_transaction() { - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); + async fn submit_multi_agent_transaction() -> Result<(), anyhow::Error> { + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + let client = builder.build().await?; - let state = client.get_state().await.unwrap(); + let state = client.get_state().await?; let seed_bytes = hex::decode("4aeeeb3f286caa91984d4a16d424786c7aa26947050b00e84ab7033f2aab0c2d") - .unwrap(); + ?; - let key = Ed25519PrivateKey::try_from(seed_bytes.as_slice()).unwrap(); + let key = Ed25519PrivateKey::try_from(seed_bytes.as_slice())?; let auth_key = AuthenticationKey::ed25519(&Ed25519PublicKey::from(&key)); let sender = auth_key.account_address(); println!("Sender: {:?}", sender); @@ -217,28 +215,26 @@ mod tests { let resource = client .get_account_resources(sender.to_string()) - .await - .unwrap() + .await? .into_inner(); let sequence_number = resource .iter() .find(|r| r.type_ == "0x1::account::Account") - .unwrap() + .ok_or_else(|| anyhow::anyhow!("missing account resource"))? .data .get("sequence_number") - .unwrap() + .ok_or_else(|| anyhow::anyhow!("missing sequence number"))? .as_str() - .unwrap() - .parse::() - .unwrap(); + .ok_or_else(|| anyhow::anyhow!("missing sequence number"))? + .parse::()?; let payload = TransactionPayload::EntryFunction(EntryFunction::new( ModuleId::new( AccountAddress::from_str( "0x0d966e595a22a025302928fe9d6e3ac28c7f1b68c3a68015a4487f8a816ed239", ) - .unwrap(), + ?, "txn".to_string(), ), "multiAgentTxn".to_string(), @@ -264,7 +260,7 @@ mod tests { vec![secondary_address], ); - let message = raw_txn.generate_signing_message().unwrap(); + let message = raw_txn.generate_signing_message()?; let signature = key.sign_message(&message); @@ -277,7 +273,7 @@ mod tests { secondary_signers: vec![AccountAuthenticator::no_authenticator()], }, )) - .await; + .await?; println!("Simulate Transaction: {:?}", simulate_transaction); let transaction = client @@ -295,7 +291,8 @@ mod tests { }], }, )) - .await; + .await?; println!("Transaction: {:?}", transaction); + Ok(()) } } diff --git a/crates/examples/src/view_function_example.rs b/crates/examples/src/view_function_example.rs index 43a5d87..6c065a5 100644 --- a/crates/examples/src/view_function_example.rs +++ b/crates/examples/src/view_function_example.rs @@ -4,150 +4,36 @@ use aptos_rust_sdk_types::api_types::move_types::{MoveStructTag, MoveType}; use aptos_rust_sdk_types::api_types::view::ViewRequest; use serde_json::Value; -/// Example demonstrating how to use the view_function method -/// to call read-only functions on the Aptos blockchain -pub async fn view_function_examples() { - println!("=== View Function Examples ===\n"); - - // Create a client for testnet - let builder = AptosClientBuilder::new(AptosNetwork::testnet()); - let client = builder.build(); - - // Example 1: Get account balance (with struct type argument) - println!("1. Getting account balance..."); - let balance_request = ViewRequest { - function: "0x1::coin::balance".to_string(), - type_arguments: vec![MoveType::Struct(MoveStructTag { - address: "0x1".to_string(), - module: "aptos_coin".to_string(), - name: "AptosCoin".to_string(), - generic_type_params: vec![], - })], - arguments: vec![ - Value::String("0x1".to_string()), // Account address - ], - }; - - match client.view_function(balance_request).await { - Ok(response) => { - let balance = response.into_inner(); - println!(" Balance: {:?}", balance); - } - Err(e) => { - println!(" Error getting balance: {:?}", e); - } - } - - // Example 2: Get current timestamp (no type arguments) - println!("\n2. Getting current timestamp..."); - let timestamp_request = ViewRequest { - function: "0x1::timestamp::now_seconds".to_string(), - type_arguments: vec![], - arguments: vec![], - }; - - match client.view_function(timestamp_request).await { - Ok(response) => { - let timestamp = response.into_inner(); - println!(" Current timestamp: {:?}", timestamp); - } - Err(e) => { - println!(" Error getting timestamp: {:?}", e); - } - } - - // Example 3: Get account sequence number (no type arguments) - println!("\n3. Getting account sequence number..."); - let sequence_request = ViewRequest { - function: "0x1::account::get_sequence_number".to_string(), - type_arguments: vec![], - arguments: vec![ - Value::String("0x1".to_string()), // Account address - ], - }; - - match client.view_function(sequence_request).await { - Ok(response) => { - let sequence_number = response.into_inner(); - println!(" Sequence number: {:?}", sequence_number); - } - Err(e) => { - println!(" Error getting sequence number: {:?}", e); - } - } - - // Example 4: Check if account exists (no type arguments) - println!("\n4. Checking if account exists..."); - let exists_request = ViewRequest { - function: "0x1::account::exists_at".to_string(), - type_arguments: vec![], - arguments: vec![ - Value::String("0x1".to_string()), // Account address - ], - }; - - match client.view_function(exists_request).await { - Ok(response) => { - let exists = response.into_inner(); - println!(" Account exists: {:?}", exists); - } - Err(e) => { - println!(" Error checking account existence: {:?}", e); - } - } - - // Example 5: Error handling - invalid function - println!("\n5. Testing error handling with invalid function..."); - let invalid_request = ViewRequest { - function: "0x1::nonexistent::function".to_string(), - type_arguments: vec![], - arguments: vec![], - }; - - match client.view_function(invalid_request).await { - Ok(response) => { - println!(" Unexpected success: {:?}", response); - } - Err(e) => { - println!(" Expected error: {:?}", e); - } - } - - println!("\n=== View Function Examples Complete ==="); - println!( - "\nNote: The following types are NOT supported in view functions due to API limitations:" - ); - println!("- MoveType::Vector (fails with Reference/GenericTypeParam conversion errors)"); - println!("- MoveType::Bool, MoveType::U64, MoveType::Address as type arguments"); - println!("- Functions with generic type parameters that resolve to references"); - println!("\nOnly struct type arguments (like for coin::balance) and functions with no type arguments"); - println!("are reliably supported in the current API implementation."); -} - -/// Example showing how to create a reusable view function helper +/// Helper struct for view function operations pub struct ViewFunctionHelper { client: aptos_rust_sdk::client::rest_api::AptosFullnodeClient, } impl ViewFunctionHelper { - pub fn new(network: AptosNetwork) -> Self { - let builder = AptosClientBuilder::new(network); - let client = builder.build(); - Self { client } + pub async fn new(network: AptosNetwork) -> Result { + let builder = AptosClientBuilder::new(network, None); + let client = builder.build().await?; + Ok(Self { client: client }) } /// Helper method to get account balance + /// + /// # Arguments + /// * `address` - Account address + /// * `coin_type` - Coin module name (e.g., "aptos_coin") + /// * `coin_name` - Coin struct name (e.g., "AptosCoin") pub async fn get_balance( &self, address: &str, coin_type: &str, + coin_name: &str, ) -> Result> { let request = ViewRequest { function: "0x1::coin::balance".to_string(), type_arguments: vec![MoveType::Struct(MoveStructTag { address: "0x1".to_string(), module: coin_type.to_string(), - name: "Coin".to_string(), + name: coin_name.to_string(), generic_type_params: vec![], })], arguments: vec![Value::String(address.to_string())], @@ -197,35 +83,162 @@ impl ViewFunctionHelper { } } -/// Example demonstrating the helper usage -pub async fn view_function_helper_example() { - println!("=== View Function Helper Example ===\n"); +#[cfg(test)] +mod tests { + use super::*; + + /// Setup test client + async fn setup_client() -> aptos_rust_sdk::client::rest_api::AptosFullnodeClient { + let builder = AptosClientBuilder::new(AptosNetwork::testnet(), None); + builder.build().await.expect("Failed to create client") + } + + #[tokio::test] + async fn test_get_account_balance_success() { + // This test should succeed - getting balance for a valid account + let client = setup_client().await; + let request = ViewRequest { + function: "0x1::coin::balance".to_string(), + type_arguments: vec![MoveType::Struct(MoveStructTag { + address: "0x1".to_string(), + module: "aptos_coin".to_string(), + name: "AptosCoin".to_string(), + generic_type_params: vec![], + })], + arguments: vec![Value::String("0x1".to_string())], + }; + + let result = client.view_function(request).await; + assert!(result.is_ok(), "Getting account balance should succeed"); + + let response = result.unwrap(); + let balance = response.into_inner(); + assert!(balance.is_array(), "Balance should be returned as an array"); + } + + #[tokio::test] + async fn test_get_timestamp_success() { + // This test should succeed - getting current timestamp + let client = setup_client().await; + let request = ViewRequest { + function: "0x1::timestamp::now_seconds".to_string(), + type_arguments: vec![], + arguments: vec![], + }; + + let result = client.view_function(request).await; + assert!(result.is_ok(), "Getting timestamp should succeed"); + + let response = result.unwrap(); + let timestamp = response.into_inner(); + assert!(timestamp.is_array(), "Timestamp should be returned as an array"); + } + + #[tokio::test] + async fn test_get_sequence_number_success() { + // This test should succeed - getting sequence number for a valid account + let client = setup_client().await; + let request = ViewRequest { + function: "0x1::account::get_sequence_number".to_string(), + type_arguments: vec![], + arguments: vec![Value::String("0x1".to_string())], + }; + + let result = client.view_function(request).await; + assert!(result.is_ok(), "Getting sequence number should succeed"); + + let response = result.unwrap(); + let sequence = response.into_inner(); + assert!(sequence.is_array(), "Sequence number should be returned as an array"); + } + + #[tokio::test] + async fn test_account_exists_success() { + // This test should succeed - checking if account exists + let client = setup_client().await; + let request = ViewRequest { + function: "0x1::account::exists_at".to_string(), + type_arguments: vec![], + arguments: vec![Value::String("0x1".to_string())], + }; + + let result = client.view_function(request).await; + assert!(result.is_ok(), "Checking account existence should succeed"); + + let response = result.unwrap(); + let exists = response.into_inner(); + assert!(exists.is_array(), "Account existence should be returned as an array"); + } + + #[tokio::test] + async fn test_invalid_function_should_fail() { + // This test should fail - calling a non-existent function + let client = setup_client().await; + let request = ViewRequest { + function: "0x1::nonexistent::function".to_string(), + type_arguments: vec![], + arguments: vec![], + }; + + let result = client.view_function(request).await; + assert!(result.is_err(), "Calling invalid function should fail"); + } - let helper = ViewFunctionHelper::new(AptosNetwork::testnet()); + #[tokio::test] + async fn test_invalid_module_should_fail() { + // This test should fail - calling function from non-existent module + let client = setup_client().await; + let request = ViewRequest { + function: "0x1::invalid_module::some_function".to_string(), + type_arguments: vec![], + arguments: vec![], + }; - // Get balance - match helper.get_balance("0x1", "aptos_coin").await { - Ok(balance) => println!("Balance: {:?}", balance), - Err(e) => println!("Error getting balance: {:?}", e), + let result = client.view_function(request).await; + assert!(result.is_err(), "Calling function from invalid module should fail"); } - // Get sequence number - match helper.get_sequence_number("0x1").await { - Ok(seq) => println!("Sequence number: {:?}", seq), - Err(e) => println!("Error getting sequence number: {:?}", e), + #[tokio::test] + async fn test_helper_get_balance_success() { + // Test helper method - should succeed + let helper = ViewFunctionHelper::new(AptosNetwork::testnet()) + .await + .expect("Failed to create helper"); + + let result = helper.get_balance("0x1", "aptos_coin", "AptosCoin").await; + assert!(result.is_ok(), "Helper get_balance should succeed"); } - // Check if account exists - match helper.account_exists("0x1").await { - Ok(exists) => println!("Account exists: {:?}", exists), - Err(e) => println!("Error checking account existence: {:?}", e), + #[tokio::test] + async fn test_helper_get_sequence_number_success() { + // Test helper method - should succeed + let helper = ViewFunctionHelper::new(AptosNetwork::testnet()) + .await + .expect("Failed to create helper"); + + let result = helper.get_sequence_number("0x1").await; + assert!(result.is_ok(), "Helper get_sequence_number should succeed"); } - // Get current timestamp - match helper.get_timestamp().await { - Ok(timestamp) => println!("Current timestamp: {:?}", timestamp), - Err(e) => println!("Error getting timestamp: {:?}", e), + #[tokio::test] + async fn test_helper_account_exists_success() { + // Test helper method - should succeed + let helper = ViewFunctionHelper::new(AptosNetwork::testnet()) + .await + .expect("Failed to create helper"); + + let result = helper.account_exists("0x1").await; + assert!(result.is_ok(), "Helper account_exists should succeed"); } - println!("\n=== View Function Helper Example Complete ==="); + #[tokio::test] + async fn test_helper_get_timestamp_success() { + // Test helper method - should succeed + let helper = ViewFunctionHelper::new(AptosNetwork::testnet()) + .await + .expect("Failed to create helper"); + + let result = helper.get_timestamp().await; + assert!(result.is_ok(), "Helper get_timestamp should succeed"); + } } From 69f2c3e49bdf76e22d876dd1fb353bcff37f190c Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Mon, 10 Nov 2025 01:43:28 +0800 Subject: [PATCH 2/3] fmt --- .../src/api_types/ledger_info.rs | 29 +++++----- .../src/api_types/numbers.rs | 4 +- crates/aptos-rust-sdk/src/client/builder.rs | 54 +++++++++---------- crates/aptos-rust-sdk/src/client/config.rs | 9 +++- .../src/tests/client/rest_api.rs | 10 +++- crates/examples/src/lib.rs | 6 +-- crates/examples/src/view_function_example.rs | 38 ++++++++----- 7 files changed, 85 insertions(+), 65 deletions(-) diff --git a/crates/aptos-rust-sdk-types/src/api_types/ledger_info.rs b/crates/aptos-rust-sdk-types/src/api_types/ledger_info.rs index de286f4..1f04dad 100644 --- a/crates/aptos-rust-sdk-types/src/api_types/ledger_info.rs +++ b/crates/aptos-rust-sdk-types/src/api_types/ledger_info.rs @@ -9,35 +9,35 @@ pub struct LedgerInfo { /// The chain ID of the network #[serde(rename = "chain_id")] pub chain_id: u8, - + /// The current epoch number #[serde(rename = "epoch")] pub epoch: U64, - + /// The current ledger version #[serde(rename = "ledger_version")] pub ledger_version: U64, - + /// The oldest ledger version available #[serde(rename = "oldest_ledger_version")] pub oldest_ledger_version: U64, - + /// The ledger timestamp in microseconds #[serde(rename = "ledger_timestamp")] pub ledger_timestamp: U64, - + /// The role of the node (e.g., "full_node") #[serde(rename = "node_role")] pub node_role: String, - + /// The oldest block height available #[serde(rename = "oldest_block_height")] pub oldest_block_height: U64, - + /// The current block height #[serde(rename = "block_height")] pub block_height: U64, - + /// The git hash of the node software #[serde(rename = "git_hash")] pub git_hash: String, @@ -48,35 +48,34 @@ impl LedgerInfo { pub fn chain_id(&self) -> ChainId { ChainId::from_u8(self.chain_id) } - + /// Get the epoch as u64 pub fn epoch_u64(&self) -> u64 { self.epoch.as_u64() } - + /// Get the ledger version as u64 pub fn ledger_version_u64(&self) -> u64 { self.ledger_version.as_u64() } - + /// Get the oldest ledger version as u64 pub fn oldest_ledger_version_u64(&self) -> u64 { self.oldest_ledger_version.as_u64() } - + /// Get the ledger timestamp as u64 pub fn ledger_timestamp_u64(&self) -> u64 { self.ledger_timestamp.as_u64() } - + /// Get the oldest block height as u64 pub fn oldest_block_height_u64(&self) -> u64 { self.oldest_block_height.as_u64() } - + /// Get the block height as u64 pub fn block_height_u64(&self) -> u64 { self.block_height.as_u64() } } - diff --git a/crates/aptos-rust-sdk-types/src/api_types/numbers.rs b/crates/aptos-rust-sdk-types/src/api_types/numbers.rs index fbbdcef..e3b7f4e 100644 --- a/crates/aptos-rust-sdk-types/src/api_types/numbers.rs +++ b/crates/aptos-rust-sdk-types/src/api_types/numbers.rs @@ -31,12 +31,12 @@ impl U64 { pub fn as_u64(&self) -> u64 { self.0 } - + /// Convert into u64 pub fn into_u64(self) -> u64 { self.0 } - + /// Create a new U64 from a u64 pub fn new(value: u64) -> Self { U64(value) diff --git a/crates/aptos-rust-sdk/src/client/builder.rs b/crates/aptos-rust-sdk/src/client/builder.rs index 3131b41..b2984f1 100644 --- a/crates/aptos-rust-sdk/src/client/builder.rs +++ b/crates/aptos-rust-sdk/src/client/builder.rs @@ -1,7 +1,7 @@ use crate::client::config::AptosNetwork; use crate::client::rest_api::AptosFullnodeClient; -use aptos_rust_sdk_types::{api_types::ledger_info::LedgerInfo, headers::X_APTOS_CLIENT}; use aptos_rust_sdk_types::AptosResult; +use aptos_rust_sdk_types::{api_types::ledger_info::LedgerInfo, headers::X_APTOS_CLIENT}; use reqwest::{ header::{self, HeaderMap, HeaderName, HeaderValue}, Client as ReqwestClient, ClientBuilder as ReqwestClientBuilder, @@ -24,10 +24,8 @@ pub struct AptosClientBuilder { impl AptosClientBuilder { /// A hidden constructor, please use `AptosClient::builder()` to create pub fn new(network: AptosNetwork, headers: Option<&HeaderMap>) -> Self { - let mut headers = headers - .map(|h| h.clone()) - .unwrap_or_else(HeaderMap::new); - + let mut headers = headers.map(|h| h.clone()).unwrap_or_else(HeaderMap::new); + headers.insert( X_APTOS_CLIENT, HeaderValue::from_static(X_APTOS_SDK_HEADER_VALUE), @@ -80,12 +78,7 @@ impl AptosClientBuilder { self.network } else { let url = self.network.rest_url().join("v1")?; - let ledger_info: LedgerInfo = rest_client - .get(url) - .send() - .await? - .json() - .await?; + let ledger_info: LedgerInfo = rest_client.get(url).send().await?.json().await?; let chain_id = ledger_info.chain_id(); self.network.with_chain_id(Some(chain_id)) }; @@ -119,7 +112,7 @@ mod tests { // If we somehow get here, try to create a network with invalid URL let network = AptosNetwork::new("invalid", invalid_url, None, None); let builder = AptosClientBuilder::new(network, None); - + // Building should fail when trying to join the path let result = builder.build().await; assert!(result.is_err(), "Building with invalid URL should fail"); @@ -129,26 +122,27 @@ mod tests { async fn test_build_with_unreachable_url() { // Test with a valid URL format but unreachable server // Use a non-existent domain to ensure connection failure - let unreachable_url = Url::parse("http://this-domain-does-not-exist-12345.invalid:8080").unwrap(); + let unreachable_url = + Url::parse("http://this-domain-does-not-exist-12345.invalid:8080").unwrap(); // Ensure chain_id is None so it tries to fetch from server let network = AptosNetwork::new("unreachable", unreachable_url, None, None); let builder = AptosClientBuilder::new(network, None); - + // Building should fail when trying to fetch chain_id from unreachable server let result = builder.build().await; assert!(result.is_err(), "Building with unreachable URL should fail"); - + // Check that the error is related to connection failure or DNS resolution let error_msg = format!("{}", result.unwrap_err()); assert!( - error_msg.contains("connection") || - error_msg.contains("refused") || - error_msg.contains("timeout") || - error_msg.contains("failed") || - error_msg.contains("error sending request") || - error_msg.contains("error decoding response body") || - error_msg.contains("dns") || - error_msg.contains("resolve"), + error_msg.contains("connection") + || error_msg.contains("refused") + || error_msg.contains("timeout") + || error_msg.contains("failed") + || error_msg.contains("error sending request") + || error_msg.contains("error decoding response body") + || error_msg.contains("dns") + || error_msg.contains("resolve"), "Error should be related to connection/DNS failure, got: {}", error_msg ); @@ -162,12 +156,18 @@ mod tests { // Ensure chain_id is None so it tries to fetch from server let network = AptosNetwork::new("test", base_url, None, None); let builder = AptosClientBuilder::new(network, None); - + // Build should succeed and fetch chain_id from the server - let client = builder.build().await.expect("Should successfully build client"); - + let client = builder + .build() + .await + .expect("Should successfully build client"); + // Verify that chain_id was fetched from the server - let chain_id = client.network.chain_id().expect("Chain id should be fetched from server"); + let chain_id = client + .network + .chain_id() + .expect("Chain id should be fetched from server"); assert_eq!(chain_id, ChainId::Mainnet, "Chain id should match mainnet"); } } diff --git a/crates/aptos-rust-sdk/src/client/config.rs b/crates/aptos-rust-sdk/src/client/config.rs index a2b6a59..8e5ec12 100644 --- a/crates/aptos-rust-sdk/src/client/config.rs +++ b/crates/aptos-rust-sdk/src/client/config.rs @@ -21,7 +21,12 @@ pub struct AptosNetwork { } impl AptosNetwork { - pub const fn new(name: &'static str, rest_url: Url, indexer_url: Option, chain_id: Option) -> AptosNetwork { + pub const fn new( + name: &'static str, + rest_url: Url, + indexer_url: Option, + chain_id: Option, + ) -> AptosNetwork { AptosNetwork { name, rest_url, @@ -81,7 +86,7 @@ impl AptosNetwork { pub fn chain_id(&self) -> Option { self.chain_id } - + pub fn with_name(mut self, name: &'static str) -> Self { self.name = name; self diff --git a/crates/aptos-rust-sdk/src/tests/client/rest_api.rs b/crates/aptos-rust-sdk/src/tests/client/rest_api.rs index 37dff34..413136d 100644 --- a/crates/aptos-rust-sdk/src/tests/client/rest_api.rs +++ b/crates/aptos-rust-sdk/src/tests/client/rest_api.rs @@ -7,7 +7,10 @@ async fn test_rest_client() { return; } // TODO: Test against local testnet - let aptos_client = AptosFullnodeClient::builder(AptosNetwork::localnet()).build().await.unwrap(); + let aptos_client = AptosFullnodeClient::builder(AptosNetwork::localnet()) + .build() + .await + .unwrap(); let state = aptos_client .get_state() .await @@ -22,7 +25,10 @@ async fn test_get_by_version() { return; } // TODO: Test against local testnet - let aptos_client = AptosFullnodeClient::builder(AptosNetwork::localnet()).build().await.unwrap(); + let aptos_client = AptosFullnodeClient::builder(AptosNetwork::localnet()) + .build() + .await + .unwrap(); // Retrieve latest blockchain state let state = aptos_client diff --git a/crates/examples/src/lib.rs b/crates/examples/src/lib.rs index c495d60..25a8d02 100644 --- a/crates/examples/src/lib.rs +++ b/crates/examples/src/lib.rs @@ -198,8 +198,7 @@ mod tests { let state = client.get_state().await?; let seed_bytes = - hex::decode("4aeeeb3f286caa91984d4a16d424786c7aa26947050b00e84ab7033f2aab0c2d") - ?; + hex::decode("4aeeeb3f286caa91984d4a16d424786c7aa26947050b00e84ab7033f2aab0c2d")?; let key = Ed25519PrivateKey::try_from(seed_bytes.as_slice())?; let auth_key = AuthenticationKey::ed25519(&Ed25519PublicKey::from(&key)); @@ -233,8 +232,7 @@ mod tests { ModuleId::new( AccountAddress::from_str( "0x0d966e595a22a025302928fe9d6e3ac28c7f1b68c3a68015a4487f8a816ed239", - ) - ?, + )?, "txn".to_string(), ), "multiAgentTxn".to_string(), diff --git a/crates/examples/src/view_function_example.rs b/crates/examples/src/view_function_example.rs index 6c065a5..04a0982 100644 --- a/crates/examples/src/view_function_example.rs +++ b/crates/examples/src/view_function_example.rs @@ -17,7 +17,7 @@ impl ViewFunctionHelper { } /// Helper method to get account balance - /// + /// /// # Arguments /// * `address` - Account address /// * `coin_type` - Coin module name (e.g., "aptos_coin") @@ -110,7 +110,7 @@ mod tests { let result = client.view_function(request).await; assert!(result.is_ok(), "Getting account balance should succeed"); - + let response = result.unwrap(); let balance = response.into_inner(); assert!(balance.is_array(), "Balance should be returned as an array"); @@ -128,10 +128,13 @@ mod tests { let result = client.view_function(request).await; assert!(result.is_ok(), "Getting timestamp should succeed"); - + let response = result.unwrap(); let timestamp = response.into_inner(); - assert!(timestamp.is_array(), "Timestamp should be returned as an array"); + assert!( + timestamp.is_array(), + "Timestamp should be returned as an array" + ); } #[tokio::test] @@ -146,10 +149,13 @@ mod tests { let result = client.view_function(request).await; assert!(result.is_ok(), "Getting sequence number should succeed"); - + let response = result.unwrap(); let sequence = response.into_inner(); - assert!(sequence.is_array(), "Sequence number should be returned as an array"); + assert!( + sequence.is_array(), + "Sequence number should be returned as an array" + ); } #[tokio::test] @@ -164,10 +170,13 @@ mod tests { let result = client.view_function(request).await; assert!(result.is_ok(), "Checking account existence should succeed"); - + let response = result.unwrap(); let exists = response.into_inner(); - assert!(exists.is_array(), "Account existence should be returned as an array"); + assert!( + exists.is_array(), + "Account existence should be returned as an array" + ); } #[tokio::test] @@ -195,7 +204,10 @@ mod tests { }; let result = client.view_function(request).await; - assert!(result.is_err(), "Calling function from invalid module should fail"); + assert!( + result.is_err(), + "Calling function from invalid module should fail" + ); } #[tokio::test] @@ -204,7 +216,7 @@ mod tests { let helper = ViewFunctionHelper::new(AptosNetwork::testnet()) .await .expect("Failed to create helper"); - + let result = helper.get_balance("0x1", "aptos_coin", "AptosCoin").await; assert!(result.is_ok(), "Helper get_balance should succeed"); } @@ -215,7 +227,7 @@ mod tests { let helper = ViewFunctionHelper::new(AptosNetwork::testnet()) .await .expect("Failed to create helper"); - + let result = helper.get_sequence_number("0x1").await; assert!(result.is_ok(), "Helper get_sequence_number should succeed"); } @@ -226,7 +238,7 @@ mod tests { let helper = ViewFunctionHelper::new(AptosNetwork::testnet()) .await .expect("Failed to create helper"); - + let result = helper.account_exists("0x1").await; assert!(result.is_ok(), "Helper account_exists should succeed"); } @@ -237,7 +249,7 @@ mod tests { let helper = ViewFunctionHelper::new(AptosNetwork::testnet()) .await .expect("Failed to create helper"); - + let result = helper.get_timestamp().await; assert!(result.is_ok(), "Helper get_timestamp should succeed"); } From 8d424f4e646037502bfbea16c4a4c04244e3aa2d Mon Sep 17 00:00:00 2001 From: WGB5445 <919603023@qq.com> Date: Mon, 10 Nov 2025 14:05:50 +0800 Subject: [PATCH 3/3] Refactor AptosClientBuilder to simplify header handling - Updated the `new` method in `AptosClientBuilder` to accept an `Option` and utilize `unwrap_or_default()` for cleaner initialization of headers. --- crates/aptos-rust-sdk/src/client/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/aptos-rust-sdk/src/client/builder.rs b/crates/aptos-rust-sdk/src/client/builder.rs index b2984f1..faca611 100644 --- a/crates/aptos-rust-sdk/src/client/builder.rs +++ b/crates/aptos-rust-sdk/src/client/builder.rs @@ -23,8 +23,8 @@ pub struct AptosClientBuilder { impl AptosClientBuilder { /// A hidden constructor, please use `AptosClient::builder()` to create - pub fn new(network: AptosNetwork, headers: Option<&HeaderMap>) -> Self { - let mut headers = headers.map(|h| h.clone()).unwrap_or_else(HeaderMap::new); + pub fn new(network: AptosNetwork, headers: Option) -> Self { + let mut headers = headers.unwrap_or_default(); headers.insert( X_APTOS_CLIENT,