Anchor开发Solana NFT

我们将在 Anchor/Rust 中创建一个 Solana 程序,使用 Solana Playground、QuickNode RPC 和 IPFS 服务直接在链上铸造 NFT,以存储与 NFT 相关的元数据和图像。

Anchor开发Solana NFT
一键发币: SOL | BNB | ETH | BASE | Blast | ARB | OP | POLYGON | AVAX | FTM | OK

欢迎来到这个新教程。 今天,我们将在 Anchor/Rust 中创建一个 Solana 程序,使用 Solana Playground、QuickNode RPC 和 IPFS 服务直接在链上铸造 NFT,以存储与 NFT 相关的元数据和图像。

1、上传NFT图像到IPFS

作为第一步,我们将在去中心化存储服务中准备 NFT 图像和元数据。 我们将使用 QuickNode IPFS(一种高端 IPFS 存储服务)来满足需要快速、高度可用的访问和高级功能的解决方案。

让我们上传将成为我们的 NFT 的图像。 在 QuickNode 仪表板上,转到 IPFS,然后转到文件,然后可以删除图像。

QuickNode IPFS

让我们复制上传文件名称旁边出现的内容标识符 (CID)。 然后,我们将其附加到地址 fastnode.myfilebase.com/ipfs/ 以获得我们将在元数据中使用的 URL。 在浏览器中测试此 URL 以确保一切正确非常重要。

2、为NFT 创建元数据

为此,我们将基于 Metaplex 针对 Solana 上的 NFT 元数据创建的标准。 您可以访问 Metaplex 文档以获取更多信息。

我们的元数据将是一个 JSON 文件,其中包含我们将为 NFT 选择的参数、名称、符号和描述。

{
    "name": "Name",
    "symbol": "symbol",
    "description": "your description",
    "image": "quicknode.myfilebase.com/ipfs/CID",
    "attributes": [
        {
            "trait_type": "type",
            "value": "value"
        }
    ],
    "properties": {
        "creators": [
            {
                "address": "creators's wallet address",
                "share": royalty
            }
        ],
        "files": [
            {
                "type": "image/png",
                "uri": "ipfs://CID"
            }
        ]
    },
    "collection": {
        "name": "name",
        "family": "family"
    }
}

一旦我们的 .json 文件准备好,将返回 QuickNode 上的文件选项卡并像处理图像文件一样拖动它。 现在我们的文件已准备好继续。

3、设置 Solana Playground

我们将前往 Solana Playground 并通过创建新钱包进行连接。

Solana Playground

单击显示 not connected 的位置,然后单击保存密钥对并继续。

保存密钥对

在这里我们可以看到创建的钱包地址:

创建的钱包地址

创建一个新项目,为其命名,然后选择 Anchor (Rust) 并创建:

创建Anchor项目

注意:我们将需要来自 DevNet 的大约 10 个 Solana 来用于我们正在部署的合约。 为了给我们的钱包充值,我们必须输入我们刚刚在 Sol Faucet 中创建的地址钱包,它允许我们每 24 小时充值 3 个 Solana。 这意味着我们需要几天的时间才能完成这次充值。 作为替代方案,我们建议使用其他钱包地址和 VPN 来加快该过程。

现在是时候使用 QuickNode RPC 配置 solpg 以在 Solana DevNet 上工作了。 单击“设置”选项卡,然后单击“端点”选项卡。

设置Solana Playground

选择自定义并粘贴适用于 Solana DevNet 的 QuickNode 私有 RPC。

自定义RPC

完成此操作并返回到新创建的项目后,我们将删除不需要的代码,并保留主导入 from anchor_lang、程序 id 的默认声明以及程序和派生帐户属性的声明。

应该是这样的:

use anchor_lang::prelude::*;

declare_id!("11111111111111111111111111111111");

#[program]


#[derive(Accounts)]

