Skip to main content

nautilus_model/defi/
token.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{fmt::Display, sync::Arc};
17
18use alloy_primitives::Address;
19use serde::{Deserialize, Serialize};
20
21use crate::defi::chain::SharedChain;
22
23/// Represents a cryptocurrency token on a blockchain network.
24#[cfg_attr(
25    feature = "python",
26    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
27)]
28#[cfg_attr(
29    feature = "python",
30    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
31)]
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct Token {
34    /// The blockchain network where this token exists.
35    pub chain: SharedChain,
36    /// The blockchain address of the token contract.
37    pub address: Address,
38    /// The full name of the token.
39    pub name: String,
40    /// The token's ticker symbol.
41    pub symbol: String,
42    /// The number of decimal places used to represent fractional token amounts.
43    pub decimals: u8,
44}
45
46/// A thread-safe shared pointer to a `Token`, enabling efficient reuse across multiple components.
47pub type SharedToken = Arc<Token>;
48
49impl Token {
50    /// Creates a new [`Token`] instance with the specified properties.
51    #[must_use]
52    pub fn new(
53        chain: SharedChain,
54        address: Address,
55        name: String,
56        symbol: String,
57        decimals: u8,
58    ) -> Self {
59        Self {
60            chain,
61            address,
62            name,
63            symbol,
64            decimals,
65        }
66    }
67
68    /// Returns true if this token is a stablecoin.
69    ///
70    /// Checks against common stablecoin symbols including USD-pegged tokens,
71    /// Euro-pegged tokens, and other algorithmic/collateralized stablecoins.
72    #[must_use]
73    pub fn is_stablecoin(&self) -> bool {
74        matches!(
75            self.symbol.as_str(),
76            "USDC"
77                | "USDT"
78                | "DAI"
79                | "BUSD"
80                | "FRAX"
81                | "LUSD"
82                | "TUSD"
83                | "USDP"
84                | "GUSD"
85                | "SUSD"
86                | "UST"
87                | "USDD"
88                | "CUSD"
89                | "EUROC"
90                | "EURT"
91                | "EURS"
92                | "AGEUR"
93                | "MIM"
94                | "FEI"
95                | "OUSD"
96                | "USDB"
97        )
98    }
99
100    /// Returns true if this token is a native blockchain currency wrapper.
101    ///
102    /// Identifies wrapped versions of native currencies like WETH (Wrapped ETH),
103    /// WMATIC (Wrapped MATIC), WBNB (Wrapped BNB), etc.
104    #[must_use]
105    pub fn is_native_currency(&self) -> bool {
106        matches!(
107            self.symbol.as_str(),
108            "WETH"
109                | "ETH"
110                | "WMATIC"
111                | "MATIC"
112                | "WBNB"
113                | "BNB"
114                | "WAVAX"
115                | "AVAX"
116                | "WFTM"
117                | "FTM"
118        )
119    }
120
121    /// Returns the priority of this token for base/quote determination.
122    ///
123    /// Lower numbers indicate higher priority to become the quote token (pricing currency).
124    /// This follows market conventions where trades are quoted in the most liquid/stable assets.
125    ///
126    /// # Priority Levels
127    /// - **1**: Stablecoins (USDC, USDT, DAI, etc.) - Highest priority to be quote
128    /// - **2**: Native currencies (WETH, WMATIC, WBNB, etc.) - Medium priority
129    /// - **3**: Other tokens - Lowest priority (typically become base tokens)
130    #[must_use]
131    pub fn get_token_priority(&self) -> u8 {
132        if self.is_stablecoin() {
133            1
134        } else if self.is_native_currency() {
135            2
136        } else {
137            3
138        }
139    }
140}
141
142impl Display for Token {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        write!(f, "Token(symbol={}, name={})", self.symbol, self.name)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use std::sync::Arc;
151
152    use rstest::rstest;
153
154    use super::*;
155    use crate::defi::{chain::chains, stubs::weth};
156
157    #[rstest]
158    fn test_token_constructor(weth: Token) {
159        assert_eq!(weth.chain.chain_id, chains::ARBITRUM.chain_id);
160        assert_eq!(weth.name, "Wrapped Ether");
161        assert_eq!(weth.symbol, "WETH");
162        assert_eq!(weth.decimals, 18);
163        assert!(weth.is_native_currency());
164    }
165
166    #[rstest]
167    fn test_token_display_with_special_characters() {
168        // Test edge case where token names/symbols contain formatting characters
169        let chain = Arc::new(chains::ETHEREUM.clone());
170        let token = Token::new(
171            chain,
172            "0xA0b86a33E6441b936662bb6B5d1F8Fb0E2b57A5D"
173                .parse()
174                .unwrap(),
175            "Test Token (with parentheses)".to_string(),
176            "TEST-1".to_string(),
177            18,
178        );
179
180        let display = token.to_string();
181        assert_eq!(
182            display,
183            "Token(symbol=TEST-1, name=Test Token (with parentheses))"
184        );
185        assert!(!token.is_native_currency());
186        assert!(!token.is_stablecoin());
187        assert_eq!(token.get_token_priority(), 3);
188    }
189}