Skip to content

Commit

Permalink
feat: 🚀 get metadata by owner
Browse files Browse the repository at this point in the history
vecheslav committed Sep 23, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 8da7b3d commit 8a415b9
Showing 4 changed files with 123 additions and 5 deletions.
38 changes: 34 additions & 4 deletions api/src/programs/metadata/accounts/Metadata.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import { ERROR_INVALID_ACCOUNT_DATA, ERROR_INVALID_OWNER } from '@metaplex/errors';
import { AnyPublicKey, StringPublicKey } from '@metaplex/types';
import { Borsh } from '@metaplex/utils';
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import bs58 from 'bs58';
import { Buffer } from 'buffer';
import { Account } from '../../../Account';
import { TokenAccount } from '../../shared';
import { MetadataKey, MetadataProgram } from '../MetadataProgram';
import { Edition } from './Edition';
import { MasterEdition } from './MasterEdition';
import { MetadataKey, MetadataProgram } from '../MetadataProgram';
import { ERROR_INVALID_ACCOUNT_DATA, ERROR_INVALID_OWNER } from '@metaplex/errors';
import { Buffer } from 'buffer';

type CreatorArgs = { address: StringPublicKey; verified: boolean; share: number };
export class Creator extends Borsh.Data<CreatorArgs> {
@@ -122,6 +124,34 @@ export class Metadata extends Account<MetadataData> {
]);
}

static async getAll(connection: Connection) {
return (
await MetadataProgram.getProgramAccounts(connection, {
filters: [
// Filter for MetadataV1 by key
{
memcmp: {
offset: 0,
bytes: bs58.encode(Buffer.from([MetadataKey.MetadataV1])),
},
},
],
})
).map((account) => Metadata.from(account));
}

static async getMetdataByOwner(connection: Connection, owner: AnyPublicKey) {
const accounts = await TokenAccount.getTokenAccountsByOwner(connection, owner);
const accountMap = new Map(accounts.map(({ data }) => [data.mint.toString(), data]));
const allMetadata = await Metadata.getAll(connection);

return allMetadata.filter(
(metadata) =>
accountMap.has(metadata.data.mint) &&
(accountMap?.get(metadata.data.mint)?.amount?.toNumber() || 0) > 0,
);
}

async getEdition(connection: Connection) {
const mint = this.data?.mint;
if (!mint) return;
73 changes: 73 additions & 0 deletions api/src/programs/shared/accounts/TokenAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ERROR_INVALID_ACCOUNT_DATA, ERROR_INVALID_OWNER } from '@metaplex/errors';
import { AnyPublicKey } from '@metaplex/types';
import {
AccountInfo as TokenAccountInfo,
AccountLayout,
TOKEN_PROGRAM_ID,
u64,
} from '@solana/spl-token';
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import { Buffer } from 'buffer';
import { Account } from '../../../Account';

export class TokenAccount extends Account<TokenAccountInfo> {
constructor(pubkey: AnyPublicKey, info: AccountInfo<Buffer>) {
super(pubkey, info);

if (!this.assertOwner(TOKEN_PROGRAM_ID)) {
throw ERROR_INVALID_OWNER();
}

if (!TokenAccount.isCompatible(this.info.data)) {
throw ERROR_INVALID_ACCOUNT_DATA();
}

this.data = deserialize(this.info.data);
}

static isCompatible(data: Buffer) {
return data.length === AccountLayout.span;
}

static async getTokenAccountsByOwner(connection: Connection, owner: AnyPublicKey) {
return (
await connection.getTokenAccountsByOwner(new PublicKey(owner), {
programId: TOKEN_PROGRAM_ID,
})
).value.map(({ pubkey, account }) => new TokenAccount(pubkey, account));
}
}

export const deserialize = (data: Buffer) => {
const accountInfo = AccountLayout.decode(data);
accountInfo.mint = new PublicKey(accountInfo.mint);
accountInfo.owner = new PublicKey(accountInfo.owner);
accountInfo.amount = u64.fromBuffer(accountInfo.amount);

if (accountInfo.delegateOption === 0) {
accountInfo.delegate = null;
accountInfo.delegatedAmount = new u64(0);
} else {
accountInfo.delegate = new PublicKey(accountInfo.delegate);
accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
}

accountInfo.isInitialized = accountInfo.state !== 0;
accountInfo.isFrozen = accountInfo.state === 2;

if (accountInfo.isNativeOption === 1) {
accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
accountInfo.isNative = true;
} else {
accountInfo.rentExemptReserve = null;
accountInfo.isNative = false;
}

if (accountInfo.closeAuthorityOption === 0) {
accountInfo.closeAuthority = null;
} else {
accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
}

return accountInfo;
};
1 change: 1 addition & 0 deletions api/src/programs/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './accounts/TokenAccount';
export * from './transactions';
16 changes: 15 additions & 1 deletion examples/node/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import { Connection, Metadata } from '@metaplex/js';
import { PublicKey } from '@solana/web3.js';
import { Keypair, PublicKey } from '@solana/web3.js';

const payer = Keypair.fromSecretKey(
new Uint8Array([
225, 60, 117, 68, 123, 252, 1, 200, 41, 251, 54, 121, 6, 167, 204, 18, 140, 168, 206, 74, 254,
156, 230, 10, 212, 124, 162, 85, 120, 78, 122, 106, 187, 209, 148, 182, 34, 149, 175, 173, 192,
85, 175, 252, 231, 130, 76, 40, 175, 177, 44, 111, 250, 168, 3, 236, 149, 34, 236, 19, 46, 9,
66, 138,
]),
);
const metadataPubkey = new PublicKey('CZkFeERacU42qjApPyjamS13fNtz7y1wYLu5jyLpN1WL');
const connection = new Connection('devnet');

const run = async () => {
// TODO: just waiting for layer service with combine
console.time('ownedMetadata');
const ownedMetadata = await Metadata.getMetdataByOwner(connection, payer.publicKey);
console.log(ownedMetadata);
console.timeEnd('ownedMetadata');

const metadata = await Metadata.load(connection, metadataPubkey);
console.log(metadata);
};

0 comments on commit 8a415b9

Please sign in to comment.