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

Solana基础 - 如何创建代币


创建代币是通过创建所谓的“铸币账户”来完成的。这个铸币账户稍后会用来将代币铸入用户的代币账户。

import {
  clusterApiUrl,
  Connection,
  Keypair,
  sendAndConfirmTransaction,
  SystemProgram,
  Transaction,
} from "@solana/web3.js";
import {
  createInitializeMintInstruction,
  TOKEN_PROGRAM_ID,
  MINT_SIZE,
  getMinimumBalanceForRentExemptMint,
  createMint,
} from "@solana/spl-token";
import bs58 from "bs58";

(async () => {
  // connection
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const recentBlockhash = await connection.getLatestBlockhash();

  // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8
  const feePayer = Keypair.fromSecretKey(
    bs58.decode(
      "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2",
    ),
  );

  // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY
  const alice = Keypair.fromSecretKey(
    bs58.decode(
      "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp",
    ),
  );

  // 1) use build-in function
  let mintPubkey = await createMint(
    connection, // connection
    feePayer, // fee payer
    alice.publicKey, // mint authority
    alice.publicKey, // freeze authority (you can use `null` to disable it. when you disable it, you can't turn it on again)
    8, // decimals
  );
  console.log(`mint: ${mintPubkey.toBase58()}`);

  // or

  // 2) compose by yourself
  const mint = Keypair.generate();
  console.log(`mint: ${mint.publicKey.toBase58()}`);

  const transaction = new Transaction().add(
    // create mint account
    SystemProgram.createAccount({
      fromPubkey: feePayer.publicKey,
      newAccountPubkey: mint.publicKey,
      space: MINT_SIZE,
      lamports: await getMinimumBalanceForRentExemptMint(connection),
      programId: TOKEN_PROGRAM_ID,
    }),
    // init mint account
    createInitializeMintInstruction(
      mint.publicKey, // mint pubkey
      8, // decimals
      alice.publicKey, // mint authority
      alice.publicKey, // freeze authority (you can use `null` to disable it. when you disable it, you can't turn it on again)
    ),
  );

  // Send transaction
  const transactionSignature = await sendAndConfirmTransaction(
    connection,
    transaction,
    [feePayer, mint], // Signers
  );

  console.log(`txhash: ${transactionSignature}`);
})();

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


Solana基础 - 如何在 Solana 程序中验证账户


由于 Solana 中的程序是无状态的,因此作为程序创建者,我们必须确保传递的帐户尽可能得到验证,以避免任何恶意帐户进入。可以进行的基本检查包括

  1. 检查预期签名者帐户是否确实已签名
  2. 检查预期状态帐户是否已被检查为可写
  3. 检查预期状态账户的所有者是否是被调用的程序 ID
  4. 如果第一次初始化状态,请检查账户是否已经初始化。
  5. 检查传递的任何跨程序 ID(无论何时需要)是否符合预期。

初始化HelloState账户的基本指令,但使用上述检查,定义如下

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    clock::Clock,
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program_error::ProgramError,
    pubkey::Pubkey,
    rent::Rent,
    system_program::ID as SYSTEM_PROGRAM_ID,
    sysvar::Sysvar,
};

entrypoint!(process_instruction);

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloState {
    is_initialized: bool,
}

// Accounts required
/// 1. [signer] Payer
/// 2. [writable] Hello state account
/// 3. [] System Program
pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();
    // Payer account
    let payer_account = next_account_info(accounts_iter)?;
    // Hello state account
    let hello_state_account = next_account_info(accounts_iter)?;
    // System Program
    let system_program = next_account_info(accounts_iter)?;

    let rent = Rent::get()?;

    // 检查付款人账户是否是签名者
    if !payer_account.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // 检查 Hello State 账户是否免租
    if !rent.is_exempt(hello_state_account.lamports(), 1) {
        return Err(ProgramError::AccountNotRentExempt);
    }

    // 检查 hello 状态帐户是否可写
    if !hello_state_account.is_writable {
        return Err(ProgramError::InvalidAccountData);
    }

    // 检查 hello 状态账户的所有者是否是当前程序
    if hello_state_account.owner.ne(&program_id) {
        return Err(ProgramError::IllegalOwner);
    }

    // 检查系统程序是否有效
    if system_program.key.ne(&SYSTEM_PROGRAM_ID) {
        return Err(ProgramError::IncorrectProgramId);
    }

    let mut hello_state = HelloState::try_from_slice(&hello_state_account.data.borrow())?;

    // 检查状态是否已初始化
    if hello_state.is_initialized {
        return Err(ProgramError::AccountAlreadyInitialized);
    }

    hello_state.is_initialized = true;
    hello_state.serialize(&mut &mut hello_state_account.data.borrow_mut()[..])?;
    msg!("Account initialized :)");

    Ok(())
}

