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}