use core::fmt;
use solana_pubkey::{pubkey, Pubkey};
mod macros;
pub type PythFeedId = [u8; 32];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenError {
UnknownAssetId(u16),
}
impl fmt::Display for TokenError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownAssetId(id) => write!(f, "no token with asset_id {id}"),
}
}
}
impl std::error::Error for TokenError {}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
#[serde(try_from = "u16", into = "u16")]
pub struct AssetId(u16);
impl AssetId {
pub const USDC: Self = Self(0);
pub const WSOL: Self = Self(1);
pub const JITOSOL: Self = Self(2);
pub const CBBTC: Self = Self(3);
pub const WETH: Self = Self(4);
pub const USDT: Self = Self(5);
pub const PYUSD: Self = Self(6);
pub const USDS: Self = Self(7);
pub const WBTC: Self = Self(8);
pub const JLP: Self = Self(9);
pub const BSOL: Self = Self(10);
pub const BONK: Self = Self(11);
pub const JUP: Self = Self(12);
pub const ZBTC: Self = Self(13);
pub const SYRUP_USDC: Self = Self(14);
pub const SPYX: Self = Self(15);
pub const QQQX: Self = Self(16);
pub const TSLAX: Self = Self(17);
pub const NVDAX: Self = Self(18);
pub const GOOGLX: Self = Self(19);
pub const AAPLX: Self = Self(20);
pub fn new(id: u16) -> Result<Self, TokenError> {
if (id as usize) < SUPPORTED_TOKENS.len() {
Ok(Self(id))
} else {
Err(TokenError::UnknownAssetId(id))
}
}
pub const fn value(self) -> u16 {
self.0
}
pub fn token(self) -> &'static Token {
&SUPPORTED_TOKENS[self.0 as usize]
}
pub fn drift_market_index(self) -> Option<u16> {
self.token().drift_market_index
}
}
impl TryFrom<u16> for AssetId {
type Error = TokenError;
fn try_from(id: u16) -> Result<Self, Self::Error> {
Self::new(id)
}
}
impl From<AssetId> for u16 {
fn from(id: AssetId) -> u16 {
id.0
}
}
impl fmt::Display for AssetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub const TOKEN_PROGRAM_ID: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
pub const TOKEN_2022_PROGRAM_ID: Pubkey = pubkey!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
pub const PYRA_PROGRAM_ID: Pubkey = pubkey!("6JjHXLheGSNvvexgzMthEcgjkcirDrGduc3HAKB2P1v2");
pub const USDC: Token = SUPPORTED_TOKENS[0];
pub const WSOL: Token = SUPPORTED_TOKENS[1];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Token {
pub asset_id: AssetId,
pub name: &'static str,
pub mint: Pubkey,
pub token_program: Pubkey,
pub decimals: u8,
pub is_usd_stablecoin: bool,
pub is_borrowable: bool,
pub drift_market_index: Option<u16>,
pub kamino_reserve: Option<Pubkey>,
}
impl Token {
pub fn find_by_asset_id(id: AssetId) -> &'static Self {
&SUPPORTED_TOKENS[id.0 as usize]
}
pub fn find_by_mint(mint: &Pubkey) -> Option<&'static Self> {
SUPPORTED_TOKENS.iter().find(|token| token.mint == *mint)
}
pub fn find_by_drift_market_index(drift_market_index: u16) -> Option<&'static Self> {
SUPPORTED_TOKENS_DRIFT
.iter()
.find(|token| token.drift_market_index == Some(drift_market_index))
}
pub fn find_by_kamino_reserve(reserve: &Pubkey) -> Option<&'static Self> {
SUPPORTED_TOKENS_KAMINO
.iter()
.find(|token| token.kamino_reserve == Some(*reserve))
}
}
pub const SUPPORTED_TOKENS: [Token; 21] = [
Token {
asset_id: AssetId(0),
name: "USDC",
mint: pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"),
token_program: TOKEN_PROGRAM_ID,
decimals: 6,
is_usd_stablecoin: true,
is_borrowable: true,
drift_market_index: Some(0),
kamino_reserve: Some(pubkey!("97zoywd8mPZsGTg8q1wdD2Wgkdrs2tqusp1Qqcxbyj7E")),
},
Token {
asset_id: AssetId(1),
name: "wSOL",
mint: pubkey!("So11111111111111111111111111111111111111112"),
token_program: TOKEN_PROGRAM_ID,
decimals: 9,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(1),
kamino_reserve: None,
},
Token {
asset_id: AssetId(2),
name: "JitoSOL",
mint: pubkey!("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn"),
token_program: TOKEN_PROGRAM_ID,
decimals: 9,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(6),
kamino_reserve: None,
},
Token {
asset_id: AssetId(3),
name: "cbBTC",
mint: pubkey!("cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij"),
token_program: TOKEN_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(27),
kamino_reserve: None,
},
Token {
asset_id: AssetId(4),
name: "wETH",
mint: pubkey!("7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"),
token_program: TOKEN_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(4),
kamino_reserve: None,
},
Token {
asset_id: AssetId(5),
name: "USDT",
mint: pubkey!("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"),
token_program: TOKEN_PROGRAM_ID,
decimals: 6,
is_usd_stablecoin: true,
is_borrowable: false,
drift_market_index: Some(5),
kamino_reserve: None,
},
Token {
asset_id: AssetId(6),
name: "PYUSD",
mint: pubkey!("2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo"),
token_program: TOKEN_2022_PROGRAM_ID,
decimals: 6,
is_usd_stablecoin: true,
is_borrowable: false,
drift_market_index: Some(22),
kamino_reserve: None,
},
Token {
asset_id: AssetId(7),
name: "USDS",
mint: pubkey!("USDSwr9ApdHk5bvJKMjzff41FfuX8bSxdKcR81vTwcA"),
token_program: TOKEN_PROGRAM_ID,
decimals: 6,
is_usd_stablecoin: true,
is_borrowable: false,
drift_market_index: Some(28),
kamino_reserve: None,
},
Token {
asset_id: AssetId(8),
name: "wBTC",
mint: pubkey!("3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh"),
token_program: TOKEN_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(3),
kamino_reserve: None,
},
Token {
asset_id: AssetId(9),
name: "JLP",
mint: pubkey!("27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4"),
token_program: TOKEN_PROGRAM_ID,
decimals: 6,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(19),
kamino_reserve: None,
},
Token {
asset_id: AssetId(10),
name: "bSOL",
mint: pubkey!("bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1"),
token_program: TOKEN_PROGRAM_ID,
decimals: 9,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(8),
kamino_reserve: None,
},
Token {
asset_id: AssetId(11),
name: "BONK",
mint: pubkey!("DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"),
token_program: TOKEN_PROGRAM_ID,
decimals: 5,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(32),
kamino_reserve: None,
},
Token {
asset_id: AssetId(12),
name: "JUP",
mint: pubkey!("JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"),
token_program: TOKEN_PROGRAM_ID,
decimals: 6,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(11),
kamino_reserve: None,
},
Token {
asset_id: AssetId(13),
name: "zBTC",
mint: pubkey!("zBTCug3er3tLyffELcvDNrKkCymbPWysGcWihESYfLg"),
token_program: TOKEN_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(45),
kamino_reserve: None,
},
Token {
asset_id: AssetId(14),
name: "syrupUSDC",
mint: pubkey!("AvZZF1YaZDziPY2RCK4oJrRVrbN3mTD9NL24hPeaZeUj"),
token_program: TOKEN_PROGRAM_ID,
decimals: 6,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: Some(57),
kamino_reserve: None,
},
Token {
asset_id: AssetId(15),
name: "SPYx",
mint: pubkey!("XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W"),
token_program: TOKEN_2022_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: None,
kamino_reserve: Some(pubkey!("UvXjBuC7YZYaGB9Rn1PpBD1GySmjzunXgE8Zev9ua8d")),
},
Token {
asset_id: AssetId(16),
name: "QQQx",
mint: pubkey!("Xs8S1uUs1zvS2p7iwtsG3b6fkhpvmwz4GYU3gWAmWHZ"),
token_program: TOKEN_2022_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: None,
kamino_reserve: Some(pubkey!("2jerdAXR8r2B6z3P7P6VgSiePQX7wqcpbEqdDbm8mgeB")),
},
Token {
asset_id: AssetId(17),
name: "TSLAx",
mint: pubkey!("XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB"),
token_program: TOKEN_2022_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: None,
kamino_reserve: Some(pubkey!("5iTiczqgUegqA3PpoNpotizMbY9n1sRWr3oL6igKvWuf")),
},
Token {
asset_id: AssetId(18),
name: "NVDAx",
mint: pubkey!("Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh"),
token_program: TOKEN_2022_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: None,
kamino_reserve: Some(pubkey!("7B66Az3tJhAo4bLkX8PzTixQ9ZGyHkkjxfVLhF26sP5q")),
},
Token {
asset_id: AssetId(19),
name: "GOOGLx",
mint: pubkey!("XsCPL9dNWBMvFtTmwcCA5v3xWPSMEBCszbQdiLLq6aN"),
token_program: TOKEN_2022_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: None,
kamino_reserve: Some(pubkey!("4wg6rEkGgHaEuxMduP46C1xFZ24Lnp5YgdNkZAHxFzsN")),
},
Token {
asset_id: AssetId(20),
name: "AAPLx",
mint: pubkey!("XsbEhLAtcf6HdfpFZ5xEMdqW8nfAvcsP5bdudRLJzJp"),
token_program: TOKEN_2022_PROGRAM_ID,
decimals: 8,
is_usd_stablecoin: false,
is_borrowable: false,
drift_market_index: None,
kamino_reserve: Some(pubkey!("CKJbqakbPGyhziowm19LPYz636UszuezfkitmpRtcLSH")),
},
];
filter_supported_tokens!(pub SUPPORTED_TOKENS_DRIFT, drift_market_index);
filter_supported_tokens!(pub SUPPORTED_TOKENS_KAMINO, kamino_reserve);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn asset_id_matches_array_position() {
for (i, token) in SUPPORTED_TOKENS.iter().enumerate() {
assert_eq!(
token.asset_id.value(),
i as u16,
"SUPPORTED_TOKENS[{i}] has asset_id {} — must match array position",
token.asset_id,
);
}
}
#[test]
fn named_constants_match_tokens() {
assert_eq!(AssetId::USDC.token().name, "USDC");
assert_eq!(AssetId::WSOL.token().name, "wSOL");
assert_eq!(AssetId::WETH.token().name, "wETH");
assert_eq!(AssetId::SYRUP_USDC.token().name, "syrupUSDC");
}
#[test]
fn drift_market_index_returns_option() {
assert_eq!(AssetId::USDC.drift_market_index(), Some(0));
assert_eq!(AssetId::PYUSD.drift_market_index(), Some(22));
}
#[test]
fn invalid_asset_id_returns_error() {
assert!(AssetId::new(999).is_err());
}
}