Skip to main content

Token transfer

Outline

It is a necessary operation to transfer tokens between accounts. Use transfer instruction of token program to perform transfer operation.

The final outcome of this chapter could be found here

How to transfer a token to other account?

So far, we have created a mint and minted it to an account. But how to transfer the mint to other accounts?

It also involves the same process. Create a TransferTokenToAnother context and create an instruction transfer_token_to_another to achieve the end goal.

How to create a TransferTokenToAnother context?

Below is the struct that we define for transferring a spl_token_mint token to other account.

// Transfer token to another account
#[derive(Accounts)]
pub struct TransferTokenToAnother<'info> {
    #[account(
         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(
        mut,
        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

    #[account(
        init,
        payer = payer,
        associated_token::mint = spl_token_mint,
        associated_token::authority = another_account
    )]
    pub another_mint_ata: Box<Account<'info, TokenAccount>>, // --> 9

    /// CHECK : We just pass the account info for the demonstration purpose. Ideally this is either signer or trusted account
    pub another_account: AccountInfo<'info>, // ---> 10
}
  1. We pass the spl_token_mint account without any mut or init decoration.
  2. We pass the vault. This can be used for security purpose.
  3. payer_mint_ata account from which we are transferring token to another ata
  4. payer signer who is transferring the token
  5. system_program account for executing the instruction.
  6. token_program account used for performing transfer operation
  7. init decorator uses rent account for creating account
  8. We are creating a new ata account. Hence we pass associated_token_program
  9. another_mint_ata account to which we transfer the token.
  10. another_account account is the owner of another_mint_ata

Create a transfer_token_to_another instruction for transferring a token.

    pub fn transfer_token_to_another(ctx : Context<TransferTokenToAnother>) -> Result<()> {
        let cpi_context = CpiContext::new(
            ctx.accounts.token_program.to_account_info(),
            token::Transfer {
                from : ctx.accounts.payer_mint_ata.to_account_info(),
                to : ctx.accounts.another_mint_ata.to_account_info(),
                authority : ctx.accounts.payer.to_account_info()
            },
        );
        token::transfer(cpi_context, 1)?;
        Ok(())
    }

Time to test the transfer_token_to_another instruction.

Let us first create anotherWallet and add it to the root of the test case.

const anotherWallet = anchor.web3.Keypair.generate(); // newly created another wallet

And add some sols to the wallet using addSols function in before function block.

before("Add sols to wallet ", async () => {
  await addSols(provider, payer.publicKey);
  await addSols(provider, anotherWallet.publicKey); // add sols to another wallet
});

We will test case in spl-token.ts. Add the following in your test file.

it("should transfer 1 token from payer_mint_ata to another_mint_ata", async () => {
  try {
    const [splTokenMint, _1] = await findSplTokenMintAddress();

    const [vaultMint, _2] = await findVaultAddress();

    const [payerMintAta, _3] = await findAssociatedTokenAccount(
      payer.publicKey,
      splTokenMint
    );

    const [anotherMintAta, _4] = await findAssociatedTokenAccount(
      anotherWallet.publicKey,
      splTokenMint
    );

    const tx = await program.methods
      .transferTokenToAnother()
      .accounts({
        splTokenMint: splTokenMint,
        vault: vaultMint,
        associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
        tokenProgram: TOKEN_PROGRAM_ID,
        systemProgram: SystemProgram.programId,
        payerMintAta: payerMintAta,
        payer: payer.publicKey,
        anotherMintAta: anotherMintAta,
        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
        anotherAccount: anotherWallet.publicKey,
      })
      .signers([payer])
      .rpc();

    console.log("Your transaction signature", tx);
  } catch (err) {
    console.log(err);
  }
});

Run the command

anchor test

Then the output should be a success as shown below.

So, far we have understood how minting and transfer work. In the next sections we will discuss about freezing a token account.