Web3: Creando tu primer App Descentralizada

El hype por las Aplicaciones Descentralizadas(DApps) no para. Esto genera que muchos desarrolladores tengan la necesidad de experimentar este maravilloso mundo llamado Web3.

Todo sobre la Web3 suena maravilloso hasta que, buscas un tutorial, y el resultado es completa desesperación, una alarma a 30 minutos de sonar y mucho café con Korn a todo volumen.

Puedes tomar el texto anterior como un relato personal. El ecosistema Web3 es muy cambiante e involucra mucha adaptación. Despiertas un lunes por la mañana y descubres que se han lanzado docenas librerías, conceptos nuevos que estudiar, Tokens, The Merge(1), y personas tirando hate en Twitter porque Solana no sube a $40 USD.

En resumen, Web3 puede generar muchísimo FOMO(2) sin importar la experiencia que tengas como desarrollador, tu background profesional, y mucho menos seniority.

Dando finiquito a la introducción anterior. A continuación podrás leer una guía detallada y elaborada con cariño para todos los interesados en adentrarse a este mundo. Tú puedes, no estás sólo en esto 🤗.

¿Qué demonios es Web3?

Al inicio de los tiempos dios creó la Web 1.0, sitios estáticos, contenido estático, y mucho texto con hipervínculos a más contenido estático.

La Web 2.0 es la Web que todos conocemos y consumimos. Esa popular y llena de memes “Internet”. Contenido dinámico y aplicaciones para consumir servicios, productos o interactuar con tus amigos de Facebook y Discord.

Web 3.0 es un concepto de descentralización de estos servicios, productos y aplicaciones. Un lugar dónde no existen intermediarios, y mucho menos empresas que se alimenten de tu información.

Ethereum Network

Ethereum(3) es una red de computadores o “nodos” distribuidos globalmente*.*

Cada nodo en esta red comparte una réplica o “estado” de la información disponible en la red. Este “estado” es definido por un cómputo único llamado “Ethereum Virtual Machine” (4).

En Ethereum, cada nodo es partícipe en un consenso para cada evento que intente modificar el “estado” de la EVM. Ya que esto requiere poder de cómputo, a los nodos participantes se les aporta ETH(Ether) como incentivo.

Podemos imaginar “Ethereum” como una gigantesca “Máquina de estados finitos” (Finite State Machine ó FSM) (5).

Con la EVM podemos manipular/condicionar el estado de la red usando “Contratos Inteligentes” (Smart Contracts).

IMPORTANTE: Para la fecha de lanzamiento de esta guía, Ethereum migró de Proof of Work a Proof of Stake, https://vitalik.ca/general/2017/12/31/pos_faq.html

Smart Contracts y Sistemas Distribuidos

Los “Smart Contracts” son programas/herramientas para acceder a la EVM, con ellos podemos leer, actualizar y escribir información a la red Ethereum.

En pocas palabras los desarrolladores tienen la posibilidad de crear aplicaciones “Semi-Turing Complete” usando lenguajes como Vyper y Solidity.

Solidity y Vyper son lenguajes de alto nivel para desarrollar Smart Contracts en la EVM. Usamos estos lenguajes porque la EVM sólo comprende “EVM Bytecode” (6).

Ejemplo de cómputo distribuido en una red global, en esta red, cada nodo comparte información entre sí y en tiempo real. Los sistemas distribuidos se caracterizan por la alta disponibilidad de información, ya que la malfunción de uno de estos nodos no afectará en la distribución de información de dicha red. Si quieres saber más del tema, recomiendo leer "Falacias de los Sistemas Distribuidos", https://architecturenotes.co/fallacies-of-distributed-systems

Talk is cheap. Show me the code

Antes de iniciar a tirar código debes considerar la siguiente lista de elementos necesarios para esta guía:

  1. Firefox o Chrome e instalar la extensión de Metamask

  2. Se recomienda crear una cuenta en Metamask sólamente para pruebas

  3. Visual Studio Code o un Editor de texto

  4. Si usas Windows tener WSL (Windows Subsystem for Linux) configurado

  5. NodeJS (Versión >= 16. Puedes usar nvm para una instalación sencilla)

  6. Crear una cuenta en Alchemy. Lo usaremos para lanzar nuestro Smart Contract en Goerli Testnet.

Si usas Visual Studio Code puedes instalar estos plugins:

