Ir al contenido principal

Tutorial de minteador de NFT

Solidity
NFT
Alchemy
contratos Inteligentes
frontend
Pinata
Intermedio
smudgil
6 de octubre de 2021
30 minuto leído

Uno de los mayores desafíos para los desarrolladores que vienen de un entorno web2 es averiguar cómo conectar su contrato inteligente a un proyecto de frontend e interactuar con él.

Creando un minteador de NFT —una interfaz de usuario sencilla donde puede introducir un enlace a su activo digital, un título y una descripción—, aprenderá como:

  • Establecer conexión con MetaMask a través del proyecto de frontend
  • Invocar métodos de contrato inteligente desde su frontend
  • Firmar transacciones usando MetaMask

En este tutorial, usaremos Reactopens in a new tab como nuestro marco de frontend. Debido a que este tutorial se centra principalmente en el desarrollo web3, no vamos a dedicar mucho tiempo a profundizar en los fundamentos de React. En su lugar, nos enfocaremos en aportar funcionalidad a nuestro proyecto.

Como requisito previo, debe tener una comprensión básica de React, es decir, cómo funcionan los componentes, props, useState/useEffect y las llamadas a funciones básicas. Si nunca ha oído hablar de ninguno de esos términos, puede que le interese consultar este tutorial de introducción a Reactopens in a new tab. Para los aprendices más visuales, recomendamos encarecidamente esta excelente serie de vídeos Full Modern React Tutorialopens in a new tab de Net Ninja.

Y si aún no lo ha hecho, definitivamente necesitará una cuenta de Alchemy para completar este tutorial, así como crear cualquier cosa en la cadena de bloques. Regístrese para obtener una cuenta gratuita aquíopens in a new tab.

Sin más preámbulos, ¡comencemos!

Creación de NFT 101

Antes de incluso empezar a experimentar con el código, es importante entender se qué se trata crear un NFT. Implica dos pasos:

Publicar un smart contract de NFT en la blockchain de Ethereum

La mayor diferencia entre los dos estándares de contrato inteligente de NFT es que ERC-1155 es un estándar multitoken e incluye funcionalidad por lotes, mientras que ERC-721 es un estándar de un solo token, por lo tanto solo permite la transferencia de un token a la vez.

Llamar a la función de acuñación

Normalmente, esta función de acuñación requiere que se pasen dos variables como parámetros: primero el recipient, que especifica la dirección que recibirá su NFT recién acuñado, y segundo el tokenURI del NFT, una cadena que se resuelve en un documento JSON que describe los metadatos del NFT.

Los metadatos de un NFT son realmente lo que le dan vida, permitiéndole tener propiedades como nombre, descripción, imagen (o un activo digital diferente) y otros atributos. Este es un ejemplo de un tokenURIopens in a new tab, que contiene los metadatos de un NFT.

En este tutorial, vamos a centrarnos en la parte 2, donde invocaremos la función de minteo de un contrato inteligente de NFT existente utilizando nuestra interfaz de usuario de React.

Este es un enlaceopens in a new tab al smart contract del NFT ERC-721 al que llamaremos en este tutorial. Si desea saber cómo lo hicimos, le recomendamos que consulte nuestro otro tutorial, "Cómo crear un NFT"opens in a new tab.

Bien, ahora que entendemos cómo es crear un NFT, ¡clonemos nuestros archivos de inicio!

Clonar los archivos de inicio

Primero, vaya al repositorio de GitHub de nft-minter-tutorialopens in a new tab para obtener los archivos de inicio para este proyecto. Clone este repositorio en su entorno local.

Cuando abra este repositorio nft-minter-tutorial clonado, verá que contiene dos carpetas: minter-starter-files y nft-minter.

  • minter-starter-files contiene los archivos de inicio (esencialmente la interfaz de usuario de React) para este proyecto. En este tutorial, estaremos trabajando en este directorio, mientras aprende cómo dar vida a esta interfaz de usuario conectándola a su monedero Ethereum y a un smart contract de NFT.
  • nft-minter contiene el tutorial completo y está ahí para usted como referencia si se atasca.

A continuación, abra su copia de minter-starter-files en su editor de código y, a continuación, navegue a su carpeta src.

Todo el código que escribiremos estará en la carpeta src. Estaremos editando el componente Minter.js y escribiendo archivos javascript adicionales para darle a nuestro proyecto la funcionalidad de Web3.

Paso 2: Eche un vistazo a nuestros archivos de inicio

Antes de empezar a programar, es importante comprobar lo que ya se nos ha proporcionado en los archivos de inicio.

