Skip to main content

gluonscan_core/
lib.rs

1//! # gluonscan-core
2//!
3//! The read + normalize **contract** for gluonscan: domain types, the adapter ports (traits),
4//! the integrity wrapper [`Complete`], the typed [`Error`], and the taxonomy enums
5//! ([`Chain`], [`Protocol`], [`Source`], [`Capability`], [`Detail`]).
6//!
7//! This crate does **no I/O**: no HTTP, no RPC, no runtime. Everything that touches the network
8//! is injected through a port (see [`ports`]). The types a fetch returns ARE the contract — there
9//! is no wire/DTO layer here; consumers map these types to whatever they need.
10//!
11//! The public API exposes [`alloy_primitives`] types ([`Address`], [`U256`]); it is re-exported so
12//! consumers can name the exact version this crate builds against and avoid a version mismatch.
13//!
14//! ## Invariants
15//! - **Complete or error.** An adapter wraps a value in [`Complete<T>`](Complete) only after every
16//!   required source succeeded — the convention that keeps partial data out of a reading.
17//! - **No fabricated value.** A missing price is [`Error::AbsentPrice`], never `0` or `1`.
18
19pub mod error;
20pub mod history;
21pub mod hygiene;
22pub mod model;
23pub mod ports;
24
25mod chain;
26pub use chain::{Chain, Ecosystem};
27pub use error::Error;
28pub use history::{EventKind, History, HistoryEvent};
29pub use model::{
30    scaled, to_raw, Amount, BorrowedAsset, Complete, Currency, LendingPosition, LiquidityPosition,
31    LockPosition, Money, NftPosition, Position, PositionStatus, Provenance, Reading, StakePosition,
32    Staleness, SuppliedAsset, Timestamp, Token, TokenAddress, WalletBalance, YieldKind,
33    YieldPosition,
34};
35pub use ports::{ChainProvider, Clock, Ctx, Http, PriceSource, ProtocolAdapter};
36
37pub use alloy_primitives::{self, Address, U256};
38
39/// A wallet identifier across ecosystems.
40#[non_exhaustive]
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub enum Wallet {
43    /// An EVM address.
44    Evm(Address),
45    /// A Solana base58 public key.
46    Solana(String),
47    /// A Bitcoin address.
48    Bitcoin(String),
49}
50
51impl Wallet {
52    /// The EVM address, or a permanent error if this is not an EVM wallet.
53    pub fn evm(&self) -> Result<Address, Error> {
54        match self {
55            Wallet::Evm(a) => Ok(*a),
56            _ => Err(Error::Permanent {
57                message: "expected an EVM wallet".into(),
58            }),
59        }
60    }
61
62    /// The Solana base58 public key, or a permanent error if this is not a Solana wallet.
63    pub fn solana(&self) -> Result<&str, Error> {
64        match self {
65            Wallet::Solana(s) => Ok(s),
66            _ => Err(Error::Permanent {
67                message: "expected a Solana wallet".into(),
68            }),
69        }
70    }
71
72    /// The Bitcoin address, or a permanent error if this is not a Bitcoin wallet.
73    pub fn bitcoin(&self) -> Result<&str, Error> {
74        match self {
75            Wallet::Bitcoin(s) => Ok(s),
76            _ => Err(Error::Permanent {
77                message: "expected a Bitcoin wallet".into(),
78            }),
79        }
80    }
81}
82
83/// A priceable asset key, chain-agnostic — unlike a bare EVM [`Address`], it can name a chain's
84/// native coin or a Solana SPL mint, so BTC and SPL balances are priceable too.
85#[non_exhaustive]
86#[derive(Debug, Clone, PartialEq, Eq, Hash)]
87pub enum Asset {
88    /// The chain's native coin (BTC on Bitcoin, ETH on Ethereum/L2s, SOL on Solana).
89    Native,
90    /// An EVM token contract.
91    Token(Address),
92    /// A Solana SPL mint (base58).
93    Mint(String),
94}
95
96impl Asset {
97    /// A short, human-readable key for diagnostics and [`Error::AbsentPrice`].
98    #[must_use]
99    pub fn label(&self) -> String {
100        match self {
101            Asset::Native => "native".to_string(),
102            Asset::Token(a) => format!("{a:#x}"),
103            Asset::Mint(m) => m.clone(),
104        }
105    }
106}
107
108/// A supported DeFi protocol. Identity only — a protocol may have several backend
109/// implementations (see [`Source`]); the user picks and configures which.
110#[non_exhaustive]
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112pub enum Protocol {
113    /// Aave V3 lending.
114    AaveV3,
115    /// Uniswap V3 concentrated liquidity.
116    UniswapV3,
117    /// Pendle yield tokens.
118    Pendle,
119    /// Raydium CLMM (Solana).
120    Raydium,
121    /// Kamino lending/liquidity (Solana).
122    Kamino,
123    /// Idle wallet token balances (not a protocol; a capability).
124    Wallet,
125    /// The chain's native coin balance (ETH, BNB, SOL, BTC, ...). Separate from [`Protocol::Wallet`]
126    /// so a single engine can route a native-balance read and a token-balance read independently on
127    /// the same chain (both would otherwise share one protocol and the engine would pick the first).
128    Native,
129    /// Wallet NFTs.
130    Nfts,
131}
132
133/// The backend a reading came from. A protocol can expose the same [`Capability`] through more
134/// than one source; capabilities are routed per source and each is configured independently.
135#[non_exhaustive]
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137pub enum Source {
138    /// A protocol's own HTTP API.
139    Api,
140    /// A subgraph (The Graph or equivalent).
141    Subgraph,
142    /// Direct on-chain reads (JSON-RPC / account decoding).
143    OnChain,
144}
145
146/// A unit of data an adapter can fetch. Capability is the addressing unit for routing: the union
147/// of a protocol's backends' capabilities is its coverage; an unsupported one yields
148/// [`Error::Unsupported`].
149#[non_exhaustive]
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
151pub enum Capability {
152    /// Current positions (balances / supplies / liquidity).
153    Positions,
154    /// Historical events (deposits, withdrawals, borrows, repays, collects).
155    History,
156    /// Uncollected / claimable fees.
157    Fees,
158    /// Account health factor.
159    HealthFactor,
160    /// Per-asset risk parameters (LTV, liquidation threshold).
161    RiskConfig,
162}
163
164/// The **minimum** detail a caller requests, which is also the cost ceiling it accepts. An adapter
165/// runs the cheapest fetch plan that satisfies it; receiving richer-than-requested (when free) is
166/// fine, doing extra work is not.
167#[non_exhaustive]
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
169pub enum Detail {
170    /// Does the wallet hold anything in this protocol?
171    Presence,
172    /// What is there, at coarse resolution.
173    Summary,
174    /// Everything: exact amounts, fees, ranges, health factor.
175    Full,
176}