Skip to main content

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}