Surou 发布的文章

使用Solana主网账户和程序


通常,本地测试依赖于本地验证器上默认不可用的程序和帐户。
Solana CLI 允许以下操作:

  • 下载程序和账户
  • 将程序和帐户加载到本地验证器

如何从主网加载账户

可以将JUP代币铸币账户下载到文件中:

# solana account -u <source cluster> --output <output format> --output-file <destination file name/path> <address of account to fetch>
solana account -u m --output json-compact --output-file jup.json JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN

然后通过在启动验证器时传递帐户的文件和目标地址(在本地集群上)将其加载到您的本地网络:

# solana-test-validator --account <address to load the account to> <path to account file> --reset
solana-test-validator --account JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN jup.json --reset

同样,也可以下载 Openbook 程序:

# solana program dump -u <source cluster> <address of account to fetch> <destination file name/path>
solana program dump -u m srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX openbook.so

然后通过在启动验证器时传递程序的文件和目标地址(在本地集群上)将其加载到您的本地网络:

# solana-test-validator --bpf-program <address to load the program to> <path to program file> --reset
solana-test-validator --bpf-program srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX openbook.so --reset

https://solana.com/zh/developers/cookbook/development/using-mainnet-accounts-programs


SysvarRent111111111111111111111111111111111 账户用途


在 Solana 区块链中, SysvarRent111111111111111111111111111111111 是一个系统变量(Sysvar)账户。系统变量账户是 Solana 网络中预定义的特殊账户,用于存储与网络运行相关的全局信息,这些信息可以被智能合约和客户端程序访问。

该账户的具体作用

SysvarRent111111111111111111111111111111111 账户专门用于存储租金(Rent)相关的信息。在 Solana 中,租金是为了确保链上存储的有效性和经济性而设计的一种机制。具体来说,该账户存储了以下重要信息:

  1. 租金费率(Rent Exemption) :

    • 这个账户记录了使账户免受租金影响所需的最低余额。如果一个账户的余额低于这个阈值,那么该账户需要定期支付租金,否则可能会被系统清除。
    • 智能合约和客户端程序可以通过读取这个系统变量来确定需要存入多少资金才能使账户免受租金的影响。
  2. 租金计算参数 :

    • 它还包含了用于计算租金的参数,例如每字节的租金费率和租金收取的周期。
    • 这些参数对于开发者和用户来说非常重要,因为它们决定了在 Solana 链上存储数据的成本。

代码示例

在 Solana 的 Rust 编程中,可以通过以下方式访问这个系统变量账户:

use anchor_lang::prelude::*;
use solana_program::sysvar::rent::Rent;

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

    pub fn read_rent_info(ctx: Context<ReadRentInfo>) -> Result<()> {
        let rent = &ctx.accounts.rent;
        // 读取租金豁免金额
        let rent_exemption = rent.minimum_balance(1024); // 假设账户大小为 1024 字节
        msg!("Rent exemption for 1024 bytes: {}", rent_exemption);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct ReadRentInfo<'info> {
    #[account(address = solana_program::sysvar::rent::ID)]
    pub rent: Account<'info, Rent>,
}

代码解释

在这个示例中,我们定义了一个名为 read_rent_info 的程序指令,用于读取租金信息。通过 Account 结构体,我们指定了 rent 账户的地址为 solana_program::sysvar::rent::ID ,即 SysvarRent111111111111111111111111111111111 。然后,我们可以通过 rent.minimum_balance 方法计算出指定大小的账户所需的租金豁免金额。

通过这种方式,开发者可以在智能合约中动态地获取租金信息,从而更好地管理账户的存储成本。


SysvarC1ock11111111111111111111111111111111 账户用途


在 Solana 区块链中, SysvarC1ock11111111111111111111111111111111 是一个系统变量账户(Sysvar),它代表着 Solana 网络中的时钟信息。下面详细介绍它的作用、使用场景以及相关代码示例。

作用

SysvarC1ock11111111111111111111111111111111 账户存储了 Solana 网络的全局时钟状态,包含了以下关键信息:

  • Unix 时间戳 :表示当前的 Unix 时间,精确到秒。这对于需要依赖时间的智能合约非常重要,比如设定特定时间的任务、活动的开始和结束时间等。
  • Slot 编号 :Slot 是 Solana 中用于衡量时间的基本单位,每个 Slot 代表一个短暂的时间间隔。通过 Slot 编号,可以了解当前区块链的进度和交易的顺序。
  • Epoch 编号 :Epoch 是一组连续的 Slot,通常用于表示区块链的一个周期。Epoch 编号有助于管理和更新网络的状态,例如验证者的轮换等。

使用场景

这个系统变量账户在 Solana 智能合约开发中有广泛的应用,以下是一些常见的使用场景:

  • 时间相关的条件判断 :智能合约可以根据当前的 Unix 时间戳来判断是否满足特定的时间条件,从而执行相应的操作。
  • 交易的时间限制 :在创建或处理交易时,可以使用时钟信息来设置交易的有效时间范围,防止过期交易被处理。
  • 数据的时效性 :对于需要保证数据时效性的应用,如价格预言机,智能合约可以根据时钟信息来判断数据是否过期。

代码示例

在 Rust 编写的 Solana 智能合约中,可以通过以下方式访问 SysvarC1ock11111111111111111111111111111111 账户:

