1use anchor_lang::prelude::*;
2use anchor_lang::solana_program::program::invoke;
3use anchor_spl::token_2022::{self, spl_token_2022};
4use spl_token_2022::{
5 extension::{
6 confidential_transfer::instruction::initialize_mint as init_ct_mint,
7 metadata_pointer::instruction::initialize as init_metadata_pointer, ExtensionType,
8 },
9 instruction::initialize_mint2,
10};
11
12use crate::errors::SssError;
13use crate::events::StablecoinInitialized;
14use crate::state::*;
15
16#[derive(AnchorSerialize, AnchorDeserialize)]
17pub struct InitializeParams {
18 pub name: String,
19 pub symbol: String,
20 pub uri: String,
21 pub decimals: u8,
22 pub preset: StablecoinPreset,
23 pub enable_permanent_delegate: Option<bool>,
24 pub enable_transfer_hook: Option<bool>,
25 pub enable_default_state_frozen: Option<bool>,
26 pub enable_confidential_transfers: Option<bool>,
27}
28
29#[derive(Accounts)]
30#[instruction(params: InitializeParams)]
31pub struct Initialize<'info> {
32 #[account(mut)]
33 pub authority: Signer<'info>,
34
35 #[account(mut)]
39 pub mint: Signer<'info>,
40
41 #[account(
42 init,
43 payer = authority,
44 space = StablecoinConfig::SPACE,
45 seeds = [StablecoinConfig::SEED_PREFIX, mint.key().as_ref()],
46 bump,
47 )]
48 pub config: Account<'info, StablecoinConfig>,
49
50 #[account(
51 init,
52 payer = authority,
53 space = RoleRegistry::SPACE,
54 seeds = [RoleRegistry::SEED_PREFIX, config.key().as_ref()],
55 bump,
56 )]
57 pub role_registry: Account<'info, RoleRegistry>,
58
59 pub system_program: Program<'info, System>,
60 pub token_program: Program<'info, token_2022::Token2022>,
61 pub rent: Sysvar<'info, Rent>,
62}
63
64pub fn handler(ctx: Context<Initialize>, params: InitializeParams) -> Result<()> {
65 require!(
66 ctx.accounts.authority.key() != Pubkey::default(),
67 SssError::ZeroAuthority
68 );
69 require!(
70 !params.name.is_empty() && params.name.len() <= StablecoinConfig::MAX_NAME_LEN,
71 SssError::NameTooLong
72 );
73 require!(
74 !params.symbol.is_empty() && params.symbol.len() <= StablecoinConfig::MAX_SYMBOL_LEN,
75 SssError::SymbolTooLong
76 );
77 require!(
78 !params.uri.is_empty() && params.uri.len() <= StablecoinConfig::MAX_URI_LEN,
79 SssError::UriTooLong
80 );
81 require!(params.decimals <= 18, SssError::InvalidDecimals);
82
83 let clock = Clock::get()?;
84 let config_key = ctx.accounts.config.key();
85
86 let (enable_permanent_delegate, enable_transfer_hook, default_account_frozen, enable_ct) =
88 match params.preset {
89 StablecoinPreset::SSS1 => (false, false, false, false),
90 StablecoinPreset::SSS2 => (true, true, false, false),
91 StablecoinPreset::SSS3 => (true, false, false, true),
92 StablecoinPreset::Custom => {
93 let pd = params
94 .enable_permanent_delegate
95 .ok_or(SssError::CustomFlagsMissing)?;
96 let th = params
97 .enable_transfer_hook
98 .ok_or(SssError::CustomFlagsMissing)?;
99 let df = params
100 .enable_default_state_frozen
101 .ok_or(SssError::CustomFlagsMissing)?;
102 let ct = params
103 .enable_confidential_transfers
104 .ok_or(SssError::CustomFlagsMissing)?;
105 (pd, th, df, ct)
106 }
107 };
108
109 let mut extensions = vec![ExtensionType::MetadataPointer];
111 if enable_permanent_delegate {
112 extensions.push(ExtensionType::PermanentDelegate);
113 }
114 if enable_transfer_hook {
115 extensions.push(ExtensionType::TransferHook);
116 }
117 if default_account_frozen {
118 extensions.push(ExtensionType::DefaultAccountState);
119 }
120 if enable_ct {
121 extensions.push(ExtensionType::ConfidentialTransferMint);
122 }
123
124 let mint_space =
125 ExtensionType::try_calculate_account_len::<spl_token_2022::state::Mint>(&extensions)
126 .map_err(|_| SssError::Overflow)?;
127
128 let rent = &ctx.accounts.rent;
129 let lamports = rent.minimum_balance(mint_space);
130
131 invoke(
133 &anchor_lang::solana_program::system_instruction::create_account(
134 ctx.accounts.authority.key,
135 ctx.accounts.mint.key,
136 lamports,
137 mint_space as u64,
138 &token_2022::Token2022::id(),
139 ),
140 &[
141 ctx.accounts.authority.to_account_info(),
142 ctx.accounts.mint.to_account_info(),
143 ctx.accounts.system_program.to_account_info(),
144 ],
145 )?;
146
147 invoke(
149 &init_metadata_pointer(
150 &token_2022::Token2022::id(),
151 ctx.accounts.mint.key,
152 Some(config_key),
153 Some(ctx.accounts.mint.key()),
154 )?,
155 &[ctx.accounts.mint.to_account_info()],
156 )?;
157
158 if enable_permanent_delegate {
160 invoke(
161 &spl_token_2022::instruction::initialize_permanent_delegate(
162 &token_2022::Token2022::id(),
163 ctx.accounts.mint.key,
164 &config_key,
165 )?,
166 &[ctx.accounts.mint.to_account_info()],
167 )?;
168 }
169
170 if enable_transfer_hook {
173 let hook_program_id = ctx
174 .remaining_accounts
175 .first()
176 .map(|a| a.key())
177 .ok_or(SssError::TransferHookNotEnabled)?;
178
179 require!(
180 hook_program_id != Pubkey::default(),
181 SssError::InvalidHookProgram
182 );
183
184 invoke(
185 &spl_token_2022::extension::transfer_hook::instruction::initialize(
186 &token_2022::Token2022::id(),
187 ctx.accounts.mint.key,
188 Some(config_key),
189 Some(hook_program_id),
190 )?,
191 &[ctx.accounts.mint.to_account_info()],
192 )?;
193 }
194
195 if default_account_frozen {
197 invoke(
198 &spl_token_2022::extension::default_account_state::instruction::initialize_default_account_state(
199 &token_2022::Token2022::id(),
200 ctx.accounts.mint.key,
201 &spl_token_2022::state::AccountState::Frozen,
202 )?,
203 &[ctx.accounts.mint.to_account_info()],
204 )?;
205 }
206
207 if enable_ct {
209 invoke(
210 &init_ct_mint(
211 &token_2022::Token2022::id(),
212 ctx.accounts.mint.key,
213 Some(config_key), true, None, )?,
217 &[ctx.accounts.mint.to_account_info()],
218 )?;
219 }
220
221 invoke(
223 &initialize_mint2(
224 &token_2022::Token2022::id(),
225 ctx.accounts.mint.key,
226 &config_key, Some(&config_key), params.decimals,
229 )?,
230 &[ctx.accounts.mint.to_account_info()],
231 )?;
232
233 let config = &mut ctx.accounts.config;
235 config.bump = ctx.bumps.config;
236 config.mint = ctx.accounts.mint.key();
237 config.master_authority = ctx.accounts.authority.key();
238 config.pending_authority = Pubkey::default();
239 config.name = params.name.clone();
240 config.symbol = params.symbol.clone();
241 config.uri = params.uri.clone();
242 config.decimals = params.decimals;
243 config.preset = params.preset;
244 config.enable_permanent_delegate = enable_permanent_delegate;
245 config.enable_transfer_hook = enable_transfer_hook;
246 config.default_account_frozen = default_account_frozen;
247 config.enable_confidential_transfers = enable_ct;
248 config.is_paused = false;
249 config.supply_cap = 0;
250 config.total_minted = 0;
251 config.total_burned = 0;
252 config.total_seized = 0;
253 config.audit_log_index = 0;
254 config.reserve_attestation_index = 0;
255 config.created_at = clock.unix_timestamp;
256 config.updated_at = clock.unix_timestamp;
257
258 let role_registry = &mut ctx.accounts.role_registry;
260 role_registry.bump = ctx.bumps.role_registry;
261 role_registry.config = config.key();
262 role_registry.master_authority = ctx.accounts.authority.key();
263 role_registry.pauser = ctx.accounts.authority.key();
264 role_registry.blacklister = if enable_permanent_delegate {
265 ctx.accounts.authority.key()
266 } else {
267 Pubkey::default()
268 };
269 role_registry.seizer = if enable_permanent_delegate {
270 ctx.accounts.authority.key()
271 } else {
272 Pubkey::default()
273 };
274
275 emit!(StablecoinInitialized {
276 config: config.key(),
277 mint: config.mint,
278 master_authority: config.master_authority,
279 name: config.name.clone(),
280 symbol: config.symbol.clone(),
281 preset: config.preset as u8,
282 timestamp: clock.unix_timestamp,
283 });
284
285 Ok(())
286}