Ponga en marcha su proyecto de React

Comencemos por ejecutar el proyecto React en nuestro navegador. La belleza de React es que, una vez que tenemos nuestro proyecto corriendo en el navegador, cualquier cambio que guardemos será actualizado en vivo en el navegador.

Para poner en marcha el proyecto, navegue hasta el directorio raíz de la carpeta minter-starter-files y ejecute npm install en su terminal para instalar las dependencias del proyecto:

cd minter-starter-files
npm install

Una vez que hayan terminado de instalarse, ejecute npm start en su terminal:

npm start

Al hacerlo, se debería abrir http://localhost:3000/opens in a new tab en el navegador, donde verá el frontend de nuestro proyecto. Debe constar de 3 campos: un lugar para introducir un enlace al activo de su NFT, uno para introducir el nombre del NFT y otro para proporcionar una descripción.

Si intenta hacer clic en los botones "Connect Wallet" o "Mint NFT", notará que no funcionan: es porque todavía necesitamos programar su funcionalidad. :)

El componente Minter.js

NOTA: ¡Asegúrese de que está en la carpeta minter-starter-files y no en la carpeta nft-minter!

Volvamos a la carpeta src de nuestro editor y abramos el archivo Minter.js. Es muy importante que entendamos todo en este archivo, ya que es el componente principal en React en el que trabajaremos.

En la parte superior de este archivo, tenemos nuestras variables de estado que actualizaremos después de eventos específicos.

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

¿Nunca escuchó hablar de variables de estado o hooks de estado de React? Consulte estosopens in a new tab documentos.

Esto es lo que representa cada una de las variables:

  • walletAddress: una cadena que almacena la dirección del monedero del usuario
  • status: una cadena que contiene un mensaje que se mostrará en la parte inferior de la interfaz de usuario
  • name: una cadena que almacena el nombre del NFT
  • description: una cadena que almacena la descripción del NFT
  • url: una cadena que es un enlace al activo digital del NFT

Después de las variables de estado, verá tres funciones no implementadas: useEffect, connectWalletPressed y onMintPressed. Notará que todas estas funciones son async, ¡esto se debe a que haremos llamadas asíncronas a la API en ellas! Sus nombres son epónimos con sus funcionalidades:

1useEffect(async () => {
2 //TODO: implementar
3}, [])
4
5const connectWalletPressed = async () => {
6 //TODO: implementar
7}
8
9const onMintPressed = async () => {
10 //TODO: implementar
11}
Mostrar todo
  • useEffectopens in a new tab: este es un React hook que se llama después de que su componente se renderice. Debido a que tiene un prop de matriz vacía [] pasado (ver línea 3), solo se llamará en la primera renderización del componente. Aquí llamaremos a nuestro oyente de billetera y otra función de billetera para actualizar nuestra interfaz de usuario a fin de reflejar si una billetera ya está conectada.
  • connectWalletPressed: esta función se llamará para conectar el monedero MetaMask del usuario a nuestra dapp.
  • onMintPressed: esta función se llamará para acuñar el NFT del usuario.

Cerca del final de este archivo, tenemos la interfaz de usuario de nuestro componente. Si examina este código con atención, se dará cuenta de que actualizamos nuestras variables de estado url, name y description cuando cambia la entrada en sus correspondientes campos de texto.

También verá que se llama a connectWalletPressed y a onMintPressed cuando se hace clic en los botones con los ID mintButton y walletButton respectivamente.

1//la UI de nuestro componente
2return (
3 <div className="Minter">
4 <button id="walletButton" onClick={connectWalletPressed}>
5 {walletAddress.length > 0 ? (
6 "Conectado: " +
7 String(walletAddress).substring(0, 6) +
8 "..." +
9 String(walletAddress).substring(38)
10 ) : (
11 <span>Conectar monedero</span>
12 )}
13 </button>
14
15 <br></br>
16 <h1 id="title">🧙‍♂️ Acuñador de NFT de Alchemy</h1>
17 <p>
18 Simplemente añada el enlace, el nombre y la descripción de su activo y pulse "Acuñar".
19 </p>
20 <form>
21 <h2>🖼 Enlace al activo: </h2>
22 <input
23 type="text"
24 placeholder="p. ej., https://gateway.pinata.cloud/ipfs/<hash>"
25 onChange={(event) => setURL(event.target.value)}
26 />
27 <h2>🤔 Nombre: </h2>
28 <input
29 type="text"
30 placeholder="p. ej., ¡Mi primer NFT!"
31 onChange={(event) => setName(event.target.value)}
32 />
33 <h2>✍️ Descripción: </h2>
34 <input
35 type="text"
36 placeholder="p. ej., Incluso mejor que los cryptokitties ;)"
37 onChange={(event) => setDescription(event.target.value)}
38 />
39 </form>
40 <button id="mintButton" onClick={onMintPressed}>
41 Acuñar NFT
42 </button>
43 <p id="status">{status}</p>
44</div>
45)
Mostrar todo

