本文详细讲解了如何使用Solana Web3.js 2.0的新编码解码工具来解析Solana账户数据结构,特别是针对Raydium AMM配置文件的解码。文章不仅介绍了二进制数据的基本概念,还提供了逐步的实施指南,如何设置项目、定义常量、创建账户解码器等,以便有效解析账户信息。
Solana 账户以原始字节形式存储数据,这些数据必须正确解码才能在你的应用中发挥作用。本指南演示如何使用 Solana Web3.js 2.0 的新编解码工具( @solana/codecs-core)来编码和解码 Solana 数据结构。在本指南中,我们将生成一个解码器,用于解析 Raydium AMM 配置文件。让我们开始吧!
使用 Solana Web3.js Legacy(v1.x)进行构建
本指南将带你了解如何使用 Solana Web3.js 2.0 进行账户反序列化。
如果你更喜欢使用 Solana Web3.js Legacy(v1.x)进行构建,请查看我们在 GitHub 的示例代码或我们的 指南:如何使用 Solana Web3.js 1.x 反序列化账户数据。
在深入实现之前,让我们了解一些关键概念,关于 Solana 中二进制数据的工作方式。
Solana 账户将数据存储为字节序列。当读取这些数据时,我们需要:
例如,一个简单的账户具有一个 u8(1 字节),后面跟着一个 11 字节/字符的字符串,将按以下方式读取:
[1, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
└─┘ └──────────────────────────────────────────────────┘
│ └── 字符串(11 字节)= "hello world"
│ (104='h', 101='e', 108='l', 108='l', 111='o', 32=' ',
│ 119='w', 111='o', 114='r', 108='l', 100='d')
└────── u8(1 字节)= 1
因此,这被反序列化为:
请查看这个有用的 空间参考表 以获取 Solana 编程中常见类型的空间分配。
字节序决定了多字节数字在内存中的存储方式:
Solana 程序可以使用程序中指定的任一字节序。在我们的示例中,Raydium 的程序使用大端字节序(如其 源代码 中所示,使用 to_be_bytes()
而不是 to_le_bytes()
),因此我们也必须使用大端字节序进行解码。
Solana 的 getAccountInfo
可以返回编码的账户数据,而不是原始字节。我们将指定 Base64 编码的字符串,因为:
这意味着我们需要:
让我们通过为 Raydium AMM 配置账户 9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x
(SolScan) 实现一个账户数据解码器。
我们的目标是提取在 SolScan 数据标签中发现的与 getAccountInfo
调用中的相同信息。让我们来做吧!
创建一个新项目并安装依赖项:
mkdir account-decoder && cd account-decoder
然后,初始化项目:
npm init -y
并安装依赖项:
npm install @solana/web3.js@2 dotenv
如果你没有全局安装这些开发依赖,请添加:
npm install --save-dev typescript ts-node @types/node
初始化你的 tsconfig:
tsc --init
将以下脚本添加到你的 package.json
:
"start": "ts-node app.ts"
要在 Solana 上进行构建,你需要一个 API 端点来连接网络。你可以使用公共节点或部署和管理自己的基础设施;但是,如果你想要 8 倍的响应时间,你可以将繁重的工作留给我们。
看看为什么超过 50% 的 Solana 项目选择 QuickNode,并在 这里 注册免费账户。我们将使用一个 Solana Mainnet 端点。
复制 HTTP 提供者链接:
创建一个 .env
文件,并填写你的 Solana RPC 端点:
HTTP_ENDPOINT=https://your-quicknode-endpoint.example
创建一个新文件 app.ts
,并添加以下导入和常量:
import {
createSolanaRpc
} from "@solana/rpc"
import {
Address,
address,
getAddressDecoder,
getProgramDerivedAddress,
} from "@solana/addresses";
import {
Endian,
getU16Encoder,
getBase64Encoder,
getStructDecoder,
FixedSizeDecoder,
fixDecoderSize,
getBytesDecoder,
getU8Decoder,
getU16Decoder,
getU32Decoder,
getU64Decoder,
getArrayDecoder,
ReadonlyUint8Array
} from "@solana/codecs";
import dotenv from "dotenv";
dotenv.config();
const PROGRAM_ID = address('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK');
const AMM_CONFIG_SEED = "amm_config";
const AMM_CONFIG_INDEX = 4;
注意我们从不同的包中导入 Solana 元素--每个包在 @solana/web3.js
中也可用,但是我们只是强调,如果你更喜欢,可以选择导入特定的包。你会看到 codecs
包中有许多根据类型进行编码和解码的数据工具。我们稍后会使用这些工具。
我们定义的常量是:
PROGRAM_ID
是 CLMM 程序的 ID(参考:GitHub)AMM_CONFIG_SEED
在程序中指定并用于派生配置账户的 PDA。(参考:GitHub)AMM_CONFIG_INDEX
是 Raydium 用于定义配置账户 PDA 的管理员指定值。(参考:GitHub)添加描述我们账户数据结构的接口。我们直接从 Raydium IDL 获取这个接口(在 idl.accounts.find(account => account.name == "AmmConfig")
)。数据结构必须正确顺序和正确类型列出,以避免在解码时出现任何解析或类型错误:
interface AmmConfig {
anchorDiscriminator: ReadonlyUint8Array;
bump: number;
index: number;
owner: Address;
protocolFeeRate: number;
tradeFeeRate: number;
tickSpacing: number;
fundFeeRate: number;
paddingU32: number;
fundOwner: Address;
padding: bigint[];
}
注意 SW3js2 要求我们将缓冲区定义为 ReadonlyUint8Array
和 u64(及更大)必须声明为 bigint。
解码器指定如何从二进制数据中读取每个字段,并且应与我们刚刚定义的 IDL 和接口相匹配。将以下内容添加到你的代码中:
const ammConfigDecoder: FixedSizeDecoder<AmmConfig> =
getStructDecoder([\
["anchorDiscriminator", fixDecoderSize(getBytesDecoder(), 8)],\
["bump", getU8Decoder()],\
["index", getU16Decoder()],\
["owner", getAddressDecoder()],\
["protocolFeeRate", getU32Decoder()],\
["tradeFeeRate", getU32Decoder()],\
["tickSpacing", getU16Decoder()],\
["fundFeeRate", getU32Decoder()],\
["paddingU32", getU32Decoder()],\
["fundOwner", getAddressDecoder()],\
["padding", getArrayDecoder(\
getU64Decoder(),\
{ size: 3 }\
)]\
]);
解码器中的每个字段指定:
由于我们已经知道要搜索的地址,实际上我们并不需要此步骤,但这在使用 Solana 的地址和编解码库时是个好习惯。
添加主函数以获取和解码账户数据:
async function main() {
// 创建用于 PDA 派生的编码器
const u16BEEncoder = getU16Encoder({ endian: Endian.Big });
// 派生配置账户地址
const [configPda] = await getProgramDerivedAddress({
programAddress: PROGRAM_ID,
seeds: [\
AMM_CONFIG_SEED,\
u16BEEncoder.encode(AMM_CONFIG_INDEX),\
]
});
console.log(`解析 AMM 配置 PDA: ${configPda}`);
// TODO - 获取并解析 PDA
}
main().catch(console.error);
在这里,我们使用 getProgramDerivedAddress
来派生我们的 PDA(来源:GitHub)。该方法期望一个程序地址和一个 Seeds 数组,后者定义为 type Seed = ReadonlyUint8Array | string;
。这意味着我们可以直接使用我们的 AMM_CONFIG_SEED
,但我们需要首先将我们的 AMM_CONFIG_INDEX
编码为 ReadonlyUint8Array
。
为此,我们定义了一个 u16 大端编码器(记住,Raydium 程序使用 .to_be_bytes()
),然后在我们的索引上调用 encode
方法。
我们将程序 ID 和种子(按顺序)传递到 getProgramDerivedAddress
函数中并等待结果。
在添加我们的账户解码器之前,让我们确保我们正确地派生了 PDA。如果我们的常量正确定义并且我们正确编码了种子,我们应该返回正确的 PDA,9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x
。
在你的终端中,运行脚本:
npm start
你应该在终端中看到记录的正确 PDA:
解析 AMM 配置 PDA: 9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x
干得好!
现在,让我们更新在 main
函数中看到的 TODO。在 main
函数内,在现有代码下面添加以下内容:
async function main() {
// 创建用于 PDA 派生的编码器
const u16BEEncoder = getU16Encoder({ endian: Endian.Big });
// 派生配置账户地址
const [configPda] = await getProgramDerivedAddress({
programAddress: PROGRAM_ID,
seeds: [\
AMM_CONFIG_SEED,\
u16BEEncoder.encode(AMM_CONFIG_INDEX),\
]
});
console.log(`解析 AMM 配置 PDA: ${configPda}`);
// 👇 添加此部分
const rpc = createSolanaRpc(process.env.HTTP_ENDPOINT as string);
const base64Encoder = getBase64Encoder();
const { value } = await rpc.getAccountInfo(configPda, { encoding: 'base64' }).send();
if (!value || !value?.data) {
throw new Error(`未找到账户: ${configPda.toString()}`);
}
let bytes = base64Encoder.encode(value.data[0]);
const decoded = ammConfigDecoder.decode(bytes);
console.log(decoded);
}
让我们拆解一下:
createSolanaRpc
函数和我们的 Solana Mainnet 端点定义我们的 rpc
getAccountInfo
数据编码为字节configPda
账户信息ammConfigDecoder
调用 decode
方法以反序列化原始数据让我们尝试一下。
在你的终端中,运行脚本:
npm start
你应该看到解码的 AMM 配置数据打印到控制台:
{
anchorDiscriminator: Uint8Array(8) [\
218, 244, 33, 104,\
203, 203, 43, 111\
],
bump: 249,
index: 4,
owner: 'projjosVCPQH49d5em7VYS7fJZzaqKixqKtus7yk416',
protocolFeeRate: 120000,
tradeFeeRate: 100,
tickSpacing: 1,
fundFeeRate: 40000,
paddingU32: 0,
fundOwner: 'FundHfY8oo8J9KYGyfXFFuQCHe7Z1VBNmsj84eMcdYs4',
padding: [ 0n, 0n, 0n ]
}
干得漂亮!你现在具备使用 Solana Web3.js 2.0 反序列化和解析 Solana 账户数据所需的工具。
Codama 是一个将 Solana 程序标准化为称为 Codama IDL 格式的工具。这些 Codama IDL 可用于生成各种输出,包括程序客户端。生成的程序客户端自动创建账户编解码器以序列化和反序列化数据 - 例如,像 getAmmConfigurationAccountDecoder
这样的方法可以通过传入 Raydium IDL 自动创建(在后台,这些方法使用 @solana/codecs 包)。
要了解有关使用 Codama 构建程序客户端的更多信息,请查看我们的指南:
在处理 Solana 中的二进制数据时,错误的余地很小。如果有一个字节位置错误,你的整个响应可能会扭曲。在处理字节数据时,这里有一些重要的考虑事项:
请注意,这些工具也可用于指令数据!你只需确保理解预期的输入参数以派生你的解码器结构。
我们很高兴看到你在做什么——在 QuickNode Discord 或 Twitter 上给我们留言,告诉我们你构建了什么!
让我们知道 如果你有任何反馈或对新主题的请求。我们很乐意听到你的声音。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!