Anchor
Token2022 with Anchor

Token2022 with Anchor

The Memo Transfer Extension

The MemoTranfer Extension is a Token account extension that enforces that all incoming transfers to a token account include a memo, facilitating enhanced transaction tracking and user identification.

Initializing the Token Account

Since Anchor doesn't have any macros for the memo_transfer extension we're going to create a Token account using the raw CPIs.

Here's how to create a token with the Memo Transfer extension:

use anchor_lang::prelude::*;
use anchor_lang::system_program::{create_account, CreateAccount};
use anchor_spl::{
    token_2022::{
        initialize_account3,
        spl_token_2022::{extension::ExtensionType, pod::PodAccount},
        InitializeAccount3,
    },
    token_interface::{memo_transfer_initialize, MemoTransfer, Mint, Token2022},
};
 
// There is currently not an anchor constraint to automatically initialize the MemoTransfer extension
// We can manually create and initialize the token account via CPIs in the instruction handler
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    // Calculate space required for token and extension data
    let token_account_size =
        ExtensionType::try_calculate_account_len::<PodAccount>(&[ExtensionType::MemoTransfer])?;
 
    // Calculate minimum lamports required for size of token account with extensions
    let lamports = (Rent::get()?).minimum_balance(token_account_size);
 
    // Invoke System Program to create new account with space for token account and extension data
    create_account(
        CpiContext::new(
            ctx.accounts.system_program.to_account_info(),
            CreateAccount {
                from: ctx.accounts.payer.to_account_info(),
                to: ctx.accounts.token_account.to_account_info(),
            },
        ),
        lamports,                          // Lamports
        token_account_size as u64,         // Space
        &ctx.accounts.token_program.key(), // Owner Program
    )?;
 
    // Initialize the standard token account data
    initialize_account3(CpiContext::new(
        ctx.accounts.token_program.to_account_info(),
        InitializeAccount3 {
            account: ctx.accounts.token_account.to_account_info(),
            mint: ctx.accounts.mint_account.to_account_info(),
            authority: ctx.accounts.payer.to_account_info(),
        },
    ))?;
 
    // Initialize the memo transfer extension
    // This instruction must come after the token account initialization
    memo_transfer_initialize(CpiContext::new(
        ctx.accounts.token_program.to_account_info(),
        MemoTransfer {
            token_program_id: ctx.accounts.token_program.to_account_info(),
            account: ctx.accounts.token_account.to_account_info(),
            owner: ctx.accounts.payer.to_account_info(),
        },
    ))?;
 
    Ok(())
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
 
    #[account(mut)]
    pub token_account: Signer<'info>,
    pub mint_account: InterfaceAccount<'info, Mint>,
    pub token_program: Program<'info, Token2022>,
    pub system_program: Program<'info, System>,
}

Transferring Token with a Memo

To use the Memo program on Solana we have to viable path.

Building our own "raw" memo instruction like this:

const message = "Hello, Solana";
 
new TransactionInstruction({
    keys: [{ pubkey: keypair.publicKey, isSigner: true, isWritable: true }],
    data: Buffer.from(message, "utf-8"), // Memo message. In this case it is "Hello, Solana"
    programId: new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), // Memo program that validates keys and memo message
}),
 
Or we can use the Memo program SDK after downloading the package like this: 
 

npm i @solana/spl-memo


> After creating the memo, the next instruction will be a simple transfer instruction.

In our example we're going to use the second option and it will look like this:

```ts
const memoInstruction = createMemoInstruction(
    "Hello, world!",
    [keypair.publicKey],
);

//...anchor instruction with the transfer instruction

const transferTransaction = new Transaction().add(
    memoInstruction,
    transferInstruction,
);

const transferSignature = await sendAndConfirmTransaction(connection, transferTransaction, [keypair]);

console.log(`Tokens transferred with memo! Check out your TX here: https://explorer.solana.com/tx/${transferSignature}?cluster=devnet`);

Disabling and Enabling the Memo

If we don't want to enforce the transfer to come with a memo, we can do so using the memo_transfer_disable function and it would look like so:

use anchor_lang::prelude::*;
use anchor_spl::token_interface::{memo_transfer_disable, MemoTransfer, Token, Mint, Token2022};
 
#[derive(Accounts)]
pub struct DisableMemo<'info> {
    #[account(mut)]
    pub owner: Signer<'info>,
 
    #[account(
        mut,
        token::authority = owner,
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Program<'info, Token2022>,
}
 
pub fn disable_memo(
    ctx: Context<DisableMemo>,
) -> Result<()> {
    memo_transfer_disable(CpiContext::new(
        ctx.accounts.token_program.to_account_info(),
        MemoTransfer {
            token_program_id: ctx.accounts.token_program.to_account_info(),
            account: ctx.accounts.token_account.to_account_info(),
            owner: ctx.accounts.owner.to_account_info(),
        },
    ))?;
    
    Ok(())
}
Contents
View Source
Blueshift © 2025Commit: 02aab65