Finalmente, abordemos dónde se añade este componente Minter.

Si va al archivo App.js, que es el componente principal en React que actúa como contenedor de todos los demás componentes, verá que nuestro componente Minter se inyecta en la línea 7.

En este tutorial, solo editaremos el archivo Minter.js y añadiremos archivos en nuestra carpeta src.

Ahora que entendemos con qué estamos trabajando, configuremos nuestra billetera de Ethereum.

Configurar su monedero de Ethereum

Para que los usuarios puedan interactuar con su contrato inteligente, necesitarán conectar su billetera de Ethereum a su dapp.

Descargar MetaMask

Para este tutorial, usaremos Metamask, una cartera virtual en el navegador usada para manejar la dirección de su cuenta Ethereum. Si quiere saber más sobre cómo funcionan las transacciones en Ethereum, consulte esta página.

Puede descargar y crear una cuenta de MetaMask gratis aquíopens in a new tab. Cuando esté creando una cuenta, o si ya tiene una, asegúrese de cambiar a la "Red de prueba Ropsten" en la parte superior derecha (para no usar dinero real).

Añada ether desde un Faucet

Para mintear nuestros NFTs (o firmar cualquier transacción en la cadena de bloques de Ethereum), necesitaremos algo de Eth de prueba (de mentira). Para obtener Eth, puede ir al faucet de Ropstenopens in a new tab e introducir la dirección de su cuenta de Ropsten, y luego hacer clic en «Send Ropsten Eth». Debería ver el Eth en su cuenta de MetaMask poco después.

Compruebe su saldo

Para comprobar que nuestro saldo está ahí, hagamos una solicitud eth_getBalanceopens in a new tab utilizando la herramienta de composición de Alchemyopens in a new tab. Esto devolverá la cantidad de Eth en nuestra billetera. Después de introducir la dirección de su cuenta de Metamask y hacer clic en «Send Request», debería ver una respuesta como esta:

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

NOTA: Este resultado es en wei, no en eth. Wei se usa como la denominación más pequeña de Ether. La conversión de wei a eth es: 1 eth = 10¹⁸ wei. Entonces si convertimos 0xde0b6b3a7640000 a decimal, obtenemos 1*10¹⁸, que equivale a 1 eth.

¡Fiu! Nuestro dinero de prueba está ahí sin problemas.

Conecte MetaMask a su interfaz de usuario

Ahora que nuestra billetera de MetaMask está configurada, vamos a conectar nuestra dapp a ella.

Debido a que queremos suscribirnos al paradigma MVCopens in a new tab, vamos a crear un archivo separado que contenga nuestras funciones para gestionar la lógica, los datos y las reglas de nuestra dapp, y luego pasar esas funciones a nuestro frontend (nuestro componente Minter.js).

La función connectWallet

Para ello, vamos a crear una nueva carpeta llamada utils en su directorio src y a añadir un archivo llamado interact.js dentro de ella, que contendrá todas nuestras funciones de interacción con el monedero y el smart contract.

En nuestro archivo interact.js, escribiremos una función connectWallet, que luego importaremos y llamaremos en nuestro componente Minter.js.