现在,我们将导入所有这些库和必要的元素。 这些库拥有在 Solana 上铸造新 NFT 所需的所有标准功能,在此过程中我们将需要它们中的每一个。

use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::metadata::{
use create_master_edition_v3, create_metadata_accounts_v3, CreateMasterEditionV3,
 CreateMetadataAccountsV3, Metadata,
};
use anchor_spl::token::{mint_to, Mint, MintTo, Token, TokenAccount};
use mpl_token_metadata::types::{Collection, Creator, DataV2};

现在你的代码应该如下所示:

use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::metadata::{
    create_master_edition_v3, create_metadata_accounts_v3, CreateMasterEditionV3,
    CreateMetadataAccountsV3, Metadata,
};
use anchor_spl::token::{mint_to, Mint, MintTo, Token, TokenAccount};
use mpl_token_metadata::types::{Collection, Creator, DataV2};

declare_id!("11111111111111111111111111111111");

#[program]

#[derive(Accounts)]

[derive(Accounts)] 属性下,我们将定义一个结构,其中包含与本程序交互所需的所有帐户。

我们将定义一个名为 CreateNFT 的公共结构。 它将有一个参数 info,表示在使用该结构作为上下文的指令执行期间它将保留在内存中。

我们将在结构中定义的第一个帐户将是 authority。 我们将其定义为具有 info 类型 生命周期的 Signer 类型的公共帐户。 我们在此结构中定义的所有帐户都将是公共类型并具有此生命周期。

#[derive(Accounts)]
pub struct CreateNFT<'info> {
   pub authority: Signer<'info>,

}

我们将为该帐户定义一个属性。 将以下行和表示可变的单词 mut 放在括号内。 这意味着NFT的授权未来可能会发生变化。

#[derive(Accounts)]
pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
}

以同样的方式,我们将定义 payer帐户:

#[derive(Accounts)]
pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
}

现在我们声明 Account 类型的 mint 账户,它将带有 mint 类型的结构。 我们从 anchor_spl 库导入了这个结构。

#[derive(Accounts)]
pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  pub mint: Account<'info, Mint>,
}

我们的 mint账户将携带以下参数:

  • init:因为我们必须初始化它。
  • payer 帐户作为将支付初始化费用的帐户。
  • 内部 mint结构:小数点为 0,因为它是不可替代代币 (NFT)
  • 然后我们将 authority 帐户作为铸币结构的权限传递。
  • freeze_authoroty 参数也是如此。
#[derive(Accounts)]
pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    )]
  pub mint: Account<'info, Mint>,
}

现在我们设置种子来创建派生地址,该地址将首先携带单词 mint(以字节为单位)和 id 参数(以字节为单位)。

#[derive(Accounts)]
pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
  pub mint: Account<'info, Mint>,
}

我们将从合约中创建的指令中获取此 id 参数,该指令将在被调用以铸造 NFT 时接收该参数。 因此,我们必须声明这个属性,它将通过以下方式从这条指令中获取。

#[derive(Accounts)]
#[instruction(id: u64)]

pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
  pub mint: Account<'info, Mint>,
}

现在我们将创建 token_account,它将携带 token 帐户类型的结构,允许我们将铸造帐户与将接收铸造的 NFT 的帐户关联起来。

#[derive(Accounts)]
#[instruction(id: u64)]

pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
  pub mint: Account<'info, Mint>,
  pub token_account: Account<'info, TokenAccount>,
}

它的参数将为 init_if_needed,即如果不存在则创建一个新帐户。 我们将发送付款人作为我们的付款人帐户。 对于 associated_token结构中存在的铸币参数,我们将发送铸币账户和付款人账户作为授权。

#[derive(Accounts)]
#[instruction(id: u64)]

pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
  pub mint: Account<'info, Mint>,
  #[account(
        init_if_needed,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer,
    )]
  pub token_account: Account<'info, TokenAccount>,

}