https://solana.com/zh/developers/cookbook/programs/verify-accounts


Solana基础 - 读取多个指令


Solana 允许我们查看当前交易中的所有指令。我们可以将它们存储在变量中并对其进行迭代。我们可以用它做很多事情,比如检查可疑交易。

use anchor_lang::{
    prelude::*,
    solana_program::{
        sysvar,
        serialize_utils::{read_pubkey,read_u16}
    }
};

declare_id!("8DJXJRV8DBFjJDYyU9cTHBVK1F1CTCi6JUBDVfyBxqsT");

#[program]
pub mod cookbook {
    use super::*;

    pub fn read_multiple_instruction<'info>(ctx: Context<ReadMultipleInstruction>, creator_bump: u8) -> Result<()> {
        let instruction_sysvar_account = &ctx.accounts.instruction_sysvar_account;

        let instruction_sysvar_account_info = instruction_sysvar_account.to_account_info();

        let id = "8DJXJRV8DBFjJDYyU9cTHBVK1F1CTCi6JUBDVfyBxqsT";

        let instruction_sysvar = instruction_sysvar_account_info.data.borrow();

        let mut idx = 0;

        let num_instructions = read_u16(&mut idx, &instruction_sysvar)
        .map_err(|_| MyError::NoInstructionFound)?;

        for index in 0..num_instructions {
            let mut current = 2 + (index * 2) as usize;
            let start = read_u16(&mut current, &instruction_sysvar).unwrap();

            current = start as usize;
            let num_accounts = read_u16(&mut current, &instruction_sysvar).unwrap();
            current += (num_accounts as usize) * (1 + 32);
            let program_id = read_pubkey(&mut current, &instruction_sysvar).unwrap();

            if program_id != id
            {
                msg!("Transaction had ix with program id {}", program_id);
                return Err(MyError::SuspiciousTransaction.into());
            }
        }

        Ok(())
    }

}

#[derive(Accounts)]
#[instruction(creator_bump:u8)]
pub struct ReadMultipleInstruction<'info> {
    #[account(address = sysvar::instructions::id())]
    instruction_sysvar_account: UncheckedAccount<'info>
}

#[error_code]
pub enum MyError {
    #[msg("No instructions found")]
    NoInstructionFound,
    #[msg("Suspicious transaction detected")]
    SuspiciousTransaction
}

https://solana.com/zh/developers/cookbook/programs/read-multiple-instructions


Solana基础 - 如何在程序中读取帐户


Solana 中的几乎所有指令都需要至少 2 - 3 个帐户,并且它们
会在指令处理程序中提及,说明这些帐户集的预期顺序。如果我们利用iter() Rust 中的方法,而不是手动索引帐户,那么这相当简单。该 next_account_info方法基本上对可迭代对象的第一个索引进行切片,并返回帐户数组中存在的帐户。让我们看一个简单的指令,它需要一堆帐户并需要解析每个帐户。

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

entrypoint!(process_instruction);

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloState {
    is_initialized: bool,
}

// Accounts required
/// 1. [signer] Payer
/// 2. [writable] Hello state account
/// 3. [] Rent account
/// 4. [] System Program
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    // Fetching all the accounts as a iterator (facilitating for loops and iterations)
    let accounts_iter = &mut accounts.iter();
    // Payer account
    let payer_account = next_account_info(accounts_iter)?;
    // Hello state account
    let hello_state_account = next_account_info(accounts_iter)?;
    // Rent account
    let rent_account = next_account_info(accounts_iter)?;
    // System Program
    let system_program = next_account_info(accounts_iter)?;

    Ok(())
}

https://solana.com/zh/developers/cookbook/programs/read-accounts


Solana基础 - 如何创建程序派生地址


