hopper_solana/
interface.rs1use hopper_runtime::account::AccountView;
24use hopper_runtime::address::Address;
25use hopper_runtime::error::ProgramError;
26use hopper_runtime::instruction::{InstructionAccount, InstructionView, Signer};
27use hopper_runtime::ProgramResult;
28
29use crate::constants::{TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum TokenProgramKind {
34 Spl,
36 Token2022,
38}
39
40impl TokenProgramKind {
41 #[inline(always)]
43 pub const fn program_id(self) -> &'static Address {
44 match self {
45 TokenProgramKind::Spl => &TOKEN_PROGRAM_ID,
46 TokenProgramKind::Token2022 => &TOKEN_2022_PROGRAM_ID,
47 }
48 }
49
50 #[inline(always)]
55 pub fn from_owner(owner: &Address) -> Result<Self, ProgramError> {
56 if owner == &TOKEN_PROGRAM_ID {
57 Ok(TokenProgramKind::Spl)
58 } else if owner == &TOKEN_2022_PROGRAM_ID {
59 Ok(TokenProgramKind::Token2022)
60 } else {
61 Err(ProgramError::IncorrectProgramId)
62 }
63 }
64
65 #[inline(always)]
70 pub fn for_account(view: &AccountView) -> Result<Self, ProgramError> {
71 if view.owned_by(&TOKEN_PROGRAM_ID) {
72 Ok(TokenProgramKind::Spl)
73 } else if view.owned_by(&TOKEN_2022_PROGRAM_ID) {
74 Ok(TokenProgramKind::Token2022)
75 } else {
76 Err(ProgramError::IncorrectProgramId)
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy)]
99pub struct InterfaceTokenAccount<'a> {
100 data: &'a [u8],
102 pub kind: TokenProgramKind,
104}
105
106impl<'a> InterfaceTokenAccount<'a> {
107 pub fn from_data(data: &'a [u8], kind: TokenProgramKind) -> Result<Self, ProgramError> {
113 if data.len() < crate::token::TOKEN_ACCOUNT_LEN {
114 return Err(ProgramError::InvalidAccountData);
115 }
116 Ok(Self { data, kind })
117 }
118
119 #[inline(always)]
121 pub fn data(&self) -> &'a [u8] {
122 self.data
123 }
124
125 #[inline(always)]
127 pub fn mint(&self) -> Result<&'a Address, ProgramError> {
128 crate::token::token_account_mint(self.data)
129 }
130
131 #[inline(always)]
134 pub fn owner(&self) -> Result<&'a Address, ProgramError> {
135 crate::token::token_account_owner(self.data)
136 }
137
138 #[inline(always)]
140 pub fn amount(&self) -> Result<u64, ProgramError> {
141 crate::token::token_account_amount(self.data)
142 }
143
144 #[inline(always)]
146 pub fn state(&self) -> Result<u8, ProgramError> {
147 crate::token::token_account_state(self.data)
148 }
149
150 #[inline(always)]
152 pub fn assert_initialized(&self) -> Result<(), ProgramError> {
153 crate::token::check_token_initialized(self.data)
154 }
155
156 #[inline(always)]
158 pub fn assert_owner(&self, expected: &Address) -> Result<(), ProgramError> {
159 crate::token::check_token_owner(self.data, expected)
160 }
161
162 #[inline(always)]
164 pub fn assert_mint(&self, expected: &Address) -> Result<(), ProgramError> {
165 crate::token::check_token_mint(self.data, expected)
166 }
167}
168
169#[derive(Debug, Clone, Copy)]
177pub struct InterfaceMint<'a> {
178 data: &'a [u8],
179 pub kind: TokenProgramKind,
181}
182
183impl<'a> InterfaceMint<'a> {
184 pub fn from_data(data: &'a [u8], kind: TokenProgramKind) -> Result<Self, ProgramError> {
187 if data.len() < crate::mint::MINT_LEN {
188 return Err(ProgramError::InvalidAccountData);
189 }
190 Ok(Self { data, kind })
191 }
192
193 #[inline(always)]
195 pub fn data(&self) -> &'a [u8] {
196 self.data
197 }
198
199 #[inline(always)]
201 pub fn supply(&self) -> Result<u64, ProgramError> {
202 crate::mint::mint_supply(self.data)
203 }
204
205 #[inline(always)]
207 pub fn decimals(&self) -> Result<u8, ProgramError> {
208 crate::mint::mint_decimals(self.data)
209 }
210
211 #[inline(always)]
213 pub fn authority(&self) -> Result<Option<&'a Address>, ProgramError> {
214 crate::mint::mint_authority(self.data)
215 }
216
217 #[inline(always)]
219 pub fn freeze_authority(&self) -> Result<Option<&'a Address>, ProgramError> {
220 crate::mint::mint_freeze_authority(self.data)
221 }
222
223 #[inline(always)]
225 pub fn assert_initialized(&self) -> Result<(), ProgramError> {
226 crate::mint::check_mint_initialized(self.data)
227 }
228}
229
230#[inline]
241pub fn interface_transfer_checked<'a>(
242 source: &'a AccountView,
243 mint: &'a AccountView,
244 destination: &'a AccountView,
245 authority: &'a AccountView,
246 amount: u64,
247 decimals: u8,
248) -> ProgramResult {
249 interface_transfer_checked_signed(source, mint, destination, authority, amount, decimals, &[])
250}
251
252#[inline]
254pub fn interface_transfer_checked_signed<'a>(
255 source: &'a AccountView,
256 mint: &'a AccountView,
257 destination: &'a AccountView,
258 authority: &'a AccountView,
259 amount: u64,
260 decimals: u8,
261 signers: &[Signer],
262) -> ProgramResult {
263 let kind = TokenProgramKind::for_account(source)?;
264
265 let mut data = [0u8; 10];
266 data[0] = 12; data[1..9].copy_from_slice(&amount.to_le_bytes());
268 data[9] = decimals;
269
270 let accounts = [
271 InstructionAccount::writable(source.address()),
272 InstructionAccount::readonly(mint.address()),
273 InstructionAccount::writable(destination.address()),
274 InstructionAccount::readonly_signer(authority.address()),
275 ];
276 let views = [source, mint, destination, authority];
277 let instruction = InstructionView {
278 program_id: kind.program_id(),
279 data: &data,
280 accounts: &accounts,
281 };
282
283 hopper_runtime::cpi::invoke_signed(&instruction, &views, signers)
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn token_program_kind_from_owner_matches_known_programs() {
292 assert_eq!(
293 TokenProgramKind::from_owner(&TOKEN_PROGRAM_ID).unwrap(),
294 TokenProgramKind::Spl,
295 );
296 assert_eq!(
297 TokenProgramKind::from_owner(&TOKEN_2022_PROGRAM_ID).unwrap(),
298 TokenProgramKind::Token2022,
299 );
300 }
301
302 #[test]
303 fn token_program_kind_from_owner_rejects_other_programs() {
304 let other = Address::new_from_array([7u8; 32]);
305 assert!(matches!(
306 TokenProgramKind::from_owner(&other),
307 Err(ProgramError::IncorrectProgramId),
308 ));
309 }
310
311 #[test]
312 fn token_program_kind_program_id_is_stable() {
313 assert_eq!(TokenProgramKind::Spl.program_id(), &TOKEN_PROGRAM_ID,);
314 assert_eq!(
315 TokenProgramKind::Token2022.program_id(),
316 &TOKEN_2022_PROGRAM_ID,
317 );
318 }
319}