现在我们声明该过程所需的以下帐户:

  • associated_token_program:关联帐户的Anchor SPL 库程序。
  • rent:Sysvar 类型的程序,有助于确定租金成本。
  • system_program:Solana 的主程序。
  • token_program:允许在 Solana 上创建代币的程序。
  • metadata_program:用于根据 Metaplex 标准将元数据关联到帐户的程序。
#[derive(Accounts)]
#[instruction(id: u64)]

pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
  pub mint: Account<'info, Mint>,
  #[account(
        init_if_needed,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer,
    )]
   pub token_account: Account<'info, TokenAccount>,
   pub associated_token_program: Program<'info, AssociatedToken>,
   pub rent: Sysvar<'info, Rent>,
   pub system_program: Program<'info, System>,
   pub token_program: Program<'info, Token>,
   pub metadata_program: Program<'info, Metadata>,
}

最后,我们声明 UncheckedAccountmaster_edition_accountnft_metadata 类型的帐户。 我们还必须添加带有 ///CHECK 一词的行,以通知编译器我们正在使用未经检查的帐户类型。

#[derive(Accounts)]
#[instruction(id: u64)]

pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
  pub mint: Account<'info, Mint>,
  #[account(
        init_if_needed,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer,
    )]
   pub token_account: Account<'info, TokenAccount>,
   pub associated_token_program: Program<'info, AssociatedToken>,
   pub rent: Sysvar<'info, Rent>,
   pub system_program: Program<'info, System>,
   pub token_program: Program<'info, Token>,
   pub metadata_program: Program<'info, Metadata>,
   /// CHECK:
   pub master_edition_account: UncheckedAccount<'info>,
   /// CHECK:
   pub nft_metadata: UncheckedAccount<'info>,
}

master_edition_account 是必要的,可以通过将 Solana 网络上的代币与主版账户关联来确保其有效地不可替代。 通过它可以创建更多的代币信息实例,所有实例都与这个唯一的帐户相关联,以创建不同类型的 NFT。

我们将为该帐户定义以下参数:

  • Mutable:它的种子将按照文档的要求准确定义如下。
  • Bump:我们必须放置此行,因为此 PDA 帐户将由元数据程序生成。
#[derive(Accounts)]
#[instruction(id: u64)]

pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
  pub mint: Account<'info, Mint>,
  #[account(
        init_if_needed,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer,
    )]
   pub token_account: Account<'info, TokenAccount>,
   pub associated_token_program: Program<'info, AssociatedToken>,
   pub rent: Sysvar<'info, Rent>,
   pub system_program: Program<'info, System>,
   pub token_program: Program<'info, Token>,
   pub metadata_program: Program<'info, Metadata>,
   #[account(
        mut,
        seeds = [
            b"metadata".as_ref(),
            metadata_program.key().as_ref(),
            mint.key().as_ref(),
            b"edition".as_ref(),
        ],
        bump,
        seeds::program = metadata_program.key()
      )]

   /// CHECK:
   pub master_edition_account: UncheckedAccount<'info>,
   /// CHECK:
   pub nft_metadata: UncheckedAccount<'info>,
}

对于 nft_metadata 帐户,我们将设置以下参数,其方式与我们对 master_edition_account 的设置非常相似。

#[derive(Accounts)]
#[instruction(id: u64)]