Stack de Desarrollo

  • NextJs: Un framework de React muy popular que se vende como “El Framework de React para producción”. Personalmente es mi favorito del Ecosistema React y me agrada que sea de los más usados en Web3.

  • Wagmi: Colección de React hooks con lo requerido para trabajar con Ethereum. Peticiones, conectividad, interacción con Smart Contracts, firmas — TODO (7).

  • RainbowKit: Conectividad sencilla para React Devs. Un proyecto de los creadores de Rainbow Wallet.

  • TailwindCSS: Un framework de utilidad que expone una set de configuración consistente la cúal agiliza el desarrollo de componentes personalizados.

  • Alchemy: RPC Provider(8). Plataforma que nos hace la vida sencilla para la arquitectura de DApps. Sus clientes incluyen, OpenSea, Chainlink y Meta. NOTA: Existen 2 plataformas similares e igual de populares que Alchemy, hablamos de Moralis e Infura (9).

  • Hardhat: Entorno de desarrollo para Ethereum. Smart Contracts, Testing, Depuración y Tooling para Solidity.

  • Por último, pero no menos importante, Typescript.

Cuéntame, ¿ Qué vamos a crear ?

Sí, ya sé que tu café se está enfriando, rellenalo que ahora entramos al objetivo de este documento; crear un pequeño proyecto, que lo extiendas y juegues con él mientras nos movemos en el Stack más popular para crear DApps. Y que por cierto, luego puedes incluir en tu portafolio ☺️.

La idea final es desarrollar un “Libro de Notas”.

Proyecto “sencillo” de codear para la Web2, pero que aportará mucho jugar con él en un enfoque Web3. ¿ Por qué ? , tanto desarrollar la  interfaz, creación e integración del Smart Contract con Solidity requieren una atención/investigación muy similar, y así cualquier desarrollador puede aprovechar este documento 💛.

Al terminar aprenderemos cómo consumir información de Goerli, pintar esta información en un frontend y crear un Smart Contract que gestione este contenido :)

Crear este “GuestBook en Ethereum” estará dividido en 5 etapas, y una etapa completamente opcional. Estas son:

  1. Genesis: RainbowKit y NextJS

  2. Hardhat: Solidity Hello World

  3. Goerli Testnet: Distribuyendo nuestro Smart Contract

  4. Solidity Cardio: Eventos, Funciones y Variables de estado

  5. Wagmi React: Interacción con el Frontend y finalización

  6. Opcional, Plus Ultra: Lanzando nuestra DApp en Vercel

Iniciamos,

Genesis: RainbowKit y NextJS

Primero necesitamos un proyecto NextJs, agregar RainbowKit y Wagmi como dependencias a este proyecto. El equipo de RainbowKit ya nos expone un comando para crear el boilerplate e incluir estas dependencias. Ejecutamos:

npm init @rainbow-me/rainbowkit@latest -y kort

Puedes cambiar el argumento ”kort” con el texto que gustes, este será el nombre de nuestro proyecto.

Ya creado nuestro proyecto, nos ubicamos en el directorio creado. Si dejaste como nombre de proyecto “kort”, haz cd kort y ejecutas npm run dev.

Hacer npm run dev lanza un servidor de desarrollo en NextJs, este refrescará el contenido servido cada vez que hagamos cambios en el árbol de archivos de nuestro espacio de trabajo.

Si visitamos nuestro local en http://localhost:3000 veremos este Frontend:

La imágen muestra la página inicial de NextJS y RainbowKit en modo desarrollo. Se visualiza un widget de conexión a distintas redes  enlaces a documentación de RainbowKit, Wagmi y NextJs.

Podemos conectarnos con una Wallet en este frontend…, pero sólamente a redes Mainnet(10), nosotros queremos, por pruebas, conectarnos a una Testnet, Goerli.

Si visualizas el contenido en pages/_app.tsx y te ubicas en la línea #15 verás esto:

