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 fee_payer: None,
139 }
140 .instruction(),
141
142 TransferType::LightToSpl => {
143 let spl = self.spl_interface.ok_or_else(|| {
144 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
145 })?;
146 TransferToSpl {
147 source: self.source,
148 destination_spl_token_account: self.destination,
149 amount: self.amount,
150 authority: self.authority,
151 mint: spl.mint,
152 payer: self.payer,
153 spl_interface_pda: spl.spl_interface_pda,
154 spl_interface_pda_bump: spl.spl_interface_pda_bump,
155 decimals: self.decimals,
156 spl_token_program: spl.spl_token_program,
157 }
158 .instruction()
159 }
160
161 TransferType::SplToLight => {
162 let spl = self.spl_interface.ok_or_else(|| {
163 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
164 })?;
165 TransferFromSpl {
166 source_spl_token_account: self.source,
167 destination: self.destination,
168 amount: self.amount,
169 authority: self.authority,
170 mint: spl.mint,
171 payer: self.payer,
172 spl_interface_pda: spl.spl_interface_pda,
173 spl_interface_pda_bump: spl.spl_interface_pda_bump,
174 decimals: self.decimals,
175 spl_token_program: spl.spl_token_program,
176 }
177 .instruction()
178 }
179
180 TransferType::SplToSpl => {
181 let spl = self.spl_interface.ok_or_else(|| {
182 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
183 })?;
184
185 let mut data = vec![12u8];
188 data.extend_from_slice(&self.amount.to_le_bytes());
189 data.push(self.decimals);
190
191 Ok(Instruction {
192 program_id: self.source_owner, accounts: vec![
194 AccountMeta::new(self.source, false),
195 AccountMeta::new_readonly(spl.mint, false),
196 AccountMeta::new(self.destination, false),
197 AccountMeta::new_readonly(self.authority, true),
198 ],
199 data,
200 })
201 }
202 }
203 }
204}
205
206impl<'info> From<&TransferInterfaceCpi<'info>> for TransferInterface {
207 fn from(cpi: &TransferInterfaceCpi<'info>) -> Self {
208 Self {
209 source: *cpi.source_account.key,
210 destination: *cpi.destination_account.key,
211 amount: cpi.amount,
212 decimals: cpi.decimals,
213 authority: *cpi.authority.key,
214 payer: *cpi.payer.key,
215 spl_interface: cpi.spl_interface.as_ref().map(SplInterface::from),
216 max_top_up: None,
217 source_owner: *cpi.source_account.owner,
218 destination_owner: *cpi.destination_account.owner,
219 }
220 }
221}
222
223pub struct TransferInterfaceCpi<'info> {
247 pub amount: u64,
248 pub decimals: u8,
249 pub source_account: AccountInfo<'info>,
250 pub destination_account: AccountInfo<'info>,
251 pub authority: AccountInfo<'info>,
252 pub payer: AccountInfo<'info>,
253 pub compressed_token_program_authority: AccountInfo<'info>,
254 pub spl_interface: Option<SplInterfaceCpi<'info>>,
255 pub system_program: AccountInfo<'info>,
257}
258
259impl<'info> TransferInterfaceCpi<'info> {
260 #[allow(clippy::too_many_arguments)]
270 pub fn new(
271 amount: u64,
272 decimals: u8,
273 source_account: AccountInfo<'info>,
274 destination_account: AccountInfo<'info>,
275 authority: AccountInfo<'info>,
276 payer: AccountInfo<'info>,
277 compressed_token_program_authority: AccountInfo<'info>,
278 system_program: AccountInfo<'info>,
279 ) -> Self {
280 Self {
281 source_account,
282 destination_account,
283 authority,
284 amount,
285 decimals,
286 payer,
287 compressed_token_program_authority,
288 spl_interface: None,
289 system_program,
290 }
291 }
292
293 pub fn with_spl_interface(
299 mut self,
300 mint: Option<AccountInfo<'info>>,
301 spl_token_program: Option<AccountInfo<'info>>,
302 spl_interface_pda: Option<AccountInfo<'info>>,
303 spl_interface_pda_bump: Option<u8>,
304 ) -> Result<Self, ProgramError> {
305 let mint =
306 mint.ok_or_else(|| ProgramError::Custom(TokenSdkError::MissingMintAccount.into()))?;
307
308 let spl_token_program = spl_token_program
309 .ok_or_else(|| ProgramError::Custom(TokenSdkError::MissingSplTokenProgram.into()))?;
310
311 let spl_interface_pda = spl_interface_pda
312 .ok_or_else(|| ProgramError::Custom(TokenSdkError::MissingSplInterfacePda.into()))?;
313
314 let spl_interface_pda_bump = spl_interface_pda_bump.ok_or_else(|| {
315 ProgramError::Custom(TokenSdkError::MissingSplInterfacePdaBump.into())
316 })?;
317
318 self.spl_interface = Some(SplInterfaceCpi {
319 mint,
320 spl_token_program,
321 spl_interface_pda,
322 spl_interface_pda_bump,
323 });
324 Ok(self)
325 }
326
327 pub fn instruction(&self) -> Result<Instruction, ProgramError> {
329 TransferInterface::from(self).instruction()
330 }
331
332 pub fn invoke(self) -> Result<(), ProgramError> {
336 use solana_cpi::invoke;
337
338 let transfer_type =
339 determine_transfer_type(self.source_account.owner, self.destination_account.owner)?;
340 let instruction = self.instruction()?;
341
342 match transfer_type {
343 TransferType::LightToLight => {
344 let account_infos = [
345 self.source_account,
346 self.destination_account,
347 self.authority,
348 ];
349 invoke(&instruction, &account_infos)
350 }
351
352 TransferType::LightToSpl => {
353 let config = self.spl_interface.ok_or_else(|| {
354 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
355 })?;
356 let account_infos = [
357 self.compressed_token_program_authority,
358 self.payer,
359 config.mint,
360 self.source_account,
361 self.destination_account,
362 self.authority,
363 config.spl_interface_pda,
364 config.spl_token_program,
365 ];
366 invoke(&instruction, &account_infos)
367 }
368
369 TransferType::SplToLight => {
370 let config = self.spl_interface.ok_or_else(|| {
371 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
372 })?;
373 let account_infos = [
374 self.compressed_token_program_authority,
375 self.payer,
376 config.mint,
377 self.destination_account,
378 self.authority,
379 self.source_account,
380 config.spl_interface_pda,
381 config.spl_token_program,
382 self.system_program,
383 ];
384 invoke(&instruction, &account_infos)
385 }
386
387 TransferType::SplToSpl => {
388 let config = self.spl_interface.ok_or_else(|| {
389 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
390 })?;
391 let account_infos = [
392 self.source_account,
393 config.mint,
394 self.destination_account,
395 self.authority,
396 ];
397 invoke(&instruction, &account_infos)
398 }
399 }
400 }
401
402 pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
406 use solana_cpi::invoke_signed;
407
408 let transfer_type =
409 determine_transfer_type(self.source_account.owner, self.destination_account.owner)?;
410 let instruction = self.instruction()?;
411
412 match transfer_type {
413 TransferType::LightToLight => {
414 let account_infos = [
415 self.source_account,
416 self.destination_account,
417 self.authority,
418 ];
419 invoke_signed(&instruction, &account_infos, signer_seeds)
420 }
421
422 TransferType::LightToSpl => {
423 let config = self.spl_interface.ok_or_else(|| {
424 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
425 })?;
426 let account_infos = [
427 self.compressed_token_program_authority,
428 self.payer,
429 config.mint,
430 self.source_account,
431 self.destination_account,
432 self.authority,
433 config.spl_interface_pda,
434 config.spl_token_program,
435 ];
436 invoke_signed(&instruction, &account_infos, signer_seeds)
437 }
438
439 TransferType::SplToLight => {
440 let config = self.spl_interface.ok_or_else(|| {
441 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
442 })?;
443 let account_infos = [
444 self.compressed_token_program_authority,
445 self.payer,
446 config.mint,
447 self.destination_account,
448 self.authority,
449 self.source_account,
450 config.spl_interface_pda,
451 config.spl_token_program,
452 self.system_program,
453 ];
454 invoke_signed(&instruction, &account_infos, signer_seeds)
455 }
456
457 TransferType::SplToSpl => {
458 let config = self.spl_interface.ok_or_else(|| {
459 ProgramError::Custom(TokenSdkError::SplInterfaceRequired.into())
460 })?;
461 let account_infos = [
462 self.source_account,
463 config.mint,
464 self.destination_account,
465 self.authority,
466 ];
467 invoke_signed(&instruction, &account_infos, signer_seeds)
468 }
469 }
470 }
471}