pub struct CreateNFT<'info> {
  #[account(mut)]
  pub authority: Signer<'info>,
  #[account(mut)]
  pub payer: Signer<'info>,
  #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
  pub mint: Account<'info, Mint>,
  #[account(
        init_if_needed,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer,
    )]
   pub token_account: Account<'info, TokenAccount>,
   pub associated_token_program: Program<'info, AssociatedToken>,
   pub rent: Sysvar<'info, Rent>,
   pub system_program: Program<'info, System>,
   pub token_program: Program<'info, Token>,
   pub metadata_program: Program<'info, Metadata>,
   #[account(
        mut,
        seeds = [
            b"metadata".as_ref(),
            metadata_program.key().as_ref(),
            mint.key().as_ref(),
            b"edition".as_ref(),
        ],
        bump,
        seeds::program = metadata_program.key()
      )]

   /// CHECK:
   pub master_edition_account: UncheckedAccount<'info>,
   #[account(
        mut,
        seeds = [
            b"metadata".as_ref(),
            metadata_program.key().as_ref(),
            mint.key().as_ref(),
        ],
        bump,
        seeds::program = metadata_program.key()
    )]
   /// CHECK:
   pub nft_metadata: UncheckedAccount<'info>,
}

准备好 CreateNFT 结构后,我们将开始创建我们的程序。 在program属性下,我们创建公共模块 nft_program

#[program]
pub mod nft_program {}

我们使用以下指令导入整个父模块:

#[program]
pub mod nft_program {
 use super::*;
}

我们将通过以下方式创建公共函数 create_single_nft

#[program]
pub mod nft_program {
 use super::*;
 pub fn create_single_nft() -> Result<()> {
}

然后我们将指定函数将接收的参数:

  • Context:我们将把我们在创建 NFT 结构中创建的所有帐户带到这里。
  • NFT ID
  • Name
  • Symbol
  • URI
  • Price
  • Quantity
#[program]
pub mod nft_program {
 use super::*;
 pub fn create_single_nft(
 ctx: Context<CreateNFT>,
 id: u64,
 name: String,
 symbol: String,
 uri: String,
 price: f32,
 cant: u64,
) -> Result<()> {
}

我们的函数将按此特定顺序调用以下 3 个函数:

  • 我们从 anchor_spl 导入的 mint_to函数
  • create_metadata_accounts_v3
  • 来自 anchor_spl 元数据库的 create_master_edition_v3
#[program]
pub mod nft_program {
 use super::*;
 pub fn create_single_nft(
 ctx: Context<CreateNFT>,
 id: u64,
 name: String,
 symbol: String,
 uri: String,
 price: f32,
 cant: u64,
) -> Result<()> {
  mint_to()?;
  create_metadata_accounts_v3()?;
  create_master_edition_v3()?;
}

如果我们查看 mint_to 函数的 Rust 文档,会看到它接收一个 CpiContext,其中有一个在同一库中定义的 mint_to 结构类型和一个数量。

CpiContext 用于我们的程序与网络上的其他程序交互。 该上下文必须通过 new_with_signer() 函数定义,因为它将需要种子来签署交易。

 mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    authority: ctx.accounts.authority.to_account_info(),
                    to: ctx.accounts.token_account.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),

                &[&seeds[..]],
             ),

new_with_signer()函数接收我们将与之交互的程序。 该程序已在我们的 CreateNFT 结构中定义,因此我们通过上下文 ctx.account 代币程序访问它,并使用 to_account_info() 函数读取其地址。 这是 Solana 上的代币创建程序。

用于创建 CpiContextnew_with_signer() 函数的下一个参数将是 MintTo 账户数组。 这将包含:

  • authority,它也在我们的上下文中定义,我们通过 ctx.account_authority 访问它,并将其转换为帐户信息。
  • to ,它是将接收 NFT 的账户,这将是与钱包付款人的代币铸币账户相关联的账户,或者是支付交易的账户。
  • mint,代币在网络上的身份。

所有这些帐户都来自我们已经创建的上下文。

最后,该函数将接收将形成种子的字节列表。

该种子必须之前通过以下方式声明:

