使用 Anchor 和 QuickNode 在 Solana 上创建NFT: 2024 版指南
欢迎来到本教程。今天,我们将使用 Solana Playground、QuickNode RPC 和一个 IPFS 服务,在 Anchor/Rust 中创建一个 Solana 程序,以直接在链上铸造 NFT。
作为预备步骤,我们将在去中心化存储服务中准备我们的 NFT 图像和元数据。我们将使用 QuickNode IPFS,这是一个 IPFS 存储服务,适用于需要快速、高可用访问和高级功能的解决方案。
让我们上传将成为我们 NFT 的图像。在 QuickNode 仪表板上,转到 IPFS 然后是文件,你可以拖放你的图像。
让我们复制出现在我们上传的文件名旁边的内容标识符 (CID)。然后我们将其附加到地址 quicknode.myfilebase.com/ipfs/
以获得我们将在元数据中使用的 URL。测试此 URL 以确保一切正确是很重要的。
为此,我们将基于 Metaplex 为 Solana 上的 NFT 元数据创建的标准。你可以访问 Metaplex 文档 了解更多信息。
我们的元数据将是一个 JSON 文件,包含我们为 NFT 选择的参数 Name、Symbol 和 Description。
{
"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 的文件选项卡并像处理图像文件一样拖放它。现在我们已经准备好继续。
我们将前往 Solana Playground 并通过创建一个新钱包进行连接。
Solana Playground
点击显示“not connected”的地方,然后保存密钥对并继续。
保存密钥对
在这里我们可以看到创建的钱包地址。
创建的钱包地址
创建一个新项目,给它命名,选择 Anchor (Rust) 并创建。
创建的 Anchor 项目
注意:我们将需要大约 10 个 DevNet 的 Solana 来部署合约。要为我们的钱包充值,我们必须在 Sol Faucet 中输入我们刚刚创建的钱包地址,该网站允许我们每 24 小时充值 3 个 Solana。这意味着我们需要几天时间来完成充值。作为替代方案,我们建议使用其他钱包地址和 VPN 来加快进程。_
现在是时候配置 solpg 以使用 QuickNode RPC 在 Solana DevNet 上工作了。点击设置选项卡,然后点击端点选项卡。
设置 Solana Playground
选择自定义并粘贴你的 QuickNode 私有 RPC 用于 Solana DevNet。
自定义 RPC
完成后,回到我们新创建的项目中,我们将删除不需要的代码,并保留来自 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::{
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
。我们将其定义为类型为 Signer
的公共账户,具有 'info
类型的生命周期。我们在此结构中定义的所有账户都将是公共类型并具有此生命周期。
#[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
账户及其相应的可变属性 mut
。
#[derive(Accounts)]
pub struct CreateNFT<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut)]
pub payer: Signer<'info>,
}
现在我们声明 mint
账户,类型为 Account
,它将携带类型为 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
账户作为 mint 结构的权限传递freeze_authority
参数同样#[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
,它将携带一种代币账户类型的结构,允许我们将铸造账户与将接收铸造 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
,即如果不存在将创建一个新账户。我们将发送 payer 作为我们的支付账户。对于 associated_token
结构中存在的 mint 参数,我们将发送铸造账户和支付账户作为权限。
#[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>,
}
最后,我们声明 UncheckedAccount
类型的账户,master_edition_account
和 nft_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。
我们将为此账户定义以下参数:
#[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()
)]
/// 检查:
pub master_edition_account: UncheckedAccount<'info>,
/// 检查:
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()
)]
/// 检查:
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()
)]
/// 检查:
pub nft_metadata: UncheckedAccount<'info>,
}
有了我们的 CreateNFT
结构,我们将开始创建我们的程序。在程序属性下,我们创建公共模块 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<()> {
}
然后我们将指定函数将接收的参数:
#[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
create_master_edition_v3
从 anchor_spl
元数据库导入
#[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()?;
}
如果我们查看 Rust 文档 中的 mint_to
函数,我们会看到它接收一个 CpiContext
,其中包含一个在同一库中定义的 mint_to
结构类型和一个数量。
rust 文档 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 上的代币创建程序。
new_with_signer()
函数的下一个参数,用于创建 CpiContext
,将是 MintTo
账户数组。它将包含:
authority
,也在我们的上下文中定义,我们通过 ctx.account_authority
访问它,也将其转换为账户信息to
,接收 NFT 的账户,这将是与代币 mint
账户关联的钱包 payer
的账户,或支付交易的账户mint
,网络上的代币身份所有这些账户都来自我们已经创建的上下文。
最后,函数将接收形成我们 seeds
的字节列表。
此种子必须以前以以下方式声明:
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
结构none
我们将首先使用来自我们上下文的元数据程序账户构建 CpiContext
。这将是我们在此函数中将与之交互的程序。
然后,我们将发送 CreateMetadataAccountsV3
结构。使用以下参数,均从我们的 CreateNFT
结构中获取:
payer
mint
账户metadata
账户system_program
也在上下文中可用rent
seeds
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
都将作为参数传递给我们的 create_single_nft
函数seller_fee_basis_points
参数的值为 0creators
、collection
和 use
都设置为 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_authority
、update_authority
、system_program
、token_program
和 rent
seeds
进行签名。我们将放置消息 msg
“Executing the create master edition function”,最后放置消息 msg
“The NFT has been successfully created”,并用 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!("铸造NFT成功");
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!("创建种子");
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!("运行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 代币
)?;
msg!("运行创建元数据账户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!("运行创建主版本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!("铸造NFT成功");
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 Succesful”的结果。
现在我们使用Deploy部署我们的程序,并在完成此过程后。
我们现在准备测试我们的程序。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!