IB-Anchor-入门

Gabrielle Lv5

Codes in lesson1

Codes in lesson2

创建 Anchor 项目

1
anchor init <new-workspace-name>
  • app:默认为空,用来放前端代码

  • programs:合约代码。默认会有一个项目同名文件夹,里面有一个 lib.rs 文件

  • tests:测试目录。 默认包含一个当前项目名的测试

  • migrations:合约的部署,迁移脚本

  • Anchor.toml:项目的配置文件

    • 本地的合约地址 ([programs.localnet])
    • 合约仓库 ([registry])
    • solana 网络 ([provider])
    • 脚本 ([scripts]):可以使用 anchor run <script_name> 运行,默认是 test 脚本

本地开发

启动本地 solana 网络

1
solana-test-validator

把 solana 命令行配置成使用本地环境

1
2
solana config get
solana config set --url localhost

创建一个文件系统钱包

要使用 Solana CLI 部署程序,需要一个带有 SOL 代币的 Solana 钱包来支付区块链上的交易和数据存储成本

创建一个简单的文件系统钱包,以便在本地开发过程中使用:

1
solana-keygen new

告诉 Solana CLI 使用此钱包来部署链上程序并获得其所有权:

1
solana config set -k ~/.config/solana/id.json

为钱包空投 SOL

请求向默认钱包免费空投 SOL 代币:

1
2
solana airdrop 2
solana balance

Anchor.toml

1
2
3
[provider]
cluster = "Localnet"
wallet = "~/.config/solana/id.json"

Anchor 合约结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// use this import to gain access to common anchor features
use anchor_lang::prelude::*;

// declare an id for your program
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

// write your business logic here
#[program]
mod hello_anchor {
use super::*;
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
}

// validate incoming accounts here
#[derive(Accounts)]
pub struct Initialize {}

declare_id! - 声明合约链上地址的宏

1
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
  • 合约中的 id 和 anchor.toml 中的 id 相同
  • 使用 anchor keys sync 来同步

#[program] - 宏下面定义了合约的所有指令

1
2
3
4
5
6
7
8
9
10
11
#[program]
pub mod anchor_timer {
use super::*;

pub fn start(ctx: Context<Initialize>, begin: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = begin;
msg!("Counter account created. Current count: {}", counter.count);
Ok(())
}
}
  • 所有公开的函数都被当作单独的 指令
  • 每一个指令 第一个参数 都是 Context,之后是额外的参数。我们使用 Rust 的类型来描述,Anchor 框架会反序列化
  • Context 需要一个泛型 T,来定义指令所依赖的账户(Accounts trait)

核心概念 - 账户

graph TD
    原生合约 --> BPF_Loader[BPF Loader]
    原生合约 --> SystemProgram[系统合约 system_program]
    
    BPF_Loader --> 自定义合约
    自定义合约 --> 合约数据
    
    SystemProgram --> 钱包账户
    
    账号结构[AccountInfo结构体] --> Data[data: 账户数据]
    账号结构 --> Executable[executable: 可执行标志]
    账号结构 --> Lamports[lamports: SOL余额]
    账号结构 --> Owner[owner: 所有者]

Anchor 中的账户

Accounts - 代表了指令所需要的账户 trait

  • 可以使用 #[derive(Accounts)] 来实现 Accounts trait,结构体的每一个字段是一个账户
  • anchor 提供了一系列账户类型用于验证
    • Account:我们自定义的账户类型
    • Signer:验证指令的签名者
    • Program:账户必须是一个合约账户,泛型指定了具体合约
  • 使用 #[account(...)] 来约束每一个账户

示例中 instruction_one 指令需要三个账户 account_name, user, 和 system_program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#[program]
pub mod anchor_timer {
use super::*;

pub fn start(ctx: Context<Initialize>, begin: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = begin;
msg!("Counter account created. Current count: {}", counter.count);
Ok(())
}
}

#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub counter: Account<'info, Counter>,

#[account(mut)]
pub user: Signer<'info>,

pub system_program: Program<'info, System>,
}

#[account(...)] - 属性宏用于约束账户

  • init: 自动初始化账户
  • payer: 初始化账户是支付人
  • space:账户的空间大小
1
2
3
4
5
6
7
8
9
10
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub counter: Account<'info, Counter>,

#[account(mut)]
pub user: Signer<'info>,

pub system_program: Program<'info, System>,
}

核心概念 - PDA

思考:如果希望每个人的计时器独立,互不影响,该如何设计?