En su archivo interact.js, añada lo siguiente:

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: "👆🏽 Escriba un mensaje en el campo de texto de arriba.",
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`}>
26 Debe instalar MetaMask, un monedero virtual de Ethereum, en su
27 navegador.
28 </a>
29 </p>
30 </span>
31 ),
32 }
33 }
34}
Mostrar todo

Analicemos lo que hace este código:

En primer lugar, nuestra función comprueba si window.ethereum está habilitado en su navegador.

window.ethereum es una API global inyectada por MetaMask y otros proveedores de monederos que permite a los sitios web solicitar las cuentas de Ethereum de los usuarios. En caso de aprobación, puede leer información de la cadena de bloques a la que el usuario se encuentra conectado y sugerir al usuario que firme mensajes y transacciones. ¡Consulte los documentos de MetaMaskopens in a new tab para obtener más información!

Si window.ethereum no está presente, significa que MetaMask no está instalado. Esto da como resultado la devolución de un objeto JSON, donde la address devuelta es una cadena vacía, y el objeto JSX status transmite que el usuario debe instalar MetaMask.

La mayoría de las funciones que escribamos devolverán objetos JSON que podremos utilizar para actualizar nuestras variables de estado y la interfaz de usuario.

Ahora, si window.ethereum está presente, es cuando las cosas se ponen interesantes.

Usando un bucle try/catch, intentaremos conectarnos a MetaMask llamando a window.ethereum.request({ method: "eth_requestAccounts" });opens in a new tab. La invocación de esta función abrirá MetaMask en el navegador, donde se le solicitará al usuario conectar su billetera a su dapp.

  • Si el usuario elige conectarse, method: "eth_requestAccounts" devolverá una matriz que contiene todas las direcciones de cuenta del usuario que están conectadas a la dapp. En conjunto, nuestra función connectWallet devolverá un objeto JSON que contiene la primera address de esta matriz (consulte la línea 9) y un mensaje de status que solicita al usuario que escriba un mensaje en el smart contract.
  • Si el usuario rechaza la conexión, el objeto JSON contendrá una cadena vacía para la address devuelta y un mensaje de status que reflejará que el usuario ha rechazado la conexión.

Añadir la función connectWallet a su componente de UI Minter.js

Ahora que hemos escrito esta función connectWallet, vamos a conectarla a nuestro componente Minter.js..

Primero, tendremos que importar nuestra función a nuestro archivo Minter.js añadiendo import { connectWallet } from "./utils/interact.js"; en la parte superior del archivo Minter.js. Sus primeras 11 líneas de Minter.js deberían tener este aspecto:

1import { useEffect, useState } from "react";
2import { connectWallet } from "./utils/interact.js";
3
4const Minter = (props) => {
5
6 //Variables de estado
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("");
Mostrar todo

A continuación, dentro de nuestra función connectWalletPressed, llamaremos a nuestra función importada connectWallet, de esta manera:

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

¿Se da cuenta de cómo la mayor parte de nuestra funcionalidad se abstrae de nuestro componente Minter.js desde el archivo interact.js? ¡Esto es así para cumplir con el paradigma M-V-C!

En connectWalletPressed, simplemente hacemos una llamada await a nuestra función connectWallet importada y, usando su respuesta, actualizamos nuestras variables status y walletAddress a través de sus state hooks.

Ahora, guardemos los dos archivos, Minter.js e interact.js, y probemos nuestra interfaz de usuario hasta ahora.

Abra el navegador en localhost:3000 y presione el botón "Connect Wallet" en la parte superior derecha de la página.

Si tiene MetaMask instalado, se le debería solicitar conectar su billetera a su dapp. Acepte la invitación para establecer la conexión.

Debería ver que el botón de billetera ahora muestra que su dirección está conectada.

A continuación, intente refrescar la página... esto es extraño. Nuestro botón de billetera nos está solicitando conectar MetaMask, aunque ya está conectado...

¡No se preocupe! Podemos solucionarlo fácilmente implementando una función llamada getCurrentWalletConnected, que comprobará si una dirección ya está conectada a nuestra dapp y actualizará nuestra interfaz de usuario en consecuencia.

La función getCurrentWalletConnected

En su archivo interact.js, añada la siguiente función getCurrentWalletConnected:

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: "👆🏽 Escriba un mensaje en el campo de texto de arriba.",
11 }
12 } else {
13 return {
14 address: "",
15 status: "🦊 Conéctese a MetaMask usando el botón superior derecho.",
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`}>
32 Debe instalar MetaMask, un monedero virtual de Ethereum, en su
33 navegador.
34 </a>
35 </p>
36 </span>
37 ),
38 }
39 }
40}
Mostrar todo

Este código es muy similar a la función connectWallet que acabamos de escribir.

La principal diferencia es que, en lugar de llamar al método eth_requestAccounts, que abre MetaMask para que el usuario conecte su monedero, aquí llamamos al método eth_accounts, que simplemente devuelve un array con las direcciones de MetaMask actualmente conectadas a nuestra dapp.

Para ver esta función en acción, llamémosla en la función useEffect de nuestro componente Minter.js.

Al igual que hicimos con connectWallet, debemos importar esta función desde nuestro archivo interact.js a nuestro archivo Minter.js de la siguiente manera:

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

Ahora, simplemente lo llamamos en nuestra función useEffect:

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

Observe que utilizamos la respuesta de nuestra llamada a getCurrentWalletConnected para actualizar nuestras variables de estado walletAddress y status.

