Skip to main content

🥇 为用户铸造代币 - 让评论变成财富!

🎮 欢迎来到代币经济的世界!

嘿,Web3建设者们!👋 我们的电影评论程序已经很酷了,但现在要让它真正Web3化!想象一下:

  • 📝 写影评 = 💰 获得代币
  • 💬 发评论 = 🪙 赚取奖励
  • 🎬 活跃用户 = 🏆 代币富翁

这就像StackOverflow遇见了加密货币!每个贡献都有价值,每个互动都能赚钱!🚀

🎯 今日任务: 集成SPL Token程序,为活跃用户铸造奖励代币!


🏗️ 项目准备 - 搭建你的代币工厂

📦 选择你的起点

你有两个选择:

# 🎯 选项1:继续使用本地环境
cd your-existing-project

# 🎯 选项2:克隆完整的起始代码
git clone https://github.com/buildspace/solana-movie-program/
cd solana-movie-program
git checkout solution-add-comments

💡 Pro Tip: 如果你想要一个干净的开始,也可以从这个Playground环境复制!

🎨 升级你的工具箱

打开 Cargo.toml,添加魔法依赖

[dependencies]
solana-program = "~1.10.29"
borsh = "0.9.3"
thiserror = "1.0.31"

# 🪙 新增的代币超能力!
spl-token = { version="3.2.0", features = [ "no-entrypoint" ] }
spl-associated-token-account = { version="=1.0.5", features = [ "no-entrypoint" ] }

🔍 这些是什么?

  • spl-token = Solana的代币程序库(创建和管理代币)
  • spl-associated-token-account = 用户代币账户助手(自动管理用户钱包)

✅ 快速健康检查

# 🔨 测试构建
cargo build-sbf

# 看到绿色的 "Finished"了吗?太棒了!🎉

🪙 理解代币铸造 - 你的印钞机!

📚 什么是Token Mint?

┌─────────────────────────────────────┐
│ 🏭 Token Mint(代币铸造) │
├─────────────────────────────────────┤
│ │
│ 就像是你的代币工厂: │
│ │
│ • 📊 记录代币总供应量 │
│ • 🔑 控制谁能铸造新币 │
│ • 📈 追踪代币小数位数 │
│ • 🎯 定义代币的基本属性 │
│ │
│ 类比:中央银行的印钞机 🏦 │
└─────────────────────────────────────┘

📝 Step 1: 更新指令系统

🎯 更新instruction.rs

// 📋 添加新的指令类型
pub enum MovieInstruction {
// 🎬 添加电影评论
AddMovieReview {
title: String,
rating: u8,
description: String,
},

// ✏️ 更新电影评论
UpdateMovieReview {
title: String,
rating: u8,
description: String,
},

// 💬 添加评论
AddComment {
comment: String,
},

// 🆕 初始化代币铸造!
InitializeMint, // 不需要任何参数 - 简单明了!
}

🔄 更新解包函数

impl MovieInstruction {
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
// 🎯 获取指令类型(第一个字节)
let (&variant, rest) = input
.split_first()
.ok_or(ProgramError::InvalidInstructionData)?;

// 🎭 根据类型解析不同的指令
Ok(match variant {
0 => {
// 🎬 处理添加评论
let payload = MovieReviewPayload::try_from_slice(rest).unwrap();
Self::AddMovieReview {
title: payload.title,
rating: payload.rating,
description: payload.description,
}
}
1 => {
// ✏️ 处理更新评论
let payload = MovieReviewPayload::try_from_slice(rest).unwrap();
Self::UpdateMovieReview {
title: payload.title,
rating: payload.rating,
description: payload.description,
}
}
2 => {
// 💬 处理添加评论
let payload = CommentPayload::try_from_slice(rest).unwrap();
Self::AddComment {
comment: payload.comment,
}
}
// 🪙 处理初始化铸造 - 超级简单!
3 => Self::InitializeMint,

_ => return Err(ProgramError::InvalidInstructionData),
})
}
}

🎮 Step 2: 更新处理器

📦 添加必要的导入

// processor.rs 顶部
use solana_program::{
// ... 现有的导入 ...

// 🆕 新增的系统级导入
sysvar::{rent::Rent, Sysvar, rent::ID as RENT_PROGRAM_ID},
native_token::LAMPORTS_PER_SOL, // SOL到lamports的转换
system_program::ID as SYSTEM_PROGRAM_ID // 系统程序ID
};

// 🪙 SPL Token相关导入
use spl_associated_token_account::get_associated_token_address;
use spl_token::{instruction::initialize_mint, ID as TOKEN_PROGRAM_ID};

