Codes in lesson1
Codes in lesson2
创建 Anchor 项目 1 anchor init <new-workspace-name>
本地开发 启动本地 solana 网络 把 solana 命令行配置成使用本地环境 1 2 solana config get solana config set --url localhost
创建一个文件系统钱包 要使用 Solana CLI 部署程序,需要一个带有 SOL 代币的 Solana 钱包来支付区块链上的交易和数据存储成本
创建一个简单的文件系统钱包,以便在本地开发过程中使用:
告诉 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 anchor_lang::prelude::*;declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" ); #[program] mod hello_anchor { use super::*; pub fn initialize (_ctx: Context<Initialize>) -> Result <()> { Ok (()) } } #[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