Solana[part10]_Anchor实战:创建用户profile

AI-摘要
sonia33 GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
Solana[part10]_Anchor实战:创建用户profile
SoniaChenSolana[part10]_Anchor实战:创建用户profile
一、项目架构概览
1.1 整体架构
anchor_social/
├── programs/ # Solana 智能合约源代码
│ └── anchor_social/ # 核心合约模块
│ ├── src/ # Rust 源代码
│ │ ├── lib.rs # 程序入口和指令路由
│ │ ├── instructions/ # 指令处理逻辑
│ │ └── state/ # 链上状态定义
│ └── Cargo.toml # Rust 项目配置
├── app/ # 前端交互代码
│ ├── api/ # API 接口模块
│ │ ├── wallet.ts # 钱包管理
│ │ └── profile.ts # 用户资料接口
│ └── index.ts # 主程序入口
├── tests/ # 测试模块
├── migrations/ # 合约部署脚本
├── Anchor.toml # Anchor 项目配置
├── package.json # 前端依赖配置
└── tsconfig.json # TypeScript 配置
1.2 技术栈
- 区块链平台: Solana
- 开发框架: Anchor v0.31.1
- 智能合约语言: Rust
- 前端交互: TypeScript
- 依赖管理:
- 前端: yarn
- 合约: Cargo
二、核心模块详解
2.1 智能合约模块 (programs/anchor_social)
2.1.1 程序入口 (lib.rs)
declare_id!("35vQtxXXv5rb99eiVrVVrwYMRYc7vscZvXas8zjEnnK5");
pub mod instructions;
pub mod state;
use instructions::*;
#[program]
pub mod anchor_social {
use super::*;
pub fn create_profile(ctx: Context<CreateProfile>, display_name: String) -> Result<()> {
instructions::profile::create_profile(ctx, display_name)
}
}
declare_id!: 定义程序地址#[program]: Anchor 宏标记程序入口- 指令路由: 将 create_profile 指令路由到 instructions 模块
2.1.2 指令处理 (instructions/profile.rs)
pub fn create_profile(ctx: Context<CreateProfile>, display_name: String) -> Result<()> {
ctx.accounts.profile.display_name = display_name;
Ok(())
}
#[derive(Accounts)]
pub struct CreateProfile<'info> {
#[account(init, payer = user, space = 8 + Profile::INIT_SPACE, seeds = [Profile::SEED_PREFIX.as_bytes(), user.key().as_ref()], bump)]
pub profile: Account<'info, Profile>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
- 业务逻辑: 简单设置显示名称
- 账户约束:
init: 创建新账户payer = user: 指定支付账户seeds: PDA 计算种子mut: 表示可变账户
2.1.3 状态定义 (state/profile.rs)
#[account]
#[derive(InitSpace)]
pub struct Profile {
#[max_len(20)]
pub display_name: String,
}
impl Profile {
pub const SEED_PREFIX: &'static str = "profile";
}
#[account]: 标记为链上账户结构#[derive(InitSpace)]: 自动生成空间计算代码#[max_len(20)]: 字符串长度限制
2.2 前端交互模块 (app/)
2.2.1 钱包管理 (api/wallet.ts)
// 默认钱包
export function useDefaultWallet() {
return anchor.Wallet.local();
}
// 访客钱包
export function useVisitorWallet() {
const keypair = anchor.web3.Keypair.fromSecretKey(new Uint8Array([...]));
return new anchor.Wallet(keypair);
}
- 钱包类型:
local(): 使用本地环境变量钱包Keypair: 通过私钥创建密钥对Wallet: 包装签名功能
- 重要约束:
- 需要 payer 属性进行交易签名
- 必须处理异步签名操作
2.2.2 用户资料接口 (api/profile.ts)
export async function createProfile(wallet: anchor.Wallet, displayName: string) {
return await program.methods.createProfile(displayName).accounts({
user: wallet.publicKey,
})
.signers([wallet.payer]) // 必须显式传递签名者
.rpc();
}
export async function getProfile(wallet: anchor.Wallet) {
const [profilePda,] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("profile"), wallet.publicKey.toBuffer()],
program.programId,
);
return await program.account.profile.fetch(profilePda);
}
- 交易构建:
accounts: 绑定账户参数signers: 指定额外签名者rpc(): 发送交易
- PDA 计算:
- 使用种子生成确定性账户地址
- 保证账户地址一致性
2.2.3 主程序入口 (index.ts)
const defaultWallet = useDefaultWallet();
const visitorWallet = useVisitorWallet();
// 创建用户资料
async function createProfiles() {
try {
await createProfile(defaultWallet, "Bob");
await createProfile(visitorWallet, "Alice");
} catch (error) {
console.error("创建资料失败:", error.message);
}
}
// 获取用户资料
async function fetchProfiles() {
try {
console.log("默认钱包资料:", await getProfile(defaultWallet));
console.log("访客钱包资料:", await getProfile(visitorWallet));
} catch (error) {
console.error("获取资料失败:", error.message);
}
}
- 异步处理: 使用 try-catch 捕获异常
- 流程控制: 分离创建和查询操作
- 日志记录: 详细输出执行结果
三、关键实现细节
3.1 钱包签名机制
class NodeWallet {
constructor(payer) {
this.payer = payer; // Keypair 实例
}
async signTransaction(tx) {
if (isVersionedTransaction(tx)) {
tx.sign([this.payer]); // 版本化交易
} else {
tx.partialSign(this.payer); // 传统交易
}
return tx;
}
}
- 签名流程:
- 检测交易版本
- 根据版本选择签名方式
- 返回签名后的交易
3.2 PDA 账户生成
const [profilePda, bump] = PublicKey.findProgramAddressSync(
[Buffer.from("profile"), wallet.publicKey.toBuffer()],
program.programId
);
- 生成规则:
- 种子由常量和用户地址组成
- 使用程序ID作为程序标识
- 确保地址唯一性和可预测性
3.3 交易错误处理
try {
await createProfile(wallet, "Alice");
} catch (error) {
console.log("错误详情:", {
message: error.message,
logs: error.logs, // 包含链上日志
errorCode: error.errorCode
});
}
- 错误分析要点:
- 查看完整日志堆栈
- 检查链上返回码
- 验证账户状态
四、常见问题及解决方案
4.1 类型错误 (TS2345)
错误信息:
Argument of type 'Keypair' is not assignable to parameter of type 'Wallet'
解决方案:
// 错误方式
return Keypair.fromSecretKey(...)
// 正确方式
return new anchor.Wallet(Keypair.fromSecretKey(...))
4.2 账户不存在错误
错误信息:
Account does not exist or has no data
解决方案:
- 验证钱包地址:
solana address -k ~/.config/solana/t3.json
- 空投 SOL:
solana airdrop 1 <WALLET_ADDRESS>
- 确认 PDA 计算正确性:
console.log("预期PDA:", profilePda.toBase58())
4.3 交易签名错误
错误信息:
Transfer: `from` must not carry data
解决方案:
// 确保正确传递签名者
.signers([wallet.payer])
五、开发最佳实践
-
模块化开发:
- 将指令和状态分离到不同文件
- 使用 pub(crate) 控制可见性
-
错误处理:
- 使用 anyhow 或 thiserror 简化错误处理
- 添加详细的日志记录
-
测试策略:
- 单元测试: 使用 anchor 的测试框架
- 集成测试: 使用本地网络测试完整流程
- 边界测试: 验证输入长度限制
-
安全性建议:
- 避免硬编码密钥
- 使用 Anchor 的账户约束
- 验证所有输入参数







