diff --git a/.gitignore b/.gitignore index 196e176..7fec66e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,11 @@ Cargo.lock # Added by cargo /target + +keys.txt + +# Ignore IntelliJ IDEA project files +.idea/ + +# Ignore Visual Studio Code project files +.vscode/ diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 87985c9..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'stellar-rust-sdk'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=stellar-rust-sdk" - ], - "filter": { - "name": "stellar-rust-sdk", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 39e9fc0..92d204e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,9 @@ edition = "2021" reqwest = { version = "0.11.4", features = ["json"] } url = "2.2.2" tokio = { version = "1.15.0", features = ["full"] } -xdr-codec = "0.4.4" -xdrgen = "0.4.4" - -[build-dependencies] \ No newline at end of file +stellar-xdr = { version = "20.0.0-rc1", features = ["base64"] } +serde_json = "1.0.107" +serde = { version = "1.0.188", features = ["derive"] } +derive-getters = "0.3.0" +hex = "0.4.3" +base64 = "0.21.4" diff --git a/src/accounts/accounts_request.rs b/src/accounts/accounts_request.rs index 3e4e2ad..b6200d7 100644 --- a/src/accounts/accounts_request.rs +++ b/src/accounts/accounts_request.rs @@ -1,40 +1,7 @@ use crate::models::*; -/// The asset type -/// Native - The native asset -/// Issued - An issued asset -/// [AccountsRequest](struct.AccountsRequest.html) -pub enum AssetType { - Native, - Issued, -} - -impl std::fmt::Display for AssetType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - AssetType::Native => write!(f, "native"), - AssetType::Issued => write!(f, "issued"), - } - } -} - -/// The order of the records -/// Asc - Ascending order -/// Desc - Descending order -/// [AccountsRequest](struct.AccountsRequest.html) -pub enum Order { - Asc, - Desc, -} - -impl std::fmt::Display for Order { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Order::Asc => write!(f, "asc"), - Order::Desc => write!(f, "desc"), - } - } -} +use super::super::Order; +use super::super::AssetType; /// AccountsRequest is the request object for the /accounts endpoint /// [More Details](https://www.stellar.org/developers/horizon/reference/endpoints/accounts.html "Accounts") @@ -168,8 +135,8 @@ impl AccountsRequest { /// # Returns /// The request object /// [AccountsRequest](struct.AccountsRequest.html) - pub fn set_sponsor(&mut self, sponsor: &str) -> &mut Self { - self.sponsor = Some(sponsor.to_owned()); + pub fn set_sponsor(&mut self, sponsor: impl Into) -> &mut Self { + self.sponsor = Some(sponsor.into()); self } diff --git a/src/accounts/accounts_response.rs b/src/accounts/accounts_response.rs index 6d4c9de..89b9e74 100644 --- a/src/accounts/accounts_response.rs +++ b/src/accounts/accounts_response.rs @@ -1,7 +1,258 @@ -pub struct AccountsResponse {} +use serde::{Deserialize, Serialize}; -impl Default for AccountsResponse { - fn default() -> Self { - Self {} +use crate::models::Response; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct SelfLink { + href: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Links { + #[serde(rename = "self")] + self_link: SelfLink, + next: Option, + prev: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Balances { + balance: String, + buying_liabilities: String, + selling_liabilities: String, + asset_type: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Thresholds { + low_threshold: i32, + med_threshold: i32, + high_threshold: i32, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Flags { + auth_required: bool, + auth_revocable: bool, + auth_immutable: bool, + auth_clawback_enabled: bool, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Signers { + weight: i32, + key: String, + #[serde(rename = "type")] + signer_type: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Record { + #[serde(rename = "_links")] + links: Links, + id: String, + account_id: String, + sequence: String, + subentry_count: i32, + last_modified_ledger: i64, + last_modified_time: String, + thresholds: Thresholds, + flags: Flags, + balances: Vec, + signers: Vec, + data: serde_json::Value, + num_sponsoring: i32, + num_sponsored: i32, + paging_token: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Embedded { + records: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct AccountsResponse { + _links: Links, + _embedded: Embedded, +} + +impl Response for AccountsResponse { + fn from_json(json: String) -> Result { + serde_json::from_str(&json).map_err(|e| e.to_string()) } } + +impl AccountsResponse { + pub fn get__links(&self) -> Links { + self._links.clone() + } + + pub fn get__embedded(&self) -> Embedded { + self._embedded.clone() + } +} + +impl Links { + pub fn get_self_link(&self) -> SelfLink { + self.self_link.clone() + } + + pub fn get_next(&self) -> Option { + self.next.clone() + } + + pub fn get_prev(&self) -> Option { + self.prev.clone() + } +} + +impl Embedded { + pub fn get_records(&self) -> Vec { + self.records.clone() + } + + pub fn get_single_record(&self, index: usize) -> Record { + self.records[index].clone() + } +} + +impl SelfLink { + pub fn get_href(&self) -> String { + self.href.clone() + } +} + +impl Record { + pub fn get__links(&self) -> Links { + self.links.clone() + } + + pub fn get_id(&self) -> String { + self.id.clone() + } + + pub fn get_account_id(&self) -> String { + self.account_id.clone() + } + + pub fn get_sequence(&self) -> String { + self.sequence.clone() + } + + pub fn get_subentry_count(&self) -> i32 { + self.subentry_count.clone() + } + + pub fn get_last_modified_ledger(&self) -> i64 { + self.last_modified_ledger.clone() + } + + pub fn get_last_modified_time(&self) -> String { + self.last_modified_time.clone() + } + + pub fn get_thresholds(&self) -> Thresholds { + self.thresholds.clone() + } + + pub fn get_flags(&self) -> Flags { + self.flags.clone() + } + + pub fn get_balances(&self) -> Vec { + self.balances.clone() + } + + pub fn get_single_balance(&self, index: usize) -> Balances { + self.balances[index].clone() + } + + pub fn get_signers(&self) -> Vec { + self.signers.clone() + } + + pub fn get_single_signer(&self, index: usize) -> Signers { + self.signers[index].clone() + } + + pub fn get_data(&self) -> serde_json::Value { + self.data.clone() + } + + pub fn get_num_sponsoring(&self) -> i32 { + self.num_sponsoring.clone() + } + + pub fn get_num_sponsored(&self) -> i32 { + self.num_sponsored.clone() + } + + pub fn get_paging_token(&self) -> String { + self.paging_token.clone() + } +} + +impl Thresholds { + pub fn get_low_threshold(&self) -> i32 { + self.low_threshold.clone() + } + + pub fn get_med_threshold(&self) -> i32 { + self.med_threshold.clone() + } + + pub fn get_high_threshold(&self) -> i32 { + self.high_threshold.clone() + } +} + +impl Balances { + pub fn get_balance(&self) -> String { + self.balance.clone() + } + + pub fn get_buying_liabilities(&self) -> String { + self.buying_liabilities.clone() + } + + pub fn get_selling_liabilities(&self) -> String { + self.selling_liabilities.clone() + } + + pub fn get_asset_type(&self) -> String { + self.asset_type.clone() + } +} + +impl Flags { + pub fn get_auth_required(&self) -> bool { + self.auth_required.clone() + } + + pub fn get_auth_revocable(&self) -> bool { + self.auth_revocable.clone() + } + + pub fn get_auth_immutable(&self) -> bool { + self.auth_immutable.clone() + } + + pub fn get_auth_clawback_enabled(&self) -> bool { + self.auth_clawback_enabled.clone() + } +} + +impl Signers { + pub fn get_weight(&self) -> i32 { + self.weight.clone() + } + + pub fn get_key(&self) -> String { + self.key.clone() + } + + pub fn get_type(&self) -> String { + self.signer_type.clone() + } +} \ No newline at end of file diff --git a/src/accounts/single_account_request.rs b/src/accounts/single_account_request.rs index 71ee2fc..762ba14 100644 --- a/src/accounts/single_account_request.rs +++ b/src/accounts/single_account_request.rs @@ -44,7 +44,8 @@ impl Request for SingleAccountRequest { } impl SingleAccountRequest { - pub fn set_account_id(&mut self, account_id: String) { + pub fn set_account_id(&mut self, account_id: String) -> &mut Self{ self.account_id = Some(account_id); + self } } diff --git a/src/accounts/single_account_response.rs b/src/accounts/single_account_response.rs index 9877a97..2a6a5fd 100644 --- a/src/accounts/single_account_response.rs +++ b/src/accounts/single_account_response.rs @@ -1,7 +1,267 @@ -pub struct SingleAccountsResponse {} +extern crate serde; +extern crate serde_json; -impl Default for SingleAccountsResponse { - fn default() -> Self { - Self {} +use serde::Deserialize; + +use crate::models::Response; + +#[derive(Debug, Deserialize, Clone)] +pub struct Links { + #[serde(rename = "self")] + self_link: SelfLink, + transactions: Link, + operations: Link, + payments: Link, + effects: Link, + offers: Link, + trades: Link, + data: Link, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct SelfLink { + href: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Link { + href: String, + templated: bool, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Thresholds { + low_threshold: u32, + med_threshold: u32, + high_threshold: u32, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Flags { + auth_required: bool, + auth_revocable: bool, + auth_immutable: bool, + auth_clawback_enabled: bool, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Balance { + balance: String, + buying_liabilities: String, + selling_liabilities: String, + asset_type: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Signer { + weight: u32, + key: String, + #[serde(rename = "type")] + type_: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Data {} + +#[derive(Debug, Deserialize, Clone)] +pub struct SingleAccountsResponse { + _links: Links, + id: String, + account_id: String, + sequence: String, + subentry_count: u32, + last_modified_ledger: u64, + last_modified_time: String, + thresholds: Thresholds, + flags: Flags, + balances: Vec, + signers: Vec, + data: Option, + num_sponsoring: u32, + num_sponsored: u32, + paging_token: String, +} + +impl Response for SingleAccountsResponse { + fn from_json(json: String) -> Result { + serde_json::from_str(&json).map_err(|e| e.to_string()) + } +} + +impl SelfLink { + pub fn get_href(&self) -> String { + self.href.clone() + } +} + +impl Link { + pub fn get_href(&self) -> String { + self.href.clone() + } + + pub fn get_templated(&self) -> bool { + self.templated.clone() } } + +impl Links { + pub fn get_self_link(&self) -> SelfLink { + self.self_link.clone() + } + + pub fn get_transactions(&self) -> Link { + self.transactions.clone() + } + + pub fn get_operations(&self) -> Link { + self.operations.clone() + } + + pub fn get_payments(&self) -> Link { + self.payments.clone() + } + + pub fn get_effects(&self) -> Link { + self.effects.clone() + } + + pub fn get_offers(&self) -> Link { + self.offers.clone() + } + + pub fn get_trades(&self) -> Link { + self.trades.clone() + } + + pub fn get_data(&self) -> Link { + self.data.clone() + } +} + +impl Thresholds { + pub fn get_low_threshold(&self) -> u32 { + self.low_threshold.clone() + } + + pub fn get_med_threshold(&self) -> u32 { + self.med_threshold.clone() + } + + pub fn get_high_threshold(&self) -> u32 { + self.high_threshold.clone() + } +} + +impl Flags { + pub fn get_auth_required(&self) -> bool { + self.auth_required.clone() + } + + pub fn get_auth_revocable(&self) -> bool { + self.auth_revocable.clone() + } + + pub fn get_auth_immutable(&self) -> bool { + self.auth_immutable.clone() + } + + pub fn get_auth_clawback_enabled(&self) -> bool { + self.auth_clawback_enabled.clone() + } +} + +impl Balance { + pub fn get_balance(&self) -> String { + self.balance.clone() + } + + pub fn get_buying_liabilities(&self) -> String { + self.buying_liabilities.clone() + } + + pub fn get_selling_liabilities(&self) -> String { + self.selling_liabilities.clone() + } + + pub fn get_asset_type(&self) -> String { + self.asset_type.clone() + } +} + +impl Signer { + pub fn get_weight(&self) -> u32 { + self.weight.clone() + } + + pub fn get_key(&self) -> String { + self.key.clone() + } + + pub fn get_type(&self) -> String { + self.type_.clone() + } +} + +impl Data { + pub fn get_value(&self) -> String { + "".to_string() + } +} + +impl SingleAccountsResponse { + pub fn get_id(&self) -> String { + self.id.clone() + } + + pub fn get_account_id(&self) -> String { + self.account_id.clone() + } + + pub fn get_sequence(&self) -> String { + self.sequence.clone() + } + + pub fn get_subentry_count(&self) -> u32 { + self.subentry_count.clone() + } + + pub fn get_last_modified_ledger(&self) -> u64 { + self.last_modified_ledger.clone() + } + + pub fn get_last_modified_time(&self) -> String { + self.last_modified_time.clone() + } + + pub fn get_thresholds(&self) -> Thresholds { + self.thresholds.clone() + } + + pub fn get_balances(&self) -> Vec { + self.balances.clone() + } + + pub fn get_signers(&self) -> Vec { + self.signers.clone() + } + + pub fn get_data(&self) -> Option { + self.data.clone() + } + + pub fn get_num_sponsoring(&self) -> u32 { + self.num_sponsoring.clone() + } + + pub fn get_num_sponsored(&self) -> u32 { + self.num_sponsored.clone() + } + + pub fn get_paging_token(&self) -> String { + self.paging_token.clone() + } + + pub fn get_flags(&self) -> Flags { + self.flags.clone() + } +} \ No newline at end of file diff --git a/src/assets/all_assets_request.rs b/src/assets/all_assets_request.rs new file mode 100644 index 0000000..20d95b0 --- /dev/null +++ b/src/assets/all_assets_request.rs @@ -0,0 +1,166 @@ +use crate::models::Request; + +use super::super::Order; + +// AllAssetsRequest is the request for the /assets endpoint +// [More Details] https://www.stellar.org/developers/horizon/reference/endpoints/assets-all.html "Assets" +pub struct AllAssetsRequest { + + /// The assets identifying code. For example, if the asset is a credit issued on the Stellar network, + /// the code will be the asset’s code. If the asset is a native asset, the code will be XLM. + asset_code: Option, + /// The account ID of the asset’s issuer. For example, if the asset is a credit issued on the Stellar + /// network, the issuer will be the account ID of the credit’s issuer. + asset_issuer: Option, + /// The paging token of the next page of results. If this value is not provided, the results will + /// begin at the first page. + cursor: Option, + /// The maximum number of records returned. The limit can range from 1 to 200 - an upper limit that + /// is hardcoded in Horizon for performance reasons. If this argument isn’t designated, it defaults + /// to 10. + limit: Option, + /// A designation of the order in which records should appear. Options include asc (ascending) or + /// desc (descending). If this argument isn’t set, it defaults to asc. + order: Option, +} + +impl Request for AllAssetsRequest { + /// Creates a new request object + /// # Returns + /// A new request object + /// [AllAssetsRequest](struct.AllAssetsRequest.html) + fn new() -> Self { + AllAssetsRequest { + asset_code: None, + asset_issuer: None, + cursor: None, + limit: None, + order: None, + } + } + + /// Gets the relative URL for the request + fn get_path(&self) -> &str { + "/assets" + } + + /// Gets the query parameters for the request + fn get_query_parameters(&self) -> String { + let mut query = String::new(); + if let Some(asset_code) = &self.asset_code { + query.push_str(&format!("asset_code={}&", asset_code)); + } + if let Some(asset_issuer) = &self.asset_issuer { + query.push_str(&format!("asset_issuer={}&", asset_issuer)); + } + if let Some(cursor) = &self.cursor { + query.push_str(&format!("cursor={}&", cursor)); + } + if let Some(limit) = &self.limit { + query.push_str(&format!("limit={}&", limit)); + } + if let Some(order) = &self.order { + query.push_str(&format!("order={}&", order)); + } + + query.trim_end_matches('&').to_string() + } + + /// Validates the request + fn validate(&self) -> Result<(), String> { + if let Some(asset_code) = &self.asset_code { + // TODO: implement full asset code regex + if asset_code.len() > 12 { + return Err("asset_code must be 12 characters or less".to_string()); + } + } + + if let Some(asset_issuer) = &self.asset_issuer { + // TODO: implement full asset issuer regex + if asset_issuer.len() != 56 { + return Err("asset_issuer must be 56 characters".to_string()); + } + } + + if let Some(limit) = &self.limit { + if *limit < 1 || *limit > 200 { + return Err("limit must be between 1 and 200".to_string()); + } + } + + if let Some(cursor) = &self.cursor { + if *cursor < 1 { + return Err("cursor must be greater than or equal to 1".to_string()); + } + } + + Ok(()) + } + + /// Builds the URL for the request + fn build_url(&self, base_url: &str) -> String { + format!( + "{}{}?{}", + base_url, + self.get_path(), + self.get_query_parameters() + ) + } +} + +impl AllAssetsRequest { + /// Sets the asset code + /// # Arguments + /// * `asset_code` - The asset code + /// # Returns + /// The request object + /// [AllAssetsRequest](struct.AllAssetsRequest.html) + pub fn set_asset_code(&mut self, asset_code: &str) -> &mut Self { + self.asset_code = Some(asset_code.to_owned()); + self + } + + /// Sets the asset issuer + /// # Arguments + /// * `asset_issuer` - The asset issuer + /// # Returns + /// The request object + /// [AllAssetsRequest](struct.AllAssetsRequest.html) + pub fn set_asset_issuer(&mut self, asset_issuer: &str) -> &mut Self { + self.asset_issuer = Some(asset_issuer.to_owned()); + self + } + + /// Sets the cursor + /// # Arguments + /// * `cursor` - The cursor + /// # Returns + /// The request object + /// [AllAssetsRequest](struct.AllAssetsRequest.html) + pub fn set_cursor(&mut self, cursor: u32) -> &mut Self { + self.cursor = Some(cursor); + self + } + + /// Sets the limit + /// # Arguments + /// * `limit` - The limit + /// # Returns + /// The request object + /// [AllAssetsRequest](struct.AllAssetsRequest.html) + pub fn set_limit(&mut self, limit: u32) -> &mut Self { + self.limit = Some(limit); + self + } + + /// Sets the order + /// # Arguments + /// * `order` - The order + /// # Returns + /// The request object + /// [AllAssetsRequest](struct.AllAssetsRequest.html) + pub fn set_order(&mut self, order: Order) -> &mut Self { + self.order = Some(order); + self + } +} diff --git a/src/assets/all_assets_response.rs b/src/assets/all_assets_response.rs new file mode 100644 index 0000000..2b70d42 --- /dev/null +++ b/src/assets/all_assets_response.rs @@ -0,0 +1,241 @@ +use crate::models::Response; +use derive_getters::Getters; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Links { + #[serde(rename = "self")] + self_link: Link, + next: Link, + prev: Link, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Link { + href: Option, + toml: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Toml { + href: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Records { + _links: Link, + asset_type: String, + asset_code: String, + asset_issuer: String, + paging_token: String, + num_accounts: u32, + num_claimable_balances: u32, + num_liquidity_pools: u32, + num_contracts: u32, + amount: String, + accounts: AccountInfo, + claimable_balances_amount: String, + liquidity_pools_amount: String, + contracts_amount: String, + balances: AccountBalances, + flags: Flags, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AccountInfo { + authorized: u32, + authorized_to_maintain_liabilities: u32, + unauthorized: u32, +} + +#[derive(Debug, Serialize, Deserialize, Getters, Clone)] +pub struct AccountBalances { + authorized: String, + authorized_to_maintain_liabilities: String, + unauthorized: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Flags { + auth_required: bool, + auth_revocable: bool, + auth_immutable: bool, + auth_clawback_enabled: bool, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Embedded { + records: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AllAssetsResponse { + _links: Links, + _embedded: Embedded, +} + +impl Response for AllAssetsResponse { + fn from_json(json: String) -> Result { + let response = serde_json::from_str(&json).map_err(|e| e.to_string())?; + + Ok(response) + } +} + +impl AllAssetsResponse { + pub fn get__links(&self) -> Links { + self._links.clone() + } + + pub fn get__embedded(&self) -> Embedded { + self._embedded.clone() + } +} + +impl Records { + pub fn get_asset_type(&self) -> String { + self.asset_type.clone() + } + + pub fn get_asset_code(&self) -> String { + self.asset_code.clone() + } + + pub fn get_asset_issuer(&self) -> String { + self.asset_issuer.clone() + } + + pub fn get_paging_token(&self) -> String { + self.paging_token.clone() + } + + pub fn get_num_accounts(&self) -> u32 { + self.num_accounts.clone() + } + + pub fn get_num_claimable_balances(&self) -> u32 { + self.num_claimable_balances.clone() + } + + pub fn get_num_liquidity_pools(&self) -> u32 { + self.num_liquidity_pools.clone() + } + + pub fn get_num_contracts(&self) -> u32 { + self.num_contracts.clone() + } + + pub fn get_amount(&self) -> String { + self.amount.clone() + } + + pub fn get_accounts(&self) -> AccountInfo { + self.accounts.clone() + } + + pub fn get_claimable_balances_amount(&self) -> String { + self.claimable_balances_amount.clone() + } + + pub fn get_liquidity_pools_amount(&self) -> String { + self.liquidity_pools_amount.clone() + } + + pub fn get_contracts_amount(&self) -> String { + self.contracts_amount.clone() + } + + pub fn get_balances(&self) -> AccountBalances { + self.balances.clone() + } + + pub fn get_flags(&self) -> Flags { + self.flags.clone() + } +} + +impl Embedded { + pub fn get_records(&self) -> Vec { + self.records.clone() + } + + pub fn get_single_record(&self, index: usize) -> Records { + self.records[index].clone() + } +} + +impl Links { + pub fn get_self_link(&self) -> Link { + self.self_link.clone() + } + + pub fn get_next_link(&self) -> Link { + self.next.clone() + } + + pub fn get_prev_link(&self) -> Link { + self.prev.clone() + } +} + +impl Link { + pub fn get_href(&self) -> Option { + self.href.clone() + } + + pub fn get_toml(&self) -> Option { + self.toml.clone() + } +} + +impl Toml { + pub fn get_href(&self) -> String { + self.href.clone() + } +} + +impl AccountInfo { + pub fn get_authorized(&self) -> u32 { + self.authorized.clone() + } + + pub fn get_authorized_to_maintain_liabilities(&self) -> u32 { + self.authorized_to_maintain_liabilities.clone() + } + + pub fn get_unauthorized(&self) -> u32 { + self.unauthorized.clone() + } +} + +impl AccountBalances { + pub fn get_authorized(&self) -> String { + self.authorized.clone() + } + + pub fn get_authorized_to_maintain_liabilities(&self) -> String { + self.authorized_to_maintain_liabilities.clone() + } + + pub fn get_unauthorized(&self) -> String { + self.unauthorized.clone() + } +} + +impl Flags { + pub fn get_auth_required(&self) -> bool { + self.auth_required.clone() + } + + pub fn get_auth_revocable(&self) -> bool { + self.auth_revocable.clone() + } + + pub fn get_auth_immutable(&self) -> bool { + self.auth_immutable.clone() + } + + pub fn get_auth_clawback_enabled(&self) -> bool { + self.auth_clawback_enabled.clone() + } +} diff --git a/src/assets/mod.rs b/src/assets/mod.rs new file mode 100644 index 0000000..7f744d2 --- /dev/null +++ b/src/assets/mod.rs @@ -0,0 +1,7 @@ +mod all_assets_request; +mod all_assets_response; + +pub mod prelude { + pub use super::all_assets_request::*; + pub use super::all_assets_response::*; +} \ No newline at end of file diff --git a/src/horizon_client/horizon_client.rs b/src/horizon_client/horizon_client.rs index 5d722c6..869b35c 100644 --- a/src/horizon_client/horizon_client.rs +++ b/src/horizon_client/horizon_client.rs @@ -1,4 +1,13 @@ -use crate::models::Request; +use crate::{ + accounts::prelude::{ + AccountsRequest, AccountsResponse, SingleAccountRequest, SingleAccountsResponse, + }, + assets::prelude::{AllAssetsRequest, AllAssetsResponse}, + ledgers::prelude::{ + LedgersRequest, LedgersResponse, SingleLedgerRequest, SingleLedgerResponse, + }, + models::{Request, Response}, +}; use reqwest; use url::Url; @@ -52,6 +61,36 @@ impl HorizonClient { self.get::(request).await } + /// Gets the base URL for the Horizon server + /// # Arguments + /// * `self` - The Horizon client + /// * request - The all assets request + /// # Returns + /// The all assets response + /// # Errors + /// Returns an error if the request fails + /// [GET /assets](https://www.stellar.org/developers/horizon/reference/endpoints/assets-all.html) + pub async fn get_all_assets( + &self, + request: &AllAssetsRequest, + ) -> Result { + self.get::(request).await + } + + pub async fn get_all_ledgers( + &self, + request: &LedgersRequest, + ) -> Result { + self.get::(request).await + } + + pub async fn get_single_ledger( + &self, + request: &SingleLedgerRequest, + ) -> Result { + self.get::(request).await + } + /// Sends a GET request to the server /// # Arguments /// * `TResponse` - The type of the response @@ -59,16 +98,24 @@ impl HorizonClient { /// The response from the server /// # Errors /// Returns an error if the request fails - async fn get(&self, request: &impl Request) -> Result { + async fn get( + &self, + request: &impl Request, + ) -> Result { // Validate the request. request.validate()?; //match request by SingleAccountRequest or AccountsRequest // Determine the url // TODO: construct with query parameters + let url = request.build_url(&self.base_url); + println!("\n\nURL: {}", url); let response = reqwest::get(&url).await.map_err(|e| e.to_string())?; + println!("Response: {:?}", response); let result: TResponse = handle_response(response).await?; + + // print!("\n\nResult: {:?}", result); Ok(result) } } @@ -80,19 +127,14 @@ impl HorizonClient { /// The deserialized response from the server response payload /// # Errors /// Returns an error if the response is not successful -async fn handle_response( +async fn handle_response( response: reqwest::Response, ) -> Result { - println!("Response: {:?}", response); + // println!("\n Response: {:?}", response); match response.status() { reqwest::StatusCode::OK => { - let response = response.text().await.map_err(|e| e.to_string())?; - Ok(TResponse::default()) - //decode(&response.as_bytes()).map_err(|e| e.to_string()); - // match result { - // Ok(response) => Ok(response), - // Err(error) => Err(error), - // } + let _response = response.text().await.map_err(|e| e.to_string())?; + TResponse::from_json(_response) } _ => { let response = response.text().await.map_err(|e| e.to_string())?; @@ -112,7 +154,9 @@ fn url_validate(url: &str) -> Result<(), String> { #[cfg(test)] mod tests { - use crate::horizon_client; + use base64::encode; + + use crate::{assets::prelude::AllAssetsRequest, ledgers::prelude::SingleLedgerRequest}; use super::*; @@ -141,15 +185,259 @@ mod tests { // construct request let mut accounts_request = AccountsRequest::new(); accounts_request - .set_sponsor("GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7") + .set_signer("GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7") .set_limit(10); // call the get_account_list method to retrieve the account list response - let _accounts_response = horizon_client.get_account_list(&accounts_request).await; - // will throw exception for now + let _accounts_response: Result = + horizon_client.get_account_list(&accounts_request).await; + assert!(_accounts_response.is_ok()); - // assert_eq!(accounts_response.accounts.len(), 0); + // Testing some random values of the response + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_account_id(), + "GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_id(), + "GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_sequence(), + "4380492979765248".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_subentry_count(), + 0 + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_last_modified_ledger(), + 1019913 + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_last_modified_time(), + "2023-08-15T09:46:25Z" + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_thresholds() + .get_low_threshold(), + 0 + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_thresholds() + .get_med_threshold(), + 0 + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_thresholds() + .get_high_threshold(), + 0 + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_flags() + .get_auth_required(), + false + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_flags() + .get_auth_revocable(), + false + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_flags() + .get_auth_immutable(), + false + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_flags() + .get_auth_clawback_enabled(), + false + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_balances()[0] + .get_balance(), + "10000.0000000".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_balances()[0] + .get_asset_type(), + "native".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_balances()[0] + .get_buying_liabilities(), + "0.0000000".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_balances()[0] + .get_selling_liabilities(), + "0.0000000".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_signers()[0] + .get_key(), + "GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_signers()[0] + .get_weight(), + 1 + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_signers()[0] + .get_type(), + "ed25519_public_key".to_string() + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_num_sponsoring(), + 0 + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_num_sponsored(), + 0 + ); + + assert_eq!( + _accounts_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_paging_token(), + "GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7".to_string() + ); } #[tokio::test] @@ -163,8 +451,820 @@ mod tests { single_account_request .set_account_id("GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7".to_string()); - let _single_account_response = horizon_client.get_single_account(&single_account_request).await; + let _single_account_response = horizon_client + .get_single_account(&single_account_request) + .await; assert!(_single_account_response.is_ok()); + + // Testing some random values of the response + assert_eq!( + _single_account_response + .clone() + .unwrap() + .get_account_id() + .to_string(), + "GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7".to_string() + ); + + assert_eq!( + _single_account_response + .clone() + .unwrap() + .get_sequence() + .to_string(), + "4380492979765248".to_string() + ); + + assert_eq!( + _single_account_response + .clone() + .as_ref() + .unwrap() + .get_subentry_count(), + 0 + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_last_modified_ledger(), + 1019913 + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_last_modified_time(), + "2023-08-15T09:46:25Z" + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_thresholds() + .get_low_threshold(), + 0 + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_thresholds() + .get_med_threshold(), + 0 + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_thresholds() + .get_high_threshold(), + 0 + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_flags() + .get_auth_required(), + false + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_flags() + .get_auth_revocable(), + false + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_flags() + .get_auth_immutable(), + false + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_flags() + .get_auth_clawback_enabled(), + false + ); + + assert_eq!( + _single_account_response.as_ref().unwrap().get_balances()[0].get_balance(), + "10000.0000000".to_string() + ); + + assert_eq!( + _single_account_response.as_ref().unwrap().get_balances()[0].get_asset_type(), + "native".to_string() + ); + + assert_eq!( + _single_account_response.as_ref().unwrap().get_balances()[0].get_buying_liabilities(), + "0.0000000".to_string() + ); + + assert_eq!( + _single_account_response.as_ref().unwrap().get_balances()[0].get_selling_liabilities(), + "0.0000000".to_string() + ); + + assert_eq!( + _single_account_response.as_ref().unwrap().get_signers()[0].get_key(), + "GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7".to_string() + ); + + assert_eq!( + _single_account_response.as_ref().unwrap().get_signers()[0].get_weight(), + 1 + ); + + assert_eq!( + _single_account_response.as_ref().unwrap().get_signers()[0].get_type(), + "ed25519_public_key".to_string() + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_num_sponsoring(), + 0 + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_num_sponsored(), + 0 + ); + + assert_eq!( + _single_account_response + .as_ref() + .unwrap() + .get_paging_token(), + "GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7".to_string() + ); + } + + #[tokio::test] + async fn test_get_all_assests() { + // Initialize horizon client + let horizon_client = + HorizonClient::new("https://horizon-testnet.stellar.org".to_string()).unwrap(); + + // construct request + let mut all_assets_request: AllAssetsRequest = AllAssetsRequest::new(); + all_assets_request + // .set_asset_issuer("GDQJUTQYK2MQX2VGDR2FYWLIYAQIEGXTQVTFEMGH2BEWFG4BRUY4CKI7") + .set_limit(1); + + let _all_assets_response = horizon_client.get_all_assets(&all_assets_request).await; + + assert!(_all_assets_response.is_ok()); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_asset_type(), + "credit_alphanum4".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_asset_code(), + "0".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_asset_issuer(), + "GCINFW5NLMVSE7KWH5BOVL2NTRP2HN6LSXTIL76GOVIORLFM6YN5ZTRS" + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_paging_token(), + "0_GCINFW5NLMVSE7KWH5BOVL2NTRP2HN6LSXTIL76GOVIORLFM6YN5ZTRS_credit_alphanum4" + .to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_num_accounts(), + 0 + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_num_claimable_balances(), + 0 + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_num_liquidity_pools(), + 0 + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_amount(), + "0.0000000".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_accounts() + .get_authorized(), + 0 + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_accounts() + .get_authorized_to_maintain_liabilities(), + 0 + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_accounts() + .get_unauthorized(), + 1 + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_claimable_balances_amount(), + "0.0000000".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_liquidity_pools_amount(), + "0.0000000".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_contracts_amount(), + "0.0000000".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_balances() + .get_authorized(), + "0.0000000".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_balances() + .get_authorized_to_maintain_liabilities(), + "0.0000000".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_balances() + .get_unauthorized(), + "1.0000000".to_string() + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_flags() + .get_auth_required(), + true + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_flags() + .get_auth_revocable(), + true + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_flags() + .get_auth_immutable(), + false + ); + + assert_eq!( + _all_assets_response + .clone() + .unwrap() + .get__embedded() + .get_records()[0] + .get_flags() + .get_auth_clawback_enabled(), + true + ); + } + + #[tokio::test] + async fn test_get_all_ledgers() { + // Initialize horizon client + + let horizon_client = + HorizonClient::new("https://horizon-testnet.stellar.org".to_string()).unwrap(); + + // construct request + let mut all_ledgers_request = LedgersRequest::new(); + all_ledgers_request.set_limit(2); + + let _all_ledgers_response = horizon_client.get_all_ledgers(&all_ledgers_request).await; + + assert!(_all_ledgers_response.clone().is_ok()); + + // Testing some random values of the response + assert_eq!( + _all_ledgers_response + .clone() + .unwrap() + .get__embedded() + .get_single_record(0) + .get_hash(), + "eca856e0073dc2087249dc929ed31c09c3babfef2e687b685d0513dbe6489a18" + ); + + assert_eq!( + _all_ledgers_response + .clone() + .unwrap() + .get__embedded() + .get_single_record(0) + .get_prev_hash(), + "63d98f536ee68d1b27b5b89f23af5311b7569a24faf1403ad0b52b633b07be99".to_string() + ); + + assert_eq!( + _all_ledgers_response + .clone() + .unwrap() + .get__embedded() + .get_single_record(0) + .get_sequence(), + 2 + ); + + assert_eq!( + _all_ledgers_response + .clone() + .unwrap() + .get__embedded() + .get_single_record(0) + .get_successful_transaction_count(), + 0 + ); + + assert_eq!( + _all_ledgers_response + .clone() + .unwrap() + .get__embedded() + .get_single_record(0) + .get_paging_token(), + "8589934592".to_string() + ); + + // Testing some random values of the response + } + + #[tokio::test] + async fn test_get_single_ledger() { + // Initialize horizon client + let horizon_client = + HorizonClient::new("https://horizon-testnet.stellar.org".to_string()).unwrap(); + + // construct request + let mut single_ledger_request = SingleLedgerRequest::new(); + single_ledger_request.set_sequence(2); + + let _single_ledger_response = horizon_client + .get_single_ledger(&single_ledger_request) + .await; + + assert!(_single_ledger_response.clone().is_ok()); + + assert_eq!( + _single_ledger_response.clone().unwrap().get_id(), + "eca856e0073dc2087249dc929ed31c09c3babfef2e687b685d0513dbe6489a18".to_string() + ); + + assert_eq!( + _single_ledger_response.clone().unwrap().get_paging_token(), + "8589934592".to_string() + ); + + assert_eq!( + _single_ledger_response.clone().unwrap().get_hash(), + "eca856e0073dc2087249dc929ed31c09c3babfef2e687b685d0513dbe6489a18".to_string() + ); + + assert_eq!( + _single_ledger_response.clone().unwrap().get_prev_hash(), + "63d98f536ee68d1b27b5b89f23af5311b7569a24faf1403ad0b52b633b07be99".to_string() + ); + + assert_eq!(_single_ledger_response.clone().unwrap().get_sequence(), 2); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_successful_transaction_count(), + 0 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_failed_transaction_count(), + 0 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_operation_count(), + 0 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_tx_set_operation_count(), + 0 + ); + + assert_eq!( + _single_ledger_response.clone().unwrap().get_closed_at(), + "2023-06-14T09:19:48Z".to_string() + ); + + assert_eq!( + _single_ledger_response.clone().unwrap().get_total_coins(), + "100000000000.0000000" + ); + + assert_eq!( + _single_ledger_response.clone().unwrap().get_fee_pool(), + "0.0000000" + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_base_fee_in_stroops(), + 100 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_base_reserve_in_stroops(), + 100000000 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_max_tx_set_size(), + 100 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_protocol_version(), + 0 + ); + + let decoded_xdr_header = _single_ledger_response + .unwrap() + .get_decoded_header_xdr() + .unwrap(); + + assert_eq!( + decoded_xdr_header.bucket_list_hash.to_string(), + "735227ed398461291237687b08446aa2c9b096e0c98a462dadda569f05dd2484" + ); + + assert_eq!(decoded_xdr_header.ledger_seq, 2); + + assert_eq!(decoded_xdr_header.total_coins, 1000000000000000000); + + assert_eq!(decoded_xdr_header.fee_pool, 0); + + assert_eq!(decoded_xdr_header.inflation_seq, 0); + + assert_eq!(decoded_xdr_header.id_pool, 0); + + assert_eq!(decoded_xdr_header.base_fee, 100); + + assert_eq!(decoded_xdr_header.base_reserve, 100000000); + + assert_eq!(decoded_xdr_header.max_tx_set_size, 100); + + let tx_set_hash = decoded_xdr_header.scp_value.tx_set_hash.to_string(); + let tx_set_hash_bytes = hex::decode(tx_set_hash).expect("Failed to decode hex"); + let tx_set_hash_base64 = encode(&tx_set_hash_bytes); + + assert_eq!( + tx_set_hash_base64, + "uZRHr9UdXKbTKiclfOjy72YZFJUkJPVcKT5htvorm1Q=" + ); + + assert_eq!( + decoded_xdr_header.scp_value.close_time, + stellar_xdr::TimePoint(1686734388) + ); + + } + + #[tokio::test] + async fn test_get_decoded_single_ledger() { + // Initialize horizon client + let horizon_client = + HorizonClient::new("https://horizon-testnet.stellar.org".to_string()).unwrap(); + + // construct request + let mut single_ledger_request = SingleLedgerRequest::new(); + single_ledger_request.set_sequence(2); + + let _single_ledger_response = horizon_client + .get_single_ledger(&single_ledger_request) + .await; + + assert!(_single_ledger_response.is_ok()); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_id() + .to_string(), + "eca856e0073dc2087249dc929ed31c09c3babfef2e687b685d0513dbe6489a18" + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_paging_token() + .to_string(), + "8589934592" + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_hash() + .to_string(), + "eca856e0073dc2087249dc929ed31c09c3babfef2e687b685d0513dbe6489a18" + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_prev_hash() + .to_string(), + "63d98f536ee68d1b27b5b89f23af5311b7569a24faf1403ad0b52b633b07be99" + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_sequence(), + 2 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_successful_transaction_count(), + 0 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_failed_transaction_count(), + 0 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_operation_count(), + 0 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_tx_set_operation_count(), + 0 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_closed_at() + .to_string(), + "2023-06-14T09:19:48Z" + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_total_coins() + .to_string(), + "100000000000.0000000" + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_fee_pool() + .to_string(), + "0.0000000" + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_base_fee_in_stroops(), + 100 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_base_reserve_in_stroops(), + 100000000 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_max_tx_set_size(), + 100 + ); + + assert_eq!( + _single_ledger_response + .clone() + .unwrap() + .get_protocol_version(), + 0 + ); + + let _decoded_header_xdr = _single_ledger_response + .unwrap() + .get_decoded_header_xdr() + .unwrap(); + + assert_eq!( + _decoded_header_xdr.bucket_list_hash.to_string(), + "735227ed398461291237687b08446aa2c9b096e0c98a462dadda569f05dd2484" + ); + + assert_eq!(_decoded_header_xdr.ledger_seq, 2); + assert_eq!(_decoded_header_xdr.total_coins, 1000000000000000000); + assert_eq!(_decoded_header_xdr.fee_pool, 0); + assert_eq!(_decoded_header_xdr.inflation_seq, 0); + assert_eq!(_decoded_header_xdr.id_pool, 0); + assert_eq!(_decoded_header_xdr.base_fee, 100); + assert_eq!(_decoded_header_xdr.base_reserve, 100000000); + assert_eq!(_decoded_header_xdr.max_tx_set_size, 100); + assert_eq!(_decoded_header_xdr.ext, stellar_xdr::LedgerHeaderExt::V0); + for decoded in _decoded_header_xdr.skip_list { + assert_eq!( + decoded.to_string(), + "0000000000000000000000000000000000000000000000000000000000000000" + ); + } } } diff --git a/src/ledgers/ledgers_request.rs b/src/ledgers/ledgers_request.rs new file mode 100644 index 0000000..c49e396 --- /dev/null +++ b/src/ledgers/ledgers_request.rs @@ -0,0 +1,118 @@ +use crate::models::*; + +use super::super::AssetType; +use super::super::Order; + +pub struct LedgersRequest { + /// The cursor for the page + cursor: Option, + + /// The maximum number of records to return + limit: Option, + + /// The order of the records + order: Option, +} + +impl Request for LedgersRequest { + fn new() -> Self { + Self { + cursor: None, + limit: None, + order: None, + } + } + + fn get_path(&self) -> &str { + "/ledgers" + } + + fn get_query_parameters(&self) -> String { + let mut query_parameters = vec![]; + + if let Some(cursor) = &self.cursor { + query_parameters.push(format!("cursor={}", cursor)); + } + + if let Some(limit) = &self.limit { + query_parameters.push(format!("limit={}", limit)); + } + + if let Some(order) = &self.order { + query_parameters.push(format!("order={}", order)); + } + + query_parameters.join("&") + } + + fn validate(&self) -> Result<(), String> { + if let Some(cursor) = &self.cursor { + if *cursor < 1 { + return Err("cursor must be greater than or equal to 1".to_string()); + } + } + + if let Some(limit) = &self.limit { + if *limit < 1 { + return Err("limit must be greater than or equal to 1".to_string()); + } + if *limit > 200 { + return Err("limit must be less than or equal to 200".to_string()); + } + } + + Ok(()) + } + + fn build_url(&self, base_url: &str) -> String { + format!( + "{}{}?{}", + base_url, + self.get_path(), + self.get_query_parameters() + ) + } +} + +impl LedgersRequest { + /// Sets the cursor + /// # Arguments + /// * `cursor` - The cursor + /// # Returns + /// The request object + /// [AllLedgersRequest](struct.AllLedgersRequest.html) + pub fn set_cursor(&mut self, cursor: u32) { + self.cursor = Some(cursor); + } + /// Sets the limit + /// # Arguments + /// * `limit` - The limit + /// # Returns + /// The request object + /// [AllAssetsRequest](struct.AllAssetsRequest.html) + pub fn set_limit(&mut self, limit: u32) { + self.limit = Some(limit); + } + + /// Sets the order + /// # Arguments + /// * `order` - The order + /// # Returns + /// The request object + /// [AllAssetsRequest](struct.AllAssetsRequest.html) + pub fn set_order(&mut self, order: Order) { + self.order = Some(order); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ledgers_request() { + let request = LedgersRequest::new(); + + assert_eq!(request.get_path(), "/ledgers"); + } +} diff --git a/src/ledgers/ledgers_response.rs b/src/ledgers/ledgers_response.rs new file mode 100644 index 0000000..8ccc745 --- /dev/null +++ b/src/ledgers/ledgers_response.rs @@ -0,0 +1,171 @@ +use serde::Deserialize; + +use crate::models::Response; + +#[derive(Debug, Deserialize, Clone)] +pub struct Link { + #[serde(rename = "self")] + self_link: SelfLink, + next: Option, + prev: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct SelfLink { + href: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Records { + _links: Link, + id: String, + paging_token: String, + hash: String, + prev_hash: String, + sequence: i32, + successful_transaction_count: i32, + failed_transaction_count: i32, + operation_count: i32, + tx_set_operation_count: i32, + closed_at: String, + total_coins: String, + fee_pool: String, + base_fee_in_stroops: i32, + base_reserve_in_stroops: i32, + max_tx_set_size: i32, + protocol_version: i32, + header_xdr: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Embedded { + records: Vec, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct LedgersResponse { + _links: Link, + _embedded: Embedded, +} + +impl Response for LedgersResponse { + fn from_json(json: String) -> Result { + // serde_json::from_str(&json).map_err(|e| e.to_string()) + + serde_json::from_str(&json).map_err(|e| e.to_string()) + } +} + +impl LedgersResponse { + pub fn get__links(&self) -> Link { + self._links.clone() + } + + pub fn get__embedded(&self) -> Embedded { + self._embedded.clone() + } +} + +impl Embedded { + pub fn get_records(&self) -> Vec { + self.records.clone() + } + + pub fn get_single_record(&self, index: usize) -> Records { + self.records[index].clone() + } +} + +impl Records { + pub fn get__links(&self) -> Link { + self._links.clone() + } + + pub fn get_id(&self) -> String { + self.id.clone() + } + + pub fn get_paging_token(&self) -> String { + self.paging_token.clone() + } + + pub fn get_hash(&self) -> String { + self.hash.clone() + } + + pub fn get_prev_hash(&self) -> String { + self.prev_hash.clone() + } + + pub fn get_sequence(&self) -> i32 { + self.sequence.clone() + } + + pub fn get_successful_transaction_count(&self) -> i32 { + self.successful_transaction_count.clone() + } + + pub fn get_failed_transaction_count(&self) -> i32 { + self.failed_transaction_count.clone() + } + + pub fn get_operation_count(&self) -> i32 { + self.operation_count.clone() + } + + pub fn get_tx_set_operation_count(&self) -> i32 { + self.tx_set_operation_count.clone() + } + + pub fn get_closed_at(&self) -> String { + self.closed_at.clone() + } + + pub fn get_total_coins(&self) -> String { + self.total_coins.clone() + } + + pub fn get_fee_pool(&self) -> String { + self.fee_pool.clone() + } + + pub fn get_base_fee_in_stroops(&self) -> i32 { + self.base_fee_in_stroops.clone() + } + + pub fn get_base_reserve_in_stroops(&self) -> i32 { + self.base_reserve_in_stroops.clone() + } + + pub fn get_max_tx_set_size(&self) -> i32 { + self.max_tx_set_size.clone() + } + + pub fn get_protocol_version(&self) -> i32 { + self.protocol_version.clone() + } + + pub fn get_header_xdr(&self) -> String { + self.header_xdr.clone() + } +} + +impl SelfLink { + pub fn get_href(&self) -> String { + self.href.clone() + } +} + +impl Link { + pub fn get_self_link(&self) -> SelfLink { + self.self_link.clone() + } + + pub fn get_next(&self) -> Option { + self.next.clone() + } + + pub fn get_prev(&self) -> Option { + self.prev.clone() + } +} diff --git a/src/ledgers/mod.rs b/src/ledgers/mod.rs new file mode 100644 index 0000000..259b295 --- /dev/null +++ b/src/ledgers/mod.rs @@ -0,0 +1,11 @@ +mod ledgers_request; +mod ledgers_response; +mod single_ledger_request; +mod single_ledger_response; + +pub mod prelude { + pub use super::ledgers_request::*; + pub use super::ledgers_response::*; + pub use super::single_ledger_request::*; + pub use super::single_ledger_response::*; +} \ No newline at end of file diff --git a/src/ledgers/single_ledger_request.rs b/src/ledgers/single_ledger_request.rs new file mode 100644 index 0000000..a2349de --- /dev/null +++ b/src/ledgers/single_ledger_request.rs @@ -0,0 +1,65 @@ +use crate::models::*; + +use super::super::AssetType; +use super::super::Order; + +pub struct SingleLedgerRequest { + /// The sequence of the ledger + sequence: u32, +} + +impl Request for SingleLedgerRequest { + fn new() -> Self { + Self { sequence: 0 } + } + + fn get_path(&self) -> &str { + "/ledgers" + } + + fn get_query_parameters(&self) -> String { + format!("{}", self.sequence) + } + + fn validate(&self) -> Result<(), String> { + if self.sequence < 1 { + return Err("sequence must be greater than or equal to 1".to_string()); + } + + Ok(()) + } + + fn build_url(&self, base_url: &str) -> String { + format!( + "{}{}/{}", + base_url, + self.get_path(), + self.get_query_parameters() + ) + } +} + +impl SingleLedgerRequest { + /// Sets the sequence + /// # Arguments + /// * `sequence` - The sequence + /// # Returns + /// The request object + /// [SingleLedgerRequest](struct.SingleLedgerRequest.html) + pub fn set_sequence(&mut self, sequence: u32) -> &mut Self { + self.sequence = sequence; + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ledgers_request() { + let request = SingleLedgerRequest::new(); + + assert_eq!(request.get_path(), "/ledgers"); + } +} \ No newline at end of file diff --git a/src/ledgers/single_ledger_response.rs b/src/ledgers/single_ledger_response.rs new file mode 100644 index 0000000..2622014 --- /dev/null +++ b/src/ledgers/single_ledger_response.rs @@ -0,0 +1,156 @@ +use serde::{de, Deserialize}; +use stellar_xdr::{LedgerHeader, ReadXdr}; + +use crate::models::Response; + +#[derive(Debug, Deserialize, Clone)] +pub struct SingleLedgerResponse { + _links: Links, + id: String, + paging_token: String, + hash: String, + prev_hash: String, + sequence: i32, + successful_transaction_count: i32, + failed_transaction_count: i32, + operation_count: i32, + tx_set_operation_count: i32, + closed_at: String, + total_coins: String, + fee_pool: String, + base_fee_in_stroops: i32, + base_reserve_in_stroops: i64, + max_tx_set_size: i32, + protocol_version: i32, + header_xdr: String, +} + +#[derive(Debug, Deserialize, Clone)] +struct Links { + #[serde(rename = "self")] + self_: Link, + transactions: Link, + operations: Link, + payments: Link, + effects: Link, +} + +#[derive(Debug, Deserialize, Clone)] +struct Link { + href: String, + templated: Option, +} + +impl Response for SingleLedgerResponse { + fn from_json(json: String) -> Result { + serde_json::from_str(&json).map_err(|e| e.to_string()) + } +} + +impl Links { + pub fn get_self(&self) -> Link { + self.self_.clone() + } + + pub fn get_transactions(&self) -> Link { + self.transactions.clone() + } + + pub fn get_operations(&self) -> Link { + self.operations.clone() + } + + pub fn get_payments(&self) -> Link { + self.payments.clone() + } + + pub fn get_effects(&self) -> Link { + self.effects.clone() + } +} + +impl Link { + pub fn get_href(&self) -> String { + self.href.clone() + } + + pub fn get_templated(&self) -> bool { + self.templated.clone().unwrap_or(false) + } +} + +impl SingleLedgerResponse { + pub fn get_id(&self) -> String { + self.id.clone() + } + + pub fn get_paging_token(&self) -> String { + self.paging_token.clone() + } + + pub fn get_hash(&self) -> String { + self.hash.clone() + } + + pub fn get_prev_hash(&self) -> String { + self.prev_hash.clone() + } + + pub fn get_sequence(&self) -> i32 { + self.sequence.clone() + } + + pub fn get_successful_transaction_count(&self) -> i32 { + self.successful_transaction_count.clone() + } + + pub fn get_failed_transaction_count(&self) -> i32 { + self.failed_transaction_count.clone() + } + + pub fn get_operation_count(&self) -> i32 { + self.operation_count.clone() + } + + pub fn get_tx_set_operation_count(&self) -> i32 { + self.tx_set_operation_count.clone() + } + + pub fn get_closed_at(&self) -> String { + self.closed_at.clone() + } + + pub fn get_total_coins(&self) -> String { + self.total_coins.clone() + } + + pub fn get_fee_pool(&self) -> String { + self.fee_pool.clone() + } + + pub fn get_base_fee_in_stroops(&self) -> i32 { + self.base_fee_in_stroops.clone() + } + + pub fn get_base_reserve_in_stroops(&self) -> i64 { + self.base_reserve_in_stroops.clone() + } + + pub fn get_max_tx_set_size(&self) -> i32 { + self.max_tx_set_size.clone() + } + + pub fn get_protocol_version(&self) -> i32 { + self.protocol_version.clone() + } + + pub fn get_header_xdr(&self) -> String { + self.header_xdr.clone() + } + + pub fn get_decoded_header_xdr(&self) -> Result { + let encoded = self.header_xdr.as_bytes(); + let decoded = stellar_xdr::LedgerHeader::from_xdr_base64(encoded).unwrap(); + Ok(decoded) + } +} diff --git a/src/lib.rs b/src/lib.rs index b6d95ad..a15f410 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,42 @@ +mod assets; mod accounts; +mod ledgers; mod horizon_client; mod models; mod xdr; +/// The asset type +/// Native - The native asset +/// Issued - An issued asset +/// [AccountsRequest](struct.AccountsRequest.html) +pub enum AssetType { + Native, + Issued, +} + +impl std::fmt::Display for AssetType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + AssetType::Native => write!(f, "native"), + AssetType::Issued => write!(f, "issued"), + } + } +} + +/// The order of the records +/// Asc - Ascending order +/// Desc - Descending order +/// [AccountsRequest](struct.AccountsRequest.html) +pub enum Order { + Asc, + Desc, +} + +impl std::fmt::Display for Order { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Order::Asc => write!(f, "asc"), + Order::Desc => write!(f, "desc"), + } + } +} \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs index af58d5c..41cda7a 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -34,6 +34,11 @@ pub trait Request { fn build_url(&self, base_url: &str) -> String; } +pub trait Response: Sized { + + fn from_json(json: String) -> Result; +} + /// is_public_key validates a public key /// # Arguments /// * `public_key` - The public key to validate diff --git a/src/xdr/mod.rs b/src/xdr/mod.rs index 8c73fa8..4ab949d 100644 --- a/src/xdr/mod.rs +++ b/src/xdr/mod.rs @@ -1,49 +1,8 @@ -use xdr_codec; - -use self::stub_data::LedgerHeader; - -/// Encodes a value to XDR. -/// # Arguments -/// * `value` - The value to encode -/// # Returns -/// The encoded value -/// # Errors -/// Returns an error if the encoding fails -pub fn encode(value: &T) -> Result { - todo!(); -} - -/// decodes a value from XDR. -/// # Arguments -/// * `bytes` - The bytes to decode -/// # Returns -/// The decoded value -/// # Errors -/// Returns an error if the decoding fails -/// # Remarks -pub fn decode(mut bytes: &[u8]) -> Result -where - T: Default, -{ - println!("bytes: {:?}", bytes); - let xdr: Result = xdr_codec::unpack(&mut bytes); - match xdr { - Ok(v) => Ok(v), - Err(e) => { - Err(e.to_string()) - }, - } -} - #[cfg(test)] mod tests { - use super::*; - use stub_data::*; - #[test] - fn encode_ledger_header() { - let ledger_header = ""; - } + use ::stellar_xdr::{LedgerHeader, ReadXdr}; + use stellar_xdr::curr as stellar_xdr; // TODO, add vice versa. // https://developers.stellar.org/docs/encyclopedia/xdr#parsing-xdr @@ -52,248 +11,58 @@ mod tests { #[test] fn decode_ledger_header() { // Decode online at : https://stellar.github.io/xdr-viewer/?type=LedgerHeader&network=public - let encoded = "AAAAAGPZj1Nu5o0bJ7W4nyOvUxG3Vpok+vFAOtC1K2M7B76ZuZRHr9UdXKbTKiclfOjy72YZFJUkJPVcKT5htvorm1QAAAAAZImGNAAAAAAAAAABAAAAAKgkzRi8nXUGTSmaW1uspDvDqi8yaTgVPYwvm7XLbfAzAAAAQLuRQK8ocAjytwfQelkpwZQa5+jReIO0pbr/9BbUbIffRxQN4Z76J3qDIn5lSJpn0OkYL8ZLPGP0W/S1vlTj5w/fP2GYBKkv20BXGS3EPddI6neK3FK8SYzoBSTAFLgRGXNSJ+05hGEpEjdoewhEaqLJsJbgyYpGLa3aVp8F3SSEAAAAAg3gtrOnZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkBfXhAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".as_bytes(); - - let decoded = decode::(&encoded).unwrap(); + let encoded: &[u8] = "AAAAAGPZj1Nu5o0bJ7W4nyOvUxG3Vpok+vFAOtC1K2M7B76ZuZRHr9UdXKbTKiclfOjy72YZFJUkJPVcKT5htvorm1QAAAAAZImGNAAAAAAAAAABAAAAAKgkzRi8nXUGTSmaW1uspDvDqi8yaTgVPYwvm7XLbfAzAAAAQLuRQK8ocAjytwfQelkpwZQa5+jReIO0pbr/9BbUbIffRxQN4Z76J3qDIn5lSJpn0OkYL8ZLPGP0W/S1vlTj5w/fP2GYBKkv20BXGS3EPddI6neK3FK8SYzoBSTAFLgRGXNSJ+05hGEpEjdoewhEaqLJsJbgyYpGLa3aVp8F3SSEAAAAAg3gtrOnZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkBfXhAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".as_bytes(); - assert_eq!(decoded.ledger_version, 0); - assert_eq!(decoded.previous_ledger_hash, "Y9mPU27mjRsntbifI69TEbdWmiT68UA60LUrYzsHvpk=".to_string()); - assert_eq!(decoded.tx_set_hash, "uZRHr9UdXKbTKiclfOjy72YZFJUkJPVcKT5htvorm1Q=".to_string()); - assert_eq!(decoded.close_time, 1686734388); - - // let decoded = decode::(&encoded).unwrap(); - // assert_eq!(decoded, LedgerHeader::default()); - // assert_eq!(decoded.ledger_version, 0); - // assert_eq!( - // decoded.previous_ledger_hash, - // "7KhW4Ac9wghySdySntMcCcO6v+8uaHtoXQUT2+ZImhg=" - // ); - // assert_eq!(decoded.scp_value, ""); - // assert_eq!( - // decoded.tx_set_hash, - // "jqmROBaaGtCUsskwAA+EA0DXG49VbiW3E/OP5gIanTc=" - // ); - // assert_eq!(decoded.close_time, 1686734395); - // assert_eq!(decoded.upgrades.len(), 0); - // assert_eq!(decoded.ext, vec!["stellarValueSigned"]); - // assert_eq!(decoded.lc_value_signature, ""); - // assert_eq!(decoded.node_id, vec!["publicKeyTypeEd25519"]); - // assert_eq!( - // decoded.ed25519, - // "7KhW4Ac9wghySdySntMcCcO6v+8uaHtoXQUT2+ZImhg=" - // ); - // assert_eq!( - // decoded.signature, - // "jqmROBaaGtCUsskwAA+EA0DXG49VbiW3E/OP5gIanTc=" - // ); - // assert_eq!( - // decoded.tx_set_result_hash, - // "jqmROBaaGtCUsskwAA+EA0DXG49VbiW3E/OP5gIanTc=" - // ); - // assert_eq!( - // decoded.bucket_list_hash, - // "jqmROBaaGtCUsskwAA+EA0DXG49VbiW3E/OP5gIanTc=" - // ); - // assert_eq!(decoded.ledger_seq, 3); - // assert_eq!(decoded.total_coins, 1000000000000000000); - // assert_eq!(decoded.fee_pool, 0); - // assert_eq!(decoded.inflation_seq, 0); - // assert_eq!(decoded.id_pool, 0); - // assert_eq!(decoded.base_fee, 100); - // assert_eq!(decoded.base_reserve, 100000000); - // assert_eq!(decoded.max_tx_set_size, 100); - // // assert_eq!(decoded.skip_list, vec![ - // // "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - // // "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - // // "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - // // "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="]); - } -} - -mod stub_data { - use xdr_codec::Unpack; - - #[derive(Debug)] - pub struct LedgerHeader { - pub ledger_version: i32, - pub previous_ledger_hash: String, - pub scp_value: String, - pub tx_set_hash: String, - pub close_time: i64, - pub upgrades: Vec, - pub ext: Vec, - pub lc_value_signature: String, - pub node_id: Vec, - pub ed25519: String, - pub signature: String, - pub tx_set_result_hash: String, - pub bucket_list_hash: String, - pub ledger_seq: i32, - pub total_coins: i64, - pub fee_pool: i64, - pub inflation_seq: i32, - pub id_pool: i64, - pub base_fee: i32, - pub base_reserve: i64, - pub max_tx_set_size: i32, - pub skip_list: Vec, - } - - impl PartialEq for LedgerHeader { - fn eq(&self, other: &Self) -> bool { - true - } - } + let decoded = stellar_xdr::LedgerHeader::from_xdr_base64(encoded).unwrap(); + // println!("Decoded: {:?}", decoded); + assert_eq!(decoded.ledger_version, 0); + assert_eq!( + decoded.previous_ledger_hash.to_string(), + "63d98f536ee68d1b27b5b89f23af5311b7569a24faf1403ad0b52b633b07be99" + ); + assert_eq!(decoded.scp_value.upgrades.len(), 0); + assert_eq!( + decoded.scp_value.tx_set_hash.to_string(), + "b99447afd51d5ca6d32a27257ce8f2ef661914952424f55c293e61b6fa2b9b54" + ); + assert_eq!(decoded.scp_value.close_time.0, 1686734388); + assert_eq!( + decoded.tx_set_result_hash.to_string(), + "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119" + ); - impl Unpack<&[u8]> for LedgerHeader { - fn unpack(input: &mut &[u8]) -> xdr_codec::Result<(Self, usize)> { - let mut size = 0; - let ledger_header = LedgerHeader { - ledger_version: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - print!("ledger_version: {:?}\n", v); - v - }, - previous_ledger_hash: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - scp_value: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - tx_set_hash: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - close_time: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - upgrades: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - ext: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - lc_value_signature: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - node_id: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - ed25519: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - signature: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - tx_set_result_hash: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - bucket_list_hash: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - ledger_seq: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - total_coins: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - fee_pool: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - inflation_seq: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - id_pool: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - base_fee: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - base_reserve: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - max_tx_set_size: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - skip_list: { - let (v, sz) = Unpack::unpack(input)?; - size += sz; - v - }, - }; - - println!("-------------LEDGER HEADER: {:?}", ledger_header); - Ok((ledger_header, size)) + match decoded.scp_value.ext { + ::stellar_xdr::StellarValueExt::Signed(signed) => { + assert_eq!( + signed.node_id.0.discriminant().to_string(), + "PublicKeyTypeEd25519" + ); + assert_eq!(signed.node_id.0.name().to_string(), "PublicKeyTypeEd25519"); + // todo check node-id public key + // todo check signature + } + _ => panic!("Expected signed"), } - } - impl Default for LedgerHeader { - fn default() -> Self { - Self { - ledger_version: 0, - previous_ledger_hash: "".to_string(), - scp_value: "".to_string(), - tx_set_hash: "".to_string(), - close_time: 0, - upgrades: vec![], - ext: vec![], - lc_value_signature: "".to_string(), - node_id: vec![], - ed25519: "".to_string(), - signature: "".to_string(), - tx_set_result_hash: "".to_string(), - bucket_list_hash: "".to_string(), - ledger_seq: 0, - total_coins: 0, - fee_pool: 0, - inflation_seq: 0, - id_pool: 0, - base_fee: 0, - base_reserve: 0, - max_tx_set_size: 0, - skip_list: vec![], - } + assert_eq!( + decoded.bucket_list_hash.to_string(), + "735227ed398461291237687b08446aa2c9b096e0c98a462dadda569f05dd2484" + ); + assert_eq!(decoded.ledger_seq, 2); + assert_eq!(decoded.total_coins, 1000000000000000000); + assert_eq!(decoded.fee_pool, 0); + assert_eq!(decoded.inflation_seq, 0); + assert_eq!(decoded.id_pool, 0); + assert_eq!(decoded.base_fee, 100); + assert_eq!(decoded.base_reserve, 100000000); + assert_eq!(decoded.max_tx_set_size, 100); + assert_eq!(decoded.ext, stellar_xdr::LedgerHeaderExt::V0); + for decoded in decoded.skip_list { + assert_eq!( + decoded.to_string(), + "0000000000000000000000000000000000000000000000000000000000000000" + ); } } }