What is an account in Solana?

When it comes to Solana development, the concept of account has been the trickiest thing for me to understand. The official docs are good but it was challenging to wrap my head around how it all works without reading sources and asking questions in Discord communities. In this article, I'll share my notes about Solana accounts I wish I had when I started to dive into Solana development.

What is an account?

Account is basically a storage location on Solana blockchain. Accounts store state like the amount of lamports (unit similar to satoshi in Bitcoin) or its owner. They can also store arbitrary binary data.

In Solana everything is an account, even programs. Accounts are mostly used by programs to store state, in the same way traditional apps use databases. Accounts are also used by user wallets on the client side to store funds and interact with on-chain programs.

Each account has a corresponding public / private keypair. Accounts' public key also acts as its address.

Difference between a keypair and an account

Keypair is not the same as account. A quick analogy might be helpful. Imagine a safe deposit box. Account is the actual deposit box (or the storage on the blockchain). Keypair is a pair of public and private keys. Public key is the deposit box number (or account's address). Private key is like a key to the deposit box.

Account ownership

Accounts are owned by programs. Only programs can modify accounts storage or state (withdrawing money, transferring ownership), and only the program that owns the account can do that. A client can ask a program to modify the account's state if they have a private key to that account. However, it is up to the program to decide whether to grant the request or not. From a on-chain program perspective, this account would be called a signer (because it was signed with its private key). This process is one of the ways a program can determine the account permissions.

Each program has its own logic to determine permissions of accounts that interact with the program. In a way, it is similar to web2 apps, where two authenticated users can have different permissions, and the app decides whether an operation should be allowed based on user identity. For instance, the system program only allows signers to allocate account storage or send lamports. Here's an example where we ask a system program to transfer some lamports to a new account, and for the call to succeed we have to sign with userWalletAccount:

const web3 = require('@solana/web3.js');

const connection = new web3.Connection(web3.clusterApiUrl('devnet'));

// this account is usually a wallet like Phantom
const userWalletAccount = web3.Keypair.generate();

const newAccount = web3.Keypair.generate();

(async () => {
	let transferTransaction = new web3.Transaction().add(
		web3.SystemProgram.transfer({
			fromPubkey: userWalletAccount.publicKey,
			toPubkey: newAccount.publicKey,
			lamports: 100000,
		}), 
	);

	// the 3rd argument here is a list of signers
	await web3.sendAndConfirmTransaction(connection, transferTransaction, [userWalletAccount])
})();

How to create an account in Solana?

Before creating an account, you have to generate a keypair first. This is done on the client. Accounts are created by the system program. Both clients and on-chain programs can ask the system program to create an account.

The most common use case is creating an account that is used for sending and receiving lamports. For this use case, you don't have to explicitly create an account, you only need to send some lamports to the account's address and you're good to go. At this point you can use your private key to sign for transactions, make calls to other programs, etc.

This account is owned by the system program, so if you decide to send lamports to another account, you would have to send a "transfer lamports" instruction to the system program, signing the transaction with your private key.

In a more complex scenario, if we would need to write and later read that data on the blockchain, we would have to create an account with storage. To do that you make a call to the system program and ask it to create account and allocate a fixed amount of storage for it. There is a helper function that simplifies the process of creating an account with storage in the Solana library:

const web3 = require('@solana/web3.js');

const connection = new web3.Connection(web3.clusterApiUrl('devnet'));

// this account is usually a wallet like Phantom
const userWalletAccount = web3.Keypair.generate()
const newAccount = web3.Keypair.generate()

(async () => {
	let createAccountTx = new web3.Transaction().add(web3.SystemProgram.createAccount({
		space: 200,
		lamports: 10000,
		fromPubkey: userWalletAccount.publicKey,
		newAccountPubkey: newAccount.publicKey,
		programId: web3.SystemProgram.programId
	}));
	await web3.sendAndConfirmTransaction(connection, createAccountTx, [userWalletAccount, newAccount])
})();

When creating an account we have to specify a source account fromPubkey because it will provide funds for the new account. Both the funding account and the new one have to sign for this instruction to work.

Under the hood the web3.SystemProgram.createAccount function is just a helper that generates a "create account" instruction that you can send in a transaction to the system program. Various programs sometimes provide their own wrapper that does some other processing in addition to creating an account, exposing this functionality as a single operation. They also provide a helper function that generates an instruction for that operation.

Account storage costs

Account needs to have enough lamports to cover the rent of its storage (the more storage used the higher the rent). It is possible to skip paying rent if you have a certain amount of lamports in the account: the equivalent of 2 years of rent payments. In most case, people just fund the account with enough lamports to qualify for rent exemption. There is a function in the Solana library that calculates the minimum amount of lamports needed to be rent exempt:

const web3 = require('@solana/web3.js');

const connection = new web3.Connection(web3.clusterApiUrl('devnet'));

(async () => {
	let space = 1000;
	let minimumAmount = await connection.getMinimumBalanceForRentExemption(space);
	console.log('minimum lamports required for account to be rent exempt:', minimumAmount);
})();

How to fetch the account's state?

It's pretty straightforward to fetch the account state on the client:

const web3 = require('@solana/web3.js');

const connection = new web3.Connection(web3.clusterApiUrl('devnet'));

(async () => {
	let address = new web3.PublicKey('37DQf55wM4kiXgWmYFExG5pa6DkK9C8iMZpEK8phiDcg');
	let account = await connection.getAccountInfo(address);
	console.log('account balance:', account.lamports);
	// this is a buffer which has to be deserialized
	console.log('account storage:', account.data);
})();

On-chain programs can't read arbitrary accounts. The program has access only to the accounts that were passed to it by the client inside the instruction. Here's the signature of the program handler:

fn process_instruction(
    program_id: &Pubkey,      // Public key of the account the program was loaded into
    accounts: &[AccountInfo], // All accounts required to process the instruction
    instruction_data: &[u8],  // Serialized instruction-specific data
) -> ProgramResult;

So the program will be able to read the state of the accounts specified in the accounts argument, and not any other account. Accounts are represented as an AccountInfo struct in the on-chain programs.

How to read/write from and to the account's storage?

This is out of scope of the article but I'll provide a quick overview. AccountInfo struct has a data field, which is the account's storage. It's a buffer of binary data, so a program will have to deserialize it into Rust structs before processing.

Writing to storage can only be done by programs, it's not possible to do from the client. The program will have to serialize the Rust structs into a binary buffer before writing to the AccountInfo.data field.

What's next?

I recommend you to check out Paul Schaaf's article. It is super detailed and helpful. It should provide even more context on how accounts are used in actual Solana programs.