Metaplex Token Metadata

repository·main·Indexed 42 days ago

https://github.com/metaplex-foundation/mpl-token-metadata

A Solana program and SDKs (JavaScript and Rust) used to attach additional data such as names, symbols, and URIs to Fungible or Non-Fungible tokens using Program Derived Addresses (PDAs). Includes a TypeScript client built with @solana/kit and a Rust SDK for off-chain instruction building and on-chain Cross-Program Invocations (CPI).

Tokens
35.7K
Snippets
95
Records
123
Agent score
54%

What's inside Metaplex Token Metadata

  1. Understand the structure of the Rust SDK

    main

    The SDK is organized into several modules to provide a clean interface for interacting with the Token Metadata program:

    • accounts: Contains structs representing the various program accounts (e.g., Metadata).
    • errors: Contains enums representing the program's error codes.
    • instructions: Contains structs to facilitate the creation of instructions, instruction arguments, and Cross-Program Invocation (CPI) instructions.
    • types: Contains structs representing the data types used by the program.
  2. Find or create PDA addresses using account helpers

    main

    Account types (such as Metadata) provide helper methods to derive Program Derived Addresses (PDAs).

    • find_pda(mint: Pubkey): Returns the PDA and the bump seed. Use this when you do not know the bump.
    • create_pda(mint: Pubkey, bump: u8): Returns the PDA directly using a known bump.

    Performance Tip: If you already know the bump seed, use create_pda instead of find_pda. It is more compute-unit efficient, which is particularly important for on-chain code.

    Example:

    // Finding a PDA
    let (metadata_pda, bump) = Metadata::find_pda(mint_pubkey);
    
    // Creating a PDA with a known bump (more efficient)
    let metadata_pda = Metadata::create_pda(mint_pubkey, bump)?;
    impl Metadata {
        pub fn find_pda(mint: Pubkey) -> (solana_program::pubkey::Pubkey, u8) {
            solana_program::pubkey::Pubkey::find_program_address(
                &[
                    "metadata".as_bytes(),
                    crate::MPL_TOKEN_METADATA_ID.as_ref(),
                    mint.as_ref(),
                ],
                &crate::MPL_TOKEN_METADATA_ID,
            )
        }
        pub fn create_pda(
            mint: Pubkey,
            bump: u8,
        ) -> Result<solana_program::pubkey::Pubkey, solana_program::pubkey::PubkeyError> {
            solana_program::pubkey::Pubkey::create_program_address(
                &[
                    "metadata".as_bytes(),
                    crate::MPL_TOKEN_METADATA_ID.as_ref(),
                    mint.as_ref(),
                    &[bump],
                ],
                &crate::MPL_TOKEN_METADATA_ID,
            )
        }
    }
  3. Manage Rule Set Revisions for Delegates

    main

    To prevent pNFTs from becoming "stuck" in contracts when authorization rules change, Token Metadata implements a rule set revision locking mechanism:

    1. Revision Locking: When a delegate is set on a pNFT, the current rule_set_revision is "locked" for that delegate.
    2. Validation: The delegated program's actions are validated against this locked revision, ensuring that subsequent edits to the global rule set do not unexpectedly break the delegate's ability to interact with the asset.
    3. Unlocking: The revision lock is automatically released when the pNFT is transferred, as the transfer clears the delegate information. Subsequent interactions will then use the latest rule set revision.
  4. Understand Token Metadata Escrow

    main
    Token Metadata Escrow is an extension of the Token Metadata contract that allows NFTs to act as owners of other tokens. It functions by attaching an escrow account to an NFT, which acts as a 'wallet' for the NFT to hold tokens. This feature is designed to be generic, allowing developers to build composable logic on top of the relationship between an NFT and its escrow account.
  5. Understand Token Delegates in Programmable NFTs

    main

    Token Metadata uses a delegation system where specific actors are granted powers to perform actions on assets or collections. There are two primary types of delegates:

    1. Token Delegates

    These operate at the token level (similar to spl-token delegates) and allow operations like burning, transferring, or freezing.

    • Storage: Information is stored on a TokenRecord account (a PDA with seeds ["metadata", program_id, mint_id, "token_record", token_account_id]).
    • Constraint: There can only be one token delegate at a time. To set a new one, the current delegate must be revoked.
    • Roles: Common roles include Sale, Transfer, LockedTransfer, Utility, Staking, and Migration.
    • Special Roles:
      • Migration: A temporary role created during the transition from NFT to pNFT. It can transfer and perform utility actions, but once used, it is cleared and cannot be replaced.
      • LockedTransfer: Can lock/unlock a pNFT and transfer it to a specific locked_address defined at creation.

    2. Metadata Delegates

    These operate at the metadata level and are represented by a MetadataDelegateRecord PDA (seeds ["metadata", program_id, mint_id, delegate_role, update_authority_id, delegate_id]). Unlike token delegates, you can have multiple metadata delegates.

    • Collection: Can set and verify NFTs within a collection.
    • Update: Can perform updates on the metadata account.
    • Use: Allows an actor to "use" the asset and decrement an on-chain usage counter.
    // TokenRecord structure for Token Delegates
    pub struct TokenRecord {
        pub key: Key,
        pub bump: u8,
        pub state: TokenState,
        pub rule_set_revision: Option<u64>,
        pub delegate: Option<Pubkey>,
        pub delegate_role: Option<TokenDelegateRole>,
        pub locked_transfer: Option<Pubkey>,
    }
    
    // MetadataDelegateRecord structure for Metadata Delegates
    pub struct MetadataDelegateRecord {
        pub key: Key,
        pub bump: u8,
        pub mint: Pubkey,
        pub delegate: Pubkey,
        pub update_authority: Pubkey,
    }
  6. Compare Token Owned Escrow (TOE) and Creator Owned Escrow (COE)

    main

    There are two primary types of escrow accounts available in the Token Metadata Escrow feature, distinguished by who manages the tokens within them:

    Token Owned Escrow (TOE)

    • Management: Managed by the current holder of the NFT.
    • Permissions: Permissions follow the NFT. If Alice adds a token to a TOE on her NFT and then sells the NFT to Bob, Bob becomes the only person allowed to transfer tokens out of that TOE.
    • Use Case: General-purpose token holding where the NFT owner controls the assets.

    Creator Owned Escrow (COE)

    • Management: Managed by a specified creator, regardless of who currently holds the NFT.
    • Permissions: The creator maintains control over the escrow account even if the base NFT is sold or transferred to a new holder.
    • Use Case: Maintaining associations that should not be broken by NFT transfers, such as Metaverse avatars where a team manages the avatar assets attached to a user's NFT.
  7. Handle positional optional accounts using the Program ID

    main

    The new instruction handlers support positional optional accounts. In a transaction, accounts must follow a pre-defined relative position to ensure the program logic remains consistent, even if an account is not present (e.g., a semi-fungible token that lacks a masterEdition account).

    To omit an optional account while maintaining the correct position for subsequent accounts, set the account's PublicKey to the Token Metadata Program ID.

    Why this approach is used:

    1. Efficiency: Since the Program ID is already in the transaction, adding it again only consumes a single byte.
    2. Consistency: It maintains the relative index of all following accounts.
    3. Detection: The program can easily distinguish between a valid user-provided PublicKey and the PROGRAM_ID placeholder.

    Example of account ordering: 0. token

    1. tokenOwner
    2. metadata
    3. masterEdition (can be set to Program ID to omit)
    4. mint
    5. payer
  8. Unified instructions for all asset classes

    main

    Token Metadata has expanded its instruction set to support a unified interface for all asset classes, including the new ProgrammableNonFungible (pNFT) assets. While existing instructions continue to work using legacy token standards, the new instructions are required for interacting with pNFTs and are designed to support all asset types (pNFTs, NFTs, Fungible Assets, and Fungibles) through a single interface. Each instruction uses versioned *Args structs (e.g., CreateArgs, MintArgs) to allow for future updates without requiring new instruction types.

    Supported instructions include:

    • Burn: Burn pNFTs, NFTs, editions, or fungibles.
    • Create: Create pNFTs, NFTs, semi-fungible tokens, or fungible tokens.
    • Delegate: Create various delegate types (Collection, Sale, Transfer, Update, Utility).
    • Lock / Unlock: Lock or unlock pNFTs, NFTs, or fungibles.
    • Migrate: Migrate Non-Fungibles to Programmable Non-Fungibles.
    • Mint: Mint pNFTs, NFTs, semi-fungible tokens, or fungible tokens.
    • Print: Mint editions (in development).
    • Revoke: Revoke various delegate types.
    • Transfer: Wallet-to-wallet, wallet-to-program, program-to-wallet, or program-to-program transfers.
    • Update: Update metadata for all asset types.
    • Unverify: Unverify collection items or creators.
    • Use: Authorize the use of a token.
    • Verify: Verify collection items or creators.
    pub enum MetadataInstruction {
        .., // existing instructions
        Burn(BurnArgs),
        Create(CreateArgs),
        Delegate(DelegateArgs),
        Migrate(MigrateArgs),
        Lock(LockArgs),
        Mint(MintArgs),
        Print(PrintArgs),
        Revoke(RevokeArgs),
        Transfer(TransferArgs),
        Unlock(LockArgs),
        Update(UpdateArgs),
        Use(UseArgs),
        Verify(VerificationArgs),
    }
  9. Run JS Kit Client tests with a local validator

    main

    To execute full transaction tests, you must build the program and run a local validator first. Follow these steps:

    1. From the repository root, build the program.
    2. In a separate terminal, start the local validator.
    3. From the clients/js-kit directory, run the tests.
    # 1. From repository root, build the program first
    pnpm programs:build
    
    # 2. Start the local validator (in a separate terminal)
    pnpm validator
    
    # 3. From clients/js-kit, run tests
    pnpm test