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}