sss_token/instructions/
mint.rs1use anchor_lang::prelude::*;
2use anchor_spl::{
3 token_2022::{self, MintTo, Token2022},
4 token_interface::TokenAccount,
5};
6
7use crate::errors::SssError;
8use crate::events::TokensMinted;
9use crate::state::*;
10use crate::utils::{require_blacklist_enabled, require_not_paused};
11
12#[derive(Accounts)]
13pub struct MintTokens<'info> {
14 #[account(mut)]
15 pub minter_authority: Signer<'info>,
16
17 #[account(
18 mut,
19 seeds = [StablecoinConfig::SEED_PREFIX, config.mint.as_ref()],
20 bump = config.bump,
21 )]
22 pub config: Account<'info, StablecoinConfig>,
23
24 #[account(
25 mut,
26 seeds = [MinterInfo::SEED_PREFIX, config.key().as_ref(), minter_authority.key().as_ref()],
27 bump = minter_info.bump,
28 constraint = minter_info.config == config.key(),
29 )]
30 pub minter_info: Account<'info, MinterInfo>,
31
32 #[account(
34 mut,
35 address = config.mint,
36 constraint = mint.owner == &token_program.key() @ SssError::InvalidAuthority,
37 )]
38 pub mint: UncheckedAccount<'info>,
39
40 #[account(
41 mut,
42 token::mint = config.mint,
43 token::token_program = token_program,
44 )]
45 pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
46
47 #[account(
55 seeds = [
56 BlacklistEntry::SEED_PREFIX,
57 config.key().as_ref(),
58 recipient_token_account.owner.as_ref(),
59 ],
60 bump,
61 )]
62 pub recipient_blacklist: UncheckedAccount<'info>,
63
64 pub token_program: Program<'info, Token2022>,
65}
66
67pub fn handler(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
68 require!(amount > 0, SssError::MintAmountZero);
69
70 let config = &ctx.accounts.config;
71 require_not_paused(config)?;
72
73 if require_blacklist_enabled(config).is_ok() {
77 if !ctx.accounts.recipient_blacklist.data_is_empty() {
79 return Err(SssError::RecipientBlacklisted.into());
80 }
81 }
82
83 let minter_info = &ctx.accounts.minter_info;
84 require!(minter_info.is_active, SssError::MinterNotActive);
85 require!(minter_info.can_mint(amount), SssError::MintQuotaExceeded);
86
87 if config.supply_cap > 0 {
88 let new_supply = config
89 .current_supply()
90 .checked_add(amount)
91 .ok_or(SssError::Overflow)?;
92 require!(new_supply <= config.supply_cap, SssError::SupplyCapExceeded);
93 }
94
95 let clock = Clock::get()?;
96
97 let mint_key = config.mint;
99 let signer_seeds: &[&[&[u8]]] = &[&[
100 StablecoinConfig::SEED_PREFIX,
101 mint_key.as_ref(),
102 &[config.bump],
103 ]];
104
105 token_2022::mint_to(
106 CpiContext::new_with_signer(
107 ctx.accounts.token_program.to_account_info(),
108 MintTo {
109 mint: ctx.accounts.mint.to_account_info(),
110 to: ctx.accounts.recipient_token_account.to_account_info(),
111 authority: ctx.accounts.config.to_account_info(),
112 },
113 signer_seeds,
114 ),
115 amount,
116 )?;
117
118 let minter_info = &mut ctx.accounts.minter_info;
120 minter_info.total_minted = minter_info
121 .total_minted
122 .checked_add(amount)
123 .ok_or(SssError::Overflow)?;
124 minter_info.last_mint_at = clock.unix_timestamp;
125
126 let config = &mut ctx.accounts.config;
128 config.total_minted = config
129 .total_minted
130 .checked_add(amount)
131 .ok_or(SssError::Overflow)?;
132 config.updated_at = clock.unix_timestamp;
133
134 emit!(TokensMinted {
135 config: config.key(),
136 minter: ctx.accounts.minter_authority.key(),
137 recipient: ctx.accounts.recipient_token_account.key(),
138 amount,
139 total_minted: config.total_minted,
140 timestamp: clock.unix_timestamp,
141 });
142
143 Ok(())
144}