Tutorial de Cunhador de NFT
Um dos maiores desafios para desenvolvedores com experiência em Web2 é descobrir como conectar seu contrato inteligente a um projeto frontend e interagir com ele.
Ao construir um cunhador de NFT — uma interface de usuário (UI) simples onde você pode inserir um link para o seu ativo digital, um título e uma descrição — você aprenderá a:
- Conectar-se à MetaMask através do seu projeto frontend
- Chamar métodos de contrato inteligente a partir do seu frontend
- Assinar transações usando a MetaMask
Neste tutorial, usaremos o React (opens in a new tab) como nosso framework frontend. Como este tutorial é focado principalmente no desenvolvimento Web3, não passaremos muito tempo detalhando os fundamentos do React. Em vez disso, focaremos em trazer funcionalidade ao nosso projeto.
Como pré-requisito, você deve ter um entendimento de nível iniciante sobre React — saber como funcionam componentes, props, useState/useEffect e chamadas básicas de função. Se você nunca ouviu falar de nenhum desses termos antes, pode ser interessante conferir este tutorial de Introdução ao React (opens in a new tab). Para os que aprendem melhor visualmente, recomendamos fortemente esta excelente série de vídeos Full Modern React Tutorial (opens in a new tab) do Net Ninja.
E se você ainda não tem, definitivamente precisará de uma conta na Alchemy para concluir este tutorial, bem como para construir qualquer coisa na blockchain. Inscreva-se para uma conta gratuita aqui (opens in a new tab).
Sem mais delongas, vamos começar!
O básico sobre a criação de NFTs
Antes mesmo de começarmos a olhar para qualquer código, é importante entender como funciona a criação de um NFT. Isso envolve duas etapas:
Implantar um contrato inteligente de NFT na blockchain Ethereum
A maior diferença entre os dois padrões de contrato inteligente de NFT é que o ERC-1155 é um padrão de múltiplos tokens e inclui funcionalidade em lote, enquanto o ERC-721 é um padrão de token único e, portanto, suporta apenas a transferência de um token por vez.
Chame a função de cunhagem
Geralmente, essa função de cunhagem exige que você passe duas variáveis como parâmetros: primeiro o recipient, que especifica o endereço que receberá seu NFT recém-cunhado, e segundo o tokenURI do NFT, uma string que resolve para um documento JSON que descreve os metadados do NFT.
Os metadados de um NFT são realmente o que lhe dá vida, permitindo que ele tenha propriedades, como um nome, descrição, imagem (ou ativo digital diferente) e outros atributos. Aqui está um exemplo de um tokenURI (opens in a new tab), que contém os metadados de um NFT.
Neste tutorial, vamos focar na parte 2, chamar uma função de cunhagem de contrato inteligente de NFT usando nossa UI em React.
Você precisará de um contrato inteligente de NFT ERC-721 implantado em uma rede de teste suportada, como a Sepolia. Se você quiser implantar um você mesmo, recomendamos o guia da Alchemy sobre como implantar um contrato inteligente na Sepolia (opens in a new tab).
Legal, agora que entendemos como funciona a criação de um NFT, vamos clonar nossos arquivos iniciais!
Clonar os arquivos iniciais
Primeiro, vá para o repositório do GitHub nft-minter-tutorial (opens in a new tab) para obter os arquivos iniciais deste projeto. Clone este repositório em seu ambiente local.
Ao abrir este repositório nft-minter-tutorial clonado, você notará que ele contém duas pastas: minter-starter-files e nft-minter.
minter-starter-filescontém os arquivos iniciais (essencialmente a UI em React) para este projeto. Neste tutorial, trabalharemos neste diretório, enquanto você aprende a dar vida a essa UI conectando-a à sua carteira Ethereum e a um contrato inteligente de NFT.nft-mintercontém todo o tutorial concluído e está lá para você como uma referência caso você fique travado.
Em seguida, abra sua cópia de minter-starter-files no seu editor de código e navegue até a pasta src.
Todo o código que escreveremos ficará na pasta src. Editaremos o componente Minter.js e escreveremos arquivos JavaScript adicionais para dar funcionalidade Web3 ao nosso projeto.
Passo 2: Verifique nossos arquivos iniciais
Antes de começarmos a programar, é importante verificar o que já nos foi fornecido nos arquivos iniciais.
Execute seu projeto React
Vamos começar executando o projeto React em nosso navegador. A beleza do React é que, uma vez que temos nosso projeto rodando no navegador, quaisquer alterações que salvarmos serão atualizadas ao vivo no navegador.
Para executar o projeto, navegue até o diretório raiz da pasta minter-starter-files e execute npm install no seu terminal para instalar as dependências do projeto:
cd minter-starter-files
npm install
Assim que a instalação terminar, execute npm start no seu terminal:
npm start
Fazer isso deve abrir http://localhost:3000/ (opens in a new tab) no seu navegador, onde você verá o frontend do nosso projeto. Ele deve consistir em 3 campos: um lugar para inserir um link para o ativo do seu NFT, inserir o nome do seu NFT e fornecer uma descrição.
Se você tentar clicar nos botões "Connect Wallet" (Conectar Carteira) ou "Mint NFT" (Cunhar NFT), 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 para a pasta src no nosso editor e abrir o arquivo Minter.js. É super importante que entendamos tudo neste arquivo, pois é o principal componente React no qual trabalharemos.
No topo deste arquivo, temos nossas variáveis de estado que atualizaremos após eventos específicos.
//Variáveis de estado
const [walletAddress, setWallet] = useState("")
const [status, setStatus] = useState("")
const [name, setName] = useState("")
const [description, setDescription] = useState("")
const [url, setURL] = useState("")
Nunca ouviu falar de variáveis de estado ou hooks de estado do React? Confira esta (opens in a new tab) documentação.
Aqui está o que cada uma das variáveis representa:
walletAddress- uma string que armazena o endereço da carteira do usuáriostatus- uma string que contém uma mensagem para exibir na parte inferior da UIname- uma string que armazena o nome do NFTdescription- uma string que armazena a descrição do NFTurl- uma string que é um link para o ativo digital do NFT
Após as variáveis de estado, você verá três funções não implementadas: useEffect, connectWalletPressed e onMintPressed. Você notará que todas essas funções são async, isso porque faremos chamadas de API assíncronas nelas! Seus nomes são epônimos com suas funcionalidades:
useEffect(async () => {
//TODO: implementararar
}, [])
const connectWalletPressed = async () => {
//TODO: implement
}
const onMintPressed = async () => {
//TODO: implement
}
useEffect(opens in a new tab) - este é um hook do React que é chamado após a renderização do seu componente. Como ele tem uma prop de array vazio[]passada para ele (veja a linha 3), ele só será chamado na primeira renderização do componente. Aqui chamaremos nosso ouvinte de carteira e outra função de carteira para atualizar nossa UI para refletir se uma carteira já está conectada.connectWalletPressed- esta função será chamada para conectar a carteira MetaMask do usuário ao nosso aplicativo descentralizado (dapp).onMintPressed- esta função será chamada para cunhar o NFT do usuário.
Perto do final deste arquivo, temos a UI do nosso componente. Se você analisar este código com cuidado, notará que atualizamos nossas variáveis de estado url, name e description 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.
//a UI do nosso componente
return (
<div className="Minter">
<button id="walletButton" onClick={connectWalletPressed}>
{walletAddress.length > 0 ? (
"Connected: " +
String(walletAddress).substring(0, 6) +
"..." +
String(walletAddress).substring(38)
) : (
<span>Connect Wallet</span>
)}
</button>
<br></br>
<h1 id="title">🧙♂️ Alchemy NFT Minter</h1>
<p>
Simply add your asset's link, name, and description, then press "Mint."
</p>
<form>
<h2>🖼 Link to asset: </h2>
<input
type="text"
placeholder="e.g., https://gateway.pinata.cloud/ipfs/<hash>"
onChange={(event) => setURL(event.target.value)}
/>
<h2>🤔 Name: </h2>
<input
type="text"
placeholder="e.g., My first NFT!"
onChange={(event) => setName(event.target.value)}
/>
<h2>✍️ Description: </h2>
<input
type="text"
placeholder="e.g., Even cooler than cryptokitties ;)"
onChange={(event) => setDescription(event.target.value)}
/>
</form>
<button id="mintButton" onClick={onMintPressed}>
Mint NFT
</button>
<p id="status">{status}</p>
</div>
)
Finalmente, vamos abordar onde este componente Minter é adicionado.
Se você for ao arquivo App.js, que é o componente principal no React que atua como um contêiner para todos os outros componentes, verá que nosso componente Minter é injetado na linha 7.
Neste tutorial, editaremos apenas o Minter.js file e adicionaremos arquivos na nossa pasta src.
Agora que entendemos com o que estamos trabalhando, vamos configurar nossa carteira Ethereum!
Configure sua carteira Ethereum
Para que os usuários possam interagir com seu contrato inteligente, eles precisarão conectar sua carteira Ethereum ao seu dapp.
Baixar a MetaMask
Para este tutorial, usaremos a MetaMask, uma carteira virtual no navegador usada para gerenciar o endereço da sua conta Ethereum. Se você quiser entender mais sobre como as transações na Ethereum funcionam, confira esta página.
Você pode baixar e criar uma conta na MetaMask gratuitamente aqui (opens in a new tab). Ao criar uma conta, ou se você já tiver uma, certifique-se de mudar para uma rede de teste suportada, como a Sepolia (para que não estejamos lidando com dinheiro real).
Adicionar ether de um faucet
Para cunhar nossos NFTs (ou assinar quaisquer transações na blockchain Ethereum), precisaremos de algum ETH falso. Para obter ETH de rede de teste, use um faucet mantido, como o faucet Sepolia da Alchemy (opens in a new tab) e insira o endereço da sua conta Sepolia. Você deve ver o ETH na sua conta MetaMask logo em seguida!
Verifique seu saldo
Para confirmar se o nosso saldo está lá, vamos fazer uma solicitação eth_getBalance (opens in a new tab) usando a ferramenta de sandbox da Alchemy (opens in a new tab). Isso retornará a quantidade de ETH na nossa carteira. Depois de inserir o endereço da sua conta MetaMask e clicar em “Send Request”, você deverá ver uma resposta como esta:
{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}
NOTA: Este resultado está em Wei, não em ETH. Wei é usado como a menor denominação de ether. A conversão de Wei para ETH é: 1 ETH = 10¹⁸ Wei. Portanto, se convertermos 0xde0b6b3a7640000 para decimal, obteremos 1*10¹⁸, o que equivale a 1 ETH.
Ufa! Nosso dinheiro falso está todo lá!
Conecte a MetaMask à sua UI
Agora que nossa carteira MetaMask está configurada, vamos conectar nosso dapp a ela!
Como queremos seguir o paradigma MVC (opens in a new tab), vamos criar um arquivo separado que contém nossas funções para gerenciar a lógica, os dados e as regras do nosso dapp, e então passar essas funções para o nosso frontend (nosso componente Minter.js).
A função connectWallet
Para fazer isso, vamos criar uma nova pasta chamada utils no seu diretório src e adicionar um arquivo chamado interact.js dentro dela, que conterá todas as nossas funções de interação com a carteira e o contrato inteligente.
No nosso arquivo interact.js, escreveremos uma função connectWallet, que então importaremos e chamaremos no nosso componente Minter.js.
No seu arquivo interact.js, adicione o seguinte:
export const connectWallet = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_requestAccounts",
})
const obj = {
status: "👆🏽 Write a message in the text-field above.",
address: addressArray[0],
}
return obj
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
}
}
} else {
return {
address: "",
status: (
<span>
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
}
}
}
Vamos detalhar o que este código faz:
Primeiro, nossa função verifica se o window.ethereum está ativado no seu navegador.
O window.ethereum é uma API global injetada pela MetaMask e outros provedores de carteira que permite que sites solicitem as contas Ethereum dos usuários. Se aprovado, ele pode ler dados das blockchains às quais 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 mais informações!
Se o window.ethereum não estiver presente, isso significa que a MetaMask não está instalada. Isso resulta no retorno de um objeto JSON, onde o address retornado é uma string vazia, e o objeto JSX status informa que o usuário deve instalar a MetaMask.
A maioria das funções que escreveremos retornará objetos JSON que podemos usar para atualizar nossas variáveis de estado e a UI.
Agora, se o window.ethereum estiver presente, é aí que as coisas ficam interessantes.
Usando um bloco try/catch, tentaremos nos conectar à MetaMask chamando window.ethereum.request({ method: "eth_requestAccounts" }); (opens in a new tab). Chamar esta função abrirá a MetaMask no navegador, onde o usuário será solicitado a conectar sua carteira ao seu dapp.
- Se o usuário optar por se conectar,
method: "eth_requestAccounts"retornará um array que contém todos os endereços de conta do usuário que estão conectados ao dapp. No geral, nossa funçãoconnectWalletretornará um objeto JSON que contém o primeiroaddressneste array (veja a linha 9) e umastatus(mensagem) que solicita ao usuário que escreva uma mensagem para o contrato inteligente. - Se o usuário rejeitar a conexão, o objeto JSON conterá uma string vazia para o
addressretornado e umastatus(mensagem) que reflete que o usuário rejeitou a conexão.
Adicione a função connectWallet ao seu componente de 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 nosso arquivo Minter.js adicionando import { connectWallet } from "./utils/interact.js"; no topo do arquivo Minter.js. Suas primeiras 11 linhas de Minter.js agora devem ficar assim:
import { useEffect, useState } from "react";
import { connectWallet } from "./utils/interact.js";
const Minter = (props) => {
//Variáveis de estado
const [walletAddress, setWallet] = useState("");
const [status, setStatus] = useState("");
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [url, setURL] = useState("");
Então, dentro da nossa função connectWalletPressed, chamaremos nossa função connectWallet importada, assim:
const connectWalletPressed = async () => {
const walletResponse = await connectWallet()
setStatus(walletResponse.status)
setWallet(walletResponse.address)
}
Notou como a maior parte da nossa funcionalidade é abstraída do nosso componente Minter.js a partir do arquivo interact.js? Isso é para estarmos em conformidade com o paradigma M-V-C!
Em connectWalletPressed, simplesmente fazemos uma chamada await para nossa função connectWallet importada e, usando sua resposta, atualizamos nossas variáveis status e walletAddress por meio de seus hooks de estado.
Agora, vamos salvar ambos os arquivos Minter.js e interact.js e testar nossa UI até agora.
Abra seu navegador em localhost:3000 e pressione o botão "Connect Wallet" (Conectar Carteira) no canto superior direito da página.
Se você tiver a MetaMask instalada, será solicitado a conectar sua carteira ao seu dapp. Aceite o convite para se conectar.
Você deve 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 solicitando a conectar a MetaMask, mesmo que ela já esteja conectada...
Mas não se preocupe! Podemos consertar isso facilmente implementando uma função chamada getCurrentWalletConnected, que verificará se um endereço já está conectado ao nosso dapp e atualizará nossa UI de acordo!
A função getCurrentWalletConnected
No seu arquivo interact.js, adicione a seguinte função getCurrentWalletConnected:
export const getCurrentWalletConnected = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_accounts",
})
if (addressArray.length > 0) {
return {
address: addressArray[0],
status: "👆🏽 Write a message in the text-field above.",
}
} else {
return {
address: "",
status: "🦊 Connect to MetaMask using the top right button.",
}
}
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
}
}
} else {
return {
address: "",
status: (
<span>
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
}
}
}
Este código é muito semelhante à função connectWallet que acabamos de escrever anteriormente.
A principal diferença é que, em vez de chamar o método eth_requestAccounts, que abre a MetaMask para o usuário conectar sua carteira, aqui chamamos o método eth_accounts, que simplesmente retorna um array contendo os endereços da MetaMask atualmente conectados ao nosso dapp.
Para ver esta função em ação, vamos chamá-la na função useEffect do nosso componente Minter.js.
Como fizemos para connectWallet, devemos importar esta função do nosso arquivo interact.js para o nosso arquivo Minter.js assim:
import { useEffect, useState } from "react"
import {
connectWallet,
getCurrentWalletConnected, //importar aqui
} from "./utils/interact.js"
Agora, simplesmente a chamamos na nossa função useEffect:
useEffect(async () => {
const { address, status } = await getCurrentWalletConnected()
setWallet(address)
setStatus(status)
}, [])
Observe que usamos a resposta da nossa chamada para getCurrentWalletConnected para atualizar nossas variáveis de estado walletAddress e status.
Depois de adicionar este código, tente atualizar a janela do nosso navegador. O botão deve dizer que você está conectado e mostrar uma prévia do endereço da sua carteira conectada - mesmo após a atualização!
Implemente addWalletListener
O passo final na configuração da carteira do nosso dapp é implementar o ouvinte de carteira para que nossa UI seja atualizada quando o estado da nossa carteira mudar, como quando o usuário se desconecta ou troca de conta.
No seu arquivo Minter.js, adicione uma função addWalletListener que se pareça com o seguinte:
function addWalletListener() {
if (window.ethereum) {
window.ethereum.on("accountsChanged", (accounts) => {
if (accounts.length > 0) {
setWallet(accounts[0])
setStatus("👆🏽 Write a message in the text-field above.")
} else {
setWallet("")
setStatus("🦊 Connect to MetaMask using the top right button.")
}
})
} else {
setStatus(
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your browser.
</a>
</p>
)
}
}
Vamos detalhar rapidamente o que está acontecendo aqui:
- Primeiro, nossa função verifica se o
window.ethereumestá ativado (ou seja, a MetaMask está instalada).- Se não estiver, simplesmente definimos nossa variável de estado
statuspara uma string JSX que solicita ao usuário que instale a MetaMask. - Se estiver ativado, configuramos o ouvinte
window.ethereum.on("accountsChanged")na linha 3 que escuta as mudanças de estado na carteira MetaMask, o que inclui 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 estadowalletAddressé atualizada como a primeira conta no arrayaccountsretornado pelo ouvinte. Caso contrário,walletAddressé definida como uma string vazia.
- Se não estiver, simplesmente definimos nossa variável de estado
Finalmente, devemos chamá-la na nossa função useEffect:
useEffect(async () => {
const { address, status } = await getCurrentWalletConnected()
setWallet(address)
setStatus(status)
addWalletListener()
}, [])
E voilà! Concluímos a programação de toda a funcionalidade da nossa carteira! Agora que nossa carteira está configurada, vamos descobrir como cunhar nosso NFT!
O básico sobre metadados de NFT
Então, lembra dos metadados de NFT sobre os quais acabamos de falar no Passo 0 deste tutorial — eles dão vida a um NFT, permitindo que ele tenha propriedades, como um ativo digital, nome, descrição e outros atributos.
Precisaremos configurar esses metadados como um objeto JSON e armazená-los, para que possamos passá-los como o parâmetro tokenURI ao chamar a função mintNFT do nosso contrato inteligente.
O texto nos campos "Link to Asset" (Link para o Ativo), "Name" (Nome) e "Description" (Descrição) compreenderá as diferentes propriedades dos metadados do nosso NFT. Formataremos esses metadados como um objeto JSON, mas há algumas opções de onde podemos armazenar esse objeto JSON:
- Poderíamos armazená-lo na blockchain Ethereum; no entanto, fazer isso seria muito caro.
- Poderíamos armazená-lo em um servidor centralizado, como AWS ou Firebase. Mas isso iria contra o nosso ethos de descentralização.
- Poderíamos usar o IPFS, um protocolo descentralizado e rede ponto a ponto para armazenar e compartilhar dados em um sistema de arquivos distribuído. Como este protocolo é descentralizado e gratuito, é a nossa melhor opção!
Para armazenar nossos metadados no IPFS, usaremos o Pinata (opens in a new tab), uma API e kit de ferramentas IPFS conveniente. No próximo passo, explicaremos exatamente como fazer isso!
Use o Pinata para fixar seus metadados no IPFS
Se você não tem uma conta no Pinata (opens in a new tab), inscreva-se para uma conta gratuita aqui (opens in a new tab) e conclua as etapas para verificar seu e-mail e conta.
Crie sua chave de API do Pinata
Navegue até a página https://pinata.cloud/keys (opens in a new tab), selecione o botão "New Key" (Nova Chave) na parte superior, defina o widget Admin como ativado e dê um nome à sua chave.
Você verá um pop-up com as informações da sua API. Certifique-se de guardar isso em um lugar seguro.
Agora que nossa chave está configurada, vamos adicioná-la ao nosso projeto para que possamos usá-la.
Crie um 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 (separada daquela que está executando o localhost) e certifique-se de estar na pasta minter-starter-files, depois execute o seguinte comando no seu terminal:
npm install dotenv --save
Em seguida, crie um arquivo .env no diretório raiz do seu minter-starter-files inserindo o seguinte na sua linha de comando:
vim.env
Isso abrirá seu arquivo .env no vim (um editor de texto). Para salvá-lo, pressione "esc" + ":" + "q" no seu teclado, nessa ordem.
Em seguida, no VSCode, navegue até o seu arquivo .env e adicione sua chave de API e segredo de API do Pinata a ele, assim:
REACT_APP_PINATA_KEY = <pinata-api-key>
REACT_APP_PINATA_SECRET = <pinata-api-secret>
Salve o arquivo e então você estará pronto para começar a escrever a função para fazer o upload dos seus metadados JSON para o IPFS!
Implemente pinJSONToIPFS
Felizmente para nós, o Pinata tem uma API especificamente para fazer upload de dados JSON para o IPFS (opens in a new tab) e um exemplo conveniente em JavaScript com axios que podemos usar, com algumas pequenas modificações.
Na sua pasta utils, vamos criar outro arquivo chamado pinata.js e então importar nosso segredo e chave do Pinata do arquivo .env assim:
require("dotenv").config()
const key = process.env.REACT_APP_PINATA_KEY
const secret = process.env.REACT_APP_PINATA_SECRET
Em seguida, cole o código adicional abaixo no seu arquivo pinata.js. Não se preocupe, vamos detalhar o que tudo significa!
require("dotenv").config()
const key = process.env.REACT_APP_PINATA_KEY
const secret = process.env.REACT_APP_PINATA_SECRET
const axios = require("axios")
export const pinJSONToIPFS = async (JSONBody) => {
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`
//fazendo requisição POST do axios para o Pinata ⬇️
return axios
.post(url, JSONBody, {
headers: {
pinata_api_key: key,
pinata_secret_api_key: secret,
},
})
.then(function (response) {
return {
success: true,
pinataUrl:
"https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,
}
})
.catch(function (error) {
console.log(error)
return {
success: false,
message: error.message,
}
})
}
Então, o que este código faz exatamente?
Primeiro, ele importa o axios (opens in a new tab), um cliente HTTP baseado em promises para o navegador e Node.js, que usaremos para fazer uma solicitação ao Pinata.
Em seguida, temos nossa função assíncrona pinJSONToIPFS, que recebe um JSONBody como entrada e a chave e o segredo da API do Pinata em seu cabeçalho, tudo para fazer uma solicitação POST para a API pinJSONToIPFS deles.
- Se esta solicitação POST for bem-sucedida, nossa função retornará um objeto JSON com o booleano
successcomo verdadeiro e opinataUrlonde nossos metadados foram fixados. Usaremos estepinataUrlretornado como a entradatokenURIpara a função de cunhagem do nosso contrato inteligente. - Se esta solicitação POST falhar, nossa função retornará um objeto JSON com o booleano
successcomo falso e uma stringmessageque transmite nosso erro.
Assim como com os tipos de retorno da nossa função connectWallet, estamos retornando objetos JSON para que possamos usar seus parâmetros para atualizar nossas variáveis de estado e a UI.
Carregue seu contrato inteligente
Agora que temos uma maneira de fazer o upload dos metadados do nosso NFT para o IPFS por meio da nossa função pinJSONToIPFS, precisaremos de uma maneira de carregar uma instância do nosso contrato inteligente para que possamos chamar sua função mintNFT.
Como mencionamos anteriormente, neste tutorial usaremos este contrato inteligente de NFT existente (opens in a new tab); no entanto, se você quiser aprender como o criamos, ou criar um você mesmo, recomendamos fortemente que confira nosso outro tutorial, "Como criar um NFT" (opens in a new tab).
A ABI do contrato
Se você examinou nossos arquivos de perto, deve ter notado que no nosso diretório src, há um arquivo contract-abi.json. Uma ABI é necessária para especificar qual função um contrato invocará, bem como para garantir que a função retornará dados no formato que você espera.
Também precisaremos de uma chave de API da Alchemy e da API Alchemy Web3 para nos conectarmos à blockchain Ethereum e carregarmos nosso contrato inteligente.
Crie sua chave de API da Alchemy
Se você ainda não tem uma conta na Alchemy, inscreva-se gratuitamente aqui. (opens in a new tab)
Depois de criar uma conta na Alchemy, você pode gerar uma chave de API criando um aplicativo. Isso nos permitirá fazer solicitações à rede de teste Sepolia.
Navegue até a página "Create App" (Criar Aplicativo) no seu Painel da Alchemy passando o mouse sobre "Apps" na barra de navegação e clicando em "Create App".
Dê um nome ao seu aplicativo (nós escolhemos "My First NFT!"), forneça uma breve descrição, selecione "Staging" para o Ambiente (Environment) usado para a organização do seu aplicativo e escolha "Sepolia" para a sua rede.
Clique em "Create app" e pronto! Seu aplicativo deve aparecer na tabela abaixo.
Incrível, agora que criamos nossa URL de API HTTP da Alchemy, copie-a para a sua área de transferência...
…e então vamos adicioná-la ao nosso arquivo .env. No geral, seu arquivo .env deve ficar assim:
REACT_APP_PINATA_KEY = <pinata-key>
REACT_APP_PINATA_SECRET = <pinata-secret>
REACT_APP_ALCHEMY_KEY = https://eth-sepolia.g.alchemy.com/v2/<alchemy-key>
Agora que temos a ABI do nosso contrato e nossa chave de API da Alchemy, estamos prontos para carregar nosso contrato inteligente usando a Alchemy Web3 (opens in a new tab).
Configure seu endpoint e contrato da Alchemy Web3
Primeiro, se você ainda não a tem, precisará instalar a Alchemy Web3 (opens in a new tab) navegando até o diretório inicial: nft-minter-tutorial no terminal:
cd ..
npm install @alch/alchemy-web3
Em seguida, vamos voltar ao nosso arquivo interact.js. No topo do arquivo, adicione o seguinte código para importar sua chave da Alchemy do seu arquivo .env e configurar seu endpoint da Alchemy Web3:
require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)
A Alchemy Web3 (opens in a new tab) é um wrapper em torno da Web3.js (opens in a new tab), fornecendo métodos de API aprimorados e outros benefícios cruciais para facilitar sua vida como desenvolvedor Web3. Ela foi projetada para exigir configuração mínima para que você possa começar a usá-la no seu aplicativo imediatamente!
Em seguida, vamos adicionar a ABI do nosso contrato e o endereço do contrato ao nosso arquivo.
require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)
const contractABI = require("../contract-abi.json")
const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE"
Assim que tivermos ambos, estaremos prontos para começar a programar nossa função de cunhagem!
Implemente a função mintNFT
Dentro do seu arquivo interact.js, vamos definir nossa função, mintNFT, que epônimamente cunhará nosso NFT.
Como faremos inúmeras chamadas assíncronas (para o Pinata para fixar nossos metadados no IPFS, para a Alchemy Web3 para carregar nosso contrato inteligente e para a MetaMask para assinar nossas transações), nossa função também será assíncrona.
As três entradas para nossa função serão o url do nosso ativo digital, name e description. Adicione a seguinte assinatura de função abaixo da função connectWallet:
export const mintNFT = async (url, name, description) => {}
Tratamento de erros de entrada
Naturalmente, faz sentido ter algum tipo de tratamento de erros de entrada no início da função, para sairmos desta função se nossos parâmetros de entrada não estiverem corretos. Dentro da nossa função, vamos adicionar o seguinte código:
export const mintNFT = async (url, name, description) => {
//tratamento de erros
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
}
Essencialmente, se algum dos parâmetros de entrada for uma string vazia, retornamos um objeto JSON onde o booleano success é falso, e a string status transmite que todos os campos na nossa UI devem estar completos.
Faça o upload dos metadados para o IPFS
Assim que soubermos que nossos metadados estão formatados corretamente, o próximo passo é envolvê-los em um objeto JSON e fazer o upload para o IPFS por meio da função pinJSONToIPFS que escrevemos!
Para fazer isso, primeiro precisamos importar a função pinJSONToIPFS para o nosso arquivo interact.js. Bem no topo do interact.js, vamos adicionar:
import { pinJSONToIPFS } from "./pinata.js"
Lembre-se de que pinJSONToIPFS recebe um corpo JSON. Portanto, antes de fazermos uma chamada para ela, precisaremos formatar nossos parâmetros url, name e description em um objeto JSON.
Vamos atualizar nosso código para criar um objeto JSON chamado metadata e então fazer uma chamada para pinJSONToIPFS com este parâmetro metadata:
export const mintNFT = async (url, name, description) => {
//tratamento de erros
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
//criar metadados
const metadata = new Object()
metadata.name = name
metadata.image = url
metadata.description = description
//fazer chamada ao Pinata
const pinataResponse = await pinJSONToIPFS(metadata)
if (!pinataResponse.success) {
return {
success: false,
status: "😢 Something went wrong while uploading your tokenURI.",
}
}
const tokenURI = pinataResponse.pinataUrl
}
Observe que armazenamos a resposta da nossa chamada para pinJSONToIPFS(metadata) no objeto pinataResponse. Em seguida, analisamos este objeto em busca de quaisquer erros.
Se houver um erro, retornamos um objeto JSON onde o booleano success é falso e nossa string status transmite que nossa chamada falhou. Caso contrário, extraímos o pinataURL do pinataResponse e o armazenamos como nossa variável tokenURI.
Agora é hora de carregar nosso contrato inteligente usando a API 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 variável global window.contract:
window.contract = await new web3.eth.Contract(contractABI, contractAddress)
A última coisa a adicionar na nossa função mintNFT é a nossa transação Ethereum:
//configurar sua transação na Ethereum
const transactionParameters = {
to: contractAddress, // Obrigatório, exceto durante publicações de contrato.
from: window.ethereum.selectedAddress, // deve corresponder ao endereço ativo do usuário.
data: window.contract.methods
.mintNFT(window.ethereum.selectedAddress, tokenURI)
.encodeABI(), //fazer chamada ao contrato inteligente de NFT
}
//assinar a transação via MetaMask
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
})
return {
success: true,
status:
"✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
txHash,
}
} catch (error) {
return {
success: false,
status: "😥 Something went wrong: " + error.message,
}
}
Se você já está familiarizado com as transações da Ethereum, notará que a estrutura é bem semelhante ao que você já viu.
- Primeiro, configuramos os parâmetros das nossas transações.
toespecifica o endereço do destinatário (nosso contrato inteligente)fromespecifica o signatário da transação (o endereço do usuário conectado à MetaMask:window.ethereum.selectedAddress)datacontém a chamada para o métodomintNFTdo nosso contrato inteligente, que recebe nossotokenURIe o endereço da carteira do usuário,window.ethereum.selectedAddress, como entradas
- Em seguida, fazemos uma chamada await,
window.ethereum.request,onde pedimos à MetaMask para assinar a transação. Observe que, nesta solicitação, estamos especificando nosso método eth (eth_SentTransaction) e passando nossotransactionParameters. Neste ponto, a MetaMask será aberta no navegador e solicitará ao usuário que 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 stringstatussolicita ao usuário que 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 e a stringstatustransmite a mensagem de erro.
- Se a transação for bem-sucedida, a função retornará um objeto JSON onde o booleano
No geral, nossa função mintNFT deve ficar assim:
export const mintNFT = async (url, name, description) => {
//tratamento de erros
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
//criar metadados
const metadata = new Object()
metadata.name = name
metadata.image = url
metadata.description = description
//requisição de pin do Pinata
const pinataResponse = await pinJSONToIPFS(metadata)
if (!pinataResponse.success) {
return {
success: false,
status: "😢 Something went wrong while uploading your tokenURI.",
}
}
const tokenURI = pinataResponse.pinataUrl
//carregar contrato inteligente
window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();
//configurar sua transação na Ethereum
const transactionParameters = {
to: contractAddress, // Obrigatório, exceto durante publicações de contrato.
from: window.ethereum.selectedAddress, // deve corresponder ao endereço ativo do usuário.
data: window.contract.methods
.mintNFT(window.ethereum.selectedAddress, tokenURI)
.encodeABI(), //fazer chamada ao contrato inteligente de NFT
}
//assinar transação via MetaMask
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
})
return {
success: true,
status:
"✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
txHash,
}
} catch (error) {
return {
success: false,
status: "😥 Something went wrong: " + error.message,
}
}
}
Essa é uma função gigante! Agora, só precisamos conectar nossa função mintNFT ao nosso componente Minter.js...
Conecte mintNFT ao nosso frontend Minter.js
Abra seu arquivo Minter.js e atualize a linha import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; no topo para ser:
import {
connectWallet,
getCurrentWalletConnected,
mintNFT,
} from "./utils/interact.js"
Finalmente, implemente a função onMintPressed para fazer uma chamada await para sua função mintNFT importada e atualize a variável de estado status para refletir se nossa transação foi bem-sucedida ou falhou:
const onMintPressed = async () => {
const { status } = await mintNFT(url, name, description)
setStatus(status)
}
Implante seu NFT em um site ativo
Pronto para colocar seu projeto no ar para que os usuários interajam com ele? Confira a documentação de implantação do React (opens in a new tab) para implantar seu cunhador em um site ativo.
Um último passo...
Conquiste o mundo da blockchain
Brincadeira, você chegou ao fim do tutorial!
Para recapitular, ao construir um cunhador de NFT, você aprendeu com sucesso a:
- Conectar-se à MetaMask através do seu projeto frontend
- Chamar métodos de contrato inteligente a partir do seu frontend
- Assinar transações usando a MetaMask
Presumivelmente, você gostaria de poder exibir os NFTs cunhados por meio do seu dapp na sua carteira — então não deixe de conferir nosso tutorial rápido Como visualizar seu NFT na sua carteira!
E, como sempre, se você tiver alguma dúvida, estamos aqui para ajudar no Discord da Alchemy (opens in a new tab). Mal podemos esperar para ver como você aplicará os conceitos deste tutorial aos seus projetos futuros!