15  ...(process.env.NEXT_PUBLIC_ENABLE_TESTNETS === 'true'

La línea #15 evalúa que el valor de la “Variable de Entorno (11)NEXT_PUBLIC_ENABLE_TESTNETS tiene como contenido “true”, esta bandera define si permitimos Testnets en nuestro proyecto.

Para habilitarlas vamos a crear un archivo que tenga las variables de entorno para usar en nuestro proyecto. Ejecutamos:

echo "NEXT_PUBLIC_ENABLE_TESTNETS=true" > .env

Se creará un archivo .env con el contenido NEXT_PUBLIC_ENABLE_TESTNETS=true

Para cargar las definiciones del archivo .env a nuestro proyecto, cerramos el proceso dónde hicimos npm run dev al comienzo de la guía, matando el proceso con CTRL + C, luego ejecutamos npm run dev por una vez más. Hacemos esto porque las definiciones en el archivo .env se cargan en el arranque del servidor de desarrollo.

Y listo, ahora podemos conectarnos a Goerli Testnet con RainbowKit.

La imágen muestra un ejemplo de cuenta enlazada con RainbowKit en Goerli Testnet. Además se visualiza un widget de información de la cuenta con acciones para COPIAR y DESCONECTAR dicha cuenta enlazada.

¡Felicidades!, acabamos de enlazar Metamask con el Frontend de “kort”.


Continuemos,

pages/
|-- _app.tsx
|-- index.tsx
.env
package.json

Del árbol de archivos en nuestro proyecto nos enfocamos en cuatro elementos.

  1. page/index.tsx define la ruta inicial de nuestra DApp, el index.html.

  2. _app.tsx es un archivo reservado de NextJs para personalizar las páginas dentro de /pages.

  3. .env archivo para configurar Variables de entorno y configuración definida por el desarrollador. NOTA: No agregues este archivo en tu repositorio, los .env files guardan información privada de nuestros proyectos.

  4. package.json contiene metadatos y configuración para NodeJs. Acá se definen las dependencias, scripts y mucho más.

NextJs tiene el concepto de “fichero dentro de /pages” = “una página”. Estas “páginas” son por defecto un componente React.

A este concepto de enrutamiento “directorio”“fichero template”, se le conoce como File based routing system.

La configuración de RainbowKit

Con _app.tsx podemos modificar cada página del proyecto, agregar estado persistente, layouts compartidas, y más. Es acá dónde inicializamos RainbowKit.

07  // _app.tsx
08
09  const { chains, provider, webSocketProvider } = configureChains(
10    [chain.mainnet, chain.polygon, chain.optimism],
11    [
12      alchemyProvider({
13        // Obten tu APIKey en https://dashboard.alchemyapi.io
14      }),
15      publicProvider(),
16    ]
17  )
18

La línea #10 define las redes en las que funcionará nuestra DApp. Cada elemento en este arreglo contiene el id de la red, nombre, URLs de conexión RPC y URLs para los Exploradores de bloques de la red.

Líneas #11 y #15 definen los proveedores(providers) de la red Ethereum. En el fragmento se usa Alchemy como proveedor RPC inicial para las redes configuradas la línea #10.

publicProvider será un Fallback si en una red específica(Ejemplo si el usuario cambia Gnosis) dicho provider no está disponible. Tener en cuenta que publicProvider intentará interactuar con Ethereum Mainnet.

Llamamos provider a una abstracción consistente para una red. Básicamente es un cliente con una API estándar para interactuar con la chain.

24  const wagmiClient = createClient({
25    autoConnect: true,
26    connectors,
27    provider,
28    webSocketProvider,
29  })

Configurando Wagmi

RainbowKit es una herramienta para conexión, su funcionalidad vive gracias a Wagmi y sus React Hooks. Ver más información para configurar Wagmi acá: https://wagmi.sh/docs/client

Configurando #createClient

  • autoConnect: true indica que Wagmi puede(por defecto) tomar la última Wallet enlazada a nuestro Frontend.

  • connectors configuración de proveedores de Wallets de nuestra DApp. Por defecto son Metamask, Coinbase, Rainbow Wallet y WalletConnect.

  • provider es el cliente obtenido de ejecutar configureChains en el fragmento de la Línea #9.

  • webSocketProvider si la ejecución de configureChains resuelve con un cliente WebSockets, Wagmi realizará la conexión por WebSockets en vez de HTTP.

Yaaay, Felicidades. Estamos listos para la siguiente etapa.

Hardhat: Solidity Hello World

Ahora, vamos a crear nuestro primer Smart Contract 😍.

NOTA: Esta guía es para desarrollar Smart Contracts con Solidity y Hardhat como entorno de desarrollo. Existe un editor llamado Remix, dónde puedes probar cositas en la EVM antes de moverlas a tu codebase.

Primero, dependencias. Hardhat y hardhat-toolbox,

npm i -D hardhat @nomicfoundation/hardhat-toolbox

Ahora necesitamos un archivo de configuración para Hardhat. En la raíz de nuestro proyecto creamos el archivo hardhat.config.ts y pegamos esto:

// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config"
import "@nomicfoundation/hardhat-toolbox"

const config: HardhatUserConfig = {
  solidity: "0.8.9",
}

export default config

Tu primer Smart Contract

Para tener más ordenado nuestro código y seguir la convención de archivos en hardhat, vamos a crear una carpeta dónde vivirán nuestros Smart Contracts y otra para definir scripts/tareas que consumen estos Smart Contracts.

En consola, mkdir contracts scripts.

Creadas las carpetas, dentro de contracts/ creamos un archivo llamado HolaMundo.sol y pegamos el siguiente contenido:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract HolaMundo {
    string hola = "Hola";
    string mundo = "Mundo!!";

    function saludar() external view returns (string memory) {
        return string(abi.encodePacked(hola, " ", mundo));
    }
}

Ejecutando HolaMundo.sol

Dentro de scripts/ creamos un archivo deploy.ts y pegaremos el siguiente contenido:

import { ethers } from "hardhat"

async function main() {
  const factory = await ethers.getContractFactory("HolaMundo")
  const contract = await factory.deploy()
  await contract.deployed()
  console.log(await contract.saludar())
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})

