Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single Offer Endpoint #42

Closed
wants to merge 14 commits into from
55 changes: 55 additions & 0 deletions stellar_rust_sdk/src/horizon_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
prelude::{Ledger, LedgersRequest, LedgersResponse, SingleLedgerRequest},
single_ledger_request::Sequence,
},
offers::prelude::*,
liquidity_pools::{
all_liquidity_pools_request::AllLiquidityPoolsRequest,
prelude::{
Expand Down Expand Up @@ -743,6 +744,60 @@ impl HorizonClient {
self.get::<EffectsResponse>(request).await
}

/// Retrieves detailed information for a specific offer from the Horizon server.
///
/// This asynchronous method fetches details of a single offer from the Horizon server.
/// It requires a [`SingleOfferRequest`] parameterized with `OfferId`, which includes the ID
/// of the offer to be retrieved.
///
/// Adheres to the <a href="https://developers.stellar.org/network/horizon/resources/get-offer-by-offer-id">Retrieve An Offer endpoint</a>
/// endpoint.
///
/// # Arguments
///
/// * `request` - A reference to a [`SingleOfferRequest<OfferId>`] instance, containing the
/// id of the offer for which details are to be fetched.
///
/// # Returns
///
/// Returns a `Result` containing an [`Offer`], which includes detailed
/// information about the requested offer. If the request fails, it returns an error
/// encapsulated within `Result`.
///
/// # Usage
/// To use this method, create an instance of [`SingleOfferRequest`] and set the
/// id of the offer to be queried.
///
/// ```
/// # use stellar_rs::offers::prelude::*;
/// # use stellar_rs::models::Request;
/// # use stellar_rs::horizon_client::HorizonClient;
/// #
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// # let base_url = "https://horizon-testnet.stellar.org".to_string();
/// # let horizon_client = HorizonClient::new(base_url)
/// # .expect("Failed to create Horizon Client");
/// let request = SingleOfferRequest::new()
/// .set_offer_id("1".to_string()) // example offer ID
/// .unwrap();
///
/// let response = horizon_client.get_single_offer(&request).await;
///
/// if let Ok(offer) = response {
/// println!("Offer ID: {}", offer.id());
/// // Additional processing...
/// }
/// # Ok({})
/// # }
/// ```
///
pub async fn get_single_offer(
&self,
request: &SingleOfferRequest<OfferId>,
) -> Result<SingleOfferResponse, String> {
self.get::<SingleOfferResponse>(request).await
}

/// Retrieves a list of all fee stats from the Horizon server.
///
/// This asynchronous method fetches a list of all fee stats from the Horizon server.
Expand Down
42 changes: 42 additions & 0 deletions stellar_rust_sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,48 @@ pub mod liquidity_pools;
///
pub mod operations;

/// Provides `Request` and `Response` structs for retrieving offers.
///
/// This module provides a set of specialized request and response structures designed for
/// interacting with the offer-related endpoints of the Horizon server. These structures
/// facilitate the construction of requests to query offer data and the interpretation of
/// the corresponding responses.
///
/// # Usage
///
/// This module is intended to be used in conjunction with the [`HorizonClient`](crate::horizon_client::HorizonClient)
/// for making specific offer-related API calls to the Horizon server. The request
/// structures are designed to be passed to the client's methods, which handle the
/// communication with the server and return the corresponding response structures.
///
/// # Example
///
/// /// To use this module, you can create an instance of a request struct, such as
/// `SingleOfferRequest`, set any desired query parameters, and pass the request to the
/// `HorizonClient`. The client will then execute the request and return the corresponding
/// response struct, like `SingleOfferResponse`.
///
/// ```rust
/// use stellar_rs::horizon_client::HorizonClient;
/// use stellar_rs::offers::prelude::*;
/// use stellar_rs::models::Request;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let horizon_client = HorizonClient::new("https://horizon-testnet.stellar.org".to_string())?;
///
/// // Example: Fetching all effects
/// let single_offer_request = SingleOfferRequest::new()
/// .set_offer_id("1".to_string())
/// .unwrap();
/// let single_offer_response = horizon_client.get_single_offer(&single_offer_request).await?;
///
/// // Process the responses...
/// # Ok(())
/// # }
/// ```
///
pub mod offers;

/// Contains core data structures and traits.
///
/// This module is used by the Stellar Rust SDK to interact with the Horizon API.
Expand Down
121 changes: 121 additions & 0 deletions stellar_rust_sdk/src/offers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/// Provides the `SingleOfferRequest`.
///
/// This module provides the `SingleOfferRequest` struct, specifically designed for
/// constructing requests to query information about a single offer from the Horizon
/// server. It is tailored for use with the [`HorizonClient::get_single_offer`](crate::horizon_client::HorizonClient::get_single_offer)
/// method.
///
pub mod single_offer_request;

/// Provides the responses.
///
/// This module defines structures representing the response from the Horizon API when querying
/// for offers. The structures are designed to deserialize the JSON response into Rust
/// objects, enabling straightforward access to various details of a single Stellar account.
///
/// These structures are equipped with serialization capabilities to handle the JSON data from the
/// Horizon server and with getter methods for easy field access.
///
pub mod response;

/// The base path for offer-related endpoints in the Horizon API.
///
/// # Usage
/// This variable is intended to be used internally by the request-building logic
/// to ensure consistent and accurate path construction for offer-related API calls.
///
static OFFERS_PATH: &str = "offers";

/// The `prelude` module of the `offers` module.
///
/// This module serves as a convenience for users of the Horizon Rust SDK, allowing for easy and
/// ergonomic import of the most commonly used items across various modules. It re-exports
/// key structs and traits from the sibling modules, simplifying access to these components
/// when using the library.
///
/// By importing the contents of `prelude`, users can conveniently access the primary
/// functionalities of the offer-related modules without needing to import each item
/// individually.
///
/// # Contents
///
/// The `prelude` includes the following re-exports:
///
/// * From `single_offer_request`: All items (e.g. `SingleOfferRequest`).
/// * From `response`: All items (e.g. `SingleOfferResponse`, `PriceR`, etc.).
///
/// # Example
/// ```
/// # use crate::stellar_rs::models::*;
/// // Import the contents of the offers prelude
/// use stellar_rs::offers::prelude::*;
///
/// // Now you can directly use SingleOfferRequest, SingleOfferResponse, etc.
/// let single_offer_request = SingleOfferRequest::new();
/// ```
///
pub mod prelude {
pub use super::single_offer_request::*;
pub use super::response::*;
}

#[cfg(test)]
pub mod test {
use super::prelude::*;
use crate::horizon_client::HorizonClient;

#[tokio::test]
async fn test_get_single_offer() {
const LINK_SELF: &str = "https://horizon-testnet.stellar.org/offers/1";
const LINK_OFFER_MAKER: &str = "https://horizon-testnet.stellar.org/accounts/GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const OFFER_ID: &str = "1";
const PAGING_TOKEN: &str = "1";
const SELLER: &str = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const SELLING_ASSET_TYPE: &str = "credit_alphanum4";
const SELLING_ASSET_CODE: &str = "USDC";
const SELLING_ASSET_ISSUER: &str = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const BUYING_ASSET_TYPE: &str = "credit_alphanum12";
const BUYING_ASSET_CODE: &str = "USDCAllow";
const BUYING_ASSET_ISSUER: &str = "GAWZGWFOURKXZ4XYXBGFADZM4QIG6BJNM74XIZCEIU3BHM62RN2MDEZN";
const AMOUNT: &str = "909086990804.0875807";
const PRICE_R_N: &u32 = &1;
const PRICE_R_D: &u32 = &1;
const PRICE: &str = "1.0000000";
const LAST_MODIFIED_LEDGER: &u32 = &747543;
const LAST_MODIFIED_TIME: &str = "2024-03-23T04:51:18Z";

let horizon_client =
HorizonClient::new("https://horizon-testnet.stellar.org"
.to_string())
.unwrap();

let single_offer_request =
SingleOfferRequest::new()
.set_offer_id(OFFER_ID.to_string())
.unwrap();

let single_offer_response = horizon_client
.get_single_offer(&single_offer_request)
.await;

assert!(single_offer_response.clone().is_ok());
let response = single_offer_response.unwrap();
assert_eq!(response.links().self_link().href().as_ref().unwrap(), LINK_SELF);
assert_eq!(response.links().offer_maker().href().as_ref().unwrap(), LINK_OFFER_MAKER);
assert_eq!(response.id(), OFFER_ID);
assert_eq!(response.paging_token(), PAGING_TOKEN);
assert_eq!(response.seller(), SELLER);
assert_eq!(response.selling().asset_type(), SELLING_ASSET_TYPE);
assert_eq!(response.selling().asset_code().as_ref().unwrap(), SELLING_ASSET_CODE);
assert_eq!(response.selling().asset_issuer().as_ref().unwrap(), SELLING_ASSET_ISSUER);
assert_eq!(response.buying().asset_type(), BUYING_ASSET_TYPE);
assert_eq!(response.buying().asset_code().as_ref().unwrap(), BUYING_ASSET_CODE);
assert_eq!(response.buying().asset_issuer().as_ref().unwrap(), BUYING_ASSET_ISSUER);
assert_eq!(response.amount(), AMOUNT);
assert_eq!(response.price_ratio().numenator(), PRICE_R_N);
assert_eq!(response.price_ratio().denominator(), PRICE_R_D);
assert_eq!(response.price_decimal(), PRICE);
assert_eq!(response.last_modified_ledger(), LAST_MODIFIED_LEDGER);
assert_eq!(response.last_modified_time(), LAST_MODIFIED_TIME);
}
}
90 changes: 90 additions & 0 deletions stellar_rust_sdk/src/offers/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::models::prelude::*;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};

/// Represents the asset to buy or to sell.
///
/// This struct details information about the asset to buy or to sell, including its type,
/// code (optional) and issuer (optional).
///
#[derive(Debug, Deserialize, Clone, Getters)]
pub struct Transaction {
/// The type of asset (e.g. "credit_alphanum4", "credit_alphanum12").
asset_type: String,
/// Optional. The code of the asset.
asset_code: Option<String>,
/// Optional. The public key of the issuer.
asset_issuer: Option<String>,
}

/// Represents the precise buy and sell price of the assets on offer.
///
/// This struct contains a numenator and a denominator, so that the price ratio can be determined
/// in a precise manner.
///
#[derive(Debug, Deserialize, Clone, Getters)]
pub struct PriceR {
/// The numenator.
#[serde(rename = "n")]
numenator: u32,
/// The denominator.
#[serde(rename = "d")]
denominator: u32,
}

/// Represents the navigational links in a single offer response from the Horizon API.
///
/// This struct includes various hyperlinks such as links to the offer itself
/// and the offer maker.
///
#[derive(Debug, Deserialize, Serialize, Clone, Getters)]
pub struct OfferResponseLinks {
/// The link to the offer itself.
#[serde(rename = "self")]
self_link: Link,
/// Link to the offer's maker.
offer_maker: Link,
}

/// Represents the response for a single offer query in the Horizon API.
///
/// This struct defines the overall structure of the response for a single offer query.
/// It includes navigational links, offer identifiers, the seller, the assets to buy and sell,
/// the amount, the price and additional data.
///
#[derive(Debug, Deserialize, Clone, Getters)]
pub struct SingleOfferResponse {
/// Navigational links related to the offer.
#[serde(rename = "_links")]
links: OfferResponseLinks,
/// The unique identifier for the offer.
id: String,
/// A token used for paging through results.
paging_token: String,
/// The ID of the seller making the offer.
seller: String,
/// The asset the offer wants to sell.
selling: Transaction,
/// The asset the offer wants to buy.
buying: Transaction,
/// The amount of `selling` that the account making this offer is willing to sell.
amount: String,
/// A precise representation of the buy and sell price of the assets on offer.
#[serde(rename = "price_r")]
price_ratio: PriceR,
/// A number representing the decimal form of `price_r`.
#[serde(rename = "price")]
price_decimal: String,
/// The sequence number of the last ledger in which the offer was modified.
last_modified_ledger: u32,
/// The time at which the offer was last modified.
last_modified_time: String,
/// The account ID of the sponsor who is paying the reserves for this offer.
sponsor: Option<String>,
}

impl Response for SingleOfferResponse {
fn from_json(json: String) -> Result<Self, String> {
serde_json::from_str(&json).map_err(|e| e.to_string())
}
}
Loading