pyra-tokens 0.5.2

Token definitions for supported assets on Pyra
Documentation
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 {}

/// A validated asset ID guaranteed to reference a supported token.
///
/// Construct via `AssetId::new()` or `TryFrom<u16>`. Once constructed,
/// lookups like `token()` and `drift_market_index()` are infallible
/// for the "unknown asset" case.
#[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 {
    // Named constants for compile-time safe construction.
    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);

    /// Create a validated `AssetId`. Returns `Err` if the ID doesn't
    /// match a supported token.
    pub fn new(id: u16) -> Result<Self, TokenError> {
        if (id as usize) < SUPPORTED_TOKENS.len() {
            Ok(Self(id))
        } else {
            Err(TokenError::UnknownAssetId(id))
        }
    }

    /// The raw `u16` value.
    pub const fn value(self) -> u16 {
        self.0
    }

    /// The token this ID refers to. Always valid — guaranteed by construction.
    pub fn token(self) -> &'static Token {
        &SUPPORTED_TOKENS[self.0 as usize]
    }

    /// The Drift market index for this asset, if it has one.
    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; 15] = [
    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,
    },
];

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());
    }
}