Cómo crear tu propio smart contract con Solidity y Hardhat 🔥🚀

josh2604

Josué Andrés

Posted on May 12, 2023

Cómo crear tu propio smart contract con Solidity y Hardhat 🔥🚀

Introducción

En esta oportunidad, vamos a explorar el potencial de los smart contracts de Ethereum mediante el uso de Solidity y Hardhat como nuestro entorno de desarrollo.
A través de la creación de una pequeña aplicación, desplegaremos en las redes de prueba de Polygon, Avalanche para demostrar las capacidades que nos ofrece el ecosistema de blockchain. Al final del proceso, tendremos una mejor comprensión del alcance y la versatilidad de los smart contracts y su aplicación práctica en el mundo real.

Instalación de dependencias

Para desarrollar nuestro contrato inteligente usaremos Harhat como IDE ya que nos permitirá probarlo dentro de un entorno local antes de llevarlo en una red pruebas o principal. Además de que nos proporciona herramientas de automatización de tareas, como pruebas automatizadas, pruebas de cobertura de código y generación de documentación, lo que nos facilita mucho el desarrollo y depuración de contratos inteligentes.
Además de que se adapta muy fácilmente a proyectos de cualquier tamaño y complejidad.

Para realizar la instalación de Hardhat primero necesitamos Node.js ya que este hace uso de este entorno de ejecución. Si ya cuentas con Node.js omite este paso, de lo contrario es necesario ir al sitio oficial, descargarlo e instalarlo para la plataforma que utilices.

Ahora a instalar Hardhat, para ello debemos seguir los pasos de instalación desde su sitio web.

Una vez instalado deberíamos de ver el siguiente resultado al ejecutar el comando para la creación de un nuevo proyecto:

$ npx hardhat
888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

👷 Welcome to Hardhat v2.9.9 👷‍

? What do you want to do? …
❯ Create a JavaScript project
  Create a TypeScript project
  Create an empty hardhat.config.js
  Quit
Enter fullscreen mode Exit fullscreen mode

en mi caso crearé uno basado en JavaScript, pero puedes crearlo basado en TypeScript o vació si es el caso. El proyecto lo llamaré notes_dapp.

Creación de smart contract

Dentro de la carpeta de contracts definiremos el smart contract correspondiente

// notes_dapp/contracts/TaskList.sol

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.18;

