Skip to main content

scope/compliance/
token_risk.rs

1//! # Token risk classes (M7)
2//!
3//! The one place that decides when liquidity is low and when trading looks
4//! like a honeypot. The crawl report, the compliance engine and the
5//! insights summary read these classes, so they never disagree on whether
6//! to flag a token. Each keeps its own scoring scale.
7
8/// Liquidity below this (USD) is very low: high slippage on any trade.
9pub const VERY_LOW_LIQUIDITY_USD: f64 = 10_000.0;
10/// Liquidity below this (USD) is low: slippage on larger trades.
11pub const LOW_LIQUIDITY_USD: f64 = 100_000.0;
12/// Liquidity above this (USD) is good: enough depth for most trades.
13pub const GOOD_LIQUIDITY_USD: f64 = 1_000_000.0;
14
15/// A buy/sell ratio above this is a high honeypot risk.
16pub const HONEYPOT_HIGH_RATIO: f64 = 10.0;
17/// A buy/sell ratio above this is a medium honeypot risk.
18pub const HONEYPOT_MEDIUM_RATIO: f64 = 3.0;
19
20/// How deep a token's pools are.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum LiquidityTier {
23    /// Below [`VERY_LOW_LIQUIDITY_USD`], including none.
24    VeryLow,
25    /// Below [`LOW_LIQUIDITY_USD`].
26    Low,
27    /// Between low and good.
28    Moderate,
29    /// Above [`GOOD_LIQUIDITY_USD`].
30    Good,
31}
32
33/// The tier of `liquidity_usd`. No liquidity is very low, not unknown.
34pub fn liquidity_tier(liquidity_usd: f64) -> LiquidityTier {
35    if liquidity_usd < VERY_LOW_LIQUIDITY_USD {
36        LiquidityTier::VeryLow
37    } else if liquidity_usd < LOW_LIQUIDITY_USD {
38        LiquidityTier::Low
39    } else if liquidity_usd > GOOD_LIQUIDITY_USD {
40        LiquidityTier::Good
41    } else {
42        LiquidityTier::Moderate
43    }
44}
45
46/// What 24h trading says about a honeypot (a token that cannot be sold).
47#[derive(Debug, Clone, Copy, PartialEq)]
48pub enum Honeypot {
49    /// No buys and no sells: nothing to judge.
50    Unknown,
51    /// Buys and no sells: the strongest sign.
52    NoSells { buys: u64 },
53    /// Buys per sell.
54    Ratio(f64),
55}
56
57/// How strong a honeypot sign is.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
59pub enum HoneypotLevel {
60    Unknown,
61    Low,
62    Medium,
63    High,
64}
65
66/// The honeypot sign of `buys` and `sells` in 24h.
67pub fn honeypot(buys: u64, sells: u64) -> Honeypot {
68    match (buys, sells) {
69        (0, 0) => Honeypot::Unknown,
70        (b, 0) => Honeypot::NoSells { buys: b },
71        (b, s) => Honeypot::Ratio(b as f64 / s as f64),
72    }
73}
74
75impl Honeypot {
76    /// The one level rule: no sells, or a ratio above
77    /// [`HONEYPOT_HIGH_RATIO`], is high; above [`HONEYPOT_MEDIUM_RATIO`] is
78    /// medium.
79    pub fn level(self) -> HoneypotLevel {
80        match self {
81            Honeypot::Unknown => HoneypotLevel::Unknown,
82            Honeypot::NoSells { .. } => HoneypotLevel::High,
83            Honeypot::Ratio(r) if r > HONEYPOT_HIGH_RATIO => HoneypotLevel::High,
84            Honeypot::Ratio(r) if r > HONEYPOT_MEDIUM_RATIO => HoneypotLevel::Medium,
85            Honeypot::Ratio(_) => HoneypotLevel::Low,
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_liquidity_tiers_and_their_bounds() {
96        assert_eq!(liquidity_tier(0.0), LiquidityTier::VeryLow);
97        assert_eq!(liquidity_tier(9_999.0), LiquidityTier::VeryLow);
98        assert_eq!(liquidity_tier(75_000.0), LiquidityTier::Low);
99        assert_eq!(liquidity_tier(500_000.0), LiquidityTier::Moderate);
100        assert_eq!(liquidity_tier(1_000_000.0), LiquidityTier::Moderate);
101        assert_eq!(liquidity_tier(1_000_001.0), LiquidityTier::Good);
102    }
103
104    #[test]
105    fn test_any_buys_with_no_sells_is_a_high_honeypot_sign() {
106        // The audit case: 5 buys and 0 sells must be flagged everywhere.
107        assert_eq!(honeypot(5, 0), Honeypot::NoSells { buys: 5 });
108        assert_eq!(honeypot(5, 0).level(), HoneypotLevel::High);
109        assert_eq!(honeypot(0, 0).level(), HoneypotLevel::Unknown);
110        assert_eq!(honeypot(11, 1).level(), HoneypotLevel::High);
111        assert_eq!(honeypot(4, 1).level(), HoneypotLevel::Medium);
112        assert_eq!(honeypot(3, 1).level(), HoneypotLevel::Low);
113    }
114}