Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 29, 2022 03:21 pm GMT

How To Mint NFTs on Solana Using Rust and Metaplex

In this how-to, youll learn how to mint an NFT on Solana by writing a Rust smart contract and using Metaplexs Token Metadata Program

img
Welcome readers. This is the start of a new series of blog posts about Solana development, and in this one, youll learn how to write a custom contract to mint your NFT in only four steps.

Some General Tips About Solana Development

In Solana development, youll face a lot of weird custom errors and bugs, and it can be quite hard and frustrating to fix them since the Solana dev ecosystem is not as big as the Eth dev ecosystem. But not to worry. When you get stuck you simply have to look in the right place for the solution.

During my development, I was constantly asking my doubts in the Anchor discord server, the Metaplex and Superteam server, and looking through other code repos on GitHub and the Metaplex Program library itself.

Project Overview

The tools well be using for this:

Solana CLI Tools The official Solana CLI toolset
Anchor Framework A high-level framework for developing Solana programs. This is a must unless youre a god-level dev, in which case you're not reading this blog. Lol.
Solana/web3.js A Solana version of web3.js
Solana/spl-token A package to work with spl tokens
Mocha A JS testing tool

Getting Started

Prep work
Use the CLI to set your network to devnet with the following command:

solana config set --url devnet

To confirm if it worked, check the output after entering the cmd:

Config File:

/Users/anoushkkharangate/.config/solana/cli/config.ymlRPC URL: https://api.devnet.solana.comWebSocket URL: wss://api.devnet.solana.com/ (computed)Keypair Path: /Users/anoushkkharangate/.config/solana/id.jsonCommitment: confirmed

Next, if you havent already, set up a file system wallet using this guide, Solana wallet docs, and also add some devnet sol using the command solana airdrop 1

Lastly, use anchor CLI to make an anchor project with this command:

anchor init <name-of-your-project>

Make sure that Anchor.toml is also set to devnet.

[features]seeds = false[programs.devnet]metaplex_anchor_nft = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"[registry]url = "https://anchor.projectserum.com"[provider]cluster = "devnet"wallet = "/Users/<user-name>/.config/solana/id.json"[scripts]test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

Thats it now. You are ready.

Step 1. Import the Dependencies

In your project, there must be a folder called programs. Go to programs//Cargo.toml, and add these dependencies. Make sure to use version 0.24.2 and you can use avm to change it

[dependencies]anchor-lang = "0.24.2"anchor-spl = "0.24.2"mpl-token-metadata = {version = "1.2.7", features = ["no-entrypoint"]}

Anchor has pulled down all versions before 0.24.2 due to a security vulnerability hence make sure to use this exact one

Then go to the lib.rs file in src and import these:

use anchor_lang::prelude::*;use anchor_lang::solana_program::program::invoke;use anchor_spl::token;use anchor_spl::token::{MintTo, Token};use mpl_token_metadata::instruction::{create_master_edition_v3, create_metadata_accounts_v2};

Cool. Now we can write the mint function!

Step 2. Writing the Mint Function Struct

First, lets create the accounts struct for the mint function

#[derive(Accounts)]pub struct MintNFT<'info> {    #[account(mut)]    pub mint_authority: Signer<'info>,/// CHECK: This is not dangerous because we don't read or write from this account    #[account(mut)]    pub mint: UncheckedAccount<'info>,    // #[account(mut)]    pub token_program: Program<'info, Token>,    /// CHECK: This is not dangerous because we don't read or write from this account    #[account(mut)]    pub metadata: UncheckedAccount<'info>,    /// CHECK: This is not dangerous because we don't read or write from this account    #[account(mut)]    pub token_account: UncheckedAccount<'info>,    /// CHECK: This is not dangerous because we don't read or write from this account    pub token_metadata_program: UncheckedAccount<'info>,    /// CHECK: This is not dangerous because we don't read or write from this account    #[account(mut)]    pub payer: AccountInfo<'info>,    pub system_program: Program<'info, System>,    /// CHECK: This is not dangerous because we don't read or write from this account    pub rent: AccountInfo<'info>,    /// CHECK: This is not dangerous because we don't read or write from this account    #[account(mut)]    pub master_edition: UncheckedAccount<'info>,}

Dont worry about the Unchecked accounts, as we will pass these into the Metaplex program which will check them for us.

In order to use Unchecked accounts in Anchor, you need to add this comment above each account:

/// CHECK: This is not dangerous because we don't read or write from this account

Step 3. The Mint Function

Lets make a function that uses the struct we just made to mint the token:

