Skip to main content

near_kit/tokens/
known.rs

1//! Known token addresses for common FT contracts.
2//!
3//! This module provides verified contract addresses for popular tokens,
4//! automatically resolving to the correct address based on the network.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use near_kit::{Near, tokens};
10//!
11//! # async fn example() -> Result<(), near_kit::Error> {
12//! let near = Near::mainnet().build();
13//!
14//! // Use known token constants - auto-resolves based on network
15//! let usdc = near.ft(tokens::USDC)?;
16//! let balance = usdc.balance_of("alice.near").await?;
17//!
18//! // Still works with raw addresses
19//! let custom = near.ft("custom-token.near")?;
20//! # Ok(())
21//! # }
22//! ```
23
24use crate::error::Error;
25use crate::types::{AccountId, Network};
26
27/// A known fungible token with verified addresses for different networks.
28///
29/// Use the predefined constants like [`USDC`], [`USDT`], and [`W_NEAR`]
30/// for common tokens.
31#[derive(Debug, Clone, Copy)]
32pub struct KnownToken {
33    /// Human-readable name for error messages.
34    pub name: &'static str,
35    /// Contract address on mainnet.
36    pub mainnet: &'static str,
37    /// Contract address on testnet (if available).
38    pub testnet: Option<&'static str>,
39}
40
41impl KnownToken {
42    /// Resolve this token to an [`AccountId`] for the given network.
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if the token is not available on the specified network
47    /// (e.g., some tokens don't have testnet deployments).
48    pub fn resolve(&self, network: Network) -> Result<AccountId, Error> {
49        let address = match network {
50            Network::Mainnet => self.mainnet,
51            Network::Testnet => self.testnet.ok_or_else(|| Error::TokenNotAvailable {
52                token: self.name.to_string(),
53                network: network.to_string(),
54            })?,
55            Network::Sandbox | Network::Custom => {
56                return Err(Error::TokenNotAvailable {
57                    token: self.name.to_string(),
58                    network: network.to_string(),
59                });
60            }
61        };
62        address.parse().map_err(Into::into)
63    }
64}
65
66/// Trait for types that can be resolved to a contract [`AccountId`].
67///
68/// This enables the `ft()` and `nft()` methods to accept both raw addresses
69/// and [`KnownToken`] constants, resolving them based on the client's network.
70pub trait IntoContractId {
71    /// Resolve this to a contract [`AccountId`] for the given network.
72    fn into_contract_id(self, network: Network) -> Result<AccountId, Error>;
73}
74
75impl IntoContractId for &str {
76    fn into_contract_id(self, _network: Network) -> Result<AccountId, Error> {
77        self.parse().map_err(Into::into)
78    }
79}
80
81impl IntoContractId for String {
82    fn into_contract_id(self, _network: Network) -> Result<AccountId, Error> {
83        self.parse().map_err(Into::into)
84    }
85}
86
87impl IntoContractId for AccountId {
88    fn into_contract_id(self, _network: Network) -> Result<AccountId, Error> {
89        Ok(self)
90    }
91}
92
93impl IntoContractId for &AccountId {
94    fn into_contract_id(self, _network: Network) -> Result<AccountId, Error> {
95        Ok(self.clone())
96    }
97}
98
99impl IntoContractId for KnownToken {
100    fn into_contract_id(self, network: Network) -> Result<AccountId, Error> {
101        self.resolve(network)
102    }
103}
104
105// =============================================================================
106// Stablecoins
107// =============================================================================
108
109/// USDC (USD Coin) - Circle's USD stablecoin.
110///
111/// - Mainnet: `17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1`
112/// - Testnet: `3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af`
113pub const USDC: KnownToken = KnownToken {
114    name: "USDC",
115    mainnet: "17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
116    testnet: Some("3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af"),
117};
118
119/// USDT (Tether USD) - Tether's USD stablecoin.
120///
121/// - Mainnet: `usdt.tether-token.near`
122/// - Testnet: Not available
123pub const USDT: KnownToken = KnownToken {
124    name: "USDT",
125    mainnet: "usdt.tether-token.near",
126    testnet: None,
127};
128
129// =============================================================================
130// Wrapped Tokens
131// =============================================================================
132
133/// wNEAR (Wrapped NEAR) - Wrapped version of NEAR for DeFi compatibility.
134///
135/// - Mainnet: `wrap.near`
136/// - Testnet: `wrap.testnet`
137pub const W_NEAR: KnownToken = KnownToken {
138    name: "wNEAR",
139    mainnet: "wrap.near",
140    testnet: Some("wrap.testnet"),
141};
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_usdc_mainnet() {
149        let account = USDC.resolve(Network::Mainnet).unwrap();
150        assert_eq!(
151            account.as_str(),
152            "17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1"
153        );
154    }
155
156    #[test]
157    fn test_usdc_testnet() {
158        let account = USDC.resolve(Network::Testnet).unwrap();
159        assert_eq!(
160            account.as_str(),
161            "3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af"
162        );
163    }
164
165    #[test]
166    fn test_usdt_mainnet() {
167        let account = USDT.resolve(Network::Mainnet).unwrap();
168        assert_eq!(account.as_str(), "usdt.tether-token.near");
169    }
170
171    #[test]
172    fn test_usdt_testnet_not_available() {
173        let result = USDT.resolve(Network::Testnet);
174        assert!(result.is_err());
175    }
176
177    #[test]
178    fn test_wnear_mainnet() {
179        let account = W_NEAR.resolve(Network::Mainnet).unwrap();
180        assert_eq!(account.as_str(), "wrap.near");
181    }
182
183    #[test]
184    fn test_wnear_testnet() {
185        let account = W_NEAR.resolve(Network::Testnet).unwrap();
186        assert_eq!(account.as_str(), "wrap.testnet");
187    }
188
189    #[test]
190    fn test_sandbox_not_available() {
191        let result = USDC.resolve(Network::Sandbox);
192        assert!(result.is_err());
193    }
194
195    #[test]
196    fn test_custom_not_available() {
197        let result = USDC.resolve(Network::Custom);
198        assert!(result.is_err());
199    }
200}