use anchor_lang::prelude::*;
use solana_program::sysvar::clock::Clock;

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

    pub fn my_function(ctx: Context<MyContext>) -> Result<()> {
        // 获取时钟系统变量
        let clock = Clock::get()?;

        // 打印当前的 Unix 时间戳
        msg!("Current Unix timestamp: {}", clock.unix_timestamp);

        // 打印当前的 Slot 编号
        msg!("Current slot: {}", clock.slot);

        // 打印当前的 Epoch 编号
        msg!("Current epoch: {}", clock.epoch);

        Ok(())
    }
}

#[derive(Accounts)]
pub struct MyContext {}

代码解释

  • use solana_program::sysvar::clock::Clock; :引入 Clock 结构体,用于访问时钟系统变量。
  • let clock = Clock::get()?; :调用 Clock::get() 方法获取当前的时钟信息。
  • msg! 宏用于在 Solana 日志中打印信息,方便调试和监控。
    通过以上代码,智能合约可以轻松地获取和使用 Solana 网络的时钟信息,从而实现各种时间相关的逻辑。

传统 Solana 程序与 Anchor 程序的区别


在 Anchor 框架创建的 Solana 程序里,你看不到 process_instruction 函数,这是因为 Anchor 框架做了抽象和封装,把底层的 Solana 程序处理逻辑简化了。下面详细解释:

1. 传统 Solana 程序与 Anchor 程序的区别

  • 传统 Solana 程序: 在传统的 Solana 程序开发里,你得手动实现 process_instruction 函数。这个函数是程序的入口点,负责接收和处理交易指令。示例如下:
use solana_program::{
    account_info::AccountInfo, entrypoint, 
    entrypoint::ProgramResult, 
    pubkey::Pubkey,
};

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    // 处理指令逻辑
    Ok(())
}
  • Anchor 程序: Anchor 框架是一个高级的 Solana 程序开发框架,它隐藏了很多底层细节。Anchor 借助宏和自定义语法来定义程序逻辑,而不是直接实现 process_instruction 函数。

2. Anchor 程序的结构

在 Anchor 里,你通常会定义 #[program] 模块,在这个模块中定义指令处理函数。示例如下:

// lib.rs

use anchor_lang::prelude::*;

