0

New Solana Anchor developer here and I have a question about how I am currently sending Sol to multiple wallets in my program. Currently im using the code below twice and then invoking two separate transactions.

system_instruction::transfer(from_account.key, to_account.key, amount);

However, I noticed in the Rust documentation there was a system instruction for "transfer_many" using a vec array of instruction.

Can someone explain to me how I would create 1 instruction with transfer_many and than invoke that instruction? The documentation is gibberish to me so if someone would explain in human terms I would appreciate it.

Here is my full code below to send sol to multiple addresses. Please ignore the "feeaccount" as eventually I will change up my code to send a percent of the sol that the program account accepts. (Later down the road once I learn more)

use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer as SplTransfer};
use solana_program::system_instruction;

declare_id!("HBj3u9jbTigqXb47TAF2H44TAoTckVJnjzFc4jxusUN2");

#[derive(Accounts)]
pub struct TransferLamports<'info> {
    #[account(mut)]
    pub from: Signer<'info>,
    #[account(mut)]
    pub to: AccountInfo<'info>,
    #[account(mut)]
    pub feeto: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

#[program]
pub mod solana_lamport_transfer {
    use super::*;
    pub fn transfer_lamports(ctx: Context<TransferLamports>, amount: u64) -> Result<()> {
        let from_account = &ctx.accounts.from;
        let to_account = &ctx.accounts.to;
        let fee_account = &ctx.accounts.feeto;

        // Create the transfer instruction
        let transfer_instruction1 =
            system_instruction::transfer(from_account.key, to_account.key, amount);
        // Create the transfer instruction
        let transfer_instruction2 =
            system_instruction::transfer(from_account.key, fee_account.key, amount);

        // Invoke the transfer instruction
        anchor_lang::solana_program::program::invoke_signed(
            &transfer_instruction1,
            &[
                from_account.to_account_info(),
                to_account.clone(),
                ctx.accounts.system_program.to_account_info(),
            ],
            &[],
        )?;

        // Invoke the transfer instruction
        anchor_lang::solana_program::program::invoke_signed(
            &transfer_instruction2,
            &[
                from_account.to_account_info(),
                fee_account.clone(),
                ctx.accounts.system_program.to_account_info(),
            ],
            &[],
        )?;

        Ok(())
    }
}

My code works how I wrote it, however, I'm so lost in using the "transfer_many" snip. Is the code I wrote fine? Or will I run into issues? I noticed that I dont pay a tx fee twice since its in the same call so maybe I dont need to use transfer_many?

4
  • Where did you find such function? It looks wrong.
    – toygr
    Commented Nov 12, 2024 at 4:12
  • I checked the docs here, I found the example of the client side quite clear while the program example after that combines a whole lot. What specific element is unclear to you? To me it seems the transfer_many returns a vec of Instructions that you’d have to invoke separately anyway so creating the instructions separately seems fine as well. I can imagine situations with many more invocations where transfer_many would make the code readable but in your case, not so much :) your code is fine!
    – JarroVGIT
    Commented Nov 12, 2024 at 4:29
  • So when I tried to follow those docs earlier I had some issues with how to structure the Vec of instructions. For example, if you had to break this down for me into two parts. 1. How to create a list of instructions using that vec 2. How to pass that instruction list into the transaction and invoke it. I feel like those docs use such short abbreviations that I dont have knowledge of yet I get lost. Commented Nov 12, 2024 at 4:49
  • Secondly, is my current implementation of using two separate transactions bad? It works and incurs no further fees. Commented Nov 12, 2024 at 4:51

1 Answer 1

1

TLDR; your code is fine.

Transferring Sol through a on-chain program requires a cross-program invocation to the System program. This native program has a defined set of possible instructions that you can give it, which are defined in the documentation here as an enumeration. The first thing that we notice is that of the 13 possible instructions, only 2 mention transfer: SystemInstruction::Transfer and SystemInstruction::TransferWithSeed. There are no TransferMany instructions available.

When we dig a bit deeper into the solana_program::system_instruction::transfer_many() function, we see it returns a Vec<Instruction> where as the transfer() function only returns a single Instruction. If you can find its definition in the source code, you can see the following:

pub fn transfer_many(from_pubkey: &Pubkey, to_lamports: &[(Pubkey, u64)]) -> Vec<Instruction> {
    to_lamports
        .iter()
        .map(|(to_pubkey, lamports)| transfer(from_pubkey, to_pubkey, *lamports))
        .collect()
}

This function actually does very little. Rather than taking from_pubkey, to_pubkey and lamports as arguments (such as the transfer() function), the transfer_many() takes a from_pubkey and slice of tuples, each containing a to_pubkey and lamports combination. For each tuple, it calls the regular transfer() method for constructing the Instruction and then returns them all together (as a Vec of Instructions).

Now, when you are creating a on-chain program, the transfer_many() adds little value, because a cross-program invocation only allows you to specify one instruction. This is different from when creating a client program (e.g. off-chain software that interacts with Solana) where instructions are bundled into a transaction. In such a case it would make sense to use the transfer_many() because you could bundle the set of instructions into one transaction.

Back in the day, the solana-sdk (for client or off-chain development) and solana_program where combined crates, so that is why you see some of these 'legacy' functions that make little sense when creating a on-chain program but are very logical to have available when building some client application.

This is actually illustrated by the examples in the aforementioned documentation. The client example constructs multiple transfer instructions using the transfer_many() and directly puts the resulting Vec<Instruction> into a new Transaction. The program example on the same page does a bad job explaining this concept in the context of on-chain development, but the key thing to see is the for-loop at the end:

for instr in instrs {
        invoke_signed(&instr, accounts, &[&[b"bank", &[bank_pda_bump_seed]]])?;
    }

It actually calls invoke_signed separately for each Instruction from transfer_many().

Hope this clarifies some bits and pieces for you!

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.