use crate::types::HashValue;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct AptosResponse<T> {
pub data: T,
pub ledger_version: Option<u64>,
pub ledger_timestamp: Option<u64>,
pub epoch: Option<u64>,
pub block_height: Option<u64>,
pub oldest_ledger_version: Option<u64>,
pub cursor: Option<String>,
}
impl<T> AptosResponse<T> {
pub fn new(data: T) -> Self {
Self {
data,
ledger_version: None,
ledger_timestamp: None,
epoch: None,
block_height: None,
oldest_ledger_version: None,
cursor: None,
}
}
pub fn into_inner(self) -> T {
self.data
}
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> AptosResponse<U> {
AptosResponse {
data: f(self.data),
ledger_version: self.ledger_version,
ledger_timestamp: self.ledger_timestamp,
epoch: self.epoch,
block_height: self.block_height,
oldest_ledger_version: self.oldest_ledger_version,
cursor: self.cursor,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingTransaction {
pub hash: HashValue,
pub sender: String,
pub sequence_number: String,
pub max_gas_amount: String,
pub gas_unit_price: String,
pub expiration_timestamp_secs: String,
}
impl PendingTransaction {
pub fn hash(&self) -> &HashValue {
&self.hash
}
pub fn sender(&self) -> &str {
&self.sender
}
pub fn sequence_number(&self) -> Result<u64, std::num::ParseIntError> {
self.sequence_number.parse()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LedgerInfo {
pub chain_id: u8,
pub epoch: String,
pub ledger_version: String,
pub oldest_ledger_version: String,
pub ledger_timestamp: String,
pub node_role: String,
pub oldest_block_height: String,
pub block_height: String,
pub git_hash: Option<String>,
}
impl LedgerInfo {
pub fn version(&self) -> Result<u64, std::num::ParseIntError> {
self.ledger_version.parse()
}
pub fn height(&self) -> Result<u64, std::num::ParseIntError> {
self.block_height.parse()
}
pub fn epoch_num(&self) -> Result<u64, std::num::ParseIntError> {
self.epoch.parse()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasEstimation {
pub deprioritized_gas_estimate: Option<u64>,
pub gas_estimate: u64,
pub prioritized_gas_estimate: Option<u64>,
}
impl GasEstimation {
pub fn recommended(&self) -> u64 {
self.gas_estimate
}
pub fn low(&self) -> u64 {
self.deprioritized_gas_estimate.unwrap_or(self.gas_estimate)
}
pub fn high(&self) -> u64 {
self.prioritized_gas_estimate.unwrap_or(self.gas_estimate)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountData {
pub sequence_number: String,
pub authentication_key: String,
}
impl AccountData {
pub fn sequence_number(&self) -> Result<u64, std::num::ParseIntError> {
self.sequence_number.parse()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Resource {
#[serde(rename = "type")]
pub typ: String,
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveModule {
pub bytecode: String,
pub abi: Option<MoveModuleABI>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveModuleABI {
pub address: String,
pub name: String,
pub exposed_functions: Vec<MoveFunction>,
pub structs: Vec<MoveStructDef>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveFunction {
pub name: String,
pub visibility: String,
pub is_entry: bool,
pub is_view: bool,
pub generic_type_params: Vec<MoveFunctionGenericTypeParam>,
pub params: Vec<String>,
#[serde(rename = "return")]
pub returns: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveFunctionGenericTypeParam {
pub constraints: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveStructDef {
pub name: String,
pub is_native: bool,
pub abilities: Vec<String>,
pub generic_type_params: Vec<MoveStructGenericTypeParam>,
pub fields: Vec<MoveStructField>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveStructGenericTypeParam {
pub constraints: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveStructField {
pub name: String,
#[serde(rename = "type")]
pub typ: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_aptos_response() {
let response = AptosResponse::new(42);
assert_eq!(response.into_inner(), 42);
}
#[test]
fn test_aptos_response_map() {
let response = AptosResponse::new(42);
let mapped = response.map(|x| x.to_string());
assert_eq!(mapped.into_inner(), "42");
}
#[test]
fn test_aptos_response_preserves_metadata() {
let mut response = AptosResponse::new(42);
response.ledger_version = Some(100);
response.epoch = Some(5);
response.block_height = Some(1000);
response.cursor = Some("abc".to_string());
let mapped = response.map(|x| x * 2);
assert_eq!(mapped.data, 84);
assert_eq!(mapped.ledger_version, Some(100));
assert_eq!(mapped.epoch, Some(5));
assert_eq!(mapped.block_height, Some(1000));
assert_eq!(mapped.cursor, Some("abc".to_string()));
}
#[test]
fn test_gas_estimation() {
let gas = GasEstimation {
deprioritized_gas_estimate: Some(50),
gas_estimate: 100,
prioritized_gas_estimate: Some(150),
};
assert_eq!(gas.low(), 50);
assert_eq!(gas.recommended(), 100);
assert_eq!(gas.high(), 150);
}
#[test]
fn test_gas_estimation_defaults() {
let gas = GasEstimation {
deprioritized_gas_estimate: None,
gas_estimate: 100,
prioritized_gas_estimate: None,
};
assert_eq!(gas.low(), 100);
assert_eq!(gas.recommended(), 100);
assert_eq!(gas.high(), 100);
}
#[test]
fn test_pending_transaction_deserialization() {
let json = r#"{
"hash": "0x0000000000000000000000000000000000000000000000000000000000000001",
"sender": "0x1",
"sequence_number": "42",
"max_gas_amount": "100000",
"gas_unit_price": "100",
"expiration_timestamp_secs": "1000000000"
}"#;
let pending: PendingTransaction = serde_json::from_str(json).unwrap();
assert_eq!(pending.sender(), "0x1");
assert_eq!(pending.sequence_number().unwrap(), 42);
}
#[test]
fn test_ledger_info_deserialization() {
let json = r#"{
"chain_id": 2,
"epoch": "100",
"ledger_version": "12345",
"oldest_ledger_version": "0",
"ledger_timestamp": "1000000000",
"node_role": "full_node",
"oldest_block_height": "0",
"block_height": "5000"
}"#;
let info: LedgerInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.chain_id, 2);
assert_eq!(info.version().unwrap(), 12345);
assert_eq!(info.height().unwrap(), 5000);
assert_eq!(info.epoch_num().unwrap(), 100);
}
#[test]
fn test_account_data_deserialization() {
let json = r#"{
"sequence_number": "10",
"authentication_key": "0x1234"
}"#;
let account: AccountData = serde_json::from_str(json).unwrap();
assert_eq!(account.sequence_number().unwrap(), 10);
}
#[test]
fn test_resource_deserialization() {
let json = r#"{
"type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>",
"data": {"coin": {"value": "1000"}}
}"#;
let resource: Resource = serde_json::from_str(json).unwrap();
assert_eq!(
resource.typ,
"0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
);
}
#[test]
fn test_move_module_abi_deserialization() {
let json = r#"{
"address": "0x1",
"name": "coin",
"exposed_functions": [
{
"name": "transfer",
"visibility": "public",
"is_entry": true,
"is_view": false,
"generic_type_params": [],
"params": ["&signer", "address", "u64"],
"return": []
}
],
"structs": []
}"#;
let abi: MoveModuleABI = serde_json::from_str(json).unwrap();
assert_eq!(abi.name, "coin");
assert_eq!(abi.exposed_functions.len(), 1);
assert!(abi.exposed_functions[0].is_entry);
}
#[test]
fn test_move_struct_def_deserialization() {
let json = r#"{
"name": "CoinStore",
"is_native": false,
"abilities": ["key"],
"generic_type_params": [
{"constraints": []}
],
"fields": [
{"name": "coin", "type": "0x1::coin::Coin<T0>"}
]
}"#;
let struct_def: MoveStructDef = serde_json::from_str(json).unwrap();
assert_eq!(struct_def.name, "CoinStore");
assert!(!struct_def.is_native);
assert_eq!(struct_def.abilities, vec!["key"]);
assert_eq!(struct_def.fields.len(), 1);
}
}