您正在查看: Solana-优秀转载 分类下的文章

如何使用 Anchor 转移 SOL 和 SPL 代币

概述

Anchor 是一个加速在 Solana 区块链上开发安全程序的框架。在使用 Solana 和 Anchor 时,你可能会遇到需要在账户之间发送 SOL 或 SPL 代币的情况(例如,处理用户支付到你的国库,或让用户将他们的 NFT 发送到托管账户)。本指南将引导你通过使用 Anchor 转移 SOL 和 SPL 代币的过程。我们将涵盖程序和测试所需的代码,以确保账户之间的代币无缝转移。

你将做什么

在本指南中,你将:

  • 使用 Anchor 和 Solana Playground 创建一个 Solana 程序
  • 创建一个程序指令,将 SOL 在两个用户之间发送
  • 创建一个程序指令,将 SPL 代币在两个用户之间发送
  • 编写测试以验证代币转移

你将需要什么

本指南中使用的依赖

依赖项 版本
anchor-lang 0.26.0
anchor-spl 0.26.0
solana-program 1.14.12
spl-token 3.5.0

启动你的项目

通过访问 https://beta.solpg.io/ 在 Solana Playground 创建一个新项目。Solana Playground 是一个基于浏览器的 Solana 代码编辑器,可以让我们快速启动这个项目。你可以在自己的代码编辑器中跟随,但本指南将根据 Solana Playground 的必需步骤进行调整。首先,点击“创建新项目”:

输入项目名称“transfers”,选择“Anchor (Rust)”:

创建并连接钱包

由于此项目仅用于演示目的,我们可以使用一个“临时”钱包。Solana Playground 使创建钱包变得简单。你应该看到浏览器窗口左下角显示红点“未连接”。点击它:

Solana Playground 将为你生成一个钱包(或者你可以导入自己的钱包)。可以保存以备后用,当你准备好时点击继续。一个新的钱包将被初始化并连接到 Solana 开发网络。Solana Playground 会自动向你的新钱包空投一些 SOL,但我们会请求一些额外的 SOL,以确保我们有足够的资金来部署我们的程序。在浏览器终端中,你可以使用 Solana CLI 命令。输入 solana airdrop 2 向你的钱包空投 2 SOL。你的钱包现在应已连接到开发网络,余额为 2 SOL:
https://explorer.solana.com/address/DGbfDHnXsQSyswLjkEkadxjiJmK3SUfrPueokNHYXEpi?cluster=devnet
你准备好了!让我们开始构建!

创建转账程序

首先打开 lib.rs 并删除启动代码。拥有一个空白的界面后,我们可以开始构建程序。首先,我们需要导入一些依赖项。将以下内容添加到文件顶部:

use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer as SplTransfer};
use solana_program::system_instruction;
declare_id!("11111111111111111111111111111111");

这些导入将允许我们使用 Anchor 框架、SPL 代币程序和系统程序。Solana Playground 在我们部署程序时将自动更新 declare_id!。

创建转移 Lamports (SOL) 函数

要创建一个转移 SOL(或 lamports)的函数,我们必须为我们的转移上下文定义一个结构。将以下内容添加到你的程序中:

#[derive(Accounts)]
pub struct TransferLamports<'info> {
    #[account(mut)]
    pub from: Signer<'info>,
    #[account(mut)]
    pub to: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

该结构定义了一个将签名 transaction 并发送 SOL 的 from 账户,一个将接收 SOL 的 to 账户,以及处理转账的系统程序。属性 #[account(mut)] 表示程序将修改该账户。
接下来,我们将创建处理转账的函数。将以下内容添加到你的程序中:

#[program]
pub mod solana_lamport_transfer {
    use super::*;
    pub fn transfer_lamports(ctx: Context<TransferLamports>, amount: u64) -> Result<()> {
        let from_account = &ctx.accounts.from;
        let to_account = &ctx.accounts.to;

        // Create the transfer instruction
        let transfer_instruction =
            system_instruction::transfer(from_account.key, to_account.key, amount);

        // Invoke the transfer instruction
        anchor_lang::solana_program::program::invoke_signed(
            &transfer_instruction,
            &[
                from_account.to_account_info(),
                to_account.clone(),
                ctx.accounts.system_program.to_account_info(),
            ],
            &[],
        )?;

        Ok(())
    }
}

以下是该代码片段不同部分的简要解释:

  1. #[program] 属性将模块标记为 Anchor 程序。它生成所需的模板以定义程序的入口点,并自动处理账户验证和反序列化。
  2. solana_lamport_transfer 模块内部,使用 use super::*; 导入父模块所需的项。
  3. transfer_lamports 函数接受一个 Context 和一个 amount 作为其参数。Context 包含交易所需的账户信息,amount 是要转移的 lamports 数量。
  4. 我们创建对上下文中的 from_accountto_account 的引用,这些引用将用于转账。
  5. system_instruction::transfer 函数创建一个转账指令,该指令接受 from_account 的公钥、to_account 的公钥以及要转移的 amount 作为参数。
  6. anchor_lang::solana_program::program::invoke_signed 函数调用转账指令,并使用交易的签名者(from_account)。它接受转账指令、from_accountto_accountsystem_program 的账户信息数组,以及一个空数组作为签名者。
  7. transfer_lamports 函数返回 Ok(()) 以表示执行成功。

