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::Transfer, transfer_from_spl::TransferFromSpl, transfer_to_spl::TransferToSpl,
9};
10use crate::error::LightTokenError;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14enum TransferType {
15 LightToLight,
17 LightToSpl,
19 SplToLight,
21 SplToSpl,
23}
24
25fn determine_transfer_type(
31 source_owner: &Pubkey,
32 destination_owner: &Pubkey,
33) -> Result<TransferType, ProgramError> {
34 let source_is_light = is_light_token_owner(source_owner)
35 .map_err(|_| ProgramError::Custom(LightTokenError::CannotDetermineAccountType.into()))?;
36 let dest_is_light = is_light_token_owner(destination_owner)
37 .map_err(|_| ProgramError::Custom(LightTokenError::CannotDetermineAccountType.into()))?;
38
39 match (source_is_light, dest_is_light) {
40 (true, true) => Ok(TransferType::LightToLight),
41 (true, false) => Ok(TransferType::LightToSpl),
42 (false, true) => Ok(TransferType::SplToLight),
43 (false, false) => {
44 if source_owner == destination_owner {
46 Ok(TransferType::SplToSpl)
47 } else {
48 Err(ProgramError::Custom(
49 LightTokenError::SplTokenProgramMismatch.into(),
50 ))
51 }
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy)]
60pub struct SplInterface {
61 pub mint: Pubkey,
62 pub spl_token_program: Pubkey,
63 pub spl_interface_pda: Pubkey,
64 pub spl_interface_pda_bump: u8,
65}
66
67impl<'info> From<&SplInterfaceCpi<'info>> for SplInterface {
68 fn from(spl: &SplInterfaceCpi<'info>) -> Self {
69 Self {
70 mint: *spl.mint.key,
71 spl_token_program: *spl.spl_token_program.key,
72 spl_interface_pda: *spl.spl_interface_pda.key,
73 spl_interface_pda_bump: spl.spl_interface_pda_bump,
74 }
75 }
76}
77
78pub struct SplInterfaceCpi<'info> {
82 pub mint: AccountInfo<'info>,
83 pub spl_token_program: AccountInfo<'info>,
84 pub spl_interface_pda: AccountInfo<'info>,
85 pub spl_interface_pda_bump: u8,
86}
87
88pub struct TransferInterface {
112 pub source: Pubkey,
113 pub destination: Pubkey,
114 pub amount: u64,
115 pub decimals: u8,
116 pub authority: Pubkey,
117 pub payer: Pubkey,
118 pub spl_interface: Option<SplInterface>,
119 pub max_top_up: Option<u16>,
121 pub source_owner: Pubkey,
123 pub destination_owner: Pubkey,
125}
126
127impl TransferInterface {
128 pub fn instruction(self) -> Result<Instruction, ProgramError> {
130 match determine_transfer_type(&self.source_owner, &self.destination_owner)? {
131 TransferType::LightToLight => Transfer {
132 source: self.source,
133 destination: self.destination,
134 amount: self.amount,
135 authority: self.authority,
136 max_top_up: self.max_top_up,
137 fee_payer: None,
138 }
139 .instruction(),
140
141 TransferType::LightToSpl => {
142 let spl = self.spl_interface.ok_or_else(|| {
143 ProgramError::Custom(LightTokenError::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(LightTokenError::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(LightTokenError::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(LightTokenError::MissingMintAccount.into()))?;
306
307 let spl_token_program = spl_token_program
308 .ok_or_else(|| ProgramError::Custom(LightTokenError::MissingSplTokenProgram.into()))?;
309
310 let spl_interface_pda = spl_interface_pda
311 .ok_or_else(|| ProgramError::Custom(LightTokenError::MissingSplInterfacePda.into()))?;
312
313 let spl_interface_pda_bump = spl_interface_pda_bump.ok_or_else(|| {
314 ProgramError::Custom(LightTokenError::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(LightTokenError::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(LightTokenError::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(LightTokenError::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(LightTokenError::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(LightTokenError::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(LightTokenError::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}