Rust와 Metaplex를 사용하여 Solana에서 NFT를 만드는 방법
이 방법에서는 Rust 스마트 계약을 작성하고 Metaplex의 토큰 메타데이터 프로그램을 사용하여 Solana에서 NFT를 작성하는 방법을 배웁니다.

독자 여러분을 환영합니다. 이것은 Solana 개발에 대한 새로운 일련의 블로그 게시물의 시작이며 이 게시물에서는 단 4단계로 NFT를 발행하기 위한 사용자 지정 계약을 작성하는 방법을 배웁니다.
Solana 개발에서는 이상한 사용자 정의 오류와 버그가 많이 발생하며 Solana 개발 생태계가 Eth 개발 생태계만큼 크지는 않지만 걱정할 필요가 없기 때문에 수정하는 것이 상당히 어렵고 실망스러울 수 있습니다. 막혔을 때 올바른 솔루션을 찾기만 하면 됩니다.
개발하는 동안 Anchor discord 서버와 Metaplex 및 Superteam 서버에 대해 지속적으로 의문을 제기하고 GitHub의 다른 코드 저장소와 Metaplex 프로그램 라이브러리 자체를 살펴보았습니다.
이를 위해 사용할 도구:
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 지갑 문서 및 일부 추가 devnet
명령을 사용하여 솔 solana airdrop 1
마지막으로 앵커 cli를 사용하여 앵커 프로젝트를 만드십시오.
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//.config/solana/id.json"[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
그게 다야 당신은 모두 유리를 먹을 준비가되었습니다!
프로젝트에는 프로그램이라는 폴더가 있어야 합니다. programs//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"]}
💫 최신 버전의 앵커를 사용하지 마십시오. 앵커가 최신 버전의 solana-program으로 자체 업데이트를 보류하면 mpl 크레이트와 앵커 크레이트의 종속성 요구 사항 간에 충돌이 발생할 수 있습니다.
그런 다음 lib.rs
src에 파일을 만들고 다음을 가져옵니다.
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};
이제 민트 함수를 작성할 수 있습니다!
먼저, 민트 함수를 위한 계정 구조체를 생성해 보겠습니다.
#[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
토큰을 발행하기 위해 방금 만든 구조체를 사용하는 함수를 만들어 보겠습니다.
pub fn mint_nft(
ctx: Context,
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!()
확인하려는 값을 기록하려면 문자열을 허용하므로 hv를 사용할 수 있습니다.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,
},
];
이 가이드의 범위에 속하지 않기 때문에 Collections를 구현하지 않았지만 다음을 사용하여 수행할 수 있습니다.
mpl_token_metadata::instruction::set_and_verify_collection
여기에서 최대 공급을 0으로 설정한 이유에 대해. Metaplex에서 토큰이 종류 중 하나인 경우 총 공급량 - 청구된 공급량(1–1)이 0이므로 최대 공급량을 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
),
함수를 작성했으면 실행 anchor build && anchor deploy
배포된 프로그램 ID가 표시되어야 합니다.

이 프로그램 ID를 Anchor.toml
이 기본 ID가 표시되는 곳마다 lib.rs 파일 Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS
무엇이든하기 전에 가져 왔는지 확인하십시오. @solana/web3.js
그리고 @solana/spl-token
. 내부에 tests/.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; 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 => {
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 => {
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());
메모: 민트와 동결 권한은 동일해야 하며 그렇지 않으면 작동하지 않습니다.
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);
그게 다야! 이제 앵커 테스트를 실행하면 nft를 만들 수 있습니다.
Account: 4swRFMNovHCkXY3gDgAGBXZwpfFuVyxWpWsgXqbYvoZG1M63nZHxyPRm7KTqAjSdTpHn2ivyPr6jQfxeLsB6a1nX
Mint key: DehGx61vZPYNaMWm9KYdP91UYXXLu1XKoc2CCu3NZFNb
User: 7CtWnYdTNBb3P9eViqSZKUekjcKnMcaasSMC7NbTVKuE
Metadata address: 7ut8YMzGqZAXvRDro8jLKkPnUccdeQxsfzNv1hjzc3Bo
MasterEdition: Au76v2ZDnWSLj23TCu9NRVEYWrbVUq6DAGNnCuALaN6o
Your transaction signature KwEst87H3dZ5GwQ5CDL1JtiRKwcXJKNzyvQShaTLiGxz4HQGsDA7EW6rrhqwbJ2TqQFRWzZFvhfBU1CpyYH7WhH
✔ Is initialized! (6950ms)
1 passing (7s)✨ Done in 9.22s.
💫 0x1과 같은 16진수 값으로 사용자 지정 프로그램 오류가 발생하면 16진수 값을 일반 텍스트로 변환한 다음 메타플렉스 깃허브 브라우저를 사용하여 숫자 + "error("
NFT는 여기에서 확인할 수 있습니다.
이 가이드가 모든 Solana 괴짜들에게 유용하기를 바랍니다. 처음 NFR을 만들려고 했을 때 머리를 뽑고 있었습니다. 훨씬 쉽게 만들었습니다.
이 프로젝트의 GitHub는 다음과 같습니다.
당신은 내에서 나를 따라갈 수 있습니다 트위터 그리고 Github은 다음 시간까지 계속 유리를 먹습니다!
덕분에 프라틱 사리아 그리고 0x딥 Solana NFT와 Anchor가 작동하는 방식을 이해하는 데 도움을 주셔서 감사합니다. 그렇지 않았다면 여전히 이해하려고 노력했을 것입니다.
'Coding' 카테고리의 다른 글
Swift 5를 사용하여 iOS용 UITabBar를 위한 드리블과 같은 플로팅 버튼 디자인하는 방법 (0) | 2022.04.18 |
---|---|
Linux Shell을 위한 놀라운 레트로 게임을 만드는 방법 (0) | 2022.04.16 |
Python .zip() – Python의 Zip 함수 (0) | 2022.04.14 |
Python 코드를 자동으로 확인하고 수정하기 위한 사전 커밋 워크플로 구축하는 방법 (0) | 2022.04.13 |
Jest를 사용하여 Spectator로 Mock을 구현하는 방법 (0) | 2022.04.12 |
댓글