Solana[part16]_Solana质押-编写应用级别的质押合约-质押部分

AI-摘要
sonia33 GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
Solana[part16]_Solana质押-编写应用级别的质押合约-质押部分
SoniaChenSolana质押-编写应用级别的质押合约-质押部分
核心代码见:
https://github.com/SoniaChan33/anchor_social/commit/48b428c2a33213275cea8cc42be199f8bfc7a332 nft mint 编写
https://github.com/SoniaChan33/anchor_social/commit/a27180fc342c3be81e670794f0e0ddf22817881c stake nft
1. NFT 铸造(Mint)功能
1.1 功能说明
通过 nft_mint_v1 方法实现 NFT 铸造,遵循 MPL Token Metadata 标准,创建包含元数据的唯一 NFT,主要流程包括:
- 创建 NFT 元数据账户
- 铸造 1 个代币到关联账户
- 创建主版本账户(Master Edition)确保唯一性
1.2 核心实现(nft_mint.rs)
1.2.1 铸造方法
pub fn nft_mint_v1(ctx: Context<NFTMint>, nft_id: String) -> Result<()> {
// 定义签名种子(PDA)
let signer_seeds: &[&[&[u8]]] = &[&[
MyNFT::SEED_PREFIX.as_bytes(),
nft_id.as_bytes(),
&[ctx.bumps.nft_mint_account],
]];
// 创建元数据账户
create_metadata_accounts_v3(
CpiContext::new_with_signer(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMetadataAccountsV3 {
metadata: ctx.accounts.meta_account.to_account_info(),
mint: ctx.accounts.nft_mint_account.to_account_info(),
mint_authority: ctx.accounts.nft_mint_account.to_account_info(),
update_authority: ctx.accounts.nft_mint_account.to_account_info(),
payer: ctx.accounts.authority.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
signer_seeds,
),
DataV2 {
name: format!("{} #{}", MyNFT::TOKEN_NAME.to_string(), nft_id),
symbol: MyNFT::TOKEN_SYMBOL.to_string(),
uri: MyNFT::TOKEN_URL.to_string(),
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
},
false,
true,
None,
)?;
// 铸造 1 个 NFT 到关联账户
mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.nft_mint_account.to_account_info(),
to: ctx.accounts.nft_associated_token_account.to_account_info(),
authority: ctx.accounts.nft_mint_account.to_account_info(),
},
signer_seeds,
),
1,
)?;
// 创建主版本账户(限制最大发行量为 1)
create_master_edition_v3(
CpiContext::new_with_signer(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMasterEditionV3 {
edition: ctx.accounts.master_edition_account.to_account_info(),
payer: ctx.accounts.authority.to_account_info(),
mint: ctx.accounts.nft_mint_account.to_account_info(),
metadata: ctx.accounts.meta_account.to_account_info(),
mint_authority: ctx.accounts.nft_mint_account.to_account_info(),
update_authority: ctx.accounts.nft_mint_account.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
signer_seeds,
),
Some(1), // 最大发行量,确保 NFT 唯一性
)?;
Ok(())
}
1.2.2 账户结构(NFTMint)
#[derive(Accounts)]
#[instruction(nft_id: String)]
pub struct NFTMint<'info> {
/// 主版本账户(MPL 标准)
#[account(
mut,
seeds = [
b"metadata",
token_metadata_program.key().as_ref(),
nft_mint_account.key().as_ref(),
b"edition".as_ref(),
],
bump,
seeds::program = token_metadata_program.key(),
)]
pub master_edition_account: UncheckedAccount<'info>,
/// 元数据账户(MPL 标准)
#[account(
mut,
seeds = [
b"metadata",
token_metadata_program.key().as_ref(),
nft_mint_account.key().as_ref(),
],
bump,
seeds::program = token_metadata_program.key(),
)]
pub meta_account: UncheckedAccount<'info>,
/// NFT Mint 账户(PDA 派生)
#[account(
init_if_needed,
payer = authority,
seeds = [MyNFT::SEED_PREFIX.as_bytes(), &nft_id.to_string().as_bytes()],
bump,
mint::decimals = MyNFT::TOKEN_DECIMALS,
mint::authority = nft_mint_account.key(),
mint::freeze_authority = nft_mint_account.key(),
)]
pub nft_mint_account: Account<'info, Mint>,
/// 用户关联的 NFT 代币账户
#[account(
init_if_needed,
payer = authority,
associated_token::mint = nft_mint_account,
associated_token::authority = authority,
)]
pub nft_associated_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub authority: Signer<'info>, // 发起者签名账户
// 依赖程序
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub token_metadata_program: Program<'info, Metadata>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub rent: Sysvar<'info, Rent>,
}
1.2.3 NFT 配置常量(state/nft.rs)
pub struct MyNFT;
impl MyNFT {
pub const SEED_PREFIX: &'static str = "MyNFT_v1"; // PDA 种子前缀
pub const TOKEN_DECIMALS: u8 = 0; // NFT 通常使用 0 位小数
pub const TOKEN_NAME: &'static str = "My NFT"; // 名称前缀
pub const TOKEN_SYMBOL: &'static str = "MTK"; // 符号
pub const TOKEN_URL: &'static str = "https://img.soniachen.com/IMG_0151.JPG"; // 元数据 URI
}
1.3 客户端调用示例(app/api/nft.ts)
import * as anchor from "@coral-xyz/anchor";
import { program } from "./wallet";
export async function nftMint(wallet: anchor.Wallet, nftId: string) {
const tx = await program.methods.nftMint(nftId).accounts({
authority: wallet.publicKey,
}).rpc();
return tx;
}
2. NFT 质押(Stake)功能
2.1 功能说明
通过 nft_stake 方法实现 NFT 质押,主要流程包括:
- 创建质押信息记录(
StakeInfo) - 将 NFT 从用户账户转移到质押池(程序托管账户)
- 铸造流动性代币作为质押奖励
2.2 核心实现(nft_stake.rs)
2.2.1 质押方法
pub fn nft_stake(ctx: Context<NFTStake>) -> Result<()> {
// 记录质押关系
let stake_info = StakeInfo::new(
ctx.accounts.authority.key(),
ctx.accounts.nft_mint_account.key(),
);
ctx.accounts.stake_info.set_inner(stake_info.clone());
// 将 NFT 从用户账户转移到质押池
transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.nft_associated_token_account.to_account_info(),
to: ctx.accounts.program_receipt_ata.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
1, // NFT 数量为 1
)?;
// 铸造流动性代币作为奖励
let signer_seeds: &[&[&[u8]]] = &[&[
MyToken::SEED_PREFIX.as_bytes(),
&[ctx.bumps.token_mint_account],
]];
mint_to(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.token_mint_account.to_account_info(),
to: ctx.accounts.associated_token_account.to_account_info(),
authority: ctx.accounts.token_mint_account.to_account_info(),
},
)
.with_signer(signer_seeds),
10000, // 奖励代币数量
)?;
Ok(())
}
2.2.2 账户结构(NFTStake)
#[derive(Accounts)]
pub struct NFTStake<'info> {
/// 质押信息记录账户
#[account(
init_if_needed,
payer = authority,
space = 8 + StakeInfo::INIT_SPACE,
seeds = [
StakeInfo::SEED_PREFIX.as_bytes(),
nft_mint_account.key().as_ref(),
],
bump,
)]
pub stake_info: Box<Account<'info, StakeInfo>>,
/// 质押池接收 NFT 的账户
#[account(
init_if_needed,
payer = authority,
associated_token::mint = nft_mint_account,
associated_token::authority = stake_info,
)]
pub program_receipt_ata: Box<Account<'info, TokenAccount>>,
/// 流动性代币 Mint 账户
#[account(
mut,
seeds = [MyToken::SEED_PREFIX.as_bytes(),],
bump,
)]
pub token_mint_account: Box<Account<'info, Mint>>,
/// 用户的流动性代币关联账户
#[account(
init_if_needed,
payer = authority,
associated_token::mint = token_mint_account,
associated_token::authority = authority,
)]
pub associated_token_account: Box<Account<'info, TokenAccount>>,
/// 被质押的 NFT Mint 账户
#[account(mut)]
pub nft_mint_account: Box<Account<'info, Mint>>,
/// 用户的 NFT 关联账户
#[account(mut,
associated_token::mint = nft_mint_account,
associated_token::authority = authority,
)]
pub nft_associated_token_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub authority: Signer<'info>, // 发起者签名账户
// 依赖程序
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
2.2.3 质押信息结构(state/stake.rs)
#[account]
#[derive(InitSpace)]
pub struct StakeInfo {
pub staker: Pubkey, // 质押人地址
pub nft_mint_account: Pubkey, // 质押的 NFT Mint 地址
pub staked_at: u64, // 质押时间(纪元)
}
impl StakeInfo {
pub const SEED_PREFIX: &'static str = "stake_v1"; // PDA 种子前缀
pub fn new(staker: Pubkey, nft_mint_account: Pubkey) -> Self {
Self {
staker,
nft_mint_account,
staked_at: Clock::get().unwrap().epoch,
}
}
}
2.3 客户端调用示例(app/api/stake.ts)
import * as anchor from "@coral-xyz/anchor";
import { program } from "./wallet";
import { getNftMintAccount } from "./account";
export async function stakeNFT(wallet: anchor.Wallet, nft_id: string) {
return await program.methods.nftStake().accounts({
nftMintAccount: getNftMintAccount(nft_id),
}).signers([wallet.payer]).rpc();
}
3. 关键技术点
-
PDA 账户管理:通过种子(Seed)派生程序派生地址(PDA)管理 NFT Mint 账户、质押信息账户等,确保账户安全性和可预测性。
-
MPL 元数据标准:遵循 Metaplex Token Metadata 标准创建 NFT 元数据和主版本账户,确保 NFT 兼容性和唯一性。
-
代币转移与铸造权限:使用 SPL Token 程序进行代币转移,通过 PDA 签名授予铸造权限,避免私钥直接暴露。
-
关联代币账户(ATA):使用关联代币账户规范管理用户与代币的关联关系,简化账户创建和管理流程。
-
质押奖励机制:质押 NFT 时铸造流动性代币作为奖励,实现激励机制闭环。
验证
查看账号,发现我的nft已经转移走了,取而代之的是token发放到我的账户中








