sealevel_tools/cpi/token_program/create_mint.rs
1use core::mem::size_of;
2
3use solana_program_pack::Pack;
4
5use crate::{
6 account_info::{is_any_token_program_id, Account},
7 cpi::{system_program::CreateAccount, CpiAuthority, CpiInstruction},
8 entrypoint::NoStdAccountInfo,
9 program_error::ProgramError,
10 pubkey::Pubkey,
11 spl_token_2022::state::Mint,
12};
13
14use super::EMPTY_EXTENSION_LEN;
15
16/// Arguments to create a mint account. This method creates an account for one of the Token programs
17/// and initializes it as a mint.
18///
19/// ### Example
20///
21/// ```
22/// use sealevel_tools::{
23/// account_info::{
24/// try_next_enumerated_account, try_next_enumerated_account_info, TokenProgram,
25/// AccountInfoConstraints, Account, Signer,
26/// },
27/// cpi::token_program::{CreateMint, InitializeMetadataPointerData, InitializeMintExtensions},
28/// entrypoint::{NoStdAccountInfo, ProgramResult},
29/// pubkey::Pubkey,
30/// };
31///
32/// fn process_instruction(
33/// program_id: &Pubkey,
34/// accounts: &[NoStdAccountInfo],
35/// instruction_data: &[u8],
36/// ) -> ProgramResult {
37/// let mut accounts_iter = accounts.iter().enumerate();
38///
39/// // Next account must writable signer (A.K.A. our payer).
40/// let (_, payer) =
41/// try_next_enumerated_account::<Signer<true>>(&mut accounts_iter, Default::default())?;
42///
43/// let (new_mint_addr, new_mint_bump) =
44/// Pubkey::find_program_address(&[b"mint"], program_id);
45///
46/// // Next account must be writable data account matching PDA address.
47/// let (_, new_mint_account) = try_next_enumerated_account::<Account<true>>(
48/// &mut accounts_iter,
49/// AccountInfoConstraints {
50/// key: Some(&new_mint_addr),
51/// ..Default::default()
52/// },
53/// )?;
54///
55/// // Next account is the mint authority.
56/// let (_, mint_authority) =
57/// try_next_enumerated_account_info(&mut accounts_iter, Default::default())?;
58///
59/// // Next account is which token program to use.
60/// let (_, token_program) =
61/// try_next_enumerated_account::<TokenProgram>(&mut accounts_iter, Default::default())?;
62///
63/// CreateMint {
64/// token_program_id: token_program.key(),
65/// payer: payer.as_cpi_authority(),
66/// mint: new_mint_account.as_cpi_authority(Some(&[b"mint", &[new_mint_bump]])),
67/// mint_authority: mint_authority.key(),
68/// decimals: 9,
69/// freeze_authority: None,
70/// extensions: InitializeMintExtensions {
71/// metadata_pointer: Some(InitializeMetadataPointerData {
72/// authority: Some(mint_authority.key()),
73/// metadata: new_mint_account.key(),
74/// }),
75/// non_transferable: true,
76/// ..Default::default()
77/// }
78/// }
79/// .try_into_invoke()?;
80///
81/// Ok(())
82/// }
83/// ```
84#[derive(Clone, PartialEq, Eq)]
85pub struct CreateMint<'a, 'b: 'a> {
86 pub token_program_id: &'a Pubkey,
87 pub payer: CpiAuthority<'a, 'b>,
88 pub mint: CpiAuthority<'a, 'b>,
89 pub mint_authority: &'a Pubkey,
90 pub decimals: u8,
91 pub freeze_authority: Option<&'a Pubkey>,
92 pub extensions: InitializeMintExtensions<'a>,
93}
94
95/// Optional extensions for mint accounts. These extensions are used to add additional features to
96/// mint accounts.
97#[derive(Debug, Default, Clone, PartialEq, Eq)]
98pub struct InitializeMintExtensions<'a> {
99 pub close_authority: Option<&'a Pubkey>,
100 pub group_pointer: Option<InitializeGroupPointerData<'a>>,
101 pub group_member_pointer: Option<InitializeGroupMemberPointerData<'a>>,
102 pub metadata_pointer: Option<InitializeMetadataPointerData<'a>>,
103 pub non_transferable: bool,
104 pub permanent_delegate: Option<&'a Pubkey>,
105 pub transfer_fee_config: Option<InitializeTransferFeeConfigData<'a>>,
106 pub transfer_hook: Option<InitializeTransferHookData<'a>>,
107
108 /// If specified, [Self::transfer_fee_config] cannot be specified unless
109 /// [Self::confidential_transfer_fee_config] is also specified.
110 pub confidential_transfer: Option<InitializeConfidentialTransferData<'a>>,
111
112 /// If specified, [Self::transfer_fee_config] and [Self::confidential_transfer] must also be
113 /// specified.
114 pub confidential_transfer_fee_config: Option<InitializeConfidentialTransferFeeConfigData<'a>>,
115 // FIXME: Uncomment when the extension is implemented.
116 //pub confidential_mint_burn: Option<InitializeConfidentialMintBurnData<'a>>,
117}
118/// Data required to initialize the group pointer extension, which is used to establish a collection
119/// of mints.
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct InitializeGroupPointerData<'a> {
122 /// Who has authority to update the group pointer. If [None], the group cannot be modified.
123 pub authority: Option<&'a Pubkey>,
124
125 /// Where the group is stored.
126 ///
127 /// ### Notes
128 ///
129 /// The initialize group pointer instruction actually takes an optional group account, where if
130 /// both the authority and group pubkeys are [None], then the Token program will revert because
131 /// at least one of these values must be present. So if you want to initialize the group
132 /// pointer, this data must be specified.
133 pub group: &'a Pubkey,
134}
135
136/// Data required to initialize the group member pointer extension, which is used to associate a
137/// mint with a group (via group pointer extension).
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct InitializeGroupMemberPointerData<'a> {
140 /// Who has authority to update the group member pointer. If [None], the group member cannot be
141 /// modified.
142 ///
143 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
144 pub authority: Option<&'a Pubkey>,
145
146 /// Where the group member is stored.
147 ///
148 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
149 ///
150 /// ### Notes
151 ///
152 /// The initialize group member pointer instruction actually takes an optional member account,
153 /// where if both the authority and member pubkeys are [None], then the Token program will
154 /// revert because at least one of these values must be present. So if you want to initialize
155 /// the group member pointer, this data must be specified.
156 pub group_member: &'a Pubkey,
157}
158
159/// Data required to initialize the metadata pointer extension, which is used to add descriptive
160/// information to a mint.
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub struct InitializeMetadataPointerData<'a> {
163 /// Who has authority to update the metadata pointer. If [None], the metadata cannot be
164 /// modified.
165 ///
166 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
167 pub authority: Option<&'a Pubkey>,
168
169 /// Where the metadata is stored.
170 ///
171 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
172 ///
173 /// ### Notes
174 ///
175 /// The initialize metadata pointer instruction actually takes an optional metadata account,
176 /// where if both the authority and metadata pubkeys are [None], then the Token program will
177 /// revert because at least one of these values must be present. So if you want to initialize
178 /// the metadata pointer, this data must be specified.
179 pub metadata: &'a Pubkey,
180}
181
182/// Data required to initialize the transfer fee extension, which is used to charge a fee for
183/// transferring tokens.
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct InitializeTransferFeeConfigData<'a> {
186 /// Who has authority to update the configuration parameters. If [None], the config cannot be
187 /// modified.
188 ///
189 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
190 pub config_authority: Option<&'a Pubkey>,
191
192 /// Who has authority to withhold fees. If [None], no one can withdraw fees.
193 ///
194 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
195 pub withdraw_withheld_authority: Option<&'a Pubkey>,
196
197 /// The basis points for the fee. Cannot exceed
198 /// [spl_token_2022::extension::transfer_fee::MAX_FEE_BASIS_POINTS].
199 pub basis_points: u16,
200
201 /// The maximum fee.
202 pub maximum_fee: u64,
203}
204
205/// Data required to initialize the transfer hook extension, which is used to execute custom logic
206/// when transferring tokens.
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct InitializeTransferHookData<'a> {
209 /// Who has authority to update the program ID. If [None], the program ID cannot be modified.
210 ///
211 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
212 pub authority: Option<&'a Pubkey>,
213
214 /// Program for transfer hook CPI.
215 ///
216 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
217 ///
218 /// ### Notes
219 ///
220 /// The initialize transfer hook instruction actually takes an optional program ID, where if
221 /// both the authority and program ID pubkeys are [None], then the Token program will revert
222 /// because at least one of these values must be present. So if you want to initialize the
223 /// transfer hook extension, this data must be specified.
224 pub program_id: &'a Pubkey,
225}
226
227/// Data required to initialize the confidential transfer extension, which is used to hide the
228/// transfer amount.
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct InitializeConfidentialTransferData<'a> {
231 /// Authority to modify the confidential transfer configuration parameters and to approve new
232 /// accounts.
233 ///
234 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
235 pub authority: Option<&'a Pubkey>,
236
237 /// Determines if newly configured accounts must be approved by [Self::authority] before they
238 /// may be used by the user.
239 pub auto_approve_new_accounts: bool,
240
241 /// New authority to decode any transfer amount in a confidential transfer.
242 ///
243 /// `Some(&[0; 32])` is analogous to [None] in this context.
244 pub auditor_elgamal: Option<&'a [u8; 32]>,
245}
246
247/// Data required to initialize the confidential transfer fee extension, which is used to hide the
248/// transfer fee amount.
249#[derive(Debug, Clone, PartialEq, Eq)]
250pub struct InitializeConfidentialTransferFeeConfigData<'a> {
251 /// Optional authority to set the withdraw withheld authority ElGamal key. If [None], the
252 /// withdraw withheld authority ElGamal key cannot be modified.
253 ///
254 /// `Some(&Pubkey::default())` is analogous to [None] in this context.
255 pub authority: Option<&'a Pubkey>,
256
257 /// Withheld fees from accounts must be encrypted with this ElGamal key.
258 ///
259 /// Note that whoever holds the ElGamal private key for this ElGamal public key has the ability
260 /// to decode any withheld fee amount that are associated with accounts. When combined with the
261 /// fee parameters, the withheld fee amounts can reveal information about transfer amounts.
262 pub withdraw_withheld_authority_elgamal: &'a [u8; 32],
263}
264
265// FIXME: Uncomment when the extension is implemented.
266// /// Data required to initialize the confidential mint burn extension, which is used to hide the
267// /// supply amount.
268// pub struct InitializeConfidentialMintBurnData<'a> {
269// /// The ElGamal pubkey used to encrypt the confidential supply.
270// pub supply_elgamal: &'a [u8; 32],
271
272// /// The initial 0 supply encrypted with the supply aes key.
273// pub decryptable_supply: &'a [u8; 36],
274// }
275
276const ONLY_AUTHORITY_LEN: usize = {
277 EMPTY_EXTENSION_LEN // type + length
278 + size_of::<Pubkey>() // authority
279};
280
281const AUTHORITY_POINTER_LEN: usize = {
282 ONLY_AUTHORITY_LEN // type + length + authority
283 + size_of::<Pubkey>() // pointer
284};
285
286impl<'a, 'b: 'a> CreateMint<'a, 'b> {
287 /// Try to consume arguments to perform CPI calls.
288 ///
289 /// ### Notes
290 ///
291 /// Extension CPI calls are optional and will only be invoked when [Some] is provided for any
292 /// of the optional extension arguments. See [InitializeMintExtensions] for more information.
293 #[inline(always)]
294 pub fn try_into_invoke(self) -> Result<Account<'b, true>, ProgramError> {
295 let Self {
296 token_program_id,
297 payer,
298 mint,
299 mint_authority,
300 decimals,
301 freeze_authority,
302 extensions:
303 InitializeMintExtensions {
304 close_authority,
305 group_pointer,
306 group_member_pointer,
307 metadata_pointer,
308 non_transferable,
309 permanent_delegate,
310 transfer_fee_config,
311 transfer_hook,
312 confidential_transfer,
313 confidential_transfer_fee_config,
314 // FIXME: Uncomment when the extension is implemented.
315 // confidential_mint_burn,
316 },
317 } = self;
318
319 let add_close_authority = close_authority.is_some();
320 let add_group_pointer = group_pointer.is_some();
321 let add_group_member_pointer = group_member_pointer.is_some();
322 let add_metadata_pointer = metadata_pointer.is_some();
323 let add_permanent_delegate = permanent_delegate.is_some();
324 let add_transfer_fee_config = transfer_fee_config.is_some();
325 let add_transfer_hook = transfer_hook.is_some();
326 let add_confidential_transfer = confidential_transfer.is_some();
327 let add_confidential_transfer_fee_config = confidential_transfer_fee_config.is_some();
328 // FIXME: Uncomment when the extension is implemented.
329 // let add_confidential_mint_burn = confidential_mint_burn.is_some();
330
331 let mint_account = if add_close_authority
332 || add_group_pointer
333 || add_group_member_pointer
334 || add_metadata_pointer
335 || non_transferable
336 || add_permanent_delegate
337 || add_transfer_fee_config
338 || add_transfer_hook
339 || add_confidential_transfer
340 || add_confidential_transfer_fee_config
341 // FIXME: Uncomment when the extension is implemented.
342 // || add_confidential_mint_burn
343 {
344 if token_program_id != &spl_token_2022::ID {
345 return Err(super::ERROR_EXTENSIONS_UNSUPPORTED.into());
346 }
347
348 // Add to this depending on which extensions to add to the mint.
349 let mut total_space = super::BASE_WITH_EXTENSIONS_LEN;
350
351 if add_close_authority {
352 total_space += ONLY_AUTHORITY_LEN;
353 }
354 if add_group_pointer {
355 total_space += AUTHORITY_POINTER_LEN;
356 }
357 if add_group_member_pointer {
358 total_space += AUTHORITY_POINTER_LEN;
359 }
360 if add_metadata_pointer {
361 total_space += AUTHORITY_POINTER_LEN;
362 }
363 if non_transferable {
364 total_space += EMPTY_EXTENSION_LEN;
365 }
366 if add_permanent_delegate {
367 total_space += ONLY_AUTHORITY_LEN;
368 }
369 if add_transfer_fee_config {
370 total_space += {
371 EMPTY_EXTENSION_LEN // type + length
372 + size_of::<Pubkey>() // authority
373 + size_of::<Pubkey>() // withdraw_withheld_authority
374 + size_of::<u64>() // withheld_amount
375 + size_of::<u64>() // older_transfer_fee_epoch
376 + size_of::<u64>() // older_transfer_fee_maximum_fee
377 + size_of::<u16>() // older_basis_points
378 + size_of::<u64>() // newer_transfer_fee_epoch
379 + size_of::<u64>() // newer_transfer_fee_maximum_fee
380 + size_of::<u16>() // newer_basis_points
381 };
382 }
383 if add_transfer_hook {
384 total_space += AUTHORITY_POINTER_LEN;
385 }
386 if add_confidential_transfer {
387 total_space += {
388 EMPTY_EXTENSION_LEN // type + length
389 + size_of::<Pubkey>() // authority
390 + size_of::<u8>() // auto_approve_new_accounts
391 + size_of::<Pubkey>() // auditor_elgamal
392 };
393 }
394 if add_confidential_transfer_fee_config {
395 total_space += {
396 EMPTY_EXTENSION_LEN // type + length
397 + size_of::<Pubkey>() // authority
398 + size_of::<[u8; 32]>() // withdraw_withheld_authority_elgamal
399 + size_of::<bool>() // harvest_to_mint_enabled
400 + 64 // withheld_amount (encrypted)
401 };
402 }
403 // FIXME: Uncomment when the extension is implemented.
404 // if add_confidential_mint_burn {
405 // total_space += {
406 // EMPTY_EXTENSION_LEN // type + length
407 // + 64 // confidential_supply
408 // + 36 // decryptable_supply
409 // + 32 // supply_elgamal
410 // };
411 // }
412
413 // First create the mint account by assigning it to the token program.
414 let mint_account = CreateAccount {
415 payer,
416 to: mint,
417 program_id: token_program_id,
418 space: Some(total_space),
419 lamports: None,
420 }
421 .try_into_invoke()?;
422
423 if let Some(close_authority) = close_authority {
424 super::extensions::InitializeMintCloseAuthority {
425 token_program_id,
426 mint: &mint_account,
427 authority: Some(close_authority),
428 }
429 .into_invoke();
430 }
431
432 if let Some(InitializeGroupPointerData { authority, group }) = group_pointer {
433 super::extensions::InitializeGroupPointer {
434 token_program_id,
435 mint: &mint_account,
436 authority,
437 group: Some(group),
438 }
439 .into_invoke();
440 }
441
442 if let Some(InitializeGroupMemberPointerData {
443 authority,
444 group_member,
445 }) = group_member_pointer
446 {
447 super::extensions::InitializeGroupMemberPointer {
448 token_program_id,
449 mint: &mint_account,
450 authority,
451 group_member: Some(group_member),
452 }
453 .into_invoke();
454 }
455
456 if let Some(InitializeMetadataPointerData {
457 authority,
458 metadata,
459 }) = metadata_pointer
460 {
461 super::extensions::InitializeMetadataPointer {
462 token_program_id,
463 mint: &mint_account,
464 authority,
465 metadata: Some(metadata),
466 }
467 .into_invoke();
468 }
469
470 if non_transferable {
471 super::extensions::InitializeNonTransferable {
472 token_program_id,
473 mint: &mint_account,
474 }
475 .into_invoke();
476 }
477
478 if let Some(delegate) = permanent_delegate {
479 super::extensions::InitializePermanentDelegate {
480 token_program_id,
481 mint: &mint_account,
482 delegate,
483 }
484 .into_invoke();
485 }
486
487 if let Some(InitializeTransferFeeConfigData {
488 config_authority,
489 withdraw_withheld_authority,
490 basis_points,
491 maximum_fee,
492 }) = transfer_fee_config
493 {
494 super::extensions::InitializeTransferFeeConfig {
495 token_program_id,
496 mint: &mint_account,
497 config_authority,
498 withdraw_withheld_authority,
499 basis_points,
500 maximum_fee,
501 }
502 .into_invoke();
503 }
504
505 if let Some(InitializeTransferHookData {
506 authority,
507 program_id,
508 }) = transfer_hook
509 {
510 super::extensions::InitializeTransferHook {
511 token_program_id,
512 mint: &mint_account,
513 authority,
514 program_id: Some(program_id),
515 }
516 .into_invoke();
517 }
518
519 if let Some(InitializeConfidentialTransferData {
520 authority,
521 auto_approve_new_accounts,
522 auditor_elgamal,
523 }) = confidential_transfer
524 {
525 super::extensions::InitializeConfidentialTransfer {
526 token_program_id,
527 mint: &mint_account,
528 authority,
529 auto_approve_new_accounts,
530 auditor_elgamal,
531 }
532 .into_invoke();
533 }
534
535 if let Some(InitializeConfidentialTransferFeeConfigData {
536 authority,
537 withdraw_withheld_authority_elgamal,
538 }) = confidential_transfer_fee_config
539 {
540 super::extensions::InitializeConfidentialTransferFeeConfig {
541 token_program_id,
542 mint: &mint_account,
543 authority,
544 withdraw_withheld_authority_elgamal,
545 }
546 .into_invoke();
547 }
548
549 // FIXME: Uncomment when the extension is implemented.
550 // if let Some(InitializeConfidentialMintBurnData {
551 // supply_elgamal,
552 // decryptable_supply,
553 // }) = confidential_mint_burn
554 // {
555 // super::extensions::InitializeConfidentialMintBurn {
556 // token_program_id,
557 // mint: &mint_account,
558 // supply_elgamal,
559 // decryptable_supply,
560 // }
561 // .into_invoke();
562 // }
563
564 mint_account
565 } else {
566 if !is_any_token_program_id(token_program_id) {
567 return Err(super::ERROR_EXPECTED_TOKEN_PROGRAM.into());
568 }
569
570 // First create the mint account by assigning it to the token program.
571 CreateAccount {
572 payer,
573 to: mint,
574 program_id: token_program_id,
575 space: Some(Mint::LEN),
576 lamports: None,
577 }
578 .try_into_invoke()?
579 };
580
581 _invoke_initialize_mint2(
582 token_program_id,
583 &mint_account,
584 mint_authority,
585 freeze_authority,
586 decimals,
587 );
588
589 Ok(mint_account)
590 }
591}
592
593/// Arguments for the initialize mint instruction (version 2), which initializes a mint account for
594/// one of the Token programs. Only use this instruction if you have already created the mint
595/// account via the System program.
596///
597/// ### Notes
598///
599/// It is preferred to use [CreateMint] instead of initializing a mint by itself because the other
600/// method will create the account and initialize it as a mint in one action.
601#[derive(Clone, PartialEq, Eq)]
602pub struct InitializeMint<'a> {
603 pub token_program_id: &'a Pubkey,
604 pub mint: &'a NoStdAccountInfo,
605 pub mint_authority: &'a Pubkey,
606 pub freeze_authority: Option<&'a Pubkey>,
607 pub decimals: u8,
608}
609
610impl<'a> InitializeMint<'a> {
611 /// Consume arguments to perform CPI call.
612 #[inline(always)]
613 pub fn into_invoke(self) {
614 let Self {
615 token_program_id,
616 mint,
617 mint_authority,
618 freeze_authority,
619 decimals,
620 } = self;
621
622 _invoke_initialize_mint2(
623 token_program_id,
624 mint,
625 mint_authority,
626 freeze_authority,
627 decimals,
628 );
629 }
630}
631
632#[inline(always)]
633fn _invoke_initialize_mint2(
634 token_program_id: &Pubkey,
635 mint: &NoStdAccountInfo,
636 mint_authority: &Pubkey,
637 freeze_authority: Option<&Pubkey>,
638 decimals: u8,
639) {
640 const IX_DATA_LEN: usize = {
641 size_of::<u8>() // selector
642 + size_of::<u8>() // decimals
643 + size_of::<Pubkey>() // mint_authority
644 + size_of::<u8>() + size_of::<Pubkey>() // freeze_authority
645 };
646
647 let mut instruction_data = [0; IX_DATA_LEN];
648
649 // Initialize mint 2 selector == 20.
650 instruction_data[0] = 20;
651 instruction_data[1] = decimals;
652 instruction_data[2..34].copy_from_slice(&mint_authority.to_bytes());
653
654 if let Some(freeze_authority) = freeze_authority {
655 instruction_data[34] = 1;
656 instruction_data[35..67].copy_from_slice(&freeze_authority.to_bytes());
657 }
658
659 CpiInstruction {
660 program_id: token_program_id,
661 accounts: &[mint.to_meta_c()],
662 data: &instruction_data,
663 }
664 .invoke_signed(&[mint.to_info_c()], &[]);
665}