Skip to content

Commit

Permalink
Adding fix for locking/unlocking pNFT editions. (#116)
Browse files Browse the repository at this point in the history
* Adding fix for locking/unlocking pNFT editions.

* Fixing test name.

* Formatting fixes.
  • Loading branch information
blockiosaurus committed Apr 9, 2024
1 parent 87af917 commit 3a3884e
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 5 deletions.
3 changes: 2 additions & 1 deletion clients/js/src/digitalAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
86 changes: 84 additions & 2 deletions clients/js/test/lockV1.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -9,6 +12,8 @@ import {
delegateUtilityV1,
fetchDigitalAssetWithAssociatedToken,
lockV1,
printSupply,
printV2,
} from '../src';
import {
FUNGIBLE_TOKEN_STANDARDS,
Expand Down Expand Up @@ -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
),
<DigitalAssetWithToken>{ 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
),
<DigitalAssetWithToken>{ tokenRecord: { state: TokenState.Locked } }
);
});

test('it can freeze a NonFungible', async (t) => {
// Given a NonFungible with a standard delegate.
const umi = await createUmi();
Expand Down
101 changes: 99 additions & 2 deletions clients/js/test/unlockV1.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -9,6 +16,8 @@ import {
delegateUtilityV1,
fetchDigitalAssetWithAssociatedToken,
lockV1,
printSupply,
printV2,
unlockV1,
} from '../src';
import {
Expand Down Expand Up @@ -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
),
<DigitalAssetWithToken>{ 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
),
<DigitalAssetWithToken>{ 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();
Expand Down
1 change: 1 addition & 0 deletions programs/token-metadata/program/src/processor/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 3a3884e

Please sign in to comment.