Una vez agregado este código, pruebe actualizar la ventana del navegador. El botón debería decir que está conectado y mostrar una vista previa de la dirección de su billetera conectada, incluso después de actualizar la página.

Implementar addWalletListener

El último paso en la configuración de la billetera de dapp es implementar el oyente de billetera para que nuestra interfaz se actualice cuando el estado de la billetera cambie, por ejemplo, cuando el usuario se desconecte o cambie de cuenta.

En su archivo Minter.js, añada una función addWalletListener que tenga el siguiente aspecto:

1function addWalletListener() {
2 if (window.ethereum) {
3 window.ethereum.on("accountsChanged", (accounts) => {
4 if (accounts.length > 0) {
5 setWallet(accounts[0])
6 setStatus("👆🏽 Escriba un mensaje en el campo de texto de arriba.")
7 } else {
8 setWallet("")
9 setStatus("🦊 Conéctese a MetaMask utilizando el botón superior derecho.")
10 }
11 })
12 } else {
13 setStatus(
14 <p>
15 {" "}
16 🦊 <a target="_blank" href={`https://metamask.io/download`}>
17 Debe instalar MetaMask, un monedero virtual de Ethereum, en su navegador.
18 </a>
19 </p>
20 )
21 }
22}
Mostrar todo

Analicemos rápidamente lo que sucede aquí:

  • Primero, nuestra función comprueba si window.ethereum está habilitado (es decir, si MetaMask está instalado).
    • Si no lo está, simplemente establecemos nuestra variable de estado status en una cadena JSX que pide al usuario que instale MetaMask.
    • Si está habilitado, configuramos el detector window.ethereum.on("accountsChanged") en la línea 3 que escucha los cambios de estado en el monedero de MetaMask, que incluyen cuándo el usuario conecta una cuenta adicional a la dapp, cambia de cuenta o desconecta una cuenta. Si hay al menos una cuenta conectada, la variable de estado walletAddress se actualiza como la primera cuenta del array accounts devuelto por el detector. De lo contrario, walletAddress se establece como una cadena vacía.

Por último, debemos llamarlo en nuestra función useEffect:

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

¡Y voilá! ¡Hemos completado la programación de nuestra funcionalidad de billetera! ¡Ahora que nuestra billetera está configurada, veamos cómo mintear nuestro NFT!

Metadatos de NFT 101

Recordemos los metadatos de NFT sobre los que hablamos en el Paso 0 de este tutorial: dan vida a un NFT, permitiendo que tenga propiedades, tales como un activo digital, un nombre, una descripción y otros atributos.

Vamos a necesitar configurar estos metadatos como un objeto JSON y almacenarlos, para que podamos pasarlos como el parámetro tokenURI al llamar a la función mintNFT de nuestro smart contract.

El texto en los campos "Link to Asset", "Name" y "Description" comprenderá las diferentes propiedades de los metadatos de nuestro NFT. Formatearemos estos metadatos como un objeto JSON, pero hay un par de opciones para almacenar este objeto JSON:

  • Podríamos almacenarlo en la cadena de bloques de Ethereum; sin embargo, hacer esto puede ser muy caro.
  • Podríamos almacenarlo en un servidor centralizado, como AWS o Firebase. Pero esto iría en contra de nuestro espíritu de descentralización.
  • Podríamos usar IPFS, un protocolo descentralizado y red entre pares para almacenar y compartir datos en un sistema de archivos distribuido. Como este protocolo es descentralizado y gratuito, es nuestra mejor opción.

Para almacenar nuestros metadatos en IPFS, utilizaremos Pinataopens in a new tab, una API y un conjunto de herramientas de IPFS muy prácticos. En el siguiente paso, explicaremos exactamente cómo hacer esto.

Use Pinata para anclar sus metadatos a IPFS

Si no tiene una cuenta de Pinataopens in a new tab, regístrese para obtener una cuenta gratuita aquíopens in a new tab y complete los pasos para verificar su correo electrónico y su cuenta.

Crear su clave de API de Pinata

Navegue a la página https://pinata.cloud/keysopens in a new tab, luego seleccione el botón «New Key» en la parte superior, habilite el widget de administración y asigne un nombre a su clave.

Luego verá una ventana emergente con la información de su API. Asegúrese de guardar estos datos en un lugar seguro.

Ahora que nuestra clave está configurada, vamos a agregarla a nuestro proyecto para poder usarla.

Crear un archivo .env

Podemos almacenar de manera segura nuestra clave y secreto de Pinata en un archivo de entorno. Instalemos el paquete dotenvopens in a new tab en el directorio de su proyecto.

