Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

160 changes: 160 additions & 0 deletions src/horizon/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use reqwest::Client;
use serde::Deserialize;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum HorizonError {
#[error("reqwest error: {0}")]
Reqwest(#[from] reqwest::Error),

#[error("json error: {0}")]
Json(#[from] serde_json::Error),

#[error("http error {0}: {1}")]
Http(u16, String),

#[error("other: {0}")]
Other(String),
}

#[derive(Clone)]
pub struct HorizonClient {
base_url: String,
client: Client,
}

impl HorizonClient {
pub fn new(base_url: impl Into<String>) -> Self {
let client = Client::builder().build().expect("reqwest client");
Self {
base_url: base_url.into().trim_end_matches('/').to_string(),
client,
}
}

/// Convenience constructor for public testnet Horizon
pub fn public_testnet() -> Self {
Self::new("https://horizon-testnet.stellar.org")
}

async fn get_json<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, HorizonError> {
let url = format!("{}{}", self.base_url, path);
let resp = self.client.get(&url).send().await?;
let status = resp.status();
let text = resp.text().await?;
if !status.is_success() {
return Err(HorizonError::Http(status.as_u16(), text));
}
let parsed = serde_json::from_str(&text)?;
Ok(parsed)
}

pub async fn get_account(&self, address: &str) -> Result<AccountResponse, HorizonError> {
let path = format!("/accounts/{}", address);
self.get_json(&path).await
}

pub async fn get_transactions(
&self,
address: &str,
cursor: Option<&str>,
) -> Result<TransactionPage, HorizonError> {
let mut path = format!("/accounts/{}/transactions?limit=10&order=desc", address);
if let Some(c) = cursor {
path.push_str("&cursor=");
path.push_str(c);
}
self.get_json(&path).await
}

pub async fn get_payments(
&self,
address: &str,
cursor: Option<&str>,
) -> Result<PaymentPage, HorizonError> {
let mut path = format!("/accounts/{}/payments?limit=10&order=desc", address);
if let Some(c) = cursor {
path.push_str("&cursor=");
path.push_str(c);
}
self.get_json(&path).await
}

pub async fn get_transaction(&self, hash: &str) -> Result<TransactionDetail, HorizonError> {
let path = format!("/transactions/{}", hash);
self.get_json(&path).await
}
}

// ---- Response structs (minimal, expand as needed) ----

#[derive(Debug, Deserialize)]
pub struct Balance {
pub asset_type: String,
#[serde(default)]
pub asset_code: Option<String>,
pub balance: String,
}

#[derive(Debug, Deserialize)]
pub struct AccountResponse {
pub id: String,
pub account_id: String,
pub sequence: String,
pub balances: Vec<Balance>,
#[serde(flatten)]
pub extra: serde_json::Value,
}

#[derive(Debug, Deserialize)]
pub struct TransactionSummary {
pub id: String,
pub paging_token: String,
pub hash: Option<String>,
pub created_at: Option<String>,
pub source_account: Option<String>,
#[serde(flatten)]
pub extra: serde_json::Value,
}

#[derive(Debug, Deserialize)]
pub struct TransactionPage {
pub _embedded: EmbeddedTransactions,
}

#[derive(Debug, Deserialize)]
pub struct EmbeddedTransactions {
pub records: Vec<TransactionSummary>,
}

#[derive(Debug, Deserialize)]
pub struct PaymentSummary {
pub id: String,
pub paging_token: String,
pub source_account: Option<String>,
pub type_: Option<String>,
#[serde(rename = "type_i")]
pub type_i: Option<u64>,
#[serde(flatten)]
pub extra: serde_json::Value,
}

#[derive(Debug, Deserialize)]
pub struct PaymentPage {
pub _embedded: EmbeddedPayments,
}

#[derive(Debug, Deserialize)]
pub struct EmbeddedPayments {
pub records: Vec<PaymentSummary>,
}

#[derive(Debug, Deserialize)]
pub struct TransactionDetail {
pub id: String,
pub hash: String,
pub ledger: Option<u64>,
pub created_at: Option<String>,
#[serde(flatten)]
pub extra: serde_json::Value,
}
3 changes: 3 additions & 0 deletions src/horizon/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod client;

pub use client::{HorizonClient, HorizonError};
11 changes: 6 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod friendbot;
mod setup;
pub mod utils;

fn main() {}
pub mod friendbot;
pub mod horizon;
mod setup;
pub mod utils;

fn main() {}
2 changes: 2 additions & 0 deletions src/setup/token_setup.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(dead_code)]

use std::fmt;
use std::process::Command;

Expand Down