🎰 NFT质押工作机制详解
🎉 欢迎来到质押的世界!
哇哦!你已经来到第三周的终点线了!🏁 是时候把你这些天学到的超能力运用到真正的项目中了 —— 让我们为你的 buildoors
NFT项目打造一个超酷的质押系统!
🎯 今天的任务: 构建一个完整的NFT质押程序(除了代币功能,那是下周的大餐!)
🎮 游戏规则
🎯 你的使命(如果你选择接受的话)
创建一个能够追踪每个用户质押状态的智能程序。想象它就像一个超级账本,记录着:
- 🎫 谁质押了什么NFT
- ⏰ 什么时候质押的
- 💰 应该获得多少奖励
💡 小贴士: 现在我们先不碰真正的代币转移,那部分下周再说。这周我们专注于状态管理!
🏗️ 架构设计 - 四大金刚指令
1️⃣ InitializeStakeAccount(初始化质押账户)🏠
/// 🏗️ 创建用户的质押小金库
///
/// 这个指令会创建一个特殊的账户,就像给每个用户
/// 建造一个专属的保险箱,用来存放他们的质押信息
pub fn initialize_stake_account(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
msg!("🎉 Creating a brand new stake account!");
// 🔑 PDA种子配方:
// - 用户的公钥(谁的保险箱)
// - NFT代币账户(放什么宝贝)
// 这样每个用户+NFT组合都有独一无二的地址!
// TODO: 实现PDA推导逻辑
// let seeds = &[
// b"stake",
// user_pubkey.as_ref(),
// nft_token_account.as_ref(),
// ];
Ok(())
}
🎯 核心概念: PDA = 用户公钥 + NFT代币账户 = 独一无二的质押地址!
2️⃣ Stake(质押)⚡
/// ⚡ 质押你的NFT - 让它开始赚钱!
///
/// 暂时只更新状态,不实际转移NFT
/// 就像在账本上记录:"小明的猴子NFT从现在开始工作了!"
pub fn stake(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
msg!("🎮 Staking NFT - Let the earning begin!");
// 📝 需要记录的信息:
// 1. is_staked = true (标记为已质押)
// 2. stake_start_time = Clock::get()?.unix_timestamp (记录开始时间)
// 3. last_redeem = stake_start_time (初始化最后领取时间)
// 💡 Pro Tip: 使用 Clock 获取链上时间
let clock = Clock::get()?;
let current_timestamp = clock.unix_timestamp;
msg!("⏰ Staking at timestamp: {}", current_timestamp);
// TODO: 更新状态账户
// account_data.is_staked = true;
// account_data.stake_start_time = current_timestamp;
msg!("✅ NFT successfully staked! Time to earn!");
Ok(())
}
🚀 技巧: 记录时间戳很重要!它决定了用户能获得多少奖励。
3️⃣ Redeem(兑换奖励)💰
/// 💰 领取你的奖励 - 收获的时刻!
///
/// 根据质押时长计算奖励
/// 公式:奖励 = 质押时长 × 奖励率
pub fn redeem(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
msg!("💎 Time to collect your rewards!");
// 🧮 奖励计算公式(简化版)
// let time_elapsed = current_time - last_redeem_time;
// let rewards = time_elapsed * REWARD_RATE; // 比如每秒1个代币
// 📊 示例计算:
// 如果用户质押了1小时(3600秒)
// 奖励 = 3600 * 1 = 3600个代币!
let clock = Clock::get()?;
let current_timestamp = clock.unix_timestamp;
// TODO: 计算奖励
// let time_staked = current_timestamp - account_data.last_redeem;
// let rewards = time_staked as u64 * TOKENS_PER_SECOND;
msg!("🎉 You earned {} tokens!", 0); // 暂时输出0
// 🔄 更新最后领取时间
// account_data.last_redeem = current_timestamp;
Ok(())
}
💡 数学小课堂:
- 质押10分钟 = 600秒
- 每秒1个代币 = 600个代币奖励!
- 记得更新
last_redeem
时间!
4️⃣ Unstake(解除质押)🔓
/// 🔓 解除质押 - 拿回你的NFT!
///
/// 结算所有未领取的奖励并解除质押状态
pub fn unstake(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
msg!("🔄 Unstaking NFT - Thanks for staking!");
// 📋 解除质押检查清单:
// ✅ 计算最终奖励
// ✅ 标记为未质押状态
// ✅ 清空质押时间记录
// ✅ (未来)返还NFT给用户
// 🎁 先结算未领取的奖励
// let final_rewards = calculate_pending_rewards();
// msg!("💰 Final rewards: {}", final_rewards);
// 🔄 重置状态
// account_data.is_staked = false;
// account_data.stake_start_time = 0;
// account_data.last_redeem = 0;
msg!("👋 NFT unstaked successfully! Come back soon!");
Ok(())
}
📦 数据结构设计
🗂️ 质押状态账户结构
/// 📊 质押账户状态 - 记录所有重要信息
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct StakeAccount {
/// 👤 质押者地址
pub owner: Pubkey,
/// 🖼️ NFT铸币地址
pub mint: Pubkey,
/// 🎯 是否已质押
pub is_staked: bool,
/// ⏱️ 质押开始时间(Unix时间戳)
pub stake_start_time: i64,
/// 🕐 上次领取奖励时间
pub last_redeem: i64,
/// 💰 累计未领取奖励(暂存)
pub pending_rewards: u64,
/// 🔢 版本号(方便未来升级)
pub version: u8,
}
impl StakeAccount {
/// 📏 账户大小常量
pub const SIZE: usize = 32 + 32 + 1 + 8 + 8 + 8 + 1;
/// 🎯 检查是否可以领取奖励
pub fn can_redeem(&self, current_time: i64) -> bool {
self.is_staked && current_time > self.last_redeem
}
/// 🧮 计算待领取奖励
pub fn calculate_rewards(&self, current_time: i64, rate: u64) -> u64 {
if !self.is_staked {
return 0;
}
let time_elapsed = (current_time - self.last_redeem) as u64;
time_elapsed * rate
}
}
🛠️ 实用工具箱
⏰ 时间管理神器
use solana_program::clock::Clock;
use solana_program::sysvar::Sysvar;
/// 🕐 获取当前链上时间
pub fn get_current_timestamp() -> Result<i64, ProgramError> {
let clock = Clock::get()?;
Ok(clock.unix_timestamp)
}
/// ⏱️ 计算时间差(秒)
pub fn calculate_time_elapsed(start: i64, end: i64) -> u64 {
(end - start).abs() as u64
}
🎯 PDA生成助手
/// 🔑 生成质押账户PDA
pub fn get_stake_account_pda(
user: &Pubkey,
nft_token_account: &Pubkey,
program_id: &Pubkey,
) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[
b"stake",
user.as_ref(),
nft_token_account.as_ref(),
],
program_id,
)
}
💡 专业提示与技巧
🎓 新手常见陷阱
-
⚠️ 时间单位混淆
// ❌ 错误:混淆毫秒和秒
let rewards = time_ms * TOKENS_PER_SECOND;
// ✅ 正确:统一使用秒
let rewards = time_seconds * TOKENS_PER_SECOND; -
⚠️ 忘记更新last_redeem
// ❌ 错误:忘记更新时间戳
calculate_rewards();
// account_data.last_redeem 没更新!
// ✅ 正确:记得更新
let rewards = calculate_rewards();
account_data.last_redeem = current_time; -
⚠️ PDA种子顺序
// ❌ 错误:种子顺序不一致
// 初始化用: ["stake", user, nft]
// 查找用: ["stake", nft, user] // 顺序不对!
// ✅ 正确:保持一致
let seeds = &[b"stake", user.as_ref(), nft.as_ref()];
🎮 测试你的程序
🧪 单元测试示例
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reward_calculation() {
// 🧪 测试奖励计算
let stake_account = StakeAccount {
is_staked: true,
last_redeem: 1000,
// ... 其他字段
};
// 质押10秒,每秒1个代币
let rewards = stake_account.calculate_rewards(1010, 1);
assert_eq!(rewards, 10);
println!("✅ 奖励计算测试通过!");
}
}
🚀 下一步行动
📋 检查清单
- 🏗️ 实现四个核心指令
- 📊 设计状态账户结构
- ⏰ 集成Clock系统变量
- 🧮 实现奖励计算逻辑
- 🧪 编写基础测试
- 🎨 (可选)添加更多功能
🎯 挑战升级
想要更大的挑战?试试这些:
- 🎲 随机奖励倍数 - 让质押更刺激!
- 📈 递增奖励率 - 质押越久,奖励越高!
- 🏆 排行榜系统 - 看谁是质押王!
- ⏰ 锁定期机制 - 设置最短质押时间
📚 学习资源
🔗 有用的链接
- 📖 Clock文档 - 时间管理必读
- 💻 解决方案代码 - 卡住了?看这里!
- 🎓 Solana Cookbook - 更多实用示例
💬 遇到困难?
🤝 记住: 挣扎是学习的一部分!先自己尝试,实在不行再看答案。每个错误都是成长的机会!
🎊 总结
你现在已经掌握了NFT质押的核心概念!虽然我们还没有处理真正的代币转移,但你已经构建了整个质押系统的骨架。
下周,我们将添加真正的代币功能,让这个系统活起来!
🌟 座右铭: "质押不仅是锁定资产,更是创造价值!"
准备好了吗?让我们开始编码吧!🚀
Happy Staking! 🎰✨