As we have seen on how Metablocks protocol works in the previous `Architecture` chapter. And After creating a `Universe`, the next step is to deposit an NFT to the universe.
You can think of every deposited NFT as a component of your main character. All the deposited NFTs together constitute the `meta NFT`. The `meta NFT` combines the metadata of all component NFTs.
(For more details - please go through this - @kyraa/metablocks )
Below section demonstrates the deposition of component-NFT in depth.
Depositing an NFT into Metablocks can be classified into a 3 step process.
1.1 Creation of MetaBlocks Authority
The Metablocks Authority
is PDA authority owned by the Meta-Blocks program. This PDA is useful for performaing various actions on metaNFT
(more about this in the following section) of the user.
What does it do anyway ? -
It acts as an escrow authority where Meta-NFT could be burnt, frozen or transferred based on the NFTs deposited by the User
The PDA is generated by the following code.
const MetaBlocksAuthorityPDA = async (
universeKey: PublicKey,
depositorKey: PublicKey
) => {
return await PublicKey.findProgramAddress(
[
Buffer.from(utils.bytes.utf8.encode('MetaBlocksAuthority')),
universeKey.toBuffer(),
depositorKey.toBuffer(),
],
programIds.META_BLOCKS_PROGRAM_ID
);
};
As we can see that PDA is generated based on universe
and depositor
public addresses.
1.2 Initialization of Meta-NFT
Meta-NFT mint is generated using a Meta-NFT
program. It serves the purpose of generating Meta-NFT
and keeping track of NFTs deposited by the user.
The mint is again is a program owned PDA. This could be generated using like -
const MetaNftMintAddressPDA = async (
universeKey: PublicKey,
depositorKey: PublicKey
) => {
return await PublicKey.findProgramAddress(
[
Buffer.from(utils.bytes.utf8.encode('MetaNftMint')),
universeKey.toBuffer(),
depositorKey.toBuffer(),
],
programIds.META_NFT_PROGRAM_ID
);
};
Even the Depsoitor Address
is stored in the program state
. This is done in this way so that when we Create the Meta-NFT
, the one who initializes the Meta-NFT
, will always receives the Meta-NFT
. The state of the program is a PDA generated address as below
const MetaNftAddressPDA = async (
depositorKey: PublicKey,
universeKey: PublicKey
) => {
return await PublicKey.findProgramAddress(
[
Buffer.from(utils.bytes.utf8.encode('MetaNft')),
universeKey.toBuffer(),
depositorKey.toBuffer(),
],
programIds.META_NFT_PROGRAM_ID
);
};
1.3 Creation of Meta-NFT
Once we have the mint
address of Meta-NFT, we can create the Meta-NFT using token-metadata
of Metaplex
. A CPI call is made to token-metadata
to create the standard Meta-NFT. After creation it is transferred to the Associated Address
of the user. The associated address can be generated by
const AssociatedAddressPDA = async (
depositorKey: PublicKey,
mintKey: PublicKey
) => {
const tokenProgramID = programIds.TOKEN_PROGRAM_ID;
return await PublicKey.findProgramAddress(
[
depositorKey.toBuffer(),
tokenProgramID.toBuffer(),
mintKey.toBuffer(),
],
programIds.ASSOCIATED_TOKEN_PROGRAM_ID
);
};
The metadata for generating token-metadata
is passed as arguments while creating Meta-NFT
for the user. The arguments required while generating Meta-NFT is name
of the NFT, uri
.
The receipt-NFT is the proof that user has deposited into the Meta-Blocks program. The receipt NFT mint is generated separately before depositing into the MetaBlocks vault.
2.1 Initialize Receipt Mint address
Like described in the previous section about mint generation for meta-NFT, even receipt mint
is generated is the same way with slight modification in the PDA generation for receipt-mint
.
const ReceiptMintAddressPDA = async (
universeKey: PublicKey,
depositorKey: PublicKey,
nftMintAddress: PublicKey
) => {
return await PublicKey.findProgramAddress(
[
Buffer.from(utils.bytes.utf8.encode('ReceiptNftMint')),
universeKey.toBuffer(),
depositorKey.toBuffer(),
nftMintAddress.toBuffer(),
],
programIds.META_BLOCKS_PROGRAM_ID
);
};
Once the receipt-mint
is created, a state called wrapped-user-nft
stores all the necessary values for depositing and withdrawing the NFTs from the program.
The wrapped-user-nft
is a PDA generated address.
const WrappedUserNftAddressPDA = async (
depositorKey: PublicKey,
receiptMintKey: PublicKey
) => {
return await PublicKey.findProgramAddress(
[
Buffer.from(utils.bytes.utf8.encode('WrappedUserNft')),
depositorKey.toBuffer(),
receiptMintKey.toBuffer(),
],
programIds.META_BLOCKS_PROGRAM_ID
);
};
Later receipt mint NFT is first transfered to vault until user's nft is stored into the vault
. The Vault
address is again a PDA generated address.
Vault address
const VaultAddressPDA = async (
universeKey: PublicKey,
depositorKey: PublicKey,
nftMintAddress: PublicKey
) => {
return await PublicKey.findProgramAddress(
[
Buffer.from(utils.bytes.utf8.encode('VaultMetaBlocks')),
universeKey.toBuffer(),
depositorKey.toBuffer(),
nftMintAddress.toBuffer(),
],
programIds.META_BLOCKS_PROGRAM_ID
);
};
Again, this PDA is used to generate an associated address to store the user's NFT.
Vault ATA
const VaultAssociatedPDA = async (
tokenRecipientKey: PublicKey,
nftMintKey: PublicKey
) => {
const tokenProgramID = programIds.TOKEN_PROGRAM_ID;
return await PublicKey.findProgramAddress(
[
vaultKey.toBuffer(),
tokenProgramID.toBuffer(),
nftMintKey.toBuffer(),
],
programIds.ASSOCIATED_TOKEN_PROGRAM_ID
);
};
Now, that we have everything in place, CPI call is made to token-metadata to generate the receipt-NFT and is minted to Vault associated address
. Then user's NFT is transfered to Vault and vice versa, the Receipt mint is transfered back to the User's Associated address.
This receipt NFT has the same metadata as the deposited NFT, but it cannot be transferred by the user. This receipt can only be used to get the component(deposited) NFT back from the program.