2

I'm trying to transfer SPL tokens from a PDA to an user account via CPI but I'm facing "instruction spent from the balance of an account it does not own" error.

The PDA is initialized via create_subscription instruction

    #[account(
        init,
        payer = signer,
        token::mint = mint,
        token::authority = subscription_account,
    )]
    pub subscription_vault: Box<Account<'info, TokenAccount>>

The SPL token transfer takes place in the cancel_subscription

#[account(
        mut,
        close = user_pubkey, 
        constraint = subscription_vault.owner == subscription_account.key() @ CustomError::VaultOwnerMisMatch
    )]
    pub subscription_vault: Box<Account<'info, TokenAccount>>

The SPL token transfer CPI is as follows

let seeds: &[&[&[u8]]; 1] = &[&[
        SUB_ACC_SEED,
        user_pubkey.as_ref(),
        &subscription_acc.id,
        &[subscription_acc.bump],
    ]];
    let transfer_ctx = CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        TransferChecked {
            from: ctx.accounts.subscription_vault.to_account_info(),
            to: ctx.accounts.user_ata.to_account_info(),
            authority: subscription_acc.to_account_info(),
            mint: ctx.accounts.mint.to_account_info(),
        },
        seeds,
    );
    transfer_checked(
        transfer_ctx,
        ctx.accounts.subscription_vault.amount,
        ctx.accounts.mint.decimals,
    )?;

1 Answer 1

2

The error you're seeing is due to the close constraint on a TokenAccount. The error is being thrown because the TokenAccount is owned by the Token Program and not your program.

To close the TokenAccount, you'll need to make a CPI to the Token Program's close_account instruction. In your cancel_subscription instruction you would just add a second CPI after the transfer CPI.

Here is an example of how to set up the close_account CPI.

use anchor_lang::prelude::*;
use anchor_spl::token::{close_account, CloseAccount, Mint, Token, TokenAccount};

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod test {
    use super::*;

    pub fn create_token_account(_ctx: Context<CreateTokenAccount>) -> Result<()> {
        Ok(())
    }

    pub fn close_token_account(ctx: Context<CloseTokenAccount>) -> Result<()> {
        let bump = *ctx.bumps.get("token_account").unwrap();
        let signer: &[&[&[u8]]] = &[&[b"token", &[bump]]];

        close_account(CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            CloseAccount {
                account: ctx.accounts.token_account.to_account_info(),
                destination: ctx.accounts.signer.to_account_info(),
                authority: ctx.accounts.token_account.to_account_info(),
            },
            signer,
        ))?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct CreateTokenAccount<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init,
        seeds = [b"token"],
        bump,
        payer = signer,
        token::mint = mint,
        token::authority = token_account
    )]
    pub token_account: Account<'info, TokenAccount>,
    pub mint: Account<'info, Mint>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct CloseTokenAccount<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        mut,
        seeds = [b"token"],
        bump
    )]
    pub token_account: Account<'info, TokenAccount>,
    pub token_program: Program<'info, Token>,
}

Note that in the example above, the same PDA is used as both the address and authority of the TokenAccount.

1
  • 1
    Oh! Thanks a lot, so my instruction needed to have two separate CPIs - one to transfer the remaining SPL tokens to an user's account and other to close the token account
    – 0xMukesh
    Jun 8, 2023 at 12:55

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.