你可以通过点击 Build 按钮或在终端中输入 anchor build 来确保一切正常。如果出现错误,请检查你的代码与本指南中的代码并遵循错误响应的建议。

创建转移 SPL 代币的功能

在我们部署程序之前,让我们添加一个第二个功能来转移 SPL 代币。首先,创建该功能的新上下文。在你的程序中 TransferLamports 结构下添加以下内容

#[derive(Accounts)]
pub struct TransferSpl<'info> {
    pub from: Signer<'info>,
    #[account(mut)]
    pub from_ata: Account<'info, TokenAccount>,
    #[account(mut)]
    pub to_ata: Account<'info, TokenAccount>,
    pub token_program: Program<'info, Token>,
}

此结构将需要 from 钱包(我们的签名者)、来自钱包的关联代币账户(ATA: Associated Token Account)、目的钱包的 ATA 和代币程序。你不需要目的钱包的主账户,因为它将保持不变(只有其 ATA 会被修改)。现在让我们创建我们的函数。在 solana_lamport_transfer 模块中,在 transfer_lamports 指令下,添加以下内容:

pub fn transfer_spl_tokens(ctx: Context<TransferSpl>, amount: u64) -> Result<()> {
    let destination = &ctx.accounts.to_ata;
    let source = &ctx.accounts.from_ata;
    let token_program = &ctx.accounts.token_program;
    let authority = &ctx.accounts.from;

    // 从承接者转账给初始化者
    let cpi_accounts = SplTransfer {
        from: source.to_account_info().clone(),
        to: destination.to_account_info().clone(),
        authority: authority.to_account_info().clone(),
    };
    let cpi_program = token_program.to_account_info();

    token::transfer(CpiContext::new(cpi_program, cpi_accounts), amount)?;
    Ok(())
}

让我们逐步分析这个函数:

  1. transfer_spl_tokens 函数接受一个 Context 和一个 amount 作为参数。TransferSpl 上下文包含我们在前一步中定义的交易所需账户信息。
  2. 我们从上下文中创建对 destinationsourcetoken_programauthority 的引用。这些变量分别代表目的 ATA、源 ATA、代币程序和签名者的钱包。
  3. 使用 sourcedestinationauthority 的账户信息创建 SplTransfer 结构。当进行跨程序调用(CPI)到 SPL Token 程序时,此结构将提供账户信息。
  4. 使用 cpi_programcpi_accounts 以及要转移的 amount 调用 token::transfer 函数。此函数执行指定 ATA 之间的实际代币转移。
  5. 我们返回 Ok(()) 以表示成功执行。

继续构建你的程序,以确保一切正常工作,方法是点击“Build”或输入 anchor build

如果程序成功构建,你可以将其部署到 Solana 开发网络。

部署程序

点击页面左侧的工具图标,然后点击“Deploy”:

当前测试时至少需要5 SOL, 如果不足,使用https://faucet.solana.com/ 进行领水

这可能需要一分钟或两分钟,但完成后,你应该在浏览器终端中看到类似如下的信息:

Program ID:4d39PLXUXdtmWN571CUETGpvnXgf7iAQ35WYSX5J5kGX

干得好!让我们测试一下。

测试程序

返回你编辑 lib.rs 文件的主文件窗口,点击页面左上角的图标。打开 anchor.test.ts 并用以下内容替换其内容:

import {
  createMint,
  createAssociatedTokenAccount,
  mintTo,
  TOKEN_PROGRAM_ID,
} from "@solana/spl-token";

describe("Test transfers", () => {});

测试转移 Lamports

在你的测试套件中,添加以下代码:

describe("Test transfers", () => {
  it("transferLamports", async () => {
    // 生成新账户的密钥对
    const newAccountKp = new web3.Keypair();
    // 发送交易
    const data = new BN(1000000);
    const tx = await pg.program.methods
      .transferLamports(data)
      .accounts({
        from: pg.wallet.publicKey,
        to: newAccountKp.publicKey,
      })
      .signers([pg.wallet.keypair])
      .transaction();
    const txHash = await web3.sendAndConfirmTransaction(
      pg.program.provider.connection,
      tx,
      [pg.wallet.keypair]
    );
    console.log(`https://explorer.solana.com/tx/${txHash}?cluster=devnet`);
    const newAccountBalance = await pg.program.provider.connection.getBalance(
      newAccountKp.publicKey
    );
    assert.strictEqual(
      newAccountBalance,
      data.toNumber(),
      "The new account should have the transferred lamports"
    );
  });
});


https://explorer.solana.com/tx/126HvQaMXvPYoJQbwZLk68rmPf98uMvmV7ucqd6KtypD3xNqUYh5XjD6VVsVsXHsysKvgFQrTC1ER1SGND1LHaQ4?cluster=devnet

