light_token/instruction/
approve_checked.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/// # Approve a delegate for a Light Token account with decimals validation:
9/// ```rust
10/// # use solana_pubkey::Pubkey;
11/// # use light_token::instruction::ApproveChecked;
12/// # let token_account = Pubkey::new_unique();
13/// # let mint = Pubkey::new_unique();
14/// # let delegate = Pubkey::new_unique();
15/// # let owner = Pubkey::new_unique();
16/// let instruction = ApproveChecked {
17///     token_account,
18///     mint,
19///     delegate,
20///     owner,
21///     amount: 100,
22///     decimals: 8,
23///     max_top_up: None,
24/// }.instruction()?;
25/// # Ok::<(), solana_program_error::ProgramError>(())
26/// ```
27pub struct ApproveChecked {
28    /// Light Token account to approve delegation for
29    pub token_account: Pubkey,
30    /// Mint account (for decimals validation - may be skipped if Light Token has cached decimals)
31    pub mint: Pubkey,
32    /// Delegate to approve
33    pub delegate: Pubkey,
34    /// Owner of the Light Token account (signer, payer for top-up)
35    pub owner: Pubkey,
36    /// Amount of tokens to delegate
37    pub amount: u64,
38    /// Expected token decimals
39    pub decimals: u8,
40    /// Maximum lamports for rent top-up. Transaction fails if exceeded. (0 = no limit)
41    pub max_top_up: Option<u16>,
42}
43
44/// # Approve Light Token via CPI with decimals validation:
45/// ```rust,no_run
46/// # use light_token::instruction::ApproveCheckedCpi;
47/// # use solana_account_info::AccountInfo;
48/// # let token_account: AccountInfo = todo!();
49/// # let mint: AccountInfo = todo!();
50/// # let delegate: AccountInfo = todo!();
51/// # let owner: AccountInfo = todo!();
52/// # let system_program: AccountInfo = todo!();
53/// ApproveCheckedCpi {
54///     token_account,
55///     mint,
56///     delegate,
57///     owner,
58///     system_program,
59///     amount: 100,
60///     decimals: 8,
61///     max_top_up: None,
62/// }
63/// .invoke()?;
64/// # Ok::<(), solana_program_error::ProgramError>(())
65/// ```
66pub struct ApproveCheckedCpi<'info> {
67    pub token_account: AccountInfo<'info>,
68    pub mint: AccountInfo<'info>,
69    pub delegate: AccountInfo<'info>,
70    pub owner: AccountInfo<'info>,
71    pub system_program: AccountInfo<'info>,
72    pub amount: u64,
73    pub decimals: u8,
74    /// Maximum lamports for rent top-up. Transaction fails if exceeded. (0 = no limit)
75    pub max_top_up: Option<u16>,
76}
77
78impl<'info> ApproveCheckedCpi<'info> {
79    pub fn instruction(&self) -> Result<Instruction, ProgramError> {
80        ApproveChecked::from(self).instruction()
81    }
82
83    pub fn invoke(self) -> Result<(), ProgramError> {
84        let instruction = ApproveChecked::from(&self).instruction()?;
85        let account_infos = [
86            self.token_account,
87            self.mint,
88            self.delegate,
89            self.owner,
90            self.system_program,
91        ];
92        invoke(&instruction, &account_infos)
93    }
94
95    pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
96        let instruction = ApproveChecked::from(&self).instruction()?;
97        let account_infos = [
98            self.token_account,
99            self.mint,
100            self.delegate,
101            self.owner,
102            self.system_program,
103        ];
104        invoke_signed(&instruction, &account_infos, signer_seeds)
105    }
106}
107
108impl<'info> From<&ApproveCheckedCpi<'info>> for ApproveChecked {
109    fn from(cpi: &ApproveCheckedCpi<'info>) -> Self {
110        Self {
111            token_account: *cpi.token_account.key,
112            mint: *cpi.mint.key,
113            delegate: *cpi.delegate.key,
114            owner: *cpi.owner.key,
115            amount: cpi.amount,
116            decimals: cpi.decimals,
117            max_top_up: cpi.max_top_up,
118        }
119    }
120}
121
122impl ApproveChecked {
123    pub fn instruction(self) -> Result<Instruction, ProgramError> {
124        let mut data = vec![13u8]; // CTokenApproveChecked discriminator (SPL compatible)
125        data.extend_from_slice(&self.amount.to_le_bytes());
126        data.push(self.decimals);
127        // Include max_top_up if set (11-byte format)
128        if let Some(max_top_up) = self.max_top_up {
129            data.extend_from_slice(&max_top_up.to_le_bytes());
130        }
131
132        Ok(Instruction {
133            program_id: Pubkey::from(LIGHT_TOKEN_PROGRAM_ID),
134            accounts: vec![
135                AccountMeta::new(self.token_account, false),
136                AccountMeta::new_readonly(self.mint, false),
137                AccountMeta::new_readonly(self.delegate, false),
138                AccountMeta::new(self.owner, true),
139                AccountMeta::new_readonly(Pubkey::default(), false),
140            ],
141            data,
142        })
143    }
144}