使用web3查询,做nft的话最常用的是getProgramAccounts函数,可是很多数据,我们只需要一部分,怎么办?
像官方的metaplex代码,写的很烂,数据都是全网抓取,不适合商用。那如果我们需要查询加灵活的条件怎么办呢,这时候web的利器memcmp来了,可以根据字节的位置查询数据。
在我上一个文章有一些基本account数据的获取方式 Solana DApp开发之 获取各种account数据的方式
但是有些数据,和account绑定关系不是那么纯粹的时候,我们就需要使用复杂查询了(主要是accountInfo函数数据实在有限)
关于条件查询
myBidderPot例子
先使用BidderPotParser 解码;
SCHEME
[
BidderPot,
{
kind: 'struct',
fields: [
['bidderPot', 'pubkeyAsString'],
['bidderAct', 'pubkeyAsString'],
['auctionAct', 'pubkeyAsString'],
['emptied', 'u8'],
],
},
]
filter例子
filters: [
{
dataSize: BIDDER_POT_LEN,
},
{
memcmp: {
offset: 0,
bytes: '7725SAfE76PUNuXReZZvn8xSBEn1K65bzgnCAvNuEUP3',
},
},
],
dataSize就是占用的空间大小
memcmp就是数据内存匹配,offset就是起点,不需要终点,终点按bytes长度加offset算的。bytes不需要编码,因为编码数据是无法匹配的(片段数据的编码和完整数据编码,是完全对不上的)。
offset的话,从0开始。
其他的字段开始根据SCHEME计算。比如查询auctionAct信息,offset就是32 + 32,也就是64.(不需要-1)。
多个条件查询完整例子
import { Connection } from '@solana/web3.js';
import { getProgramAccounts } from 'contexts/meta/loadAccounts';
import { AUCTION_ID, BIDDER_POT_LEN } from 'npms/oystoer';
interface myBidderPotProps {
connection: Connection;
walletPubkey: string;
auctionPubkey: string;
}
export const getMyBidderPot = async ({
connection,
walletPubkey,
auctionPubkey,
}: myBidderPotProps) => {
const biderPot = await getProgramAccounts(connection, AUCTION_ID, {
filters: [
{
dataSize: BIDDER_POT_LEN,
},
{
memcmp: {
offset: 32,
// bidderAct
bytes: walletPubkey,
},
},
{
memcmp: {
offset: 32 + 32,
// auctionAct
bytes: auctionPubkey,
},
},
],
});
return biderPot;
};
根据account查询Edition
像Solana web3里面accountInfo方法masteredtion地址是可以查询出来的(使用json,base64不行),但是edtion就不可以这样查询了
现有开源代码里面 metaplex是这样做的
// 使用getProgramAccounts,根据METADATA_PROGRAM_ID数据数据
getProgramAccounts(connection, METADATA_PROGRAM_ID).then(
forEach(processMetaData),
)
// 遍历后,前端过滤buffer数据
const isEditionV1Account = (account: AccountInfo<Buffer>) =>
account.data[0] === MetadataKey.EditionV1;
// 这是edtion数据格式
export class Edition {
key: MetadataKey;
/// Points at MasterEdition struct
parent: StringPublicKey;
/// Starting at 0 for master record, this is incremented for each edition minted.
edition: BN;
constructor(args: {
key: MetadataKey;
parent: StringPublicKey;
edition: BN;
}) {
this.key = MetadataKey.EditionV1;
this.parent = args.parent;
this.edition = args.edition;
}
}
// 这是SCHEME格式
[
Edition,
{
kind: 'struct',
fields: [
['key', 'u8'],
['parent', 'pubkeyAsString'],
['edition', 'u64'],
],
}
]
看这样子,单查询的话,我们需要根据 parent来查询,offset就是u8的大小,也就是1.
这样filter就可以这样写
filters: [
{
memcmp: {
offset: 1,
bytes: parentPubkey,
},
},
]
这样我们可以获取edtion列表了,但是,未必这就代表他是edtion数据,因为少了头部字节的判断,但是这个判断,和上面的不太一样,这个怎么整?
有两种,1是前端过滤,这是最简单的,因为数据被过滤后本身不多了,所以问题不大;2是直接由提供节点的web3处理,我们说下这个情况。
这里我们先理解下,下面这个Buffer其实是base64或者bse58解码后的数据,也就是可以和查询直接对上关系,所以account.data[0]就代表了schema上面的第一个字段里面的内容,这个值是有范围的,u8刚好。我们要查询edtion,看下schema,这个其实就是key值啦,所以直接加一个条件offset: 0,即可。 这时候filters代码如下:
const isEditionV1Account = (account: AccountInfo<Buffer>) =>
account.data[0] === MetadataKey.EditionV1;
filters: [
{
memcmp: {
offset: 0,
bytes: bytes: base58.encode(new Uint8Array([MetadataKey.EditionV1])),
},
}
{
memcmp: {
offset: 1,
bytes: parentPubkey,
},
},
]
export enum MetadataKey {
Uninitialized = 0,
MetadataV1 = 4,
EditionV1 = 1,
MasterEditionV1 = 2,
MasterEditionV2 = 6,
EditionMarker = 7,
}
完整函数代码
export const accountGetEditionInfo = async ({
parentPubkey,
connection,
}: {
parentPubkey: string;
connection: Connection;
}) => {
const res = await getProgramAccounts(connection, METADATA_PROGRAM_ID, {
filters: [
{
memcmp: {
offset: 0,
bytes: base58.encode(new Uint8Array([MetadataKey.EditionV1])),
},
},
{
memcmp: {
offset: 1,
bytes: parentPubkey,
},
},
],
});
return res;
};
注意
bs58.encode(Buffer.from(key)) ×
base58.encode(new Uint8Array([key])) √
Int类型不能用Buffer
内存大小
基础类型占用空间大小表
| 类型 | 内存大小 |
|---|---|
| pubkeyAsString | 32 |
| u64 | 8 |
| u8 | 1 |
鸣谢
- 感谢Solana群友fun的热心帮助
- 关于key类型(数字类型)的过滤,感谢discord Solana社区的
chido指证
--完--