Para ejecutar este script, hacemos:

npx hardhat run scripts/deploy.ts

Esto distribuye en local el Smart Contract “HolaMundo.sol” usando la Hardhat Network.


En la imágen podemos observar un error por una mala configuración en tsconfig.json. Para solventarlo se modifica la configuración de "module" en tsconfig.json a "commonjs".

Si visualizas este error, tranquilidad. NextJs ha definido que nuestro proyecto será un módulo “esnext” para poder hacer imports dinámicos, causando este error. Para solventarlo cambiamos la propiedad “module” en tsconfig.json a “commonjs”.

{
  "compilerOptions": {
    ...tu configuración acá,
    "module": "commonjs"
  },
}

Ejecutamos de nuevo npx hardhat run scripts/deploy.ts y Voilà.

Yaaay. Hola Mundo 👋

¿Y si intentamos crear una función que espere un parámetro?

Hagámoslo…, y por cierto, a este punto ya tienes una base para jugar con lo que se te ocurra con Solidity, mira ejemplos de Uniswap y tomalos de guía.

SaludarConNombre()

function saludarConNombre(string calldata nombre)
    external
    pure
    returns (string memory)
{
    return string(abi.encodePacked("Hola ", nombre));
}

Antes de invocar nuestra función en el script deploy.ts, una pequeña explicación de las palabras “pure”, “external” y “calldata” en este contexto.

  • pure, una función “pura” es aquella que no modifica propiedades que están fuera de su scope. En pocas palabras, saludarConNombre() es una función que sólo manipula la data con la que se invocó dicha función, más nunca escribirá ni leerá fuera de su contexto.

  • external describe la “visibilidad” que la función saludarConNombre() tendrá cuando se distribuya en la red. Existen 4 tipos de visibilidad, public, private, internal y external.

  • calldata representa el alojamiento de memoria de la función.

Modificamos la función main() para agregar saludos a “Seth” y “Atzil”,

async function main() {
  const factory = await ethers.getContractFactory("HolaMundo")
  const contract = await factory.deploy()
  await contract.deployed()
  console.log(await contract.saludar())
  console.log(await contract.saludarConNombre("Seth"))
  console.log(await contract.saludarConNombre("Atzil"))
}

npx hardhat run scripts/deploy.ts una vez más,

El resultado de ejecutar el comando "npx hardhat run scripts/deploy.ts"

abi.encodePacked es usado(en este caso) para concatenar strings. La función encodePacked agrupa los bytes de una variable y retorna los bytes de toda esa agrupación, al final convertimos esos bytes a un string.

Goerli Testnet: Distribuyendo nuestro Smart Contract

Para esta sección necesitas tener creada una cuenta en Alchemy, esto para distribuir nuestro contrato en Goerli Testnet : )

Como ya has notado, ejecutar los Smart Contracts requiere trabajo repetitivo, a medida que nuestro proyecto crece esto puede generar mucha distorsión.

La comunidad ha adoptado un plugin para agilizar este proceso — hardhat-deploy, el plugin de hardhat que permite definir scripts secuenciales e interactuar con el Hardhat runtime environment. Ya que esta guía intenta mostrarte lo más usado y adoptado por el ecosistema, vamos a instalarlo.

npm i -D hardhat-deploy dotenv

Ahora agregamos el plugin en hardhat.config.ts,

// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config"
import "@nomicfoundation/hardhat-toolbox"
import "hardhat-deploy" // Importamos el plugin
import "dotenv/config" // Paquete para cargar variables de entorno

Configurando Alchemy

Luego de registrarte en Alchemy, crea una nueva App con el nombre que quieras, la descripción que quieras, pero eso sí, la chain será Ethereum y en Network marcamos Goerli.

Configuración de una DApp en Alchemy.

Luego de creado el proyecto, vamos al Dashboard y copiamos el valor de “API KEY”

Dashboard del proyecto Kort. Se visualizan widgets para copiar los valores de la API KEY para conectarnos con Alchemy RPC.

Agregamos el valor de API KEY en nuestro archivo .env,

NEXT_PUBLIC_ENABLE_TESTNETS=true
ALCHEMY_API_KEY=PEGA TU API KEY ACÁ

Distribuyendo HolaMundo.sol

Creamos un archivo en deploy/00_HolaMundo.ts y agregamos lo siguiente:

// deploy/00_HolaMundo.ts
import { HardhatRuntimeEnvironment } from "hardhat/types"
import { DeployFunction } from "hardhat-deploy/types"

const fn: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
  const { getNamedAccounts, deployments } = hre
  const { deployer } = await getNamedAccounts()
  // "deployer" es tomado de hardhat.config.ts#namedAccounts
  await deployments.deploy("HolaMundo", {
    from: deployer,
  })
}