在这个 transferLamports 测试中发生了什么:

  1. 我们为目的账户生成一个新的密钥对。
  2. 我们将转移的金额定义为 data,其值设为 1,000,000 lamports (0.001 SOL)(注意:Anchor 期望我们将此值作为大数字类型传递)。
  3. 我们通过调用 pg.program.methods.transferLamports(data) 执行 Solana 程序的 transferLamports 函数。该交易使用 accounts 方法指定所用账户,其中 from 账户是测试钱包的公钥,to 账户是新生成账户的公钥。Anchor 知道我们需要系统程序,因此这里不需要传递它。
  4. 交易使用测试钱包的密钥对进行签名,通过 signers 方法传递。
  5. 使用 transaction() 方法创建交易。
  6. 测试使用 sendAndConfirmTransaction() 等待交易被 确认。在我们检查新账户的余额时,确保它已更新为转移的金额是很重要的。
  7. 使用 getBalance() 获取新账户的余额,并将其存储在 newAccountBalance 变量中。
  8. 使用 assert.strictEqual 进行断言,以确认新账户的余额与转移金额匹配。只有当余额匹配预期金额时,测试才会成功。

测试转移 SPL 代币

在你的 transferLamports 测试之后,但在同一个测试套件中,添加一个测试以测试你的 SPL 代币转移:

describe("Test transfers", () => {
  it("transferSplTokens", async () => {
    // 为新账户生成密钥对
    const fromKp = pg.wallet.keypair;
    const toKp = new web3.Keypair();
    // 创建新的铸币并初始化它
    const mint = await createMint(
      pg.program.provider.connection,
      pg.wallet.keypair,
      fromKp.publicKey,
      null,
      0
    );
    // 为新账户创建关联代币账户
    const fromAta = await createAssociatedTokenAccount(
      pg.program.provider.connection,
      pg.wallet.keypair,
      mint,
      fromKp.publicKey
    );
    const toAta = await createAssociatedTokenAccount(
      pg.program.provider.connection,
      pg.wallet.keypair,
      mint,
      toKp.publicKey
    );
    // 铸造代币到 'from' 关联代币账户
    const mintAmount = 1000;
    await mintTo(
      pg.program.provider.connection,
      pg.wallet.keypair,
      mint,
      fromAta,
      pg.wallet.keypair.publicKey,
      mintAmount
    );
    // 发送交易
    const transferAmount = new BN(500);
    const tx = await pg.program.methods
      .transferSplTokens(transferAmount)
      .accounts({
        from: fromKp.publicKey,
        fromAta: fromAta,
        toAta: toAta,
        tokenProgram: TOKEN_PROGRAM_ID,
      })
      .signers([pg.wallet.keypair, fromKp])
      .transaction();
    const txHash = await web3.sendAndConfirmTransaction(
      pg.program.provider.connection,
      tx,
      [pg.wallet.keypair, fromKp]
    );
    console.log(`https://explorer.solana.com/tx/${txHash}?cluster=devnet`);
    const toTokenAccount = await pg.connection.getTokenAccountBalance(toAta);
    assert.strictEqual(
      toTokenAccount.value.uiAmount,
      transferAmount.toNumber(),
      "The 'to' token account should have the transferred tokens"
    );
  });
});

以下是 transferSplTokens 测试中发生的事情:

  1. 我们为目标账户生成一个新的密钥对。
  2. 我们创建一个新的铸币并初始化它。
  3. 我们为源钱包和目标钱包创建与新代币铸币关联的关联代币账户。
  4. 我们向源(来自钱包)的关联代币账户铸造 1,000 个代币。
  5. 我们执行在程序中创建的 transferSplTokens 指令。此次交易使用的账户通过 accounts 方法指定,其中 from 帐户是测试钱包的公钥,fromAta 帐户是源关联代币账户,toAta 帐户是目标关联代币账户,而 tokenProgram 是 SPL Token 程序 ID。
  6. 使用 transaction() 方法创建交易。
  7. 测试通过 await sendAndConfirmTransaction() 等待交易被 最终确定。这对于确保当我们检查新账户的余额时,已更新为转移的金额非常重要。
  8. 使用 getTokenAccountBalance() 获取新代币账户的余额,并将其存储在 toTokenAccount 变量中。
  9. 使用 assert.strictEqual 进行断言以确认新账户的余额与转移金额匹配。仅当余额与预期金额匹配时,测试才会成功。

干得不错——让我们测试一下!按下屏幕左侧的 测试 按钮运行测试,如下所示:
https://explorer.solana.com/tx/2eAQANiLddDh3fxyqkc28LmVNpWQHNeQUi3waxnu3qoNev9h1juX5npkbprFen3u8Xm8JzpdrstMbBevxcUWcEuB?cluster=devnet

总结

你已经使用自己的 Solana 程序实现了本地 SOL 转账和 SPL 代币转账。这是构建自己的 NFT 项目、游戏或 DeFi 应用程序的一个很好的开始。继续构建吧!

完整代码

use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer as SplTransfer};
use solana_program::system_instruction;
declare_id!("4d39PLXUXdtmWN571CUETGpvnXgf7iAQ35WYSX5J5kGX");

#[derive(Accounts)]
pub struct TransferLamports<'info> {
    #[account(mut)]
    pub from: Signer<'info>,
    #[account(mut)]
    pub to: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct TransferSpl<'info> {
    pub from: Signer<'info>,
    #[account(mut)]
    pub from_ata: Account<'info, TokenAccount>,
    #[account(mut)]
    pub to_ata: Account<'info, TokenAccount>,
    pub token_program: Program<'info, Token>,
}

