How do I transfer lamports from an account to a public key

I am trying to transfer SOL from a program owned account to a public key. All that find online is:

        invoke(
            &system_instruction::transfer(payer_info.key, account_info.key, required_lamports),
            &[
                payer_info.clone(),
                account_info.clone(),
                system_program_info.clone(),
            ],
        )?;

I have the payer info account, but how do I get an account info object from a private key in order to call the invoke transfer method?

The destination key cannot come from the UI as it should be a private key owned by me where I deposit into a jackpot and if they win return the money.

Hi @cucuSOL and welcome to the forum! :wave:

You actually have to pass in the account when you submit the transaction, but you don’t need the private key to pass the account in.

For instance, when constructing an instruction in the frontend you provide a list of keys in the TransactionInstruction constructor fields.

myTransaction.add(
    new TransactionInstruction({
      myProgramId,
      keys: [
        {
          pubkey: payerPubkey,
          isWritable: true,
          isSigner: true,
        },
        {
          pubkey: receiverPubkey,
          isWritable: true,
          isSigner: false,
        },
        {
          pubkey: SystemProgram.programId,
          isWritable: true,
          isSigner: false,
        },
      ],
      data: myInstructionDataBuffer,
    })
  );

Then in your rust program you get the account info for each of those keys by iterating over them like this:

let payer_info = next_account_info(accounts)?;
let receiver_info = next_account_info(accounts)?;
let system_program_info = next_account_info(accounts)?;

Note that it’s important that the order you send the accounts in. You need to use them in your smart contract in the same order that you send them from the frontend.

thanks, that helped. I have a few questions. Right now I am getting:

 Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account 

And in the output it’s telling me the payerPubKey signer privilege escalated
I don’t know if that has anything to do with it. I am using anchor

What my account looks like:

#[derive(Accounts)]
pub struct UpdateSpin<'info> {
    #[account(mut)]
    pub spin: Account<'info, Spin>,
    #[account(mut)]
    pub jackpot: AccountInfo<'info>,
    pub author: AccountInfo<'info>,
    #[account(address = system_program::ID)]
    pub system_program: AccountInfo<'info>,
}

What my transfer looks like:

 let transfer_instruction = anchor_lang::solana_program::system_instruction::transfer(
            &payer_info.key(),
            &jackpot.key(),
            spin_amount,
        );

        msg!("transfering to [{}] from [{}]", &jackpot.key(), &payer_info.key());
        msg!("meta data jackpot[{}] data payer [{}]", &jackpot., &payer_info.key());

        let required_accounts_for_create = &[
            payer_info.to_account_info().clone(),
            jackpot.clone(),
            program_info.clone()
        ][..];

        invoke(&transfer_instruction, required_accounts_for_create)?;

    await program.rpc.spin(new anchor.BN(10),{
      accounts: {
        spin: tweet.publicKey,
        jackpot:"BGjniJviEPnTFD2TjLFa1vh9CfU7Z7edasG54Hy9HhmP",
        author: program.provider.wallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      }
    });

what I found online it seems an account is not writable. but the both of the accounts are system owned accounts created with the program and they had #mut I would have expected for it to be writable.

Where is the payer_info variable coming from in your transfer code? Is that the author in your anchor accounts struct?

the payer account gets created with this:

#[derive(Accounts)]
pub struct CreateSpin<'info> {
    #[account(init, payer = author, space = Spin::LEN)]
    pub spin: Account<'info, Spin>,
    #[account(mut)]
    pub author: Signer<'info>,
    #[account(address = system_program::ID)]
    pub system_program: AccountInfo<'info>,
}

pub mod spinner {
    use super::*;
    // initial spin creation, account is created in order to story spin data.
    pub fn first_spin(ctx: Context<CreateSpin>) -> ProgramResult {
        let spin: &mut Account<Spin> = &mut ctx.accounts.spin;
        let author: &Signer = &ctx.accounts.author;
        let clock: Clock = Clock::get().unwrap();
        let v = get_spin_vector(clock.slot.to_string(), clock.unix_timestamp.to_string(), author.key.to_string());

        spin.author = *author.key;
        spin.timestamp = clock.unix_timestamp;
        spin.results = v;

        msg!("first spin for wallet [{}] spin results [{:?}]", &spin.author, &spin.results);
        Ok(())
    }



#[account]
pub struct Spin {
    pub author: Pubkey,
    pub timestamp: i64,
    pub results: Vec<Vec<u32>>,
}

My idea is on the first spin when there is no account I create an account that holds the spin values.
and that account will have 1 SOL for example (I am testing this by just requesting airdrop but in reality, it should come from the users wallet) So initial spin I create an account that holds spin values, and it has 1 SOL then the program will transfer that SOL to another account owned by the program which is like the jackpot account. If wins I have to transfer back to the payer.

Could you show the code around the transfer() instruction section?

I’m trying to understand where the payer_info variable, where you call payer_info.key() is coming from.

That account will need to be in the accounts struct, marked as mut so that you can right to it. I think that’s why you’re getting the error.

this is the full program:


use anchor_lang::solana_program::system_program;
use sha256::digest;

declare_id!("H5m1vHR3aHFDctkPZkZRmdecKq2Z5piHBovRaUDHBbd5");

#[program]
pub mod spinner {
    use super::*;
    // initial spin creation, account is created in order to story spin data.
    pub fn first_spin(ctx: Context<CreateSpin>) -> ProgramResult {
        let spin: &mut Account<Spin> = &mut ctx.accounts.spin;
        let author: &Signer = &ctx.accounts.author;
        let clock: Clock = Clock::get().unwrap();
        let v = get_spin_vector(clock.slot.to_string(), clock.unix_timestamp.to_string(), author.key.to_string());

        spin.author = *author.key;
        spin.timestamp = clock.unix_timestamp;
        spin.results = v;

        msg!("first spin for wallet [{}] spin results [{:?}]", &spin.author, &spin.results);
        Ok(())
    }