contract TaskList {
  struct Task {
    bytes32 description;
    bool completed;
  }

  mapping(uint => Task) public tasks;
  uint public taskCount;

  function addTask(bytes32 _description) public {
    taskCount++;
    tasks[taskCount] = Task(_description, false);
  }

  function completeTask(uint _taskId) public {
    require(tasks[_taskId].completed == false, "Task is already completed");
    tasks[_taskId].completed = true;
  }

  function deleteTask(uint _taskId) public {
    require(tasks[_taskId].completed == true, "Can only delete completed tasks");
    delete tasks[_taskId];
  }

  function getTask(uint _taskId) public view returns (bytes32, bool){
    return (tasks[_taskId].description, tasks[_taskId].completed);
  }

  function getTaskCount() public view returns (uint) {
    return taskCount;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Task: Estructura que nos permitirá la descripción de la tarea y el status de la misma
  • tasks: Map que contendrá todas las tareas que vayamos definiendo a futuro
  • completeTask: Función que nos permitirá marcar como completada determinada tarea
  • deleteTask: Nos permitirá eliminar una tarea en especifico
  • getTask: Obtiene una tarea mediante un id proporcionado, podremos obtener los la descripción y el status de la tarea
  • getTaskCount: Función que nos permite obtener el numero de tareas

Podremos compilar el smart contract mediante el siguiente comando:

❯ npx hardhat compile
Compiled 2 Solidity files successfully
Enter fullscreen mode Exit fullscreen mode

Nos dice que se compilaron dos archivos, esto debido a que por default hardhat genera un smart contract de ejemplo llamado Lock.sol, esto solo para los proyectos que no son generados de forma vacía, el cual fue nuestro caso. Por lo que deberías de ver algo asi en tu carpeta contracts

contracts/
├── Lock.sol
└── TaskList.sol
Enter fullscreen mode Exit fullscreen mode

Tests

Otra parte fundamental para cualquier proyecto de desarrollo de software es el apartado de los tests, estos nos permitirán reducir posibles errores en desarrollos posteriores y como buenos programadores que debemos ser no omitiremos estos de nuestro proyecto.
Por lo que, dentro de la carpeta test agregare el archivo con los tests correspondientes:

// notes_dapp/test/TaskList.js

const { expect } = require("chai")
const { ethers } = require("hardhat")
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers")

describe("Task list contract contract", function () {
  async function deployContract() {
    const contract = await ethers.getContractFactory("TaskList")
    const deploy = await contract.deploy()

    return { deploy }
  }

  it("Contract Deploy", async function () {
    const { deploy } = await loadFixture(deployContract)

    const _description = "test description"

    const description = ethers.utils.formatBytes32String(_description)
    await deploy.addTask(description)

    const taskOne = await deploy.getTask(1)

    expect(taskOne[0], _description)
    expect(taskOne[1], false)
  })

  it("Test, when add two tasks", async function () {
    const { deploy } = await loadFixture(deployContract)
    const _description = "test description"
    const _description2 = "test description"

    const description = ethers.utils.formatBytes32String(_description)
    const description2 = ethers.utils.formatBytes32String(_description2)

    await deploy.addTask(description)
    await deploy.addTask(description2)

    const taskOne = await deploy.getTask(1)
    const taskTwo = await deploy.getTask(2)

    expect(taskOne[0], _description)
    expect(taskOne[1], false)

    expect(taskTwo[0], _description2)
    expect(taskTwo[1], false)
    expect(await deploy.getTaskCount()).to.equals(2)
  })

  it("Test, set complete task, successfully", async function () {
    const { deploy } = await loadFixture(deployContract)
    const _description = "test description"

    const description = ethers.utils.formatBytes32String(_description)
    await deploy.addTask(description)
    await deploy.completeTask(1)

    const taskOne = await deploy.getTask(1)

    expect(taskOne[1], true)
  })

  it("Test, Error trying to complete a completed task", async function () {
    const { deploy } = await loadFixture(deployContract)
    const _description = "test description"

    const description = ethers.utils.formatBytes32String(_description)
    await deploy.addTask(description)
    await deploy.completeTask(1)
    await expect(deploy.completeTask(1)).to.be.revertedWith(
      "Task is already completed"
    )
  })

  it("Test, Delete task", async function () {
    const { deploy } = await loadFixture(deployContract)
    const _description = "test description"

    const description = ethers.utils.formatBytes32String(_description)
    await deploy.addTask(description)
    await deploy.completeTask(1)

    const task = await deploy.getTask(1)
    expect(task[1]).to.equals(true)
    await deploy.deleteTask(1)
  })

  it("Test, Error deleting task", async function () {
    const { deploy } = await loadFixture(deployContract)
    const _description = "test description"

    const description = ethers.utils.formatBytes32String(_description)
    await deploy.addTask(description)

    const task = await deploy.getTask(1)
    expect(task[1]).to.equals(false)
    await expect(deploy.deleteTask(1)).to.be.revertedWith(
      "Can only delete completed tasks"
    )
  })
})
Enter fullscreen mode Exit fullscreen mode

Podemos correr los tests ejecutando:

❯ npx hardhat test


  Lock
    Deployment
      ✔ Should set the right unlockTime (2831ms)
      ✔ Should set the right owner
      ✔ Should receive and store the funds to lock
      ✔ Should fail if the unlockTime is not in the future (89ms)
    Withdrawals
      Validations
        ✔ Should revert with the right error if called too soon (89ms)
        ✔ Should revert with the right error if called from another account (45ms)
        ✔ Shouldn't fail if the unlockTime has arrived and the owner calls it (45ms)
      Events
        ✔ Should emit an event on withdrawals (38ms)
      Transfers
        ✔ Should transfer the funds to the owner (89ms)

  Task list contract contract
    ✔ Contract Deploy (109ms)
    ✔ Test, when add two tasks (291ms)
    ✔ Test, set complete task, successfully (44ms)
    ✔ Test, Error trying to complete a completed task (62ms)
    ✔ Test, Delete task (60ms)
    ✔ Test, Error deleting task (70ms)


  15 passing (4s)
Enter fullscreen mode Exit fullscreen mode

Puedes eliminar el archivo de test para el smart contract generado por hardhat al iniciar el proyecto, en mi caso no lo hice pero son archivos que no necesitaremos.

Deploy

Una vez generados nuestros tests para los casos de nuestro smart contract crearemos el script de deploy el cual nos servirá para realizar el deploy en las diferentes redes de prueba donde funcionara nuestro contrato.
Dentro de la carpeta scripts/ agregaremos el archivo TaskListDeploy.js con el siguiente contenido

const hre = require("hardhat")

async function main() {
  const Notes = await hre.ethers.getContractFactory("TaskList")
  const note = await Notes.deploy()

  await note.deployed()

  console.log(`Note dApp deployed on ${note.address}`)
}

main().catch(error => {
  console.log(error)
  process.exitCode = 1
})
Enter fullscreen mode Exit fullscreen mode

Por el momento nos aseguraremos de que nuestro contrato sea desplegado en nuestra red de pruebas ya proporcionada por hardhat. Para esto iremos al archivo:
hardhat.config.js y agregaremos la red por default.

Hardhat al ser una herramienta completa para el desarrollo blockchain ya nos proporciona una red de pruebas local sin la necesidad de ejecutar alguna otra aplicación que simule ser un nodo.
También nos da la posibilidad de ejecutar el nodo mediante el comando npx hardhat node (HardHat node).

En este caso no es necesario ejecutar el nodo.

require("@nomicfoundation/hardhat-toolbox")

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.18",
  defaultNetwork: "hardhat",
}
Enter fullscreen mode Exit fullscreen mode

Ahora probaremos que nuestro contrato se despliegue correctamente en la red por defecto que hemos seleccionado:

❯ npx hardhat run scripts/TaskListDeploy.js
Note dApp deployed on 0x5FbDB2315678afecb367f032d93F642f64180aa3
Enter fullscreen mode Exit fullscreen mode

si todo salio bien podremos ver la dirección de despliegue del contrato que en este caso pertenece a una dirección de nuestro nodo de pruebas. Ya que verificamos que funciona el despliegue y que el comportamiento es el que esperamos podremos desplegar nuestro contrato den las diferentes redes que se mencionaron en la introducción.

Polygon

Para poder desplegar nuestro contrato en la red de Polygon necesitamos contar con una billetera de criptomonedas, en mi caso hare uso de Metamask, pero serviría cualquiera que nos permita conectarnos las redes principales y de pruebas de las redes que usaremos.(Guía inicial de Metamask)

Posteriormente necesitaremos agregar la red pruebas de Polygon en nuestra billetera, para ello necesitamos lo siguiente:

Debería de verse algo similar:

tesnet-connection

Paso siguiente es añadir fondos a nuestra billetera, para ello necesitamos ir al siguiente sitio (https://faucet.polygon.technology/), nos aseguramos de que se encuentre seleccionada la red Mumbai y que el token sea MATIC, colocamos la dirección de nuestra billetera y damos click en submit.

polygon_faucet

se abrirá una ventana donde confirmaremos la transacción

funding_confirmation

después de un par de minutos podremos ver 0.2 Matic en nuestra dirección

funded_wallet

Ahora agregaremos la red de MATIC a la configuración de hardhat así como las variables de entorno correspondientes:

require("@nomicfoundation/hardhat-toolbox")
+ require("dotenv").config()

const MUMBAI_URL= process.env.MUMBAI_URL;
const MUMBAI_PRIVATE_KEY = process.env.MUMBAI_PRIVATE_KEY;

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.18",
  defaultNetwork: "hardhat",
+  networks: {
+    mumbai: {
+     url: MUMBAI_URL,
+      accounts: [MUMBAI_PRIVATE_KEY]
+    }
+  }
Enter fullscreen mode Exit fullscreen mode

Instalar el paquete dotenv mediante el gestor de maquetes de tu preferencia, en mi caso en yarn

$ yarn add dotenv
Enter fullscreen mode Exit fullscreen mode

Se crea el archivo .env en la raíz del proyecto con el siguiente contenido:

MUMBAI_URL=https://rpc-mumbai.maticvigil.com
MUMBAI_PRIVATE_KEY=TU_CLAVE_PRIVADA
Enter fullscreen mode Exit fullscreen mode

La clave la obtienes de la siguiente forma:

private_key
private_key_2

Ya que esta configurada la red estamos listos para desplegar el contrato mediante el comando:

$ npx hardhat run scripts/TaskListDeploy.js --network mumbai
Enter fullscreen mode Exit fullscreen mode

después de ejecutar el comando deberíamos de tener lo siguiente:

$ npx hardhat run scripts/TaskListDeploy.js --network mumbai
Notes dApp deployed on {tu dirección de contrato}
Enter fullscreen mode Exit fullscreen mode

Para visualizarlo vamos al explorador de mumbai y colocamos la dirección del contrato, se debería de poder visualizar la transacción

deployed_contract

Continuaremos con la creación de un script en la carpeta de scripts que nos permitirá leer la información del contrato una vez desplegado en la red de pruebas, agregaremos a nuestro archivo de variables .env la dirección del contrato

MUMBAI_URL=https://rpc-mumbai.maticvigil.com
MUMBAI_PRIVATE_KEY=TU_CLAVE_PRIVADA
+CONTRACT_ADDRESS=TU_DIRECCIÓN_DE_CONTRATO
Enter fullscreen mode Exit fullscreen mode

posteriormente crearemos el siguiente archivo donde agregaremos lo siguiente

// scripts/TaskList.js
const hre = require("hardhat");
const abi = require("../artifacts/contracts/TaskList.sol/TaskList.json");

const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS;
const MUMBAI_URL = process.env.MUMBAI_URL;
const PRIVATE_KEY = process.env.MUMBAI_PRIVATE_KEY;

async function main(){
  const contractAddress = CONTRACT_ADDRESS;
  const contractABI = abi.abi;

  const provider = new hre.ethers.providers.JsonRpcProvider(MUMBAI_URL);

  const signer = new hre.ethers.Wallet(PRIVATE_KEY, provider);

  const taskList = new hre.ethers.Contract(contractAddress, contractABI, signer);

  // Nos permite agregar nuevas tareas
  // const description = hre.ethers.utils.formatBytes32String("Deploy de contrato en tesnet");
  // await taskList.addTask(description);

  const tasks = await taskList.getTaskCount();
  console.log(`Task number: ${tasks}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

mediante el script anterior podremos interactuar con nuestro contrato sin necesidad de crear una interfaz web, pero claro el objetivo es que este se comunique con un frontend ;), ahora si ejecutamos el script deberíamos de ver 0 tareas cargadas.

$ npx hardhat run scripts/TaskList.js
Task number: 0
Enter fullscreen mode Exit fullscreen mode

Si se descomenta el código y lo ejecutamos de nuevo estaríamos creando la tarea que especifiquemos, ejecutamos nuevamente el comando anterior

npx hardhat run scripts/TaskList.js
Task number: 0
Enter fullscreen mode Exit fullscreen mode

este nos arrojara que son 0 tareas creadas, pero si ejecutamos nuevamente pero antes comentando el código de creación este nos devolverá ahora el total de una tarea

npx hardhat run scripts/TaskList.js
Task number: 1
Enter fullscreen mode Exit fullscreen mode

y podremos ver la transacción en el explorador de bloques

first_transaction

Podemos agregar la función de marcar como terminada la tarea

// scripts/TaskList.js
const hre = require("hardhat");
const abi = require("../artifacts/contracts/TaskList.sol/TaskList.json");

const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS;
const MUMBAI_URL = process.env.MUMBAI_URL;
const PRIVATE_KEY = process.env.MUMBAI_PRIVATE_KEY;

async function main(){
  const contractAddress = CONTRACT_ADDRESS;
  const contractABI = abi.abi;

  const provider = new hre.ethers.providers.JsonRpcProvider(MUMBAI_URL);

  const signer = new hre.ethers.Wallet(PRIVATE_KEY, provider);

  const taskList = new hre.ethers.Contract(contractAddress, contractABI, signer);

  // Nos permite agregar nuevas tareas
  // const description = hre.ethers.utils.formatBytes32String("Deploy de contrato en tesnet");
  // await taskList.addTask(description);

+  // Permite marcar como terminada una tarea
+  await taskList.completeTask(1);

+  // Elimina una tarea
+  // await taskList.deleteTask(1);


  const tasks = await taskList.getTaskCount();
  console.log(`Task number: ${tasks}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

si ejecutamos de nuevo tendremos como resultado un numero de tareas de 1 sin embargo podremos ver en el explorador la transacción que marca como completada la tarea

npx hardhat run scripts/TaskList.js
Task number: 1
Enter fullscreen mode Exit fullscreen mode

mark_complete

De la misma forma podemos probar todas las funciones de nuestro contrato, y asi es como finalizamos con el despliegue de contratos inteligentes en Polygon, ahora aprenderás a hacerlo en la red de Avalanche.

Avalanche

Para el despliegue del contrato en la red de Avalanche necesitamos configurar la información de la red en el archivo de configuración de hardhat hardhat.config.js

require("@nomicfoundation/hardhat-toolbox")
require("dotenv").config()

const MUMBAI_URL= process.env.MUMBAI_URL;
const MUMBAI_PRIVATE_KEY = process.env.MUMBAI_PRIVATE_KEY;

+ const FUJI_URL = process.env.FUJI_URL;
+ const FUJI_PRIVATE_KEY = process.env.FUJI_PRIVATE_KEY;

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.18",
  defaultNetwork: "hardhat",
  networks: {
    mumbai: {
      url: MUMBAI_URL,
      accounts: [MUMBAI_PRIVATE_KEY]
+    },
+    fuji: {
+      url: FUJI_URL,
+      accounts: [FUJI_PRIVATE_KEY],
+    }
  }
};
Enter fullscreen mode Exit fullscreen mode

y dentro del archivo .env agregamos las variables correspondientes

MUMBAI_URL=https://rpc-mumbai.maticvigil.com
MUMBAI_PRIVATE_KEY=TU_CLAVE_PRIVADA
CONTRACT_ADDRESS=TU_DIRECCIÓN_DE_CONTRATO

+FUJI_URL=https://api.avax-test.network/ext/bc/C/rpc
+FUJI_PRIVATE_KEY=TU_CLAVE_PRIVADA
Enter fullscreen mode Exit fullscreen mode

Ahora necesitamos agregar fondos a nuestra billetera para poder pagar las tarifas de gas al momento de desplegar el contrato, para ello vamos al siguiente sitio https://faucet.avax.network/, colocaremos la dirección de billetera y pediremos monedas de la red de pruebas de Avalanche

request_avax

Una vez que los fondos se encuentren en nuestra billetera podremos desplegar el contrato

$ npx hardhat run scripts/TaskListDeploy.js --network fuji
Notes dApp deployed on {tu dirección de contrato}
Enter fullscreen mode Exit fullscreen mode

para poder visualizar el contrato desplegado vamos a snowtrace colocamos en el buscador la dirección del contrato y podremos visualizarlo ya desplegado en la blockchain de pruebas de Avalanche

snowtrace_contract

Para probar el contrato comentaremos las credenciales que pertenecían a la red de Polygon y colocaremos las correspondientes a la red de Avalanche

const hre = require("hardhat");
const abi = require("../artifacts/contracts/TaskList.sol/TaskList.json");

const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS;
const MUMBAI_URL = process.env.MUMBAI_URL;
-const PRIVATE_KEY = process.env.MUMBAI_PRIVATE_KEY;
+const MUMBAI_PRIVATE_KEY = process.env.MUMBAI_PRIVATE_KEY;

+const FUJI_URL = process.env.FUJI_URL;
+const FUJI_PRIVATE_KEY = process.env.FUJI_PRIVATE_KEY;

async function main(){
  const contractAddress = CONTRACT_ADDRESS;
  const contractABI = abi.abi;

  // const provider = new hre.ethers.providers.EtherscanProvider("maticmum", process.env.MATIC_PRIVATE_KEY);
- const provider = new hre.ethers.providers.JsonRpcProvider(MUMBAI_URL);
+ const provider = new hre.ethers.providers.JsonRpcProvider(FUJI_URL);

-  const signer = new hre.ethers.Wallet(PRIVATE_KEY, provider);
+  const signer = new hre.ethers.Wallet(FUJI_PRIVATE_KEY, provider);

  const taskList = new hre.ethers.Contract(contractAddress, contractABI, signer);

  // Nos permite agregar nuevas tareas
  const description = hre.ethers.utils.formatBytes32String("Deploy de contrato en tesnet");
  await taskList.addTask(description);

  // Permite marcar como terminada una tarea
  // await taskList.completeTask(1);

  // Elimina una tarea
  //await taskList.deleteTask(1);


  const tasks = await taskList.getTaskCount();
  console.log(`Task number: ${tasks}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

por último solo probaremos la creación y actualización a completado de nuestra tarea

npx hardhat run scripts/TaskList.js
Task number: 0
Enter fullscreen mode Exit fullscreen mode

nos dirá que nuestro total de tareas son 0, pero si vamos al explorador de bloques veremos la transacción, por último probaremos la actualización de la tarea comentando el código de creación y se descomenta la función completeTask, ejecutaremos de nuevo

npx hardhat run scripts/TaskList.js
Task number: 1
Enter fullscreen mode Exit fullscreen mode

veremos que el número de tareas es 1, y en el explorador de bloques podremos visualizar la transacción

complete_task

de esta forma ya contamos con un smart contract funcional que podríamos llevar a las redes principales de las cadenas de bloques, para ello solo basta con agregar las direcciones correspondientes a su respectiva mainnet y se configuran en el file hardhat.config.js así como en el archivo .env colocar las URL rpc correspondientes.

Conclusiones

Como se podemos observar el uso de hardhat como entorno de desarrollo es sumamente potente, ya que nos permite realizar pruebas y testing de forma local con todas las herramientas necesarias ya integradas, ahorrandonos mucho tiempo en instalar software extra.
Por otro lado las redes de Polygon y Avalanche son muy económicas para el despliegue de contratos inteligentes dentro de su mainnet y en cuanto a pruebas estas te proporcionan todo un entorno muy completo para que puedas validar tus desarrollos, en lo personal estas dos redes son las que más me han agradado hasta el momento.

Si llegaste hasta aquí espero que el tutorial te haya servido tanto si eres nuevo en el mundo de la blockchain o si ya tienes experiencia, y que esta tecnología te parezca tan interesante como a mi, seguiré agregando posts relacionados a blockchain y sobre otro temas relacionados, así que no te pierdas de novedades.

Puedes encontrar más posts como este en mi sitio web: LiteralCoder

Repositorio

GitHub logo Josh2604 / notes-dapp-sample

Notes dApp sample, created with hardhat.

Dapp de Notas

Esta es una Dapp (aplicación descentralizada) de notas construida con Hardhat y Ethereum. Esta aplicación permite a los usuarios crear, leer, actualizar y eliminar notas. La Dapp está conectada a dos redes blockchain diferentes: Polygon y Avalanche.

Funcionalidades

La Dapp de Notas tiene las siguientes funcionalidades:

Crear notas: los usuarios pueden crear nuevas notas ingresando un contenido Numero de notas: los usuarios pueden ver el numero de notas creadas Actualizar notas: los usuarios marcar como completada una nota en especifico Eliminar notas: los usuarios pueden eliminar una nota existente.

Tecnologías utilizadas

Hardhat: una herramienta de desarrollo de Ethereum que permite compilar, probar y desplegar contratos inteligentes React: una biblioteca de JavaScript para construir interfaces de usuario ethers.js: una biblioteca de JavaScript que permite interactuar con contratos inteligentes de Ethereum. Polygon: una red blockchain que permite transacciones rápidas y baratas. Avalanche: una red blockchain escalable y de alta…

💖 💪 🙅 🚩
josh2604
Josué Andrés

Posted on May 12, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related