export default fn

El plugin hardhay-deploy ejecutará los archivos dentro de /deploy en orden, podemos agregar tantos scripts queramos

¿Deploy?. Aún no, espera un poco, un poquito más. Tenemos que definir en hardhat quién será el “deployer” de nuestro Smart Contract.

Para ello necesitamos la “Llave privada” de una Wallet. Para Metamask puedes seguír este post: https://metamask.zendesk.com/hc/en-us/articles/360015289632

Ahora que tenemos nuestra “Llave privada” la agregamos a nuestro .env

NEXT_PUBLIC_ENABLE_TESTNETS=true
ALCHEMY_API_KEY=PEGA TU API KEY ACÁ
PRIVATE_KEY=Y ACÁ TU LLAVE PRIVADA

Necesitamos agregar PRIVATE_KEY a la configuración de Hardhat para Goerli. hardhat.config.ts deberá quedar así:

// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config"
import "@nomicfoundation/hardhat-toolbox"
import "hardhat-deploy"
import "dotenv/config"

const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY
const PRIVATE_KEY = process.env.PRIVATE_KEY!

const config: HardhatUserConfig = {
  solidity: "0.8.9",
  networks: {
    goerli: {
      url: `https://eth-goerli.g.alchemy.com/v2/${ALCHEMY_API_KEY}`,
      accounts: [PRIVATE_KEY],
    },
  },
  namedAccounts: {
    deployer: 0, // Posición 0 en `networks.<goerli>.accounts[]`
  },
}

export default config

Un último comando,

npx hardhat deploy --network goerli

Resultado de ejecutar el lanzamiento de HolaMundo.sol


Oye, espera un momento .

¿ Sabes que acabas de crear y distribuir un Smart Contract en Goerli ? , te mereces una donita más para el café 💛.

Compartelo con tus amigos, publicalo en tus redes sociales :3

https://www.youtube.com/watch?v=gAjR4_CbPpQ

Hay algo más que podemos hacer para mejorar el deployment, si verificas tu Smart Contract aportas muchos beneficios.

Ejemplo, si visualizas este Smart Contract podrás ver su código fuente, ejecutar sus métodos de escritura/lectura, y obtener información sobre bugs. Todo esto gracias a que dicho contrato está verificado en Etherscan.


Full Extras: Verificando nuestro Smart Contract en Etherscan

Al verificar nuestro Smart Contract el código fuente de este será visible, lo cúal genera transparencia a cualquier interesado en la lógica detrás de una DApp. Y además cualquier persona puede interactuar con el contrato desde el Explorador.

Para verificar, primero creamos una cuenta en https://etherscan.io/apis, copiamos la API KEY y agregamos la definición ETHERSCAN_API_KEY=”TU API KEY” en .env

Configuramos el plugin hardhat-etherscan en hardhat.config.ts

// hardhat.config.ts

// Sacamos la definición ETHERSCAN_API_KEY de .env
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY!
const config: HardhatUserConfig = {
  ...,
  etherscan: {
    apiKey: {
      goerli: ETHERSCAN_API_KEY, // https://goerli.etherscan.io
    },
  },
}

export default config

El plugin usado es @nomiclabs/hardhat-etherscan y viene instalado junto a hardhat-toolbox

Del paso anterior(Distribuyendo HolaMundo.sol) debió crearse una carpeta( deployments/) con la información del lanzamiento.

En la ruta deployments/goerli/HolaMundo.json observaras algo parecido a esto:

{
  "address": "0xA1012F4B18badFd4fA9122fEc7e4de160aD0ffBB",
  "abi": [
    {
      "inputs": [],
      "name": "saludar",
      "stateMutability": "view",
      "type": "function"
    },
    {
      "name": "saludarConNombre",
      "stateMutability": "pure",
      "type": "function"
    }
  ]
}

Para verificar HolaMundo.sol en etherscan necesitamos la propiedad address, esta es la dirección dónde se alojó el contrato en la red.

ABI(Application Binary Interface) define los métodos y propiedades para interactuar con HolaMundo.sol.

😋 Quiero verificar HolaMundo.sol ya

Anda, para verificar HolaMundo.sol en Goerli ejecutamos:

npx hardhat verify --network goerli <address>

Ahora si vamos a la ruta de nuestro contrato(address). Veremos una interfaz dónde puedes interactuar : )

💎 Usando hardhat-deploy

Para automatizar este proceso podemos crear un deploy script en /deploy

// deploy/99_VerifyABIs.ts

import { HardhatRuntimeEnvironment } from "hardhat/types"
import { DeployFunction } from "hardhat-deploy/types"

// Agrega las chains que ejecutarán `verify`
const TO_VERIFY_NETWORKS = ["goerli"]
// Lista el nombre de los Smart Contracts a verificar en EtherScan
const VERIFY_CONTRACTS = ["HolaMundo"]

