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);
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}`);
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:

  • data: A byte array that stores arbitrary data for an account. For non-executable accounts, this often stores state that's meant be read from. For program accounts (smart contracts), this contains the executable program code. The data field is commonly called "account data."
  • executable: This flag shows if an account is a program.
  • lamports: The account's balance in lamports, the smallest unit of SOL (1 SOL = 1 billion lamports).
  • owner: The program ID (public key) of the program that owns this account. Only the owner program can change the account's data or deduct its lamports balance.
  • rent_epoch: A legacy field from when Solana had a mechanism that periodically deducted lamports from accounts. While this field still exists in the Account type, it is no longer used since rent collection was deprecated.
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,
}
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);
Click to execute the code.

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 isn't active anymore.

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

System Program

By default, all new accounts are owned to the System Program. The System Program does a few key things:

  • New Account Creation: Only the System Program can create new accounts.
  • Space Allocation: Sets the byte capacity for the data field of each account.
  • Transfer / Assign Program Ownership: Once 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.

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

System AccountSystem Account

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.

import { Address, createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const SYSVAR_CLOCK_ADDRESS =
"SysvarC1ock11111111111111111111111111111111" as Address;
const accountInfo = await rpc
.getAccountInfo(SYSVAR_CLOCK_ADDRESS, { encoding: "base64" })
.send();
console.log(accountInfo);
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").

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);
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 redeployment/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 transfer or 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.

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
} from "@solana-program/token-2022";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://127.0.0.1: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);
Click to execute the code.

Is this page helpful?

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

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