Skip to content

Commit 6f22a6c

Browse files
authored
Merge pull request #22 from Oluwaseyi89/feature/Setup-Horizon-API-Client-with-reqwest
feat: Setup Horizon API Client with reqwest
2 parents 0a6f885 + 07e38b5 commit 6f22a6c

5 files changed

Lines changed: 175 additions & 9 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/horizon/client.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
use reqwest::Client;
2+
use serde::Deserialize;
3+
use thiserror::Error;
4+
5+
#[derive(Debug, Error)]
6+
pub enum HorizonError {
7+
#[error("reqwest error: {0}")]
8+
Reqwest(#[from] reqwest::Error),
9+
10+
#[error("json error: {0}")]
11+
Json(#[from] serde_json::Error),
12+
13+
#[error("http error {0}: {1}")]
14+
Http(u16, String),
15+
16+
#[error("other: {0}")]
17+
Other(String),
18+
}
19+
20+
#[derive(Clone)]
21+
pub struct HorizonClient {
22+
base_url: String,
23+
client: Client,
24+
}
25+
26+
impl HorizonClient {
27+
pub fn new(base_url: impl Into<String>) -> Self {
28+
let client = Client::builder().build().expect("reqwest client");
29+
Self {
30+
base_url: base_url.into().trim_end_matches('/').to_string(),
31+
client,
32+
}
33+
}
34+
35+
/// Convenience constructor for public testnet Horizon
36+
pub fn public_testnet() -> Self {
37+
Self::new("https://horizon-testnet.stellar.org")
38+
}
39+
40+
async fn get_json<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, HorizonError> {
41+
let url = format!("{}{}", self.base_url, path);
42+
let resp = self.client.get(&url).send().await?;
43+
let status = resp.status();
44+
let text = resp.text().await?;
45+
if !status.is_success() {
46+
return Err(HorizonError::Http(status.as_u16(), text));
47+
}
48+
let parsed = serde_json::from_str(&text)?;
49+
Ok(parsed)
50+
}
51+
52+
pub async fn get_account(&self, address: &str) -> Result<AccountResponse, HorizonError> {
53+
let path = format!("/accounts/{}", address);
54+
self.get_json(&path).await
55+
}
56+
57+
pub async fn get_transactions(
58+
&self,
59+
address: &str,
60+
cursor: Option<&str>,
61+
) -> Result<TransactionPage, HorizonError> {
62+
let mut path = format!("/accounts/{}/transactions?limit=10&order=desc", address);
63+
if let Some(c) = cursor {
64+
path.push_str("&cursor=");
65+
path.push_str(c);
66+
}
67+
self.get_json(&path).await
68+
}
69+
70+
pub async fn get_payments(
71+
&self,
72+
address: &str,
73+
cursor: Option<&str>,
74+
) -> Result<PaymentPage, HorizonError> {
75+
let mut path = format!("/accounts/{}/payments?limit=10&order=desc", address);
76+
if let Some(c) = cursor {
77+
path.push_str("&cursor=");
78+
path.push_str(c);
79+
}
80+
self.get_json(&path).await
81+
}
82+
83+
pub async fn get_transaction(&self, hash: &str) -> Result<TransactionDetail, HorizonError> {
84+
let path = format!("/transactions/{}", hash);
85+
self.get_json(&path).await
86+
}
87+
}
88+
89+
// ---- Response structs (minimal, expand as needed) ----
90+
91+
#[derive(Debug, Deserialize)]
92+
pub struct Balance {
93+
pub asset_type: String,
94+
#[serde(default)]
95+
pub asset_code: Option<String>,
96+
pub balance: String,
97+
}
98+
99+
#[derive(Debug, Deserialize)]
100+
pub struct AccountResponse {
101+
pub id: String,
102+
pub account_id: String,
103+
pub sequence: String,
104+
pub balances: Vec<Balance>,
105+
#[serde(flatten)]
106+
pub extra: serde_json::Value,
107+
}
108+
109+
#[derive(Debug, Deserialize)]
110+
pub struct TransactionSummary {
111+
pub id: String,
112+
pub paging_token: String,
113+
pub hash: Option<String>,
114+
pub created_at: Option<String>,
115+
pub source_account: Option<String>,
116+
#[serde(flatten)]
117+
pub extra: serde_json::Value,
118+
}
119+
120+
#[derive(Debug, Deserialize)]
121+
pub struct TransactionPage {
122+
pub _embedded: EmbeddedTransactions,
123+
}
124+
125+
#[derive(Debug, Deserialize)]
126+
pub struct EmbeddedTransactions {
127+
pub records: Vec<TransactionSummary>,
128+
}
129+
130+
#[derive(Debug, Deserialize)]
131+
pub struct PaymentSummary {
132+
pub id: String,
133+
pub paging_token: String,
134+
pub source_account: Option<String>,
135+
pub type_: Option<String>,
136+
#[serde(rename = "type_i")]
137+
pub type_i: Option<u64>,
138+
#[serde(flatten)]
139+
pub extra: serde_json::Value,
140+
}
141+
142+
#[derive(Debug, Deserialize)]
143+
pub struct PaymentPage {
144+
pub _embedded: EmbeddedPayments,
145+
}
146+
147+
#[derive(Debug, Deserialize)]
148+
pub struct EmbeddedPayments {
149+
pub records: Vec<PaymentSummary>,
150+
}
151+
152+
#[derive(Debug, Deserialize)]
153+
pub struct TransactionDetail {
154+
pub id: String,
155+
pub hash: String,
156+
pub ledger: Option<u64>,
157+
pub created_at: Option<String>,
158+
#[serde(flatten)]
159+
pub extra: serde_json::Value,
160+
}

src/horizon/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod client;
2+
3+
pub use client::{HorizonClient, HorizonError};

src/main.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
pub mod friendbot;
2-
mod setup;
3-
pub mod utils;
4-
5-
fn main() {}
1+
pub mod friendbot;
2+
pub mod horizon;
3+
mod setup;
4+
pub mod utils;
5+
6+
fn main() {}

src/setup/token_setup.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(dead_code)]
2+
13
use std::fmt;
24
use std::process::Command;
35

0 commit comments

Comments
 (0)