pub fn mint_nft(        ctx: Context<MintNFT>,        creator_key: Pubkey,        uri: String,        title: String,    ) -> Result<()> {        msg!("Initializing Mint NFT");        let cpi_accounts = MintTo {            mint: ctx.accounts.mint.to_account_info(),            to: ctx.accounts.token_account.to_account_info(),            authority: ctx.accounts.payer.to_account_info(),        }; msg!("CPI Accounts Assigned");        let cpi_program = ctx.accounts.token_program.to_account_info();        msg!("CPI Program Assigned");        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);        msg!("CPI Context Assigned");        token::mint_to(cpi_ctx, 1)?;        msg!("Token Minted !!!");        let account_info = vec![            ctx.accounts.metadata.to_account_info(),            ctx.accounts.mint.to_account_info(),            ctx.accounts.mint_authority.to_account_info(),            ctx.accounts.payer.to_account_info(),            ctx.accounts.token_metadata_program.to_account_info(),            ctx.accounts.token_program.to_account_info(),            ctx.accounts.system_program.to_account_info(),            ctx.accounts.rent.to_account_info(),        ];        msg!("Account Info Assigned");        let creator = vec![            mpl_token_metadata::state::Creator {                address: creator_key,                verified: false,                share: 100,            },            mpl_token_metadata::state::Creator {                address: ctx.accounts.mint_authority.key(),                verified: false,                share: 0,            },        ];        msg!("Creator Assigned");        let symbol = std::string::ToString::to_string("symb");        invoke(            &create_metadata_accounts_v2(                ctx.accounts.token_metadata_program.key(),                ctx.accounts.metadata.key(),                ctx.accounts.mint.key(),                ctx.accounts.mint_authority.key(),                ctx.accounts.payer.key(),                ctx.accounts.payer.key(),                title,                symbol,                uri,                Some(creator),                1,                true,                false,                None,                None,            ),            account_info.as_slice(),        )?;        msg!("Metadata Account Created !!!");        let master_edition_infos = vec![            ctx.accounts.master_edition.to_account_info(),            ctx.accounts.mint.to_account_info(),            ctx.accounts.mint_authority.to_account_info(),            ctx.accounts.payer.to_account_info(),            ctx.accounts.metadata.to_account_info(),            ctx.accounts.token_metadata_program.to_account_info(),            ctx.accounts.token_program.to_account_info(),            ctx.accounts.system_program.to_account_info(),            ctx.accounts.rent.to_account_info(),        ];        msg!("Master Edition Account Infos Assigned");        invoke(            &create_master_edition_v3(                ctx.accounts.token_metadata_program.key(),                ctx.accounts.master_edition.key(),                ctx.accounts.mint.key(),                ctx.accounts.payer.key(),                ctx.accounts.mint_authority.key(),                ctx.accounts.metadata.key(),                ctx.accounts.payer.key(),                Some(0),            ),            master_edition_infos.as_slice(),        )?;        msg!("Master Edition Nft Minted !!!");        Ok(())    }

If you want debug your program, better use msg!() to log whatever value you want to check. It accepts string so youll have to use std::string::ToString to convert. Your logs will appear in the terminal or in .anchor/program-logs/

Image description

So, a Few Things Here
The creator array needs to have the person minting the NFTs as part of it, but you can set the share as 0, so it doesnt really matter. Heres the code:

let creator = vec![            mpl_token_metadata::state::Creator {                address: creator_key,                verified: false,                share: 100,            },            mpl_token_metadata::state::Creator {                address: ctx.accounts.mint_authority.key(),                verified: false,                share: 0,            },        ];

I havent implemented Collections, as its not in the scope of this guide but you can do it by using:

mpl_token_metadata::instruction::set_and_verify_collection

Regarding why Ive set Max supply to 0 here. In Metaplex, if the token is meant to be one of a kind, then you have to set its max supply to zero since total supply supply claimed (11) equals 0

