Transfer Mint
Outline
We will understand how to transfer a newly created mint in the previous chapter. The final outcome can be found here
To transfer a new mint to an account, use mint_to
instruction from token
program.
How to transfer the Minted token to an account?
Before transferring an spl_token_mint
to an account, it is time to understand about Associated Token Account (ATA).
The spl_token_mint
can only be transferred to ATA.
- ATA is normally generated off-chain.
- ATA is PDA. ATA is generated by finding a suitable PDA
- On the client side, one could generate a ATA as follows
Following code is an example ATA generation using PublicKey.findProgramAddress
in @solana/web3.js
library.
Add find ATA to test file
Add the following in the spl-token.ts
test file.
import {PublicKey} from "@solana/web3.js"
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
export const findAssociatedTokenAccount = async (
payerKey: PublicKey,
mintKey: PublicKey
) => {
return await PublicKey.findProgramAddress(
[
payerKey.toBuffer(), // could be any public key
TOKEN_PROGRAM_ID.toBuffer(),
mintKey.toBuffer(),
],
ASSOCIATED_TOKEN_PROGRAM_ID
);
};
As we can see above, ATA is generated using
- Any public address +
- A mint address +
- Token program ID address +
- Associated Token Program ID.
You should pass ATA to an instruction to transfer a mint. Again, In the program, use mint_to
instruction to transfer a newly created mint.
Now, back in the rust lib.rs
file, we will write a transfer
context and then we will write an instruction to transfer the spl-token-mint` to an ATA.
How to create transfer
context?
We create another struct
called TransferMint
context for transfer_mint
instruction.
First update the imports from token
library
use anchor_spl::{token::{self, Mint, Token, TokenAccount}, associated_token::AssociatedToken};
Next create the TransferMint
context.
// Transfer mint context
#[derive(Accounts)]
pub struct TransferMint<'info> {
#[account(
mut,
seeds = [
b"spl-token-mint".as_ref(),
],
bump = vault.spl_token_mint_bump,
)]
pub spl_token_mint: Account<'info, Mint>, // ---> 1
#[account(
seeds = [
b"vault"
],
bump = vault.bump, // --> 2
)]
pub vault : Account<'info, Vault>,
#[account(
init,
payer = payer,
associated_token::mint = spl_token_mint,
associated_token::authority = payer
)]
pub payer_mint_ata: Box<Account<'info, TokenAccount>>, // --> 3
#[account(mut)]
pub payer: Signer<'info>, // ---> 4
pub system_program: Program<'info, System>, // ---> 5
pub token_program: Program<'info, Token>, // ---> 6
pub rent: Sysvar<'info, Rent>, // ---> 7
pub associated_token_program : Program<'info, AssociatedToken> // ---> 8
}
So here is what's happening the TransferMint
context
- We have used the same
spl_token_mint
account. However, we are not instantiating this time. We use thespl_token_mint_bump
that was stored inVault
state previously. - We get the
vault
account again by using storedbump
from theVault
state - This time, we are passing an ATA for minting the
spl_token_mint
into thepayer_mint_ata
account. We are settingassociated_token::mint
tospl_token_mint
andassociated_token::authority
topayer
account. - We are passing the
payer
account from which the program deducts payment token_program
account is same as beforesystem_program
account is same as described in the previouslyrent
account is same as previously. Here we are passing rent for creatingassociated token account
associated_token_program
account is passed for creating ATA.
We will now create an instruction transfer_mint
to transfer the spl_token_mint
into an ATA.
We use the mint_to
instruction from token_program
. We will call the instruction via cross program invocation (CPI) to another program. Learn more about this here
pub fn transfer_mint(ctx : Context<TransferMint>) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::MintTo {
mint: ctx.accounts.spl_token_mint.to_account_info(),
to: ctx.accounts.payer_mint_ata.to_account_info(),
authority: ctx.accounts.payer.to_account_info(),
},
);
token::mint_to(cpi_context, 10)?; // we are minting 10 tokens
Ok(())
}
With this, the spl_token_mint
is minted into the payer_mint_ata
account.
Let us test this out by writing another test case in spl-token.ts
file. Let's update the file like it shown below.
// add this block into the describe block of the test file
it("should mint the spl-token-mint to payer_mint_ata", async () => {
const [splTokenMint, _1] = await findSplTokenMintAddress();
const [vaultMint, _2] = await findVaultAddress();
const [payerMintAta, _3] = await findAssociatedTokenAccount(
payer.publicKey,
splTokenMint
);
const tx = await program.methods
.transferMint()
.accounts({
splTokenMint: splTokenMint,
vault: vaultMint,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
payerMintAta: payerMintAta,
payer: payer.publicKey,
})
.signers([payer])
.rpc();
console.log("Your transaction signature", tx);
});
Run the command
anchor test
After this, you should be able to see the result like below.
With this, we have understood how to use mint_to
to transfer new mint. In the next chapter we will discuss on how to transfer tokens between accounts.