Solana Program Snippets
Tags: solana, anchor, rust, programming
Published: 2022-09-26 07:48:35

I've compiled a list of Solana program snippets that I thought could be useful for any project. These snippets assume you're using [anchor](https://www.anchor-lang.com/docs/installation).

- [Resources](#resources)
- [Basic Program](#prog)
- [Initializing An Account](#init)
- [Updating An Account](#update)
- [Transferring SOL](#sol)
- [Transferring SOL from a PDA](#sol-pda)
- [Transferring SPL Tokens](#spl)
- [Transferring SPL Tokens from a PDA](#spl-pda)
- [Checking Metaplex Metadata](#metaplex)
- [`remaining_accounts`](#remaining)
- [`msg!`](#msg)
- [Errors](#errors)
- [Finding Help](#help)

## Resources <a name="resources"></a>
Learn about the basics of how Solana works:

- [Transactions](https://docs.solana.com/developing/programming-model/transactions)
- [Instructions](https://docs.solana.com/developing/programming-model/transactions#instructions)
- [Accounts](https://docs.solana.com/developing/programming-model/accounts)
- [Program Derived Addresses](https://solanacookbook.com/core-concepts/pdas.html#facts)

Here are some useful repos I referenced while learning:

- https://github.com/coral-xyz/anchor
- https://github.com/coral-xyz/anchor-by-example
- https://github.com/solana-labs/solana-program-library
- https://github.com/metaplex-foundation/metaplex-program-library
- https://github.com/metaplex-foundation/js
- https://github.com/jet-lab/jet-v2

Some other useful resources I found snippets from:

- https://spl.solana.com/
- https://examples.anchor-lang.com/
- https://solanacookbook.com/
- https://hackmd.io/@ironaddicteddog/solana-anchor-escrow

## Dependencies
These are the dependencies I used when writing these snippets (these go in your `Cargo.toml` inside your program directory):

```toml
[dependencies]
anchor-lang = "0.25.0"
anchor-spl = "0.25.0"
mpl-token-metadata = {version="1.3.6", features = ["no-entrypoint"]}
solana-program = "~1.10.29"
spl-token = "~3.3.0"
```

> Use the `"no-entrypoint"` feature with `mpl-token-metadata` to prevent "global allocator errors"

## Basic Program <a name="prog"></a>
You can write a program by just creating a context and a function that uses the context. The context defines what accounts are expected as inputs to your function, and a function is an instruction (the things you put in a transaction) that your program can process. A minimal example:
```rust
#[program]
pub mod my_program {
    use super::*;
    // Instruction
    pub fn do_something<'info>(ctx: Context<DoSomething>) -> Result<()> {
        msg!("Doing something to {}", &ctx.accounts.account_to_do_something_with.key());
        Ok(())
    }
}

// Context
#[derive(Accounts)]
pub struct DoSomething<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    /// CHECK: Data held in account could be anything
    pub account_to_do_something_with: AccountInfo<'info>
}
```

### State
If your program contains state you define structs modeling your state and Anchor can automatically initialize and deserialize them in your instructions. By using the `Account<T>` type in your context, you're telling Anchor that the data held in the given account matches the type used. If it doesn't match (eg. using a Metadata account when expecting YourAccount), the program will fail to process the instruction. More on accounts [here](https://www.anchor-lang.com/docs/the-accounts-struct).

## Initializing An Account <a name="init"></a>
In the example below we make use of Anchor's [`#[account]`](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html#constraints) attribute with the `init` constraint to automatically initialize the given account. The space required for an account is 8 bytes for Anchor's [account discriminator](https://twitter.com/armaniferrante/status/1411589634228772870), then add the size of each field in the account struct. This value goes in the `space` constraint. `payer` is (surprise!) the wallet paying for the new account to be created (cost is determined by the amount of space needed). `seeds` are used to distinguish between [PDAs](#pdas).

> In this example only the `payer` is used in the `seeds`, but IRL you'd want some sort of post ID too, so a user can have multiple posts.

```rust
#[program]
pub mod my_program {
    use super::*;
    pub fn create_post<'info>(ctx: Context<CreatePost>) -> Result<()> {
        ctx.accounts.post.owner = ctx.accounts.payer.key();
        Ok(())
    }
}

#[derive(Accounts)]
pub struct CreatePost<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    #[account(
        init,
        seeds = [payer.key().as_ref(), b"post".as_ref()],
        bump,
        space = POST_SIZE,
        payer = payer,
    )]
    pub post: Account<'info, Post>,

    // Required to init the account
    pub system_program: Program<'info, System>,
}

pub const POST_SIZE: usize = 8 + 32 + 4 + 4;
// State
#[account]
pub struct Post {
    pub owner: Pubkey;
    pub likes: u32,
    pub replies: u32,
}
```
> The [`#[instruction]`](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html#instruction-attribute) attribute is another useful attribute that allows you to use instruction args in your constraints.

## Updating An Account <a name="update"></a>
When updating an account, you'll have to use the `mut` constraint to mark the account as mutable. If you forget it, you'll see a build error complaining about mutability. The `LikePost` instruction below just increments the number of `likes` on the account, then computes the `ratio`.

```rust
#[program]
pub mod my_program {
    use super::*;
    pub fn like_post<'info>(ctx: Context<LikePost>) -> Result<()> {
        ctx.accounts.post.likes = ctx.accounts.post.likes + 1;
        if ctx.accounts.post.replies > 0 {
            ctx.accounts.post.ratio = ctx.accounts.post.likes.checked_div(ctx.accounts.post.replies)
                .ok_or(Error::InvalidRatio)?;
        }
        Ok(())
    }
}

#[derive(Accounts)]
pub struct LikePost<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    #[account(mut)]
    pub post: Account<'info, Post>,
}
```
There are loads of useful constraints you can use to harden your code. Check out all the contraints [here](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html).

## Transferring SOL <a name="sol"></a>
Transfer SOL from one account to another. `lamports` is the SOL amount in [lamports](https://docs.solana.com/introduction#what-are-sols). `account_a` and `account_b` must be marked with `mut` in your context. The System program will also need to be included in your context:
```rust
// In your instruction function
solana_program::program::invoke(
    &solana_program::system_instruction::transfer(
        &ctx.accounts.account_a.key(),
        &ctx.accounts.account_b.key(),
        lamports,
    ),
    &[
        ctx.accounts.account_a.to_account_info().clone(),
        ctx.accounts.account_b.to_account_info().clone(),
    ]
)?;

// In your context
#[account(mut)]
pub account_a: Signer<'info>,

#[account(mut)]
pub account_b: AccountInfo<'info>,

pub system_program: Program<'info, System>
```

## Transferring SOL from a PDA <a name="sol-pda"></a>
### PDA auth seeds
When transferring assets from a PDA account that isn't owned by your program (eg. owned by token program) you'll have to sign the instruction using the same seeds used to derive the PDA.
```rust
// If you use the PDA constraint:
// seeds = [seed_1.key().as_ref(), b"my_account_type".as_ref()]
// Get your signer_seeds with:
let signer_seeds = &[
    &ctx.accounts.seed_1.key().to_bytes()[..],
    &b"my_account_type"[..],
    // pda_bump can be passed in as an instruction arg or derived again using Pubkey::find_program_address
    &[pda_bump]
];
```
Now that you have your `signer_seeds`, you can call `invoke_signed` to do the transfer:
```rust
// In your instruction function
solana_program::program::invoke_signed(                                                            
    &solana_program::system_instruction::transfer(
      &ctx.accounts.pda_account.key(),
      &ctx.accounts.account_b.key(),
      lamports
    ),
    &[                          
      ctx.accounts.pda_account.to_account_info().clone(),
      ctx.accounts.account_b.to_account_info().clone(),
    ],
    &[&signer_seeds[..]]
)?;
```
However, if the PDA is owned by your program (eg. created using the `init` account constraint), you can transfer lamports using:
```rust
let src = &mut ctx.accounts.pda_account.to_account_info();
**src.try_borrow_mut_lamports()? = src
    .lamports()
    .checked_sub(lamports)
    .ok_or(ProgramError::InvalidArgument)?;

let dst = &mut ctx.accounts.account_b.to_account_info();
**dst.try_borrow_mut_lamports()? = dst
    .lamports()
    .checked_add(lamports)
    .ok_or(ProgramError::InvalidArgument)?;
```

## Transferring SPL Tokens <a name="spl"></a>
Transfer an SPL token (like FOXY or USDC) from one account to another. The `from` and `to` accounts are [associated token accounts](https://spl.solana.com/associated-token-account#finding-the-associated-token-account-address) and must be marked with `mut` in your context and the `authority` account must be a signer. The Token program will also need to be included in your context:
```rust
// In your instruction function
anchor_spl::token::transfer(
    CpiContext::new(ctx.accounts.token_program.to_account_info(), anchor_spl::token::Transfer {
        from: ctx.accounts.token_account_a.to_account_info().clone(),
        to: ctx.accounts.token_account_b.to_account_info().clone(),
        authority: ctx.accounts.payer.to_account_info().clone(),
    }),
    amount
)?;

// In your context
#[account(
    mut,
    // check for a certain owner if needed: constraint = token_account_a.owner == payer.key(),
    // check for a certain token mint if needed: token::mint = mint,
)]
pub token_account_a: Account<'info, TokenAccount>,

#[account(mut)]
pub token_account_b: Account<'info, TokenAccount>,

pub token_program: Program<'info, Token>
```

## Transferring SPL Tokens from a PDA <a name="spl-pda"></a>
Similar to the above example, except now with the added `with_signer` method call:
```rust
anchor_spl::token::transfer(
    CpiContext::new(ctx.accounts.token_program.to_account_info(), anchor_spl::token::Transfer {
        from: ctx.accounts.pda_account.to_account_info().clone(),
        to: ctx.accounts.token_account_b.to_account_info().clone(),
        authority: ctx.accounts.pda_account.to_account_info().clone(),
    }).with_signer(&[&signer_seeds[..]]),
    amount
)?;
```

## Checking Metaplex Metadata <a name="metaplex"></a>
At the time of writing the Anchor team is putting together an Anchor-friendly `MetadataAccount` to deserialize and check Metaplex metadata automatically, but it wasn't ready yet so I dug around and found this:
```rust
// Imports
use mpl_token_metadata::state::{Metadata, TokenMetadataAccount};

// Instruction
// Verify the metadata account given is the expected one for the given mint
pub fn verify_metadata<'info>(ctx: Context<VerifyMetadata>) -> Result<()> {
    let mint_key = &ctx.accounts.mint.key();
    let metadata_program_id = &mpl_token_metadata::id();
    let metadata_seeds = &[
        mpl_token_metadata::state::PREFIX.as_bytes(),
        metadata_program_id.as_ref(),
        mint_key.as_ref()
    ];
    // Get the metadata PDA for the given mint
    let (expected_metadata_account, _) = Pubkey::find_program_address(
        metadata_seeds,
        metadata_program_id
    );
    assert_eq!(expected_metadata_account, ctx.accounts.metadata_account.key(),
        "Invalid metadata account");
    Ok(())
}

// Context
#[derive(Accounts)]
pub struct VerifyMetadata<'info> {
    pub mint: Account<'info, Mint>,
    pub metadata_account: AccountInfo<'info>
}
```
If the given `metadata_account` doesn't match what we derive from known seed inputs, it's not the metadata for the NFT and can't be trusted.

### Deserializing Metaplex Metadata
Now that you know you have the expected metadata account, you can deserialize it with this:
```rust
let metadata: Metadata = Metadata::from_account_info(&ctx.accounts.metadata_account)?;
```
Which gives you access to check royalties, creator shares, etc for the noble lads and lassies out there looking to disburse royalties.

## `remaining_accounts` <a name="remaining"></a>
When your instruction can accept a variable number of accounts (say you're disbursing royalties to n number of creators), use the `ctx.remaining_accounts` property. These accounts are set up as generic `AccountInfo`'s so you'll have to be careful when reading data from them (always check keys with PDAs if possible).

On the front-end side of things you can populate these accounts like so: `program.methods.doSomething().remainingAccounts([accounts])`.

## `msg!` <a name="msg"></a>
There are deeper ways of debugging programs, but I found that simply logging using the `msg!` method was sufficient for me. These messages are written to transaction logs when executed. While testing you can find these logs in `<working-dir>/.anchor/program-logs/program-id.program-name.log`

## Errors <a name="errors"></a>
Here are some other generic errors I've run into and solutions that may work for you:

### `program failed to complete`
An error that occurs when executing a transaction. They say it might have something to do with running out of heap space. I boxed all of my accounts to make it go away (add `Box<>` around your `Account<>`'s in your context).

### `lifetime mismatch`
Build error in Rust. Something that has to do with Rust lifetimes. I added lifetime qualifiers to my instruction's ctx arg to make it go away `Context<'_, '_, '_, 'info, DoSomething<'info>>` instead of `Context<DoSomething>`.

### `accounts not balanced after tx`
That's not the exact phrase, but something along those lines. I got this when closing an account in the same instruction as transferring lamports. It should be possible, but may have been an issue with the mix of how I was transferring/closing. My fix was to split the transfer and close actions into separate instructions.

### `invalid account data for instruction`
This may appear when trying to execute a transaction using an account that hasn't been initialized yet (empty data).

## Finding Help <a name="help"></a>
When searching for errors, I found that searching the [Anchor Discord](http://discord.gg/ZCHmqvXgDw) yielded better results than Google most of the time.

## Fin

I'll keep jotting down notes and publishing more snippets as I learn and grow more in this ecosystem. Till next time, good luck out there SOLdier 🫡

Let's connect on Twitter: [@meditatingsloth](https://twitter.com/meditatingsloth)