Solana Account Model

On Solana, all data is stored in what are called "accounts." You can think of data on Solana as a public database with a single "Accounts" table, where each entry in this table is an "account." Every Solana account shares the same base Account type.

AccountsAccounts

Key Points

  • Accounts can store up to 10MiB of data, which contains either executable program code or program state.
  • Accounts require a rent deposit in lamports (SOL) that's proportional to the amount of data stored, and you can fully recover it when you close the account.
  • Every account has a program owner. Only the program that owns an account can change its data or deduct its lamport balance. But anyone can increase the balance.
  • Sysvar accounts are special accounts that store network cluster state.
  • Program accounts store the executable code of smart contracts.
  • Data accounts are created by programs to store and manage program state.

Account

Every account on Solana has a unique 32 byte address, often shown as a base58 encoded string (e.g. 14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5).

The relationship between the account and its address works like a key-value pair, where the address is the key to locate the corresponding on-chain data of the account. The account address acts as the "unique ID" for each entry in the "Accounts" table.

Account AddressAccount Address

Most Solana accounts use an Ed25519 public key as their address.

import { generateKeyPairSigner } from "@solana/kit";
// Kit does not enable extractable private keys
const keypairSigner = await generateKeyPairSigner();
console.log(keypairSigner);
Console
Click to execute the code.

While public keys are commonly used as account addresses, Solana also supports a feature called Program Derived Addresses (PDAs). PDAs are special addresses that you can deterministically derive from a program ID and optional inputs (seeds).

import { Address, getProgramDerivedAddress } from "@solana/kit";
const programAddress = "11111111111111111111111111111111" as Address;
const seeds = ["helloWorld"];
const [pda, bump] = await getProgramDerivedAddress({
programAddress,
seeds
});
console.log(`PDA: ${pda}`);
console.log(`Bump: ${bump}`);
Console
Click to execute the code.

Account Type

Accounts have a max size of 10MiB and every account on Solana shares the same base Account type.

Account TypeAccount Type

Every Account on Solana has the following fields.

Base Account Type
pub struct Account {
/// lamports in the account
pub lamports: u64,
/// data held in this account
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
pub data: Vec<u8>,
/// the program that owns this account. If executable, the program that loads this account.
pub owner: Pubkey,
/// this account's data contains a loaded program (and is now read-only)
pub executable: bool,
/// the epoch at which this account will next owe rent
pub rent_epoch: Epoch,
}

Lamports Field

The account's balance in lamports, the smallest unit of SOL (1 SOL = 1 billion lamports). An account's SOL balance is the amount in the lamports field converted to SOL.

Solana accounts must have a minimum lamport balance that is proportional to the amount of data stored on the account (in bytes). This minimum balance is called "rent."

The lamport balance stored in the account can be fully recovered when the account is closed.

Data Field

A byte array that stores arbitrary data for an account. The data field is commonly called "account data."

  • For program accounts (smart contracts), this field contains either the executable program code itself or the address of another account that stores the executable program code.
  • For non-executable accounts, this generally stores state that's meant be read from.

Reading data from a Solana account involves two steps:

  1. Fetch the account using its address (public key)
  2. Deserialize the account's data field from raw bytes into the appropriate data structure, which is defined by the program that owns the account

Owner Field

The program ID (public key) of the program that owns this account.

Every Solana account has a designated program as its owner. Only the program that owns an account can change the account's data or deduct its lamports balance.

The instructions defined in a program determine how the account's data and lamports balance can be changed.

Executable Field

This field indicates if an account is an executable program.

  • If true, the account is an executable Solana program.
  • If false, the account is a data account that stores state.

For executable accounts, the owner field contains the program ID of a loader program. Loader programs are built-in programs responsible for loading and managing executable program accounts.

Rent Epoch Field

The rent_epoch field is a legacy field that is no longer used.

Originally, this field tracked when an account would need to pay rent (in lamports) to maintain its data on the network. However, this rent collection mechanism has since been deprecated.

Lamports Field

The account's balance in lamports, the smallest unit of SOL (1 SOL = 1 billion lamports). An account's SOL balance is the amount in the lamports field converted to SOL.

