Neste tutorial, você aprenderá como cunhar um NFT em Solana escrevendo um contrato inteligente Rust e usando o Programa de Metadados de Token da Metaplex
Bem-vindos leitores. Este é o começo de uma nova série de postagens do blog sobre o desenvolvimento em Solana e, neste primeiro, você aprenderá a escrever um contrato personalizado para cunhar seu NFT em apenas quatro etapas.
Algumas dicas gerais sobre o desenvolvimento em Solana
No desenvolvimento em Solana, você enfrentará muitos erros e bugs especificamente estranhos, e pode ser bastante difícil e frustrante corrigi-los, já que o ecossistema de desenvolvedores Solana não é tão grande quanto o ecossistema de desenvolvimento Eth. Mas não se preocupe. Quando você ficar preso, basta procurar no lugar certo a solução.
Durante meu desenvolvimento, eu estava constantemente tirando minhas dúvidas nos servidores do discord do Anchor, do Metaplex e do Superteam e examinando outros repositórios de código no GitHub e na própria biblioteca do programa Metaplex.
Visão geral do projeto
As ferramentas que usaremos para isso:
Ferramentas CLI do Solana — O conjunto de ferramentas oficial da CLI da Solana
Framework Anchor — Um framework de alto nível para o desenvolvimento de programas Solana. Isso é obrigatório, a menos que você seja um dev nível-deus; nesse caso, você não está lendo este blog. kkkkk.
Solana / web3.js — Uma versão Solana de web3.js
Solana / spl-token — Um pacote para trabalhar com tokens spl
Mocha — Uma ferramenta de teste JS
Introdução
Preparar o trabalho
Use a CLI para definir sua rede como devnet com o seguinte comando:
solana config set --url devnet
Para confirmar se funcionou, verifique a saída após inserir o 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
Em seguida, se você ainda não o fez, configure uma carteira de sistema de arquivos usando este guia, Documentos da carteira Solana, e também adicione alguns sol devnet
usando o comando
solana airdrop 1
Por fim, use a CLI anchor para fazer um projeto anchor com este comando:
anchor init <name-of-your-project>
Certifique-se de que Anchor.toml
também está definido como 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"
É isso. Você está pronto para o mais difícil!
Etapa 1. Importar as dependências
No seu projeto, deve haver uma pasta chamada programs. Vá para programs/<nome-do seu-projeto>/Cargo.toml
, e adicione essas dependências. Certifique-se de usar a versão 0.24.2
e você pode usar [avm](https://book.anchor-lang.com/chapter_5/avm.html)
para mudá-la
[dependencies]
anchor-lang = "0.24.2"
anchor-spl = "0.24.2"
mpl-token-metadata = {version = "1.2.7", features = ["no-entrypoint"]}
O Anchor retirou todas as versões antes de 0.24.2 devido a uma vulnerabilidade de segurança, portanto, certifique-se de usar exatamente essa.
Então vá para o arquivo em src lib.rs
e importe isso:
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};
Legal. Agora podemos escrever a função de cunhagem!
Etapa 2. Escrevendo a struct da função de cunhagem
Primeiro, criaremos a estrutura de contas para a função mint
#[derive(Accounts)]
pub struct MintNFT<'info> {
#[account(mut)]
pub mint_authority: Signer<'info>,
/// CHECK:Isso não é perigoso porque não lemos ou escrevemos nesta conta
#[account(mut)]
pub mint: UncheckedAccount<'info>,
// #[account(mut)]
pub token_program: Program<'info, Token>,
/// CHECK: Isso não é perigoso porque não lemos ou escrevemos nesta conta
#[account(mut)]
pub metadata: UncheckedAccount<'info>,
/// CHECK: Isso não é perigoso porque não lemos ou escrevemos nesta conta
#[account(mut)]
pub token_account: UncheckedAccount<'info>,
/// CHECK: Isso não é perigoso porque não lemos ou escrevemos nesta conta
pub token_metadata_program: UncheckedAccount<'info>,
/// CHECK: Isso não é perigoso porque não lemos ou escrevemos nesta conta
#[account(mut)]
pub payer: AccountInfo<'info>,
pub system_program: Program<'info, System>,
/// CHECK: Isso não é perigoso porque não lemos ou escrevemos nesta conta
pub rent: AccountInfo<'info>,
/// CHECK: Isso não é perigoso porque não lemos ou escrevemos nesta conta
#[account(mut)]
pub master_edition: UncheckedAccount<'info>,
}
Não se preocupe com as contas não controladas, pois as passaremos para o programa Metaplex, que as verificará.
Para usar contas não verificadas no Anchor, você precisa adicionar este comentário acima de cada conta:
/// CHECK: This is not dangerous because we don't read or write from this account
Etapa 3. A função de cunhagem 🪙
Vamos fazer uma função que use a estrutura que acabamos de criar para cunhar o token:
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(())
}
Se você deseja depurar seu programa, melhor usar msg!()
para registrar o valor que você deseja verificar. Ele aceita string então você precisa usar std::string::ToString
para a conversão. Seus logs aparecerão no terminal ou em .anchor/program-logs/<program-id>
Então, algumas coisas até aqui…
A matriz creator
precisa ter a pessoa cunhando as NFTs como parte dela, mas você pode definir a ação como 0, para que isso realmente não importe. Aqui está o código:
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,
},
];
Não implementei coleções, pois não estão no escopo deste guia, mas você pode fazê-lo usando:
mpl_token_metadata :: instrução :: set_and_verify_collection
Em relação ao motivo pelo qual defini o suprimento máximo para 0 aqui é que no Metaplex, se o token for único, você deverá definir sua oferta máxima como zero, pois a oferta total — reivindicada ( 1 – 1 ) 1 é igual a 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)
Após escrever a função, execute anchor build && anchor deploy
e você deve ver o ID do programa implantado
cole este ID do programa no seu Anchor.toml
e o lib.rs
onde quer que você veja esse ID padrão Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS
Etapa 4. Chamando a função de cunhagem
Antes de fazer qualquer coisa, verifique se você importou @solana/web3.js
e @solana/spl-token
. Dentro de tests/<test-file>.ts
adicione essas importações e constantes:
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();
Agora vamos fazer o token e a conta de token associada, como mostrado abaixo:
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());
Nota: a autorização de cunhagem e congelamento deve ser a mesma, caso contrário não funcionará.
createInitializeMintInstruction( mintKey.publicKey, 0, program.provider.wallet.publicKey,// mint auth program.provider.wallet.publicKey // freeze auth
),
Agora, chame a função de cunhagem e passe todos os dados e contas
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);
É isso aí! Agora basta executar o teste anchor e você poderá cunhar seu 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.
Se você receber algum erro de programa específico com um valor hexadecimal como 0x1, converta o valor hexadecimal em texto sem formatação e vá para o github do metaplex e pesquise usando o navegador por number + 1 th appearance of the word “error(“
Você pode conferir o NFT aqui:
https://solscan.io/token/DehGx61vZPYNaMWm9KYdP91UYXXLu1XKoc2CCu3NZFNb?cluster=devnet
Resumindo
Espero que este guia tenha sido útil para todos os nerds de Solana por aí. Quando tentei cunhar um NFT, estava arrancando meus cabelos, mas lentamente começou a fazer sentido quando algumas outras pessoas que gostam de coisas difíceis me explicaram. Felizmente, tornei isso muito mais fácil para você.
Aqui está o GitHub para este projeto:
https://github.com/anoushk1234/metaplex-anchor-nft
Você pode me seguir no meu Twitter e Github. Até a próxima, continue buscando o difícil!
Muito obrigado a Pratik Saria e 0xDeep por me ajudarem a entender como os NFTs Solana e o Anchor funcionam. Se não fosse por eles, eu ainda estaria tentando descobrir.
Este artigo foi escrito por Anoushk Kharangate e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.
Top comments (0)