🚦 更新路由函数

pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// 📦 解包指令
let instruction = MovieInstruction::unpack(instruction_data)?;

// 🎯 路由到对应的处理函数
match instruction {
MovieInstruction::AddMovieReview { title, rating, description } => {
msg!("🎬 处理添加电影评论...");
add_movie_review(program_id, accounts, title, rating, description)
},

MovieInstruction::UpdateMovieReview { title, rating, description } => {
msg!("✏️ 处理更新电影评论...");
update_movie_review(program_id, accounts, title, rating, description)
},

MovieInstruction::AddComment { comment } => {
msg!("💬 处理添加评论...");
add_comment(program_id, accounts, comment)
},

// 🆕 处理初始化铸造!
MovieInstruction::InitializeMint => {
msg!("🪙 初始化代币铸造...");
initialize_token_mint(program_id, accounts)
},
}
}

🏭 Step 3: 实现代币铸造功能

🎯 完整的初始化函数

pub fn initialize_token_mint(
program_id: &Pubkey,
accounts: &[AccountInfo]
) -> ProgramResult {
msg!("🏭 开始创建代币铸造工厂...");

// 📋 Step 1: 解析账户列表
// 账户顺序很重要!客户端会按这个顺序发送
let account_info_iter = &mut accounts.iter();

// 👤 谁发起了这个交易
let initializer = next_account_info(account_info_iter)?;
msg!("👤 初始化者: {}", initializer.key);

// 🪙 代币铸造PDA - 在客户端派生
let token_mint = next_account_info(account_info_iter)?;

// 🔑 代币铸造权限账户
let mint_auth = next_account_info(account_info_iter)?;

// ⚙️ 系统程序 - 用于创建新账户
let system_program = next_account_info(account_info_iter)?;

// 🎯 Solana Token程序地址
let token_program = next_account_info(account_info_iter)?;

// 📊 系统租金账户 - 用于计算租金
let sysvar_rent = next_account_info(account_info_iter)?;

// 🔐 Step 2: 派生并验证PDA
// 种子是 "token_mint" - 简单明了!
let (mint_pda, mint_bump) = Pubkey::find_program_address(
&[b"token_mint"], // 🌱 种子
program_id
);

// 派生铸造权限PDA
// 种子是 "token_auth"
let (mint_auth_pda, _mint_auth_bump) = Pubkey::find_program_address(
&[b"token_auth"], // 🌱 种子
program_id
);

msg!("🪙 Token mint地址: {:?}", mint_pda);
msg!("🔑 Mint权限地址: {:?}", mint_auth_pda);

// ✅ Step 3: 验证所有重要账户
if mint_pda != *token_mint.key {
msg!("❌ Token mint账户不匹配!");
return Err(ReviewError::IncorrectAccountError.into());
}

if *token_program.key != TOKEN_PROGRAM_ID {
msg!("❌ Token程序ID不正确!");
return Err(ReviewError::IncorrectAccountError.into());
}

if *mint_auth.key != mint_auth_pda {
msg!("❌ Mint权限账户不匹配!");
return Err(ReviewError::IncorrectAccountError.into());
}

if *system_program.key != SYSTEM_PROGRAM_ID {
msg!("❌ 系统程序ID不正确!");
return Err(ReviewError::IncorrectAccountError.into());
}

if *sysvar_rent.key != RENT_PROGRAM_ID {
msg!("❌ 租金程序ID不正确!");
return Err(ReviewError::IncorrectAccountError.into());
}

// 💰 Step 4: 计算租金
let rent = Rent::get()?;
// Mint账户的大小是82字节(记住这个数字!)
let rent_lamports = rent.minimum_balance(82);
msg!("💵 需要租金: {} lamports", rent_lamports);

// 🏗️ Step 5: 创建Token Mint PDA账户
msg!("🔨 创建token mint账户...");
invoke_signed(
// 创建账户的系统指令
&system_instruction::create_account(
initializer.key, // 💰 付款人
token_mint.key, // 🆕 新账户地址
rent_lamports, // 💵 租金
82, // 📏 账户大小(字节)
token_program.key, // 👮 账户所有者
),
// 涉及的账户
&[
initializer.clone(),
token_mint.clone(),
system_program.clone(),
],
// 🔑 PDA签名种子
&[&[b"token_mint", &[mint_bump]]],
)?;

msg!("✅ Token mint账户创建成功!");

// 🎉 Step 6: 初始化mint账户
msg!("🎯 初始化mint账户...");
invoke_signed(
// SPL Token的初始化mint指令
&initialize_mint(
token_program.key, // Token程序
token_mint.key, // Mint账户
mint_auth.key, // Mint权限
Option::None, // 冻结权限 - 我们不需要冻结功能!
9, // 小数位数(9 = 像SOL一样)
)?,
// 涉及的账户
&[
token_mint.clone(),
sysvar_rent.clone(),
mint_auth.clone()
],
// 🔑 PDA签名种子
&[&[b"token_mint", &[mint_bump]]],
)?;

msg!("🎊 Token mint初始化完成!");
msg!("🏭 代币工厂已经开始运作!");

Ok(())
}

