Soldev

Solana minting

Last updated:

Mints are a type of account that represents a token. It tracks general information about the token, such as the supply of tokens, and the number of decimal precision that the token has.

Initializing a Mint is also where you specify the mint_authority. The mint authority works alongside a token_account, the authority mints new tokens, and the token account holds them.

There are other options such as the freeze_authority which is an account that can freeze token accounts.

The first steps of creating a new mint and minting new tokens is roughly as follows:

Here's a snippet of what this looks like in the JS getCreateAccountInstruction:

// Generate keypair to use as address of mint
const mint = await generateKeyPairSigner();

// Instruction to create new account for mint (token 2022 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
  payer: feePayer,
  newAccount: mint,
  lamports: rent,
  space,
  programAddress: TOKEN_2022_PROGRAM_ADDRESS,
});

Token accounts

In order for people to own your tokens, they'll need to open up specific token_accounts that hold your currency.

Token accounts are for:

Creating basic token accounts

Associated Token Accounts

Similar to token accounts, but sits at a Program Derived Address, which was created by the Associated Token Program. It's the default account.

Creating associated token accounts

Creating a Token Mint

Creating a Token/Mint is a pre-requisite to building an amm as the automated market maker will be working off of this token and providing pools for transfer between it and other tokens.

Here's the CreateTokenMint struct used in the anchor example found in the docs:

#[derive(Accounts)]
#[instruction(_token_decimals: u8)]
pub struct CreateTokenMint<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    /// CHECK: Validate address by deriving pda
    #[account(
        mut,
        seeds = [b"metadata", token_metadata_program.key().as_ref(), mint_account.key().as_ref()],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    pub metadata_account: UncheckedAccount<'info>,
    // Create new mint account
    #[account(
        init,
        payer = payer,
        mint::decimals = _token_decimals,
        mint::authority = payer.key(),
    )]
    pub mint_account: Account<'info, Mint>,

    pub token_metadata_program: Program<'info, Metadata>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
}

Creating a Token is really creating the mint_account through the macro with init alongside setting the decimals, payer and authority. This mint_account will manage the Token.

We can see how the ownership and authority pans out in the function:

#![allow(clippy::result_large_err)]

use {
    anchor_lang::prelude::*,
    anchor_spl::{
        metadata::{
            create_metadata_accounts_v3, mpl_token_metadata::types::DataV2,
            CreateMetadataAccountsV3, Metadata,
        },
        token::{Mint, Token},
    },
};

declare_id!("GwvQ53QTu1xz3XXYfG5m5jEqwhMBvVBudPS8TUuFYnhT");

#[program]
pub mod create_token {
    use super::*;

    pub fn create_token_mint(
        ctx: Context<CreateTokenMint>,
        _token_decimals: u8,
        token_name: String,
        token_symbol: String,
        token_uri: String,
    ) -> Result<()> {
        msg!("Creating metadata account...");
        msg!(
            "Metadata account address: {}",
            &ctx.accounts.metadata_account.key()
        );

        // Cross Program Invocation (CPI)
        // Invoking the create_metadata_account_v3 instruction on the token metadata program
        create_metadata_accounts_v3(
            CpiContext::new(
                ctx.accounts.token_metadata_program.to_account_info(),
                CreateMetadataAccountsV3 {
                    metadata: ctx.accounts.metadata_account.to_account_info(),
                    mint: ctx.accounts.mint_account.to_account_info(),
                    mint_authority: ctx.accounts.payer.to_account_info(),
                    update_authority: ctx.accounts.payer.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
            ),
            DataV2 {
                name: token_name,
                symbol: token_symbol,
                uri: token_uri,
                seller_fee_basis_points: 0,
                creators: None,
                collection: None,
                uses: None,
            },
            false, // Is mutable
            true,  // Update authority is signer
            None,  // Collection details
        )?;

        msg!("Token mint created successfully.");

        Ok(())
    }
}

We create a metadata_account for the newly initialized mint here.

Also of note, is that the mint_authority and update_authority are set to the payer, which makes sense.

If building an AMM, the payer should be the same as the AMM program's PDA you'll use to manage this token. We'll want to manage minting new tokens and changing metadata through the same PDA.

If we haphazardly made the payer in this example your personal wallet, rather than the PDA that will also manage the AMM, you would:

To sum up, the AMM's PDA needs to control the mint_authority if we want automated minting and a trustworthy setup.

Minting tokens

Using the MintTo instruction on a token program is the ratchet to create new tokens.

Only the mint authority address can mint new tokens, and it requires a token account to place the newly minted tokens into.

Read more