light_token/instruction/
burn.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/// # Burn tokens from a ctoken account:
9/// ```rust
10/// # use solana_pubkey::Pubkey;
11/// # use light_token::instruction::Burn;
12/// # let source = Pubkey::new_unique();
13/// # let mint = Pubkey::new_unique();
14/// # let authority = Pubkey::new_unique();
15/// let instruction = Burn {
16///     source,
17///     mint,
18///     amount: 100,
19///     authority,
20///     max_top_up: None,
21///     fee_payer: None,
22/// }.instruction()?;
23/// # Ok::<(), solana_program_error::ProgramError>(())
24/// ```
25pub struct Burn {
26    /// Light Token account to burn from
27    pub source: Pubkey,
28    /// Mint account (supply tracking)
29    pub mint: Pubkey,
30    /// Amount of tokens to burn
31    pub amount: u64,
32    /// Owner of the Light Token account
33    pub authority: Pubkey,
34    /// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
35    /// When set to a non-zero value, includes max_top_up in instruction data
36    pub max_top_up: Option<u16>,
37    /// Optional fee payer for rent top-ups. If not provided, authority pays.
38    pub fee_payer: Option<Pubkey>,
39}
40
41/// # Burn ctoken via CPI:
42/// ```rust,no_run
43/// # use light_token::instruction::BurnCpi;
44/// # use solana_account_info::AccountInfo;
45/// # let source: AccountInfo = todo!();
46/// # let mint: AccountInfo = todo!();
47/// # let authority: AccountInfo = todo!();
48/// # let system_program: AccountInfo = todo!();
49/// BurnCpi {
50///     source,
51///     mint,
52///     amount: 100,
53///     authority,
54///     system_program,
55///     max_top_up: None,
56///     fee_payer: None,
57/// }
58/// .invoke()?;
59/// # Ok::<(), solana_program_error::ProgramError>(())
60/// ```
61pub struct BurnCpi<'info> {
62    pub source: AccountInfo<'info>,
63    pub mint: AccountInfo<'info>,
64    pub amount: u64,
65    pub authority: AccountInfo<'info>,
66    pub system_program: AccountInfo<'info>,
67    /// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
68    pub max_top_up: Option<u16>,
69    /// Optional fee payer for rent top-ups. If not provided, authority pays.
70    pub fee_payer: Option<AccountInfo<'info>>,
71}
72
73impl<'info> BurnCpi<'info> {
74    pub fn instruction(&self) -> Result<Instruction, ProgramError> {
75        Burn::from(self).instruction()
76    }
77
78    pub fn invoke(self) -> Result<(), ProgramError> {
79        let instruction = Burn::from(&self).instruction()?;
80        if let Some(fee_payer) = self.fee_payer {
81            let account_infos = [
82                self.source,
83                self.mint,
84                self.authority,
85                self.system_program,
86                fee_payer,
87            ];
88            invoke(&instruction, &account_infos)
89        } else {
90            let account_infos = [self.source, self.mint, self.authority, self.system_program];
91            invoke(&instruction, &account_infos)
92        }
93    }
94
95    pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
96        let instruction = Burn::from(&self).instruction()?;
97        if let Some(fee_payer) = self.fee_payer {
98            let account_infos = [
99                self.source,
100                self.mint,
101                self.authority,
102                self.system_program,
103                fee_payer,
104            ];
105            invoke_signed(&instruction, &account_infos, signer_seeds)
106        } else {
107            let account_infos = [self.source, self.mint, self.authority, self.system_program];
108            invoke_signed(&instruction, &account_infos, signer_seeds)
109        }
110    }
111}
112
113impl<'info> From<&BurnCpi<'info>> for Burn {
114    fn from(cpi: &BurnCpi<'info>) -> Self {
115        Self {
116            source: *cpi.source.key,
117            mint: *cpi.mint.key,
118            amount: cpi.amount,
119            authority: *cpi.authority.key,
120            max_top_up: cpi.max_top_up,
121            fee_payer: cpi.fee_payer.as_ref().map(|a| *a.key),
122        }
123    }
124}
125
126impl Burn {
127    pub fn instruction(self) -> Result<Instruction, ProgramError> {
128        // Authority is writable only when max_top_up is set AND no fee_payer
129        // (authority pays for top-ups only if no separate fee_payer)
130        let authority_meta = if self.max_top_up.is_some() && self.fee_payer.is_none() {
131            AccountMeta::new(self.authority, true)
132        } else {
133            AccountMeta::new_readonly(self.authority, true)
134        };
135
136        let mut accounts = vec![
137            AccountMeta::new(self.source, false),
138            AccountMeta::new(self.mint, false),
139            authority_meta,
140            // System program required for rent top-up CPIs
141            AccountMeta::new_readonly(Pubkey::default(), false),
142        ];
143
144        // Add fee_payer if provided (must be signer and writable)
145        if let Some(fee_payer) = self.fee_payer {
146            accounts.push(AccountMeta::new(fee_payer, true));
147        }
148
149        Ok(Instruction {
150            program_id: Pubkey::from(LIGHT_TOKEN_PROGRAM_ID),
151            accounts,
152            data: {
153                let mut data = vec![8u8]; // CTokenBurn discriminator
154                data.extend_from_slice(&self.amount.to_le_bytes());
155                // Include max_top_up if set (10-byte format)
156                if let Some(max_top_up) = self.max_top_up {
157                    data.extend_from_slice(&max_top_up.to_le_bytes());
158                }
159                data
160            },
161        })
162    }
163}