#[program]
pub mod solana_lamport_transfer {
    use super::*;
    pub fn transfer_lamports(ctx: Context<TransferLamports>, amount: u64) -> Result<()> {
        let from_account = &ctx.accounts.from;
        let to_account = &ctx.accounts.to;

        // Create the transfer instruction
        let transfer_instruction =
            system_instruction::transfer(from_account.key, to_account.key, amount);

        // Invoke the transfer instruction
        anchor_lang::solana_program::program::invoke_signed(
            &transfer_instruction,
            &[
                from_account.to_account_info(),
                to_account.clone(),
                ctx.accounts.system_program.to_account_info(),
            ],
            &[],
        )?;

        Ok(())
    }

    pub fn transfer_spl_tokens(ctx: Context<TransferSpl>, amount: u64) -> Result<()> {
        let destination = &ctx.accounts.to_ata;
        let source = &ctx.accounts.from_ata;
        let token_program = &ctx.accounts.token_program;
        let authority = &ctx.accounts.from;

        // 从承接者转账给初始化者
        let cpi_accounts = SplTransfer {
            from: source.to_account_info().clone(),
            to: destination.to_account_info().clone(),
            authority: authority.to_account_info().clone(),
        };
        let cpi_program = token_program.to_account_info();

        token::transfer(CpiContext::new(cpi_program, cpi_accounts), amount)?;
        Ok(())
    }
}

原文:https://www.quicknode.com/guides/solana-development/anchor/transfer-tokens

Gulf Stream:Solana 的无内存池交易转发协议

Solana是全球性能最强的免许可区块链。在 Solana 测试网的当前迭代中,由 200 个物理上不同的节点组成的网络在使用 GPU 运行时支持每秒超过 50,000 笔交易的持续吞吐量。

有 8 项关键创新使得 Solana 网络成为可能:

在这篇博文中,我们将探讨Gulf Stream,这是 Solana 为高性能对抗网络提供的内存池管理解决方案。在后续的博文中,我们将介绍这 7 项关键创新。

内存池详解

内存池是一组已提交但尚未被网络处理的交易。您现在可以查看比特币以太坊内存池。


以字节为单位测量 30 天的比特币内存池。

以交易量衡量的 30 天以太坊内存池

对于比特币和以太坊,未确认交易的数量通常约为 20K-100K,如上所示。内存池的大小(通常以未确认交易的数量来衡量)取决于区块空间的供需。即使在区块链时代的早期,当内存池增加时,也会对整个网络造成严重的瓶颈效应。

那么,Solana 做得更好吗?在不增加网络吞吐量的情况下,Solana 验证器可以管理 100,000 大小的内存池。这意味着,在网络吞吐量为 50,000 TPS 的情况下,100,000 个交易的内存池只需几秒钟即可执行。这就是 Solana 成为世界上性能最高的无许可区块链的原因。

令人印象深刻,对吧?但这种简单的分析忽略了很多重要因素……

以太坊和比特币中的内存池使用八卦协议以对等方式在随机节点之间传播。网络中的节点定期构建代表本地内存池的布隆过滤器,并从网络上的其他节点请求任何与该过滤器不匹配的交易(以及其他一些条件,例如最低费用)。将单个交易传播到网络的其余部分将至少需要 log(N) 步,消耗过滤它所需的带宽、内存和计算资源。

当基准客户端开始每秒生成 100,000 笔交易时,八卦协议就会不堪重负。计算过滤器并在机器之间应用过滤器,同时将所有交易保留在内存中的成本变得高得令人望而却步。领导者(区块生产者)还必须在一个区块中重新传输相同的交易,这意味着每笔交易至少要通过网络传播两次。这既不高效也不实用。

Gulf Stream 简介

我们在 Solana 网络上解决此问题的方法就是将交易缓存和转发推到网络边缘。我们称之为Gulf Stream。由于每个验证者都知道即将上任的领导者的顺序,因此客户端和验证者会提前将交易转发给预期的领导者。这使得验证者可以提前执行交易,减少确认时间,更快地切换领导者,并减少未确认交易池对验证者的内存压力。这种解决方案在具有非确定性领导者的网络中是不可能的

那么它是如何工作的呢?客户端(例如钱包)签署引用特定区块哈希的交易。客户端选择一个已被网络完全确认的较新的区块哈希。大约每 800 毫秒提出一个区块,并且需要指数增加的超时来展开每个附加区块。使用我们的默认超时曲线,在最坏情况下,完全确认的区块哈希是 32 个区块。交易仅在引用区块的子区块中有效,并且仅对 X 个区块有效。虽然 X 尚未最终确定,但我们预计区块哈希的 TTL(生存时间)约为 32 个区块。假设区块时间为 800 毫秒,则相当于 24 秒。

一旦交易被转发给任何验证者,验证者就会将其转发给即将上任的领导者之一。客户端可以订阅来自验证者的交易确认。客户端知道区块哈希会在有限的时间内过期,或者交易已得到网络确认。这允许客户端签署保证执行或失败的交易。一旦网络越过回滚点,交易引用的区块哈希已过期,客户端就可以保证交易现在无效,并且永远不会在链上执行。

这种架构本身具有许多积极的副作用。首先,在负载下,验证者可以提前执行交易并丢弃任何失败的交易。其次,领导者可以根据转发交易的验证者的权益权重对交易进行优先处理。这允许网络在发生大规模拒绝服务时优雅地降级。

到目前为止,区块链网络的功能性取决于其内存池的最小程度,这一点已经变得非常明显。虽然交易吞吐量有限的网络承担着尝试改造全新扩展技术以应对不断增长的内存池的崇高使命,但 Solana 自构思以来就采用了历史证明Gulf StreamSealevel等优化技术,以解决第一代区块链网络的问题并实现巨大的交易吞吐量。从一开始,这就是全球范围内的惊人速度,也是为世界各地的企业、经济体和人民创建高度实用的去中心化基础设施的根本性发展。

原文:https://medium.com/solana-labs/gulf-stream-solanas-mempool-less-transaction-forwarding-protocol-d342e72186ad

Solana - JS/TS 客户端

Anchor 提供了一个 Typescript 客户端库(@coral-xyz/anchor),简化了使用 JavaScript 或 TypeScript 从客户端与 Solana 程序交互的过程。

客户端程序

Program 要使用客户端库,首先使用Anchor 生成的IDL 文件创建一个实例 。
创建 的实例Program需要程序的 IDL 和 AnchorProvider。AnchorProvider是结合了两件事的抽象:

  • Connection- 与Solana 集群的连接 (即 localhost、devnet、mainnet)
  • Wallet- (可选)用于支付和签署交易的默认钱包

前端/节点

当使用钱包适配器与前端集成时 ,您需要设置AnchorProvider和Program。

import { Program, AnchorProvider, setProvider } from "@coral-xyz/anchor";
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
import type { HelloAnchor } from "./idlType";
import idl from "./idl.json";

const { connection } = useConnection();
const wallet = useAnchorWallet();

const provider = new AnchorProvider(connection, wallet, {});
setProvider(provider);

export const program = new Program(idl as HelloAnchor, {
  connection,
});

在上面的代码片段中:

  • idl.json是 Anchor 生成的 IDL 文件,可以 /target/idl/<program-name>.json在 Anchor 项目中找到。
  • idlType.ts是 IDL 类型(用于 TS), /target/types/<program-name>.ts在 Anchor 项目中找到。

Program或者,您可以仅使用 IDL 和Solana 集群创建 的实例Connection。这意味着没有默认的 Wallet,但允许您使用Program来获取帐户或构建指令,而无需连接钱包

import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
import { Program } from "@coral-xyz/anchor";
import type { HelloAnchor } from "./idlType";
import idl from "./idl.json";

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

export const program = new Program(idl as HelloAnchor, {
  connection,
});

测试文件

Anchor 会自动在新项目的默认测试文件中设置一个Program实例。但是,此设置与在 Anchor 工作区外部初始化的方式不同Program ,例如在 React 或 Node.js 应用程序中。

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { HelloAnchor } from "../target/types/hello_anchor";

describe("hello_anchor", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.HelloAnchor as Program<HelloAnchor>;

  it("Is initialized!", async () => {
    // Add your test here.
    const tx = await program.methods.initialize().rpc();
    console.log("Your transaction signature", tx);
  });
});

调用指令

一旦Program使用程序 IDL 设置,您就可以使用 Anchor MethodsBuilder 来:

  • 建立个别指令
  • 建立交易
  • 建立并发送交易

基本格式如下:

await program.methods // 这是用于从程序的 IDL 创建指令调用的构建器 API
  .instructionName(instructionData) // 接下来.methods,从程序 IDL 中指定指令的名称,并将任何所需的参数作为逗号分隔的值传递。
  .accounts({}) // 按照IDL中指定的方式传入指令所需的账户地址
  .signers([]) //  可选地传入指令所需的作为附加签名者的密钥对数组。这通常在创建新帐户时使用,其中帐户地址是新生成的密钥对的公钥。请注意,.signers只有在使用.rpc()时才应使用。当使用 .transaction()或 时.instruction(),应在发送之前将签名者添加到交易中。
  .rpc();

Anchor 提供了多种构建程序指令的方法:

.rpc

该 rpc() 方法 发送 带有指定指令的签名交易TransactionSignature并返回。
当使用 时.rpc,Wallet来自 的Provider将自动包含在签名者中。

// Generate keypair for the new account
const newAccountKp = new Keypair();

const data = new BN(42);
const transactionSignature = await program.methods
  .initialize(data)
  .accounts({
    newAccount: newAccountKp.publicKey,
    signer: wallet.publicKey,
    systemProgram: SystemProgram.programId,
  })
  .signers([newAccountKp])
  .rpc();

.transaction()

该 transaction() 方法 使用指定的指令构建Transaction 而不发送交易。

// Generate keypair for the new account
const newAccountKp = new Keypair();

const data = new BN(42);
const transaction = await program.methods
  .initialize(data)
  .accounts({
    newAccount: newAccountKp.publicKey,
    signer: wallet.publicKey,
    systemProgram: SystemProgram.programId,
  })
  .transaction();

const transactionSignature = await connection.sendTransaction(transaction, [
  wallet.payer,
  newAccountKp,
]);

.instruction()

该 instruction() 方法 使用指定的指令构建TransactionInstruction 。如果您想手动将指令添加到交易并将其与其他指令相结合,这很有用。

// Generate keypair for the new account
const newAccountKp = new Keypair();

const data = new BN(42);
const instruction = await program.methods
  .initialize(data)
  .accounts({
    newAccount: newAccountKp.publicKey,
    signer: wallet.publicKey,
    systemProgram: SystemProgram.programId,
  })
  .instruction();

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

const transactionSignature = await connection.sendTransaction(transaction, [
  wallet.payer,
  newAccountKp,
]);

获取账户

客户端Program简化了获取和反序列化由 Anchor 程序创建的帐户的过程。
使用program.account后跟 IDL 中定义的帐户类型的名称。Anchor 提供了多种方法来获取帐户。

.all()

用于 all() 获取特定帐户类型的所有现有帐户。

const accounts = await program.account.newAccount.all();

memcmp

使用memcmp(内存比较)来筛选与特定偏移量处的特定值匹配的帐户数据。使用memcmp需要您了解要获取的帐户类型的数据字段的字节布局。
计算偏移量时,请记住 Anchor 程序创建的账户中的前 8 个字节是为账户鉴别器保留的。

const accounts = await program.account.newAccount.all([
  {
    memcmp: {
      offset: 8,
      bytes: "",
    },
  },
]);

fetch()

用于 fetch() 获取单个账户的账户数据

const account = await program.account.newAccount.fetch(ACCOUNT_ADDRESS);

fetchMultiple()

fetchMultiple() 通过传入账户地址数组来获取多个账户的账户数据

const accounts = await program.account.newAccount.fetchMultiple([
  ACCOUNT_ADDRESS_ONE,
  ACCOUNT_ADDRESS_TWO,
]);

原文:https://solana.com/docs/programs/anchor/client-typescript

Solana - IDL 文件

接口定义语言 (IDL) 文件提供了描述程序指令和账户的标准化 JSON 文件。此文件简化了链上程序与客户端应用程序集成的过程。

IDL 的主要优势:

  • 标准化:提供一致的格式来描述程序的指令和帐户
  • 客户端生成:用于生成与程序交互的客户端代码

anchor build命令生成一个位于 的 IDL 文件 /target/idl/<program-name>.json
下面的代码片段突出显示了程序、IDL 和客户端如何相互关联。

程序指令

IDL 中的数组instructions直接对应于程序中定义的指令。它指定每条指令所需的帐户和参数。

  • Program:下面的程序包含一条initialize指令,指定其所需的帐户和参数。
  • 程序帐户:accounts IDL 中的数组对应于程序中用宏注释的结构体。#[account]这些结构体定义了程序创建的账户中存储的数据。
use anchor_lang::prelude::*;

declare_id!("BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd");

#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        ctx.accounts.new_account.data = data;
        msg!("Changed data to: {}!", data);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct NewAccount {
    data: u64,
}

IDL

生成的 IDL 文件包含标准化 JSON 格式的指令,包括其名称、帐户、参数和鉴别器。

{
  "address": "BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd",
  "metadata": {
    "name": "hello_anchor",
    "version": "0.1.0",
    "spec": "0.1.0",
    "description": "Created with Anchor"
  },
  "instructions": [
    {
      "name": "initialize",
      "discriminator": [175, 175, 109, 31, 13, 152, 155, 237],
      "accounts": [
        {
          "name": "new_account",
          "writable": true,
          "signer": true
        },
        {
          "name": "signer",
          "writable": true,
          "signer": true
        },
        {
          "name": "system_program",
          "address": "11111111111111111111111111111111"
        }
      ],
      "args": [
        {
          "name": "data",
          "type": "u64"
        }
      ]
    }
  ],
  "accounts": [
    {
      "name": "NewAccount",
      "discriminator": [176, 95, 4, 118, 91, 177, 125, 232]
    }
  ],
  "types": [
    {
      "name": "NewAccount",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "data",
            "type": "u64"
          }
        ]
      }
    }
  ]
}

Client

然后使用IDL文件生成与程序交互的客户端,简化调用程序指令的过程。

import * as anchor from "@coral-xyz/anchor";
import { Program, BN } from "@coral-xyz/anchor";
import { HelloAnchor } from "../target/types/hello_anchor";
import { Keypair } from "@solana/web3.js";
import assert from "assert";

describe("hello_anchor", () => {
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);
  const wallet = provider.wallet as anchor.Wallet;
  const program = anchor.workspace.HelloAnchor as Program<HelloAnchor>;

  it("initialize", async () => {
    // Generate keypair for the new account
    const newAccountKp = new Keypair();

    // Send transaction
    const data = new BN(42);
    const transactionSignature = await program.methods
      .initialize(data)
      .accounts({
        newAccount: newAccountKp.publicKey,
        signer: wallet.publicKey,
      })
      .signers([newAccountKp])
      .rpc();

    // Fetch the created account
    const newAccount = await program.account.newAccount.fetch(
      newAccountKp.publicKey,
    );

    console.log("Transaction signature: ", transactionSignature);
    console.log("On-chain data is:", newAccount.data.toString());
    assert(data.eq(newAccount.data));
  });
});

鉴别器

Anchor 为程序中的每个指令和账户类型分配一个唯一的 8 字节鉴别符。这些鉴别符作为标识符来区分不同的指令或账户类型。
鉴别器是使用前缀的 Sha256 哈希的前 8 个字节与指令或帐户名称相结合生成的。从 Anchor v0.30 开始,这些鉴别器包含在 IDL 文件中。
请注意,使用 Anchor 时,您通常不需要直接与这些鉴别器交互。本节主要介绍如何生成和使用鉴别器。

instructions

指令鉴别器被程序用来决定在调用时要执行哪条具体指令。
当调用 Anchor 程序指令时,鉴别符将作为指令数据的前 8 个字节包含在内。此操作由 Anchor 客户端自动完成。

IDL

  "instructions": [
    {
      "name": "initialize",
      "discriminator": [175, 175, 109, 31, 13, 152, 155, 237],
       ...
    }
  ]

accounts

账户鉴别器用于在反序列化链上数据时识别具体的账户类型,在账户创建时设置。

IDL

  "accounts": [
    {
      "name": "NewAccount",
      "discriminator": [176, 95, 4, 118, 91, 177, 125, 232]
    }
  ]

指令的鉴别器是前缀的 Sha256 哈希的前 8 个字节global加上指令名称。

例如:

sha256("global:initialize")

十六进制输出:

af af 6d 1f 0d 98 9b ed d4 6a 95 07 32 81 ad c2 1b b5 e0 e1 d7 73 b2 fb bd 7a b5 04 cd d4 aa 30

前 8 个字节用作指令的鉴别符。

af = 175
af = 175
6d = 109
1f = 31
0d = 13
98 = 152
9b = 155
ed = 237

您可以在此处的Anchor 代码库中找到鉴别器生成的实现 ,该代码库在此处使用 。

总结:https://solana.com/docs/programs/anchor/idl

Solana Hello World(安装和故障排除)

这是 Solana 的 Hello World 教程。我们将引导您完成安装 Solana 的步骤并解决可能出现的问题。
如果您遇到问题,请查看本文末尾的故障排除部分。

安装步骤

安装 Rust

如果您已经安装了 Rust,请跳过此步骤。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装 Yarn

您需要它来运行单元测试。如果您已经安装了 yarn,请跳过此步骤。

corepack enable

安装 Solana cli

我们强烈建议使用stable版本,而不是latest。 Solana 安装不再支持符号通道名称(edge、beta、stable),因此我们必须指定版本。

sh -c "$(curl -sSfL https://release.anza.xyz/v2.1.5/install)"

安装锚点

Anchor 是 Solana 开发的框架。它在很多方面与 hardhat 非常相似。

cargo install --git https://github.com/coral-xyz/anchor avm --locked --force

avm install latest
avm use latest

测试安装

初始化并构建一个锚点程序(用于 hello world)

调用您的程序day_1而不是day1因为 Anchor 有时似乎会在 idl路径上插入下划线。

anchor init day_1
cd day_1
anchor build

根据您的机器和互联网连接,此步骤可能需要一段时间。这也是您可能遇到安装问题的地方,因此如有必要,请参阅故障排除部分。

配置 Solana 在本地主机上运行

solana config set --url localhost

运行测试验证器节点

在新的 shell 中(而不是在 Anchor 项目中)运行以下命令。但不要关闭运行的 shell anchor build。这会在您的机器上运行本地(测试)Solana 节点实例:

solana-test-validator

确保 program_id 与 Anchor 键同步

返回 Anchor 项目的 shell 并运行以下命令:

anchor keys sync

运行测试

在 Anchor 项目中运行此命令

anchor test --skip-local-validator

上面的命令运行我们程序的测试。如果您尚未创建测试钱包,Anchor 将为您提供如何创建钱包的说明。我们在此不提供这些说明,因为它取决于您的操作系统和文件结构。您可能还需要通过在终端中运行来为自己空投一些本地 Sol 。您可以通过在命令行中solana airdrop 100 {YOUR_WALLET_ADDRESS}运行来获取您的钱包地址。solana address

预期输出如下:

你好世界

现在让我们让程序输出“Hello, world!”。将以下标有 的行添加NEW LINE HERE到programs/day_1/src/lib.rs。

use anchor_lang::prelude::*;

declare_id!("...");

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

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("Hello, world!"); // **** NEW LINE HERE ****
        Ok(())
    }
}

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

再次运行测试之前,请终止本地验证器进程并使用以下命令重新启动它:

solana-test-validator --reset

再次运行测试

anchor test --skip-local-validator

通过运行查找日志文件

ls .anchor/program-logs/

打开该文件即可看到记录的“Hello world”

实时 Solana 日志

或者,您可以通过打开第三个 shell 并运行以下命令来查看日志:

solana logs

现在再次运行测试,您应该在运行的终端中看到相同的消息solana logs。

program 查询

查询已安装

solana program show --programs

关闭程序

solana program close 3ynNB373...2iTyg --bypass-warning

请注意,一旦关闭某个程序,则不能重新使用该程序 ID 来部署新程序。

