WEB3DEV

Cover image for Como cunhar NFTs em Solana usando Rust e Metaplex
Adriano P. Araujo
Adriano P. Araujo

Posted on

Como cunhar NFTs em Solana usando Rust e Metaplex

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

Enter fullscreen mode Exit fullscreen mode

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"

Enter fullscreen mode Exit fullscreen mode

É 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"]}

Enter fullscreen mode Exit fullscreen mode

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};

Enter fullscreen mode Exit fullscreen mode

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>,

}

Enter fullscreen mode Exit fullscreen mode

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(())

    }

Enter fullscreen mode Exit fullscreen mode

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,

            },

        ];

Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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());

Enter fullscreen mode Exit fullscreen mode

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

),

Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

É 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.

Enter fullscreen mode Exit fullscreen mode

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.

Latest comments (0)