declare_id!
("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsL
nS");

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

    pub fn initialize(ctx: 
    Context<Initialize>) -> Result<()> {
        // 初始化逻辑
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    // 账户声明
}

在这个示例中,initialize 函数就是一个指令处理函数,它会在接收到相应的交易指令时被调用。Anchor 会自动处理 process_instruction 函数的实现,把交易指令路由到对应的处理函数。

3. 查看 Anchor 生成的 process_instruction

如果你想查看 Anchor 生成的 process_instruction 函数,可以编译程序,然后查看生成的 Rust 代码。执行以下命令编译程序:

anchor build

编译完成后,在 target/idl 目录下可以找到生成的 IDL(Interface Definition Language)文件,在 target/deploy 目录下可以找到生成的 Rust 代码。这些生成的代码包含了 process_instruction 函数的实现。

总结来说,Anchor 框架通过抽象和封装,让开发者不用直接处理 process_instruction 函数,从而提高了开发效率。

其它

Solana 传统程序: https://solana.com/zh/developers/guides/getstarted/local-rust-hello-world


Solana system程序功能分析


代码分析

版本:v2.0.14
核心代码:https://github.com/anza-xyz/agave/blob/v2.0.14/sdk/program/src/system_instruction.rs

SystemError

system程序调用中,常见的错误

错误类型 错误解释
AccountAlreadyInUse 具有相同地址的帐户已存在
ResultWithNegativeLamports 帐户没有足够的 SOL 来执行操作
InvalidProgramId 无法将帐户分配给此程序 ID
InvalidAccountDataLength 无法分配此长度的帐户数据
MaxSeedLengthExceeded 请求的Seed长度太长
AddressWithSeedMismatch 提供的地址与Seed得出的地址不匹配
NonceNoRecentBlockhashes 推进存储的随机数需要填充的RecentBlockhashes sysvar
NonceBlockhashNotExpired 存储的 nonce 仍在 recent_blockhashes 中
NonceUnexpectedBlockhashValue 指定的 nonce 与存储的 nonce 不匹配

固定变量

变量名 解释 默认值
MAX_PERMITTED_DATA_LENGTH 允许的最大帐户数据大小 10 MiB
MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION 每笔交易允许的新分配的最大大小,以字节为单位 MAX_PERMITTED_DATA_LENGTH x 2

数据结构

系统程序的指令和构造函数。
系统程序负责创建帐户和随机数帐户。它负责从系统程序拥有的帐户(包括典型的用户钱包帐户)转移 Lamport。
帐户创建通常涉及三个步骤:分配空间、转移 Lamport 以进行出租、分配给其拥有程序。create_account 函数一次完成所有三个步骤。所有新帐户必须包含足够的 Lamport 才能免除租金,否则创建指令将失败。
系统程序创建的帐户可以是用户控制的,其中密钥保存在区块链之外,也可以是程序派生的地址,其中拥有程序授予对帐户的写访问权限。
系统程序 ID 在 system_program 中定义。
此模块中的大多数函数都构造一个指令,必须将其提交给运行时执行,可以通过 RPC(通常使用 RpcClient)或通过跨程序调用。
通过 CPI 调用时,invoke 或invoke_signed 指令要求所有帐户引用都明确提供为 AccountInfo 值。所需的帐户引用在每个系统程序指令的 SystemInstruction 变体的文档中指定,并且这些变体与其构造函数的文档链接在一起。

pub enum SystemInstruction {
    /// 创建新账户
    /// # 账户引用
    /// 0. `[WRITE, SIGNER]` 资金账户
    /// 1. `[WRITE, SIGNER]` 新账户
    CreateAccount {
        /// 要转移到新帐户的lamports数量
        lamports: u64,

        /// 要分配的内存字节数
        space: u64,

        /// 所有者程序帐户地址
        owner: Pubkey,
    },

    /// 将账户分配给程序
    /// # 账户引用
    /// 0. `[WRITE, SIGNER]` 分配账户公钥
    Assign {
        /// 程序拥有者账户地址
        owner: Pubkey,
    },

    /// 转账 lampors
    ///
    /// # 账户引用
    /// 0. `[WRITE, SIGNER]` 资金账户
    /// 1. `[WRITE]` 收款账户
    Transfer { lamports: u64 },

    /// 在从基本公钥和种子派生的地址上创建新帐户
    ///
    /// # 帐户引用
    /// 0. `[WRITE, SIGNER]` 资金帐户
    /// 1. `[WRITE]` 创建帐户
    /// 2. `[SIGNER]` (可选) 基本帐户;必须将与以下基本公钥匹配的帐户
    /// 作为签名者提供,但可以与资金帐户相同
    /// 并作为帐户 0 提供
    CreateAccountWithSeed {
        /// 基本公钥
        base: Pubkey,

        /// ASCII 字符的字符串,不超过“Pubkey::MAX_SEED_LEN”
        seed: String,

        /// 要转移到新帐户的lamports数量
        lamports: u64,

        /// 要分配的内存字节数
        space: u64,

        /// 所有者程序帐户地址
        owner: Pubkey,
    },

    /// 使用存储的随机数,用后继者替换它
    ///
    /// # 帐户引用
    /// 0. `[WRITE]` 随机数帐户
    /// 1. `[]` 最近区块哈希系统变量
    /// 2. `[SIGNER]` 随机数权限
    AdvanceNonceAccount,

    /// 从 nonce 账户中提取资金
    ///
    /// # 账户引用
    /// 0. `[WRITE]` Nonce 账户
    /// 1. `[WRITE]` 接收者账户
    /// 2. `[]` RecentBlockhashes 系统变量
    /// 3. `[]` Rent 系统变量
    /// 4. `[SIGNER]` Nonce 权限
    ///
    /// `u64` 参数是要提取的 lampor,它必须使
    /// 账户余额高于免租储备或为零。
    WithdrawNonceAccount(u64),

    /// 将 Uninitialized nonce 账户的状态驱动为 Initialized,设置 nonce 值
    ///
    /// # 账户引用
    /// 0. `[WRITE]` Nonce 账户
    /// 1. `[]` RecentBlockhashes 系统变量
    /// 2. `[]` Rent 系统变量
    ///
    /// `Pubkey` 参数指定有权在账户上执行 nonce 指令的实体
    ///
    /// 执行此指令不需要签名,从而可以派生
    /// nonce 账户地址
    InitializeNonceAccount(Pubkey),

    /// 更改授权在账户上执行 nonce 指令的实体
    ///
    /// # 账户引用
    /// 0. `[WRITE]` Nonce 账户
    /// 1. `[SIGNER]` Nonce 授权
    ///
    /// `Pubkey` 参数标识要授权的实体
    AuthorizeNonceAccount(Pubkey),

    /// 在(可能是新的)账户中分配空间,无需资金
    ///
    /// # 账户引用
    /// 0. `[WRITE, SIGNER]` 新账户
    Allocate {
        /// 要分配的内存字节数
        space: u64,
    },

    /// 为某个地址分配空间并指定一个账户
    /// 源自一个基本公钥和一个种子
    ///
    /// # 账户引用
    /// 0. `[WRITE]` 已分配账户
    /// 1. `[SIGNER]` 基本账户
    AllocateWithSeed {
        /// 基本公钥
        base: Pubkey,

        /// ASCII 字符的字符串,不超过“pubkey::MAX_SEED_LEN”
        seed: String,

        /// 要分配的内存字节数
        space: u64,

        /// 所有者程序帐户地址
        owner: Pubkey,
    },

    /// 根据种子将账户分配给程序
    ///
    /// # 账户引用
    /// 0. `[WRITE]` 分配账户
    /// 1. `[SIGNER]` 基本账户
    AssignWithSeed {
        /// 基本公钥
        base: Pubkey,

        /// ASCII 字符的字符串,不超过“pubkey::MAX_SEED_LEN”
        seed: String,

        /// 所有者程序帐户地址
        owner: Pubkey,
    },

    /// 从派生地址转移 lamport
    ///
    /// # 账户引用
    /// 0. `[WRITE]` 资金账户
    /// 1. `[SIGNER]` 资金账户基础
    /// 2. `[WRITE]` 接收账户
    TransferWithSeed {
        /// 转账金额
        lamports: u64,

        /// 用于获取资金账户地址的Seed
        from_seed: String,

        /// 所有者用来获取资金账户地址
        from_owner: Pubkey,
    },

    /// 对旧版 nonce 版本进行一次性幂等升级,以便将它们从链块哈希域中剔除。
    ///
    /// # 帐户引用
    /// 0. `[WRITE]` Nonce 帐户
    UpgradeNonceAccount,
}

Create an account

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::CreateAccount。
帐户创建通常涉及三个步骤:分配空间、转移 Lamport 以进行租赁、分配给其拥有程序。create_account 函数一次完成所有三个步骤。

必需的签名者

from_pubkey 和 to_pubkey 签名者必须签署交易。

示例

这些示例使用 SystemInstruction::CreateAccount 的单次调用来创建新帐户、分配一些空间、向其转移最低 Lamport 以进行租赁豁免,并将其分配给系统程序,

示例:客户端 RPC

此示例从 RPC 客户端提交指令。付款人和 new_account 是签名者。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    system_program,
    transaction::Transaction,
};
use anyhow::Result;

