Tutorial de criação de uma NFT
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-filesnpm 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 variables2const [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áriostatus
- uma string que contém uma mensagem a ser exibida na parte inferior da interface do usuárioname
- uma string que armazena o nome da NFTdescrição
- uma string que armazena a descrição da NFTurl
- 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: implement3}, [])45const connectWalletPressed = async () => {6 //TODO: implement7}89const onMintPressed = async () => {10 //TODO: implement11}Exibir tudo
useEffect
(opens in a new tab) - este é um React Hook que é chamado depois que seu componente é renderizado. Porque ele tem uma array vazia[]
"prop" passada para ela (veja a linha 3), ela só será chamada na primeira renderização do componente. Aqui vamos chamar nosso ouvinte de carteira e outra função de carteira para atualizar nossa interface de usuário para refletir se uma carteira já está conectada.connectWalletPressed
- esta função será chamada para conectar a carteira MetaMask do usuário ao nosso dapp.onMintPressed
- esta função será chamada para mintar a NFT do usuário.
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 component2return (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>1415 <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 <input23 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 <input29 type="text"30 placeholder="e.g. My first NFT!"31 onChange={(event) => setName(event.target.value)}32 />33 <h2>✍️ Description: </h2>34 <input35 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 NFT42 </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 obj12 } 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 your27 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çãoconnectWallet
retornará um objeto JSON que contém o primeiroaddress
desta matriz (ver linha 9) e uma mensagemstatus
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 destatus
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";34const Minter = (props) => {56 //State variables7 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 your33 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 here5} 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 estadowalletAddress
é atualizada como a primeira conta no arrayaccounts
retornada pelo ouvinte. Caso contrário,walletAddress
é definida como uma string vazia.
- Caso contrário, nós simplesmente configuramos a variável de estado
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)56 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!
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!
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_KEY3const 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_KEY3const secret = process.env.REACT_APP_PINATA_SECRET45const axios = require("axios")67export const pinJSONToIPFS = async (JSONBody) => {8 const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`9 //making axios POST request to Pinata ⬇️10 return axios11 .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 apinataUrl
onde nossos metadados foram fixados. Nós usaremos apinataUrl
retornada, como entrada natokenURI
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 umamessage
que transmite nosso erro.
Assim como na nossa função connectWallet
retorna 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_KEY3const { 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_KEY3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")4const web3 = createAlchemyWeb3(alchemyKey)56const 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, name
e 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 handling3 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.
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
, name
e 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 handling3 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 }910 //make metadata11 const metadata = new Object()12 metadata.name = name13 metadata.image = url14 metadata.description = description1516 //make pinata call17 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.pinataUrl25}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 transaction2const transactionParameters = {3 to: contractAddress, // Required except during contract publications.4 from: window.ethereum.selectedAddress, // must match user's active address.5 data: window.contract.methods6 .mintNFT(window.ethereum.selectedAddress, tokenURI)7 .encodeABI(), //make call to NFT smart contract8}910//sign the transaction via MetaMask11try {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étodomintNFT
, que recebe nossatokenURI
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 nossostransactionParameters
. 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 stringstatus
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.
- Se a transação for bem-sucedida, a função retornará um objeto JSON onde o booleano
Ao todo, nossa função mintNFT
deve-se parecer com isto:
1export const mintNFT = async (url, name, description) => {2 //error handling3 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 }910 //make metadata11 const metadata = new Object()12 metadata.name = name13 metadata.image = url14 metadata.description = description1516 //pinata pin request17 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.pinataUrl2526 //load smart contract27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();2829 //set up your Ethereum transaction30 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.methods34 .mintNFT(window.ethereum.selectedAddress, tokenURI)35 .encodeABI(), //make call to NFT smart contract36 }3738 //sign transaction via MetaMask39 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 mintNFT
importada 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