    pub fn spin(ctx: Context<UpdateSpin>, spin_amount: u64) -> ProgramResult {
        let clock: Clock = Clock::get().unwrap();
        //let spin = &mut ctx.accounts.spin;
        let payer_info: &mut Account<Spin> = &mut ctx.accounts.spin;
        let jackpot =  &mut ctx.accounts.jackpot;
        let author = &ctx.accounts.author;
        let program_info = &ctx.accounts.system_program;

//        payer_info.timestamp = clock.unix_timestamp;
//        payer_info.results = get_spin_vector(clock.slot.to_string(), clock.unix_timestamp.to_string(), author.key.to_string());
//        msg!("spin for existing wallet [{}] spin results [{:?}]", &payer_info.author, &payer_info.results);

//
        let transfer_instruction = anchor_lang::solana_program::system_instruction::transfer(
            &payer_info.key(),
            &jackpot.key(),
            spin_amount,
        );

        msg!("transfering to [{}] from [{}]", &jackpot.key(), &payer_info.key());
        msg!("meta data jackpot[{}] data payer [{}]", &jackpot., &payer_info.key());

        let required_accounts_for_create = &[
            payer_info.to_account_info().clone(),
            jackpot.clone(),
            program_info.clone()
        ][..];

        invoke(&transfer_instruction, required_accounts_for_create)?;

        Ok(())
    }
}


#[derive(Accounts)]
pub struct CreateSpin<'info> {
    #[account(init, payer = author, space = Spin::LEN)]
    pub spin: Account<'info, Spin>,
    #[account(mut)]
    pub author: Signer<'info>,
    #[account(address = system_program::ID)]
    pub system_program: AccountInfo<'info>,
}

#[derive(Accounts)]
pub struct UpdateSpin<'info> {
    #[account(mut)]
    pub spin: Account<'info, Spin>,
    #[account(mut)]
    pub jackpot: AccountInfo<'info>,
    pub author: AccountInfo<'info>,
    #[account(address = system_program::ID)]
    pub system_program: AccountInfo<'info>,
}

#[account]
pub struct Spin {
    pub author: Pubkey,
    pub timestamp: i64,
    pub results: Vec<Vec<u32>>,
}

impl Spin {
    const LEN: usize = DISCRIMINATOR_LENGTH
        + PUBLIC_KEY_LENGTH // Author.
        + TIMESTAMP_LENGTH // Timestamp.
        + RESULTS_LENGTH; // Content.
}


const DISCRIMINATOR_LENGTH: usize = 8;
const PUBLIC_KEY_LENGTH: usize = 32;
const TIMESTAMP_LENGTH: usize = 8;
const RESULTS_LENGTH: usize = 72;

this is the client test method:

  it('can update spin', async () => {

    let tweet = anchor.web3.Keypair.generate();
    const signature = await program.provider.connection.requestAirdrop(tweet.publicKey, 1000000000);

    // Send a tweet and fetch its account.
    const author = program.provider.wallet.publicKey;

    await program.rpc.firstSpin({
      accounts: {
        spin: tweet.publicKey,
        author: program.provider.wallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      },
      signers: [tweet],
    });


    //Update the Tweet.
    await program.rpc.spin(new anchor.BN(10),{
      accounts: {
        spin: tweet.publicKey,
        jackpot:"BGjniJviEPnTFD2TjLFa1vh9CfU7Z7edasG54Hy9HhmP",
        author: program.provider.wallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      }
    });
  });

});

Ah, I think I know the issue. It’s that the spin account is owned by your program, so the system program can’t transfer lamports from it.

OK, following the pattern from here in the token program source code, it seems like we can avoid using a transfer instruction and just literally modify the lamports field of the accounts manually!

Try this instead of the transfer invocation:

let previous_jackpot_balance = payer_info.lamports();
jackpot.lamports.borrow_mut() = previous_jackpot_balance
    .checked_add(payer_info.lamports())
    .expect("TODO: Throw overflow error");
payer_info.lamports.borrow_mut() = 0;

This works because Solana will make sure that we don’t create lamports from thin air by adding more lamports than we subtract. :slight_smile:

I was getting some errors with that one something with mismatched types. Probably something I am doing wrong with RUST since I am new to RUST as well. The following worked I was able to finally transfer.

        let payer_balance = payer_info.to_account_info().lamports();

        **jackpot.lamports.borrow_mut() += payer_balance;
        **payer_info.to_account_info().lamports.borrow_mut() = 0;

So just to recap on some of the concepts. So if the account I was transferring to was not owned by the program I would have to use the transfer method?

Also these accounts get generated from javascript client, when that happens doesnt the user have access to the private key? Or after the account is created that private key wont work?

Oh, yeah, that was probably my fault. I hadn’t tested the code, just copied some similar code from a non-anchor program.

Awesome!

I’m not 100% sure, but it appears that we don’t need to call the system program transfer instruction under any circumstances as far as I could tell. It looks like Solana will make sure that the lamport transfers all follow the rules, not subtracting form accounts that haven’t signed the transaction, etc.

So, I think in programs it’s easier to just have your program write modify lamports when you want to do native SOL transfers.

The frontend app does technically have access to the private key, but it won’t work after you transfer ownership of the account to your program, and usually you’ll just let the keypair variable get dropped by the JavaScript garbage collector because it isn’t needed anymore.