Solana[part11]_Anchor实战:用户发帖&点赞

AI-摘要
sonia33 GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
Solana[part11]_Anchor实战:用户发帖&点赞
SoniaChenSolana[part11]_Anchor实战:用户发帖&点赞
发帖功能(createTweet)
1. 核心数据结构
发帖功能的核心数据结构为Tweet,定义在programs/anchor_social/src/state/tweet.rs中,用于存储帖子内容及点赞数:
// programs/anchor_social/src/state/tweet.rs
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct Tweet {
#[max_len(50)] // 限制帖子内容最大长度为50字符
pub body: String, // 帖子内容
pub like_count: u64, // 点赞数,初始为0
}
impl Tweet {
pub const SEED_PREFIX: &'static str = "tweet"; // PDA种子前缀
// 初始化新帖子
pub fn new(body: String) -> Self {
Self {
body,
like_count: 0,
}
}
}
2. 前端API实现(创建帖子)
前端通过app/api/tweet.ts中的createTweet函数发起创建帖子的交易,核心逻辑包括生成帖子PDA、调用链上程序:
// app/api/tweet.ts
import * as anchor from "@coral-xyz/anchor";
import { program } from "./wallet";
export async function createTweet(
wallet: anchor.Wallet,
body: string,
): Promise<[anchor.web3.PublicKey, string]> {
// 1. 生成用户Profile的PDA(用于关联帖子所属用户)
const [profilePda,] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("profile"), wallet.publicKey.toBuffer()], // 种子:"profile" + 用户公钥
program.programId,
);
// 2. 获取用户Profile数据(需先创建Profile)
const profile = await program.account.profile.fetch(profilePda);
// 3. 生成帖子的PDA(唯一标识帖子)
const [tweetPda] = anchor.web3.PublicKey.findProgramAddressSync(
[
Buffer.from("tweet"), // 种子1:固定前缀"tweet"
profilePda.toBuffer(), // 种子2:用户Profile的PDA(关联用户)
Buffer.from((profile.tweet_count + 1).toString()) // 种子3:用户的第N+1条帖子(确保唯一性)
],
program.programId,
);
// 4. 调用链上程序创建帖子,返回帖子PDA和交易签名
return [
tweetPda,
await program.methods.createTweet(body)
.accounts({
authority: wallet.publicKey, // 交易发起者(帖子作者)
tweet: tweetPda, // 帖子账户(新建)
})
.rpc() // 发送交易
];
}
3. 后端程序逻辑(创建帖子)
链上程序逻辑定义在programs/anchor_social/src/instructions/tweet.rs中,负责初始化帖子账户、更新用户发帖数:
// programs/anchor_social/src/instructions/tweet.rs
use crate::state::profile::*;
use crate::state::tweet::*;
use anchor_lang::prelude::*;
// 处理创建帖子的核心逻辑
pub fn create_tweet(ctx: Context<CreateTweet>, body: String) -> Result<()> {
// 1. 更新用户Profile的发帖数(+1)
let profile = &mut ctx.accounts.profile;
profile.tweet_count += 1;
// 2. 初始化帖子数据
let tweet = Tweet::new(body);
// 将帖子数据写入账户(set_inner用于更新Anchor账户的内部数据)
ctx.accounts.tweet.set_inner(tweet.clone());
Ok(())
}
// 定义创建帖子所需的账户结构
#[derive(Accounts)]
pub struct CreateTweet<'info> {
// 帖子账户(新建)
#[account(
init, // 初始化新账户
payer = authority, // 由authority支付账户创建费用
space = 8 + Tweet::INIT_SPACE, // 分配存储空间(8字节为Anchor账户前缀)
seeds = [ // PDA种子(需与前端生成逻辑一致)
Tweet::SEED_PREFIX.as_bytes(), // "tweet"
profile.key().as_ref(), // 用户Profile的PDA
(profile.tweet_count + 1).to_string().as_ref() // 第N+1条帖子
],
bump // 自动计算PDA的bump值
)]
pub tweet: Account<'info, Tweet>,
// 用户Profile账户(需已存在,用于记录发帖数)
#[account(mut, seeds = [Profile::SEED_PREFIX.as_bytes(), authority.key().as_ref()], bump)]
pub profile: Account<'info, Profile>,
// 交易发起者(帖子作者,签名者)
#[account(mut)]
pub authority: Signer<'info>,
// Solana系统程序(用于创建新账户)
pub system_program: Program<'info, System>,
}
4. 关键技术点解析
- PDA(Program Derived Address)设计:帖子的PDA由
"tweet" + 用户Profile的PDA + 帖子序号组成,确保每个用户的每条帖子有唯一标识,且可通过种子逆向推导。 - 状态更新依赖:创建帖子时必须先存在用户Profile(用于记录
tweet_count),否则会因无法获取Profile数据而失败。 - 存储空间分配:
space = 8 + Tweet::INIT_SPACE中,8字节是Anchor账户的固定前缀(用于存储账户 discriminator),Tweet::INIT_SPACE由#[derive(InitSpace)]自动计算(包含body和like_count的空间)。
点赞功能(CreateLike)
1. 核心数据结构
点赞功能涉及两个核心数据结构:Like(存储点赞关系)和Tweet(记录点赞数)。Like定义在programs/anchor_social/src/state/like.rs中:
// programs/anchor_social/src/state/like.rs
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct Like {
pub profile_pubkey: Pubkey, // 点赞用户的公钥
pub tweet_pubkey: Pubkey, // 被点赞帖子的公钥
}
impl Like {
pub const SEED_PREFIX: &'static str = "like"; // PDA种子前缀
// 初始化新点赞
pub fn new(profile_pubkey: Pubkey, tweet_pubkey: Pubkey) -> Self {
Self {
profile_pubkey,
tweet_pubkey,
}
}
}
2. 前端API实现(创建点赞)
前端通过app/api/tweet.ts中的createLike函数发起点赞交易:
// app/api/tweet.ts
export async function createLike(
wallet: anchor.Wallet,
tweetPdas: anchor.web3.PublicKey // 被点赞帖子的PDA
) {
// 调用链上程序创建点赞,返回交易签名
return await program.methods.createLike()
.accounts({
tweet: tweetPdas, // 被点赞的帖子账户
authority: wallet.publicKey, // 点赞用户
})
.signers([wallet.payer]) // 签名者(点赞用户)
.rpc();
}
3. 后端程序逻辑(创建点赞)
链上程序逻辑定义在programs/anchor_social/src/instructions/tweet.rs中,负责创建点赞记录、更新帖子点赞数:
// programs/anchor_social/src/instructions/tweet.rs
use crate::state::like::*;
use crate::state::tweet::*;
use anchor_lang::prelude::*;
// 处理创建点赞的核心逻辑
pub fn create_like(ctx: Context<CreateLike>) -> Result<()> {
// 1. 更新帖子的点赞数(+1)
let tweet = &mut ctx.accounts.tweet;
tweet.like_count += 1;
// 2. 初始化点赞记录(关联点赞用户和帖子)
let like = Like::new(ctx.accounts.authority.key(), tweet.key());
// 将点赞数据写入账户
ctx.accounts.like.set_inner(like);
Ok(())
}
// 定义创建点赞所需的账户结构
#[derive(Accounts)]
pub struct CreateLike<'info> {
// 点赞记录账户(新建)
#[account(
init, // 初始化新账户
payer = authority, // 由点赞用户支付费用
space = 8 + Like::INIT_SPACE, // 分配存储空间
seeds = [ // PDA种子(确保唯一:同一用户对同一帖子只能点赞一次)
Like::SEED_PREFIX.as_bytes(), // "like"
profile.key().as_ref(), // 点赞用户的Profile PDA
tweet.key().as_ref() // 被点赞帖子的PDA
],
bump
)]
pub like: Account<'info, Like>,
// 被点赞的帖子账户(需已存在,更新点赞数)
#[account(mut)]
pub tweet: Account<'info, Tweet>,
// 点赞用户的Profile账户(验证用户身份)
#[account(mut, seeds = [Profile::SEED_PREFIX.as_bytes(), authority.key().as_ref()], bump)]
pub profile: Account<'info, Profile>,
// 点赞用户(签名者)
#[account(mut)]
pub authority: Signer<'info>,
// 系统程序(用于创建点赞账户)
pub system_program: Program<'info, System>,
}
4. 关键技术点解析
- 点赞唯一性保障:点赞记录的PDA由
"like" + 点赞用户Profile PDA + 帖子PDA组成,确保同一用户对同一帖子只能创建一条点赞记录(重复创建会因PDA已存在而失败)。 - 状态联动更新:点赞时同时更新两个状态:
tweet.like_count(帖子的点赞数+1)和Like账户(存储点赞关系)。 - 依赖验证:点赞操作依赖三个前提:帖子账户已存在、点赞用户的Profile账户已存在,否则会因账户不存在而失败。
使用示例
以下是app/index.ts中发帖和点赞的调用示例,展示完整流程:
// app/index.ts
import { useDefaultWallet, useVisitorWallet } from "./api/wallet";
import { createTweet, getTweet, createLike } from "./api/tweet";
(async () => {
const defaultWallet = useDefaultWallet(); // 发帖用户钱包
const visitorWallet = useVisitorWallet(); // 点赞用户钱包
// 1. 创建帖子
const [tweetPda, createTweetTx] = await createTweet(defaultWallet, "Hello, world!");
console.log("帖子创建成功,PDA:", tweetPda.toBase58(), "交易签名:", createTweetTx);
// 2. 获取帖子详情(验证创建结果)
const tweet = await getTweet(defaultWallet, tweetPda);
console.log("帖子初始信息:", tweet); // like_count应为0
// 3. 对帖子点赞
const createLikeTx = await createLike(visitorWallet, tweetPda);
console.log("点赞成功,交易签名:", createLikeTx);
// 4. 再次获取帖子详情(验证点赞数更新)
const updatedTweet = await getTweet(defaultWallet, tweetPda);
console.log("点赞后帖子信息:", updatedTweet); // like_count应为1
})();
总结
发帖和点赞功能通过Anchor框架实现了Solana区块链上的状态管理:
- 发帖功能通过PDA关联用户与帖子,确保唯一性并记录发帖数;
- 点赞功能通过PDA保障唯一点赞,并联动更新帖子的点赞数;
- 核心依赖Profile账户作为用户身份标识,所有操作均需验证账户存在性。
理解上述逻辑有助于掌握Solana上的PDA设计、账户交互及状态更新模式。







