Pular para o conteúdo principal

Tutorial de criação de uma NFT

solidityNFTalchemycontratos inteligentesfront-endPinata
Intermediário
smudgil
6 de outubro de 2021
28 minutos de leitura minute read

Um dos maiores desafios para desenvolvedores vindos de um background Web2 é descobrir como conectar seu contrato inteligente a um projeto frontend e interagir com ele.

Ao criar um minter NFT — uma simples UI onde você pode inserir um link para seu ativo digital, um título e uma descrição — você aprenderá a:

  • Conectar ao MetaMask através do seu projeto frontend
  • Chamar métodos de contrato inteligentes no seu frontend
  • Assine transações usando MetaMask

Neste tutorial, usaremos o React(opens in a new tab) como nossa estrutura de frontend. Como este tutorial está focado principalmente no desenvolvimento da Web3, nós não passaremos muito tempo detalhando os fundamentos do React. Em vez disso, nós focaremos em trazer funcionalidade para o nosso projeto.

Como pré-requisito, você deve ter uma compreensão mínima do React – saber como funcionam componentes, props, useState/useEffect e chamadas de funções básicas. Se você nunca ouviu falar de nenhum desses termos antes, você pode querer conferir este Intro to React tutorial(opens in a new tab). Para os que apreciam mais visualidade, é altamente recomendável esta excelente série de vídeos Full Modern React Tutorial(opens in a new tab) por Net Ninja.

E se você ainda não fez, você definitivamente precisará criar uma conta Alchemy para concluir este tutorial, bem como construir qualquer coisa no blockchain. Inscreva-se para uma conta gratuita aqui(opens in a new tab).

Sem mais delongas, vamos começar!

Criando NFTs 101

Antes de começarmos a olhar para qualquer código, é importante entender como funciona fazer uma NFT. Envolve duas etapas:

Publicar um contrato inteligente da NFT no blockchain Ethereum

A maior diferença entre os dois padrões de contrato inteligente NFT é que o ERC-1155 é um padrão multi-token e inclui a funcionalidade de lote, enquanto o ERC-721 é um padrão de token único, portanto, suporta apenas a transferência de um token por vez.

Chamar a função mint

Normalmente, esta função mint requer que você passe duas variáveis como parâmetros, primeiro o destinatário recipient, que especifica o endereço que receberá a sua NFT recém-mintada, e segundo o tokenURI da NFT, uma string que indica a um documento JSON que descreve os metadados da NFT.

Os metadados de uma NFT são o que realmente a torna realidade, permitindo que tenha propriedades configuráveis, como um nome, descrição, imagem (ou diferentes ativos digitais), e outros atributos. Aqui está um exemplo de um tokenURI(opens in a new tab), que contém os metadados de uma NFT.

Neste tutorial, vamos nos concentrar na parte 2, chamando a função mint de contrato inteligente de uma NFT existente usando nossa interface do React.

Aqui está um link(opens in a new tab) para o contrato inteligente NFT ERC-721 que vamos chamar neste tutorial. Se você gostaria de saber como o fizemos, é altamente recomendável que você veja nosso outro tutorial, "Como criar uma NFT"(opens in a new tab).

Legal, agora que entendemos como fazer uma NFT funcionar, vamos clonar nossos arquivos iniciais!

Clonar os arquivos iniciais

Primeiro, vá para o repositório GitHub do nft-minter-tutorial(opens in a new tab) para obter os arquivos iniciais para este projeto. Clone este repositório para o seu ambiente local.=

Quando você abrir este repositório clonado nft-minter-tutorial, irá notar que ele contém duas pastas: minter-starter-files e nft-minter.

  • minter-starter-files contém os arquivos iniciais (essencialmente a interface do React) para este projeto. Neste tutorial, trabalharemos nesse diretório, enquanto você aprende a dar vida a sua interface do usuário, conectando-a à sua carteira Ethereum e a um contrato inteligente de NFT.
  • nft-minter contém o tutorial completo e serve para você como uma referência se você ficar preso.

Em seguida, abra sua cópia de minter-starter-files no seu editor de código e navegue para a pasta src.

Todo o código que vamos escrever será exibido na pasta src. Vamos editar o componente Minter.js e escrever arquivos javascript adicionais para dar funcionalidades Web3 ao nosso projeto.

Passo 2: Confira nossos arquivos iniciais

Antes de começarmos a codificar, é importante verificar o que já está fornecido para nós nos arquivos iniciais.

Tenha seu projeto React em execução

Vamos começar executando o projeto React em nosso navegador. A beleza do React é que uma vez que nosso projeto esteja sendo executado no nosso navegador, qualquer alteração que salvarmos será atualizada ao vivo em nosso navegador.

Para fazer com que o projeto funcione, navegue até o diretório raiz da pasta minter-starter-files, e executenpm install no seu terminal para instalar as dependências do projeto:

