4

So I have been trying to make a program to just transfer some SOL from one account to another.

    pub fn fund_vault(ctx: Context<FundVault>) -> Result<()> {
        let payer = &mut ctx.accounts.payer;
        let receiver = &mut ctx.accounts.vault;
        let amount: u64 = 1_000_000_000;

        if payer.lamports() < amount {
            return Err(error!(ErrorCode::NotEnoughSol));
        }

        let instruction = transfer(&payer.key(), &receiver.key(), amount);

        let account_infos = [
            payer.to_account_info().clone(),
            receiver.to_account_info().clone(),
            ctx.accounts.system_program.to_account_info().clone(),
        ];

        invoke(&instruction, &account_infos)?;

        Ok(())
    }

with this context

#[derive(Accounts)]
pub struct FundVault<'info> {
    #[account(mut, seeds = [b"vault"], bump)]
    pub vault: Account<'info, Vault>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

essentially I am trying to send SOL from payer to the vault, I have also tried to change the vault to be a simple Account (not PDA)

my typescript test code looks like this

    const newUser = anchor.web3.Keypair.generate();

    let airdropSig = await provider.connection.requestAirdrop(
      newUser.publicKey,
      5 * anchor.web3.LAMPORTS_PER_SOL,
    );

    const latestBlockHash = await provider.connection.getLatestBlockhash();

    await provider.connection.confirmTransaction({
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: airdropSig,
    });

    const tx2 = await program.methods
      .fundVault()
      .accounts({
        payer: newUser.publicKey,
        vault: pdaVaultPublicKey,
      })
      .signers([newUser])
      .rpc();

so I am creating a keypair, funding it with some SOL and trying to invoke the fundVault method. But I am always getting this error

     Error: failed to send transaction: Transaction simulation failed: This account may not be used to pay transaction fees
      at Connection.sendEncodedTransaction (node_modules/@solana/web3.js/src/connection.ts:4546:13)
      at processTicksAndRejections (internal/process/task_queues.js:95:5)
      at Connection.sendRawTransaction (node_modules/@solana/web3.js/src/connection.ts:4505:20)
      at sendAndConfirmRawTransaction (node_modules/@project-serum/anchor/src/provider.ts:288:21)
      at AnchorProvider.sendAndConfirm (node_modules/@project-serum/anchor/src/provider.ts:148:14)
      at MethodsBuilder.rpc [as _rpcFn] (node_modules/@project-serum/anchor/src/program/namespace/rpc.ts:29:16)

Anybody having an idea what is going on? Is it possible that the owner of the generated account is not system program?

4 Answers 4

5

Transferring SOL requires the sender and receiver to be a SystemAccount.

vault should be a SystemAccount.

change

    #[account(mut, seeds = [b"vault"], bump)]
    pub vault: Account<'info, Vault>,

to

    #[account(mut, seeds = [b"vault"], bump)]
    pub vault: SystemAccount<'info>,

you also don't have to initialize the PDA. Every PDA is a System Account by default unless you have initialized it.

3
  • 1
    thanks for the swift answer, but unfortunately the same error is thrown even with your modifications...shouldnt the fee payer be the user that is sending the SOL? so technically the provider.wallet is not necessary?
    – mosmislit
    Commented Aug 2, 2022 at 10:02
  • edited, a good practice is to always make payer the feepayer.
    – vicyyn
    Commented Aug 2, 2022 at 10:19
  • 1
    well it turns out the problem is not so this instruction per se. But your comments seem to be useful, so thanks for that. as far as I can tell the error is thrown if I have another instruction call before 'fundVault'. I am suspecting that something with the provider ownership changes and for some reason I can not call anything after. Will do another post with more details
    – mosmislit
    Commented Aug 2, 2022 at 16:14
1

While @vicyyn is correct in that using SystemInstruction::Transfer requires that the source account be owned by the System Program (generally, only the program which owns an account may debit its lamports field), that is not the problem here. Instead the problem is that the account being used to pay the transaction fee is not owned by the System Program, which is a runtime requirement.

I'm no anchor pro, but I think you at least need to be passing program.provider.wallet to signers()

1
  • while the fundVault instruction now works, calling it after another instruction fails...I have describe this "strange" behavior in a more detailed post link
    – mosmislit
    Commented Aug 2, 2022 at 17:08
1

If you want to transfer SOL from a PDA , I found the following to be the most reliable method, from is a PDA.

pub fn transfer_sol(from: &mut AccountInfo, to: &mut AccountInfo, amount: u64) -> Result<()> {
    let post_from = from
        .lamports()
        .checked_sub(amount)
        .ok_or(Errors::NumericalOverflow)?;
    let post_to = to
        .lamports()
        .checked_add(amount)
        .ok_or(Errors::NumericalOverflow)?;

    **from.try_borrow_mut_lamports().unwrap() = post_from;
    **to.try_borrow_mut_lamports().unwrap() = post_to;
    Ok(())
}

I believe an invoke_signed would work too, but only if your PDA data is empty.

1

Ohad Dahan's answer was the only answer that worked for me for transferring from a PDA account(with data) to a System account.

Neither of the following approaches worked for me:

  let from_pda_seeds = [
                    b"from",
                    &[from.bump]
                ];
        
         let ix = anchor_lang::solana_program::system_instruction::transfer(
                    &from.key(),
                    &to.key(),
                    5_000_000,
                );
        
          
                anchor_lang::solana_program::program::invoke_signed(
                    &ix,
                    &[from.to_account_info(), to.to_account_info()],
                    &[&from_pda_seeds[..]],
                )?;

NOR

from
    .to_account_info()
    .try_borrow_mut_lamports()?
     .checked_sub(5_000_000)
     .ok_or(ErrorCode::UnableToDebitAccount)?;
        
 to.
    .to_account_info()
    .try_borrow_mut_lamports()?
    .checked_add(5_000_000)
    .ok_or(ErrorCode::UnableToCreditAccount)?;

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.