From b18bbb094bdd56022f61e0891c6bf661accf2823 Mon Sep 17 00:00:00 2001 From: Fernando Otero Date: Sat, 18 Nov 2023 14:29:18 +0000 Subject: [PATCH] Add client helper for ProgrammableNonFungible master editions (#64) * Fix shank annotation on account * Add helper * Regenerate clients * Add key check * Change error type * Improve data length test * Use specific length error * Code style --- .../setAndVerifySizedCollectionItem.ts | 2 +- .../set_and_verify_sized_collection_item.rs | 4 +- clients/rust/src/utils.rs | 39 +++++++++++++++++++ idls/token_metadata.json | 2 +- .../program/src/instruction/mod.rs | 2 +- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/clients/js/src/generated/instructions/setAndVerifySizedCollectionItem.ts b/clients/js/src/generated/instructions/setAndVerifySizedCollectionItem.ts index 7486a1d2..e29d0430 100644 --- a/clients/js/src/generated/instructions/setAndVerifySizedCollectionItem.ts +++ b/clients/js/src/generated/instructions/setAndVerifySizedCollectionItem.ts @@ -106,7 +106,7 @@ export function setAndVerifySizedCollectionItem( collection: { index: 5, isWritable: true, value: input.collection ?? null }, collectionMasterEditionAccount: { index: 6, - isWritable: true, + isWritable: false, value: input.collectionMasterEditionAccount ?? null, }, collectionAuthorityRecord: { diff --git a/clients/rust/src/generated/instructions/set_and_verify_sized_collection_item.rs b/clients/rust/src/generated/instructions/set_and_verify_sized_collection_item.rs index 30fbac6e..59d7857b 100644 --- a/clients/rust/src/generated/instructions/set_and_verify_sized_collection_item.rs +++ b/clients/rust/src/generated/instructions/set_and_verify_sized_collection_item.rs @@ -61,7 +61,7 @@ impl SetAndVerifySizedCollectionItem { self.collection, false, )); - accounts.push(solana_program::instruction::AccountMeta::new( + accounts.push(solana_program::instruction::AccountMeta::new_readonly( self.collection_master_edition_account, false, )); @@ -333,7 +333,7 @@ impl<'a, 'b> SetAndVerifySizedCollectionItemCpi<'a, 'b> { *self.collection.key, false, )); - accounts.push(solana_program::instruction::AccountMeta::new( + accounts.push(solana_program::instruction::AccountMeta::new_readonly( *self.collection_master_edition_account.key, false, )); diff --git a/clients/rust/src/utils.rs b/clients/rust/src/utils.rs index 27ef0a5b..7d727ed2 100644 --- a/clients/rust/src/utils.rs +++ b/clients/rust/src/utils.rs @@ -1,4 +1,43 @@ +use borsh::BorshDeserialize; + +use crate::{ + errors::MplTokenMetadataError, + types::{Key, TokenStandard}, +}; + +/// The offset of the token standard byte in the master edition +/// from the end of the account data. +const TOKEN_STANDARD_OFFSET: usize = 1; + /// Removes all null bytes from a string. pub fn clean(value: String) -> String { value.replace('\0', "") } + +/// Checks that the `master_edition` is Programmable NFT master edition. +pub fn assert_edition_is_programmable(edition_data: &[u8]) -> Result<(), MplTokenMetadataError> { + if edition_data.len() > TOKEN_STANDARD_OFFSET { + // the first byte is the account key + let key = Key::deserialize(&mut &edition_data[0..1]) + .map_err(|_error| MplTokenMetadataError::InvalidEditionKey)?; + + return match key { + Key::MasterEditionV1 | Key::MasterEditionV2 => { + // the last byte is the token standard + let standard = TokenStandard::deserialize( + &mut &edition_data[edition_data.len() - TOKEN_STANDARD_OFFSET..], + ) + .map_err(|_error| MplTokenMetadataError::InvalidTokenStandard)?; + + return match standard { + TokenStandard::ProgrammableNonFungible + | TokenStandard::ProgrammableNonFungibleEdition => Ok(()), + _ => Err(MplTokenMetadataError::InvalidTokenStandard), + }; + } + _ => Err(MplTokenMetadataError::InvalidEditionKey), + }; + } + + Err(MplTokenMetadataError::InvalidMasterEditionAccountLength) +} diff --git a/idls/token_metadata.json b/idls/token_metadata.json index 00dff659..8ce75658 100644 --- a/idls/token_metadata.json +++ b/idls/token_metadata.json @@ -2288,7 +2288,7 @@ }, { "name": "collectionMasterEditionAccount", - "isMut": true, + "isMut": false, "isSigner": false, "docs": [ "MasterEdition2 Account of the Collection Token" diff --git a/programs/token-metadata/program/src/instruction/mod.rs b/programs/token-metadata/program/src/instruction/mod.rs index 63f65a0d..413f8ba2 100644 --- a/programs/token-metadata/program/src/instruction/mod.rs +++ b/programs/token-metadata/program/src/instruction/mod.rs @@ -431,7 +431,7 @@ pub enum MetadataInstruction { #[account(3, name="update_authority", desc="Update Authority of Collection NFT and NFT")] #[account(4, name="collection_mint", desc="Mint of the Collection")] #[account(5, writable, name="collection", desc="Metadata Account of the Collection")] - #[account(6, writable, name="collection_master_edition_account", desc="MasterEdition2 Account of the Collection Token")] + #[account(6, name="collection_master_edition_account", desc="MasterEdition2 Account of the Collection Token")] #[account(7, optional, name="collection_authority_record", desc="Collection Authority Record PDA")] #[legacy_optional_accounts_strategy] SetAndVerifySizedCollectionItem,