const fn: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
  const { name } = hre.network
  // Tomar todos los deployments hasta este momento
  const deployed = await hre.deployments.all()
  // Validar que estoy en una chain que puede ejecutar verify.
  // No vamos a verificar nuestro contrato en Hardhat localhost
  if (TO_VERIFY_NETWORKS.includes(name)) {
    for (let name of VERIFY_CONTRACTS) {
      const contract = deployed[name]
      if (contract) {
        await hre.run("verify:verify", {
          address: contract.address,
        })
      } else throw `Could't find deployment for ${name}`
    }
  }
}

// Forzar ejecutar esta función al final
fn.runAtTheEnd = true

export default fn

Este script se ejecutará cada cuando hagamos npx hardhat deploy : )

Solidity Cardio: Eventos y Variables de estado

En esta sección aprenderemos un poco de los primitivos en Solidity. De antemano comento que no será un tutorial de condicionales, bucles y basis de otros lenguajes de programación.

Tu mayor fuente de información es la documentación oficial de Solidity, Google y amigos en comunidades Web3 : )

Eventos

Los eventos en Solidity tienen muchos usos, exponer información, para por ejemplo comunicarnos con servicios externos de la red y además guardar data.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract NFTLogger {
    // Declaramos un evento con el nombre `NuevaCompra`.
    // En los eventos podemos "emitir" información.
    // Esta puede ser filtrada al marcarse como `indexed`.
    // * NOTA: A estas propiedades indexadas se les llama topics
    event NuevaCompra(address indexed owner, string nft_id);

    function comprarNFT() public {
        // Emitimos el evento NuevaCompra
        emit NuevaCompra(msg.sender, "NFT-004");
    }
}

Ver ejemplo en Etherscan.

Estructuras

Las estructuras(structs) en Solidity son un tipo de dato que aloja propiedades primitivas, dónde estas propiedades se agrupan como una tupla. “Objetos”

struct UnaLLama {
    bool guapa;
    string nombre;
    uint edad;
}

Para crear una “Llama”, hacemos:

LLama llama = LLama(true, "Juanita", 33);
// Creación por posición, LLama(guapa, nombre, edad)

Un pequeño ejemplo con Deploy scripts:

// contracts/Llama.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract Llama {
    struct UnaLLama {
        bool guapa;
        string nombre;
        uint edad;
    }

    function crearLLama(
        bool _guapa,
        string calldata _nombre,
        uint _edad
    ) public pure returns (UnaLLama memory) {
        return UnaLLama(_guapa, _nombre, _edad);
    }
}
// scripts/deploy.ts
import { ethers } from "hardhat"

async function main() {
  const factory = await ethers.getContractFactory("Llama")
  const contract = await factory.deploy()
  await contract.deployed()
  console.log(await contract.crearLLama(true, "Juanita 2", 42))
}

main()

Mappings

Los mappings son en pocas palabras una tabla “clave-valor”.

mapping(string => UnaLLama) corralito;

function guardarLlamaEnCorral(
    bool _guapa,
    string calldata _nombre,
    uint _edad
) external {
    corralito[_nombre] = crearLLama(_guapa, _nombre, _edad);
}

function sacarLlamaDeCorral(string calldata _nombre)
    external
    view
    returns (UnaLLama memory)
{
    return corralito[_nombre];
}

Ejemplo de deploy script,

async function main() {
  const factory = await ethers.getContractFactory("Llama")
  const ct = await factory.deploy()
  await ct.deployed()
  console.log(await ct.crearLLama(true, "Juanita 2", 42))
  const tx = await ct.guardarLlamaEnCorral(true, "Juanita", 42)
  await tx.wait()
  console.log(await ct.sacarLlamaDeCorral("Juanita"))
}

¿ ints, strings, arrays ?

Estos son primitivos en muchos lenguajes de programación y apuesto que ya tienes idea de como usarlos. Vamos, modifica Llama.sol y prueba : )

Lista de “tipos de datos” en Solidity: https://docs.soliditylang.org/en/v0.8.16/types.html


Wagmi React: Interacción con el Frontend y finalización

NO has llevado la guía a la ligera, hasta este punto ya tienes lo suficiente conocimiento para crear tus contratos e integrarlos con el Frontend.

Mi enfoque no es enseñarte React, mucho menos Javascript. Del Frontend, NextJs es importante ya que se usa muchísimo en el Ecosistema, saber el enrutamiento y la estructura de archivos y cómo configurar Wagmi & Rainbowkit es suficiente para arrancar.

La documentación de estas herramientas es tu fuente de la verdad.

Para esta etapa vamos a necesitar un template que trae todo lo que vimos en la guía. La plantilla ya trae un set de componentes React y una interfaz básica para modificar y jugar con el código.

