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