账户类型

摘要

程序账户存放可执行的 sBPF 代码。数据账户存储状态,并由程序拥有。系统账户由 System Program 拥有。Sysvar 提供集群范围的状态,可通过预定义地址访问。

executable 字段决定了账户的类别:

  • 程序账户:executable = true。包含可执行代码。
  • 数据账户:executable = false。存储状态或用户数据。

这种将代码与可变状态分离的设计意味着,程序只需部署一次,即可管理任意数量的数据账户。

程序账户

程序账户用于存储可执行代码。每个程序账户都由一个加载器程序(loader program)拥有。部署程序时,运行时会创建一个程序账户来保存其字节码。

程序账户、其 4 个组成部分及其加载器程序的示意图。程序账户、其 4 个组成部分及其加载器程序的示意图。

程序数据账户

使用 loader-v3 部署的程序(参见加载器程序)不会在自身的 data 字段中存储可执行字节码。相反,它们的 data 会指向一个单独的程序数据账户,该账户包含程序代码。(见下图。)

带有数据的程序账户。该数据指向一个单独的程序数据账户带有数据的程序账户。该数据指向一个单独的程序数据账户

在程序部署或升级期间,缓冲账户(buffer account)用于临时暂存上传的内容。

以下示例获取 Token Program 账户。executable 字段为 true,这证实它是一个程序账户。

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

数据账户

数据账户不包含可执行代码。它们存储由程序定义的状态。

程序状态账户

程序将其状态存储在数据账户中。创建程序状态账户包括两个步骤:

  1. 调用 System Program 来创建账户。System Program 会将所有权转移给指定的程序。
  2. 拥有该账户的程序根据其指令初始化账户的 data 字段。

由程序账户拥有的数据账户示意图由程序账户拥有的数据账户示意图

以下示例创建并获取一个由 Token 2022 程序拥有的 Token Mint 账户。

import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";
import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";
import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";
import { systemProgram } from "@solana-program/system";
import {
getInitializeMintInstruction,
getMintSize,
TOKEN_2022_PROGRAM_ADDRESS,
fetchMint
} from "@solana-program/token-2022";
const client = await createClient()
.use(generatedPayer())
.use(
solanaRpc({
rpcUrl: "http://localhost:8899",
rpcSubscriptionsUrl: "ws://localhost:8900"
})
)
.use(rpcAirdrop())
.use(airdropPayer(lamports(1_000_000_000n)))
.use(systemProgram());
// 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 client.rpc.getMinimumBalanceForRentExemption(space).send();
const transactionSignature = await client.sendTransaction([
client.system.instructions.createAccount({
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
}),
getInitializeMintInstruction({
mint: mint.address,
decimals: 9,
mintAuthority: client.payer.address
})
]);
console.log("Mint Address:", mint.address);
console.log("Transaction Signature:", transactionSignature.context.signature);
const accountInfo = await client.rpc.getAccountInfo(mint.address).send();
console.log(accountInfo);
const mintAccount = await fetchMint(client.rpc, mint.address);
console.log(mintAccount);
Console
Click to execute the code.

系统账户

创建后仍由 System Program 拥有的账户称为系统账户。首次向一个新地址发送 SOL 时,会在该地址创建一个由 System Program 拥有的新账户。

所有钱包账户都是系统账户。交易的手续费支付者必须是系统账户,因为只有由 System Program 拥有的账户才能支付交易费用。

由 System Program 拥有、包含 1,000,000 lamport 的钱包由 System Program 拥有、包含 1,000,000 lamport 的钱包

以下示例生成一个新的 keypair,用 SOL 为其充值,并获取该账户。owner 字段为 11111111111111111111111111111111(即 System Program)。

import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";
import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";
import { generatedPayer } from "@solana/kit-plugin-signer";
const client = await createClient()
.use(generatedPayer())
.use(
solanaRpc({
rpcUrl: "http://localhost:8899",
rpcSubscriptionsUrl: "ws://localhost:8900"
})
)
.use(rpcAirdrop());
// 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 client.airdrop(
keypair.address,
lamports(1_000_000_000n)
);
const accountInfo = await client.rpc.getAccountInfo(keypair.address).send();
console.log(accountInfo);
Console
Click to execute the code.

Sysvar 账户

Sysvar 账户是位于预定义地址的特殊账户,提供对集群状态数据的只读访问。它们会在每个 slot 动态更新。

Sysvar地址用途
ClockSysvarC1ock11111111111111111111111111111111当前 slot、epoch 和 Unix 时间戳
EpochScheduleSysvarEpochSchedu1e111111111111111111111111在创世区块中设定的 epoch 调度常量
EpochRewardsSysvarEpochRewards1111111111111111111111111epoch 奖励的分发状态和进度
RentSysvarRent111111111111111111111111111111111rent 费率和免除阈值
SlotHashesSysvarS1otHashes111111111111111111111111111slot 父 bank 的最新哈希值
StakeHistorySysvarStakeHistory1111111111111111111111111每个 epoch 的质押激活与停用情况
LastRestartSlotSysvarLastRestartS1ot1111111111111111111111集群最近一次重启的 slot
InstructionsSysvar1nstructions1111111111111111111111111当前交易的序列化指令
SlotHistorySysvarS1otHistory11111111111111111111111111上一个 epoch 中已产生 slot 的记录

以下示例获取并反序列化 Sysvar Clock 账户。

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

Is this page helpful?

Table of Contents

Edit Page
©️ 2026 Solana 基金会版权所有