1use solana_account_info::AccountInfo;
2use solana_instruction::{AccountMeta, Instruction};
3use solana_program_error::ProgramError;
4use solana_pubkey::Pubkey;
5
6use super::{
7 transfer::Transfer, transfer_from_spl::TransferFromSpl, transfer_to_spl::TransferToSpl,
8};
9use crate::error::TokenSdkError;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13enum TransferType {
14 LightToLight,
16 LightToSpl,
18 SplToLight,
20 SplToSpl,
22}
23
24fn determine_transfer_type(
30 source_owner: &Pubkey,
31 destination_owner: &Pubkey,
32) -> Result<TransferType, ProgramError> {
33 use crate::utils::is_light_token_owner;
34
35 let source_is_light = is_light_token_owner(source_owner)
36 .map_err(|_| ProgramError::Custom(TokenSdkError::CannotDetermineAccountType.into()))?;
37 let dest_is_light = is_light_token_owner(destination_owner)
38 .map_err(|_| ProgramError::Custom(TokenSdkError::CannotDetermineAccountType.into()))?;
39
40 match (source_is_light, dest_is_light) {
41 (true, true) => Ok(TransferType::LightToLight),
42 (true, false) => Ok(TransferType::LightToSpl),
43 (false, true) => Ok(TransferType::SplToLight),
44 (false, false) => {
45 if source_owner == destination_owner {
47 Ok(TransferType::SplToSpl)
48 } else {
49 Err(ProgramError::Custom(
50 TokenSdkError::SplTokenProgramMismatch.into(),
51 ))
52 }
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy)]
61pub struct SplInterface {
62 pub mint: Pubkey,
63 pub spl_token_program: Pubkey,
64 pub spl_interface_pda: Pubkey,
65 pub spl_interface_pda_bump: u8,
66}
67
68impl<'info> From<&SplInterfaceCpi<'info>> for SplInterface {
69 fn from(spl: &SplInterfaceCpi<'info>) -> Self {
70 Self {
71 mint: *spl.mint.key,
72 spl_token_program: *spl.spl_token_program.key,
73 spl_interface_pda: *spl.spl_interface_pda.key,
74 spl_interface_pda_bump: spl.spl_interface_pda_bump,
75 }
76 }
77}
78
79pub struct SplInterfaceCpi<'info> {
83 pub mint: AccountInfo<'info>,
84 pub spl_token_program: AccountInfo<'info>,
85 pub spl_interface_pda: AccountInfo<'info>,
86 pub spl_interface_pda_bump: u8,
87}
88
89pub struct TransferInterface {
113 pub source: Pubkey,
114 pub destination: Pubkey,
115 pub amount: u64,
116 pub decimals: u8,
117 pub authority: Pubkey,
118 pub payer: Pubkey,
119 pub spl_interface: Option<SplInterface>,
120 pub max_top_up: Option<u16>,
122 pub source_owner: Pubkey,
124 pub destination_owner: Pubkey,
126}
127
128impl TransferInterface {
129 pub fn instruction(self) -> Result<Instruction, ProgramError> {
131 match determine_transfer_type(&self.source_owner, &self.destination_owner)? {
132 TransferType::LightToLight => Transfer {
133 source: self.source,
134 destination: self.destination,
135 amount: self.amount,
136 authority: self.authority,
137 max_top_up: self.max_top_up,
138 }
139 .instruction(),
140
141 TransferType::LightToSpl => {
142 let spl = self.spl_interface.ok_or_else(|| {
143 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
144 })?;
145 TransferToSpl {
146 source: self.source,
147 destination_spl_token_account: self.destination,
148 amount: self.amount,
149 authority: self.authority,
150 mint: spl.mint,
151 payer: self.payer,
152 spl_interface_pda: spl.spl_interface_pda,
153 spl_interface_pda_bump: spl.spl_interface_pda_bump,
154 decimals: self.decimals,
155 spl_token_program: spl.spl_token_program,
156 }
157 .instruction()
158 }
159
160 TransferType::SplToLight => {
161 let spl = self.spl_interface.ok_or_else(|| {
162 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
163 })?;
164 TransferFromSpl {
165 source_spl_token_account: self.source,
166 destination: self.destination,
167 amount: self.amount,
168 authority: self.authority,
169 mint: spl.mint,
170 payer: self.payer,
171 spl_interface_pda: spl.spl_interface_pda,
172 spl_interface_pda_bump: spl.spl_interface_pda_bump,
173 decimals: self.decimals,
174 spl_token_program: spl.spl_token_program,
175 }
176 .instruction()
177 }
178
179 TransferType::SplToSpl => {
180 let spl = self.spl_interface.ok_or_else(|| {
181 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
182 })?;
183
184 let mut data = vec![12u8];
187 data.extend_from_slice(&self.amount.to_le_bytes());
188 data.push(self.decimals);
189
190 Ok(Instruction {
191 program_id: self.source_owner, accounts: vec![
193 AccountMeta::new(self.source, false),
194 AccountMeta::new_readonly(spl.mint, false),
195 AccountMeta::new(self.destination, false),
196 AccountMeta::new_readonly(self.authority, true),
197 ],
198 data,
199 })
200 }
201 }
202 }
203}
204
205impl<'info> From<&TransferInterfaceCpi<'info>> for TransferInterface {
206 fn from(cpi: &TransferInterfaceCpi<'info>) -> Self {
207 Self {
208 source: *cpi.source_account.key,
209 destination: *cpi.destination_account.key,
210 amount: cpi.amount,
211 decimals: cpi.decimals,
212 authority: *cpi.authority.key,
213 payer: *cpi.payer.key,
214 spl_interface: cpi.spl_interface.as_ref().map(SplInterface::from),
215 max_top_up: None,
216 source_owner: *cpi.source_account.owner,
217 destination_owner: *cpi.destination_account.owner,
218 }
219 }
220}
221
222pub struct TransferInterfaceCpi<'info> {
246 pub amount: u64,
247 pub decimals: u8,
248 pub source_account: AccountInfo<'info>,
249 pub destination_account: AccountInfo<'info>,
250 pub authority: AccountInfo<'info>,
251 pub payer: AccountInfo<'info>,
252 pub compressed_token_program_authority: AccountInfo<'info>,
253 pub spl_interface: Option<SplInterfaceCpi<'info>>,
254 pub system_program: AccountInfo<'info>,
256}
257
258impl<'info> TransferInterfaceCpi<'info> {
259 #[allow(clippy::too_many_arguments)]
269 pub fn new(
270 amount: u64,
271 decimals: u8,
272 source_account: AccountInfo<'info>,
273 destination_account: AccountInfo<'info>,
274 authority: AccountInfo<'info>,
275 payer: AccountInfo<'info>,
276 compressed_token_program_authority: AccountInfo<'info>,
277 system_program: AccountInfo<'info>,
278 ) -> Self {
279 Self {
280 source_account,
281 destination_account,
282 authority,
283 amount,
284 decimals,
285 payer,
286 compressed_token_program_authority,
287 spl_interface: None,
288 system_program,
289 }
290 }
291
292 pub fn with_spl_interface(
298 mut self,
299 mint: Option<AccountInfo<'info>>,
300 spl_token_program: Option<AccountInfo<'info>>,
301 spl_interface_pda: Option<AccountInfo<'info>>,
302 spl_interface_pda_bump: Option<u8>,
303 ) -> Result<Self, ProgramError> {
304 let mint =
305 mint.ok_or_else(|| ProgramError::Custom(TokenSdkError::MissingMintAccount.into()))?;
306
307 let spl_token_program = spl_token_program
308 .ok_or_else(|| ProgramError::Custom(TokenSdkError::MissingSplTokenProgram.into()))?;
309
310 let spl_interface_pda = spl_interface_pda
311 .ok_or_else(|| ProgramError::Custom(TokenSdkError::MissingSplInterfacePda.into()))?;
312
313 let spl_interface_pda_bump = spl_interface_pda_bump.ok_or_else(|| {
314 ProgramError::Custom(TokenSdkError::MissingSplInterfacePdaBump.into())
315 })?;
316
317 self.spl_interface = Some(SplInterfaceCpi {
318 mint,
319 spl_token_program,
320 spl_interface_pda,
321 spl_interface_pda_bump,
322 });
323 Ok(self)
324 }
325
326 pub fn instruction(&self) -> Result<Instruction, ProgramError> {
328 TransferInterface::from(self).instruction()
329 }
330
331 pub fn invoke(self) -> Result<(), ProgramError> {
335 use solana_cpi::invoke;
336
337 let transfer_type =
338 determine_transfer_type(self.source_account.owner, self.destination_account.owner)?;
339 let instruction = self.instruction()?;
340
341 match transfer_type {
342 TransferType::LightToLight => {
343 let account_infos = [
344 self.source_account,
345 self.destination_account,
346 self.authority,
347 ];
348 invoke(&instruction, &account_infos)
349 }
350
351 TransferType::LightToSpl => {
352 let config = self.spl_interface.ok_or_else(|| {
353 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
354 })?;
355 let account_infos = [
356 self.compressed_token_program_authority,
357 self.payer,
358 config.mint,
359 self.source_account,
360 self.destination_account,
361 self.authority,
362 config.spl_interface_pda,
363 config.spl_token_program,
364 ];
365 invoke(&instruction, &account_infos)
366 }
367
368 TransferType::SplToLight => {
369 let config = self.spl_interface.ok_or_else(|| {
370 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
371 })?;
372 let account_infos = [
373 self.compressed_token_program_authority,
374 self.payer,
375 config.mint,
376 self.destination_account,
377 self.authority,
378 self.source_account,
379 config.spl_interface_pda,
380 config.spl_token_program,
381 self.system_program,
382 ];
383 invoke(&instruction, &account_infos)
384 }
385
386 TransferType::SplToSpl => {
387 let config = self.spl_interface.ok_or_else(|| {
388 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
389 })?;
390 let account_infos = [
391 self.source_account,
392 config.mint,
393 self.destination_account,
394 self.authority,
395 ];
396 invoke(&instruction, &account_infos)
397 }
398 }
399 }
400
401 pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
405 use solana_cpi::invoke_signed;
406
407 let transfer_type =
408 determine_transfer_type(self.source_account.owner, self.destination_account.owner)?;
409 let instruction = self.instruction()?;
410
411 match transfer_type {
412 TransferType::LightToLight => {
413 let account_infos = [
414 self.source_account,
415 self.destination_account,
416 self.authority,
417 ];
418 invoke_signed(&instruction, &account_infos, signer_seeds)
419 }
420
421 TransferType::LightToSpl => {
422 let config = self.spl_interface.ok_or_else(|| {
423 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
424 })?;
425 let account_infos = [
426 self.compressed_token_program_authority,
427 self.payer,
428 config.mint,
429 self.source_account,
430 self.destination_account,
431 self.authority,
432 config.spl_interface_pda,
433 config.spl_token_program,
434 ];
435 invoke_signed(&instruction, &account_infos, signer_seeds)
436 }
437
438 TransferType::SplToLight => {
439 let config = self.spl_interface.ok_or_else(|| {
440 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
441 })?;
442 let account_infos = [
443 self.compressed_token_program_authority,
444 self.payer,
445 config.mint,
446 self.destination_account,
447 self.authority,
448 self.source_account,
449 config.spl_interface_pda,
450 config.spl_token_program,
451 self.system_program,
452 ];
453 invoke_signed(&instruction, &account_infos, signer_seeds)
454 }
455
456 TransferType::SplToSpl => {
457 let config = self.spl_interface.ok_or_else(|| {
458 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
459 })?;
460 let account_infos = [
461 self.source_account,
462 config.mint,
463 self.destination_account,
464 self.authority,
465 ];
466 invoke_signed(&instruction, &account_infos, signer_seeds)
467 }
468 }
469 }
470}