cd minter-starter-files
npm install

Uma vez terminada a instalação, execute npm start em seu terminal:

npm start

Feito isso, você deve abrir http://localhost:3000/ no seu navegador, onde você verá o frontend do nosso projeto. Ele deve consistir de 3 campos: um local para inserir um link para o ativo do seu NFT, digite o nome da sua NFT e forneça uma descrição.

Se você tentar clicar nos botões "Connectar Wallet" ou "Mint NFT", você notará que eles não funcionam — isso porque ainda precisamos programar a funcionalidade deles! :)

O componente Minter.js

NOTA: Certifique-se de estar na pasta minter-starter-files e não na pasta nft-minter!

Vamos voltar à pasta src no nosso editor e abrir o arquivo Minter.js. É muito importante que entendamos tudo neste arquivo, pois é o principal componente do React no qual vamos trabalhar.

No topo do nosso arquivo, temos nossas variáveis de estado que serão atualizadas após eventos específicos.

1//State variables
2const [walletAddress, setWallet] = useState("")
3const [status, setStatus] = useState("")
4const [name, setName] = useState("")
5const [description, setDescription] = useState("")
6const [url, setURL] = useState("")

Nunca ouviu falar de variáveis de estado do React ou State Hooks? Confira está(opens in a new tab) documentação.

Veja aqui o que cada uma das variáveis representa:

  • walletAddress - uma string que armazena o endereço da carteira do usuário
  • status - uma string que contém uma mensagem a ser exibida na parte inferior da interface do usuário
  • name - uma string que armazena o nome da NFT
  • descrição - uma string que armazena a descrição da NFT
  • url - uma string que é um link para o ativo digital da NFT

Após as variáveis de estado, você verá três funções não implementadas: useEffect, connectWalletPressed, e onMintPressed. Você irá notar que todas essas funções são async, isso é porque iremos fazer chamadas assíncronas da API nelas! Os nomes delas são relacionadas com sua funcionalidade:

1useEffect(async () => {
2 //TODO: implement
3}, [])
4
5const connectWalletPressed = async () => {
6 //TODO: implement
7}
8
9const onMintPressed = async () => {
10 //TODO: implement
11}
Exibir tudo

Perto do final desse arquivo, temos a interface de usuário do nosso componente. Se você escanear este código com cuidado, notará que atualizamos nossa url, name, e description variáveis de estado quando a entrada em seus campos de texto correspondentes muda.

Você também verá que connectWalletPressed e onMintPressed são chamadas quando os botões com IDs mintButton e walletButton são clicados respectivamente.

1//the UI of our component
2return (
3 <div className="Minter">
4 <button id="walletButton" onClick={connectWalletPressed}>
5 {walletAddress.length > 0 ? (
6 "Connected: " +
7 String(walletAddress).substring(0, 6) +
8 "..." +
9 String(walletAddress).substring(38)
10 ) : (
11 <span>Connect Wallet</span>
12 )}
13 </button>
14
15 <br></br>
16 <h1 id="title">🧙‍♂️ Alchemy NFT Minter</h1>
17 <p>
18 Simply add your asset's link, name, and description, then press "Mint."
19 </p>
20 <form>
21 <h2>🖼 Link to asset: </h2>
22 <input
23 type="text"
24 placeholder="e.g. https://gateway.pinata.cloud/ipfs/<hash>"
25 onChange={(event) => setURL(event.target.value)}
26 />
27 <h2>🤔 Name: </h2>
28 <input
29 type="text"
30 placeholder="e.g. My first NFT!"
31 onChange={(event) => setName(event.target.value)}
32 />
33 <h2>✍️ Description: </h2>
34 <input
35 type="text"
36 placeholder="e.g. Even cooler than cryptokitties ;)"
37 onChange={(event) => setDescription(event.target.value)}
38 />
39 </form>
40 <button id="mintButton" onClick={onMintPressed}>
41 Mint NFT
42 </button>
43 <p id="status">{status}</p>
44 </div>
45)
Exibir tudo

Finalmente, vamos endereçar onde esse componente Minter será adicionado.

Se você for ao arquivo App.js, que é o componente principal do React que atua como um contêiner para todos os outros componentes, você verá que nosso componente Minter é injetado na linha 7.

Neste tutorial, vamos apenas editar o arquivo Minter.js e adicionar arquivos em nossa pasta src.

Agora que entendemos com o que estamos trabalhando, vamos configurar a nossa carteira Ethereum!

Configure sua carteira Ethereum

Para que os usuários possam interagir com o seu contrato inteligente, eles precisarão conectar a sua carteira Ethereum ao seu dapp.

Baixar MetaMask

