light_token/instruction/
mint_to.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 (simple 3-account instruction):
9/// ```rust
10/// # use solana_pubkey::Pubkey;
11/// # use light_token::instruction::MintTo;
12/// # let mint = Pubkey::new_unique();
13/// # let destination = Pubkey::new_unique();
14/// # let authority = Pubkey::new_unique();
15/// let instruction = MintTo {
16///     mint,
17///     destination,
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 MintTo {
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    /// Mint authority
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/// # Mint to ctoken via CPI:
42/// ```rust,no_run
43/// # use light_token::instruction::MintToCpi;
44/// # use solana_account_info::AccountInfo;
45/// # let mint: AccountInfo = todo!();
46/// # let destination: AccountInfo = todo!();
47/// # let authority: AccountInfo = todo!();
48/// # let system_program: AccountInfo = todo!();
49/// MintToCpi {
50///     mint,
51///     destination,
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 MintToCpi<'info> {
62    pub mint: AccountInfo<'info>,
63    pub destination: 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> MintToCpi<'info> {
74    pub fn instruction(&self) -> Result<Instruction, ProgramError> {
75        MintTo::from(self).instruction()
76    }
77
78    pub fn invoke(self) -> Result<(), ProgramError> {
79        let instruction = MintTo::from(&self).instruction()?;
80        if let Some(fee_payer) = self.fee_payer {
81            let account_infos = [
82                self.mint,
83                self.destination,
84                self.authority,
85                self.system_program,
86                fee_payer,
87            ];
88            invoke(&instruction, &account_infos)
89        } else {
90            let account_infos = [
91                self.mint,
92                self.destination,
93                self.authority,
94                self.system_program,
95            ];
96            invoke(&instruction, &account_infos)
97        }
98    }
99
100    pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> {
101        let instruction = MintTo::from(&self).instruction()?;
102        if let Some(fee_payer) = self.fee_payer {
103            let account_infos = [
104                self.mint,
105                self.destination,
106                self.authority,
107                self.system_program,
108                fee_payer,
109            ];
110            invoke_signed(&instruction, &account_infos, signer_seeds)
111        } else {
112            let account_infos = [
113                self.mint,
114                self.destination,
115                self.authority,
116                self.system_program,
117            ];
118            invoke_signed(&instruction, &account_infos, signer_seeds)
119        }
120    }
121}
122
123impl<'info> From<&MintToCpi<'info>> for MintTo {
124    fn from(cpi: &MintToCpi<'info>) -> Self {
125        Self {
126            mint: *cpi.mint.key,
127            destination: *cpi.destination.key,
128            amount: cpi.amount,
129            authority: *cpi.authority.key,
130            max_top_up: cpi.max_top_up,
131            fee_payer: cpi.fee_payer.as_ref().map(|a| *a.key),
132        }
133    }
134}
135
136impl MintTo {
137    pub fn instruction(self) -> Result<Instruction, ProgramError> {
138        // Authority is writable only when max_top_up is set AND no fee_payer
139        // (authority pays for top-ups only if no separate fee_payer)
140        let authority_meta = if self.max_top_up.is_some() && self.fee_payer.is_none() {
141            AccountMeta::new(self.authority, true)
142        } else {
143            AccountMeta::new_readonly(self.authority, true)
144        };
145
146        let mut accounts = vec![
147            AccountMeta::new(self.mint, false),
148            AccountMeta::new(self.destination, false),
149            authority_meta,
150            // System program required for rent top-up CPIs
151            AccountMeta::new_readonly(Pubkey::default(), false),
152        ];
153
154        // Add fee_payer if provided (must be signer and writable)
155        if let Some(fee_payer) = self.fee_payer {
156            accounts.push(AccountMeta::new(fee_payer, true));
157        }
158
159        Ok(Instruction {
160            program_id: Pubkey::from(LIGHT_TOKEN_PROGRAM_ID),
161            accounts,
162            data: {
163                let mut data = vec![7u8]; // MintTo discriminator
164                data.extend_from_slice(&self.amount.to_le_bytes());
165                // Include max_top_up if set (10-byte format)
166                if let Some(max_top_up) = self.max_top_up {
167                    data.extend_from_slice(&max_top_up.to_le_bytes());
168                }
169                data
170            },
171        })
172    }
173}