ostium-rust-sdk 0.1.0

Rust SDK for interacting with the Ostium trading platform on Arbitrum
Documentation
//! Smart contract interfaces and implementations
//!
//! This module contains contract wrappers for interacting with Ostium's smart contracts
//! on Arbitrum, including trading, storage, and USDC token contracts.

/// Trading contract interface for opening/closing positions
pub mod trading;
/// Trading storage contract interface for querying position data
pub mod trading_storage;
/// USDC token contract interface for balance and transfer operations
pub mod usdc;

// Generated contracts are now handled by the ABI module

pub use trading::*;
pub use trading_storage::*;
pub use usdc::UsdcContract;

/// Dynamic contract fetcher for runtime updates
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;

    /// Contract metadata from GitHub
    #[derive(Debug, Clone)]
    pub struct ContractMetadata {
        /// Contract name
        pub name: String,
        /// File path in the repository
        pub path: String,
        /// Git SHA hash of the file
        pub sha: String,
        /// Direct download URL for the file
        pub download_url: String,
        /// Last modification timestamp
        pub last_modified: String,
    }

    /// Runtime contract fetcher
    pub struct ContractFetcher {
        client: Client,
        cache: HashMap<String, (ContractMetadata, std::time::SystemTime)>,
    }

    impl ContractFetcher {
        /// Create a new contract fetcher instance
        pub fn new() -> Self {
            Self {
                client: Client::new(),
                cache: HashMap::new(),
            }
        }

        /// Fetch contract metadata from GitHub
        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)
        }

        /// Fetch the source code of a specific contract
        pub async fn fetch_contract_source(
            &mut self,
            metadata: &ContractMetadata,
        ) -> Result<String> {
            // Check cache first
            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
                {
                    // Cache is still valid, but we need to fetch the source
                }
            }

            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))
            })?;

            // Update cache
            self.cache.insert(
                metadata.name.clone(),
                (metadata.clone(), std::time::SystemTime::now()),
            );

            Ok(source_code)
        }

        /// Check if contracts have been updated since last fetch
        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 {
                    // New contract
                    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()
        }
    }

    /// Utility function to get the latest contract ABIs
    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?;
            // In a production environment, you would compile the Solidity source
            // and extract the ABI. For now, we'll store the source code.
            abis.insert(contract.name.trim_end_matches(".sol").to_string(), source);
        }

        Ok(abis)
    }
}