Skip to main content

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

  1. Any public address +
  2. A mint address +
  3. Token program ID address +
  4. 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

  1. We have used the same spl_token_mint account. However, we are not instantiating this time. We use the spl_token_mint_bump that was stored in Vault state previously.
  2. We get the vault account again by using stored bump from the Vault state
  3. This time, we are passing an ATA for minting the spl_token_mint into the payer_mint_ata account. We are setting associated_token::mint to spl_token_mint and associated_token::authority to payer account.
  4. We are passing the payer account from which the program deducts payment
  5. token_program account is same as before
  6. system_program account is same as described in the previously
  7. rent account is same as previously. Here we are passing rent for creating associated token account
  8. 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.