Abra una nueva pestaña en su terminal (separada de la que está ejecutando el host local) y asegúrese de que está en la carpeta minter-starter-files, luego ejecute el siguiente comando en su terminal:

1npm install dotenv --save

A continuación, cree un archivo .env en el directorio raíz de sus minter-starter-files introduciendo lo siguiente en su línea de comandos:

1vim.env

Esto abrirá su archivo .env en vim (un editor de texto). Para guardarlo, presione "esc" + ":" + "q" en el teclado en dicho orden.

A continuación, en VSCode, navegue hasta su archivo .env y añádale su clave API y su secreto API de Pinata, de la siguiente manera:

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

Guarde el archivo y luego estará listo para iniciar la escritura de la función para subir los metadatos JSON a IPFS.

opens in a new tabImplementar pinJSONToIPFS

Afortunadamente para nosotros, Pinata tiene una API específica para cargar datos JSON en IPFSopens in a new tab y un práctico ejemplo de JavaScript con axios que podemos usar, con algunas ligeras modificaciones.

En su carpeta utils, vamos a crear otro archivo llamado pinata.js y luego importar nuestro secreto y clave de Pinata desde el archivo .env de esta manera:

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

A continuación, pegue el código adicional de abajo en su archivo pinata.js. ¡No se preocupe, explicaremos lo que significa todo esto!

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 //haciendo una solicitud POST de axios a 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}
Mostrar todo

Entonces, ¿qué hace este código exactamente?

Primero, importa axiosopens in a new tab, un cliente HTTP basado en promesas para el navegador y node.js, que utilizaremos para hacer una solicitud a Pinata.

Luego tenemos nuestra función asíncrona pinJSONToIPFS, que toma un JSONBody como entrada y la clave y el secreto de la API de Pinata en su cabecera, todo para hacer una solicitud POST a su API pinJSONToIPFS.

  • Si esta solicitud POST tiene éxito, entonces nuestra función devuelve un objeto JSON con el booleano success como verdadero y el pinataUrl donde se anclaron nuestros metadatos. Utilizaremos este pinataUrl devuelto como entrada de tokenURI para la función de acuñación de nuestro smart contract.
  • Si esta solicitud POST falla, entonces nuestra función devuelve un objeto JSON con el booleano success como falso y una cadena de message que transmite nuestro error.

Al igual que con los tipos de retorno de nuestra función connectWallet, estamos devolviendo objetos JSON para poder usar sus parámetros para actualizar nuestras variables de estado y la interfaz de usuario.

Cargar su smart contract

Ahora que tenemos una forma de subir los metadatos de nuestro NFT a IPFS a través de nuestra función pinJSONToIPFS, vamos a necesitar una forma de cargar una instancia de nuestro smart contract para poder llamar a su función mintNFT.

Como mencionamos anteriormente, en este tutorial utilizaremos este smart contract de NFT existenteopens in a new tab; sin embargo, si le gustaría aprender cómo lo hicimos, o hacer uno usted mismo, le recomendamos encarecidamente que consulte nuestro otro tutorial, «Cómo crear un NFT»opens in a new tab.

El ABI del contrato

Si ha examinado nuestros archivos de cerca, habrá notado que en nuestro directorio src, hay un archivo contract-abi.json. Un ABI es necesario para especificar qué función invocará un contrato y para asegurar que la función devolverá datos en el formato esperado.

Tambien necesitaremos una clave de API de Alchemy y la API Web3 de Alchemy para establecer conexión con la cadena de bloques de Ethereum y cargar nuestro contrato inteligente.

Cree su clave de API de Alchemy

Si aún no tiene una cuenta de Alchemy, regístrese gratis aquíopens in a new tab.

Una vez que haya creado una cuenta de Alchemy, puede generar una clave de API creando una aplicación. Esto nos permitirá hacer solicitudes a la red de pruebas de Ropsten.

Vaya a la página "Create App" en el panel de Alchemy colocando el cursor sobre "Apps" en la barra de navegación y haciendo click en "Create App".

Asigne un nombre a la aplicación (nosotros elegimos "Mi primer NFT"), ofrezca una descripción, seleccione "Staging" para el Entorno usado para la contabilidad de la aplicación y seleccione "Ropsten" para la red.

¡Haga clic en «Crear app» y ya está! Su aplicación debería aparecer en la siguiente tabla.

¡Genial! Ahora que hemos creado la URL HTTP de la API de Alchemy, cópiela en el portapapeles...

