Skip to main content

typhoon_token/
lib.rs

1#![no_std]
2
3use {
4    core::{mem::transmute, ops::Deref},
5    pinocchio::error::ProgramError,
6    pinocchio_associated_token_account::ID as ATA_PROGRAM_ID,
7    pinocchio_token::{
8        state::{Mint as SplMint, TokenAccount as SplTokenAccount},
9        ID as TOKEN_PROGRAM_ID,
10    },
11    solana_address::{address_eq, Address},
12    typhoon_traits::{Accessor, CheckOwner, CheckProgramId, DataStrategy, Discriminator},
13};
14
15mod traits;
16
17pub use {
18    pinocchio_associated_token_account::instructions as ata_instructions,
19    pinocchio_token::instructions as spl_instructions, traits::*,
20};
21
22#[cfg(feature = "token2022")]
23const TOKEN_2022_PROGRAM_ID: Address =
24    Address::from_str_const("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
25
26pub struct AtaTokenProgram;
27
28impl CheckProgramId for AtaTokenProgram {
29    #[inline(always)]
30    fn address_eq(program_id: &Address) -> bool {
31        address_eq(program_id, &ATA_PROGRAM_ID)
32    }
33}
34
35pub struct TokenProgram;
36
37impl CheckProgramId for TokenProgram {
38    #[inline(always)]
39    fn address_eq(program_id: &Address) -> bool {
40        #[cfg(feature = "token2022")]
41        {
42            address_eq(program_id, &TOKEN_PROGRAM_ID)
43                || address_eq(program_id, &TOKEN_2022_PROGRAM_ID)
44        }
45        #[cfg(not(feature = "token2022"))]
46        {
47            address_eq(program_id, &TOKEN_PROGRAM_ID)
48        }
49    }
50}
51
52pub struct SplStrategy;
53
54impl<'a> Accessor<'a, Mint> for SplStrategy {
55    type Data = &'a Mint;
56
57    #[inline(always)]
58    fn access(data: &'a [u8]) -> Result<Self::Data, ProgramError> {
59        // SAFETY: `Mint` is `#[repr(transparent)]` over `SplMint`,
60        // so the reference cast preserves layout/alignment/lifetime. The caller
61        // must also guarantee `data` encodes a valid token account state.
62        Ok(unsafe { transmute::<&SplMint, &Mint>(SplMint::from_bytes_unchecked(data)) })
63    }
64
65    #[inline(always)]
66    fn read(data: &mut &'a [u8]) -> Result<Self::Data, ProgramError> {
67        let Some((to_read, rem)) = data.split_at_checked(Mint::LEN) else {
68            return Err(ProgramError::InvalidInstructionData);
69        };
70        *data = rem;
71        Ok(<Self as Accessor<Mint>>::access(to_read)?)
72    }
73}
74
75impl<'a> Accessor<'a, TokenAccount> for SplStrategy {
76    type Data = &'a TokenAccount;
77
78    #[inline(always)]
79    fn access(data: &'a [u8]) -> Result<Self::Data, ProgramError> {
80        // SAFETY: `TokenAccount` is `#[repr(transparent)]` over `SplTokenAccount`,
81        // so the reference cast preserves layout/alignment/lifetime. The caller
82        // must also guarantee `data` encodes a valid token account state.
83        Ok(unsafe {
84            transmute::<&SplTokenAccount, &TokenAccount>(SplTokenAccount::from_bytes_unchecked(
85                data,
86            ))
87        })
88    }
89
90    #[inline(always)]
91    fn read(data: &mut &'a [u8]) -> Result<Self::Data, ProgramError> {
92        let Some((to_read, rem)) = data.split_at_checked(TokenAccount::LEN) else {
93            return Err(ProgramError::InvalidInstructionData);
94        };
95        *data = rem;
96        Ok(<Self as Accessor<TokenAccount>>::access(to_read)?)
97    }
98}
99
100#[repr(transparent)]
101pub struct Mint(SplMint);
102
103impl Mint {
104    pub const LEN: usize = SplMint::LEN;
105}
106
107impl DataStrategy for Mint {
108    type Strategy = SplStrategy;
109}
110
111impl Discriminator for Mint {
112    const DISCRIMINATOR: &'static [u8] = &[];
113}
114
115impl CheckOwner for Mint {
116    #[inline(always)]
117    fn owned_by(program_id: &Address) -> bool {
118        #[cfg(feature = "token2022")]
119        {
120            address_eq(program_id, &TOKEN_PROGRAM_ID)
121                || address_eq(program_id, &TOKEN_2022_PROGRAM_ID)
122        }
123        #[cfg(not(feature = "token2022"))]
124        {
125            address_eq(program_id, &TOKEN_PROGRAM_ID)
126        }
127    }
128}
129
130impl Deref for Mint {
131    type Target = SplMint;
132
133    fn deref(&self) -> &Self::Target {
134        &self.0
135    }
136}
137
138#[repr(transparent)]
139pub struct TokenAccount(SplTokenAccount);
140
141impl TokenAccount {
142    pub const LEN: usize = SplTokenAccount::LEN;
143}
144
145impl DataStrategy for TokenAccount {
146    type Strategy = SplStrategy;
147}
148
149impl Discriminator for TokenAccount {
150    const DISCRIMINATOR: &'static [u8] = &[];
151}
152
153impl CheckOwner for TokenAccount {
154    #[inline(always)]
155    fn owned_by(program_id: &Address) -> bool {
156        #[cfg(feature = "token2022")]
157        {
158            address_eq(program_id, &TOKEN_PROGRAM_ID)
159                || address_eq(program_id, &TOKEN_2022_PROGRAM_ID)
160        }
161        #[cfg(not(feature = "token2022"))]
162        {
163            address_eq(program_id, &TOKEN_PROGRAM_ID)
164        }
165    }
166}
167
168impl Deref for TokenAccount {
169    type Target = SplTokenAccount;
170
171    fn deref(&self) -> &Self::Target {
172        &self.0
173    }
174}
175
176pub fn find_associated_token_address(mint: &Address, owner: &Address) -> Address {
177    Address::find_program_address(
178        &[owner.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
179        &ATA_PROGRAM_ID,
180    )
181    .0
182}