简体   繁体   中英

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;

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".

Since your PDA has data, and you don't intend on closing the account, you must keep a minimum balance for rent exemption, otherwise new errors associated to this will arise. For this, you can pass in an amount derived in the client-side app specific to the data size of your account. Otherwise, you can simply do the following:

pub fn handler(ctx: Context<PayoutFromVault>, minimum_balance_for_rent_exemption: u64) -> Result<()> {

let vault_account_info: &mut AccountInfo = &mut ctx.accounts.vault.to_account_info();
let owner_account_info: &mut AccountInfo = &mut ctx.accounts.owner.to_account_info();

let vault_lamports_initial = vault_account_info.lamports();
let owner_lamports_initial = owner_account_info.lamports();

let transfer_amount = vault_lamports_initial.try_sub(minimum_balance_for_rent_exemption)?;

**owner_account_info.lamports.borrow_mut() = owner_lamports_initial.try_add(transfer_amount)?;
**vault_account_info.lamports.borrow_mut() = minimum_balance_for_rent_exemption;

msg!("{} lamports transferred from vault to {}", amount, ctx.accounts.owner.key());
Ok(())
}    

In Solana, all accounts are owned by a program. Your normal account is owned by the system program, whereas your token account is owned by the token program. You can't use the system program to sign off for the token account.Hence the error "transfer can't contain data".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM