Solana DApp开发教程:快速入门与实战指南

2025-02-13 22:49:59 82

Solana DApp 开发:从入门到精通

Solana 以其惊人的交易速度和低廉的交易费用,正迅速成为 DApp 开发的热门选择。本教程将引导你逐步了解 Solana DApp 的开发过程,从环境搭建到智能合约编写,再到前端用户界面构建。

准备工作

开始 Solana 智能合约(在 Solana 术语中称为 Programs)开发之前,需要配置合适的本地开发环境。 务必仔细遵循以下步骤,确保所有必要的工具都已正确安装和配置,这对于顺利进行后续开发至关重要:

  • Node.js 和 npm (或 yarn/pnpm): Node.js 是一个 JavaScript 运行时环境,npm(Node Package Manager)是 Node.js 的默认包管理器。它们用于执行 JavaScript 代码和管理项目依赖项,尤其是在前端开发和构建过程中。 推荐安装 Node.js 的长期支持 (LTS) 版本,以获得更稳定的开发体验。 可以考虑使用 yarn 或 pnpm 等替代包管理器,它们在依赖管理和性能方面可能有所改进。
  • Rust 编程语言: Solana Programs 主要采用 Rust 编写,Rust 是一种注重安全性和性能的系统编程语言。需要安装 Rust 编译器 (rustc) 和包管理器 (cargo)。
  • Solana Tool Suite: Solana Tool Suite 是一组命令行工具,用于与 Solana 区块链进行交互。它包括 solana CLI,允许你部署 Programs、发送交易、查询账户信息等。
  • Anchor 框架: Anchor 是一个强大的框架,旨在简化 Solana Program 的开发过程。它提供了一系列工具和约定,例如自动生成样板代码、简化测试和部署流程等,能够显著提高开发效率。

安装 Rust 时,强烈建议使用 rustup ,一个官方的 Rust 版本管理工具。 rustup 允许你轻松安装、更新和切换不同的 Rust 版本,这在处理不同项目对 Rust 版本有不同要求时非常有用。 通过 rustup 安装 Rust 的命令通常如下所示:

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

安装 Solana Tool Suite 之后,必须将其添加到系统的环境变量 PATH 中。 这样,你就可以在终端的任何位置直接运行 solana 命令,而无需指定其完整路径。 具体操作取决于你的操作系统:

  • Linux/macOS: 编辑 ~/.bashrc , ~/.zshrc 或其他 shell 配置文件,添加 export PATH="/path/to/solana/cli:$PATH" ,并将 /path/to/solana/cli 替换为 Solana CLI 的实际安装路径。 然后,运行 source ~/.bashrc (或相应的 shell 配置文件) 以使更改生效。
  • Windows: 在“系统属性”->“环境变量”中,编辑“用户变量”或“系统变量”中的 “Path” 变量,添加 Solana CLI 的安装路径。

安装 Anchor 可以通过 Cargo 进行,命令如下:

bash cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked

--locked 标志确保使用 Cargo.lock 文件中指定的精确依赖版本,从而提高构建的可重复性和可靠性。 安装完成后,可以通过以下命令验证 Anchor 是否已正确安装并检查其版本:

bash anchor --version

此命令将显示 Anchor CLI 的版本号,表明 Anchor 已成功安装并可以在你的开发环境中使用。 如果出现任何错误,请仔细检查之前的安装步骤,并确保所有依赖项都已正确配置。

创建 Anchor 项目

使用 Anchor 框架启动一个新的 Solana 项目是一个快速且标准化的过程。只需在您的命令行界面中执行以下命令,即可创建一个具备预定义结构的初始项目:

anchor init my_solana_dapp

上述命令会在当前工作目录下创建一个名为 my_solana_dapp 的新目录。这个目录是您 Solana 应用程序的根目录,它包含了一系列重要的子目录和文件,用于组织和管理您的智能合约代码、部署脚本以及测试用例。项目的核心结构包括:

  • programs: 该目录用于存放构成您的 Solana 智能合约的 Rust 源代码文件。每个智能合约通常包含多个函数(或指令),用于定义链上逻辑和状态转换。
  • migrations: 存放部署智能合约到 Solana 区块链的脚本。这些脚本通常使用 Anchor 的部署工具来自动化合约部署过程,并初始化必要的链上数据结构。
  • tests: 存放用于测试智能合约功能的 JavaScript 或 TypeScript 代码。良好的测试覆盖率对于确保智能合约的正确性和安全性至关重要。Anchor 提供了测试框架,可以方便地模拟链上环境并验证合约行为。

创建项目后,使用以下命令导航到新创建的项目目录,以便进行后续的开发工作:

cd my_solana_dapp

现在,您已经进入了项目的根目录,可以开始编写智能合约代码、定义数据结构、添加业务逻辑,并创建相应的测试用例,从而构建您的 Solana 应用程序。

编写智能合约

接下来,我们将深入探讨如何编写一个Solana智能合约,也被称为程序。我们将创建一个基础的计数器程序,它能够递增和存储一个数字。这个例子将帮助你理解Solana程序的基本结构和关键概念,例如账户、指令和序列化。

打开位于 programs/my-solana-dapp/src/lib.rs 的程序主文件。这个文件是你的Solana程序的入口点,包含了程序逻辑的核心代码。

使用你喜欢的文本编辑器或IDE打开该文件,然后按照下面的代码示例修改其内容。代码中的注释将帮助你理解每一部分的作用。

lib.rs 文件的内容应如下所示:


use anchor_lang::prelude::*;

declare_id!("YOUR_PROGRAM_ID"); // 替换为你的程序ID

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

    pub fn initialize(ctx: Context, initial_value: u64) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count = initial_value;
        counter.authority = *ctx.accounts.authority.key;
        msg!("Counter initialized with initial value: {}", initial_value); // 添加日志
        Ok(())
    }

    pub fn increment(ctx: Context) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count += 1;
        msg!("Counter incremented to: {}", counter.count); // 添加日志
        Ok(())
    }

    pub fn decrement(ctx: Context) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count -= 1;
        msg!("Counter decremented to: {}", counter.count); // 添加日志
        Ok(())
    }

    pub fn update_authority(ctx: Context, new_authority: Pubkey) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        require!(counter.authority == *ctx.accounts.authority.key, MyError::IncorrectAuthority);
        counter.authority = new_authority;
        msg!("Counter authority updated to: {}", new_authority); // 添加日志
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = authority, space = 8 + 8 + 32, seeds = [b"counter"], bump)]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut, seeds = [b"counter"], bump)]
    pub counter: Account<'info, Counter>,
}

#[derive(Accounts)]
pub struct Decrement<'info> {
    #[account(mut, seeds = [b"counter"], bump)]
    pub counter: Account<'info, Counter>,
}

#[derive(Accounts)]
pub struct UpdateAuthority<'info> {
    #[account(mut, seeds = [b"counter"], bump, has_one = authority)]
    pub counter: Account<'info, Counter>,
    pub authority: Signer<'info>,
}

#[account]
pub struct Counter {
    pub count: u64,
    pub authority: Pubkey,
}

#[error_code]
pub enum MyError {
    #[msg("Incorrect authority provided")]
    IncorrectAuthority,
}

务必将 YOUR_PROGRAM_ID 替换为你实际的程序ID。 程序 ID 是一个唯一的公钥,用于标识你的 Solana 程序。你可以使用 `solana-keygen new` 命令生成一个新的密钥对,并将生成的公钥用作你的程序 ID。你需要将此公钥复制并粘贴到 declare_id!() 宏中。

[program]

pub mod my_solanadapp 模块定义了Solana DApp的核心逻辑。它导入必要的依赖项,并包含用于初始化和递增计数器的函数。


pub mod my_solanadapp {
    use super::*;

    /// `initialize` 函数用于初始化计数器账户。
    ///
    /// # Arguments
    ///
    /// * `ctx`: 上下文,包含对必要账户的引用。类型为 `Context<Initialize>`,由 Anchor 框架提供。
    /// * `authority`: 授权密钥,用于控制计数器账户。类型为 `Pubkey`,代表一个 Solana 公钥。
    ///
    /// # Returns
    ///
    /// * `Result<()>`: 如果初始化成功,则返回 `Ok(())`;否则返回一个错误。
    pub fn initialize(ctx: Context<Initialize>, authority: Pubkey) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.authority = authority;
        counter.count = 0;
        Ok(())
    }

    /// `increment` 函数用于递增计数器账户中的计数。
    ///
    /// # Arguments
    ///
    /// * `ctx`: 上下文,包含对必要账户的引用。类型为 `Context<Increment>`,由 Anchor 框架提供。
    ///
    /// # Returns
    ///
    /// * `Result<()>`: 如果递增成功,则返回 `Ok(())`;否则返回一个错误。
    pub fn increment(ctx: Context<Increment>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count += 1;
        Ok(())
    }
}

该模块定义了两个关键函数:`initialize` 和 `increment`。`initialize` 函数设置计数器的初始状态,包括授权密钥和初始计数。`increment` 函数原子性地增加计数器的值。这些函数利用 Anchor 框架提供的上下文(`Context`)来安全地访问和修改 Solana 账户。 counter.authority 存储授权账户的公钥,用于后续的权限验证,确保只有授权账户才能修改计数器。 counter.count 存储当前的计数值,通过 += 1 进行原子递增。

[derive(Accounts)]

[instruction(authority: Pubkey)]

Initialize 指令用于初始化一个新的 Counter 账户。该指令需要提供一个授权者(authority)的公钥,该授权者将拥有管理该计数器账户的权限。

pub struct Initialize<'info> { #[account(init, payer = authority, space = 8 + 8 + 32, seeds = [b"counter"], bump)] pub counter: Account<'info, Counter>, #[account(mut)] pub authority: Signer<'info>, pub system_program: Program<'info, System>, }

字段说明:

  • counter : 这是一个待初始化的 Counter 账户。
    • init : 此属性表示这是一个新账户的初始化操作。
    • payer = authority : 指定 authority 作为支付创建此账户所需费用的账户。
    • space = 8 + 8 + 32 : 定义账户需要分配的存储空间大小(字节)。其中,8字节用于账户鉴别器,8字节用于存储计数器的数值,32字节用于存储授权者的公钥。
    • seeds = [b"counter"] : 指定用于派生账户地址的种子,确保地址的唯一性和可预测性,通常结合 bump 种子一起使用。
    • bump : 由Solana运行时环境提供,与 seeds 结合使用,确保生成的程序派生地址(PDA)有效且不在椭圆曲线的有效私钥范围内。
  • authority : 授权者,也是交易的签名者。
    • mut : 表示此账户的余额可能会在此指令执行过程中发生变化。
    • Signer<'info> : 表示此账户必须是交易的签名者,用于授权初始化操作。
  • system_program : Solana系统程序。
    • Program<'info, System> : 一个对Solana系统程序的引用,负责账户的创建和资金转移等底层操作。

详细说明:

Initialize 指令创建一个新的 Counter 账户,并设置其初始状态。 authority 账户支付创建账户所需的租金。 seeds bump 共同用于生成程序派生地址(PDA),确保 counter 账户的地址由程序控制,而不是由私钥控制。初始化后, authority 可以使用其他指令来增加或减少计数器的值。 system_program 用于执行底层的账户创建操作。

#[derive(Accounts)]

在Solana程序开发中, #[derive(Accounts)] 是一个强大的宏,它来自Anchor框架,用于简化账户结构的定义。它通过声明式的语法,允许开发者将多个账户组合成一个逻辑单元,并自动生成样板代码,以便于在程序指令中安全地访问和操作这些账户。

考虑以下使用 #[derive(Accounts)] 定义的 Increment 结构体,该结构体用于表示一个增加计数器的操作:


pub struct Increment<'info> {
    #[account(mut, seeds = [b"counter"], bump)]
    pub counter: Account<'info, Counter>,
}

结构体定义: Increment<'info> 定义了一个名为 Increment 的结构体,它携带了生命周期参数 'info 。该生命周期参数与Solana账户的生命周期相关联,确保在交易处理过程中账户引用的有效性。

#[account(...)] 属性: #[account(...)] 属性是Anchor框架提供的关键特性,用于声明账户约束。这些约束在程序执行前由Anchor自动验证,以确保程序的安全性。

mut mut 关键字表示 counter 账户在指令执行期间会被修改。如果缺少 mut ,则尝试修改该账户将导致交易失败。

seeds = [b"counter"] seeds 参数指定了程序派生地址(PDA)的种子。PDA是由程序控制的账户,而不是由私钥控制。在此示例中, b"counter" 是一个字节串字面量,用作生成PDA的种子。这意味着 counter 账户的地址由程序本身派生,而不是由用户提供。

bump bump 参数用于处理查找PDA时可能出现的冲突。由于相同的种子可能生成多个地址, bump 是一个单字节值,附加到种子中以唯一地标识PDA。Anchor会自动处理 bump 的查找和验证,开发者无需手动管理。

Account<'info, Counter> Account<'info, Counter> 表示 counter 字段是一个类型为 Counter 的账户。 Counter 通常是另一个使用 #[derive(AnchorSerialize, AnchorDeserialize, Clone)] 定义的结构体,用于描述账户中存储的数据的布局。 'info 生命周期参数确保账户引用的有效性。

总而言之,这段代码片段展示了如何使用 #[derive(Accounts)] 宏定义一个包含一个可变计数器账户的结构体。该计数器账户是一个由程序派生的地址,并通过指定的种子和bump值进行唯一标识。Anchor框架会自动处理账户约束的验证,从而简化了Solana程序的开发并提高了安全性。

[account]

在Solana程序开发中,账户(Account)是存储数据的主要方式。以下代码定义了一个名为 Counter 的账户结构体,用于存储计数器的状态信息。


pub struct Counter {
    pub authority: Pubkey, // 负责更新计数器的公钥
    pub count: u64,       // 计数器的当前值,使用无符号64位整数
}

Counter 结构体包含两个字段: authority count authority 字段是一个 Pubkey 类型,表示有权限修改 count 字段的公钥,通常是程序的管理员或拥有者。 count 字段是一个 u64 类型,存储计数器的当前值。使用 u64 可以存储较大的计数值。

账户的初始化和更新通常通过程序指令来完成。例如,一个 initialize 指令可以用于设置 authority 的初始值和将 count 初始化为0。一个 increment 指令则用于增加 count 的值。

在实际应用中,需要创建一个Solana程序来处理与 Counter 账户相关的逻辑。创建程序时,请确保将 "YOUR_PROGRAM_ID" 替换为你自己的程序 ID。可以使用 Solana CLI 工具中的 solana-keygen new 命令生成一个新的密钥对,并将其公钥作为程序 ID。该程序ID是唯一标识你的Solana程序的地址,对于程序的部署和调用至关重要。

程序需要验证交易签名,确保只有授权的 authority 才能修改 count 值。 安全地处理账户数据至关重要,以防止未经授权的修改。 可以使用Solana提供的安全编程模型来保护账户数据,例如检查签名、验证账户所有权等。

构建和部署智能合约

编写完成并充分测试智能合约代码后,需要进行构建,使其成为可执行的程序,然后部署到Solana区块链上。构建过程是将人类可读的源代码转换为机器可以理解和执行的字节码的过程。在你的Anchor项目根目录中,打开终端并运行以下命令:

anchor build

这条命令会调用Anchor框架的构建工具,它会自动处理依赖关系,编译你的智能合约代码,并进行优化。构建成功后,会在 target/deploy 目录下生成一个扩展名为 .so 的共享库文件。这个 .so 文件包含了已经编译好的智能合约程序,可以被Solana运行时环境加载和执行。这个文件是后续部署流程的关键。

智能合约构建完成后,下一步是将它部署到Solana区块链上,使其能够被网络上的其他程序和用户调用。在部署之前,需要确保你的Solana命令行界面(Solana CLI)已经正确配置,并且连接到你希望部署到的目标网络。目标网络可以是本地测试网络(localnet)、开发网络(devnet)、测试网络(testnet)或主网络(mainnet)。你可以使用以下命令来检查当前的Solana CLI配置:

solana config get

这条命令会显示当前Solana CLI所连接的网络、密钥对路径和其他配置信息。确认配置正确后,可以使用以下命令来部署智能合约:

anchor deploy

这条命令会执行 migrations/deploy.ts 脚本。这个脚本通常包含部署智能合约所需的逻辑,比如创建智能合约账户、分配账户空间、设置账户所有者以及初始化智能合约的状态。 anchor deploy 命令会自动处理这些步骤,并将智能合约部署到区块链上。部署成功后,终端会显示智能合约的程序ID(Program ID)。程序ID是智能合约在Solana区块链上的唯一标识符,其他程序和用户可以使用这个ID来与你的智能合约进行交互。

编写客户端代码

现在,需要编写客户端代码,与部署在Solana区块链上的智能合约进行交互。创建一个新的 JavaScript 文件,例如 client.js ,用于实现与智能合约的交互逻辑。该文件将使用 Anchor 框架提供的工具来简化与智能合约的通信。

client.js 示例代码:

javascript const anchor = require("@coral-xyz/anchor"); const { Program, web3 } = anchor; const { SystemProgram, Keypair, PublicKey } = web3;

async function main() { // 配置 Anchor Provider,使用默认的环境配置(例如本地 Solana 集群) const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); // 你的智能合约的程序 ID,必须替换为实际部署的程序 ID const programId = new PublicKey("YOUR_PROGRAM_ID"); // 使用 IDL 和程序 ID 创建 Program 实例 const program = new Program(IDL, programId, provider); // 生成一个新的密钥对,作为计数器账户的地址 const counter = Keypair.generate(); // 获取当前 Provider 的钱包公钥,用作计数器的 authority const authority = provider.wallet.publicKey;

console.log("Initializing counter..."); // 调用智能合约的 initialize 函数 await program.methods .initialize(authority) .accounts({ counter: counter.publicKey, authority, systemProgram: SystemProgram.programId, }) .signers([counter]) .rpc();

console.log("Incrementing counter..."); // 调用智能合约的 increment 函数 await program.methods .increment() .accounts({ counter: counter.publicKey, }) .rpc();

// 获取计数器账户的信息 const counterAccount = await program.account.counter.fetch(counter.publicKey); console.log("Counter value:", counterAccount.count.toString()); }

const IDL = { "version": "0.1.0", "name": "my_solana_dapp", "instructions": [ { "name": "initialize", "accounts": [ { "name": "counter", "isMut": true, "isSigner": false }, { "name": "authority", "isMut": true, "isSigner": true }, { "name": "systemProgram", "isMut": false, "isSigner": false } ], "args": [ { "name": "authority", "type": "publicKey" } ] }, { "name": "increment", "accounts": [ { "name": "counter", "isMut": true, "isSigner": false } ], "args": [] } ], "accounts": [ { "name": "Counter", "type": { "kind": "struct", "fields": [ { "name": "authority", "type": "publicKey" }, { "name": "count", "type": "u64" } ] } } ] };

main();

这段客户端代码演示了如何使用 Anchor SDK 与 Solana 智能合约进行交互。它首先配置 Anchor Provider,用于管理与 Solana 集群的连接。然后,它使用合约的接口定义语言 (IDL) 和程序 ID 创建一个 Program 实例,该实例提供了与智能合约交互的方法。

接下来,代码生成一个新的密钥对,该密钥对将用作计数器账户的地址。然后,它调用 initialize 函数来初始化计数器账户,并将当前 Provider 的钱包公钥设置为计数器的管理者。 initialize 函数需要提供计数器账户的公钥,管理者公钥,以及 Solana 系统程序的程序 ID 作为参数。 systemProgram 负责在 Solana 上分配账户空间。

然后,代码调用 increment 函数来增加计数器的值。这个函数只需要计数器账户的公钥作为参数。调用 increment 函数后,代码获取计数器账户的信息,并打印出计数器的当前值。

必须将代码中的 "YOUR_PROGRAM_ID" 替换为你实际部署的 Solana 程序的程序 ID。程序 ID 是 Solana 网络上智能合约的唯一标识符。同时,需要确保 IDL 变量与你的智能合约的 IDL 相匹配。智能合约的 IDL 描述了合约的接口,包括函数和数据结构。IDL 文件通常位于 target/idl 目录下,在构建智能合约后生成。使用正确的程序 ID 和 IDL 对于客户端与智能合约的成功交互至关重要。

运行客户端代码

为了成功执行客户端代码,请务必确保你的开发环境中已正确安装以下关键依赖项: @coral-xyz/anchor @solana/web3.js @coral-xyz/anchor 简化了与 Solana 程序的交互,而 @solana/web3.js 提供了与 Solana 区块链通信所需的底层功能。

你可以使用以下命令通过 npm(Node Package Manager)安装这些依赖项。在终端或命令提示符中,导航到你的项目目录并执行:

npm install @coral-xyz/anchor @solana/web3.js

此命令将从 npm 仓库下载并安装 @coral-xyz/anchor @solana/web3.js 及其所有必需的子依赖项到你的项目 node_modules 目录中。 安装完成后,你就可以在客户端代码中导入和使用这些库。

安装完依赖项后,就可以执行客户端程序了。 假设你的客户端代码位于名为 client.js 的文件中,你可以使用 Node.js 运行时环境执行它。 在终端或命令提示符中,导航到包含 client.js 文件的目录并运行以下命令:

node client.js

此命令将启动 Node.js 运行时环境,并执行 client.js 文件中包含的 JavaScript 代码。 客户端代码将与已部署的 Solana 程序交互,执行诸如初始化计数器和增加计数器等操作。

成功执行客户端代码后,你将在控制台中看到详细的输出信息。 此输出通常包括有关程序执行的各种消息,例如:初始化计数器的确认、增加计数器的交易哈希以及更新后的计数器值。 通过检查控制台输出,你可以验证客户端代码是否正在按预期与 Solana 程序交互,并且计数器正在正确更新。

前端用户界面

你可以利用现代前端框架,如 React、Vue.js 或 Angular,构建与 Solana 智能合约无缝交互的用户界面。这些框架提供了组件化和数据绑定的能力,极大地简化了 UI 开发过程。核心在于使用 @solana/web3.js 库和 @coral-xyz/anchor 框架,它们分别负责与 Solana 区块链建立连接,并提供高级抽象来简化智能合约的调用。

一个基础的前端用户界面,用于与计数器智能合约交互,通常包含以下关键组件:

  • 初始化按钮: 用于部署智能合约并初始化计数器状态。这通常涉及发送一个初始化事务到 Solana 网络。
  • 增加按钮: 用于原子性地增加计数器的数值。每次点击都会触发一个事务,更新链上数据。
  • 计数器显示: 使用文本框或其他显示元素实时反映计数器的当前值。通过订阅 Solana 网络的事件或定期轮询合约状态来实现实时更新。

与 Solana 钱包(如 Phantom、Solflare 或 Backpack)的集成是至关重要的。 @solana/web3.js 库提供了连接钱包并请求用户授权进行交易的功能。一旦用户授权,前端应用可以使用 Anchor SDK 创建一个 `Program` 实例,该实例封装了与特定智能合约交互的所有必要信息,包括合约地址、ABI 和密钥对。通过 `Program` 实例,可以方便地调用智能合约的方法,构建和发送交易,并处理链上返回的数据。在实际应用中,需要妥善处理交易签名、错误处理和用户体验优化,确保应用的稳定性和易用性。

The End

发布于:2025-02-13,除非注明,否则均为币看点原创文章,转载请注明出处。