  • 我们将作为参数接收到的 id 转换为字节 id_bytes = id.to_le_bytes
  • 然后,我们将 seeds 声明为一个数组,该数组首先将字符串 mint转换为字节。
  • 我们的 id_bytes 变量转换为引用,并且从我们的上下文中动态获取并以这种方式引用 mint帐户的 bump

这将是签署以下所有函数调用的种子:

 ) -> Result<()> {
        let id_bytes = id.to_le_bytes();
        let seeds = &["mint".as_bytes(),id_bytes.as_ref(),&[ctx.bumps.mint],
        ];

mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    authority: ctx.accounts.authority.to_account_info(),
                    to: ctx.accounts.token_account.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),

                &[&seeds[..]],
             ),

现在让我们构建对 create_metadata_accounts_v3 函数的调用。 这次,参数将是:

  • 再次使用 new_with_signer() 函数创建 CpiContext
  • DataV2 结构。
  • 我们将发送两个布尔值 true,第一个将权限帐户设置为同一签名者,第二个使元数据可变。
  • 最后, none一词代表集合详细信息。

我们将首先使用上下文中的元数据程序帐户构建 CpiContext。 这将是我们在此函数中与之交互的程序。

然后,我们将发送 CreateMetadataAccountsV3 结构。 具有以下参数,全部从我们的 CreateNFT 结构中获得:

  • payer
  • mint账户,
  • NFT metadata账户
  • 与权限账户一起的铸造和更新权限
  • system_program也可在上下文中使用
  • rent

最后,该函数将接收我们已经创建并可用的种子。

