Hello! Welcome back to our module on working with Solana tokens.
In our previous lessons, we've laid all the necessary groundwork. We learned how to:
- Create a token mint, which acts as the blueprint for our new token.
- Create an Associated Token Account (ATA), which gives a user a dedicated place to hold that token.
Now, we've reached the most rewarding part: putting tokens into circulation. The goal for this lesson is to implement the program logic to mint new tokens to a token account. This is the process of creating tokens out of thin air and depositing them into a user's account, a fundamental action for everything from gaming rewards to decentralized finance (DeFi) protocols.
We will achieve this by performing another Cross-Program Invocation (CPI), this time calling the mint_to instruction on the SPL Token Program.
The Authority to Mint
Before we write any code, let's address the most important question: who is allowed to mint new tokens?
When we created our token mint in a previous lesson, we assigned its mint_authority to a Program Derived Address (PDA) owned by our program. This was a deliberate and crucial design choice. It means that only our program can authorize the creation of new tokens. No outside user or program can do it.
How does our program prove it has this authority? When we make a CPI to the SPL Token Program's mint_to instruction, our program must "sign" the transaction on behalf of its PDA. This special type of signature is achieved by providing the seeds and bump used to derive the PDA, which only our program knows. Anchor makes this process straightforward, as we'll see.
Minting Tokens with an Anchor CPI
Let's explore a practical example. We'll look at a simple game program where defeating an enemy rewards the player with one token. This is a perfect, self-contained use case for minting.
The following guide from the official Solana documentation walks through this exact scenario.
How to interact with tokens in programs
Please read the following guide, 'How to interact with tokens in programs'. It contains an excellent example of a token-minting instruction called kill_enemy.
Focus on the section titled 'Kill Enemy Instruction'. Read through the description of the required accounts and study the Rust code for both the kill_enemy function and the KillEnemy Accounts struct.
Let's break down the key components from that example.
1. The KillEnemy Accounts Struct
The #[derive(Accounts)] struct defines all the accounts our instruction needs to perform the mint.
#[derive(Accounts)]
pub struct KillEnemy<'info> {
#[account(mut)]
pub player: Signer<'info>, // The payer for ATA creation if needed
// ... other accounts ...
// The destination for the new tokens.
// `init_if_needed` is used here just like in our last lesson.
#[account(
init_if_needed,
payer = player,
associated_token::mint = reward_token_mint,
associated_token::authority = player
)]
pub player_token_account: Account<'info, TokenAccount>,
// The mint account of the token we are minting.
// It must be mutable because its `supply` field will be updated.
#[account(
mut,
seeds = [b"reward"], // The PDA seeds for our mint account
bump,
)]
pub reward_token_mint: Account<'info, Mint>,
// The SPL Token program, which we will be calling via CPI.
pub token_program: Program<'info, Token>,
// ... other programs ...
}
Key points here:
reward_token_mint: This is the mint account we created previously. It's markedmutbecause themint_toinstruction will increase its total supply. Its PDA derivation is specified withseedsandbump.player_token_account: This is the user's ATA where the newly minted tokens will be deposited. It is alsomutbecause its balance will increase.token_program: We must include theTokenprogram itself, as it's the target of our CPI.
2. The kill_enemy Instruction Logic
The function body contains the logic for making the CPI.
pub fn kill_enemy(ctx: Context<KillEnemy>) -> Result<()> {
// ... (health subtraction logic) ...
// 1. Define the seeds for the PDA that is the mint authority.
let seeds = b"reward";
let bump = *ctx.bumps.get("reward_token_mint").unwrap();
let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]];
// 2. Create the CpiContext for the `mint_to` instruction.
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(), // Target program
MintTo { // Instruction accounts
mint: ctx.accounts.reward_token_mint.to_account_info(),
to: ctx.accounts.player_token_account.to_account_info(),
authority: ctx.accounts.reward_token_mint.to_account_info(), // The PDA is the authority
},
signer, // The PDA's "signature"
);
// 3. Handle token decimals.
let amount = (1u64)
.checked_mul(10u64.pow(ctx.accounts.reward_token_mint.decimals as u32))
.unwrap();
// 4. Execute the CPI.
mint_to(cpi_ctx, amount)?;
Ok(())
}
This sequence is a fundamental pattern in Anchor:
- Prepare the Signer: We assemble the
seedsandbumpinto asignerslice. This is how our program will sign on behalf of thereward_token_mintPDA. - Build the
CpiContext: We useCpiContext::new_with_signerbecause a PDA is signing. We pass it:- The
token_programwe are calling. - A
MintTostruct, which is part ofanchor_spl::tokenand maps directly to the accounts required by the SPL Token program'smint_toinstruction. Theauthorityis ourreward_token_mintaccount itself, which is the PDA. - The
signerseeds we just prepared.
- The
- Calculate the Amount: This is a subtle but critical step. SPL tokens have a
decimalsproperty. A token with 9 decimals means that 1 whole token is represented by the integer1,000,000,000. The code correctly calculates the raw integer amount to mint 1 whole token by accounting for these decimals. - Invoke
mint_to: Finally, we call themint_tofunction from theanchor_spl::tokencrate, passing our context and the amount. Anchor handles theinvoke_signedcall under the hood.
Test your understanding!
In the MintTo struct within the CPI context, the authority is specified as ctx.accounts.reward_token_mint.to_account_info(). We know this account is a PDA.
What would happen if you tried to set the authority to ctx.accounts.player.to_account_info() instead, thinking the player should authorize the minting to themselves?
Show answer
The transaction would fail with an authority error (specifically, ConstraintMintAuthority).
The SPL Token program doesn't care who is receiving the tokens. It only cares about who is authorizing the creation of new tokens. It checks the signatures passed to the instruction (player's signature, in this hypothetical) against the mint_authority field stored on the mint account data. Since the mint account's authority is the PDA (reward_token_mint), and not the player, the signature check fails, and the CPI is rejected.
Generalizing the Pattern
The pattern of using CpiContext::new_with_signer to call mint_to is not specific to gaming. It's used in any scenario where a program needs to distribute tokens, such as distributing staking rewards.
The following resource provides another concise example of this same pattern.
A Developer's Guide to Solana's CPI Patterns
To see how versatile this pattern is, let's look at another example from 'A Developer's Guide to Solana's CPI Patterns'. This shows the same mint_to CPI but in the context of a DeFi staking protocol.
Quickly review the code snippet under 'The Staking Rewards Pattern'. Notice the nearly identical structure: a CpiContext::new_with_signer is created, it's passed to token::mint_to, and a PDA's seeds are used for the signer.
This reinforces that once you understand the core mint_to CPI pattern, you can apply it across many different types of Solana programs.
Conclusion
Congratulations! You have now learned how to complete the initial lifecycle of a token: creating the mint, creating an account to hold it, and now, minting tokens into that account.
Here are the key takeaways from this lesson:
- Minting new SPL tokens is done via a CPI to the
mint_toinstruction of the SPL Token Program. - To maintain control, the
mint_authorityof the token mint should be a PDA owned by your program. - In Anchor, you use
CpiContext::new_with_signerto callmint_to, providing the PDA's seeds as thesigner. This allows your program to authorize the mint. - The
MintTostruct fromanchor_spl::tokenprovides a type-safe way to specify the accounts for the CPI: themint, the destinationtoaccount, and theauthorityPDA. - Always remember to account for the token's
decimalswhen specifying the amount to mint.
We've focused on bringing tokens into existence and giving them to a user. In our next and final lesson of this module, we will learn how to enable the movement of these tokens between users by implementing a token transfer instruction.
Can't find a good explanation? Sign up and we'll make it for you
Sign up