diff --git a/js/packages/common/src/actions/metadata.ts b/js/packages/common/src/actions/metadata.ts index feb788f..9070d2c 100644 --- a/js/packages/common/src/actions/metadata.ts +++ b/js/packages/common/src/actions/metadata.ts @@ -213,6 +213,7 @@ export class Metadata { data: Data; primarySaleHappened: boolean; isMutable: boolean; + editionNonce: number | null; // set lazy masterEdition?: PublicKey; @@ -224,6 +225,7 @@ export class Metadata { data: Data; primarySaleHappened: boolean; isMutable: boolean; + editionNonce: number | null; }) { this.key = MetadataKey.MetadataV1; this.updateAuthority = args.updateAuthority; @@ -231,6 +233,7 @@ export class Metadata { this.data = args.data; this.primarySaleHappened = args.primarySaleHappened; this.isMutable = args.isMutable; + this.editionNonce = args.editionNonce; } public async init() { diff --git a/js/packages/web/src/contexts/meta/loadAccounts.ts b/js/packages/web/src/contexts/meta/loadAccounts.ts index fa33d5c..12ad553 100644 --- a/js/packages/web/src/contexts/meta/loadAccounts.ts +++ b/js/packages/web/src/contexts/meta/loadAccounts.ts @@ -4,7 +4,7 @@ import { METAPLEX_ID, VAULT_ID, } from '@oyster/common/dist/lib/utils/ids'; -import { Connection } from '@solana/web3.js'; +import { Connection, PublicKey } from '@solana/web3.js'; import { AccountAndPubkey, MetaState, ProcessAccountsFunc } from './types'; import { isMetadataPartOfStore } from './isMetadataPartOfStore'; import { processAuctions } from './processAuctions'; @@ -12,12 +12,16 @@ import { processMetaplexAccounts } from './processMetaplexAccounts'; import { processMetaData } from './processMetaData'; import { processVaultData } from './processVaultData'; import { + findProgramAddress, + getEdition, + getMultipleAccounts, MAX_CREATOR_LEN, MAX_CREATOR_LIMIT, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH, MAX_URI_LENGTH, Metadata, + METADATA_PREFIX, ParsedAccount, } from '../../../../common/dist/lib'; import { @@ -83,35 +87,46 @@ export const loadAccounts = async (connection: Connection, all: boolean) => { tempCache.whitelistedCreatorsByCreator, ); - for (let i = 0; i < MAX_CREATOR_LIMIT; i++) { - for (let j = 0; j < whitelistedCreators.length; j++) { - additionalPromises.push( - connection - .getProgramAccounts(METADATA_PROGRAM_ID, { - filters: [ - { - memcmp: { - offset: - 1 + // key - 32 + // update auth - 32 + // mint - 4 + // name string length - MAX_NAME_LENGTH + // name - 4 + // uri string length - MAX_URI_LENGTH + // uri - 4 + // symbol string length - MAX_SYMBOL_LENGTH + // symbol - 2 + // seller fee basis points - 1 + // whether or not there is a creators vec - 4 + // creators vec length - i * MAX_CREATOR_LEN, - bytes: whitelistedCreators[j].info.address.toBase58(), + if (whitelistedCreators.length > 3) { + console.log(' too many creators, pulling all nfts in one go'); + additionalPromises.push( + connection + .getProgramAccounts(METADATA_PROGRAM_ID) + .then(forEach(processMetaData)), + ); + } else { + console.log('pulling optimized nfts'); + + for (let i = 0; i < MAX_CREATOR_LIMIT; i++) { + for (let j = 0; j < whitelistedCreators.length; j++) { + additionalPromises.push( + connection + .getProgramAccounts(METADATA_PROGRAM_ID, { + filters: [ + { + memcmp: { + offset: + 1 + // key + 32 + // update auth + 32 + // mint + 4 + // name string length + MAX_NAME_LENGTH + // name + 4 + // uri string length + MAX_URI_LENGTH + // uri + 4 + // symbol string length + MAX_SYMBOL_LENGTH + // symbol + 2 + // seller fee basis points + 1 + // whether or not there is a creators vec + 4 + // creators vec length + i * MAX_CREATOR_LEN, + bytes: whitelistedCreators[j].info.address.toBase58(), + }, }, - }, - ], - }) - .then(forEach(processMetaData)), - ); + ], + }) + .then(forEach(processMetaData)), + ); + } } } }), @@ -122,6 +137,63 @@ export const loadAccounts = async (connection: Connection, all: boolean) => { await postProcessMetadata(tempCache, all); console.log('Metadata size', tempCache.metadata.length); + if (additionalPromises.length > 0) { + console.log('Pulling editions for optimized metadata'); + let setOf100MetadataEditionKeys: string[] = []; + const editionPromises = []; + + for (let i = 0; i < tempCache.metadata.length; i++) { + let edition: PublicKey; + if (tempCache.metadata[i].info.editionNonce != null) { + edition = await PublicKey.createProgramAddress( + [ + Buffer.from(METADATA_PREFIX), + METADATA_PROGRAM_ID.toBuffer(), + tempCache.metadata[i].info.mint.toBuffer(), + new Uint8Array([tempCache.metadata[i].info.editionNonce || 0]), + ], + METADATA_PROGRAM_ID, + ); + } else { + edition = await getEdition(tempCache.metadata[i].info.mint); + } + + setOf100MetadataEditionKeys.push(edition.toBase58()); + + if (setOf100MetadataEditionKeys.length >= 100) { + console.log('push'); + editionPromises.push( + getMultipleAccounts( + connection, + setOf100MetadataEditionKeys, + 'recent', + ), + ); + setOf100MetadataEditionKeys = []; + } + } + + const responses = await Promise.all(editionPromises); + for (let i = 0; i < responses.length; i++) { + const returnedAccounts = responses[i]; + for (let j = 0; j < returnedAccounts.array.length; j++) { + processMetaData( + { + pubkey: new PublicKey(returnedAccounts.keys[j]), + account: returnedAccounts.array[j], + }, + updateTemp, + all, + ); + } + } + console.log( + 'Edition size', + Object.keys(tempCache.editions).length, + Object.keys(tempCache.masterEditions).length, + ); + } + return tempCache; }; diff --git a/js/packages/web/src/contexts/meta/processMetaData.ts b/js/packages/web/src/contexts/meta/processMetaData.ts index 8f07480..c36f3b2 100644 --- a/js/packages/web/src/contexts/meta/processMetaData.ts +++ b/js/packages/web/src/contexts/meta/processMetaData.ts @@ -84,8 +84,12 @@ export const processMetaData: ProcessAccountsFunc = ( } }; -const isMetadataAccount = (account: AccountInfo) => - account.owner.equals(METADATA_PROGRAM_ID); +const isMetadataAccount = (account: AccountInfo) => { + return account.owner.equals + ? account.owner.equals(METADATA_PROGRAM_ID) + : //@ts-ignore + account.owner === METADATA_PROGRAM_ID.toBase58(); +}; const isMetadataV1Account = (account: AccountInfo) => account.data[0] === MetadataKey.MetadataV1; diff --git a/rust/token-metadata/program/src/processor.rs b/rust/token-metadata/program/src/processor.rs index f977683..1f798ad 100644 --- a/rust/token-metadata/program/src/processor.rs +++ b/rust/token-metadata/program/src/processor.rs @@ -592,6 +592,16 @@ pub fn process_puff_metadata_account( assert_owned_by(metadata_account_info, program_id)?; puff_out_data_fields(&mut metadata); + + let edition_seeds = &[ + PREFIX.as_bytes(), + program_id.as_ref(), + metadata.mint.as_ref(), + EDITION.as_bytes() + ]; + let (_, edition_bump_seed) = + Pubkey::find_program_address(edition_seeds, program_id); + metadata.edition_nonce = Some(edition_bump_seed); metadata.serialize(&mut *metadata_account_info.data.borrow_mut())?; Ok(()) diff --git a/rust/token-metadata/program/src/state.rs b/rust/token-metadata/program/src/state.rs index 8a13fdf..656a5b0 100644 --- a/rust/token-metadata/program/src/state.rs +++ b/rust/token-metadata/program/src/state.rs @@ -23,14 +23,20 @@ pub const MAX_URI_LENGTH: usize = 200; pub const MAX_METADATA_LEN: usize = 1 + 32 + 32 + + 4 + MAX_NAME_LENGTH + + 4 + MAX_SYMBOL_LENGTH + + 4 + MAX_URI_LENGTH - + MAX_CREATOR_LIMIT * MAX_CREATOR_LEN + 2 + 1 + + 4 + + MAX_CREATOR_LIMIT * MAX_CREATOR_LEN + 1 - + 198; + + 1 + + 9 + + 172; pub const MAX_EDITION_LEN: usize = 1 + 32 + 8 + 200; @@ -93,6 +99,8 @@ pub struct Metadata { pub primary_sale_happened: bool, // Whether or not the data struct is mutable, default is not pub is_mutable: bool, + /// nonce for easy calculation of editions, if present + pub edition_nonce: Option } impl Metadata { diff --git a/rust/token-metadata/program/src/utils.rs b/rust/token-metadata/program/src/utils.rs index f3dde9e..a764e58 100644 --- a/rust/token-metadata/program/src/utils.rs +++ b/rust/token-metadata/program/src/utils.rs @@ -851,6 +851,17 @@ pub fn process_create_metadata_accounts_logic( metadata.update_authority = *update_authority_info.key; puff_out_data_fields(&mut metadata); + + let edition_seeds = &[ + PREFIX.as_bytes(), + program_id.as_ref(), + metadata.mint.as_ref(), + EDITION.as_bytes() + ]; + let (_, edition_bump_seed) = + Pubkey::find_program_address(edition_seeds, program_id); + metadata.edition_nonce = Some(edition_bump_seed); + metadata.serialize(&mut *metadata_account_info.data.borrow_mut())?; Ok(()) diff --git a/rust/token-metadata/test/src/main.rs b/rust/token-metadata/test/src/main.rs index f69e29d..6cfc402 100644 --- a/rust/token-metadata/test/src/main.rs +++ b/rust/token-metadata/test/src/main.rs @@ -40,7 +40,7 @@ fn puff_unpuffed_metadata(_app_matches: &ArgMatches, payer: Keypair, client: Rpc for acct in metadata_accounts { if acct.1.data[0] == Key::MetadataV1 as u8 { let account: Metadata = try_from_slice_unchecked(&acct.1.data).unwrap(); - if account.data.name.len() < MAX_NAME_LENGTH || account.data.uri.len() < MAX_URI_LENGTH || account.data.symbol.len() < MAX_SYMBOL_LENGTH { + if account.data.name.len() < MAX_NAME_LENGTH || account.data.uri.len() < MAX_URI_LENGTH || account.data.symbol.len() < MAX_SYMBOL_LENGTH || account.edition_nonce.is_none() { needing_puffing.push(acct.0); } }