Skip to main content

aptos_sdk/api/
response.rs

1//! API response types.
2
3use crate::types::HashValue;
4use serde::{Deserialize, Serialize};
5
6/// A response from the Aptos API with headers metadata.
7#[derive(Debug, Clone)]
8pub struct AptosResponse<T> {
9    /// The response body.
10    pub data: T,
11    /// The ledger version at the time of the request.
12    pub ledger_version: Option<u64>,
13    /// The ledger timestamp in microseconds.
14    pub ledger_timestamp: Option<u64>,
15    /// The epoch number.
16    pub epoch: Option<u64>,
17    /// The block height.
18    pub block_height: Option<u64>,
19    /// The oldest ledger version available.
20    pub oldest_ledger_version: Option<u64>,
21    /// The cursor for pagination.
22    pub cursor: Option<String>,
23}
24
25impl<T> AptosResponse<T> {
26    /// Creates a new response with data only.
27    pub fn new(data: T) -> Self {
28        Self {
29            data,
30            ledger_version: None,
31            ledger_timestamp: None,
32            epoch: None,
33            block_height: None,
34            oldest_ledger_version: None,
35            cursor: None,
36        }
37    }
38
39    /// Returns the inner data.
40    pub fn into_inner(self) -> T {
41        self.data
42    }
43
44    /// Maps the inner data using a function.
45    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> AptosResponse<U> {
46        AptosResponse {
47            data: f(self.data),
48            ledger_version: self.ledger_version,
49            ledger_timestamp: self.ledger_timestamp,
50            epoch: self.epoch,
51            block_height: self.block_height,
52            oldest_ledger_version: self.oldest_ledger_version,
53            cursor: self.cursor,
54        }
55    }
56}
57
58/// Response when submitting a transaction.
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct PendingTransaction {
61    /// The transaction hash.
62    pub hash: HashValue,
63    /// The sender address.
64    pub sender: String,
65    /// The sequence number.
66    pub sequence_number: String,
67    /// Maximum gas amount.
68    pub max_gas_amount: String,
69    /// Gas unit price.
70    pub gas_unit_price: String,
71    /// Expiration timestamp.
72    pub expiration_timestamp_secs: String,
73}
74
75impl PendingTransaction {
76    /// Returns the transaction hash.
77    pub fn hash(&self) -> &HashValue {
78        &self.hash
79    }
80
81    /// Returns the sender address as a string.
82    pub fn sender(&self) -> &str {
83        &self.sender
84    }
85
86    /// Returns the sequence number.
87    ///
88    /// # Errors
89    /// Returns an error if the sequence number string cannot be parsed as u64.
90    pub fn sequence_number(&self) -> Result<u64, std::num::ParseIntError> {
91        self.sequence_number.parse()
92    }
93}
94
95/// Ledger information from the API.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct LedgerInfo {
98    /// The chain ID.
99    pub chain_id: u8,
100    /// The epoch number.
101    pub epoch: String,
102    /// The ledger version.
103    pub ledger_version: String,
104    /// The oldest ledger version.
105    pub oldest_ledger_version: String,
106    /// The ledger timestamp in microseconds.
107    pub ledger_timestamp: String,
108    /// The node role.
109    pub node_role: String,
110    /// The oldest block height.
111    pub oldest_block_height: String,
112    /// The block height.
113    pub block_height: String,
114    /// Git hash of the node.
115    pub git_hash: Option<String>,
116}
117
118impl LedgerInfo {
119    /// Returns the ledger version as u64.
120    ///
121    /// # Errors
122    /// Returns an error if the ledger version string cannot be parsed as u64.
123    pub fn version(&self) -> Result<u64, std::num::ParseIntError> {
124        self.ledger_version.parse()
125    }
126
127    /// Returns the block height as u64.
128    ///
129    /// # Errors
130    /// Returns an error if the block height string cannot be parsed as u64.
131    pub fn height(&self) -> Result<u64, std::num::ParseIntError> {
132        self.block_height.parse()
133    }
134
135    /// Returns the epoch as u64.
136    ///
137    /// # Errors
138    /// Returns an error if the epoch string cannot be parsed as u64.
139    pub fn epoch_num(&self) -> Result<u64, std::num::ParseIntError> {
140        self.epoch.parse()
141    }
142}
143
144/// Gas estimation response.
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct GasEstimation {
147    /// Deprioritized gas estimate.
148    pub deprioritized_gas_estimate: Option<u64>,
149    /// Normal gas estimate.
150    pub gas_estimate: u64,
151    /// Prioritized gas estimate.
152    pub prioritized_gas_estimate: Option<u64>,
153}
154
155impl GasEstimation {
156    /// Returns the recommended gas price.
157    pub fn recommended(&self) -> u64 {
158        self.gas_estimate
159    }
160
161    /// Returns the low gas price for non-urgent transactions.
162    pub fn low(&self) -> u64 {
163        self.deprioritized_gas_estimate.unwrap_or(self.gas_estimate)
164    }
165
166    /// Returns the high gas price for urgent transactions.
167    pub fn high(&self) -> u64 {
168        self.prioritized_gas_estimate.unwrap_or(self.gas_estimate)
169    }
170}
171
172/// Account data from the API.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct AccountData {
175    /// The sequence number.
176    pub sequence_number: String,
177    /// The authentication key.
178    pub authentication_key: String,
179}
180
181impl AccountData {
182    /// Returns the sequence number as u64.
183    ///
184    /// # Errors
185    /// Returns an error if the sequence number string cannot be parsed as u64.
186    pub fn sequence_number(&self) -> Result<u64, std::num::ParseIntError> {
187        self.sequence_number.parse()
188    }
189}
190
191/// A resource stored on chain.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct Resource {
194    /// The resource type.
195    #[serde(rename = "type")]
196    pub typ: String,
197    /// The resource data as JSON.
198    pub data: serde_json::Value,
199}
200
201/// A Move module stored on chain.
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct MoveModule {
204    /// The module bytecode as hex.
205    pub bytecode: String,
206    /// The module ABI.
207    pub abi: Option<MoveModuleABI>,
208}
209
210/// Move module ABI.
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct MoveModuleABI {
213    /// The module address.
214    pub address: String,
215    /// The module name.
216    pub name: String,
217    /// Exposed functions.
218    pub exposed_functions: Vec<MoveFunction>,
219    /// Structs defined in the module.
220    pub structs: Vec<MoveStructDef>,
221}
222
223/// A function defined in a Move module.
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct MoveFunction {
226    /// Function name.
227    pub name: String,
228    /// Visibility.
229    pub visibility: String,
230    /// Whether this is an entry function.
231    pub is_entry: bool,
232    /// Whether this is a view function.
233    pub is_view: bool,
234    /// Generic type parameters.
235    pub generic_type_params: Vec<MoveFunctionGenericTypeParam>,
236    /// Function parameters.
237    pub params: Vec<String>,
238    /// Return types.
239    #[serde(rename = "return")]
240    pub returns: Vec<String>,
241}
242
243/// Generic type parameter in a function.
244#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct MoveFunctionGenericTypeParam {
246    /// Constraints on the type parameter.
247    pub constraints: Vec<String>,
248}
249
250/// A struct defined in a Move module.
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct MoveStructDef {
253    /// Struct name.
254    pub name: String,
255    /// Whether this is a native struct.
256    pub is_native: bool,
257    /// Abilities of the struct.
258    pub abilities: Vec<String>,
259    /// Generic type parameters.
260    pub generic_type_params: Vec<MoveStructGenericTypeParam>,
261    /// Fields of the struct.
262    pub fields: Vec<MoveStructField>,
263}
264
265/// Generic type parameter in a struct.
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct MoveStructGenericTypeParam {
268    /// Constraints on the type parameter.
269    pub constraints: Vec<String>,
270}
271
272/// A field in a Move struct.
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct MoveStructField {
275    /// Field name.
276    pub name: String,
277    /// Field type.
278    #[serde(rename = "type")]
279    pub typ: String,
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn test_aptos_response() {
288        let response = AptosResponse::new(42);
289        assert_eq!(response.into_inner(), 42);
290    }
291
292    #[test]
293    fn test_aptos_response_map() {
294        let response = AptosResponse::new(42);
295        let mapped = response.map(|x| x.to_string());
296        assert_eq!(mapped.into_inner(), "42");
297    }
298
299    #[test]
300    fn test_aptos_response_preserves_metadata() {
301        let mut response = AptosResponse::new(42);
302        response.ledger_version = Some(100);
303        response.epoch = Some(5);
304        response.block_height = Some(1000);
305        response.cursor = Some("abc".to_string());
306
307        let mapped = response.map(|x| x * 2);
308        assert_eq!(mapped.data, 84);
309        assert_eq!(mapped.ledger_version, Some(100));
310        assert_eq!(mapped.epoch, Some(5));
311        assert_eq!(mapped.block_height, Some(1000));
312        assert_eq!(mapped.cursor, Some("abc".to_string()));
313    }
314
315    #[test]
316    fn test_gas_estimation() {
317        let gas = GasEstimation {
318            deprioritized_gas_estimate: Some(50),
319            gas_estimate: 100,
320            prioritized_gas_estimate: Some(150),
321        };
322        assert_eq!(gas.low(), 50);
323        assert_eq!(gas.recommended(), 100);
324        assert_eq!(gas.high(), 150);
325    }
326
327    #[test]
328    fn test_gas_estimation_defaults() {
329        let gas = GasEstimation {
330            deprioritized_gas_estimate: None,
331            gas_estimate: 100,
332            prioritized_gas_estimate: None,
333        };
334        assert_eq!(gas.low(), 100);
335        assert_eq!(gas.recommended(), 100);
336        assert_eq!(gas.high(), 100);
337    }
338
339    #[test]
340    fn test_pending_transaction_deserialization() {
341        let json = r#"{
342            "hash": "0x0000000000000000000000000000000000000000000000000000000000000001",
343            "sender": "0x1",
344            "sequence_number": "42",
345            "max_gas_amount": "100000",
346            "gas_unit_price": "100",
347            "expiration_timestamp_secs": "1000000000"
348        }"#;
349        let pending: PendingTransaction = serde_json::from_str(json).unwrap();
350        assert_eq!(pending.sender(), "0x1");
351        assert_eq!(pending.sequence_number().unwrap(), 42);
352    }
353
354    #[test]
355    fn test_ledger_info_deserialization() {
356        let json = r#"{
357            "chain_id": 2,
358            "epoch": "100",
359            "ledger_version": "12345",
360            "oldest_ledger_version": "0",
361            "ledger_timestamp": "1000000000",
362            "node_role": "full_node",
363            "oldest_block_height": "0",
364            "block_height": "5000"
365        }"#;
366        let info: LedgerInfo = serde_json::from_str(json).unwrap();
367        assert_eq!(info.chain_id, 2);
368        assert_eq!(info.version().unwrap(), 12345);
369        assert_eq!(info.height().unwrap(), 5000);
370        assert_eq!(info.epoch_num().unwrap(), 100);
371    }
372
373    #[test]
374    fn test_account_data_deserialization() {
375        let json = r#"{
376            "sequence_number": "10",
377            "authentication_key": "0x1234"
378        }"#;
379        let account: AccountData = serde_json::from_str(json).unwrap();
380        assert_eq!(account.sequence_number().unwrap(), 10);
381    }
382
383    #[test]
384    fn test_resource_deserialization() {
385        let json = r#"{
386            "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>",
387            "data": {"coin": {"value": "1000"}}
388        }"#;
389        let resource: Resource = serde_json::from_str(json).unwrap();
390        assert_eq!(
391            resource.typ,
392            "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
393        );
394    }
395
396    #[test]
397    fn test_move_module_abi_deserialization() {
398        let json = r#"{
399            "address": "0x1",
400            "name": "coin",
401            "exposed_functions": [
402                {
403                    "name": "transfer",
404                    "visibility": "public",
405                    "is_entry": true,
406                    "is_view": false,
407                    "generic_type_params": [],
408                    "params": ["&signer", "address", "u64"],
409                    "return": []
410                }
411            ],
412            "structs": []
413        }"#;
414        let abi: MoveModuleABI = serde_json::from_str(json).unwrap();
415        assert_eq!(abi.name, "coin");
416        assert_eq!(abi.exposed_functions.len(), 1);
417        assert!(abi.exposed_functions[0].is_entry);
418    }
419
420    #[test]
421    fn test_move_struct_def_deserialization() {
422        let json = r#"{
423            "name": "CoinStore",
424            "is_native": false,
425            "abilities": ["key"],
426            "generic_type_params": [
427                {"constraints": []}
428            ],
429            "fields": [
430                {"name": "coin", "type": "0x1::coin::Coin<T0>"}
431            ]
432        }"#;
433        let struct_def: MoveStructDef = serde_json::from_str(json).unwrap();
434        assert_eq!(struct_def.name, "CoinStore");
435        assert!(!struct_def.is_native);
436        assert_eq!(struct_def.abilities, vec!["key"]);
437        assert_eq!(struct_def.fields.len(), 1);
438    }
439}