在这一篇文章中,我们将学习如何编写一个自定义合约,并且只需要四个步骤就可以生成NFT。
在这一篇文章中,我们将学习如何编写一个自定义合约,并且只需要四个步骤就可以生成NFT。
在Solana的开发过程中,我们会遇到很多奇怪的自定义错误和bug,因为Solana的开发生态系统不像以太坊的开发生态系统那么大,所以修复它们会非常困难和令人沮丧。但不用担心。当我们陷入困境时,我们只需要在正确的地方寻找解决方案。
我们将使用的工具:
准备工作
在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/中。
该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>
将此程序 ID 粘贴到Anchor.toml
和lib.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爱好者有用。
ChinaDeFi - ChinaDeFi.com 是一个研究驱动的DeFi创新组织,同时我们也是区块链开发团队。每天从全球超过500个优质信息源的近900篇内容中,寻找思考更具深度、梳理更为系统的内容,以最快的速度同步到中国市场提供决策辅助材料。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!