问答

  1. 为什么 declared_id! 和 msg! 后面有感叹号?
    在 Rust 中,感叹号表示这些是宏。我们将在后面的教程中重新讨论宏。
  2. 我需要一个初始化函数吗?
    不,这是由 Anchor 框架自动生成的。您可以随意命名。
    在此上下文中,initialize 这个名称没有什么特殊之处,因此我们可以将其更改为任何我们喜欢的名称。这与其他一些关键字和语言不同,例如 main 在某些语言中是一个特殊名称,或者在 Solidity 中,constructor 是一个特殊名称。
    练习:尝试将initialize中的programs/day_1/src/lib.rs和initialize中的重命名为 ,tests/day_1.ts然后initialize2再次运行测试。请参见下面橙色圆圈中标记的更改。
  3. 为什么我们要使用 –skip-local-validator 来运行测试?
    当测试针对某个节点运行时,我们将能够查询该节点的状态变化。如果您无法让节点运行,则可以anchor test在不使用--skip-local-validator标志的情况下运行。但是,这将使您的开发和测试变得更加困难,因此我们建议让本地验证器正常工作。

故障排除

Solana 是一款快速发展的软件,您可能会遇到安装问题。我们在以下部分记录了您最有可能遇到的问题。
我们的教程系列使用以下版本编写: Anchor = 版本 0.29.0 Solana = 版本 1.16.25 * Rustc = 1.77.0-nightly
您可以通过运行以下命令来更改 Anchor 版本:

avm install 0.29.0
avm use 0.29.0
  1. 错误:solana-program v1.18.0无法构建包
    error: package `solana-program v1.18.0` cannot be built because it requires rustc 1.72.0 or newer, while the currently active rustc version is 1.68.0-dev
    Either upgrade to rustc 1.72.0 or newer, or use
    cargo update -p solana-program@1.18.0 --precise ver

    检查您正在使用的 Solana 版本solana --version。然后将该版本插入到ver上面的内容中。示例解决方案如下所示:

  2. 错误[E0658]:使用不稳定库功能“build_hasher_simple_hash_one”
    如果出现以下错误:
    error[E0658]: use of unstable library feature 'build_hasher_simple_hash_one'
    --> src/random_state.rs:463:5
    |
    463 | / fn hash_one<T: Hash>(&self, x: T) -> u64 {
    464 | | RandomState::hash_one(self, x)
    465 | | }
    | |_____^
    |
    = note: see issue #86161 https://github.com/rust-lang/rust/issues/86161 for more information
    = help: add #![feature(build_hasher_simple_hash_one)] to the crate attributes to enable

    运行以下命令:cargo update -p ahash@0.8.7 --precise 0.8.6
    来源:https ://solana.stackexchange.com/questions/8800/cant-build-hello-world

  3. 错误:部署程序失败:错误处理指令 1:自定义程序错误:0x1
    Error: Deploying program failed: Error processing Instruction 1: custom program error: 0x1
    There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

    如果出现此错误,则表示您的密钥未同步。运行anchor keys sync

  4. 错误:无法发送交易:交易模拟失败:尝试加载不存在的程序
    您的密钥未同步。运行anchor keys sync
  5. 错误:您配置的 rpc 端口:8899 已被使用
    当验证器在后台运行时,您anchor test没有运行--skip-local-validator。请关闭验证器并运行,anchor test或在验证器运行时运行anchor test --skip-local-validator。跳过本地验证器意味着跳过它为项目创建的临时验证器,而不是在后台运行的验证器。
  6. 错误:帐户 J7t…zjK 资金不足,无法消费
    运行以下命令将 100 SOL 空投到您的开发地址:
    solana airdrop 100 J7t...zjK
  7. 错误:RPC 请求错误:集群版本查询失败
    Error: RPC request error: cluster version query failed: error sending request for url (http://localhost:8899/): error trying to connect: tcp connect error: Connection refused (os error 61)
    There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

    这意味着solana-test-validator不在后台运行。solana-test-validator在另一个 shell 中运行。

  8. 线程“main”因“调用Option::unwrap()某个None值”而惊慌失措
    thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', /Users/username/.cargo/git/checkouts/anchor-50c4b9c8b5e0501f/347c225/lang/syn/src/idl/file.rs:214:73
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

    你可能还没跑anchor build

  9. 我在使用 Mac,出现错误:无法启动验证器:无法在测试分类账中创建分类账:块存储错误
    按照此Stack Exchange 线程中的说明进行操作。
  10. 我的 Mac 上有 node.js,但是没有 corepack
    运行以下命令:
    brew install corepack
    brew link --overwrite corepack

    来源:https ://stackoverflow.com/questions/70082424/command-not-found-corepack-when-installing-yarn-on-node-v17-0-1

  11. 错误:不是目录
    BPF SDK: /Users/rareskills/.local/share/solana/install/releases/stable-43daa37937907c10099e30af10a5a0b43e2dd2fe/solana-release/bin/sdk/bpf
    cargo-build-bpf child: rustup toolchain list -v
    cargo-build-bpf child: rustup toolchain link bpf /Users/rareskills/.local/share/solana/install/releases/stable-43daa37937907c10099e30af10a5a0b43e2dd2fe/solana-release/bin/sdk/bpf/dependencies/bpf-tools/rust
    error: not a directory:

    清除缓存:运行rm -rf ~/.cache/solana/*

  12. 错误:target/idl/day_1.json 不存在。您运行了吗anchor build?
    创建一个新项目并将其命名为 day_1 而不是 day1。Anchor 似乎会在某些机器上默默插入下划线。

原文:https://www.rareskills.io/post/hello-world-solana

搜索