1use light_compressed_token_sdk::utils::is_light_token_owner;
2use solana_account_info::AccountInfo;
3use solana_instruction::{AccountMeta, Instruction};
4use solana_program_error::ProgramError;
5use solana_pubkey::Pubkey;
6
7use super::{
8 transfer_checked::TransferChecked, transfer_from_spl::TransferFromSpl,
9 transfer_to_spl::TransferToSpl,
10};
11use crate::error::LightTokenError;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15enum TransferType {
16 LightToLight,
18 LightToSpl,
20 SplToLight,
22 SplToSpl,
24}
25
26fn determine_transfer_type(
32 source_owner: &Pubkey,
33 destination_owner: &Pubkey,
34) -> Result<TransferType, ProgramError> {
35 let source_is_light = is_light_token_owner(source_owner)
36 .map_err(|_| ProgramError::Custom(LightTokenError::CannotDetermineAccountType.into()))?;
37 let dest_is_light = is_light_token_owner(destination_owner)
38 .map_err(|_| ProgramError::Custom(LightTokenError::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 LightTokenError::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 {
114 pub source: Pubkey,
115 pub destination: Pubkey,
116 pub amount: u64,
117 pub decimals: u8,
118 pub authority: Pubkey,
119 pub payer: Pubkey,
120 pub mint: Pubkey,
121 pub spl_interface: Option<SplInterface>,
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 => TransferChecked {
133 source: self.source,
134 mint: self.mint,
135 destination: self.destination,
136 amount: self.amount,
137 decimals: self.decimals,
138 authority: self.authority,
139 fee_payer: self.payer,
140 }
141 .instruction(),
142
143 TransferType::LightToSpl => {
144 let spl = self.spl_interface.ok_or_else(|| {
145 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
146 })?;
147 TransferToSpl {
148 source: self.source,
149 destination_spl_token_account: self.destination,
150 amount: self.amount,
151 authority: self.authority,
152 mint: spl.mint,
153 payer: self.payer,
154 spl_interface_pda: spl.spl_interface_pda,
155 spl_interface_pda_bump: spl.spl_interface_pda_bump,
156 decimals: self.decimals,
157 spl_token_program: spl.spl_token_program,
158 }
159 .instruction()
160 }
161
162 TransferType::SplToLight => {
163 let spl = self.spl_interface.ok_or_else(|| {
164 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
165 })?;
166 TransferFromSpl {
167 source_spl_token_account: self.source,
168 destination: self.destination,
169 amount: self.amount,
170 authority: self.authority,
171 mint: spl.mint,
172 payer: self.payer,
173 spl_interface_pda: spl.spl_interface_pda,
174 spl_interface_pda_bump: spl.spl_interface_pda_bump,
175 decimals: self.decimals,
176 spl_token_program: spl.spl_token_program,
177 }
178 .instruction()
179 }
180
181 TransferType::SplToSpl => {
182 let spl = self.spl_interface.ok_or_else(|| {
183 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
184 })?;
185
186 let mut data = vec![12u8];
189 data.extend_from_slice(&self.amount.to_le_bytes());
190 data.push(self.decimals);
191
192 Ok(Instruction {
193 program_id: self.source_owner, accounts: vec![
195 AccountMeta::new(self.source, false),
196 AccountMeta::new_readonly(spl.mint, false),
197 AccountMeta::new(self.destination, false),
198 AccountMeta::new_readonly(self.authority, true),
199 ],
200 data,
201 })
202 }
203 }
204 }
205}
206
207impl<'info> From<&TransferInterfaceCpi<'info>> for TransferInterface {
208 fn from(cpi: &TransferInterfaceCpi<'info>) -> Self {
209 Self {
210 source: *cpi.source_account.key,
211 destination: *cpi.destination_account.key,
212 amount: cpi.amount,
213 decimals: cpi.decimals,
214 authority: *cpi.authority.key,
215 payer: *cpi.payer.key,
216 mint: *cpi.mint.key,
217 spl_interface: cpi.spl_interface.as_ref().map(SplInterface::from),
218 source_owner: *cpi.source_account.owner,
219 destination_owner: *cpi.destination_account.owner,
220 }
221 }
222}
223
224pub struct TransferInterfaceCpi<'info> {
250 pub amount: u64,
251 pub decimals: u8,
252 pub source_account: AccountInfo<'info>,
253 pub destination_account: AccountInfo<'info>,
254 pub authority: AccountInfo<'info>,
255 pub payer: AccountInfo<'info>,
256 pub compressed_token_program_authority: AccountInfo<'info>,
257 pub mint: AccountInfo<'info>,
258 pub spl_interface: Option<SplInterfaceCpi<'info>>,
259 pub system_program: AccountInfo<'info>,
261}
262
263impl<'info> TransferInterfaceCpi<'info> {
264 #[allow(clippy::too_many_arguments)]
275 pub fn new(
276 amount: u64,
277 decimals: u8,
278 source_account: AccountInfo<'info>,
279 destination_account: AccountInfo<'info>,
280 authority: AccountInfo<'info>,
281 payer: AccountInfo<'info>,
282 compressed_token_program_authority: AccountInfo<'info>,
283 mint: AccountInfo<'info>,
284 system_program: AccountInfo<'info>,
285 ) -> Self {
286 Self {
287 source_account,
288 destination_account,
289 authority,
290 amount,
291 decimals,
292 payer,
293 compressed_token_program_authority,
294 mint,
295 spl_interface: None,
296 system_program,
297 }
298 }
299
300 pub fn with_spl_interface(
306 mut self,
307 mint: Option<AccountInfo<'info>>,
308 spl_token_program: Option<AccountInfo<'info>>,
309 spl_interface_pda: Option<AccountInfo<'info>>,
310 spl_interface_pda_bump: Option<u8>,
311 ) -> Result<Self, ProgramError> {
312 let mint =
313 mint.ok_or_else(|| ProgramError::Custom(LightTokenError::MissingMintAccount.into()))?;
314
315 let spl_token_program = spl_token_program
316 .ok_or_else(|| ProgramError::Custom(LightTokenError::MissingSplTokenProgram.into()))?;
317
318 let spl_interface_pda = spl_interface_pda
319 .ok_or_else(|| ProgramError::Custom(LightTokenError::MissingSplInterfacePda.into()))?;
320
321 let spl_interface_pda_bump = spl_interface_pda_bump.ok_or_else(|| {
322 ProgramError::Custom(LightTokenError::MissingSplInterfacePdaBump.into())
323 })?;
324
325 self.spl_interface = Some(SplInterfaceCpi {
326 mint,
327 spl_token_program,
328 spl_interface_pda,
329 spl_interface_pda_bump,
330 });
331 Ok(self)
332 }
333
334 pub fn instruction(&self) -> Result<Instruction, ProgramError> {
336 TransferInterface::from(self).instruction()
337 }
338
339 pub fn invoke(self) -> Result<(), ProgramError> {
343 use solana_cpi::invoke;
344
345 let transfer_type =
346 determine_transfer_type(self.source_account.owner, self.destination_account.owner)?;
347 let instruction = self.instruction()?;
348
349 match transfer_type {
350 TransferType::LightToLight => {
351 let account_infos = [
352 self.source_account,
353 self.mint,
354 self.destination_account,
355 self.authority,
356 self.system_program,
357 self.payer,
358 ];
359 invoke(&instruction, &account_infos)
360 }
361
362 TransferType::LightToSpl => {
363 let config = self.spl_interface.ok_or_else(|| {
364 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
365 })?;
366 let account_infos = [
367 self.compressed_token_program_authority,
368 self.payer,
369 config.mint,
370 self.source_account,
371 self.destination_account,
372 self.authority,
373 config.spl_interface_pda,
374 config.spl_token_program,
375 ];
376 invoke(&instruction, &account_infos)
377 }
378
379 TransferType::SplToLight => {
380 let config = self.spl_interface.ok_or_else(|| {
381 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
382 })?;
383 let account_infos = [
384 self.compressed_token_program_authority,
385 self.payer,
386 config.mint,
387 self.destination_account,
388 self.authority,
389 self.source_account,
390 config.spl_interface_pda,
391 config.spl_token_program,
392 self.system_program,
393 ];
394 invoke(&instruction, &account_infos)
395 }
396
397 TransferType::SplToSpl => {
398 let config = self.spl_interface.ok_or_else(|| {
399 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
400 })?;
401 let account_infos = [
402 self.source_account,
403 config.mint,
404 self.destination_account,
405 self.authority,
406 ];
407 invoke(&instruction, &account_infos)
408 }
409 }
410 }
411
412 pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
416 use solana_cpi::invoke_signed;
417
418 let transfer_type =
419 determine_transfer_type(self.source_account.owner, self.destination_account.owner)?;
420 let instruction = self.instruction()?;
421
422 match transfer_type {
423 TransferType::LightToLight => {
424 let account_infos = [
425 self.source_account,
426 self.mint,
427 self.destination_account,
428 self.authority,
429 self.system_program,
430 self.payer,
431 ];
432 invoke_signed(&instruction, &account_infos, signer_seeds)
433 }
434
435 TransferType::LightToSpl => {
436 let config = self.spl_interface.ok_or_else(|| {
437 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
438 })?;
439 let account_infos = [
440 self.compressed_token_program_authority,
441 self.payer,
442 config.mint,
443 self.source_account,
444 self.destination_account,
445 self.authority,
446 config.spl_interface_pda,
447 config.spl_token_program,
448 ];
449 invoke_signed(&instruction, &account_infos, signer_seeds)
450 }
451
452 TransferType::SplToLight => {
453 let config = self.spl_interface.ok_or_else(|| {
454 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
455 })?;
456 let account_infos = [
457 self.compressed_token_program_authority,
458 self.payer,
459 config.mint,
460 self.destination_account,
461 self.authority,
462 self.source_account,
463 config.spl_interface_pda,
464 config.spl_token_program,
465 self.system_program,
466 ];
467 invoke_signed(&instruction, &account_infos, signer_seeds)
468 }
469
470 TransferType::SplToSpl => {
471 let config = self.spl_interface.ok_or_else(|| {
472 ProgramError::Custom(LightTokenError::SplInterfaceRequired.into())
473 })?;
474 let account_infos = [
475 self.source_account,
476 config.mint,
477 self.destination_account,
478 self.authority,
479 ];
480 invoke_signed(&instruction, &account_infos, signer_seeds)
481 }
482 }
483 }
484}