方式 1: hashmap

  • 账户有 10Mb 大小的限制
  • Dapp 当查询某一个人的计时器需要拉取全部数据
graph LR
    subgraph Solana账户关系图
        style WA fill:#ffcccc,stroke:#333,stroke-width:2px
        style WB fill:#ffcccc,stroke:#333,stroke-width:2px
        style CP fill:#cce5ff,stroke:#333,stroke-width:2px
        style CA fill:#e6ccff,stroke:#333,stroke-width:2px
        
        WA["Wallet A
PubKey: 2TQy..."] -->|交互| CP WB["Wallet B
PubKey: Gs88..."] -->|交互| CP CP["Counter Program
PubKey: 3yWr..."] -->|管理| CA CA["Counter Account
PubKey: Eg07o..."] CA -->|存储| HM end subgraph HM[计数器状态] style HM fill:#f0f0f0,stroke:#333,stroke-dasharray:5 direction LR P1["2TQy..."] --> V1["0"] P2["Gs88..."] --> V2["0"] P3["其他公钥"] --> V3["0"] end classDef wallet fill:#ffcccc,stroke:#333,stroke-width:2px; classDef program fill:#cce5ff,stroke:#333,stroke-width:2px; classDef account fill:#e6ccff,stroke:#333,stroke-width:2px; class WA,WB wallet; class CP program; class CA account;

方式 2: 独立账户

  • 需要记录钱包和账户的映射关系
graph LR
    %% 修复后的图表定义
    classDef wallet fill:#ffffff,stroke-width:3px;
    classDef program fill:#ffffff,stroke:#3399ff,stroke-width:3px;
    classDef account fill:#ffffff,stroke:#cc99ff,stroke-width:3px;
    
    %% --- 节点定义 ---
    WA["🏦 Wallet A"]:::wallet
    WB["🏦 Wallet B"]:::wallet
    CP["💻 Counter Program"]:::program
    CA1["🔢 Counter Account 1"]:::account
    CA2["🔢 Counter Account 2"]:::account
    
    %% --- 标签和细节 ---
    WA_details["PubKey: 2TQy..."]:::detail
    WB_details["PubKey: Gs88..."]:::detail
    CP_details["PubKey: 3yWr..."]:::detail
    CA1_details["PubKey: Eg07o... | count: 0"]:::detail
    CA2_details["PubKey: Bf2g... | count: 0"]:::detail
    
    %% --- 交互关系 ---
    WA -->|交互请求| CP
    WB -->|交互请求| CP
    CP -->|创建/管理| CA1
    CP -->|创建/管理| CA2
    
    %% --- 节点连接细节 ---
    WA -- 公钥 --> WA_details
    WB -- 公钥 --> WB_details
    CP -- 公钥 --> CP_details
    CA1 -- 账户信息 --> CA1_details
    CA2 -- 账户信息 --> CA2_details
    
    %% 细节样式定义
    classDef detail fill:#f9f9f9,stroke:#d9d9d9,stroke-width:1px;
    
    %% 特定样式覆盖
    style WA stroke:#ff6666
    style WB stroke:#66cc66

方式 3: PDA

优点:

  • 不需要记录钱包和账户的映射关系
  • 每个账户独立且不会冲突
graph LR
    %% 图表定义
    classDef wallet fill:#ffffff,stroke-width:3px;
    classDef program fill:#ffffff,stroke:#3399ff,stroke-width:3px;
    classDef account fill:#ffffff,stroke:#cc99ff,stroke-width:3px;
    
    %% --- 节点定义 ---
    WA["🏦 Wallet A"]:::wallet
    WB["🏦 Wallet B"]:::wallet
    CP["💻 Counter Program"]:::program
    CA1["🔢 Counter Account 1"]:::account
    CA2["🔢 Counter Account 2"]:::account
    
    %% --- 标签和细节 ---
    WA_details["PubKey: 2TQy..."]:::detail
    WB_details["PubKey: Gs88..."]:::detail
    CP_details["PubKey: 3yWr..."]:::detail
    CA1_details["PubKey: wallet A pubkey + program pubkey | count: 0"]:::detail
    CA2_details["PubKey: wallet B pubkey + program pubkey | count: 0"]:::detail
    
    %% --- 交互关系 ---
    WA -->|交互请求| CP
    WB -->|交互请求| CP
    CP -->|创建/管理| CA1
    CP -->|创建/管理| CA2
    
    %% --- 节点连接细节 ---
    WA -- 公钥 --> WA_details
    WB -- 公钥 --> WB_details
    CP -- 公钥 --> CP_details
    CA1 -- 账户信息 --> CA1_details
    CA2 -- 账户信息 --> CA2_details
    
    %% --- 特定颜色样式 ---
    linkStyle 0 stroke:#ff6666,stroke-width:2px;
    linkStyle 1 stroke:#66cc66,stroke-width:2px;
    
    %% 细节样式定义
    classDef detail fill:#f9f9f9,stroke:#d9d9d9,stroke-width:1px;
    
    %% 特定样式覆盖
    style WA stroke:#ff6666
    style WB stroke:#66cc66
1
2
3
4
5
6
7
8
9
10
11
12
import { PublicKey } from "@solana/web3.js";

const programId = new PublicKey("11111111111111111111111111111111");
const string = "helloWorld";

const [PDA, bump] = PublicKey.findProgramAddressSync(
[Buffer.from(string)],
programId,
);

console.log(`PDA: ${PDA}`);
console.log(`Bump: ${bump}`);

实战 - Social 项目

账户

graph LR
    %% ==== 样式定义 ====
    classDef wallet fill:#ffcccc,stroke:#cc0000,stroke-width:2px;
    classDef program fill:#cce5ff,stroke:#0066cc,stroke-width:2px;
    classDef account fill:#e6ccff,stroke:#6600cc,stroke-width:2px;
    classDef field fill:#ffffff,stroke:#cccccc,stroke-width:1px,stroke-dasharray:2;
    
    %% ==== 用户钱包 ====
    wa["Wallet A"]:::wallet
    wb["Wallet B"]:::wallet
    
    %% ==== Twitter 程序 ====
    tp["Twitter Program"]:::program
    
    %% ==== 数据账户 ====
    pa["Profile Account"]:::account
    ta["Tweet Account"]:::account
    la["Like Account"]:::account
    
    %% ==== 账户字段 ====
    pa_seed["seed: 'profile' + payer"]:::field
    pa_display["display_name: String"]:::field
    pa_count["tweet_count: u32"]:::field
        
    ta_seed["seed: 'tweet' + payer + count"]:::field
    ta_body["body: String"]:::field
    ta_like["like_count: u32"]:::field
        
    la_seed["seed: 'like' + payer + tweet"]:::field
    la_payer["payer: Pubkey"]:::field
    la_tweet["tweet: Pubkey"]:::field
    
    %% ==== 连接关系 ====
    wa -->|创建/更新| tp
    wb -->|创建/更新| tp
    
    tp -->|管理| pa
    tp -->|创建| ta
    tp -->|记录| la
    
    pa --> pa_seed
    pa --> pa_display
    pa --> pa_count
    
    ta --> ta_seed
    ta --> ta_body
    ta --> ta_like
    
    la --> la_seed
    la --> la_payer
    la --> la_tweet
    
    %% ==== 账户间关联 ====
    pa -.->|创建| ta
    ta -.->|关联| la

SPL Token

graph TD

  subgraph 程序账户[程序账户]
    SP[System Program]:::program
    TP[Token Program]:::program
    TMP[TokenMetadata Program]:::program
  end

  subgraph 钱包账户[钱包账户]
    WA[Wallet Account A
Owner: A]:::wallet WB[Wallet Account B
Owner: B]:::wallet end subgraph 数据账户[数据账户] TA[Token Account
mint: XXX
authority: A]:::tokenAccount TB[Token Account
mint: XXX
authority: B]:::tokenAccount MA[Mint Account
decimals
mint authority]:::mintAccount MDA[Metadata Account
name/symbol/uri
mint authority]:::metadataAccount end WA -->|签名调用| TP TP -->|管理| TA TP -->|管理| TB TP -->|管理| MA TMP -->|管理| MDA MDA -->|关联| MA MA -->|铸造关联| TA MA -->|铸造关联| TB WA -->|持有代币| TA WB -->|持有代币| TB WA -->|mint authority| MA MDA -->|mint authority| MA classDef program fill:#ffcccc,stroke:#ff0000 classDef wallet fill:#ccffcc,stroke:#00cc00 classDef tokenAccount fill:#ccccff,stroke:#0000cc classDef mintAccount fill:#ffccff,stroke:#cc00cc classDef metadataAccount fill:#ffffcc,stroke:#cccc00
  • Title: IB-Anchor-入门
  • Author: Gabrielle
  • Created at : 2025-07-11 10:51:44
  • Updated at : 2025-07-20 18:11:18
  • Link: https://zoella-w.github.io/2025/07/11/95-IB-Anchor-入门/
  • License: This work is licensed under CC BY-NC-SA 4.0.