light_token/instruction/
transfer_from_spl.rs

1use light_compressed_account::instruction_data::compressed_proof::ValidityProof;
2use light_token_interface::instructions::transfer2::{Compression, MultiTokenTransferOutputData};
3use solana_account_info::AccountInfo;
4use solana_cpi::{invoke, invoke_signed};
5use solana_instruction::{AccountMeta, Instruction};
6use solana_program_error::ProgramError;
7use solana_pubkey::Pubkey;
8
9use crate::compressed_token::{
10    transfer2::{
11        create_transfer2_instruction, Transfer2AccountsMetaConfig, Transfer2Config, Transfer2Inputs,
12    },
13    CTokenAccount2,
14};
15
16/// # Create a transfer SPL to cToken instruction
17/// ```rust
18/// # use solana_pubkey::Pubkey;
19/// # use light_token::instruction::TransferFromSpl;
20/// # let source_spl_token_account = Pubkey::new_unique();
21/// # let destination = Pubkey::new_unique();
22/// # let authority = Pubkey::new_unique();
23/// # let mint = Pubkey::new_unique();
24/// # let payer = Pubkey::new_unique();
25/// # let spl_interface_pda = Pubkey::new_unique();
26/// # let spl_token_program = Pubkey::new_unique();
27/// let instruction = TransferFromSpl {
28///     amount: 100,
29///     spl_interface_pda_bump: 255,
30///     decimals: 9,
31///     source_spl_token_account,
32///     destination,
33///     authority,
34///     mint,
35///     payer,
36///     spl_interface_pda,
37///     spl_token_program,
38/// }.instruction()?;
39/// # Ok::<(), solana_program_error::ProgramError>(())
40/// ```
41pub struct TransferFromSpl {
42    pub amount: u64,
43    pub spl_interface_pda_bump: u8,
44    pub decimals: u8,
45    pub source_spl_token_account: Pubkey,
46    /// Destination ctoken account (writable)
47    pub destination: Pubkey,
48    pub authority: Pubkey,
49    pub mint: Pubkey,
50    pub payer: Pubkey,
51    pub spl_interface_pda: Pubkey,
52    pub spl_token_program: Pubkey,
53}
54
55/// # Transfer SPL to ctoken via CPI:
56/// ```rust,no_run
57/// # use light_token::instruction::TransferFromSplCpi;
58/// # use solana_account_info::AccountInfo;
59/// # let source_spl_token_account: AccountInfo = todo!();
60/// # let destination: AccountInfo = todo!();
61/// # let authority: AccountInfo = todo!();
62/// # let mint: AccountInfo = todo!();
63/// # let payer: AccountInfo = todo!();
64/// # let spl_interface_pda: AccountInfo = todo!();
65/// # let spl_token_program: AccountInfo = todo!();
66/// # let compressed_token_program_authority: AccountInfo = todo!();
67/// # let system_program: AccountInfo = todo!();
68/// TransferFromSplCpi {
69///     amount: 100,
70///     spl_interface_pda_bump: 255,
71///     decimals: 9,
72///     source_spl_token_account,
73///     destination,
74///     authority,
75///     mint,
76///     payer,
77///     spl_interface_pda,
78///     spl_token_program,
79///     compressed_token_program_authority,
80///     system_program,
81/// }
82/// .invoke()?;
83/// # Ok::<(), solana_program_error::ProgramError>(())
84/// ```
85pub struct TransferFromSplCpi<'info> {
86    pub amount: u64,
87    pub spl_interface_pda_bump: u8,
88    pub decimals: u8,
89    pub source_spl_token_account: AccountInfo<'info>,
90    /// Destination ctoken account (writable)
91    pub destination: AccountInfo<'info>,
92    pub authority: AccountInfo<'info>,
93    pub mint: AccountInfo<'info>,
94    pub payer: AccountInfo<'info>,
95    pub spl_interface_pda: AccountInfo<'info>,
96    pub spl_token_program: AccountInfo<'info>,
97    pub compressed_token_program_authority: AccountInfo<'info>,
98    /// System program - required for compressible account lamport top-ups
99    pub system_program: AccountInfo<'info>,
100}
101
102impl<'info> TransferFromSplCpi<'info> {
103    pub fn instruction(&self) -> Result<Instruction, ProgramError> {
104        TransferFromSpl::from(self).instruction()
105    }
106
107    pub fn invoke(self) -> Result<(), ProgramError> {
108        let instruction = TransferFromSpl::from(&self).instruction()?;
109        // Account order must match instruction metas: cpi_authority_pda, fee_payer, packed_accounts...
110        let account_infos = [
111            self.compressed_token_program_authority, // CPI authority PDA (first)
112            self.payer,                              // Fee payer (second)
113            self.mint,                               // Index 0: Mint
114            self.destination,                        // Index 1: Destination ctoken account
115            self.authority,                          // Index 2: Authority (signer)
116            self.source_spl_token_account,           // Index 3: Source SPL token account
117            self.spl_interface_pda,                  // Index 4: SPL interface PDA
118            self.spl_token_program,                  // Index 5: SPL Token program
119            self.system_program,                     // Index 6: System program
120        ];
121        invoke(&instruction, &account_infos)
122    }
123
124    pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
125        let instruction = TransferFromSpl::from(&self).instruction()?;
126        // Account order must match instruction metas: cpi_authority_pda, fee_payer, packed_accounts...
127        let account_infos = [
128            self.compressed_token_program_authority, // CPI authority PDA (first)
129            self.payer,                              // Fee payer (second)
130            self.mint,                               // Index 0: Mint
131            self.destination,                        // Index 1: Destination ctoken account
132            self.authority,                          // Index 2: Authority (signer)
133            self.source_spl_token_account,           // Index 3: Source SPL token account
134            self.spl_interface_pda,                  // Index 4: SPL interface PDA
135            self.spl_token_program,                  // Index 5: SPL Token program
136            self.system_program,                     // Index 6: System program
137        ];
138        invoke_signed(&instruction, &account_infos, signer_seeds)
139    }
140}
141
142impl<'info> From<&TransferFromSplCpi<'info>> for TransferFromSpl {
143    fn from(account_infos: &TransferFromSplCpi<'info>) -> Self {
144        Self {
145            source_spl_token_account: *account_infos.source_spl_token_account.key,
146            destination: *account_infos.destination.key,
147            amount: account_infos.amount,
148            authority: *account_infos.authority.key,
149            mint: *account_infos.mint.key,
150            payer: *account_infos.payer.key,
151            spl_interface_pda: *account_infos.spl_interface_pda.key,
152            spl_interface_pda_bump: account_infos.spl_interface_pda_bump,
153            decimals: account_infos.decimals,
154            spl_token_program: *account_infos.spl_token_program.key,
155        }
156    }
157}
158
159impl TransferFromSpl {
160    pub fn instruction(self) -> Result<Instruction, ProgramError> {
161        let packed_accounts = vec![
162            // Mint (index 0)
163            AccountMeta::new_readonly(self.mint, false),
164            // Destination ctoken account (index 1) - writable
165            AccountMeta::new(self.destination, false),
166            // Authority for compression (index 2) - signer
167            AccountMeta::new_readonly(self.authority, true),
168            // Source SPL token account (index 3) - writable
169            AccountMeta::new(self.source_spl_token_account, false),
170            // SPL interface PDA (index 4) - writable
171            AccountMeta::new(self.spl_interface_pda, false),
172            // SPL Token program (index 5) - needed for CPI
173            AccountMeta::new_readonly(self.spl_token_program, false),
174            // System program (index 6) - needed for compressible account lamport top-ups
175            AccountMeta::new_readonly(Pubkey::default(), false),
176        ];
177
178        let wrap_from_spl = CTokenAccount2 {
179            inputs: vec![],
180            output: MultiTokenTransferOutputData::default(),
181            compression: Some(Compression::compress_spl(
182                self.amount,
183                0, // mint
184                3, // source or recipient
185                2, // authority
186                4, // pool_account_index:
187                0, // pool_index
188                self.spl_interface_pda_bump,
189                self.decimals,
190            )),
191            delegate_is_set: false,
192            method_used: true,
193        };
194
195        let unwrap_to_destination = CTokenAccount2 {
196            inputs: vec![],
197            output: MultiTokenTransferOutputData::default(),
198            compression: Some(Compression::decompress(self.amount, 0, 1)),
199            delegate_is_set: false,
200            method_used: true,
201        };
202
203        let inputs = Transfer2Inputs {
204            validity_proof: ValidityProof::new(None),
205            transfer_config: Transfer2Config::default().filter_zero_amount_outputs(),
206            meta_config: Transfer2AccountsMetaConfig::new_decompressed_accounts_only(
207                self.payer,
208                packed_accounts,
209            ),
210            in_lamports: None,
211            out_lamports: None,
212            token_accounts: vec![wrap_from_spl, unwrap_to_destination],
213            output_queue: 0, // Decompressed accounts only, no output queue needed
214            in_tlv: None,
215        };
216
217        create_transfer2_instruction(inputs).map_err(ProgramError::from)
218    }
219}