pub mod trading;
pub mod trading_storage;
pub mod usdc;
pub use trading::*;
pub use trading_storage::*;
pub use usdc::UsdcContract;
pub mod fetcher {
use crate::error::{OstiumError, Result};
use reqwest::Client;
use serde_json::Value;
use std::collections::HashMap;
const GITHUB_API_BASE: &str = "https://api.github.com/repos/0xOstium/smart-contracts-public";
const CACHE_DURATION_HOURS: u64 = 24;
#[derive(Debug, Clone)]
pub struct ContractMetadata {
pub name: String,
pub path: String,
pub sha: String,
pub download_url: String,
pub last_modified: String,
}
pub struct ContractFetcher {
client: Client,
cache: HashMap<String, (ContractMetadata, std::time::SystemTime)>,
}
impl ContractFetcher {
pub fn new() -> Self {
Self {
client: Client::new(),
cache: HashMap::new(),
}
}
pub async fn fetch_contract_list(&mut self) -> Result<Vec<ContractMetadata>> {
let url = format!("{}/contents/src", GITHUB_API_BASE);
let response = self
.client
.get(&url)
.header("User-Agent", "ostium-rust-sdk")
.send()
.await
.map_err(|e| OstiumError::network(format!("Failed to fetch from GitHub: {}", e)))?;
if !response.status().is_success() {
return Err(OstiumError::network(format!(
"GitHub API error: {}",
response.status()
)));
}
let contents: Vec<Value> = response.json().await.map_err(|e| {
OstiumError::parsing(format!("Failed to parse GitHub response: {}", e))
})?;
let mut contracts = Vec::new();
for item in contents {
if let Some(metadata) = self.parse_contract_metadata(&item) {
contracts.push(metadata);
}
}
Ok(contracts)
}
pub async fn fetch_contract_source(
&mut self,
metadata: &ContractMetadata,
) -> Result<String> {
if let Some((cached_metadata, cached_time)) = self.cache.get(&metadata.name) {
let cache_age = cached_time
.elapsed()
.unwrap_or(std::time::Duration::from_secs(0));
if cache_age < std::time::Duration::from_secs(CACHE_DURATION_HOURS * 3600)
&& cached_metadata.sha == metadata.sha
{
}
}
let response = self
.client
.get(&metadata.download_url)
.header("User-Agent", "ostium-rust-sdk")
.send()
.await
.map_err(|e| {
OstiumError::network(format!("Failed to fetch contract source: {}", e))
})?;
if !response.status().is_success() {
return Err(OstiumError::network(format!(
"Failed to download contract: {}",
response.status()
)));
}
let source_code = response.text().await.map_err(|e| {
OstiumError::parsing(format!("Failed to read contract source: {}", e))
})?;
self.cache.insert(
metadata.name.clone(),
(metadata.clone(), std::time::SystemTime::now()),
);
Ok(source_code)
}
pub async fn check_for_updates(
&mut self,
known_contracts: &[ContractMetadata],
) -> Result<Vec<ContractMetadata>> {
let latest_contracts = self.fetch_contract_list().await?;
let mut updated_contracts = Vec::new();
for latest in &latest_contracts {
if let Some(known) = known_contracts.iter().find(|c| c.name == latest.name) {
if known.sha != latest.sha {
updated_contracts.push(latest.clone());
}
} else {
updated_contracts.push(latest.clone());
}
}
Ok(updated_contracts)
}
fn parse_contract_metadata(&self, item: &Value) -> Option<ContractMetadata> {
let item_type = item["type"].as_str()?;
let name = item["name"].as_str()?;
if item_type == "file" && name.ends_with(".sol") {
Some(ContractMetadata {
name: name.to_string(),
path: item["path"].as_str()?.to_string(),
sha: item["sha"].as_str()?.to_string(),
download_url: item["download_url"].as_str()?.to_string(),
last_modified: item
.get("last_modified")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string(),
})
} else {
None
}
}
}
impl Default for ContractFetcher {
fn default() -> Self {
Self::new()
}
}
pub async fn get_latest_contract_abis() -> Result<HashMap<String, String>> {
let mut fetcher = ContractFetcher::new();
let contracts = fetcher.fetch_contract_list().await?;
let mut abis = HashMap::new();
for contract in contracts {
let source = fetcher.fetch_contract_source(&contract).await?;
abis.insert(contract.name.trim_end_matches(".sol").to_string(), source);
}
Ok(abis)
}
}