Para este tutorial, usaremos uma carteira virtual no navegador, a MetaMask, para gerenciar o endereço da sua conta Ethereum. Se você quiser entender mais sobre como as transações no Ethereum funcionam, confira esta página na Fundação Ethereum.

Você pode baixar e criar uma conta MetaMask gratuitamente neste link(opens in a new tab). Quando estiver criando uma conta, ou se já tiver uma, certifique-se de mudar para a "Ropsten Test Network", no canto superior direito (para não precisar lidar com dinheiro de verdade).

Etapa: Adicionar Faucet ether

Para mintar as nossas NFT (ou assinar quaisquer transações no blockchain Ethereum), precisaremos de alguns Eth falsos. Para obter Eth você pode ir para o faucet da Ropsten(opens in a new tab), inserir seu endereço de conta Ropsten e clicar em "Send Ropsten Eth." Em seguida, você deve ver Eth em sua conta Metamask!

Conferir o seu saldo

Para verificar novamente que tem saldo, vamos fazer uma solicitação através da ferramenta eth_getBalance(opens in a new tab) fornecida pelo compositor da Alchemy(opens in a new tab). Ela mostrará a quantidade de Eth na sua carteira. Depois de inserir o endereço da sua conta da MetaMask e clicar em "Send Request", você verá uma resposta como esta:

1{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}

NOTA: Este resultado está em wei, não em ETH. Lembre-se de que "Wei" é a menor unidade de ether. A conversão de wei para eth é: 1 eth = 10¹⁸ wei. Então, se convertemos 0xde0b6b3a7640000 para decimal, temos 1*10¹⁸ wei, que é igual a 1 eth.

Ufa! Nosso dinheiro falso está todo lá!

Conecte o MetaMask à sua interface

Agora que nossa carteira MetaMask está configurada, vamos conectar nosso dapp a ela!

Como queremos prescrever conforme o paradigma MVC(opens in a new tab), vamos criar um arquivo separado que contém nossas funções para gerenciar a lógica, dados e regras de nosso dapp, e então passar essas funções para nosso frontend (nosso componente Minter.js).

Função connectWallet

Para fazer isso, vamos criar uma nova pasta chamada utils em seu diretório src e adicionar um arquivo chamado interact.js dentro dele, que conterá todas as funções de nossa carteira e da interação com o contrato inteligente.

No nosso arquivo interact.js, vamos escrever uma função connectWallet, que então importar e chamará nosso componente Minter.js.

No seu arquivointeract.js, adicione o seguinte

1export const connectWallet = async () => {
2 if (window.ethereum) {
3 try {
4 const addressArray = await window.ethereum.request({
5 method: "eth_requestAccounts",
6 })
7 const obj = {
8 status: "👆🏽 Write a message in the text-field above.",
9 address: addressArray[0],
10 }
11 return obj
12 } catch (err) {
13 return {
14 address: "",
15 status: "😥 " + err.message,
16 }
17 }
18 } else {
19 return {
20 address: "",
21 status: (
22 <span>
23 <p>
24 {" "}
25 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
26 You must install MetaMask, a virtual Ethereum wallet, in your
27 browser.
28 </a>
29 </p>
30 </span>
31 ),
32 }
33 }
34}
Exibir tudo

Vamos dividir o que este código faz:

Primeiro, nossa função verifica se o window.ethereum está habilitado no seu navegador.

window.ethereum é uma API global injetada pela MetaMask e outros provedores de carteira que permitem que sites solicitem contas Ethereum dos usuários. Se aprovada, ela pode ler dados das blockchains ao qual o usuário está conectado e sugerir que o usuário assine mensagens e transações. Confira a documentação da MetaMask(opens in a new tab) para obter mais informações!

Se window.ethereum não está presente, então isso significa que o MetaMask não está instalado. Isso resulta em um objeto JSON sendo retornado, onde o endereço retornado é uma string vazia, e o status do objeto JSX repassa que o usuário deve instalar o MetaMask.

A maioria das funções que escrevermos retornarão objetos JSON que podemos usar para atualizar nossas variáveis de estado e interface de usuário.

Agora se window.ethereum estiver presente, e é aí que as coisas ficam interessantes.

Usando um loop de try/catch, tentaremos nos conectar a MetaMask chamando[window.ethereum.request({ method: "eth_requestAccounts" });](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts). Chamando esta função o MetaMask irá abrir no navegador, onde o usuário será solicitado a conectar sua carteira ao seu dapp.

  • Se o usuário escolher conectar-se, método: "eth_requestAccounts" retornará um array que contém todos os endereços de conta do usuário que estão conectados ao dapp. No total, nossa função connectWallet retornará um objeto JSON que contém o primeiro address desta matriz (ver linha 9) e uma mensagem status que pede que o usuário escreva uma mensagem para o contrato inteligente.
  • Se o usuário rejeitar a conexão, então o objeto JSON vai conter uma string vazia para o address retornado e uma mensagem de status que reflete que o usuário rejeitou a conexão.