create_metadata_accounts_v3(
            CpiContext::new_with_signer(
                ctx.accounts.metadata_program.to_account_info(),
                CreateMetadataAccountsV3 {
                    payer: ctx.accounts.payer.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                    metadata: ctx.accounts.nft_metadata.to_account_info(),
                    mint_authority: ctx.accounts.authority.to_account_info(),
                    update_authority: ctx.accounts.authority.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &[&seeds[..]],

我们仍然需要填写 DataV2 结构的参数,即:

  • namesymbolURI 所有这些都将作为 create_single_nft 函数的参数
  • seller_fee_basis_points 参数值为 0
  • 参数 creaetorcollectionuser均设置为 none

我们将放置正在执行创建元数据帐户功能的消息 msg,我们的代码将如下所示:

  msg!("Run create metadata accounts v3");

        create_metadata_accounts_v3(
            CpiContext::new_with_signer(
                ctx.accounts.metadata_program.to_account_info(),
                CreateMetadataAccountsV3 {
                    payer: ctx.accounts.payer.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                    metadata: ctx.accounts.nft_metadata.to_account_info(),
                    mint_authority: ctx.accounts.authority.to_account_info(),
                    update_authority: ctx.accounts.authority.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &[&seeds[..]],
            ),
            DataV2 {
                name,
                symbol,
                uri,
                seller_fee_basis_points: 0,
                creators: None,
                collection: None,
                uses: None,
            },
            true,
            true,
            None,
        )?;

我们还将以相同的方式调用 create_master_edition_v3 函数,使用 new_with_signer() 函数创建的 CpiContext,最大供应量为 1,如下所示 some(1)

new_with_signer() 函数接收元数据程序作为程序,这是一个 CreateMetadataAccountsV3 结构,其中包含来自上下文的所有这些帐户:

  • edition 账户、作为付款人的 payer账户、 mint账户、具有代币 metadata的账户。
  • mint_authorityupdate_authoritysystem_programtoken_programrent
  • 最后,我们放置 seeds进行签名。 我们将放置消息 msg执行创建主版本功能,并在最后放置消息 msg NFT 已成功创建,然后我们通过 ok 完成该功能。

我们的代码如下所示:

  msg!("Run create master edition v3");

        create_master_edition_v3(
            CpiContext::new_with_signer(
                ctx.accounts.metadata_program.to_account_info(),
                CreateMasterEditionV3 {
                    edition: ctx.accounts.master_edition_account.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                    metadata: ctx.accounts.nft_metadata.to_account_info(),
                    mint_authority: ctx.accounts.authority.to_account_info(),
                    update_authority: ctx.accounts.authority.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    token_program: ctx.accounts.token_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &[&seeds[..]],
            ),
      Some(1),
          )?;

          msg!("Minted NFT successfully");

          Ok(())
    }

这将是整个代码:

use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::metadata::{
    create_master_edition_v3, create_metadata_accounts_v3, CreateMasterEditionV3,
    CreateMetadataAccountsV3, Metadata,
};
use anchor_spl::token::{mint_to, Mint, MintTo, Token, TokenAccount};
use mpl_token_metadata::types::{Collection, Creator, DataV2};

declare_id!("nuvdhmYq5Z2Eg4nBi29Tu2VcbpE9nuiCQ68rkyAB3A1");

#[program]
pub mod nft_program {
 use super::*;
 pub fn create_single_nft(
 ctx: Context<CreateNFT>,
 id: u64,
 name: String,
 symbol: String,
 uri: String,
 price: f32,
 cant: u64,
) -> Result<()> {
 msg!("Creating seeds");
        let id_bytes = id.to_le_bytes();
        let seeds = &["mint".as_bytes(),id_bytes.as_ref(),&[ctx.bumps.mint],
        ];

        msg!("Run mint_to");

        mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    authority: ctx.accounts.authority.to_account_info(),
                    to: ctx.accounts.token_account.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                },
                &[&seeds[..]],
            ),
            1, // 1 token
        )?;

        msg!("Run create metadata accounts v3");

        create_metadata_accounts_v3(
            CpiContext::new_with_signer(
                ctx.accounts.metadata_program.to_account_info(),
                CreateMetadataAccountsV3 {
                    payer: ctx.accounts.payer.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                    metadata: ctx.accounts.nft_metadata.to_account_info(),
                    mint_authority: ctx.accounts.authority.to_account_info(),
                    update_authority: ctx.accounts.authority.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &[&seeds[..]],
            ),
            DataV2 {
                name,
                symbol,
                uri,
                seller_fee_basis_points: 0,
                creators: None,
                collection: None,
                uses: None,
            },
            true,
            true,
            None,
        )?;

        msg!("Run create master edition v3");

        create_master_edition_v3(
            CpiContext::new_with_signer(
                ctx.accounts.metadata_program.to_account_info(),
                CreateMasterEditionV3 {
                    edition: ctx.accounts.master_edition_account.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                    metadata: ctx.accounts.nft_metadata.to_account_info(),
                    mint_authority: ctx.accounts.authority.to_account_info(),
                    update_authority: ctx.accounts.authority.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    token_program: ctx.accounts.token_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &[&seeds[..]],
            ),
            Some(1),
        )?;

        msg!("Minted NFT successfully");

        Ok(())
    }

    pub fn mint_to_collection(
        ctx: Context<MintToCollection>,
        id_collection: u64,
        id_nft: u64,
        name: String,
        symbol: String,
        uri: String,
        price: f32,
        cant: u64,
    ) -> Result<()> {
        msg!("Creating seeds");
        let id_bytes = id_collection.to_le_bytes();
        let id_nft_bytes = id_nft.to_le_bytes();
        let seeds = &[
            "mint".as_bytes(),
            id_bytes.as_ref(),
            id_nft_bytes.as_ref(),
            &[ctx.bumps.mint],
        ];

        msg!("Run mint_to");

        mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    authority: ctx.accounts.authority.to_account_info(),
                    to: ctx.accounts.token_account.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                },
                &[&seeds[..]],
            ),
            1, // 1 token
        )?;

        msg!("Run create metadata accounts v3");

        create_metadata_accounts_v3(
            CpiContext::new_with_signer(
                ctx.accounts.metadata_program.to_account_info(),
                CreateMetadataAccountsV3 {
                    payer: ctx.accounts.payer.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                    metadata: ctx.accounts.nft_metadata.to_account_info(),
                    mint_authority: ctx.accounts.authority.to_account_info(),
                    update_authority: ctx.accounts.authority.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &[&seeds[..]],
            ),
            DataV2 {
                name,
                symbol,
                uri,
                seller_fee_basis_points: 0,
                creators: Some(vec![Creator {
                    address: ctx.accounts.payer.key(),
                    verified: true,
                    share: 100,
                }]),
                collection: Some(Collection {
                    key: ctx.accounts.collection.key(),
                    verified: false,
                }),
                uses: None,
            },
            true,
            true,
            None,
        )?;

        msg!("Run create master edition v3");

        create_master_edition_v3(
            CpiContext::new_with_signer(
                ctx.accounts.metadata_program.to_account_info(),
                CreateMasterEditionV3 {
                    edition: ctx.accounts.master_edition_account.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                    metadata: ctx.accounts.nft_metadata.to_account_info(),
                    mint_authority: ctx.accounts.authority.to_account_info(),
                    update_authority: ctx.accounts.authority.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    token_program: ctx.accounts.token_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &[&seeds[..]],
            ),
            Some(1),
        )?;

        msg!("Minted NFT successfully");

        Ok(())
    }

}

#[derive(Accounts)]
#[instruction(id: u64)]
pub struct CreateNFT<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), id.to_le_bytes().as_ref()], 
    bump,
    )]
    pub mint: Account<'info, Mint>,
    #[account(
        init_if_needed,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer,
    )]
    pub token_account: Account<'info, TokenAccount>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
    pub metadata_program: Program<'info, Metadata>,
    #[account(
        mut,
        seeds = [
            b"metadata".as_ref(),
            metadata_program.key().as_ref(),
            mint.key().as_ref(),
            b"edition".as_ref(),
        ],
        bump,
        seeds::program = metadata_program.key()
    )]
    /// CHECK:
    pub master_edition_account: UncheckedAccount<'info>,
    #[account(
        mut,
        seeds = [
            b"metadata".as_ref(),
            metadata_program.key().as_ref(),
            mint.key().as_ref(),
        ],
        bump,
        seeds::program = metadata_program.key()
    )]
    /// CHECK:
    pub nft_metadata: UncheckedAccount<'info>,
}

