Reading an account balance in Anchor: address(account).balance in Solana

Solona and Anchor get account balance

Reading an account balance in Anchor Rust

To read the Solana balance of an address inside a Solana program, use the following code:

use anchor_lang::prelude::*;

declare_id!("Gnf6u7S7fGJbqEGH9PuDE5Prq6f6ZrDxHY3jNJ4SYySQ");

#[program]
pub mod balance {
    use super::*;

    pub fn read_balance(ctx: Context<ReadBalance>) -> Result<()> {
        let balance = ctx.accounts.acct.to_account_info().lamports();

        msg!("balance in Lamports is {}", balance);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct ReadBalance<'info> {
    /// CHECK: although we read this account's balance, we don't do anything with the information
    pub acct: UncheckedAccount<'info>,
}

And the following is the web3 js code to trigger it:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Balance } from "../target/types/balance";

describe("balance", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.Balance as Program<Balance>;

  // the following is the Solana wallet we are using
  let pubkey = new anchor.web3.PublicKey("5jmigjgt77kAfKsHri3MHpMMFPo6UuiAMF19VdDfrrTj");


  it("Tests the balance", async () => {
    const tx = await program.methods.readBalance().accounts({ acct: pubkey }).rpc();
  });
});

Some items in this example differ from earlier tutorials, particularly the use of UncheckedAccount.

What is UncheckedAccount in Solana Anchor?

The UncheckedAccount type tells to Anchor to not check if the account being read is owned by the program.

Note that the account we passed through the Context struct is not an account that this program initialized, hence the program does not own it.

When Anchor reads an account of type Account in the #[derive(Accounts)] it will check (behind the scenes) if that account is owned by that program. If not, the execution will halt.

This serves as an important safety check.

If a malicious user crafts an account the program did not create and then passes it to the Solana program, and the Solana program blindly trusts the data in the account, critical errors may occur.

For example, if the program is a bank, and the account is storing how much balance a user has, then a hacker could supply a different account with an artificially higher balance than they actually have.

To pull off this hack, however, the user would have to create the false account in a separate transaction, then pass it to the Solana program. However, the Anchor framework checks behind the scenes to see if the account is not owned by the program, and rejects reading the account.

UncheckedAccount bypasses this safety check.

Important: AccountInfo and UncheckedAccount are aliases for each other and AccountInfo has the same security considerations.

In our case, we are passing in accounts that are certainly not owned by the program — we want to check the balance of an arbitrary account. Therefore, we must be certain that no critical logic can be tampered with with this safety check removed.

In our case, we are just logging the balance to the console, but most real-world use cases will have more complex logic.

What is /// CHECK:?

Because of the danger of using UncheckedAccount, Anchor forces you to include this comment to encourage you not to ignore the safety considerations.

Exercise: remove the /// Check: comment and run anchor build you should see the build halt and ask you to add the comment back with an explanation for why an Unchecked Account is safe. That is, reading in an untrusted account could be dangerous, Anchor wants to make sure you are not doing anything critical with the data in the account.

Why is there no #[account] struct in the program?

The #[account] struct tells Anchor how to deserialize an account holding data. For example, an account struct that looks like the following will inform Anchor that it should deserialize the data stored in the account into a single u64:

#[account]
pub struct Counter {
    counter: u64
}

In our case however, we are not reading the data from the account — we are only reading the balance. This is similar to how this similar to how we can read the balance of an Ethereum address but not read any of it’s code. Since we do not want to deserialize the data, we don’t supply an #[account] struct.

Not all the SOL in an account is spendable

Recall from our discussion of Solana account rent that the account must maintain a certain balance of SOL to be “rent exempt” or the runtime will delete the account. Just because the account has “1 SOL” in it does not necessarily mean the account can spend the entire 1 SOL.

For example, if you are building a staking or bank application where a user’s deposited SOL is kept in separate accounts, it is not accurate to simply measure the SOL balance of those accounts as the rent will be included in the balance.

Learn more with RareSkills

See our Solana developer course for more Solana materials.

Originally Published February, 29, 2024

Cross Program Invocation In Anchor

Cross Program Invocation In Anchor Cross Program Invocation (CPI) is Solana’s terminology for a program calling the public function of another program. We’ve already done CPI before when we sent a transfer SOL transaction to the system program. Here is the relevant snippet by way of reminder: pub fn send_sol(ctx: Context<SendSol>, amount: u64) -> Result<()> […]

Reading Another Anchor Program’s Account Data On Chain

Reading Another Anchor Program’s Account Data On Chain In Solidity, reading another contract’s storage requires calling a view function or the storage variable being public. In Solana, an off-chain client can read a storage account directly. This tutorial shows how an on-chain Solana program can read the data in an account it does not own. […]

#[derive(Accounts)] in Anchor: different kinds of accounts

[derive(Accounts)] in Anchor: different kinds of accounts #[derive(Accounts)] in Solana Anchor is an attribute-like macro for structs that holds references to all the accounts the function will access during its execution. In Solana, every account the transaction will access must be specified in advance One reason Solana is so fast is that it executes transactions […]

Modifying accounts using different signers

Modifying accounts using different signers In our Solana tutorials thus far, we’ve only had one account initialize and write to the account. In practice, this is very restrictive. For example, if user Alice is transferring points to Bob, Alice must be able to write to an account initialized by user Bob. In this tutorial we […]