Adicionar função connectWallet ao seu componente UI Minter.js

Agora que escrevemos esta função connectWallet, vamos conectá-la ao nosso componente Minter.js..

Primeiro, teremos que importar nossa função para o arquivo Minter.js adicionando import { connectWallet } from "./utils/interact.js"; para o topo do arquivo Minter.js. Suas primeiras 11 linhas de Minter.js agora devem se parecer com isto:

1import { useEffect, useState } from "react";
2import { connectWallet } from "./utils/interact.js";
3
4const Minter = (props) => {
5
6 //State variables
7 const [walletAddress, setWallet] = useState("");
8 const [status, setStatus] = useState("");
9 const [name, setName] = useState("");
10 const [description, setDescription] = useState("");
11 const [url, setURL] = useState("");
Exibir tudo

Então, dentro da nossa função connectWalletPressed, vamos chamar nossa função importada connectWallet, assim:

1const connectWalletPressed = async () => {
2 const walletResponse = await connectWallet()
3 setStatus(walletResponse.status)
4 setWallet(walletResponse.address)
5}

Observe como a maior parte das nossas funcionalidades está abstraída do nosso componente Minter.js do arquivo interact.js? É assim que respeitamos o paradigma M-V-C!

Em connectWalletPressed, simplesmente fazemos uma chamada de espera (await) para a função connectWallet, importada, e usando sua resposta, nós atualizaremos nossas variáveis status e walletAddress através de seus state hooks.

Agora, vamos salvar os dois arquivos Minter.js e interact.js e testar nossa UI até agora.

Abra seu navegador em localhost:3000, e pressione o botão "Conectar Carteira" no canto superior direito da página.

Se você tiver o MetaMask instalado, você será solicitado a conectar sua carteira ao seu dapp. Aceite o convite para se conectar.

Você verá que o botão da carteira agora reflete que seu endereço está conectado.

Em seguida, tente atualizar a página... isso é estranho. Nosso botão de carteira está nos pedindo para conectar o MetaMask, mesmo que já esteja conectado...

Mas não se preocupe! Nós podemos facilmente corrigir isso implementando uma função chamada getCurrentWalletConnected, que irá verificar se um endereço já está conectado ao nosso dapp e atualizará nossa interface do usuário adequadamente!

Função getCurrentWalletConnected

Em seu arquivo interact.js, adicione a funçãogetCurrentWalletConnected:

1export const getCurrentWalletConnected = async () => {
2 if (window.ethereum) {
3 try {
4 const addressArray = await window.ethereum.request({
5 method: "eth_accounts",
6 })
7 if (addressArray.length > 0) {
8 return {
9 address: addressArray[0],
10 status: "👆🏽 Write a message in the text-field above.",
11 }
12 } else {
13 return {
14 address: "",
15 status: "🦊 Connect to MetaMask using the top right button.",
16 }
17 }
18 } catch (err) {
19 return {
20 address: "",
21 status: "😥 " + err.message,
22 }
23 }
24 } else {
25 return {
26 address: "",
27 status: (
28 <span>
29 <p>
30 {" "}
31 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
32 You must install MetaMask, a virtual Ethereum wallet, in your
33 browser.
34 </a>
35 </p>
36 </span>
37 ),
38 }
39 }
40}
Exibir tudo

Este código é muito semelhante à função connectWallet que acabamos de escrever.

A diferença principal é que, em vez de chamar o método eth_requestAccounts, que abre o MetaMask para o usuário conectar sua carteira, aqui chamamos o método eth_accounts, que simplesmente retorna uma matriz que contém os endereços MetaMask atualmente conectados ao nosso dapp.

Para ver essa função em ação, vamos chamá-la na função useEffect do nosso componente Minter.js.

Como fizemos para connectWallet, devemos importar essa função do nosso arquivo interact.js para o Minter.js, assim:

1import { useEffect, useState } from "react"
2import {
3 connectWallet,
4 getCurrentWalletConnected, //import here
5} from "./utils/interact.js"

Agora, simplesmente a chamamos em nossa função useEffect:

1useEffect(async () => {
2 const { address, status } = await getCurrentWalletConnected()
3 setWallet(address)
4 setStatus(status)
5}, [])

Note que nós usamos a resposta da nossa chamada a getCurrentWalletConnected para atualizar nossa walletAddress e nossa variável de estado status.

Depois de adicionar este código, tente atualizar a janela do navegador. O botão deve dizer que você está conectado e mostrar uma visualização do endereço de sua carteira conectada - mesmo depois de atualizar!

Implementar addWalletListener

O passo final na configuração da nossa carteira dapp é implementar o ouvinte de carteira, para que nossa interface atualize quando o estado mudar, como quando o usuário desconecta ou troca de contas.

No seu arquivo Minter.js, adicione a função addWalletListener que se parece com o seguinte:

1function addWalletListener() {
2 if (window.ethereum) {
3 window.ethereum.on("accountsChanged", (accounts) => {
4 if (accounts.length > 0) {
5 setWallet(accounts[0])
6 setStatus("👆🏽 Write a message in the text-field above.")
7 } else {
8 setWallet("")
9 setStatus("🦊 Connect to MetaMask using the top right button.")
10 }
11 })
12 } else {
13 setStatus(
14 <p>
15 {" "}
16 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
17 You must install MetaMask, a virtual Ethereum wallet, in your browser.
18 </a>
19 </p>
20 )
21 }
22}
Exibir tudo

Vamos dividir rapidamente o que está acontecendo aqui:

  • Primeiro, nossa função verifica se o window.ethereum está habilitado no seu navegador (ex. MetaMask instalado).
    • Caso contrário, nós simplesmente configuramos a variável de estado status para uma JSX string que solicita o usuário instalar a MetaMask.
    • Se estiver habilitado, configuramos o ouvinte window.ethereum.on("accountsChanged") na linha 3 que houve mudança de estado na carteira MetaMask, inclusive quando o usuário conecta uma conta adicional ao dapp, troca de conta ou desconecta uma conta. Se houver pelo menos uma conta conectada, a variável de estado walletAddress é atualizada como a primeira conta no array accounts retornada pelo ouvinte. Caso contrário, walletAddress é definida como uma string vazia.

Finalmente, nós devemos chamá-la em nossa função useEffect:

1useEffect(async () => {
2 const { address, status } = await getCurrentWalletConnected()
3 setWallet(address)
4 setStatus(status)
5
6 addWalletListener()
7}, [])

E Voila! Concluímos a programação de toda a funcionalidade da nossa carteira! Agora que a nossa carteira está pronta, vamos descobrir como mintar nossa NFT!

Metadados NFT 101

Lembra dos metadados da NFT que acabamos de falar no Passo 0 deste tutorial - ele dá vida a uma NFT, permitindo que tenha propriedades, como um ativo digital, nome, descrição e outros atributos.

Vamos precisar configurar esse metadado como um objeto JSON e amarzena-lo, para que possamos passa-lo como parâmetro tokenURI quando chamarmos a função mintNFT do nosso contrato inteligente.

No campo texto "Link to Asset", "Name", "Description" inclui as diferentes propriedades dos metadados de nosso NFT. Nós vamos formatar estes metadados como um objeto JSON, mas há algumas opções para onde podemos armazenar este objeto JSON:

  • Poderíamos armazená-lo no blockchain Ethereum; no entanto, fazê-lo seria muito caro.
  • Nós poderíamos armazená-lo em um servidor centralizado, como AWS ou Firebase. Mas isso iria contra nossa ética de descentralização.
  • Poderíamos usar o IPFS, um protocolo descentralizado e uma rede peer-to-peer para armazenar e compartilhar dados em um sistema de arquivos distribuído. Como este protocolo é descentralizado e gratuito, essa é a melhor opção!

Para armazenar nossos metadados no IPFS, vamos usar Pinata(opens in a new tab), uma conveniente API IPFS e um conjunto de ferramentas. Na próxima etapa, vamos explicar exatamente como fazer isso!

(opens in a new tab)Use o Pinata para fixar seus metadados no IPFS

Se você não tem uma conta no Pinata(opens in a new tab), cadastre-se aqui(opens in a new tab) gratuitamente e conclua as etapas de confirmação do seu e-mail e conta.

Crie sua chave API do Pinata

Navegue para a páginahttps://pinata.cloud/keys(opens in a new tab), então selecione o botão "New Key" no topo da página, defina o Admin widget como ativado, e nomeie sua chave.

Será mostrado a você um pop-up com as informações da sua API. Certifique-se de colocar isto num lugar seguro.

Agora que a nossa chave está configurada, vamos adicioná-la ao nosso projeto para que possamos usá-la.

Criar o arquivo .env

Podemos armazenar com segurança nossa chave e segredo do Pinata em um arquivo de ambiente. Vamos instalar o pacote dotenv(opens in a new tab) no diretório do seu projeto.

Abra uma nova aba no seu terminal (separado do terminal executando o local host) e certifique-se de estar na pasta minter-starter-files, então execute o seguinte comando no seu terminal:

1npm install dotenv --save

Em seguida, crie um arquivo .env no diretório raiz dos seus minter-starter-files inserindo o seguinte na sua linha de comando:

1vim.env

Isto abrirá seu arquivo .env no formato vim (um editor de texto). Para salvar, aperte "esc" + ":" + "q" no seu teclado nesta ordem.

Em seguida, no VSCode, navegue até o seu arquivo .env e adicione sua chave de API Pinata e sua API secreta, assim:

1REACT_APP_PINATA_KEY = <pinata-api-key>
2REACT_APP_PINATA_SECRET = <pinata-api-secret>

Salve o arquivo e então você estará pronto para começar a escrever a função de enviar seus metadados JSON para IPFS!

(opens in a new tab)Implementar pinJSONToIPFS

Felizmente para nós, a Pinata tem uma API especificamente para carregar dados JSON para o IPFS(opens in a new tab) e um JavaScript conveniente com axios de exemplo que podemos usar, com algumas pequenas modificações.

Na sua pasta utils, vamos criar outro arquivo chamado pinata.js e então importar nossa chave Pinata do arquivo .env assim:

1require("dotenv").config()
2const key = process.env.REACT_APP_PINATA_KEY
3const secret = process.env.REACT_APP_PINATA_SECRET

Em seguida, cole o código adicional abaixo no seu arquivo pinata.js. Não se preocupe, nós iremos clarificar o que tudo isso significa!

1require("dotenv").config()
2const key = process.env.REACT_APP_PINATA_KEY
3const secret = process.env.REACT_APP_PINATA_SECRET
4
5const axios = require("axios")
6
7export const pinJSONToIPFS = async (JSONBody) => {
8 const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`
9 //making axios POST request to Pinata ⬇️
10 return axios
11 .post(url, JSONBody, {
12 headers: {
13 pinata_api_key: key,
14 pinata_secret_api_key: secret,
15 },
16 })
17 .then(function (response) {
18 return {
19 success: true,
20 pinataUrl:
21 "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,
22 }
23 })
24 .catch(function (error) {
25 console.log(error)
26 return {
27 success: false,
28 message: error.message,
29 }
30 })
31}
Exibir tudo

Então, o que esse código faz exatamente?

Primeiro, importa axios(opens in a new tab), a um cliente HTTP baseado em promessas para o navegador e node.js, que utilizaremos para fazer um pedido a Pinata.

Em seguida, temos nossa função assíncrona pinJSONToIPFS, que recebe um JSONBody como sua entrada e a chave e senha do API Pinata em seu cabeçalho, tudo para fazer uma solicitação POST para sua API pinJSONToIPFS.

  • Se esta solicitação POST for bem sucedida, então nossa função retorna um objeto JSON com o valor booleano sucess como verdadeiro e a pinataUrl onde nossos metadados foram fixados. Nós usaremos a pinataUrl retornada, como entrada na tokenURI para a função mint do nosso contrato inteligente.
  • Se esta solicitação POST falhar, então, nossa função retorna um objeto JSON com o booleano success como falso e uma message que transmite nosso erro.

Assim como na nossa função connectWalletretorna tipos, estamos retornando objetos JSON para que possamos usar seus parâmetros para atualizar nossas variáveis de estado e nossa interface de usuário.

Carregar seu contrato inteligente

Agora que temos uma maneira de enviar nossos metadados NFT para IPFS através de nossa função de pinJSONToIPFS, vamos precisar de uma forma de carregar uma instância do nosso contrato inteligente para que possamos chamar a função mintNFT.

Como mencionado anteriormente, neste tutorial usaremos este é um contrato inteligente NFT existente(opens in a new tab); no entanto, se você quer aprender como o fizemos ou como fazer um você mesmo, é altamente recomendável que você confira nosso outro tutorial, "Como criar uma NFT.(opens in a new tab).

O contrato ABI

Se você examinar de perto nossos arquivos, você notará que no nosso diretório src, há um arquivo contract-abi.json. Um ABI é necessário para especificar qual função um contrato irá invocar, como também garantir que a função retornará dados no formato que você espera.

Também precisaremos de uma chave API Alchemy e da API Alchemy Web3 para conectar ao blockchain Ethereum e carregar o nosso contrato inteligente.

Crie a sua chave API Alchemy

Se ainda não tiver uma conta na Alchemy, você pode se cadastrar gratuitamente neste link(opens in a new tab)

Assim que criar uma conta na Alchemy, você pode gerar uma chave de API criando um "app". Isso nos permitirá fazer solicitações à rede de testes Ropsten.

Navegue até a pagina "Create App" no seu "Dashboard da Alchemy", passe o cursor sob "Apps" na barra de navegação e clique em “Create App”.

Nomeie seu aplicativo; nós escolhemos "Minha primeira NFT!", faça uma breve descrição, selecione "Staging" para o ambiente (usado para a contabilidade do seu ‘app’) e escolha "Ropsten" para sua rede.

Clique em "Create App", e é isso e tudo! Seu app deveria aparecer na tabela abaixo.

Incrível agora que criamos a nossa URL de API Alchemy HTTP, copie-a para a sua área de transferência...

…e então vamos adicioná-lo ao nosso arquivo .env. Ao todo, seu arquivo .env deve se parecer com isto:

1REACT_APP_PINATA_KEY = <pinata-key>
2REACT_APP_PINATA_SECRET = <pinata-secret>
3REACT_APP_ALCHEMY_KEY = https://eth-ropsten.alchemyapi.io/v2/<alchemy-key>

Agora que temos nosso contrato ABI e nossa chave API do Alchemy, estamos prontos para carregar o nosso contrato inteligente usando Alchemy Web3(opens in a new tab).

Configure seu Alchemy Web3 endpoint e contrato

Primeiro, se você ainda não tiver, você precisará instalar Alchemy Web3(opens in a new tab) navegando até o diretório home: nft-minter-tutorial no terminal:

1cd ..
2yarn add @alch/alchemy-web3

Em seguida, voltaremos para o nosso arquivo interact.js. No topo do arquivo, adicione o seguinte código para importar a chave de Alchemy do seu arquivo .env e configure seu Alchemy Web3 endpoint:

1require("dotenv").config()
2const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
4const web3 = createAlchemyWeb3(alchemyKey)

Alchemy Web3(opens in a new tab) é um invólucro em torno do Web3.js(opens in a new tab), fornecendo métodos aprimorados da API e outros benefícios cruciais para tornar a sua vida de desenvolvedor da Web3 mais fácil. Ele foi projetado para exigir uma configuração mínima, para que você possa começar a usá-la no seu aplicativo imediatamente!

Em seguida, vamos adicionar nosso contrato ABI e endereço do contrato ao nosso arquivo.

1require("dotenv").config()
2const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
4const web3 = createAlchemyWeb3(alchemyKey)
5
6const contractABI = require("../contract-abi.json")
7const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE"

Assim que tivermos ambas as coisas, estaremos prontos para começar a codificar a nossa função "mint"!

Implementar a função mintNFT

Dentro do seu arquivo interact.js, vamos definir nossa função, mintNFT, que deliberadamente vai criar nossa NFT.

Porque vamos fazer numerosas chamadas assíncronas (para o Pinata fixar nossos metadados para IPFS, Alchemy Web3 para carregar o nosso contrato inteligente, e MetaMask para assinar nossas transações), nossa função também será assíncrona.

As três entradas para nossa função serão a url do nosso ativo digital, namee description. Adicione a seguinte assinatura da função abaixo da função connectWallet:

1export const mintNFT = async (url, name, description) => {}

Manipulação de erros de script

Naturalmente, faz sentido ter algum tipo de tratamento de erro de entrada no início da função, então vamos sair desta função se nossos parâmetros de entrada não estiverem corretos. Dentro da nossa função, vamos adicionar o seguinte código:

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9}
Exibir tudo

Essencialmente, se algum dos parâmetros de entrada for uma string vazia, então retornamos um objeto JSON onde o valor booleano success é falso, e a string status repassa que todos os campos na nossa UI precisam estar completos.

(opens in a new tab)Carregar os metadados para o IPFS

Assim que soubermos que nossos metadados estão formatados corretamente, o próximo passo é envolvê-lo em um objeto JSON e enviá-lo para IPFS através do pinJSONToIPFS que escrevemos!

Para fazer isso, precisamos primeiro importar a função pinJSONToIPFS para nosso arquivo interact.js. No topo do interact.js, vamos adicionar:

1import { pinJSONToIPFS } from "./pinata.js"

Lembre-se que pinJSONToIPFS recebe um corpo JSON. Então, antes de fazer a chamada, precisaremos formatar a nossa url, namee description parâmetros em um objeto JSON.

Vamos atualizar nosso código para criar um objeto JSON chamado metadada e então fazer uma chamada para pinJSONToIPFS com este parâmetro metadada:

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9
10 //make metadata
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //make pinata call
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 Something went wrong while uploading your tokenURI.",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25}
Exibir tudo

Note, nós armazenamos a resposta de nossa chamada para pinJSONToIPFS(metadada) no objeto pinataResponse. Então, analisamos esse objeto para quaisquer erros.

Se houver um erro, nós retornamos um objeto JSON onde o sucess booleano é falso e nossa string status relata que nossa chamada falhou. Caso contrário, nós extraímos a pinataURL da pinataResponse e armazenamos como nossa variável tokenURI.

Agora é hora de carregar o nosso contrato inteligente usando a API da Alchemy Web3 que inicializamos no topo do nosso arquivo. Adicione a seguinte linha de código na parte inferior da função mintNFT para definir o contrato na window.contract variável global:

1window.contract = await new web3.eth.Contract(contractABI, contractAddress)

A última coisa a adicionar em nossa função mintNFT é a nossa transação Ethereum:

1//set up your Ethereum transaction
2const transactionParameters = {
3 to: contractAddress, // Required except during contract publications.
4 from: window.ethereum.selectedAddress, // must match user's active address.
5 data: window.contract.methods
6 .mintNFT(window.ethereum.selectedAddress, tokenURI)
7 .encodeABI(), //make call to NFT smart contract
8}
9
10//sign the transaction via MetaMask
11try {
12 const txHash = await window.ethereum.request({
13 method: "eth_sendTransaction",
14 params: [transactionParameters],
15 })
16 return {
17 success: true,
18 status:
19 "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
20 txHash,
21 }
22} catch (error) {
23 return {
24 success: false,
25 status: "😥 Something went wrong: " + error.message,
26 }
27}
Exibir tudo

Se você já está familiarizado com as transações na Ethereum, perceberá que a estrutura é bem parecida com a que você já viu.

  • Primeiro, nós configuramos nossos parâmetros de transações.
    • to especificar o endereço do destinatário (nosso contrato inteligente)
    • from especifica o signatário da transação (o endereço conectado ao MetaMask: window.ethereum.selectedAddress)
    • data contém a chamada para nosso contrato inteligente do método mintNFT, que recebe nossa tokenURI e o endereço da carteira do usuário, window.ethereum.selectedAddress como entradas
  • Então, faremos uma chamada para, window.ethereum.request, onde pedimos ao MetaMask para assinar a transação. Note que nessa solicitação, estamos especificando nosso método eth (eth_SentTransaction) e passando em nossos transactionParameters. Neste ponto, a MetaMask irá abrir no navegador e pedirá que o usuário assine ou rejeite a transação.
    • Se a transação for bem-sucedida, a função retornará um objeto JSON onde o booleano success é definido como verdadeiro e a string status pede que o usuário verifique o Etherscan para obter mais informações sobre sua transação.
    • Se a transação falhar, a função retornará um objeto JSON onde o booleano success é definido como falso, status string retransmite a mensagem de erro.

Ao todo, nossa função mintNFT deve-se parecer com isto:

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9
10 //make metadata
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //pinata pin request
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 Something went wrong while uploading your tokenURI.",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25
26 //load smart contract
27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();
28
29 //set up your Ethereum transaction
30 const transactionParameters = {
31 to: contractAddress, // Required except during contract publications.
32 from: window.ethereum.selectedAddress, // must match user's active address.
33 data: window.contract.methods
34 .mintNFT(window.ethereum.selectedAddress, tokenURI)
35 .encodeABI(), //make call to NFT smart contract
36 }
37
38 //sign transaction via MetaMask
39 try {
40 const txHash = await window.ethereum.request({
41 method: "eth_sendTransaction",
42 params: [transactionParameters],
43 })
44 return {
45 success: true,
46 status:
47 "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
48 txHash,
49 }
50 } catch (error) {
51 return {
52 success: false,
53 status: "😥 Something went wrong: " + error.message,
54 }
55 }
56}
Exibir tudo

Essa é uma função gigante! Agora, só precisamos conectar nossa função mintNFT com nosso componente Minter.js...

Conectando mintNFT ao nosso frontend Minter.js

Abra o seu arquivo Minter.js e atualize import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; a linha em cima deve ser:

1import {
2 connectWallet,
3 getCurrentWalletConnected,
4 mintNFT,
5} from "./utils/interact.js"

Finalmente, implemente a função onMintPressed para fazer a chamada(await call) para a função mintNFTimportada e atualize a variável de estado status para refletir se nossa transação foi bem-sucedida ou falhou:

1const onMintPressed = async () => {
2 const { status } = await mintNFT(url, name, description)
3 setStatus(status)
4}

Implante seu NFT a um site ao vivo

Pronto para deixar seu projeto ao vivo para que usuários interajam? Confira este tutorial(opens in a new tab) para implantar seu Minter em um site ao vivo.

Um último passo...

Leve o mundo blockchain numa enxurrada

Só uma brincadeira, você chegou ao fim do tutorial!

Para recapitular, construindo um minter NFT, você aprendeu com sucesso como:

  • Conectar ao MetaMask através do seu projeto frontend
  • Chamar métodos de contrato inteligentes no seu frontend
  • Assine transações usando MetaMask

Provavelmente você gostaria de poder exibir seu NFT na sua carteira — então certifique-se de conferir a parte Como ver seu NFT na sua carteira(opens in a new tab)!

E, como sempre, se você tiver alguma dúvida, estamos aqui para ajudar no Alchemy Discord(opens in a new tab). Mal podemos esperar para ver como você aplicará os conceitos deste tutorial em seus projetos futuros!

Última edição: @wackerow(opens in a new tab), 7 de maio de 2024

Este tutorial foi útil?