程序派生地址只是程序拥有的一个帐户,但没有私钥。相反,它的签名是通过一组种子和一个碰撞(一个
随机数,确保它“偏离曲线”,即不是有效的公钥)获得的。“生成”程序地址
不同于“创建”它。可以使用生成 PDA Pubkey::find_program_address。创建 PDA 本质上意味着用空间初始化地址并将状态设置为它。可以在我们的程序之外创建一个普通的密钥对帐户,然后输入以初始化它的状态。不幸的是,对于 PDA,它必须在链上创建,因为它不能代表自己签名。因此,我们使用invoke_signed传递 PDA 的种子,以及资金账户的签名,从而创建 PDA 账户。

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

entrypoint!(process_instruction);

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloState {
    is_initialized: bool,
}

// Accounts required
/// 1. [signer, writable] Funding account
/// 2. [writable] PDA account
/// 3. [] System Program
pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    const ACCOUNT_DATA_LEN: usize = 1;

    let accounts_iter = &mut accounts.iter();
    // Getting required accounts
    let funding_account = next_account_info(accounts_iter)?;
    let pda_account = next_account_info(accounts_iter)?;
    let system_program = next_account_info(accounts_iter)?;

    // Getting PDA Bump from instruction data
    let (pda_bump, _) = instruction_data
        .split_first()
        .ok_or(ProgramError::InvalidInstructionData)?;

    // Checking if passed PDA and expected PDA are equal
    let signers_seeds: &[&[u8]; 3] = &[
        b"customaddress",
        &funding_account.key.to_bytes(),
        &[*pda_bump],
    ];
    let pda = Pubkey::create_program_address(signers_seeds, program_id)?;

    if pda.ne(&pda_account.key) {
        return Err(ProgramError::InvalidAccountData);
    }

    // Assessing required lamports and creating transaction instruction
    let lamports_required = Rent::get()?.minimum_balance(ACCOUNT_DATA_LEN);
    let create_pda_account_ix = system_instruction::create_account(
        &funding_account.key,
        &pda_account.key,
        lamports_required,
        ACCOUNT_DATA_LEN.try_into().unwrap(),
        &program_id,
    );
    // Invoking the instruction but with PDAs as additional signer
    invoke_signed(
        &create_pda_account_ix,
        &[
            funding_account.clone(),
            pda_account.clone(),
            system_program.clone(),
        ],
        &[signers_seeds],
    )?;

    // Setting state for PDA
    let mut pda_account_state = HelloState::try_from_slice(&pda_account.data.borrow())?;
    pda_account_state.is_initialized = true;
    pda_account_state.serialize(&mut &mut pda_account.data.borrow_mut()[..])?;

    Ok(())
}

可以通过客户端发送所需的帐户,如下所示

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

const PAYER_KEYPAIR = Keypair.generate();

(async () => {
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const latestBlockHash = await connection.getLatestBlockhash();
  const programId = new PublicKey(
    "6eW5nnSosr2LpkUGCdznsjRGDhVb26tLmiM1P8RV1QQp",
  );

  // Airdrop to Payer
  await connection.confirmTransaction(
    {
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: await connection.requestAirdrop(
        PAYER_KEYPAIR.publicKey,
        LAMPORTS_PER_SOL,
      ),
    },
    "confirmed",
  );

  const [pda, bump] = await PublicKey.findProgramAddress(
    [Buffer.from("customaddress"), PAYER_KEYPAIR.publicKey.toBuffer()],
    programId,
  );

  console.log(`PDA Pubkey: ${pda.toString()}`);

  const createPDAIx = new TransactionInstruction({
    programId: programId,
    data: Buffer.from(Uint8Array.of(bump)),
    keys: [
      {
        isSigner: true,
        isWritable: true,
        pubkey: PAYER_KEYPAIR.publicKey,
      },
      {
        isSigner: false,
        isWritable: true,
        pubkey: pda,
      },
      {
        isSigner: false,
        isWritable: false,
        pubkey: SystemProgram.programId,
      },
    ],
  });

  const transaction = new Transaction();
  transaction.add(createPDAIx);

  const txHash = await sendAndConfirmTransaction(connection, transaction, [
    PAYER_KEYPAIR,
  ]);
  console.log(`Created PDA successfully. Tx Hash: ${txHash}`);
})();

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