Search code examples
rustsolanaanchor-solana

How to transfer SOL from a PDA which created a token associate account to a normal account in Anchor?


If once a PDA is created as an associate token address, it can not transfer SOL from the PDA to another account?

I'd like to transfer both SOL and SPL-Token using a single PDA account.

I tried both solana_program::program::invoke_signed with system_instruction::transfer and also try_borrow_mut_lamports(). But, it is not working.

Whenever transfer with system_program, I got Transfer: from must not carry data, the other way was instruction spent from the balance of an account it does not own.

What am I missing?

Can I get some advice?

Thx.

the associate token account in Struct

#[account(init, 
    seeds = [authority.to_account_info().key.as_ref()], 
    bump, 
    payer = authority, 
    token::mint = mint, 
    token::authority = authority)
]    
pub vault: Account<'info, TokenAccount>,

Create another PDA, authority for when trasfering spl-token from the vault account

let (vault_authority, vault_authority_bump) = Pubkey::find_program_address(
    &[ctx.accounts.vault.to_account_info().key.as_ref()], ctx.program_id
);

let cpi_accounts = SetAuthority {
    account_or_mint: ctx.accounts.vault.to_account_info().clone(),
    current_authority: ctx.accounts.authority.to_account_info().clone()
};
let cpi_context = CpiContext::new(ctx.accounts.token_program.to_account_info().clone(), cpi_accounts);
token::set_authority(cpi_context, AuthorityType::AccountOwner, Some(vault_authority))?;

Failed case 1, transfer SOL from vault to owner using system_program

// Error message : Transfer: `from` must not carry data
let vault_key = ctx.accounts.authority.to_account_info().key.as_ref();
let (_vault, bump) = Pubkey::find_program_address(&[vault_key], ctx.program_id);
let seeds = &[vault_key, &[bump]];
let signer = &[&seeds[..]];

let owner_key = &ctx.accounts.owner.key();
let vault_key = &ctx.accounts.vault.key();
let ix = system_instruction::transfer(vault_key, owner_key, amount);

let owner_account = ctx.accounts.owner.to_account_info();
let vault_account = ctx.accounts.vault.to_account_info();
solana_program::program::invoke_signed(&ix, &[vault_account, owner_account], signer)?;

Failed case 2, transfer SOL from vault to owner using 'try_borrow_mut_lamports'

// Error message : instruction spent from the balance of an account it does not own
**ctx.accounts.vault.to_account_info().try_borrow_mut_lamports()? -= amount;
**ctx.accounts.owner.to_account_info().try_borrow_mut_lamports()? += amount;

Solution

  • It is not possible to transfer both SOL and SPL tokens from the same account. The SPL token account (associated or not) is owned by the SPL token program, which means that only the SPL token program can change its data and deduct its lamports. Otherwise, it would be possible for anyone to bring a token account under rent-exemption and break guarantees of the token program.

    Let's go through your cases and explain why they didn't work:

    • Failed case 1, transfer SOL from vault to owner using system_program triggering: Transfer: from must not carry data

    There's actually two errors here, but only the first one is reported. When you try to system_instruction::transfer from the vault, it will mainly fail because the vault is not owned by the system program. It's owned by the token program, so only the token program can move its lamports, and not the system program. The reported failure says that the account also has data, which is the token data (owner, amount, delegate, etc).

    • Failed case 2, transfer SOL from vault to owner using 'try_borrow_mut_lamports'

    This one's very similar -- instead of the system program trying to deduct lamports from the token account, it's your program trying to do it, so the runtime will again say "this isn't allowed".