//! aldabra chain backends — Koios first, Ogmios next. //! //! Trait-first design: the MCP server depends on [`ChainBackend`], not //! on a specific implementation. Swapping Koios → Ogmios is a config //! change. //! //! Phase 1: read-only queries (`get_utxos`, `get_balance`) against //! Koios over HTTPS. //! //! Phase 2 (TODO): submission paths — `submit_tx`, `tx_status`. //! //! ## Backends //! //! - [`koios::KoiosClient`] — Koios REST client (POST `/address_utxos`, //! `/address_info`). Points at any Koios endpoint — //! `https://api.koios.rest/api/v1` for the public mainnet instance, //! the preprod/preview equivalents for testnets, or a self-hosted //! Koios at any URL. //! - Ogmios (TODO) — websocket client. use serde::{Deserialize, Serialize}; use thiserror::Error; pub mod koios; pub use koios::KoiosClient; #[derive(Debug, Error)] pub enum ChainError { #[error("network error: {0}")] Network(String), #[error("backend returned malformed response: {0}")] Decode(String), #[error("not yet implemented (phase 1 scaffold)")] NotYetImplemented, } /// One UTXO at an address. Multi-asset bundle is a flat map of /// `policy_id || asset_name_hex` → quantity for now. We'll model it /// more strictly when minting lands in phase 3. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Utxo { pub tx_hash: String, pub output_index: u32, pub lovelace: u64, /// Hex-encoded `policy_id || asset_name_hex` → quantity. /// Empty for plain ADA UTXOs. pub assets: std::collections::BTreeMap, } /// Aggregated balance at an address. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Balance { pub lovelace: u64, pub assets: std::collections::BTreeMap, } /// Confirmation status of a submitted transaction. /// /// `NotFound` covers two cases the chain backend can't easily /// distinguish: the tx is in some mempool but not yet indexed by /// Koios, or it was never submitted / was rejected. Treat /// `NotFound` as "keep polling" up to a reasonable timeout, after /// which give up. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "status", rename_all = "snake_case")] pub enum TxStatus { /// Confirmed on-chain with `num_confirmations` blocks built on /// top of it (1 = just landed, ~15 = practically final on /// preprod, ≥3 stake-pool epochs = absolute finality on mainnet). Confirmed { num_confirmations: u64 }, /// Koios's `/tx_status` returned a record for this tx but with /// `num_confirmations: null` — the tx is known to the backend /// (some node accepted it) but is not yet in a block. Pending, /// Not seen by the chain backend (not in any node's pool, /// not confirmed, possibly never submitted or rejected). NotFound, } #[async_trait::async_trait] pub trait ChainBackend: Send + Sync { /// All UTXOs at the given address. async fn get_utxos(&self, address: &str) -> Result, ChainError>; /// Aggregated ADA + native-asset balance at the address. async fn get_balance(&self, address: &str) -> Result; /// Submit a signed transaction. `raw_tx_cbor` is the binary CBOR /// payload of the signed tx; on success returns the tx hash as /// a hex string. async fn submit_tx(&self, raw_tx_cbor: &[u8]) -> Result; /// Poll the chain backend for a tx's current confirmation status. async fn tx_status(&self, tx_hash: &str) -> Result; }