Skip to main content

safe_oracle/
registry_client.rs

1use soroban_sdk::{contractclient, contracttype, Address, Env};
2
3/// Cross-contract client trait for `LiquidityRegistry`.
4///
5/// The auto-generated `LiquidityRegistryClient` is consumed by Phase 4's
6/// `check_liquidity` and `check_thin_sampling` to read SDEX trade
7/// attestations. Defining the binding here (rather than depending on the
8/// `liquidity-registry` crate directly) keeps `safe-oracle` decoupled at link
9/// time and matches the `ReflectorClient` pattern already in use for the
10/// oracle binding.
11///
12/// Only `get_snapshot` is mirrored: `safe-oracle` is a read-side consumer of
13/// the registry — write-side methods (`initialize`, `add_attester`,
14/// `remove_attester`, `write_snapshot`) belong to admin/attester tooling and
15/// have no place in the guardrail call path.
16// The trait exists solely so `#[contractclient]` can synthesize the client
17// struct; nothing calls it directly.
18#[allow(dead_code)]
19#[contractclient(name = "LiquidityRegistryClient")]
20pub trait LiquidityRegistry {
21    fn get_snapshot(env: Env, asset: Address) -> Option<LiquiditySnapshot>;
22}
23
24/// Mirror of `liquidity-registry::LiquiditySnapshot`.
25///
26/// Defined independently here to avoid making `safe-oracle` depend on the
27/// `liquidity-registry` crate. Soroban's contracttype serialization is
28/// structural, so a snapshot written by the contract round-trips into this
29/// type as long as the field order, names, and types stay aligned. Any change
30/// to the registry's snapshot shape must be mirrored here in lockstep — Phase
31/// 4 tests will catch a drift via integration round-trip.
32///
33/// **Precision:** All USD-denominated fields use **7-decimal precision**
34/// (Stellar stroop convention). 1 USD = 10_000_000 (10^7). This matches
35/// `SafeOracleConfig::min_liquidity_usd` for direct comparison without scaling.
36/// Reflector uses 14-decimal precision for *prices*, but liquidity volumes are
37/// dollar-denominated and follow the project-wide 7-decimal convention.
38#[contracttype]
39#[derive(Clone, Debug, Eq, PartialEq)]
40pub struct LiquiditySnapshot {
41    /// Asset this snapshot describes.
42    pub asset: Address,
43    /// SDEX trading volume during the last 30 minutes, denominated in USD with
44    /// 7-decimal precision. Example: $50,000 = `500_000_000_000`.
45    pub volume_30m_usd: i128,
46    /// Count of unique trades on SDEX during the last 1 hour. Used by
47    /// `safe_oracle::check_thin_sampling` (Layer 2 Guardrail #5) to reject
48    /// markets where price discovery is too thin to trust.
49    pub unique_trades_1h: u32,
50    /// Snapshot creation time, in Unix seconds (matching ledger timestamp).
51    /// Consumers compare against `env.ledger().timestamp()` and their own
52    /// `max_snapshot_age_seconds` threshold to enforce freshness.
53    pub timestamp: u64,
54    /// Address that wrote this snapshot. Must be in the attester whitelist;
55    /// equality with the caller is enforced in `write_snapshot`.
56    pub attester: Address,
57}