分类 Solana-优秀转载 下的文章

Solana基础 - 如何关闭账户


关闭账户可让您重新获得用于开立账户的 SOL,但需要删除账户中的所有信息。关闭账户时,请确保在同一指令中将数据清零,以避免有人在同一交易中重新打开账户并访问数据。这是因为在交易完成之前,账户实际上并未关闭。

use solana_program::{
    account_info::next_account_info, account_info::AccountInfo, entrypoint,
    entrypoint::ProgramResult, pubkey::Pubkey,
};

entrypoint!(process_instruction);

fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();

    let source_account_info = next_account_info(account_info_iter)?;
    let dest_account_info = next_account_info(account_info_iter)?;

    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;

    source_account_info.assign(&system_program::ID);
    source_account_info.realloc(0, false).map_err(Into::into)

    Ok(())
}

https://solana.com/zh/developers/cookbook/accounts/close-account


Solana基础 - 如何使用 PDA 账户签名


程序派生地址 (PDA) 可用于拥有可签名程序所拥有的账户。如果您希望程序拥有代币账户,并且希望程序将代币从一个账户转移到另一个账户,那么这很有用。

use solana_program::{
    account_info::next_account_info, account_info::AccountInfo, entrypoint,
    entrypoint::ProgramResult, program::invoke_signed, pubkey::Pubkey, system_instruction,
};

entrypoint!(process_instruction);

fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();

    let pda_account_info = next_account_info(account_info_iter)?;
    let to_account_info = next_account_info(account_info_iter)?;
    let system_program_account_info = next_account_info(account_info_iter)?;

    // pass bump seed for saving compute budget
    let bump_seed = instruction_data[0];

    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]]],
    )?;

    Ok(())
}

https://solana.com/zh/developers/cookbook/accounts/sign-with-pda


Solana基础 - 如何创建 PDA 账户


程序派生地址 (PDA) 中的帐户只能在链上创建。这些帐户的地址具有关联的曲线外公钥,但没有密钥。

要生成 PDA,请使用findProgramAddressSync所需的种子。使用相同的种子生成将始终生成相同的 PDA。

生成 PDA

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

const programId = new PublicKey("G1DCNUQTSGHehwdLCAmRyAG8hf51eCHrLNUqkgGKYASj");

let [pda, bump] = PublicKey.findProgramAddressSync(
  [Buffer.from("test")],
  programId,
);
console.log(`bump: ${bump}, pubkey: ${pda.toBase58()}`);
// 你会发现结果与“createProgramAddress”不同。
// 这是预料之中的,因为我们用来计算的真实种子是 ["test" + bump]

在PDA上创建账户

程序

use solana_program::{
    account_info::next_account_info, account_info::AccountInfo, entrypoint,
    entrypoint::ProgramResult, program::invoke_signed, pubkey::Pubkey, system_instruction, sysvar::{rent::Rent, Sysvar}
};

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();

    let payer_account_info = next_account_info(account_info_iter)?;
    let pda_account_info = next_account_info(account_info_iter)?;
    let rent_sysvar_account_info = &Rent::from_account_info(next_account_info(account_info_iter)?)?;

    // find space and minimum rent required for account
    let space = instruction_data[0];
    let bump = instruction_data[1];
    let rent_lamports = rent_sysvar_account_info.minimum_balance(space.into());

    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]]]
    )?;

    Ok(())
}

客户端

import {
  clusterApiUrl,
  Connection,
  Keypair,
  Transaction,
  SystemProgram,
  PublicKey,
  TransactionInstruction,
  LAMPORTS_PER_SOL,
  SYSVAR_RENT_PUBKEY,
} from "@solana/web3.js";

(async () => {
  // program id
  const programId = new PublicKey(
    "7ZP42kRwUQ2zgbqXoaXzAFaiQnDyp6swNktTSv8mNQGN",
  );

  // connection
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

  // setup fee payer
  const feePayer = Keypair.generate();
  const feePayerAirdropSignature = await connection.requestAirdrop(
    feePayer.publicKey,
    LAMPORTS_PER_SOL,
  );
  await connection.confirmTransaction(feePayerAirdropSignature);

  // setup pda
  let [pda, bump] = await PublicKey.findProgramAddress(
    [feePayer.publicKey.toBuffer()],
    programId,
  );
  console.log(`bump: ${bump}, pubkey: ${pda.toBase58()}`);

  const data_size = 0;

  let tx = new Transaction().add(
    new TransactionInstruction({
      keys: [
        {
          pubkey: feePayer.publicKey,
          isSigner: true,
          isWritable: true,
        },
        {
          pubkey: pda,
          isSigner: false,
          isWritable: true,
        },
        {
          pubkey: SYSVAR_RENT_PUBKEY,
          isSigner: false,
          isWritable: false,
        },
        {
          pubkey: SystemProgram.programId,
          isSigner: false,
          isWritable: false,
        },
      ],
      data: Buffer.from(new Uint8Array([data_size, bump])),
      programId: programId,
    }),
  );

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

https://solana.com/zh/developers/cookbook/accounts/create-pda-account


Solana基础 - 如何计算账户创建成本


在 Solana 上保持账户活跃会产生数据存储成本,称为 租金。在计算时,您需要考虑您打算在帐户中存储的数据量。如果帐户关闭,可以全额收回租金。

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

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

// allocate 1.5k bytes of extra space in the account for data
const space = 1500;

const lamports = await connection.getMinimumBalanceForRentExemption(space);
console.log("Minimum balance for rent exemption:", lamports);

Solana基础 - 如何创建账户


创建帐户需要使用系统程序createAccount 指令。Solana 运行时将授予帐户所有者程序写入其数据或传输 Lamport 的权限。创建帐户时,我们必须预先分配固定的存储空间(以字节为单位)和足够的 Lamport 来支付租金。

import {
  SystemProgram,
  Keypair,
  Transaction,
  sendAndConfirmTransaction,
  Connection,
  clusterApiUrl,
  LAMPORTS_PER_SOL,
} from "@solana/web3.js";

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

const fromKeypair = Keypair.generate();
const newAccount = Keypair.generate();

// Airdrop SOL for transferring lamports to the created account
const airdropSignature = await connection.requestAirdrop(
  fromKeypair.publicKey,
  LAMPORTS_PER_SOL,
);
await connection.confirmTransaction(airdropSignature);

// amount of space to reserve for the account
const space = 0;

// Seed the created account with lamports for rent exemption
const rentLamports = await connection.getMinimumBalanceForRentExemption(space);

const createAccountTransaction = new Transaction().add(
  SystemProgram.createAccount({
    fromPubkey: fromKeypair.publicKey,
    newAccountPubkey: newAccount.publicKey,
    lamports: rentLamports,
    space,
    programId: SystemProgram.programId,
  }),
);

await sendAndConfirmTransaction(connection, createAccountTransaction, [
  fromKeypair,
  newAccount,
]);

https://solana.com/zh/developers/cookbook/accounts/create-account