使用 Rust 和 Metaplex 在 Solana 上铸造 NFT

在这一篇文章中,我们将学习如何编写一个自定义合约,并且只需要四个步骤就可以生成NFT

1.jpg

在这一篇文章中,我们将学习如何编写一个自定义合约,并且只需要四个步骤就可以生成NFT。

关于Solana开发的一些提示

在Solana的开发过程中,我们会遇到很多奇怪的自定义错误和bug,因为Solana的开发生态系统不像以太坊的开发生态系统那么大,所以修复它们会非常困难和令人沮丧。但不用担心。当我们陷入困境时,我们只需要在正确的地方寻找解决方案。

项目概述

我们将使用的工具:

  • Solana CLI工具——Solana 官方 CLI工具集
  • Anchor Framework—— 开发Solana项目的高级框架
  • Solana/web3.js——web3.js的Solana版本
  • Solana/ spla -token ——一个使用spl代币的包
  • Mocha——一个JS测试工具

准备工作

在CLI中使用如下命令设置网络为devnet:

solana config set --url devnet

要确认它是否工作,然后输入cmd后检查输出:

Config File: /Users/anoushkkharangate/.config/solana/cli/config.yml RPC URL: https://api.devnet.solana.com WebSocket URL: wss://api.devnet.solana.com/ (computed) Keypair Path: /Users/anoushkkharangate/.config/solana/id.json Commitment: confirmed

接下来,如果还没有设置的话,我们是需要设置文件系统钱包的,并使用命令Solana airdrop 1添加一些devnet sol。

最后,使用anchor CLI 命令行创建一个anchor 项目:

anchor init

确保Anchor.toml也被设置为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"

步骤1. 导入依赖项

在我们的项目中,必须有一个名为programs的文件夹。转到programs/<your-project-name>/Cargo.toml并添加这些依赖项。确保使用0.22.1版本,并且可以使用avm来更改它。

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

不要使用最新版本的anchor ,因为anchor会延迟使用最新版本的Solana程序进行自我更新,所以它可能会导致mpl crate 和 anchor crate 的依赖需求之间的冲突。

然后转到lib.rssrc 中的文件并导入这些:

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};

现在我们可以编写mint函数了!

步骤2. 编写Mint函数结构

首先,让我们为mint函数创建accounts结构。

#[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>,
}

不要担心未检查的帐户,因为我们将把它们传递到Metaplex程序,它将为我们检查它们。

为了在Anchor中使用未检查帐户,我们需要在每个帐户上面添加这个注释:

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

步骤3. Mint函数

让我们创建一个函数,使用我们刚刚创建的结构来生成代币:

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(())
    }

如果想调试我们的程序,最好使用msg!()来记录我们想要检查的任何值。它接受字符串,所以我们必须使用std::string::ToString来转换。我们的日志将显示在终端或.anchor/program-logs/中。

2.jpg

该creator数组需要有铸造 NFT 的人作为其中的一部分,但我们可以将份额设置为 0,所以这并不重要。以下是代码:

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,
            },
        ];

现在还没有实现集合,因为它不在本指南的范围内,但我们可以通过使用以下方法来实现:

mpl_token_metadata::instruction::set_and_verify_collection

<br/>

关于为什么我把这里的最大供给量设为0。在Metaplex中,如果代币是独一无二的,那么我们必须将其最大供应量设置为零,因为要求的总供应-实际供应(1-1)等于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
            ),

<span>一旦我们写了这个函数,运行anchor build && anchor deploy,我们应该会看到部署的程序ID。</span>

<span><br/></span>

3.jpg

将此程序 ID 粘贴到Anchor.tomllib.rs文件中,我们将看到此默认 ID 的任何位置Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS。

步骤4. 调用Mint函数

在做任何事情之前,确保我们已经导入了@solana/web3.js和@solana/spl-token。在tests/<test-file>.ts添加这些导入和常量:

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; q
const 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();

现在让我们制作代币和与其关联的代币账户,如下所示:

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());

注意:mint 和 freeze 权限必须相同,否则就不起作用。 createInitializeMintInstruction( mintKey.publicKey, 0, program.provider.wallet.publicKey,// mint auth program.provider.wallet.publicKey // freeze auth ),

现在,调用mint函数并传递所有的数据和帐户。

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);

现在只需运行anchor 测试,我们应该就能够创建我们的NFT。

账户:

4swrfmnovhckxy3gdgagbxzwpffuvyxwpwsgxqbyvozg1m63nzhxyprm7ktqajsdtphn2ivypr6jqfxelsb6a1nx

Mint key:DehGx61vZPYNaMWm9KYdP91UYXXLu1XKoc2CCu3NZFNb 用户:7ctwnydtnbb3p9eviqszkuekjcknmcaassmc7nbtvkue

元数据地址:7ut8ymzgqzaxvrdro8jlkkpnuccdeqxsfznv1hjzc3bo MasterEdition:Au76v2ZDnWSLj23TCu9NRVEYWrbVUq6DAGNnCuALaN6o

交易签名是kwest87h3dz5gw5cdl1jtirkwcxjknzyvqshatligxz4hqgsda7ew6rrhqbj2tqqfrwzzfvhfbu1cpyyh7whh

✔初始化!(6950毫秒)

1通过(7)

✨9.22秒完成。

如果我们得到任何带有十六进制值(如0x1)的自定义程序错误,将十六进制值转换为纯文本,然后转到metaplex github并使用浏览器搜索数字+第1次出现的单词“error(”。

总结

我希望这篇指南对所有Solana爱好者有用。

Source:https://medium.com/better-programming/how-to-mint-nfts-on-solana-using-rust-and-metaplex-f66bac717cb8

关于

ChinaDeFi - ChinaDeFi.com 是一个研究驱动的DeFi创新组织,同时我们也是区块链开发团队。每天从全球超过500个优质信息源的近900篇内容中,寻找思考更具深度、梳理更为系统的内容,以最快的速度同步到中国市场提供决策辅助材料。

本文首发于:https://mp.weixin.qq.com/s/KbkB79VTTv-NyqUjU-7AKA

点赞 2
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ChinaDeFi 去中心化金融社区
ChinaDeFi 去中心化金融社区
ChinaDeFi.com 是一个研究驱动的DeFi创新组织,同时我们也是区块链开发团队。每天从全球超过500个优质信息源的近900篇内容中,寻找思考更具深度、梳理更为系统的内容,以最快的速度同步到中国市场提供决策辅助材料。