本文介绍了通过 Solana Web3.js 和 Anchor 从前端读取账户数据的三种方法:自家 Anchor 程序用 fetch、跨程序读取需 IDL、任意账户用 Web3.js 自定义反序列化,强调了序列化与 IDL 的作用,为 Solana dApp 开发提供指导。
本文展示如何通过 Solana Web3.js 和 Anchor 从前端直接读取账户数据,适用于 Web 应用程序。前文使用 solana account <地址> 查看数据,但此方法不适合 dApp。本教程将通过客户端计算账户地址、读取数据并反序列化,实现前端数据访问。
以以太坊为例,若避免公共变量或视图函数,可通过 getStorageAt(contract_address, slot) 获取存储槽数据。Solana 类似,但只需提供程序地址并推导数据账户地址,无需槽索引。
以下为前文 Rust 代码,未作修改:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("9XZGDi1imvGFV3bLuDJK1bkUht51GPCRsVxMe4DpqWEo");
#[program]
pub mod basic_storage {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, new_x: u64) -> Result<()> {
ctx.accounts.my_storage.x = new_x;
Ok(())
}
}
#[derive(Accounts)]
pub struct Set<'info> {
#[account(mut, seeds = [], bump)]
pub my_storage: Account<'info, MyStorage>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = signer,
space = size_of::<MyStorage>() + 8,
seeds = [],
bump
)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyStorage {
x: u64,
}
测试代码初始化账户并写入 x = 170,然后读取:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { BasicStorage} from "../target/types/basic_storage";
describe("basic_storage", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.BasicStorage as Program<BasicStorage>;
it("Is initialized!", async () => {
const seeds = []
const [myStorage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
console.log("the storage account address is", myStorage.toBase58());
await program.methods.initialize().accounts({myStorage: myStorage}).rpc();
await program.methods.set(new anchor.BN(170)).accounts({myStorage: myStorage}).rpc();
// 读取数据
let myStorageStruct = await program.account.myStorage.fetch(myStorage);
console.log("The value of x is:",myStorageStruct.x.toString());
});
});
解析 fetch
IDL 定义:
"accounts": [
{
"name": "MyStorage",
"type": {
"kind": "struct",
"fields": [
{
"name": "x",
"type": "u64"
}
]
}
}
],
限制:此方法仅适用于自身程序或具备 IDL 的 Anchor 程序。对任意账户,反序列化会失败。
若知晓另一 Anchor 程序的 IDL,可读取其账户数据。以下新建程序 other_program:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("Abn2q3x6sLk25bCkZysDnwcjoKddhtJLjCcjyuRMCZkt");
#[program]
pub mod other_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn setbool(ctx: Context<SetFlag>, flag: bool) -> Result<()> {
ctx.accounts.true_or_false.flag = flag;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
signer: Signer<'info>,
system_program: Program<'info, System>,
#[account(init, payer = signer, space = size_of::<TrueOrFalse>() + 8, seeds = [], bump)]
true_or_false: Account<'info, TrueOrFalse>,
}
#[derive(Accounts)]
pub struct SetFlag<'info> {
#[account(mut)]
true_or_false: Account<'info, TrueOrFalse>,
}
#[account]
pub struct TrueOrFalse {
flag: bool,
}
测试初始化并设置 flag = true:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { OtherProgram } from "../target/types/other_program";
describe("other_program", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.OtherProgram as Program<OtherProgram>;
it("Is initialized!", async () => {
const seeds = []
const [TrueOrFalse, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
console.log("address: ", program.programId.toBase58());
await program.methods.initialize().accounts({trueOrFalse: TrueOrFalse}).rpc();
await program.methods.setbool(true).accounts({trueOrFalse: TrueOrFalse}).rpc();
});
});
新建 read 项目,仅用 Typescript 读取 other_program 数据:
parent_dir/
├── other_program/
└── read/
代码如下(需确保 otherProgramAddress 和 IDL 路径正确):
import * as anchor from "@coral-xyz/anchor";
describe("read", () => {
anchor.setProvider(anchor.AnchorProvider.env());
it("Read other account", async () => {
// 替换为实际地址
const otherProgramAddress = "Abn2q3x6sLk25bCkZysDnwcjoKddhtJLjCcjyuRMCZkt";
const otherProgramId = new anchor.web3.PublicKey(otherProgramAddress);
const otherIdl = JSON.parse(
require("fs").readFileSync("../other_program/target/idl/other_program.json", "utf8")
);
const otherProgram = new anchor.Program(otherIdl, otherProgramId);
const seeds = []
const [trueOrFalseAcc, _bump] =
anchor.web3.PublicKey.findProgramAddressSync(seeds, otherProgramId);
let otherStorageStruct = await otherProgram.account.trueOrFalse.fetch(trueOrFalseAcc);
console.log("The value of flag is:", otherStorageStruct.flag.toString());
});
});
运行 anchor test --skip-local-validator,输出:
The value of flag is: true
✔ Read other account
限制:仅适用于 Anchor 程序,依赖其序列化规则。
对于非 Anchor 程序,需使用 Solana Web3.js 的原始方法。文档有限,推荐参考 HTTP JSON RPC,getAccountInfo 是首选。
Solana 账户数据无强制序列化标准:
本文展示了三种读取账户数据的方法:
【笔记配套代码】 https://github.com/0xE1337/rareskills_evm_to_solana 【参考资料】 https://learnblockchain.cn/column/119 https://www.rareskills.io/solana-tutorial
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!