fn create_account(
    client: &RpcClient,
    payer: &Keypair,
    new_account: &Keypair,
    space: u64,
) -> Result<()> {
    let rent = client. get_minimum_balance_for_rent_exemption(space. try_into()?)?;
    let instr = system_instruction::create_account(
        &payer. pubkey(),
        &new_account. pubkey(),
        rent,
        space,
        &system_program::ID,
    );

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &[instr],
        Some(&payer. pubkey()),
        &[payer, new_account],
        blockhash,
    );

    let _sig = client. send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例从链上 Solana 程序提交指令。创建的帐户是程序派生的地址。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

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

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CreateAccountInstruction {
    /// The PDA seed used to distinguish the new account from other PDAs
    pub new_account_seed: [u8; 16],
    /// The PDA bump seed
    pub new_account_bump_seed: u8,
    /// The amount of space to allocate for `new_account_pda`
    pub space: u64,
}

entrypoint!(process_instruction);

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

    let account_info_iter = &mut accounts. iter();

    let payer = next_account_info(account_info_iter)?;
    let new_account_pda = next_account_info(account_info_iter)?;
    let system_account = next_account_info(account_info_iter)?;

    assert!(payer. is_signer);
    assert!(payer. is_writable);
    // Note that `new_account_pda` is not a signer yet.
    // This program will sign for it via `invoke_signed`.
    assert!(!new_account_pda. is_signer);
    assert!(new_account_pda. is_writable);
    assert!(system_program::check_id(system_account. key));

    let new_account_seed = &instr. new_account_seed;
    let new_account_bump_seed = instr. new_account_bump_seed;

    let rent = Rent::get()?
        .minimum_balance(instr. space. try_into().expect("overflow"));

    invoke_signed(
        &system_instruction::create_account(
            payer. key,
            new_account_pda. key,
            rent,
            instr. space,
            &system_program::ID
        ),
        &[payer. clone(), new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    Ok(())
}
程序代码
pub fn create_account(
    from_pubkey: &Pubkey,
    to_pubkey: &Pubkey,
    lamports: u64,
    space: u64,
    owner: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, true),
        AccountMeta::new(*to_pubkey, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::CreateAccount {
            lamports,
            space,
            owner: *owner,
        },
        account_metas,
    )
}

// we accept `to` as a parameter so that callers do their own error handling when
//   calling create_with_seed()
pub fn create_account_with_seed(
    from_pubkey: &Pubkey,
    to_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner)
    base: &Pubkey,
    seed: &str,
    lamports: u64,
    space: u64,
    owner: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, true),
        AccountMeta::new(*to_pubkey, false),
        AccountMeta::new_readonly(*base, true),
    ];

    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::CreateAccountWithSeed {
            base: *base,
            seed: seed.to_string(),
            lamports,
            space,
            owner: *owner,
        },
        account_metas,
    )
}

从系统程序分配帐户所有权。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::Assign。
必需的签名者公钥签名者必须签署交易。

示例

这些示例为帐户分配空间,向其转移最低余额以免除租金,并将帐户分配给程序。

示例:客户端 RPC

此示例从 RPC 客户端提交指令。它将帐户分配给提供的程序帐户。付款人和新帐户是签名者。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn create_account(
    client: &RpcClient,
    payer: &Keypair,
    new_account: &Keypair,
    owning_program: &Pubkey,
    space: u64,
) -> Result<()> {
    let rent = client. get_minimum_balance_for_rent_exemption(space. try_into()?)?;

    let transfer_instr = system_instruction::transfer(
        &payer. pubkey(),
        &new_account. pubkey(),
        rent,
    );

    let allocate_instr = system_instruction::allocate(
        &new_account. pubkey(),
        space,
    );

    let assign_instr = system_instruction::assign(
        &new_account. pubkey(),
        owning_program,
    );

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &[transfer_instr, allocate_instr, assign_instr],
        Some(&payer. pubkey()),
        &[payer, new_account],
        blockhash,
    );

    let _sig = client. send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例提交来自链上 Solana 程序的指令。创建的帐户是程序派生的地址,由付款人提供资金,并分配给正在运行的程序。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

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

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CreateAccountInstruction {
    /// The PDA seed used to distinguish the new account from other PDAs
    pub new_account_seed: [u8; 16],
    /// The PDA bump seed
    pub new_account_bump_seed: u8,
    /// The amount of space to allocate for `new_account_pda`
    pub space: u64,
}

