light_token/instruction/
transfer.rs

1use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID;
2use solana_account_info::AccountInfo;
3use solana_cpi::{invoke, invoke_signed};
4use solana_instruction::{AccountMeta, Instruction};
5use solana_program_error::ProgramError;
6use solana_pubkey::Pubkey;
7
8/// # Create a transfer ctoken instruction:
9/// ```rust
10/// # use solana_pubkey::Pubkey;
11/// # use light_token::instruction::Transfer;
12/// # let source = Pubkey::new_unique();
13/// # let destination = Pubkey::new_unique();
14/// # let authority = Pubkey::new_unique();
15/// let instruction = Transfer {
16///     source,
17///     destination,
18///     amount: 100,
19///     authority,
20///     max_top_up: None,
21/// }.instruction()?;
22/// # Ok::<(), solana_program_error::ProgramError>(())
23/// ```
24pub struct Transfer {
25    pub source: Pubkey,
26    pub destination: Pubkey,
27    pub amount: u64,
28    pub authority: Pubkey,
29    /// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
30    /// When set, includes max_top_up in instruction data and adds system program account for compressible top-up
31    pub max_top_up: Option<u16>,
32}
33
34/// # Transfer ctoken via CPI:
35/// ```rust,no_run
36/// # use light_token::instruction::TransferCpi;
37/// # use solana_account_info::AccountInfo;
38/// # let source: AccountInfo = todo!();
39/// # let destination: AccountInfo = todo!();
40/// # let authority: AccountInfo = todo!();
41/// TransferCpi {
42///     source,
43///     destination,
44///     amount: 100,
45///     authority,
46///     max_top_up: None,
47/// }
48/// .invoke()?;
49/// # Ok::<(), solana_program_error::ProgramError>(())
50/// ```
51pub struct TransferCpi<'info> {
52    pub source: AccountInfo<'info>,
53    pub destination: AccountInfo<'info>,
54    pub amount: u64,
55    pub authority: AccountInfo<'info>,
56    /// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
57    pub max_top_up: Option<u16>,
58}
59
60impl<'info> TransferCpi<'info> {
61    pub fn instruction(&self) -> Result<Instruction, ProgramError> {
62        Transfer::from(self).instruction()
63    }
64
65    pub fn invoke(self) -> Result<(), ProgramError> {
66        let instruction = Transfer::from(&self).instruction()?;
67        let account_infos = [self.source, self.destination, self.authority];
68        invoke(&instruction, &account_infos)
69    }
70
71    pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
72        let instruction = Transfer::from(&self).instruction()?;
73        let account_infos = [self.source, self.destination, self.authority];
74        invoke_signed(&instruction, &account_infos, signer_seeds)
75    }
76}
77
78impl<'info> From<&TransferCpi<'info>> for Transfer {
79    fn from(account_infos: &TransferCpi<'info>) -> Self {
80        Self {
81            source: *account_infos.source.key,
82            destination: *account_infos.destination.key,
83            amount: account_infos.amount,
84            authority: *account_infos.authority.key,
85            max_top_up: account_infos.max_top_up,
86        }
87    }
88}
89
90impl Transfer {
91    pub fn instruction(self) -> Result<Instruction, ProgramError> {
92        // Authority is writable only when max_top_up is set (for compressible top-up lamport transfer)
93        let authority_meta = if self.max_top_up.is_some() {
94            AccountMeta::new(self.authority, true)
95        } else {
96            AccountMeta::new_readonly(self.authority, true)
97        };
98
99        let mut accounts = vec![
100            AccountMeta::new(self.source, false),
101            AccountMeta::new(self.destination, false),
102            authority_meta,
103        ];
104
105        // Include system program for compressible top-up when max_top_up is set
106        if self.max_top_up.is_some() {
107            accounts.push(AccountMeta::new_readonly(
108                solana_pubkey::pubkey!("11111111111111111111111111111111"),
109                false,
110            ));
111        }
112
113        Ok(Instruction {
114            program_id: Pubkey::from(LIGHT_TOKEN_PROGRAM_ID),
115            accounts,
116            data: {
117                let mut data = vec![3u8];
118                data.extend_from_slice(&self.amount.to_le_bytes());
119                // Include max_top_up if set (10-byte format)
120                if let Some(max_top_up) = self.max_top_up {
121                    data.extend_from_slice(&max_top_up.to_le_bytes());
122                }
123                data
124            },
125        })
126    }
127}