light_token/instruction/
mint_to_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/// # Mint tokens to a ctoken account with decimals validation:
9/// ```rust
10/// # use solana_pubkey::Pubkey;
11/// # use light_token::instruction::MintToChecked;
12/// # let mint = Pubkey::new_unique();
13/// # let destination = Pubkey::new_unique();
14/// # let authority = Pubkey::new_unique();
15/// let instruction = MintToChecked {
16///     mint,
17///     destination,
18///     amount: 100,
19///     decimals: 8,
20///     authority,
21///     max_top_up: None,
22/// }.instruction()?;
23/// # Ok::<(), solana_program_error::ProgramError>(())
24/// ```
25pub struct MintToChecked {
26    /// Mint account (supply tracking)
27    pub mint: Pubkey,
28    /// Destination Light Token account to mint to
29    pub destination: Pubkey,
30    /// Amount of tokens to mint
31    pub amount: u64,
32    /// Expected token decimals
33    pub decimals: u8,
34    /// Mint authority
35    pub authority: Pubkey,
36    /// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
37    /// When set to a non-zero value, includes max_top_up in instruction data
38    pub max_top_up: Option<u16>,
39}
40
41/// # Mint to ctoken via CPI with decimals validation:
42/// ```rust,no_run
43/// # use light_token::instruction::MintToCheckedCpi;
44/// # use solana_account_info::AccountInfo;
45/// # let mint: AccountInfo = todo!();
46/// # let destination: AccountInfo = todo!();
47/// # let authority: AccountInfo = todo!();
48/// MintToCheckedCpi {
49///     mint,
50///     destination,
51///     amount: 100,
52///     decimals: 8,
53///     authority,
54///     max_top_up: None,
55/// }
56/// .invoke()?;
57/// # Ok::<(), solana_program_error::ProgramError>(())
58/// ```
59pub struct MintToCheckedCpi<'info> {
60    pub mint: AccountInfo<'info>,
61    pub destination: AccountInfo<'info>,
62    pub amount: u64,
63    pub decimals: u8,
64    pub authority: AccountInfo<'info>,
65    /// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
66    pub max_top_up: Option<u16>,
67}
68
69impl<'info> MintToCheckedCpi<'info> {
70    pub fn instruction(&self) -> Result<Instruction, ProgramError> {
71        MintToChecked::from(self).instruction()
72    }
73
74    pub fn invoke(self) -> Result<(), ProgramError> {
75        let instruction = MintToChecked::from(&self).instruction()?;
76        let account_infos = [self.mint, self.destination, self.authority];
77        invoke(&instruction, &account_infos)
78    }
79
80    pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
81        let instruction = MintToChecked::from(&self).instruction()?;
82        let account_infos = [self.mint, self.destination, self.authority];
83        invoke_signed(&instruction, &account_infos, signer_seeds)
84    }
85}
86
87impl<'info> From<&MintToCheckedCpi<'info>> for MintToChecked {
88    fn from(cpi: &MintToCheckedCpi<'info>) -> Self {
89        Self {
90            mint: *cpi.mint.key,
91            destination: *cpi.destination.key,
92            amount: cpi.amount,
93            decimals: cpi.decimals,
94            authority: *cpi.authority.key,
95            max_top_up: cpi.max_top_up,
96        }
97    }
98}
99
100impl MintToChecked {
101    pub fn instruction(self) -> Result<Instruction, ProgramError> {
102        Ok(Instruction {
103            program_id: Pubkey::from(LIGHT_TOKEN_PROGRAM_ID),
104            accounts: vec![
105                AccountMeta::new(self.mint, false),
106                AccountMeta::new(self.destination, false),
107                AccountMeta::new_readonly(self.authority, true),
108            ],
109            data: {
110                let mut data = vec![14u8]; // TokenMintToChecked discriminator
111                data.extend_from_slice(&self.amount.to_le_bytes());
112                data.push(self.decimals);
113                // Include max_top_up if set (11-byte format)
114                if let Some(max_top_up) = self.max_top_up {
115                    data.extend_from_slice(&max_top_up.to_le_bytes());
116                }
117                data
118            },
119        })
120    }
121}