La plantilla: NextJS-Rainbow-HH

https://github.com/D3Portillo/nextjs-rainbow-hh

Para usar la plantilla, vamos a https://github.com/D3Portillo/nextjs-rainbow-hh y clickeamos en “Use this template”, agregamos un nombre que deseemos asignar para este repositorio a crear.

La imágen muestra la interfaz de creación de un repositorio usando la plantilla en https://github.com/D3Portillo/nextjs-rainbow-hh

Estructura del proyecto

La estructura del proyecto es muy similar a nuestra experimentación anterior. La diferencia es que ahora hay una nueva carpeta(components/) que contiene los trozos de interfaz que pintamos en nuestra DApp.

También ha cambiado el nombre de algunas variables de entorno. Unas llevan el prefijo NEXT_PUBLIC, esto es porque NextJs tomara las definiciones de entorno sólo al ejecutarse en NodeJs más no en el navegador, para “permitir” acceder a estas definiciones se antepone NEXT_PUBLIC por convención y consistencia.

GuestBook.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract GuestBook {
    event NewNote(address indexed persona, string contenido);

    function addNote(string calldata _contenido) external {
        emit NewNote(msg.sender, _contenido);
    }
}

Nuestro proyecto sólamente envía logs al invocar la función addNote. Para acceder a los logs desde el frontend, filtramos los registros de la chain con el React Hook ubicado en la ruta lib/hooks/useNotesList.tsx

import { useEffect, useState } from "react"

import getContract from "@/lib/getContract"
import { noOp } from "@/lib/helpers"

const GuestBook = getContract("GuestBook")
function useNotesList() {
  const [list, setList] = useState([] as Note[])

  useEffect(() => {
    GuestBook.queryFilter(GuestBook.filters.NewNote())
      .then((list) => {
        return list.map((item) => {
          const {
            args: { persona, contenido },
            transactionHash,
          } = item
          return {
            transactionHash,
            persona,
            contenido,
          }
        })
      })
      .then(setList)
      .catch(noOp)
  }, [])

  return list
}

export default useNotesList

deployments/goerli/GuestBook.json

Esta es la información de los deployments que hagamos (espejo de los deploy scripts en /deploy). Por lo que te invito a modificar /contracts/GuestBook.json con una feature, agregar condicionales, o por ejemplo:

  • ¿Qué sucede sí mi string está vacío ?, validación de errores.

  • Agregar un deploy a Ethereum Mainnet, Solana o Polygon.

  • ¿No usar logs para obtener las notas dejadas en la DApp y usar una variable de estado ?

  • Convertir cada nota en un NFT ó SBT

  • ¿ Limitar la cantidad de Notas que puede dejar una persona ?

Lo que gustes, vos podes hacer lo que querrás 😋

Tu Proyecto

Luego de modificar el proyecto ejecuta npx hardhat run deploy, levanta nodos locales con npx hardhat run y lo más importante, juega, modifica, pregunta : )

Haz deploy de tus cambios porque la información en /deployments es mía, de mi cuenta de pruebas. Haz el proyecto tuyo.

Ya habrás notado que la guía es un tutorial de cómo crear el template que acabas de usar, Felicidades!! 🦄. Te invito a leer un poco la documentación de React y NextJs, no vendrá mal, y además no debe faltar leer sobre Hardhat y Solidity.

Sugerencias, comentarios, quejas; lo que se te ocurra 🥴, más que bienvenid@s :)

Plus Ultra: Lanzando nuestra DApp en Vercel

Vercel es una compañía amada por los Frontend devs para lanzar sus proyectos. “Develop, Preview, Ship” es su eslogan y les pega a la perfección.

Nota random personal: Vercel es una de las empresas dónde quiero trabajar y tirar código. Tengo un crush con su CEO — Guillermo Rauch(12), un crack.

Para esta sección necesitamos crear una cuenta en https://vercel.com/new, y luego inicias sesión con tu GitHub.

La imágen muestra la pantalla de inicio de sesión de vercel.com

Ahora buscamos el proyecto que vamos a lanzar en Vercel de la lista de repositorios(en mi caso es “mi-proyecto-guapo”). Damos a “Import” y configuramos variables de entorno.

Seleccionando el repositorio dónde vive nuestro proyecto NextJS

Configuramos las variables de entorno. Recuerda que para forzar Goerli seteamos NEXT_PUBLIC_FORCE_GOERLI_PROVIDER=true y reemplazas NEXT_PUBLIC_ALCHEMY_API_KEY por tu API Key de Alchemy 😛.

La imágen muestra la configuración de Variables de entorno en el Dashboard de Vecel

Esperamos un momento, y tendremos nuestro DApp con url para compartir 😍

https://bukrr.vercel.app


