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