... y luego vamos a añadirla a nuestro archivo .env. En definitiva el archivo .env debe lucir así:

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

Ahora que tenemos nuestro ABI del contrato y nuestra clave de API de Alchemy, estamos listos para cargar nuestro smart contract usando Alchemy Web3opens in a new tab.

Configurar su punto de conexión y contrato de Alchemy Web3

Primero, si aún no lo tiene, necesitará instalar Alchemy Web3opens in a new tab navegando al directorio principal: nft-minter-tutorial en la terminal:

1cd ..
2npm install @alch/alchemy-web3

A continuación, volvamos a nuestro archivo interact.js. En la parte superior del archivo, agregue el siguiente código para importar la clave de Alchemy desde el archivo .env y configurar su terminal de Alchemy Web3:

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

Alchemy Web3opens in a new tab es un envoltorio de Web3.jsopens in a new tab, que proporciona métodos de API mejorados y otros beneficios cruciales para facilitar su vida como desarrollador de web3. Se diseñó para requerir una configuración mínima, por lo que puede comenzar a usarla en su aplicación de inmediato.

Ahora, agreguemos nuestro ABI de contrato y la dirección del contrato a nuestro archivo.

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"

Una vez que tengamos ambos, estamos listos para comenzar con el código de nuestra función de minteo.

Implementar la función mintNFT

Dentro de su archivo interact.js, definamos nuestra función mintNFT, que epónimamente acuñará nuestro NFT.

Debido a que realizaremos varias llamadas asíncronas (a Pinata para fijar los metadatos a IPFS, a Alchemy Web3 para cargar nuestro contrato inteligente y a MetaMask para firmar las transacciones), nuestra función también será asíncrona.

Las tres entradas de nuestra función serán la url de nuestro activo digital, el name y la description. Añada la siguiente firma de función debajo de la función connectWallet:

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

Manejo de errores de entrada

Naturalmente, tiene sentido tener algún tipo de manejo de errores de entrada en el inicio de la función, por lo que podemos abandonar esta función si nuestros parámetros de entrada son incorrectos. Dentro de nuestra función, agreguemos el siguiente código:

1export const mintNFT = async (url, name, description) => {
2 //manejo de errores
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Asegúrese de que todos los campos estén completos antes de acuñar.",
7 }
8 }
9}
Mostrar todo

Esencialmente, si alguno de los parámetros de entrada es una cadena vacía, devolvemos un objeto JSON donde el booleano success es falso, y la cadena status transmite que todos los campos de nuestra interfaz de usuario deben estar completos.

opens in a new tabCargar los metadatos a IPFS

Una vez que sepamos que nuestros metadatos están formateados correctamente, el siguiente paso es envolverlos en un objeto JSON y subirlos a IPFS a través de la función pinJSONToIPFS que escribimos.

Para ello, primero tenemos que importar la función pinJSONToIPFS a nuestro archivo interact.js. En la parte superior de interact.js, añadamos:

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

Recuerde que pinJSONToIPFS toma un cuerpo JSON. Así que antes de llamarla, vamos a tener que formatear nuestros parámetros url, name y description en un objeto JSON.

Actualicemos nuestro código para crear un objeto JSON llamado metadata y luego hacer una llamada a pinJSONToIPFS con este parámetro metadata:

1export const mintNFT = async (url, name, description) => {
2 //manejo de errores
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Asegúrese de que todos los campos estén completos antes de acuñar.",
7 }
8 }
9
10 //crear metadatos
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //hacer llamada a pinata
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 Algo salió mal al subir su tokenURI.",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25}
Mostrar todo

Observe que almacenamos la respuesta de nuestra llamada a pinJSONToIPFS(metadata) en el objeto pinataResponse. Luego, analizamos este objeto para buscar errores.

Si hay un error, devolvemos un objeto JSON donde el booleano success es falso y nuestra cadena status transmite que nuestra llamada ha fallado. De lo contrario, extraemos la pinataURL de la pinataResponse y la almacenamos como nuestra variable tokenURI.

Ahora es momento de cargar nuestro contrato inteligente usando la API web3 de Alchemy que inicializamos en la parte superior de nuestro archivo. Añada la siguiente línea de código al final de la función mintNFT para establecer el contrato en la variable global window.contract:

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

Lo último que hay que añadir en nuestra función mintNFT es nuestra transacción Ethereum:

