Hello! Let's continue our exploration of the Anchor framework.
In our last lesson, we focused on securing access to existing accounts. You learned to use powerful constraints like has_one and constraint to declare and enforce security rules, ensuring that only authorized signers can modify specific data.
But this raises a fundamental question: how do those data accounts get created in the first place? On Solana, unlike EVM-based chains where storage is part of the contract itself, data lives in separate accounts that must be explicitly created and funded with SOL to cover rent.
This lesson addresses exactly that. Our goal is to learn how to initialize new accounts using Anchor's init constraint and payer. By the end, you will understand the complete lifecycle of a data account, from creation to its first use.
The Need for Account Initialization
In Solana, everything is an account. Your program code lives in an account marked as "executable." The data your program operates on, like a user's profile or a game's state, lives in separate "non-executable" data accounts.
Before you can write any data (e.g., setting an initial counter value to 0), you must first perform a separate step: creating the account itself. This involves asking the Solana runtime to allocate a certain amount of memory (space) and transferring enough SOL to that account to make it rent-exempt.
This process is handled by a core, on-chain utility program called the System Program. While you could construct a raw create_account instruction to call the System Program manually, Anchor provides a much safer and more convenient abstraction: the init constraint.
The init Constraint: Your Account Creation Tool
The init constraint is the cornerstone of creating new accounts in Anchor. When you apply it to an account in your Accounts struct, you are telling Anchor to automatically insert a Cross-Program Invocation (CPI) to the System Program to create that account before your instruction logic runs.
For init to work, you must provide three critical pieces of information:
space: How many bytes of storage to allocate for the new account.payer: Which account will pay the lamports (SOL) required for rent.system_program: The System Program itself, which is needed to execute the creation.
Let's look at a canonical example of an Initialize struct for creating a simple counter account.
use anchor_lang::prelude::*;
// The main program module
#[program]
pub mod basic_counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// We can set initial values here
let counter_account = &mut ctx.accounts.counter;
counter_account.count = 0;
Ok(())
}
}
// 1. The data structure for our new account
#[account]
pub struct Counter {
pub count: u64, // 8 bytes
}
// 2. The Accounts context for the `initialize` instruction
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = user,
space = 8 + 8
)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
This pattern is fundamental, so let's break down the Initialize struct.
Initializing Accounts in Solana and Anchor
The article "Initializing Accounts in Solana and Anchor" from RareSkills provides an excellent, detailed walkthrough of this exact pattern. It explains each component of the initialization process.
Read the sections from "Account initialization boilerplate code" through to "What is the system program?". Pay close attention to the breakdown of the Initialize struct, the role of each field (my_storage, signer, system_program), and the explanation of the init, payer, and space parameters.
As the article explains, the magic happens in the attributes above the counter account:
#[account(init, ...)]: This markscounteras the account to be created. Anchor ensures this account does not already exist, preventing accidental re-initialization.payer = user: This specifies that theuseraccount (defined below it) will pay the lamports for the newcounteraccount's rent.space = 8 + 8: This defines the account's size in bytes. We'll dissect this next.user: Signer<'info>: This is the fee payer. It must be aSignerto authorize the lamport transfer, and it must be marked#[account(mut)]because its SOL balance will decrease.system_program: Program<'info, System>: This passes in the System Program, whichinitcalls under the hood. Anchor requires this exact field name and type.
Calculating Account space
The space parameter is crucial. If you allocate too little, your program will fail when it tries to write data that doesn't fit. If you allocate too much, you are wasting the user's SOL on rent.
The formula is: Total Space = 8 bytes (Discriminator) + Size of Data
- Size of Data: This is the space your custom struct requires. For our
Counterstruct, it's a singleu64(unsigned 64-bit integer), which is 8 bytes. - 8-byte Discriminator: This is an internal Anchor feature. Anchor takes the first 8 bytes of a SHA-256 hash of your struct's name (e.g.,
Counter) and prepends it to the account's data. When you later try to read this account, Anchor checks this discriminator to ensure the data belongs to theCounterstruct, preventing a whole class of account-confusion attacks.
So, for our Counter struct, the space is 8 (discriminator) + 8 (for the u64) = 16 bytes.
A Beginner's Guide to Building Solana Programs with Anchor
For a deeper dive on account space, the Helius guide provides an excellent breakdown of data type sizes and the role of the discriminator.
Read the section "Account Space". Focus on the subsections "Sizing Variables" and "Anchor's Internal Discriminator". This will solidify your understanding of where the + 8 comes from and how to size other data types you might use in the future.
Test your understanding!
You are creating a PlayerProfile account to store a player's level and their experience points. The struct is defined as:
#[account]
pub struct PlayerProfile {
pub level: u16, // 2 bytes
pub xp: u64, // 8 bytes
pub authority: Pubkey, // 32 bytes
}
What value should you use for the space constraint when initializing this account?
Show answer
The total space for the data is 2 (level) + 8 (xp) + 32 (authority) = 42 bytes.
You must also add the 8-byte discriminator.
So, the correct space is 8 + 42 = 50 bytes. The constraint would be space = 8 + 2 + 8 + 32 or simply space = 50.
Seeing it in Action
Reading about it is one thing, but seeing it work provides clarity. The following video demonstrates these concepts in a live-coding format, which is a great way to see how the pieces fit together.
Anchor Basics [Solana Development Course M5P1] - Feb 22nd '23
This segment from a Solandy tutorial provides a clear, practical demonstration of initializing an account with init, payer, and space.
Watch the section "Initializing New Accounts with init, payer, and space" from 00:21:37 to 00:26:08. The presenter walks through the code, explaining the purpose of each constraint, including the 8-byte discriminator, and how they relate to the underlying System Program call.
Testing Account Initialization
Given your extensive frontend background, the testing part of Anchor, which is done in TypeScript, will feel very familiar. When testing an initialize instruction, Anchor's client-side library simplifies things greatly.
You typically need to:
- Generate a new
Keypairfor the account you are about to create. - Call the program's
initializemethod. - Pass the public key of the new keypair into the
.accounts({})object. - Crucially, pass the new keypair itself into the
.signers([])array. Theinitconstraint requires the new account's keypair to sign the transaction to prove ownership of the address being created. - Ensure the
payeraccount has enough SOL to pay for the transaction and the rent.
The next video segment continues from where the last one left off, showing exactly how to write and run a test for an instruction that initializes an account.
Anchor Basics [Solana Development Course M5P1] - Feb 22nd '23
Let's continue with the Solandy video to see how to test our initialization logic. This part is especially relevant to your experience with TypeScript.
Watch the section "Testing Account Initialization and Data Storage" from 00:34:44 to 00:41:58. Pay attention to how a new Keypair is created for the data account on the client side, how the payer is funded with an airdrop, and how the test is executed with anchor test. The presenter also uses the Solana CLI to inspect the newly created account's data on-chain, confirming the discriminator and the stored value.
As you saw in the video, a common error when testing is Transaction simulation failed: Error processing Instruction 0: custom program error: 0x1. This usually means the payer account doesn't have enough SOL. The video shows the solution: airdropping SOL to the payer's keypair before running the test.
Conclusion
You have now mastered one of the most fundamental patterns in Solana development: creating new accounts. By using Anchor's init constraint, you can abstract away the low-level details of CPIs and rent, allowing you to focus on your program's logic.
Here are the key takeaways from this lesson:
- Solana requires explicit account initialization before data can be stored, a key difference from the EVM model.
- The
#[account(init, ...)]constraint is Anchor's declarative tool for creating and initializing new accounts. initrequires three companions:payer: TheSigneraccount that will pay the rent for the new account.space: The size in bytes to allocate, calculated as8 (discriminator) + size_of_your_data_struct.system_program: The Solana System Program, whichinitcalls to perform the creation.
- The
payeraccount must be marked asmutbecause its SOL balance will change. - Anchor protects you from accidentally re-initializing an existing account when using
init.
In the next lesson, we will formalize our knowledge of writing and running integration tests. While we've already seen how to test an initialize instruction, we will now cover the testing framework more broadly, learning how to test various instructions (increment, update, etc.) and make assertions about the state of our on-chain accounts.
Can't find a good explanation? Sign up and we'll make it for you
Sign up