Solana accounts must have a minimum lamport balance that is proportional to the amount of data stored on the account (in bytes). This minimum balance is called "rent."

The lamport balance stored in the account can be fully recovered when the account is closed.

Data Field

A byte array that stores arbitrary data for an account. The data field is commonly called "account data."

  • For program accounts (smart contracts), this field contains either the executable program code itself or the address of another account that stores the executable program code.
  • For non-executable accounts, this generally stores state that's meant be read from.

Reading data from a Solana account involves two steps:

  1. Fetch the account using its address (public key)
  2. Deserialize the account's data field from raw bytes into the appropriate data structure, which is defined by the program that owns the account

Owner Field

The program ID (public key) of the program that owns this account.

Every Solana account has a designated program as its owner. Only the program that owns an account can change the account's data or deduct its lamports balance.

The instructions defined in a program determine how the account's data and lamports balance can be changed.

Executable Field

This field indicates if an account is an executable program.

  • If true, the account is an executable Solana program.
  • If false, the account is a data account that stores state.

For executable accounts, the owner field contains the program ID of a loader program. Loader programs are built-in programs responsible for loading and managing executable program accounts.

Rent Epoch Field

The rent_epoch field is a legacy field that is no longer used.

Originally, this field tracked when an account would need to pay rent (in lamports) to maintain its data on the network. However, this rent collection mechanism has since been deprecated.

Base Account Type
pub struct Account {
/// lamports in the account
pub lamports: u64,
}
// Example Token Mint Account
Account {
lamports: 1461600,
}
// Example Token Program Account
Account {
lamports: 4513200894,
}

Rent

To store data on-chain, accounts must also keep a lamport (SOL) balance that's proportional to the amount of data stored on the account (in bytes). This balance is called "rent," but it works more like a deposit because you can recover the full amount when you close an account. You can find the calculation here using these constants.

The term "rent" comes from a deprecated mechanism that regularly deducted lamports from accounts that fell below the rent threshold. This mechanism is no longer active.

Program Owner

On Solana, "smart contracts" are called programs. Program ownership is a key part of the Solana Account Model. Every account has a designated program as its owner. Only the owner program can:

  • Change the account's data field
  • Deduct lamports from the account's balance

Each program defines the structure of the data stored in an account's data field. The program's instructions determine how this data and the account's lamports balance can be changed.

System Program

By default, all new accounts are owned to the System Program. The System Program performs the following key functions:

FunctionDescription
New Account CreationOnly the System Program can create new accounts.
Space AllocationSets the byte capacity for the data field of each account.
Assign Program OwnershipOnce the System Program creates an account, it can reassign the designated program owner to a different program account. That's how custom programs take ownership of new accounts created by the System Program.
Transfer SOLTransfers lamports (SOL) from System Accounts to other accounts.

Note that all "wallet" accounts on Solana are "System Accounts" owned by the System Program. The lamport balance in these accounts shows the amount of SOL owned by the wallet. Only System Accounts can pay transaction fees.

System AccountSystem Account

When SOL is sent to a new address for the first time, an account is automatically created at that address owned by the System Program.

In the example below, a new keypair is generated and funded with SOL. Run the code to see the output. Note that the owner field of the account is the System Program with the address 11111111111111111111111111111111.

import {
airdropFactory,
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
lamports
} from "@solana/kit";
// Create a connection to Solana cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate a new keypair
const keypair = await generateKeyPairSigner();
console.log(`Public Key: ${keypair.address}`);
// Funding an address with SOL automatically creates an account
const signature = await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: keypair.address,
lamports: lamports(1_000_000_000n),
commitment: "confirmed"
});
const accountInfo = await rpc.getAccountInfo(keypair.address).send();
console.log(accountInfo);
Console
Click to execute the code.

Sysvar Accounts

Sysvar accounts are special accounts at predefined addresses that provide access to cluster state data. These accounts update dynamically with data about the network cluster. You can find the full list of Sysvar Accounts here.

The following example shows how to fetch and deserialize the data from the Sysvar Clock account.

