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 pyth_price_feed: PythFeedId,
pub is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x67be9f519b95cf24338801051f9a808eff0a578ccb388db73b7f6fe1de019ffb"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x2817d7bfe5c64b8ea956e9a26f573ef64e72e4d7891f2d6af9bcc93f7aff9a97"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x9d4294bbcd1174d6f2003ec365831e64cc31d9f6f15a2b85399db8d5000960f6"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b"
),
is_usd_stablecoin: true,
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,
pyth_price_feed: feed_id!(
"0xc1da1b73d7f01e7ddd54b3766cf7fcd644395ad14f70aa706ec5384c59e76692"
),
is_usd_stablecoin: true,
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,
pyth_price_feed: feed_id!(
"0x77f0971af11cc8bac224917275c1bf55f2319ed5c654a1ca955c82fa2d297ea1"
),
is_usd_stablecoin: true,
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,
pyth_price_feed: feed_id!(
"0xc9d8b075a5c69303365ae23633d4e085199bf5c520a3b90fed1322a0342ffc33"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0xc811abc82b4bad1f9bd711a2773ccaa935b03ecef974236942cec5e0eb845a3a"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x89875379e70f8fbadc17aef315adf3a8d5d160b811435537e03c97e8aac97d9c"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x72b021217ca3fe68922a19aaf990109cb9d84e9ad004b4d2025ad6f529314419"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x0a0408d619e9380abad35060f9192039ed5042fa6f82301d0e48bb52be830996"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x3d824c7f7c26ed1c85421ecec8c754e6b52d66a4e45de20a9c9ea91de8b396f9"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x2ad31d1c4a85fbf2156ce57fab4104124c5ef76a6386375ecfc8da1ed5ce1486"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x2817b78438c769357182c04346fddaad1178c82f4048828fe0997c3c64624e14"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x178a6f73a5aede9d0d682e86b0047c9f333ed0efe5c6537ca937565219c4054d"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x47a156470288850a440df3a6ce85a55917b813a19bb5b31128a33a986566a362"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x4244d07890e4610f46bbde67de8f43a4bf8b569eebe904f136b469f148503b7f"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0xb911b0329028cd0283e4259c33809d62942bd2716a58084e5f31d64c00b5424e"
),
is_usd_stablecoin: 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,
pyth_price_feed: feed_id!(
"0x978e6cc68a119ce066aa830017318563a9ed04ec3a0a6439010fc11296a58675"
),
is_usd_stablecoin: 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());
}
}