Solana minting
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:
- Allocate space for a new mint account through the System Program
- Transfer ownership to the Token Program
Token-2022
invoke
Token-2022
to initialize the mint account
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:
- a specific Mint
- a specific owner (authority over the account)
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:
- Not be able to mint tokens through the AMM, only you would be able to, manually. If it's a fixed supply token, this might be okay, but if you want to mint tokens dynamically (incentives, farming, elastic supply etc.) you can't.
- Undermine the decentralized and predictable nature of AMMs. You would be able to rug pull at any time, either purposefully or if you'd been compromised.
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.