From 3a3884e7075328528401b6d5107c08c3eb463951 Mon Sep 17 00:00:00 2001 From: blockiosaurus <90809591+blockiosaurus@users.noreply.github.com> Date: Mon, 8 Apr 2024 20:35:06 -0400 Subject: [PATCH] Adding fix for locking/unlocking pNFT editions. (#116) * Adding fix for locking/unlocking pNFT editions. * Fixing test name. * Formatting fixes. --- clients/js/src/digitalAsset.ts | 3 +- clients/js/test/lockV1.test.ts | 86 ++++++++++++++- clients/js/test/unlockV1.test.ts | 101 +++++++++++++++++- .../program/src/processor/state/mod.rs | 1 + 4 files changed, 186 insertions(+), 5 deletions(-) diff --git a/clients/js/src/digitalAsset.ts b/clients/js/src/digitalAsset.ts index 8fb82a91..5317609b 100644 --- a/clients/js/src/digitalAsset.ts +++ b/clients/js/src/digitalAsset.ts @@ -235,4 +235,5 @@ export const isNonFungible = (tokenStandard: TokenStandard): boolean => !isFungible(tokenStandard); export const isProgrammable = (tokenStandard: TokenStandard): boolean => - tokenStandard === TokenStandard.ProgrammableNonFungible; + tokenStandard === TokenStandard.ProgrammableNonFungible || + tokenStandard === TokenStandard.ProgrammableNonFungibleEdition; diff --git a/clients/js/test/lockV1.test.ts b/clients/js/test/lockV1.test.ts index 9b7a20f3..92c67092 100644 --- a/clients/js/test/lockV1.test.ts +++ b/clients/js/test/lockV1.test.ts @@ -1,5 +1,8 @@ -import { TokenState as SplTokenState } from '@metaplex-foundation/mpl-toolbox'; -import { generateSigner } from '@metaplex-foundation/umi'; +import { + setComputeUnitLimit, + TokenState as SplTokenState, +} from '@metaplex-foundation/mpl-toolbox'; +import { generateSigner, percentAmount } from '@metaplex-foundation/umi'; import test from 'ava'; import { DigitalAssetWithToken, @@ -9,6 +12,8 @@ import { delegateUtilityV1, fetchDigitalAssetWithAssociatedToken, lockV1, + printSupply, + printV2, } from '../src'; import { FUNGIBLE_TOKEN_STANDARDS, @@ -46,6 +51,83 @@ test('it can lock a ProgrammableNonFungible', async (t) => { >{ tokenRecord: { state: TokenState.Locked } }); }); +test('it can lock a ProgrammableNonFungibleEdition', async (t) => { + // Given a ProgrammableNonFungible with a utility delegate. + const umi = await createUmi(); + const utilityDelegate = generateSigner(umi); + const originalOwner = generateSigner(umi); + const originalMint = await createDigitalAssetWithToken(umi, { + name: 'My NFT', + symbol: 'MNFT', + uri: 'https://example.com/nft.json', + sellerFeeBasisPoints: percentAmount(5.42), + tokenOwner: originalOwner.publicKey, + printSupply: printSupply('Limited', [10]), + tokenStandard: TokenStandard.ProgrammableNonFungible, + }); + + // When we print a new edition of the asset. + const editionMint = generateSigner(umi); + const editionOwner = generateSigner(umi); + await printV2(umi, { + masterTokenAccountOwner: originalOwner, + masterEditionMint: originalMint.publicKey, + editionMint, + editionTokenAccountOwner: editionOwner.publicKey, + editionNumber: 1, + tokenStandard: TokenStandard.ProgrammableNonFungible, + }) + .prepend( + setComputeUnitLimit(umi, { + units: 400000, + }) + ) + .sendAndConfirm(umi); + + const editionAsset = await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ); + + await delegateUtilityV1(umi, { + mint: editionMint.publicKey, + delegate: utilityDelegate.publicKey, + tokenStandard: TokenStandard.ProgrammableNonFungibleEdition, + token: editionAsset.token.publicKey, + tokenRecord: editionAsset.tokenRecord?.publicKey, + authority: editionOwner, + }).sendAndConfirm(umi); + t.like( + await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ), + { tokenRecord: { state: TokenState.Unlocked } } + ); + + // When the utility delegate locks the asset. + await lockV1(umi, { + mint: editionMint.publicKey, + authority: utilityDelegate, + tokenStandard: TokenStandard.ProgrammableNonFungibleEdition, + token: editionAsset.token.publicKey, + tokenRecord: editionAsset.tokenRecord?.publicKey, + payer: editionOwner, + }).sendAndConfirm(umi); + + // Then the token state was successfully updated. + t.like( + await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ), + { tokenRecord: { state: TokenState.Locked } } + ); +}); + test('it can freeze a NonFungible', async (t) => { // Given a NonFungible with a standard delegate. const umi = await createUmi(); diff --git a/clients/js/test/unlockV1.test.ts b/clients/js/test/unlockV1.test.ts index cb578a26..1d0c870d 100644 --- a/clients/js/test/unlockV1.test.ts +++ b/clients/js/test/unlockV1.test.ts @@ -1,5 +1,12 @@ -import { TokenState as SplTokenState } from '@metaplex-foundation/mpl-toolbox'; -import { generateSigner, transactionBuilder } from '@metaplex-foundation/umi'; +import { + setComputeUnitLimit, + TokenState as SplTokenState, +} from '@metaplex-foundation/mpl-toolbox'; +import { + generateSigner, + percentAmount, + transactionBuilder, +} from '@metaplex-foundation/umi'; import test from 'ava'; import { DigitalAssetWithToken, @@ -9,6 +16,8 @@ import { delegateUtilityV1, fetchDigitalAssetWithAssociatedToken, lockV1, + printSupply, + printV2, unlockV1, } from '../src'; import { @@ -53,6 +62,94 @@ test('it can unlock a ProgrammableNonFungible', async (t) => { >{ tokenRecord: { state: TokenState.Unlocked } }); }); +test('it can unlock a ProgrammableNonFungibleEdition', async (t) => { + // Given a ProgrammableNonFungible with a utility delegate. + const umi = await createUmi(); + const utilityDelegate = generateSigner(umi); + const originalOwner = generateSigner(umi); + const originalMint = await createDigitalAssetWithToken(umi, { + name: 'My NFT', + symbol: 'MNFT', + uri: 'https://example.com/nft.json', + sellerFeeBasisPoints: percentAmount(5.42), + tokenOwner: originalOwner.publicKey, + printSupply: printSupply('Limited', [10]), + tokenStandard: TokenStandard.ProgrammableNonFungible, + }); + + // When we print a new edition of the asset. + const editionMint = generateSigner(umi); + const editionOwner = generateSigner(umi); + await printV2(umi, { + masterTokenAccountOwner: originalOwner, + masterEditionMint: originalMint.publicKey, + editionMint, + editionTokenAccountOwner: editionOwner.publicKey, + editionNumber: 1, + tokenStandard: TokenStandard.ProgrammableNonFungible, + }) + .prepend( + setComputeUnitLimit(umi, { + units: 400000, + }) + ) + .sendAndConfirm(umi); + + const editionAsset = await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ); + + await delegateUtilityV1(umi, { + mint: editionMint.publicKey, + delegate: utilityDelegate.publicKey, + tokenStandard: TokenStandard.ProgrammableNonFungibleEdition, + token: editionAsset.token.publicKey, + tokenRecord: editionAsset.tokenRecord?.publicKey, + authority: editionOwner, + }) + .add( + lockV1(umi, { + mint: editionMint.publicKey, + authority: utilityDelegate, + tokenStandard: TokenStandard.ProgrammableNonFungibleEdition, + token: editionAsset.token.publicKey, + tokenRecord: editionAsset.tokenRecord?.publicKey, + payer: editionOwner, + }) + ) + .sendAndConfirm(umi); + t.like( + await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ), + { tokenRecord: { state: TokenState.Locked } } + ); + + // When the utility delegate unlocks the asset. + await unlockV1(umi, { + mint: editionMint.publicKey, + authority: utilityDelegate, + tokenStandard: TokenStandard.ProgrammableNonFungibleEdition, + token: editionAsset.token.publicKey, + tokenRecord: editionAsset.tokenRecord?.publicKey, + payer: editionOwner, + }).sendAndConfirm(umi); + + // Then the token state was successfully updated. + t.like( + await fetchDigitalAssetWithAssociatedToken( + umi, + editionMint.publicKey, + editionOwner.publicKey + ), + { tokenRecord: { state: TokenState.Unlocked } } + ); +}); + test('it can unfreeze a NonFungible', async (t) => { // Given a NonFungible with a standard delegate that has locked the asset. const umi = await createUmi(); diff --git a/programs/token-metadata/program/src/processor/state/mod.rs b/programs/token-metadata/program/src/processor/state/mod.rs index 743e32a3..08c477db 100644 --- a/programs/token-metadata/program/src/processor/state/mod.rs +++ b/programs/token-metadata/program/src/processor/state/mod.rs @@ -91,6 +91,7 @@ pub(crate) fn toggle_asset_state( if matches!( metadata.token_standard, Some(TokenStandard::ProgrammableNonFungible) + | Some(TokenStandard::ProgrammableNonFungibleEdition) ) { let AuthorityResponse { authority_type, .. } = AuthorityType::get_authority_type(AuthorityRequest {