#[derive(Accounts)]
#[instruction(id_collection: u64, id_nft: u64)]
pub struct MintToCollection<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account( 
    init,
    payer = payer, 
    mint::decimals = 0,
    mint::authority = authority,
    mint::freeze_authority = authority,
    seeds = ["mint".as_bytes(), 
             id_collection.to_le_bytes().as_ref(),
             id_nft.to_le_bytes().as_ref()], 
    bump,
    )]
    pub mint: Account<'info, Mint>,
    #[account(
        init_if_needed,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer,
    )]
    pub token_account: Account<'info, TokenAccount>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
    pub metadata_program: Program<'info, Metadata>,
    #[account(
        mut,
        seeds = [
            b"metadata".as_ref(),
            metadata_program.key().as_ref(),
            mint.key().as_ref(),
            b"edition".as_ref(),
        ],
        bump,
        seeds::program = metadata_program.key()
    )]
    /// CHECK:
    pub master_edition_account: UncheckedAccount<'info>,
    #[account(
        mut,
        seeds = [
            b"metadata".as_ref(),
            metadata_program.key().as_ref(),
            mint.key().as_ref(),
        ],
        bump,
        seeds::program = metadata_program.key()
    )]
    /// CHECK:
    pub nft_metadata: UncheckedAccount<'info>,
    /// CHECK:
    pub collection: UncheckedAccount<'info>,
}

我们将在终端中执行Build并得到结果“Build Success”。

现在我们使用 Deploy 来部署我们的程序,并完成此过程。

我们现在准备测试我们的程序。


原文链接:Create your own on-chain NFTs on Solana with Anchor and QuickNode: a step-by-step guide 2024

DefiPlot翻译整理,转载请标明出处

通过 NowPayments 打赏