entrypoint!(process_instruction);

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

    let account_info_iter = &mut accounts. iter();

    let payer = next_account_info(account_info_iter)?;
    let new_account_pda = next_account_info(account_info_iter)?;
    let system_account = next_account_info(account_info_iter)?;

    assert!(payer. is_signer);
    assert!(payer. is_writable);
    // Note that `new_account_pda` is not a signer yet.
    // This program will sign for it via `invoke_signed`.
    assert!(!new_account_pda. is_signer);
    assert!(new_account_pda. is_writable);
    assert!(system_program::check_id(system_account. key));

    let new_account_seed = &instr. new_account_seed;
    let new_account_bump_seed = instr. new_account_bump_seed;

    let rent = Rent::get()?
        .minimum_balance(instr. space. try_into().expect("overflow"));

    invoke_signed(
        &system_instruction::transfer(
            payer. key,
            new_account_pda. key,
            rent,
        ),
        &[payer. clone(), new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::allocate(
            new_account_pda. key,
            instr. space,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::assign(
            new_account_pda. key,
            &program_id,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    Ok(())
}

程序代码

pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
    let account_metas = vec![AccountMeta::new(*pubkey, true)];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::Assign { owner: *owner },
        account_metas,
    )
}

pub fn assign_with_seed(
    address: &Pubkey, // must match create_with_seed(base, seed, owner)
    base: &Pubkey,
    seed: &str,
    owner: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*address, false),
        AccountMeta::new_readonly(*base, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::AssignWithSeed {
            base: *base,
            seed: seed.to_string(),
            owner: *owner,
        },
        account_metas,
    )
}

从系统程序拥有的帐户转移 lamport。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::Transfer。
必需的签名者from_pubkey 签名者必须签署交易。

示例

这些示例为帐户分配空间,向其转移最低余额以免除租金,并将帐户分配给程序。

示例:客户端 RPC

此示例从 RPC 客户端提交指令。它将帐户分配给提供的程序帐户。付款人和 new_account 是签名者。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn create_account(
    client: &RpcClient,
    payer: &Keypair,
    new_account: &Keypair,
    owning_program: &Pubkey,
    space: u64,
) -> Result<()> {
    let rent = client.get_minimum_balance_for_rent_exemption(space. try_into()?)?;

    let transfer_instr = system_instruction::transfer(
        &payer. pubkey(),
        &new_account. pubkey(),
        rent,
    );

    let allocate_instr = system_instruction::allocate(
        &new_account. pubkey(),
        space,
    );

    let assign_instr = system_instruction::assign(
        &new_account. pubkey(),
        owning_program,
    );

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &[transfer_instr, allocate_instr, assign_instr],
        Some(&payer. pubkey()),
        &[payer, new_account],
        blockhash,
    );

    let _sig = client.send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例提交来自链上 Solana 程序的指令。创建的帐户是程序派生的地址,由付款人提供资金,并分配给正在运行的程序。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

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

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CreateAccountInstruction {
    /// The PDA seed used to distinguish the new account from other PDAs
    pub new_account_seed: [u8; 16],
    /// The PDA bump seed
    pub new_account_bump_seed: u8,
    /// The amount of space to allocate for `new_account_pda`
    pub space: u64,
}

entrypoint!(process_instruction);

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

    let account_info_iter = &mut accounts. iter();

    let payer = next_account_info(account_info_iter)?;
    let new_account_pda = next_account_info(account_info_iter)?;
    let system_account = next_account_info(account_info_iter)?;

    assert!(payer. is_signer);
    assert!(payer. is_writable);
    // Note that `new_account_pda` is not a signer yet.
    // This program will sign for it via `invoke_signed`.
    assert!(!new_account_pda. is_signer);
    assert!(new_account_pda. is_writable);
    assert!(system_program::check_id(system_account. key));

    let new_account_seed = &instr. new_account_seed;
    let new_account_bump_seed = instr. new_account_bump_seed;

    let rent = Rent::get()?
        .minimum_balance(instr. space. try_into().expect("overflow"));

    invoke_signed(
        &system_instruction::transfer(
            payer. key,
            new_account_pda. key,
            rent,
        ),
        &[payer. clone(), new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::allocate(
            new_account_pda. key,
            instr. space,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::assign(
            new_account_pda. key,
            &program_id,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    Ok(())
}

程序代码

pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, true),
        AccountMeta::new(*to_pubkey, false),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::Transfer { lamports },
        account_metas,
    )
}

pub fn transfer_with_seed(
    from_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner)
    from_base: &Pubkey,
    from_seed: String,
    from_owner: &Pubkey,
    to_pubkey: &Pubkey,
    lamports: u64,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, false),
        AccountMeta::new_readonly(*from_base, true),
        AccountMeta::new(*to_pubkey, false),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::TransferWithSeed {
            lamports,
            from_seed,
            from_owner: *from_owner,
        },
        account_metas,
    )
}

为帐户分配空间。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::Allocate。
如果帐户的大小已大于 0,或者请求的大小大于 MAX_PERMITTED_DATA_LENGTH,则交易将失败。
必需的签名者公钥签名者必须签署交易。

示例

这些示例为帐户分配空间,向其转移租金豁免的最低余额,并将帐户分配给程序。

示例:客户端 RPC