1//configure su transacción de Ethereum
2const transactionParameters = {
3 to: contractAddress, // Requerido excepto durante las publicaciones de contratos.
4 from: window.ethereum.selectedAddress, // debe coincidir con la dirección activa del usuario.
5 data: window.contract.methods
6 .mintNFT(window.ethereum.selectedAddress, tokenURI)
7 .encodeABI(), //hacer llamada al smart contract de NFT
8}
9
10//firmar la transacción a través de 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 "✅ Consulte su transacción en Etherscan: https://ropsten.etherscan.io/tx/" +
20 txHash,
21 }
22} catch (error) {
23 return {
24 success: false,
25 status: "😥 Algo salió mal: " + error.message,
26 }
27}
Mostrar todo

Si ya está familiarizado con las transacciones de Ethereum, notará que la estructura es muy similar a lo que ha visto.

  • Primero establecemos los parámetros de las transacciones.
    • to especifica la dirección del destinatario (nuestro smart contract)
    • from especifica el firmante de la transacción (la dirección del usuario conectada a MetaMask: window.ethereum.selectedAddress)
    • data contiene la llamada al método mintNFT de nuestro smart contract, que recibe nuestro tokenURI y la dirección del monedero del usuario, window.ethereum.selectedAddress, como entradas
  • Luego, hacemos una llamada await, window.ethereum.request, donde le pedimos a MetaMask que firme la transacción. Observe que, en esta solicitud, estamos especificando nuestro método eth (eth_SentTransaction) y pasando nuestros transactionParameters. En este punto, MetaMask se abrirá en el navegador y solicitará al usuario firmar o rechazar la transacción.
    • Si la transacción tiene éxito, la función devolverá un objeto JSON en el que el booleano success se establece en verdadero y la cadena status indica al usuario que consulte Etherscan para obtener más información sobre su transacción.
    • Si la transacción falla, la función devolverá un objeto JSON donde el booleano success se establece en falso, y la cadena status transmite el mensaje de error.

En conjunto, nuestra función mintNFT debería tener este aspecto:

1export const mintNFT = async (url, name, description) => {
2 //manejo de errores
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Asegúrese de que todos los campos estén completos antes de acuñar.",
7 }
8 }
9
10 //crear metadatos
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //solicitud de anclaje de pinata
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 Algo salió mal al subir su tokenURI.",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25
26 //cargar smart contract
27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();
28
29 //configure su transacción de Ethereum
30 const transactionParameters = {
31 to: contractAddress, // Requerido excepto durante las publicaciones de contratos.
32 from: window.ethereum.selectedAddress, // debe coincidir con la dirección activa del usuario.
33 data: window.contract.methods
34 .mintNFT(window.ethereum.selectedAddress, tokenURI)
35 .encodeABI(), //hacer llamada al smart contract de NFT
36 }
37
38 //firmar transacción a través de 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 "✅ Consulte su transacción en Etherscan: https://ropsten.etherscan.io/tx/" +
48 txHash,
49 }
50 } catch (error) {
51 return {
52 success: false,
53 status: "😥 Algo salió mal: " + error.message,
54 }
55 }
56}
Mostrar todo

¡Es una función gigante! Ahora, solo tenemos que conectar nuestra función mintNFT a nuestro componente Minter.js...

Conectar mintNFT a nuestro frontend Minter.js

Abra su archivo Minter.js y actualice la línea import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; en la parte superior para que sea:

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

Finalmente, implemente la función onMintPressed para hacer una llamada await a su función importada mintNFT y actualice la variable de estado status para reflejar si nuestra transacción tuvo éxito o falló:

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

Despliegue su NFT en un sitio web en vivo

¿Está preparado para implementar su proyecto para que los usuarios interactúen? Consulte este tutorialopens in a new tab para desplegar su acuñador en un sitio web en vivo.

Un último paso...

Conquiste el mundo de la blockchain

¡Solo bromeaba! Ha llegado al final del tutorial.

Para recapitular, al crear un minteador de NFT, ha aprendido correctamente cómo:

  • Establecer conexión con MetaMask a través del proyecto de frontend
  • Invocar métodos de contrato inteligente desde su frontend
  • Firmar transacciones usando MetaMask

Presumiblemente, le gustaría poder mostrar los NFT acuñados a través de su dapp en su monedero, ¡así que asegúrese de consultar nuestro rápido tutorial Cómo ver su NFT en su monederoopens in a new tab!

Y, como siempre, si tiene alguna pregunta, estamos aquí para ayudarle en el Discord de Alchemyopens in a new tab. Estamos ansiosos de ver cómo aplica lo que aprendió en este tutorial en sus proyectos.

Última actualización de la página: 22 de octubre de 2025

¿Le ha resultado útil este tutorial?