Skip to main content

账户

如何创建系统账户

创建一个由系统程序 拥有的账户。Solana运行时将授予账户的所有者对其数据的写入权限或转移Lamports的访问权限。在创建账户时,我们需要预先分配一定大小的存储空间(space)和足够的Lamports来支付租金。 租金(Rent) 是在Solana上保持账户活跃所需支付的费用。

const createAccountParams = {
fromPubkey: fromPubkey.publicKey,
newAccountPubkey: newAccountPubkey.publicKey,
lamports: rentExemptionAmount,
space,
programId: SystemProgram.programId,
};

const createAccountTransaction = new Transaction().add(
SystemProgram.createAccount(createAccountParams)
);

await sendAndConfirmTransaction(connection, createAccountTransaction, [
fromPubkey,
newAccountPubkey,
]);

如何计算账户费用

在Solana上保持账户活跃会产生一项存储费用,称为 租金/rent。通过存入至少两年租金的金额,你可以使账户完全免除租金收取。对于费用的计算,你需要考虑你打算在账户中存储的数据量。

import { Connection, clusterApiUrl } from "@solana/web3.js";

(async () => {
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

// length of data in the account to calculate rent for
const dataLength = 1500;
const rentExemptionAmount =
await connection.getMinimumBalanceForRentExemption(dataLength);
console.log({
rentExemptionAmount,
});
})();

如何使用种子创建账户

你可以使用 createAccountWithSeed 方法来管理您的账户,而无需创建大量不同的密钥对。

生成

PublicKey.createWithSeed(basePubkey, seed, programId);

创建

const tx = new Transaction().add(
SystemProgram.createAccountWithSeed({
fromPubkey: feePayer.publicKey, // funder
newAccountPubkey: derived,
basePubkey: basePubkey,
seed: seed,
lamports: 1e8, // 0.1 SOL
space: 0,
programId: owner,
})
);

console.log(
`txhash: ${await sendAndConfirmTransaction(connection, tx, [feePayer, base])}`
);

转账

const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: derived,
basePubkey: basePubkey,
toPubkey: Keypair.generate().publicKey, // create a random receiver
lamports: 0.01 * LAMPORTS_PER_SOL,
seed: seed,
programId: programId,
})
);
console.log(
`txhash: ${await sendAndConfirmTransaction(connection, tx, [feePayer, base])}`
);
info

Only an account owned by system program can transfer via system program.

贴士 只有由系统程序拥有的账户才能通过系统程序进行转账。

如何创建PDA

程序派生地址/Program derived address(PDA) 与普通地址相比具有以下区别:

  1. 不在ed25519曲线上
  2. 使用程序进行签名,而不是使用私钥
info

注意: PDA账户只能在程序上创建,地址可以在客户端创建。

贴士 尽管PDA是由程序ID派生的,但这并不意味着PDA归属于相同的程序。(举个例子,你可以将PDA初始化为代币账户,这是一个由代币程序拥有的账户)

生成一个PDA

findProgramAddress会在你的种子末尾添加一个额外的字节。它从255递减到0,并返回第一个不在ed25519曲线上的公钥。如果您传入相同的程序ID和种子,您将始终获得相同的结果。

import { PublicKey } from "@solana/web3.js";

(async () => {
const programId = new PublicKey(
"G1DCNUQTSGHehwdLCAmRyAG8hf51eCHrLNUqkgGKYASj"
);

let [pda, bump] = await PublicKey.findProgramAddress(
[Buffer.from("test")],
programId
);
console.log(`bump: ${bump}, pubkey: ${pda.toBase58()}`);
// you will find the result is different from `createProgramAddress`.
// It is expected because the real seed we used to calculate is ["test" + bump]
})();

创建一个PDA

以下是一个创建由程序拥有的PDA账户的示例程序,以及一个使用客户端调用该程序的示例。

下面是一个示例,展示了使用system_instruction::create_account创建一个具有预分配数据大小为space、预支付rent_lamports数量的lamports的PDA账户的单条指令。该指令使用PDA进行签名,并使用invoke_signed进行调用,与前面讨论的类似。

invoke_signed(
&system_instruction::create_account(
&payer_account_info.key,
&pda_account_info.key,
rent_lamports,
space.into(),
program_id
),
&[
payer_account_info.clone(),
pda_account_info.clone()
],
&[&[&payer_account_info.key.as_ref(), &[bump]]]
)?;

如何使用PDA签名

PDAs只能在程序内部进行签名。以下是使用PDA进行签名的程序示例,并使用客户端调用该程序的示例。

以下示例展示了一个单个指令,用于从由种子escrow派生的 PDA 转账 SOL 到指定的账户。使用 invoke_signed 函数来使用 PDA 签名。

invoke_signed(
&system_instruction::transfer(
&pda_account_info.key,
&to_account_info.key,
100_000_000, // 0.1 SOL
),
&[
pda_account_info.clone(),
to_account_info.clone(),
system_program_account_info.clone(),
],
&[&[b"escrow", &[bump_seed]]],
)?;

如何获取程序账户

返回所有由程序拥有的账户。请参考 指南部分 ,获取有关getProgramAccounts及其配置的更多信息。

import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";

(async () => {
const MY_PROGRAM_ID = new PublicKey(
"6a2GdmttJdanBkoHt7f4Kon4hfadx4UTUgJeRkCaiL3U"
);
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

const accounts = await connection.getProgramAccounts(MY_PROGRAM_ID);

console.log(`Accounts for program ${MY_PROGRAM_ID}: `);
console.log(accounts);

/*
// Output

Accounts for program 6a2GdmttJdanBkoHt7f4Kon4hfadx4UTUgJeRkCaiL3U:
[
{
account: {
data: <Buffer 60 06 66 ca 2c 1d c7 85 04 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 fc>,
executable: false,
lamports: 1064880,
owner: [PublicKey],
rentEpoch: 228
},
pubkey: PublicKey {
_bn: <BN: 82fc5b91154dc5c840cb464ba6a89212d0fd789367c0a1488fb1941d78f9727a>
}
},
{
account: {
data: <Buffer 60 06 66 ca 2c 1d c7 85 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 fd>,
executable: false,
lamports: 1064880,
owner: [PublicKey],
rentEpoch: 229
},
pubkey: PublicKey {
_bn: <BN: 404dc1fe368cf194f20cf3c681a071c61893ced98f65cda12ba5a147e984e669>
}
}
]
*/
})();

如何关闭账户

你可以通过移除账户中的所有 SOL(以擦除所有存储数据的方式)来关闭一个账户。(你可以参考rent来了解更多信息。)

let dest_starting_lamports = dest_account_info.lamports();
**dest_account_info.lamports.borrow_mut() = dest_starting_lamports
.checked_add(source_account_info.lamports())
.unwrap();
**source_account_info.lamports.borrow_mut() = 0;

let mut source_data = source_account_info.data.borrow_mut();
source_data.fill(0);

如何获取账户余额

console.log(`${(await connection.getBalance(wallet)) / LAMPORTS_PER_SOL} SOL`);
info

如果你想获取代币余额,你需要知道代币账户的地址。如果像了解更多信息,请参考Token References。