此示例从 RPC 客户端提交指令。它将帐户分配给提供的程序帐户。付款人和新帐户是签名者。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn create_account(
    client: &RpcClient,
    payer: &Keypair,
    new_account: &Keypair,
    owning_program: &Pubkey,
    space: u64,
) -> Result<()> {
    let rent = client. get_minimum_balance_for_rent_exemption(space. try_into()?)?;

    let transfer_instr = system_instruction::transfer(
        &payer. pubkey(),
        &new_account. pubkey(),
        rent,
    );

    let allocate_instr = system_instruction::allocate(
        &new_account. pubkey(),
        space,
    );

    let assign_instr = system_instruction::assign(
        &new_account. pubkey(),
        owning_program,
    );

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &[transfer_instr, allocate_instr, assign_instr],
        Some(&payer. pubkey()),
        &[payer, new_account],
        blockhash,
    );

    let _sig = client. send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例提交来自链上 Solana 程序的指令。创建的帐户是程序派生的地址,由付款人提供资金,并分配给正在运行的程序。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

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

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CreateAccountInstruction {
    /// The PDA seed used to distinguish the new account from other PDAs
    pub new_account_seed: [u8; 16],
    /// The PDA bump seed
    pub new_account_bump_seed: u8,
    /// The amount of space to allocate for `new_account_pda`
    pub space: u64,
}

entrypoint!(process_instruction);

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

    let account_info_iter = &mut accounts. iter();

    let payer = next_account_info(account_info_iter)?;
    let new_account_pda = next_account_info(account_info_iter)?;
    let system_account = next_account_info(account_info_iter)?;

    assert!(payer. is_signer);
    assert!(payer. is_writable);
    // Note that `new_account_pda` is not a signer yet.
    // This program will sign for it via `invoke_signed`.
    assert!(!new_account_pda. is_signer);
    assert!(new_account_pda. is_writable);
    assert!(system_program::check_id(system_account. key));

    let new_account_seed = &instr. new_account_seed;
    let new_account_bump_seed = instr. new_account_bump_seed;

    let rent = Rent::get()?
        .minimum_balance(instr. space. try_into().expect("overflow"));

    invoke_signed(
        &system_instruction::transfer(
            payer. key,
            new_account_pda. key,
            rent,
        ),
        &[payer. clone(), new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::allocate(
            new_account_pda. key,
            instr. space,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::assign(
            new_account_pda. key,
            &program_id,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    Ok(())
}

程序代码

pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
    let account_metas = vec![AccountMeta::new(*pubkey, true)];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::Allocate { space },
        account_metas,
    )
}

pub fn allocate_with_seed(
    address: &Pubkey, // must match create_with_seed(base, seed, owner)
    base: &Pubkey,
    seed: &str,
    space: u64,
    owner: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*address, false),
        AccountMeta::new_readonly(*base, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::AllocateWithSeed {
            base: *base,
            seed: seed.to_string(),
            space,
            owner: *owner,
        },
        account_metas,
    )
}

将 lamport 从系统程序拥有的帐户转移到多个帐户。

此函数生成一个指令向量,该向量必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::Transfers。
所需签名者from_pubkey 签名者必须签署交易。

示例

示例:客户端 RPC

此示例在单个交易中执行多次转移。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn transfer_lamports_to_many(
    client: &RpcClient,
    from: &Keypair,
    to_and_amount: &[(Pubkey, u64)],
) -> Result<()> {
    let instrs = system_instruction::transfer_many(&from. pubkey(), to_and_amount);

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &instrs,
        Some(&from. pubkey()),
        &[from],
        blockhash,
    );

    let _sig = client. send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例从“银行”账户(由调用程序拥有的程序派生地址)进行多次转账。此示例提交来自链上 Solana 程序的指令。创建的帐户是程序派生的地址,并分配给正在运行的程序。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

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

/// # Accounts 
/// 
/// - 0: bank_pda - writable 
/// - 1: system_program - executable 
/// - *: to - writable
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct TransferLamportsToManyInstruction {
    pub bank_pda_bump_seed: u8,
    pub amount_list: Vec<u64>,
}

entrypoint!(process_instruction);

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

    let account_info_iter = &mut accounts. iter();

    let bank_pda = next_account_info(account_info_iter)?;
    let bank_pda_bump_seed = instr. bank_pda_bump_seed;
    let system_account = next_account_info(account_info_iter)?;

    assert!(system_program::check_id(system_account. key));

    let to_accounts = next_account_infos(account_info_iter, account_info_iter. len())?;

    for to_account in to_accounts {
         assert!(to_account. is_writable);
         // ... do other verification ...
    }

    let to_and_amount = to_accounts
        .iter()
        .zip(instr. amount_list. iter())
        .map(|(to, amount)| (*to. key, *amount))
        .collect::<Vec<(Pubkey, u64)>>();

    let instrs = system_instruction::transfer_many(bank_pda. key, to_and_amount. as_ref());

    for instr in instrs {
        invoke_signed(&instr, accounts, &[&[b"bank", &[bank_pda_bump_seed]]])?;
    }

    Ok(())
}

程序代码

pub fn transfer_many(from_pubkey: &Pubkey, to_lamports: &[(Pubkey, u64)]) -> Vec<Instruction> {
    to_lamports
        .iter()
        .map(|(to_pubkey, lamports)| transfer(from_pubkey, to_pubkey, *lamports))
        .collect()
}

