0

In order to send nft to my contract then it can transfer to other accounts, I create an associated account from programId and transfer to that ata. Then how can I transfer NFT from my contract to other accounts?

        let tokenAccountOfProgram = await createAssociatedTokenAccount(
            provider.connection,
            owner,
            minter,
            program.programId
        )

        await program.methods
            .createMarketItem(...)
            .accounts({
                // systemProgram: SystemProgram.programId,
                stateAccount: state_pda.publicKey,
                to: tokenAccountOfProgram,
                tokenProgram: TOKEN_PROGRAM_ID,
                user: owner,
                from: tokenAccount,
                authProgram: program.programId
            }).signers([owner]).rpc();

This is the code for transfering and its context:

        let transfer_instruction = Transfer {
            from: ctx.accounts.from.to_account_info(),
            to: ctx.accounts.to.to_account_info(),
            authority: ctx.accounts.user.to_account_info(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_ctx = CpiContext::new(cpi_program, transfer_instruction);
        anchor_spl::token::transfer(cpi_ctx, 1)?;

#[derive(Accounts)]
pub struct CreateMarketItem<'info> {
    #[account(mut)]
    pub user: Signer<'info>, // seller
    #[account(mut)]
    pub state_account: Account<'info, State>,
    /// CHECK:
    #[account(mut)]
    pub to: AccountInfo<'info>,

    /// CHECK:
    #[account(mut)]
    pub from: AccountInfo<'info>,
    pub token_program: Program<'info, Token>,
    /// CHECK:
    #[account(mut)]
    pub auth_program: AccountInfo<'info>,
}

And this is the code I use for transferring back:

    pub fn transfer_back(
        ctx: Context<CreateMarketItem>,
    ) -> Result<()> {
        let transfer_instruction = Transfer {
            from: ctx.accounts.to.to_account_info(),
            to: ctx.accounts.from.to_account_info(),
            authority: ctx.accounts.auth_program.to_account_info(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_ctx = CpiContext::new(cpi_program, transfer_instruction);
        anchor_spl::token::transfer(cpi_ctx, 1)?;
        Ok(())
    }

// code on client
await program.methods
            .transferBack()
            .accounts({
                // systemProgram: SystemProgram.programId,
                stateAccount: state_pda.publicKey,
                to: tokenAccountOfProgram,
                tokenProgram: TOKEN_PROGRAM_ID,
                user: owner,
                from: tokenAccount,
                authProgram: program.programId
            }).signers([owner]).rpc();

It return "Error: AnchorError caused by account: auth_program. Error Code: ConstraintMut. Error Number: 2000. Error Message: A mut constraint was violated.". How can I fix this?

1 Answer 1

2

To transfer an SPL token from a Token account controlled by a PDA of your program, the PDA will need to sign the transfer instruction.

In the example below, auth represents the PDA with authority over the token account.

    #[account(
        seeds = ["auth".as_bytes().as_ref()],
        bump,
    )]
    pub auth: UncheckedAccount<'info>,

And is used to sign the transfer instruction, note to use CpiContext::new_with_signer

pub fn transfer_handler(ctx: Context<TokenTransfer>) -> Result<()> {
    let seeds = &["auth".as_bytes(), &[*ctx.bumps.get("auth").unwrap()]];
    let signer = [&seeds[..]];

    let cpi_ctx = CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        token::Transfer {
            from: ctx.accounts.from_token_account.to_account_info(),
            to: ctx.accounts.to_token_account.to_account_info(),
            authority: ctx.accounts.auth.to_account_info(),
        },
        &signer,
    );

    token::transfer(cpi_ctx, 1)?;
    Ok(())
}

Here is reference:

use anchor_lang::prelude::*;
use anchor_spl::{
    associated_token::AssociatedToken,
    token::{self, Mint, Token, TokenAccount},
};

#[derive(Accounts)]
pub struct TokenTransfer<'info> {
    /// CHECK: token account authority PDA
    #[account(
        seeds = ["auth".as_bytes().as_ref()],
        bump,
    )]
    pub auth: UncheckedAccount<'info>,
    #[account(
        mut,
        token::mint = mint,
        token::authority = auth
    )]
    pub from_token_account: Account<'info, TokenAccount>,
    #[account(
        init_if_needed,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer
    )]
    pub to_token_account: Account<'info, TokenAccount>,
    pub mint: Account<'info, Mint>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub token_program: Program<'info, Token>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
}

// transfer SPL token from PDA token account to user token account
pub fn transfer_handler(ctx: Context<TokenTransfer>) -> Result<()> {
    let seeds = &["auth".as_bytes(), &[*ctx.bumps.get("auth").unwrap()]];
    let signer = [&seeds[..]];

    let cpi_ctx = CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        token::Transfer {
            from: ctx.accounts.from_token_account.to_account_info(),
            to: ctx.accounts.to_token_account.to_account_info(),
            authority: ctx.accounts.auth.to_account_info(),
        },
        &signer,
    );

    token::transfer(cpi_ctx, 1)?;
    Ok(())
}

Additionally, you'll also need an instruction in your program to create a Token Account. In the reference below, the PDA is both the address of the Token Account and the authority of the token account.

// create PDA token account
use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, Token, TokenAccount};
#[derive(Accounts)]
pub struct Init<'info> {
    #[account(
        init,
        payer = payer,
        token::mint = mint,
        token::authority = auth,
        seeds = ["auth".as_bytes().as_ref()],
        bump,
    )]
    pub token_account: Account<'info, TokenAccount>,
    pub mint: Account<'info, Mint>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
    /// CHECK: PDA
    #[account(
        seeds = ["auth".as_bytes().as_ref()],
        bump,
    )]
    pub auth: UncheckedAccount<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
}

pub fn init_handler(_ctx: Context<Init>) -> Result<()> {
    Ok(())
}
6
  • Hi @john, tks for your answer but when change the code called from client: const [pda, bump] = PublicKey.findProgramAddressSync( [Buffer.from('auth')], program.programId ) await program.methods .transferBack() .accounts({ toTokenAccount: tokenAccountOfProgram, auth: pda, fromTokenAccount: tokenAccount, mint: minter, }).signers([owner]).rpc(); It return "A token owner constraint was violated." error
    – marethyu
    Jan 27, 2023 at 4:26
  • You have to use the seeds of the PDA you used as the authority of the program's Token Account when you created the token account
    – john
    Jan 27, 2023 at 4:43
  • I created the ata but it return "Cross-program invocation with unauthorized signer or writable account". Here is the code: await program.methods .transferBack() .accounts({ toTokenAccount: tokenAccount, auth: pda, fromTokenAccount: tokenAccountOfProgram.address, mint: minter, }).signers([owner]).rpc(); Is it because the signers is wrong?
    – marethyu
    Jan 27, 2023 at 7:07
  • If you're trying to transfer a token from the PDA Token Account (decrease its balance), the PDA has to be included as a signer in the transfer instruction. Here's some reference for CPIs in Anchor: anchor-lang.com/docs/cross-program-invocations Here's some reference for PDAs in Anchor: anchor-lang.com/docs/pdas
    – john
    Jan 27, 2023 at 7:36
  • Yeah I included the pda as a signer in transfer ix like your code
    – marethyu
    Jan 27, 2023 at 7:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.