💡 关键概念解析:

  • PDA(程序派生地址) = 只有程序能控制的特殊地址
  • Bump Seed = 确保地址不在Ed25519曲线上的魔法数字
  • 租金 = 在Solana上存储数据需要支付的费用

🚨 Step 4: 添加错误处理

⚠️ 更新error.rs

#[derive(Debug, Error)]
pub enum ReviewError {
// 😴 账户未初始化
#[error("Account not initialized yet")]
UninitializedAccount,

// 🔍 PDA不匹配
#[error("PDA derived does not equal PDA passed in")]
InvalidPDA,

// 📏 数据太长
#[error("Input data exceeds max length")]
InvalidDataLength,

// ⭐ 评分无效
#[error("Rating greater than 5 or less than 1")]
InvalidRating,

// 🆕 账户不匹配错误
#[error("Accounts do not match")]
IncorrectAccountError, // 用于验证账户时
}

🚀 部署和测试

🔨 构建和部署

# 🧹 Step 1: 清理旧的部署文件
# 在文件浏览器中删除 target/deploy 文件夹中的密钥对

# 🔨 Step 2: 构建程序
cargo build-sbf

# 🚀 Step 3: 部署到本地网络
# 复制控制台输出的部署命令并运行

# 💰 如果余额不足
solana airdrop 2

🧪 设置测试客户端

# 📥 Step 1: 克隆测试客户端
git clone https://github.com/buildspace/solana-movie-token-client
cd solana-movie-token-client

# 📦 Step 2: 安装依赖
npm install

⚙️ 配置客户端

  1. 更新程序ID 📝

    // index.ts
    const PROGRAM_ID = "你的程序ID";
  2. 切换到本地网络 🌐

    // 第67行
    const connection = new web3.Connection("http://localhost:8899");
  3. 开启日志监控 👀

    # 在新的终端窗口
    solana logs 你的程序ID

🎮 运行测试

# 🚀 启动测试脚本
npm start

# 🎉 你应该看到类似这样的输出:
# "Created token mint account"
# "Initialized token mint"
# "Token mint initialization complete!"

💡 实用技巧与最佳实践

🎯 技巧1:调试技巧

// 🔍 添加详细的调试信息
msg!("🔍 调试信息:");
msg!(" 程序ID: {}", program_id);
msg!(" Mint PDA: {}", mint_pda);
msg!(" Bump: {}", mint_bump);

🎯 技巧2:错误处理模式

// 🛡️ 优雅的错误处理
fn validate_accounts(
expected: &Pubkey,
actual: &Pubkey,
error_msg: &str
) -> ProgramResult {
if expected != actual {
msg!("❌ {}", error_msg);
return Err(ReviewError::IncorrectAccountError.into());
}
Ok(())
}

🎯 技巧3:租金计算助手

// 💰 通用租金计算函数
fn calculate_rent(size: usize) -> Result<u64, ProgramError> {
let rent = Rent::get()?;
Ok(rent.minimum_balance(size))
}

🎓 知识总结

📚 你学到了什么?

┌─────────────────────────────────────┐
│ 🏆 成就解锁 │
├─────────────────────────────────────┤
│ ✅ 理解Token Mint概念 │
│ ✅ 集成SPL Token程序 │
│ ✅ 创建和初始化Mint账户 │
│ ✅ 使用PDA控制代币 │
│ ✅ 处理跨程序调用(CPI) │
└─────────────────────────────────────┘

🔮 接下来是什么?

下一步我们将:

  1. 🪙 为用户创建代币账户
  2. 💰 实现代币铸造逻辑
  3. 🎁 为评论和留言发放奖励
  4. 📊 创建代币经济系统

🚀 总结

恭喜!🎉 你已经成功创建了你的第一个代币铸造工厂!现在你的程序有了自己的货币系统,可以奖励活跃用户了!

💭 思考: 这只是开始!想象一下你可以用代币做什么:

  • 🏪 创建市场
  • 🗳️ 投票权重
  • 🎮 游戏化体验
  • 💎 VIP功能

准备好铸造你的第一批代币了吗?让我们继续前进! 🚀💰✨