🤘Genial, hemos aprendido mucho. Ahora tú tienes conocimientos sobre el Ecosistema Web3, Ethereum, Sistemas Distribuidos y puntualmente “Cómo desarrollar Aplicaciones Descentralizadas” :)

Recuerda, la idea de esta guía es demostrar lo más posible cómo desarrollar DApps con un set de herramientas populares y validadas por los desarrolladores. Faltaron un par de cositas que espero con mucha inquietud presentar en otra Guía. Una de ellas es mejorar el consumo de información en el Frontend con Subgraphs.

Algo más, si te preguntas, ¿Cómo se interconecta la información de Ethereum con servicios externos a la red ?, la respuesta, Oracles.


Si llegaste hasta este punto de la lectura, y bueno; sí cualquier trozo de texto te ayudó para aprendizaje, espero y te sientas orgulloso. Sé que tienes muchas preguntas, pero ahora te será más sencillo buscar lo que no sabes y reforzar lo que sabes.

También, si este fue tu primer bailecito con la Web3, Bienvenido 🥰.

Notas finales y un poco sobre mí

Me llamo Denny Portillo, Salvadoreño y estudiante de 42 Madrid. Trabajo como desarrollador Frontend Web3 en Daoism Systems. Quiero comentarte con mucha honestidad que en este ecosistema nunca paras de encontrar contenido que aprender. Muchas ocasiones te desesperas por TODO; agrego que esto me divierte, me gusta. No es aburrido y la Web3 tiene mucha vida por delante.

Si te interesa desarrollar DApps, hazlo. Inténtalo, equivócate, práctica, práctica, práctica. No dejes de dudar y preguntar. Únete a comunidades de desarrolladores y crea lo que se te ocurra, : )

¿No sabes a qué comunidad unirte ?, puedes iniciar en https://buildspace.so. En Buildspace aprendí a crear Smart Contracts y lanzarlos en una Testnet (13).

Para terminar. No sabes lo que me gustó crear este documento, espero compartas la guía con tus amigos y si tienes comentarios, dudas; arrojalas que estamos para apoyar.

Antes de irte, si gustas, sigueme en Twitter 🚀: https://twitter.com/d3portillo

Referencias

  1. The Merge: https://ethereum.org/en/upgrades/merge

  2. FOMO: https://blog.orange.es/consejos-y-trucos/que-es-fomo/

  3. Intro to Ethereum: https://ethereum.org/en/developers/docs/intro-to-ethereum

  4. Ethereum Virtual Machine: https://ethereum.org/es/developers/docs/evm

  5. Maquina de Estados: https://es.wikipedia.org/wiki/Máquina_de_estados

  6. “Turing Completeness” y Modelo de ejecución en la EVM

    1. Turing Complete Blockchain: https://academy.binance.com/en/glossary/turing-complete

    2. Ethereum Yellow Paper #8fea825, Cap. 9 — Execution Model: https://ethereum.github.io/yellowpaper/paper.pdf

  7. Linkedin post “5 Paquetes de React para Frontend Devs en Web3”: https://www.linkedin.com/posts/d3portillo_5-paquetes-de-react-para-frontends-en-web3-activity-6948890361192722432-nXGU

  8. Ethereum RPC: https://ethereum.org/en/developers/docs/apis/json-rpc

  9. Moralis vs Infura vs Alchemy: https://moralis.io/whats-the-difference-between-moralis-alchemy-and-infura

  10. Mainnet Network: https://academy.binance.com/es/glossary/mainnet

  11. Variables de Entorno: https://es.wikipedia.org/wiki/Variable_de_entorno

  12. Guillermo es el creador de Socket.io, Mongoose y otras herramientas populares del ecosistema NodeJS. Hay contenido en este documento de su charla “Merging Design and Development”, dónde habla sobre “Consistent UI” y Sistemas distribuidos, mirala acá: https://www.youtube.com/watch?v=3hccXiXI0u8

  13. Repositorios usados en la guía

    1. Jeshejojo: https://github.com/D3Portillo/jeshejojo

    2. Plantilla Bukrr: https://github.com/D3Portillo/nextjs-rainbow-hh

Glosario

  • Blockend: El Desarrollo Backend, Frontend y de Smart Contracts para una DApp.

  • DApp: Aplicación Descentralizada (Decentralized App).

  • ETH: Ether(Ξ). Token principal y nativo de la red Ethereum.

  • EVM: Ethereum Virtual Machine.

  • Solidity: Lenguaje de programación parecido a Javascript para crear Smart Contracts en la EVM.

  • Testnet: Una copia/instancia para pruebas de una red.

  • WAGMI: We’re all gonna make it.

  • Vyper: Lenguaje de programación parecido a Python para crear Smart Contracts en la EVM.

Gracias Ana por la sugerencia en crear este documento :)