本文详细介绍了如何在Solana上进行账户数据的反序列化,特别是使用Borsh序列化格式。文章通过一系列清晰的步骤指导读者设置项目、获取账户信息、反序列化账户数据以及清理结果,以使得在Solana开发中处理数据变得更加高效和便捷。
Solana 使用一个叫做 Binary Object Representation Serializer for Hashing(通常称为 Borsh)的过程来对链上的账户数据进行序列化。Borsh 序列化是将数据转换为紧凑的二进制格式的方式,可以在 Solana 区块链上高效存储和传输。它有助于节省区块链上的空间并加快数据传输速度。
如果你曾经使用 solanaWeb3.js 进行过 getAccountInfo
调用,你可能会注意到返回的数据并不易于阅读。这是因为它已经通过 Borsh 进行了序列化。本指南将引导你完成使这些数据对人类可读的步骤。
使用 Solana Web3.js v2.0 构建
本指南将引导你使用 Solana Web3.js 1.x 进行账户反序列化的基础知识。
如果你更喜欢使用 Solana Web3.js v2.0 进行构建,请查看我们在 GitHub 上的示例代码或我们的 指南:如何使用 Solana Web3.js 2.0 反序列化账户数据。
本指南将引导你了解账户结构的一些基础知识,以及如何使用结构模式反序列化 Solana 账户数据。
要跟随本指南,你需要以下内容:
在我们开始之前,让我们花一点时间了解 Borsh 的目的以及为什么它用于数据的序列化。一般来说,Borsh 有助于节省区块链上的空间并加快数据传输速度。它通过以下方式实现:
由于 Borsh 序列化使用更少的字节来表示相同的数据,因此在每次交易中需要传输的数据更少,从而加快数据传输速度。
让我们开始吧!
在终端中创建一个新的项目目录,使用以下命令:
mkdir deserialize-solana
cd deserialize-solana
为你的应用创建一个文件,app.ts:
echo > app.ts
使用“yes”标志初始化你的项目,以使用新包的默认值:
yarn init --yes
#或
npm init --yes
我们需要为本练习添加 Solana Web3 和 Buffer Layout 库。如果你之前没有使用过 buffer-layout 库,这是可以的。这些库将提供定义我们结构的关键元素(注意:还有其他工具可以用于反序列化 Borsh 数据——附录中包括了额外的参考链接)。
在你的终端中输入:
yarn add @solana/web3.js@1 @solana/buffer-layout @solana/buffer-layout-utils
#或
npm install @solana/web3.js@1 @solana/buffer-layout @solana/buffer-layout-utils
我们需要从这些库中引入一些组件。在 app.ts 的第 1 行添加:
import { Connection, PublicKey } from "@solana/web3.js";
import { publicKey, u64, bool } from '@solana/buffer-layout-utils';
import { u32, u8, struct } from '@solana/buffer-layout';
在本指南中要在 Solana 上进行构建,你需要一个 Mainnet API 终端以连接到网络。你可以使用公共节点,也可以部署和管理你自己的基础设施;不过,如果你想要 8 倍的响应速度,可以将繁重的工作留给我们。
了解为什么超过 50% 的 Solana 项目选择 QuickNode,并在 这里 注册免费账户。
接下来,复制 HTTP 提供者链接:
在 app.ts
中的导入语句下,声明你的 RPC 并建立你与 Solana 的 Connection:
const QUICKNODE_RPC = 'https://example.solana-mainnet.quiknode.pro/0123456/'; //替换为你的 HTTP 提供者地址 https://www.quicknode.com/endpoints
const SOLANA_CONNECTION = new Connection(QUICKNODE_RPC);
最后,让我们定义一个我们将要反序列化的账户:
const MINT_ADDRESS = new PublicKey('7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU');
虽然你可以反序列化任何账户数据,但每种类型的账户都有不同的数据模式。为了跟随本指南,你应该使用一个有效的 SPL Token Mint 地址(例如:'7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU')。
你的环境应该看起来像这样。
准备好了吗?让我们开始构建吧!
在 app.ts
中,创建一个新的异步函数,fetchAndParseMint
,该函数接受一个 Connection 和一个 mint 的 PublicKey:
export const fetchAndParseMint = async (mint: PublicKey, solanaConnection: Connection) => {
try {
console.log(`步骤 - 1:获取账户数据 ${mint.toBase58()}`);
let { data } = await solanaConnection.getAccountInfo(mint) || {};
if (!data) return;
console.log(data);
}
catch {
return null;
}
}
fetchAndParseMint(MINT_ADDRESS, SOLANA_CONNECTION);
在上面的代码片段中,我们通过解构 getAccountInfo
调用的结果来获取我们账户的数据。如果发现结果,我们将其记录到控制台。在声明我们的函数后,我们调用它,以便在传入 MINT_ADDRESS
和 SOLANA_CONNECTION
时查看结果。
在你的终端中运行你的应用:
ts-node app
你应该会看到一些不太有用的缓冲账户数据,如下所示:
让我们反序列化这些数据,以便找到更有用的信息。
为了反序列化我们的数据,我们需要了解来自链上程序结构的账户模式,并且需要一个对应于该结构的 TypeScript 接口。
让我们开始定义我们的账户数据的 TypeScript 接口。在你的 fetchAndParseMint
函数下方,声明一个新的接口 RawMint
:
export interface RawMint {
mintAuthorityOption: 1 | 0;
mintAuthority: PublicKey;
supply: bigint;
decimals: number;
isInitialized: boolean;
freezeAuthorityOption: 1 | 0;
freezeAuthority: PublicKey;
}
我们为我们的应用定义了如何期望我们的反序列化数据结构。我们在这里使用的结构必须与链上程序匹配。对于核心 Solana 程序(例如 SPL Token 程序),程序结构是开源并有良好文档的。我们能够从 SPL Program Library GitHub 获取到这个接口。有时,这些接口并不那么易于访问,你可能需要查看一个公共注册中心或探测器(通常发布为接口描述语言,IDL)或直接联系开发者。我们在本指南的最后包含了一些查找 IDL 的资源。
注意:你可能注意到这里使用了一个非原生类型
bigint
。bigint 表示大整数,是一个 JavaScript 的长度整型库,用于支持大数字。虽然这种类型在你的环境中应该可以工作,但如果你使用的是较旧版本的 JavaScript,可能需要将 Big Integer 作为依赖项添加到你的项目中。
接下来,我们必须定义我们的缓冲布局,以便找到数据结构中每个元素的位置。
export const MintLayout = struct<RawMint>([
u32('mintAuthorityOption'),
publicKey('mintAuthority'),
u64('supply'),
u8('decimals'),
bool('isInitialized'),
u32('freezeAuthorityOption'),
publicKey('freezeAuthority'),
]);
我们使用 struct
函数,它是一个泛型函数,输入一个字段数组,返回类型为 RawMint
的 Structure 对象。通过使用 struct<RawMint>
,传递给函数的字段需要与 RawMint
接口中定义的属性匹配,函数将返回一个 Structure<RawMint>
对象。这允许更可靠的类型检查和对 mint 对象预期结构的更好文档。
结构 类有几个方法如 decode
和 encode
,可以用于序列化和反序列化数据。
传递给 struct
函数的每个字段定义了 RawMint 对象结构的特定方面。这些字段在 @solana/buffer-layout-utils 和 @solana/buffer-layout 中定义,通常是接受一个字符串参数的函数,该字符串指定了与 RawMint 对象中对应字段的属性名称。
例如,u32('mintAuthorityOption')
用于为 RawMint 对象中的 mintAuthorityOption
属性定义一个字段,并告诉结构函数,该属性应被解释为 32 位无符号整数。类似地,publicKey('mintAuthority')
用于为 RawMint 对象中的 mintAuthority
属性定义一个字段,并告诉结构函数,该属性应被解释为 PublicKey。简而言之,这些字段定义了 RawMint 对象的每个属性在编码或解码时应如何被解释、读取或写入。
回到你的 fetchAndParseMint
函数,更新你的 try
语句,通过将我们的数据传递给新定义的 MintLayout
的 .decode
:
try {
console.log(`步骤 - 1:获取账户数据 ${mint.toBase58()}`);
let { data } = await solanaConnection.getAccountInfo(mint) || {};
if (!data) return;
console.log(`步骤 - 2:反序列化找到的账户数据`);
const deserialized = MintLayout.decode(data);
console.log(deserialized);
}
我们应该期望这将把我们的 data
解码为 RawMint
类型。继续运行你更新后的代码,在终端中输入:
ts-node app
希望你能看到像这样的结果:
我们还有一些清理工作要做,但你应该可以看到新的对象已经是我们的 RawMint
类的形式。做得好!
让我们稍微清理一下 console.log,提高我们结果的可读性。
在你的 fetchAndParseMint
中,将 console.log(deserialized)
替换为一系列日志,以解构我们的数据:
console.log(`步骤 - 3:清理并记录反序列化数据`);
console.log(' Mint Authority Option:', deserialized.mintAuthorityOption);
console.log(' Mint Authority:', deserialized.mintAuthority.toString());
console.log(' Supply:', (Number(deserialized.supply) / 10 ** deserialized.decimals).toLocaleString(undefined, { maximumFractionDigits: 0 })); // 必须将 bigint 转换为数字
console.log(' Decimals:', deserialized.decimals);
console.log(' Initialized:', deserialized.isInitialized);
console.log(' Freeze Authority Option:', deserialized.freezeAuthorityOption);
console.log(' Freeze Authority:', deserialized.freezeAuthority.toString());
这里有几点:
toString()
将其转换为字符串。Number(value:bigint)
将其转换为数字。因为我们使用的是 SPL Token 程序,因此我们必须考虑我们 token 的 decimals。为此,我们可以通过 10^numDecimals
进行除法。最后,由于数字较大,我们使用 toLocaleString
将数字格式化为本地语言格式(maximumFractionDigits 会移除任何小数)。最后一次运行你的代码,看看你会得到什么。在终端中输入:
ts-node app
🤯 哇!我们从 <Buffer 00 00...
变为一组精美的有用信息,才花了几分钟。干得好。
在结束之前,我们想向你介绍一个无需编写任何代码即可反序列化账户的有用工具。SOL/Borsh 解码器 是一个友好的用户界面,可以做到我们刚刚做的事情。查看一下——它们已为 SPL 程序预加载了结构!
你应该看到我们刚刚从这个练习中得到的结果。玩玩这个工具和其他几个账户——它可以成为帮助你了解账户结构或快速回答你已知结构的便捷工具!
我们已经为你创建了一个 devnet 的账户,你可以用来测试:
pub struct Message {
// discriminator: u64
secret_one: String,
secret_two: String,
value: u8,
completed: bool,
}
你的输入应如下所示:
你看到这个消息了吗?🙌
反序列化数据是 Solana 开发的关键组成部分。干得好,达成了这一点。你正在反序列化什么?这对你下一个项目有什么帮助?在我们的 Discord 里分享你的工作,或者在 Twitter 上关注我们,以获取最新信息!
如果你对本指南有任何反馈,请告知我们。我们很想听听你的想法。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!