&create_master_edition_v3(                ctx.accounts.token_metadata_program.key(),                ctx.accounts.master_edition.key(),                ctx.accounts.mint.key(),                ctx.accounts.payer.key(),                ctx.accounts.mint_authority.key(),                ctx.accounts.metadata.key(),                ctx.accounts.payer.key(),                Some(0), // max supply 0            ),

Once youve written the function run anchor build && anchor deploy and you should see the deployed Program ID

paste this program ID in your Anchor.toml and the lib.rs file wherever you see this default ID Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS

Step 4. Calling the Mint Function

Before doing anything, make sure that you have imported @solana/web3.js and @solana/spl-token. Inside tests/.ts add these imports and constants:

import {  TOKEN_PROGRAM_ID,  createAssociatedTokenAccountInstruction,  getAssociatedTokenAddress,  createInitializeMintInstruction,  MINT_SIZE,} from "@solana/spl-token";import { LAMPORTS_PER_SOL } from "@solana/web3.js";const { PublicKey, SystemProgram } = anchor.web3; qconst TOKEN_METADATA_PROGRAM_ID = new anchor.web3.PublicKey(      "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"    );    const lamports: number =      await program.provider.connection.getMinimumBalanceForRentExemption(        MINT_SIZE      );    const getMetadata = async (      mint: anchor.web3.PublicKey    ): Promise<anchor.web3.PublicKey> => {      return (        await anchor.web3.PublicKey.findProgramAddress(          [            Buffer.from("metadata"),            TOKEN_METADATA_PROGRAM_ID.toBuffer(),            mint.toBuffer(),          ],          TOKEN_METADATA_PROGRAM_ID        )      )[0];    };    const getMasterEdition = async (      mint: anchor.web3.PublicKey    ): Promise<anchor.web3.PublicKey> => {      return (        await anchor.web3.PublicKey.findProgramAddress(          [            Buffer.from("metadata"),            TOKEN_METADATA_PROGRAM_ID.toBuffer(),            mint.toBuffer(),            Buffer.from("edition"),          ],          TOKEN_METADATA_PROGRAM_ID        )      )[0];    };    const mintKey: anchor.web3.Keypair = anchor.web3.Keypair.generate();

Now let's make the token and the associated token account, as shown below:

const NftTokenAccount = await getAssociatedTokenAddress(      mintKey.publicKey,      program.provider.wallet.publicKey    );    console.log("NFT Account: ", NftTokenAccount.toBase58());    const mint_tx = new anchor.web3.Transaction().add(      anchor.web3.SystemProgram.createAccount({        fromPubkey: program.provider.wallet.publicKey,        newAccountPubkey: mintKey.publicKey,        space: MINT_SIZE,        programId: TOKEN_PROGRAM_ID,        lamports,      }),      createInitializeMintInstruction(        mintKey.publicKey,        0,        program.provider.wallet.publicKey,        program.provider.wallet.publicKey      ),      createAssociatedTokenAccountInstruction(        program.provider.wallet.publicKey,        NftTokenAccount,        program.provider.wallet.publicKey,        mintKey.publicKey      )    );    const res = await program.provider.send(mint_tx, [mintKey]);    console.log(      await program.provider.connection.getParsedAccountInfo(mintKey.publicKey)    );    console.log("Account: ", res);    console.log("Mint key: ", mintKey.publicKey.toString());    console.log("User: ", program.provider.wallet.publicKey.toString());    const metadataAddress = await getMetadata(mintKey.publicKey);    const masterEdition = await getMasterEdition(mintKey.publicKey);    console.log("Metadata address: ", metadataAddress.toBase58());    console.log("MasterEdition: ", masterEdition.toBase58());

Note: the mint and freeze authority has to be same otherwise it doesnt work.

createInitializeMintInstruction( mintKey.publicKey, 0, program.provider.wallet.publicKey,// mint auth program.provider.wallet.publicKey // freeze auth
),

Now, call the mint function and pass all the data and accounts

const tx = await program.rpc.mintNft(      mintKey.publicKey,      "https://arweave.net/y5e5DJsiwH0s_ayfMwYk-SnrZtVZzHLQDSTZ5dNRUHA",      "NFT Title",      {        accounts: {          mintAuthority: program.provider.wallet.publicKey,          mint: mintKey.publicKey,          tokenAccount: NftTokenAccount,          tokenProgram: TOKEN_PROGRAM_ID,          metadata: metadataAddress,          tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,          payer: program.provider.wallet.publicKey,          systemProgram: SystemProgram.programId,          rent: anchor.web3.SYSVAR_RENT_PUBKEY,          masterEdition: masterEdition,        },      }    );    console.log("Your transaction signature", tx);

Thats it! Now just run the anchor test, and you should be able to mint your NFT.

Account:  4swRFMNovHCkXY3gDgAGBXZwpfFuVyxWpWsgXqbYvoZG1M63nZHxyPRm7KTqAjSdTpHn2ivyPr6jQfxeLsB6a1nXMint key:  DehGx61vZPYNaMWm9KYdP91UYXXLu1XKoc2CCu3NZFNbUser:  7CtWnYdTNBb3P9eViqSZKUekjcKnMcaasSMC7NbTVKuEMetadata address:  7ut8YMzGqZAXvRDro8jLKkPnUccdeQxsfzNv1hjzc3BoMasterEdition:  Au76v2ZDnWSLj23TCu9NRVEYWrbVUq6DAGNnCuALaN6oYour transaction signature KwEst87H3dZ5GwQ5CDL1JtiRKwcXJKNzyvQShaTLiGxz4HQGsDA7EW6rrhqwbJ2TqQFRWzZFvhfBU1CpyYH7WhH     Is initialized! (6950ms)  1 passing (7s)  Done in 9.22s.

If you get any custom program error with a hex value like 0x1, convert the hex value to plain text, then go to metaplex github and search using your browser for the number + 1 th appearance of the word error(

Wrapping Up

I hope this guide was useful for all the Solana geeks out there. When I first tried to mint an NFT, I was pulling my hair out, but it slowly started to make sense once some fellow glass eaters explained it to me. Hopefully, Ive made it that much easier for you.

You can follow me on Telegram and Github. Until next time, keep eating glass!


Original Link: https://dev.to/smile0307/how-to-mint-nfts-on-solana-using-rust-and-metaplex-1eph

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To