import { createSolanaRpc } from "@solana/kit";
import { fetchSysvarClock, SYSVAR_CLOCK_ADDRESS } from "@solana/sysvars";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const accountInfo = await rpc
.getAccountInfo(SYSVAR_CLOCK_ADDRESS, { encoding: "base64" })
.send();
console.log(accountInfo);
// Automatically fetch and deserialize the account data
const clock = await fetchSysvarClock(rpc);
console.log(clock);
Console
Click to execute the code.

Program Account

Deploying a Solana program creates an executable program account. The program account stores the executable code of the program. Program accounts are owned by a Loader Program.

Program AccountProgram Account

For simplicity, you can treat the program account as the program itself. When you invoke a program's instructions, you specify the program account's address (commonly called the "Program ID").

The following example fetches the Token Program account to show that program accounts have the same base Account type, except the executable field is set to true. Since program accounts contain executable code in their data field, we do not deserialize the data.

import { Address, createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const programId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address;
const accountInfo = await rpc
.getAccountInfo(programId, { encoding: "base64" })
.send();
console.log(accountInfo);
Console
Click to execute the code.

When you deploy a Solana program, it's stored in a program account. Program accounts are owned by a Loader Program. There are several versions of the loader, but all except loader-v3 store the executable code directly in the program account. Loader-v3 stores the executable code in a separate "program data account" and the program account just points to it. When you deploy a new program, the Solana CLI uses the latest loader version by default.

Buffer Account

Loader-v3 has a special account type for temporarily staging the upload of a program during deployment or upgrades. In loader-v4, there are still buffers, but they're just normal program accounts.

Program Data Account

Loader-v3 works differently from all other BPF Loader programs. The program account only contains the address of a program data account, which stores the actual executable code:

Program Data AccountProgram Data Account

Don't confuse these program data accounts with the data accounts of programs (see below).

Data Account

On Solana, the executable code of a program is stored in a different account than the program's state. This is like how operating systems typically have separate files for programs and their data.

To maintain state, programs define instructions to create separate accounts that they own. Each of these accounts has its own unique address and can store any arbitrary data defined by the program.

Data AccountData Account

Note that only the System Program can create new accounts. Once the System Program creates an account, it can then assign ownership of the new account to another program.

In other words, creating a data account for a custom program takes two steps:

  1. Invoke the System Program to create an account, then transfer ownership to the custom program
  2. Invoke the custom program, which now owns the account, to initialize the account data as defined by the program's instruction

This account creation process is often abstracted as a single step, but it's helpful to understand the underlying process.

The following example shows how to create and fetch a Token Mint account owned by the Token 2022 program.

import {
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from "@solana/kit";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
getInitializeMintInstruction,
getMintSize,
TOKEN_2022_PROGRAM_ADDRESS,
fetchMint
} from "@solana-program/token-2022";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate keypairs for fee payer
const feePayer = await generateKeyPairSigner();
// Fund fee payer
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: feePayer.address,
lamports: lamports(1_000_000_000n),
commitment: "confirmed"
});
// Generate keypair to use as address of mint
const mint = await generateKeyPairSigner();
// Get default mint account size (in bytes), no extensions enabled
const space = BigInt(getMintSize());
// Get minimum balance for rent exemption
const rent = await rpc.getMinimumBalanceForRentExemption(space).send();
// Instruction to create new account for mint (token 2022 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token 2022 program
const initializeMintInstruction = getInitializeMintInstruction({
mint: mint.address,
decimals: 9,
mintAuthority: feePayer.address
});
const instructions = [createAccountInstruction, initializeMintInstruction];
// Get latest blockhash to include in transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }), // Create transaction message
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx), // Set fee payer
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), // Set transaction blockhash
(tx) => appendTransactionMessageInstructions(instructions, tx) // Append instructions
);
// Sign transaction message with required signers (fee payer and mint keypair)
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Send and confirm transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
// Get transaction signature
const transactionSignature = getSignatureFromTransaction(signedTransaction);
console.log("Mint Address:", mint.address);
console.log("Transaction Signature:", transactionSignature);
const accountInfo = await rpc.getAccountInfo(mint.address).send();
console.log(accountInfo);
const mintAccount = await fetchMint(rpc, mint.address);
console.log(mintAccount);
Console
Click to execute the code.

Is this page helpful?

सामग्री तालिका

पृष्ठ संपादित करें