7

I want to use my pda as a vault.

Deposit function works fine, but withdraw doesn't

    pub fn withdraw(ctx: Context<DepositWithdrawVault>, amount: u64) -> Result<()> {
        let config = &mut ctx.accounts.config;
        let recipient = &mut ctx.accounts.user;

        if transfer_amount > config.balance {
            return Err(errors::InsufficientBalance.into())
        }

        let transfer_sol = transfer( &config.key(), &recipient.key(), amount);
        let required_accounts_for_transfer = [
          config.to_account_info().clone(), 
          recipient.to_account_info().clone(), 
          ctx.accounts.system_program.to_account_info().clone()
        ];

        invoke(
            &transfer_sol,
            &required_accounts_for_transfer,
        )?;
        
        config.balance -= transfer_amount;
        Ok(())

        // another solution is:
        //    // Debit from_account and credit to_account
        // **from_account.try_borrow_mut_lamports()? -= amount_of_lamports;
        // **to_account.try_borrow_mut_lamports()? += amount_of_lamports;
        // but how to modify it?
    }

and the test

initialize seed, bump and all that stuff

        await program.methods.withdraw(1)
            .accounts({
                user: provider.wallet.publicKey,
                config: config,
                systemProgram: SystemProgram.programId,
            }).rpc();

essentially, the error:

Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
      at Connection.sendEncodedTransaction (node_modules/@solana/web3.js/src/connection.ts:4480:13)
      at processTicksAndRejections (node:internal/process/task_queues:95:5)
      at Connection.sendRawTransaction (node_modules/@solana/web3.js/src/connection.ts:4439: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)
2
  • 1
    Please share the error that you get
    – C.OG
    Commented Jul 29, 2022 at 16:12
  • my mistake, apologies for that. Just edited, please have a look :)
    – Solomon
    Commented Aug 1, 2022 at 7:08

3 Answers 3

8

Thanks to @Ademola, here is the answer:

**ctx.accounts.config.to_account_info().try_borrow_mut_lamports()? -= amount;
**ctx.accounts.user.try_borrow_mut_lamports()? += amount;
2

In order to withdraw SOL from any account, the owner of the account needs to sign.

The result should look something like this:

let seeds = &[
    b"escrow".as_ref(),
    &[ctx.bumps.get("escrow")],
];
let signer = &[&seeds[..]];
let ix = anchor_lang::solana_program::system_instruction::transfer(
    &ctx.accounts.escrow.key,
    &ctx.accounts.destination.key,
    amount,
);
anchor_lang::solana_program::program::invoke_signed(
    &ix,
    &[
        ctx.accounts.escrow.to_account_info(),
        ctx.accounts.destination.to_account_info(),
    ],
    signer,
)?;

In this example, the escrow account is a PDA. Using the seeds and bump used to create the PDA, its signing seeds are recreated and used on the transfer instruction.

5
  • this only works if the PDA is owned by the system program, which does not appear to be OP's case
    – trent.sol
    Commented Jul 29, 2022 at 22:55
  • Could you please explain a way to make a transaction then, I found this article: solanacookbook.com/references/… "The fundamental rule is that your program can transfer lamports from any account owned by your program to any account at all." Also, there is invoke_signed() function, but I am not sure how to pass seeds there as a signature, if anyone could help with that would be awesome!
    – Solomon
    Commented Aug 1, 2022 at 7:09
  • Here's a link to a similar issue. You don't need signer seeds when the account is owned by your program and not the system program. solana.stackexchange.com/questions/293/…
    – Ademola
    Commented Aug 1, 2022 at 10:20
  • public key + b"VAULT"
    – Solomon
    Commented Aug 1, 2022 at 10:21
  • 1
    Check out the link. I think it has your answer
    – Ademola
    Commented Aug 1, 2022 at 10:23
0

Before you withdraw, you need to add an if statement to make sure you have enough funds to withdraw and pay for the rent:

let config = &mut ctx.accounts.config;
let rent_due=Rent::get()?.minimum_balance(config.to_account_info().data_len());

then add the if statement

if **config.to_account_info().lamports.borrow()-rent_due<amount{
        return Err(ProgramError::InsufficientFunds);
    }

After you pass the guard:

  **config.to_account_info().try_borrow_mut_lamports()?-=amount;
  **recipient.to_account_info().try_borrow_mut_lamports()?+=amount;

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.