Features
Locking Assets
As mentioned in the "Delegate Authorities" page, certain delegates can lock and unlock assets, preventing their owners from transferring or burning them. A locked asset also forbids the owner from revoking the delegate's authority. This locking mechanism enables various utility use cases — such as staking — that would otherwise require an escrow account to function.
In the table below, we list all the Token Delegates that support locking assets. You can learn more about each of these delegates and how to approve/revoke them in their respective sections.
| Delegate | Lock/Unlock | Transfer | Burn | For |
|---|---|---|---|---|
| Standard | ✅ | ✅ | ✅ | All except pNFTs |
| Locked Transfer | ✅ | ✅ | ❌ | pNFTs only |
| Utility | ✅ | ❌ | ✅ | pNFTs only |
| Staking | ✅ | ❌ | ❌ | pNFTs only |
Assuming we have an approved Token Delegate on an asset, let's now see how the delegate can lock and unlock it.
Lock an Asset
NFT
To lock an asset, the delegate may use the Lock instruction of the Token Metadata program. This instruction accepts the following attributes:
- Mint: The address of the asset's Mint account.
- Authority: The signer that authorizes the lock. This must be the delegated authority.
- Token Standard: The standard of the asset being locked. Note that the Token Metadata program does not explicitly require this argument but our SDKs do so they can provide adequate default values for most of the other parameters.
1import { lockV1 } from '@metaplex-foundation/mpl-token-metadata';
2
3// Assuming umi, mint, and authority (delegate) are set up
4
5await lockV1(umi, {
6 mint,
7 authority,
8 tokenStandard: TokenStandard.NonFungible,
9}).sendAndConfirm(umi);
10
11console.log('NFT locked');
1import {
2 getLockV1InstructionAsync,
3 TokenStandard,
4} from '@metaplex-foundation/mpl-token-metadata-kit';
5
6// Assuming rpc, rpcSubscriptions, sendAndConfirm, authority (delegate), and tokenOwner are set up
7
8const mintAddress = 'mintAddress...'; // The NFT mint address
9
10// Lock an NFT (freeze) - requires approved delegate authority
11const lockIx = await getLockV1InstructionAsync({
12 mint: mintAddress,
13 authority,
14 payer: authority,
15 tokenOwner,
16 tokenStandard: TokenStandard.NonFungible,
17});
18
19await sendAndConfirm({
20 instructions: [lockIx],
21 payer: authority,
22});
23
24console.log('NFT locked');
pNFT
1import {
2 fetchDigitalAssetWithAssociatedToken,
3 lockV1,
4 TokenStandard,
5} from '@metaplex-foundation/mpl-token-metadata';
6import { publicKey } from '@metaplex-foundation/umi';
7import { base58 } from '@metaplex-foundation/umi/serializers';
8
9// Assuming umi is set up with mplTokenMetadata plugin
10
11// Mint ID of the pNFT Asset
12const mintId = publicKey('11111111111111111111111111111111');
13
14// Fetch pNFT Asset with Token Accounts
15const assetWithToken = await fetchDigitalAssetWithAssociatedToken(
16 umi,
17 mintId,
18 umi.identity.publicKey
19);
20
21// Send lock instruction
22const { signature } = await lockV1(umi, {
23 // Mint ID of the pNFT Asset
24 mint: mintId,
25 // Update Authority or Delegate Authority
26 authority: umi.identity,
27 // Token Standard
28 tokenStandard: TokenStandard.ProgrammableNonFungible,
29 // Owner of the pNFT Asset
30 tokenOwner: assetWithToken.token.owner,
31 // Token Account of the pNFT Asset
32 token: assetWithToken.token.publicKey,
33 // Token Record of the pNFT Asset
34 tokenRecord: assetWithToken.tokenRecord?.publicKey,
35}).sendAndConfirm(umi);
36
37console.log('Signature: ', base58.deserialize(signature));
1import {
2 getLockV1InstructionAsync,
3 fetchDigitalAssetWithAssociatedToken,
4 TokenStandard,
5} from '@metaplex-foundation/mpl-token-metadata-kit';
6
7// Assuming rpc, rpcSubscriptions, sendAndConfirm, and authority (delegate) are set up
8
9const mintAddress = 'mintAddress...'; // The pNFT mint address
10
11// Fetch pNFT Asset with Token Accounts
12const assetWithToken = await fetchDigitalAssetWithAssociatedToken(
13 rpc,
14 mintAddress,
15 authority.address
16);
17
18// Lock a pNFT - requires approved delegate authority
19const lockIx = await getLockV1InstructionAsync({
20 mint: mintAddress,
21 authority,
22 payer: authority,
23 tokenOwner: assetWithToken.token.owner,
24 token: assetWithToken.token.address,
25 tokenRecord: assetWithToken.tokenRecord?.address,
26 tokenStandard: TokenStandard.ProgrammableNonFungible,
27});
28
29await sendAndConfirm({
30 instructions: [lockIx],
31 payer: authority,
32});
33
34console.log('pNFT locked');
Unlock an Asset
NFT
Reciprocally, the delegate may use the Unlock instruction of the Token Metadata program to unlock an asset. This instruction accepts the same attributes as the Lock instruction and can be used in the same way.
1import { unlockV1 } from '@metaplex-foundation/mpl-token-metadata';
2
3// Assuming umi, mint, and authority (delegate) are set up
4
5await unlockV1(umi, {
6 mint,
7 authority,
8 tokenStandard: TokenStandard.NonFungible,
9}).sendAndConfirm(umi);
10
11console.log('NFT unlocked');
1import {
2 getUnlockV1InstructionAsync,
3 TokenStandard,
4} from '@metaplex-foundation/mpl-token-metadata-kit';
5
6// Assuming rpc, rpcSubscriptions, sendAndConfirm, authority (delegate), and tokenOwner are set up
7
8const mintAddress = 'mintAddress...'; // The NFT mint address
9
10// Unlock an NFT (thaw) - requires approved delegate authority
11const unlockIx = await getUnlockV1InstructionAsync({
12 mint: mintAddress,
13 authority,
14 payer: authority,
15 tokenOwner,
16 tokenStandard: TokenStandard.NonFungible,
17});
18
19await sendAndConfirm({
20 instructions: [unlockIx],
21 payer: authority,
22});
23
24console.log('NFT unlocked');
pNFT
1import {
2 fetchDigitalAssetWithAssociatedToken,
3 TokenStandard,
4 unlockV1,
5} from '@metaplex-foundation/mpl-token-metadata';
6import { publicKey } from '@metaplex-foundation/umi';
7import { base58 } from '@metaplex-foundation/umi/serializers';
8
9// Assuming umi is set up with mplTokenMetadata plugin
10
11// Mint pNFT ID of the Asset
12const mintId = publicKey('11111111111111111111111111111111');
13
14// Fetch the mint token accounts
15const assetWithToken = await fetchDigitalAssetWithAssociatedToken(
16 umi,
17 mintId,
18 umi.identity.publicKey
19);
20
21// Send unlock instruction
22const { signature } = await unlockV1(umi, {
23 // Mint ID of the pNFT Asset
24 mint: mintId,
25 // Update Authority or Delegate Authority
26 authority: umi.identity,
27 // Token Standard
28 tokenStandard: TokenStandard.ProgrammableNonFungible,
29 // Owner of the pNFT Assets
30 tokenOwner: assetWithToken.token.owner,
31 // Token Account of the pNFT Asset
32 token: assetWithToken.token.publicKey,
33 // Token Record of the pNFT Asset
34 tokenRecord: assetWithToken.tokenRecord?.publicKey,
35}).sendAndConfirm(umi);
36
37console.log('Signature: ', base58.deserialize(signature));
1import {
2 getUnlockV1InstructionAsync,
3 fetchDigitalAssetWithAssociatedToken,
4 TokenStandard,
5} from '@metaplex-foundation/mpl-token-metadata-kit';
6
7// Assuming rpc, rpcSubscriptions, sendAndConfirm, and authority (delegate) are set up
8
9const mintAddress = 'mintAddress...'; // The pNFT mint address
10
11// Fetch pNFT Asset with Token Accounts
12const assetWithToken = await fetchDigitalAssetWithAssociatedToken(
13 rpc,
14 mintAddress,
15 authority.address
16);
17
18// Unlock a pNFT - requires approved delegate authority
19const unlockIx = await getUnlockV1InstructionAsync({
20 mint: mintAddress,
21 authority,
22 payer: authority,
23 tokenOwner: assetWithToken.token.owner,
24 token: assetWithToken.token.address,
25 tokenRecord: assetWithToken.tokenRecord?.address,
26 tokenStandard: TokenStandard.ProgrammableNonFungible,
27});
28
29await sendAndConfirm({
30 instructions: [unlockIx],
31 payer: authority,
32});
33
34console.log('pNFT unlocked');
