Skip to content

Commit

Permalink
feat: 🚀 get metadata by owner
Browse files Browse the repository at this point in the history
  • Loading branch information
vecheslav committed Sep 23, 2021
1 parent 8da7b3d commit 8a415b9
Show file tree
Hide file tree
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> {
Expand Down Expand Up @@ -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;
Expand Down
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);
};
Expand Down

0 comments on commit 8a415b9

Please sign in to comment.