1use crate::instruction::{InstructionAccount, InstructionView, Signer};
10use crate::token_2022_ix::encoders as ext;
11use crate::{AccountView, Address, ProgramError, ProgramResult};
12
13pub use crate::token::TOKEN_2022_PROGRAM_ID;
14
15pub type MintProgram = crate::token::TokenProgram;
18
19#[derive(Clone, Copy)]
21pub struct MintConfig<'a> {
22 pub decimals: u8,
23 pub mint_authority: &'a Address,
24 pub freeze_authority: Option<&'a Address>,
25}
26
27#[derive(Clone, Copy)]
36#[non_exhaustive]
37pub enum MintExtension<'a> {
38 TransferFeeConfig {
39 authority: Option<&'a Address>,
40 withdraw_authority: Option<&'a Address>,
41 basis_points: u16,
42 maximum_fee: u64,
43 },
44 MintCloseAuthority(Option<&'a Address>),
45 NonTransferable,
46 PermanentDelegate(&'a Address),
47 TransferHook {
48 authority: Option<&'a Address>,
49 program_id: Option<&'a Address>,
50 },
51 MetadataPointer {
52 authority: Option<&'a Address>,
53 metadata_address: Option<&'a Address>,
54 },
55 DefaultAccountState(u8),
57 InterestBearing {
60 rate_authority: Option<&'a Address>,
61 rate: i16,
62 },
63 ScaledUiAmount {
66 authority: Option<&'a Address>,
67 multiplier: f64,
68 },
69 Pausable(&'a Address),
71 GroupPointer {
72 authority: Option<&'a Address>,
73 group_address: Option<&'a Address>,
74 },
75 GroupMemberPointer {
76 authority: Option<&'a Address>,
77 member_address: Option<&'a Address>,
78 },
79 PermissionedBurn(&'a Address),
81}
82
83impl MintExtension<'_> {
84 pub const fn extension_type(&self) -> u16 {
86 match self {
87 Self::TransferFeeConfig { .. } => 1,
88 Self::MintCloseAuthority(_) => 3,
89 Self::DefaultAccountState(_) => 6,
90 Self::NonTransferable => 9,
91 Self::InterestBearing { .. } => 10,
92 Self::PermanentDelegate(_) => 12,
93 Self::TransferHook { .. } => 14,
94 Self::MetadataPointer { .. } => 18,
95 Self::GroupPointer { .. } => 20,
96 Self::GroupMemberPointer { .. } => 22,
97 Self::ScaledUiAmount { .. } => 25,
98 Self::Pausable(_) => 26,
99 Self::PermissionedBurn(_) => 28,
100 }
101 }
102
103 pub const fn value_len(&self) -> usize {
105 match self {
106 Self::TransferFeeConfig { .. } => 108,
107 Self::MintCloseAuthority(_)
108 | Self::PermanentDelegate(_)
109 | Self::PermissionedBurn(_) => 32,
110 Self::NonTransferable => 0,
111 Self::TransferHook { .. }
112 | Self::MetadataPointer { .. }
113 | Self::GroupPointer { .. }
114 | Self::GroupMemberPointer { .. } => 64,
115 Self::DefaultAccountState(_) => 1,
116 Self::InterestBearing { .. } => 52,
117 Self::ScaledUiAmount { .. } => 56,
118 Self::Pausable(_) => 33,
119 }
120 }
121
122 fn validate(&self) -> ProgramResult {
123 fn nullable(value: Option<&Address>) -> ProgramResult {
124 if value.is_some_and(|a| a.as_array() == &[0; 32]) {
125 Err(ProgramError::InvalidArgument)
126 } else {
127 Ok(())
128 }
129 }
130 match *self {
131 Self::TransferFeeConfig {
132 authority,
133 withdraw_authority,
134 basis_points,
135 ..
136 } => {
137 nullable(authority)?;
138 nullable(withdraw_authority)?;
139 if basis_points > 10_000 {
140 return Err(ProgramError::InvalidArgument);
141 }
142 }
143 Self::MintCloseAuthority(authority) => nullable(authority)?,
144 Self::PermanentDelegate(delegate) => nullable(Some(delegate))?,
145 Self::TransferHook {
146 authority,
147 program_id,
148 } => {
149 nullable(authority)?;
150 nullable(program_id)?;
151 }
152 Self::MetadataPointer {
153 authority,
154 metadata_address,
155 } => {
156 nullable(authority)?;
157 nullable(metadata_address)?;
158 }
159 Self::NonTransferable => {}
160 Self::DefaultAccountState(state) => {
162 ext::encode_initialize_default_account_state(state)?;
163 }
164 Self::InterestBearing {
165 rate_authority,
166 rate,
167 } => {
168 ext::encode_initialize_interest_bearing_mint(rate_authority, rate)?;
169 }
170 Self::ScaledUiAmount {
171 authority,
172 multiplier,
173 } => {
174 ext::encode_initialize_scaled_ui_amount(authority, multiplier)?;
175 }
176 Self::Pausable(authority) => {
177 ext::encode_initialize_authority(ext::IX_PAUSABLE, authority)?;
178 }
179 Self::GroupPointer {
180 authority,
181 group_address,
182 } => {
183 ext::encode_initialize_pointer(ext::IX_GROUP_POINTER, authority, group_address)?;
184 }
185 Self::GroupMemberPointer {
186 authority,
187 member_address,
188 } => {
189 ext::encode_initialize_pointer(
190 ext::IX_GROUP_MEMBER_POINTER,
191 authority,
192 member_address,
193 )?;
194 }
195 Self::PermissionedBurn(authority) => {
196 ext::encode_initialize_authority(ext::IX_PERMISSIONED_BURN, authority)?;
197 }
198 }
199 Ok(())
200 }
201
202 pub fn instruction_data(&self) -> Result<MintInstructionData, ProgramError> {
204 self.validate()?;
205 let mut out = MintInstructionData::new();
206 match *self {
207 Self::TransferFeeConfig {
208 authority,
209 withdraw_authority,
210 basis_points,
211 maximum_fee,
212 } => {
213 out.push(&[26, 0]);
214 out.option(authority);
215 out.option(withdraw_authority);
216 out.push(&basis_points.to_le_bytes());
217 out.push(&maximum_fee.to_le_bytes());
218 }
219 Self::MintCloseAuthority(authority) => {
220 out.push(&[25]);
221 out.option(authority);
222 }
223 Self::NonTransferable => out.push(&[32]),
224 Self::PermanentDelegate(delegate) => {
225 out.push(&[35]);
226 out.push(delegate.as_array());
227 }
228 Self::TransferHook {
229 authority,
230 program_id,
231 } => {
232 out.push(&[36, 0]);
233 out.nullable(authority);
234 out.nullable(program_id);
235 }
236 Self::MetadataPointer {
237 authority,
238 metadata_address,
239 } => {
240 out.push(&[39, 0]);
241 out.nullable(authority);
242 out.nullable(metadata_address);
243 }
244 Self::DefaultAccountState(state) => {
245 out.push(&ext::encode_initialize_default_account_state(state)?);
246 }
247 Self::InterestBearing {
248 rate_authority,
249 rate,
250 } => {
251 out.push(
252 ext::encode_initialize_interest_bearing_mint(rate_authority, rate)?.as_slice(),
253 );
254 }
255 Self::ScaledUiAmount {
256 authority,
257 multiplier,
258 } => {
259 out.push(
260 ext::encode_initialize_scaled_ui_amount(authority, multiplier)?.as_slice(),
261 );
262 }
263 Self::Pausable(authority) => {
264 out.push(ext::encode_initialize_authority(ext::IX_PAUSABLE, authority)?.as_slice());
265 }
266 Self::GroupPointer {
267 authority,
268 group_address,
269 } => {
270 out.push(
271 ext::encode_initialize_pointer(
272 ext::IX_GROUP_POINTER,
273 authority,
274 group_address,
275 )?
276 .as_slice(),
277 );
278 }
279 Self::GroupMemberPointer {
280 authority,
281 member_address,
282 } => {
283 out.push(
284 ext::encode_initialize_pointer(
285 ext::IX_GROUP_MEMBER_POINTER,
286 authority,
287 member_address,
288 )?
289 .as_slice(),
290 );
291 }
292 Self::PermissionedBurn(authority) => {
293 out.push(
294 ext::encode_initialize_authority(ext::IX_PERMISSIONED_BURN, authority)?
295 .as_slice(),
296 );
297 }
298 }
299 Ok(out)
300 }
301}
302
303pub struct MintInstructionData {
305 bytes: [u8; 78],
306 len: usize,
307}
308
309impl MintInstructionData {
310 fn new() -> Self {
311 Self {
312 bytes: [0; 78],
313 len: 0,
314 }
315 }
316 fn push(&mut self, bytes: &[u8]) {
317 self.bytes[self.len..self.len + bytes.len()].copy_from_slice(bytes);
318 self.len += bytes.len();
319 }
320 fn option(&mut self, address: Option<&Address>) {
321 self.push(&[u8::from(address.is_some())]);
322 if let Some(address) = address {
323 self.push(address.as_array());
324 }
325 }
326 fn nullable(&mut self, address: Option<&Address>) {
327 self.push(address.map_or(&[0; 32][..], |a| &a.as_array()[..]));
328 }
329 pub fn as_bytes(&self) -> &[u8] {
330 &self.bytes[..self.len]
331 }
332}
333
334impl MintConfig<'_> {
335 pub fn instruction_data(&self) -> MintInstructionData {
337 let mut out = MintInstructionData::new();
338 out.push(&[20, self.decimals]);
339 out.push(self.mint_authority.as_array());
340 out.option(self.freeze_authority);
341 out
342 }
343}
344
345pub struct InitializeMint2<'a, 'b> {
352 pub mint: &'a AccountView<'a>,
353 pub program: MintProgram,
354 pub config: MintConfig<'b>,
355}
356
357impl InitializeMint2<'_, '_> {
358 pub fn invoke(&self) -> ProgramResult {
359 invoke_mint(
360 self.mint,
361 self.program,
362 self.config.instruction_data().as_bytes(),
363 )
364 }
365}
366
367fn invoke_mint(mint: &AccountView<'_>, program: MintProgram, data: &[u8]) -> ProgramResult {
368 if !mint.owned_by(program.address()) {
369 return Err(ProgramError::IncorrectProgramId);
370 }
371 if !mint.is_writable() {
372 return Err(ProgramError::InvalidArgument);
373 }
374 let accounts = [InstructionAccount::writable(mint.address())];
375 let instruction = InstructionView {
376 program_id: program.address(),
377 accounts: &accounts,
378 data,
379 };
380 crate::cpi::invoke(&instruction, &[mint])
381}
382
383pub struct MintPlan<'a> {
390 program: MintProgram,
391 config: MintConfig<'a>,
392 extensions: &'a [MintExtension<'a>],
393 space: usize,
394}
395
396impl<'a> MintPlan<'a> {
397 pub fn new(
398 program: MintProgram,
399 config: MintConfig<'a>,
400 extensions: &'a [MintExtension<'a>],
401 ) -> Result<Self, ProgramError> {
402 if program == MintProgram::Legacy && !extensions.is_empty() {
403 return Err(ProgramError::InvalidArgument);
404 }
405 let mut seen = 0u32;
406 let mut space = if extensions.is_empty() { 82 } else { 166 };
407 for extension in extensions {
408 extension.validate()?;
409 let mask = 1u32 << extension.extension_type();
410 if seen & mask != 0 {
411 return Err(ProgramError::InvalidArgument);
412 }
413 seen |= mask;
414 space += 4 + extension.value_len();
415 }
416 if seen & (1 << 10) != 0 && seen & (1 << 25) != 0 {
419 return Err(ProgramError::InvalidArgument);
420 }
421 if space == 355 {
423 space += 2;
424 }
425 Ok(Self {
426 program,
427 config,
428 extensions,
429 space,
430 })
431 }
432
433 pub const fn space(&self) -> usize {
434 self.space
435 }
436
437 pub fn check_space(&self, space: usize) -> ProgramResult {
439 if space != self.space {
440 return Err(ProgramError::InvalidAccountData);
441 }
442 Ok(())
443 }
444
445 pub fn initialize(&self, mint: &AccountView<'_>) -> ProgramResult {
450 if !mint.owned_by(self.program.address()) {
451 return Err(ProgramError::IncorrectProgramId);
452 }
453 if !mint.is_writable() {
454 return Err(ProgramError::InvalidArgument);
455 }
456 self.check_space(mint.data_len())?;
457 if mint.lamports() < crate::rent::minimum_balance_live(self.space)? {
458 return Err(ProgramError::AccountNotRentExempt);
459 }
460 {
461 let data = mint.try_borrow()?;
462 if data.iter().any(|b| *b != 0) {
463 return Err(ProgramError::AccountAlreadyInitialized);
464 }
465 }
466 for extension in self.extensions {
467 invoke_mint(mint, self.program, extension.instruction_data()?.as_bytes())?;
468 }
469 InitializeMint2 {
470 mint,
471 program: self.program,
472 config: self.config,
473 }
474 .invoke()
475 }
476
477 pub fn create(
485 &self,
486 payer: &AccountView<'_>,
487 mint: &AccountView<'_>,
488 signers: &[Signer<'_, '_>],
489 ) -> ProgramResult {
490 if payer.address() == mint.address() || !payer.is_writable() || !mint.is_writable() {
491 return Err(ProgramError::InvalidArgument);
492 }
493 if !mint.owned_by(&crate::system::SYSTEM_PROGRAM_ID)
494 || !payer.owned_by(&crate::system::SYSTEM_PROGRAM_ID)
495 {
496 return Err(ProgramError::IncorrectProgramId);
497 }
498 if mint.data_len() != 0 || payer.data_len() != 0 {
499 return Err(ProgramError::InvalidAccountData);
500 }
501 let rent = crate::rent::minimum_balance_live(self.space)?;
502 let funding = rent.saturating_sub(mint.lamports());
503 if payer.lamports() < funding {
504 return Err(ProgramError::InsufficientFunds);
505 }
506 crate::system::CreateAccountAllowPrefund {
507 to: mint,
508 funding: Some((payer, funding)),
509 space: self.space as u64,
510 owner: self.program.address(),
511 }
512 .invoke_signed(signers)?;
513 self.initialize(mint)
514 }
515}