创建一个包含持久交易 nonce 的帐户。

此函数生成一个指令向量,该向量必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::CreateAccount 和 SystemInstruction::InitializeNonceAccount。
持久交易 nonce 是一个特殊帐户,可以执行过去已签名的交易。
标准 Solana 交易包括最近的区块哈希(有时称为 nonce)。在执行过程中,Solana 运行时会验证最近的区块哈希大约不到两分钟,并且在这两分钟内没有执行具有相同区块哈希的其他相同交易。这些检查可防止意外重放交易。因此,不可能签署交易,等待两分钟以上,然后成功执行该交易。
持久交易 nonce 是标准最近区块哈希 nonce 的替代方案。它们存储在链上的帐户中,每次使用时,它们的值都会更改为新值以供下次使用。运行时会验证每个持久 nonce 值是否仅使用一次,并且对 nonce 的“旧”程度没有任何限制。由于它们存储在链上并需要额外的指令才能使用,因此使用持久交易 nonce 进行交易比使用标准交易更昂贵。
持久 nonce 的值本身就是一个区块哈希,可通过 nonce::state::Data 的区块哈希字段访问,该字段从 nonce 帐户数据反序列化而来。
基本的持久交易 nonce 生命周期是

  1. 使用 create_nonce_account 指令创建 nonce 帐户。
  2. 提交包含 advance_nonce_account 指令的特殊格式的交易。
  3. 通过使用 withdraw_nonce_account 指令撤回其 lamports 来销毁 nonce 帐户。

Nonce 帐户有一个关联的授权帐户,该帐户存储在其帐户数据中,可以使用 authorize_nonce_account 指令进行更改。授权机构必须签署包含 advance_nonce_account、authorize_nonce_account 和 withdraw_nonce_account 指令的交易。
Nonce 帐户归系统程序所有。
此构造函数创建 SystemInstruction::CreateAccount 指令和 SystemInstruction::InitializeNonceAccount 指令。
必需的签名者
from_pubkey 和 nonce_pubkey 签名者必须签署交易。

示例

从链下客户端创建 nonce 帐户:

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
    nonce::State,
};
use anyhow::Result;

fn submit_create_nonce_account_tx(
    client: &RpcClient,
    payer: &Keypair,
) -> Result<()> {

    let nonce_account = Keypair::new();

    let nonce_rent = client. get_minimum_balance_for_rent_exemption(State::size())?;
    let instr = system_instruction::create_nonce_account(
        &payer. pubkey(),
        &nonce_account. pubkey(),
        &payer. pubkey(), // Make the fee payer the nonce account authority
        nonce_rent,
    );

    let mut tx = Transaction::new_with_payer(&instr, Some(&payer. pubkey()));

    let blockhash = client. get_latest_blockhash()?;
    tx. try_sign(&[&nonce_account, payer], blockhash)?;

    client. send_and_confirm_transaction(&tx)?;

    Ok(())
}

程序代码

pub fn create_nonce_account(
    from_pubkey: &Pubkey,
    nonce_pubkey: &Pubkey,
    authority: &Pubkey,
    lamports: u64,
) -> Vec<Instruction> {
    vec![
        create_account(
            from_pubkey,
            nonce_pubkey,
            lamports,
            nonce::State::size() as u64,
            &system_program::id(),
        ),
        Instruction::new_with_bincode(
            system_program::id(),
            &SystemInstruction::InitializeNonceAccount(*authority),
            vec![
                AccountMeta::new(*nonce_pubkey, false),
                #[allow(deprecated)]
                AccountMeta::new_readonly(recent_blockhashes::id(), false),
                AccountMeta::new_readonly(rent::id(), false),
            ],
        ),
    ]
}

pub fn create_nonce_account_with_seed(
    from_pubkey: &Pubkey,
    nonce_pubkey: &Pubkey,
    base: &Pubkey,
    seed: &str,
    authority: &Pubkey,
    lamports: u64,
) -> Vec<Instruction> {
    vec![
        create_account_with_seed(
            from_pubkey,
            nonce_pubkey,
            base,
            seed,
            lamports,
            nonce::State::size() as u64,
            &system_program::id(),
        ),
        Instruction::new_with_bincode(
            system_program::id(),
            &SystemInstruction::InitializeNonceAccount(*authority),
            vec![
                AccountMeta::new(*nonce_pubkey, false),
                #[allow(deprecated)]
                AccountMeta::new_readonly(recent_blockhashes::id(), false),
                AccountMeta::new_readonly(rent::id(), false),
            ],
        ),
    ]
}

提高持久交易 nonce 的值。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::AdvanceNonceAccount。
每个依赖于持久交易 nonce 的交易都必须包含一个 SystemInstruction::AdvanceNonceAccount 指令作为消息中的第一个指令,由此函数创建。当包含在第一个位置时,Solana 运行时会将该交易识别为依赖于持久交易 nonce 的交易并对其进行相应处理。Message::new_with_nonce 函数可用于构造正确格式的消息,而无需直接调用 advance_nonce_account。
在构建包含 AdvanceNonceInstruction 的交易时,必须以不同的方式处理 recent_blockhash — 不是将其设置为最近的区块哈希,而是必须从 nonce 帐户中检索并反序列化 nonce 的值,并将该值指定为“最近的区块哈希”。可以使用 solana_rpc_client_nonce_utils::data_from_account 函数反序列化 nonce 帐户。
有关持久事务 nonce 的进一步描述,请参阅 create_nonce_account。
所需签名者
authorized_pubkey 签名者必须签署交易。

示例

使用持久 nonce 创建和签署交易:

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use std::path::Path;
use anyhow::Result;

fn create_transfer_tx_with_nonce(
    client: &RpcClient,
    nonce_account_pubkey: &Pubkey,
    payer: &Keypair,
    receiver: &Pubkey,
    amount: u64,
    tx_path: &Path,
) -> Result<()> {

    let instr_transfer = system_instruction::transfer(
        &payer. pubkey(),
        receiver,
        amount,
    );

    // In this example, `payer` is `nonce_account_pubkey`'s authority
    let instr_advance_nonce_account = system_instruction::advance_nonce_account(
        nonce_account_pubkey,
        &payer. pubkey(),
    );

    // The `advance_nonce_account` instruction must be the first issued in
    // the transaction.
    let message = Message::new(
        &[
            instr_advance_nonce_account,
            instr_transfer
        ],
        Some(&payer. pubkey()),
    );

    let mut tx = Transaction::new_unsigned(message);

    // Sign the tx with nonce_account's `blockhash` instead of the
    // network's latest blockhash.
    let nonce_account = client. get_account(nonce_account_pubkey)?;
    let nonce_data = solana_rpc_client_nonce_utils::data_from_account(&nonce_account)?;
    let blockhash = nonce_data. blockhash();

    tx. try_sign(&[payer], blockhash)?;

    // Save the signed transaction locally for later submission.
    save_tx_to_file(&tx_path, &tx)?;

    Ok(())
}

程序代码

pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*nonce_pubkey, false),
        #[allow(deprecated)]
        AccountMeta::new_readonly(recent_blockhashes::id(), false),
        AccountMeta::new_readonly(*authorized_pubkey, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::AdvanceNonceAccount,
        account_metas,
    )
}

从持久交易 nonce 账户中提取 lamport。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::WithdrawNonceAccount。
提取 nonce 账户的全部余额将导致运行时在成功完成交易后销毁它。
否则,nonce 账户必须保持大于或等于租金豁免所需的最低余额。如果此指令的结果使 nonce 账户的余额低于租金豁免所需的余额,但也大于零,则交易将失败。
此构造函数创建一个 SystemInstruction::WithdrawNonceAccount 指令。
必需的签名者
authorized_pubkey 签名者必须签署交易。

示例

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn submit_withdraw_nonce_account_tx(
    client: &RpcClient,
    nonce_account_pubkey: &Pubkey,
    authorized_account: &Keypair,
) -> Result<()> {

    let nonce_balance = client. get_balance(nonce_account_pubkey)?;

    let instr = system_instruction::withdraw_nonce_account(
        &nonce_account_pubkey,
        &authorized_account. pubkey(),
        &authorized_account. pubkey(),
        nonce_balance,
    );

    let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account. pubkey()));

    let blockhash = client. get_latest_blockhash()?;
    tx. try_sign(&[authorized_account], blockhash)?;

    client. send_and_confirm_transaction(&tx)?;

    Ok(())
}

程序代码

pub fn withdraw_nonce_account(
    nonce_pubkey: &Pubkey,
    authorized_pubkey: &Pubkey,
    to_pubkey: &Pubkey,
    lamports: u64,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*nonce_pubkey, false),
        AccountMeta::new(*to_pubkey, false),
        #[allow(deprecated)]
        AccountMeta::new_readonly(recent_blockhashes::id(), false),
        AccountMeta::new_readonly(rent::id(), false),
        AccountMeta::new_readonly(*authorized_pubkey, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::WithdrawNonceAccount(lamports),
        account_metas,
    )
}

更改持久交易 nonce 帐户的权限。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::AuthorizeNonceAccount。
此构造函数创建一个 SystemInstruction::AuthorizeNonceAccount 指令。
必需的签名者
authorized_pubkey 签名者必须签署交易。

示例

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn authorize_nonce_account_tx(
    client: &RpcClient,
    nonce_account_pubkey: &Pubkey,
    authorized_account: &Keypair,
    new_authority_pubkey: &Pubkey,
) -> Result<()> {

    let instr = system_instruction::authorize_nonce_account(
        &nonce_account_pubkey,
        &authorized_account. pubkey(),
        &new_authority_pubkey,
    );

    let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account. pubkey()));

    let blockhash = client. get_latest_blockhash()?;
    tx. try_sign(&[authorized_account], blockhash)?;

    client. send_and_confirm_transaction(&tx)?;

    Ok(())
}

程序代码

pub fn authorize_nonce_account(
    nonce_pubkey: &Pubkey,
    authorized_pubkey: &Pubkey,
    new_authority: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*nonce_pubkey, false),
        AccountMeta::new_readonly(*authorized_pubkey, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::AuthorizeNonceAccount(*new_authority),
        account_metas,
    )
}

对旧版 nonce 版本进行一次性幂等升级,以将其排除在链块哈希域之外。

pub fn upgrade_nonce_account(nonce_pubkey: Pubkey) -> Instruction {
    let account_metas = vec![AccountMeta::new(nonce_pubkey, /*is_signer:*/ false)];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::UpgradeNonceAccount,
        account_metas,
    )
}