En este tutorial, construiremos una Dapp completamente funcional con la biblioteca de TypeScript Viem + React (Next.js). Cubriremos los pasos necesarios para conectar tu wallet, transferir criptomonedas, interactuar con contratos inteligentes (por ejemplo, mintear NFTs) y consultar la blockchain.
Viem es una alternativa en TypeScript a las interfaces de bajo nivel de Ethereum existentes como web3.js y ethers.js. Soporta el BigInt nativo del navegador e infiere tipos automáticamente a partir de ABIs y EIP-712. Tiene un tamaño de bundle de 35kb, un diseño tree-shakable para minimizar el bundle final y una cobertura de pruebas del 99.8%. Echa un vistazo a sus benchmarks y documentación completa.
Hemos estructurado este tutorial con explicaciones concisas entregadas a través de comentarios dentro del código. Simplemente copia y pega los códigos y lee las explicaciones.
Aquí tienes un resumen del tutorial:
- Aclarando Terminologías de Viem: Client | Transport | Chain
- Empezando: Configurar Client y Transport con React + Viem
- Parte 1: Conectar a la Wallet Web3 con React + Viem
- Parte 2: Transferir Criptomonedas con React + Viem
- Parte 3: Mintear un NFT con React + Viem
Una muestra de lo que construirás:

Código completo en el Repositorio de Git
Aclarando Terminologías de Viem: Client | Transport | Chain
Viem tiene tres conceptos fundamentales: Client, Transport y Chain
- El Client en Viem es similar al Provider de ethers.js. Proporciona las funciones de TypeScript para realizar acciones comunes en Ethereum. Dependiendo de la acción, caerá en uno de los tres tipos de clientes.
- Public Client es una interfaz para los métodos “públicos” de la API JSON RPC, por ejemplo, obtener números de bloque, consultar saldos de cuentas, acceder a funciones “view” en contratos inteligentes y otras operaciones de solo lectura que no cambian el estado. Estas funciones se conocen como Public Actions.
- Wallet Client es una interfaz para interactuar con Cuentas de Ethereum, por ejemplo, enviar transacciones, firmar mensajes, solicitar direcciones, cambiar de cadenas y operaciones que requieren el permiso del usuario. Por ejemplo, mintear un NFT cambia el estado, por lo que esto se haría bajo el Wallet Client. Estas funciones se conocen como Wallet Actions.
- Test Client se utiliza para crear transacciones simuladas para pruebas. Esto normalmente se usaría en pruebas unitarias.
- El Transport se instancia con el Client, representa la capa intermediaria para ejecutar solicitudes. Hay tres tipos de Transport:
- HTTP Transport, que utiliza HTTP JSON-RPC;
- WebSocket Transport, para conexiones en tiempo real a través de WebSocket JSON-RPC;
- Custom Transport, que maneja solicitudes a través del método de solicitud EIP-1193;
- Fallback te permite especificar múltiples transports en una lista. Si uno falla, avanza en la lista para encontrar uno funcional. Se proporcionará un ejemplo más adelante.
- Chain se refiere a la cadena compatible con la EVM para establecer una conexión; se identifican a través del objeto chain (identificado por el chain id). Solo se puede instanciar una chain junto con un client. Podemos usar la biblioteca de chains proporcionada por Viem, por ejemplo, polygon, eth mainnet o construir la tuya propia manualmente.
Public Client
Así es como se declara un Public Client.
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
Así es como utilizas las Public Actions.
const balance = await publicClient.getBalance({
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
})
const block = await publicClient.getBlockNumber()
Estas son las Public Actions disponibles:
- getChainId
- getGasPrice
- signMessage
- verifyMessage
- getTransactionReceipt
- Más en la Documentación de Public Actions
Viem tiene una característica llamada Optimization Public Client, soporta eth_call Aggregation para mejorar el rendimiento enviando solicitudes por lotes. Esto está muy bien documentado.
Wallet Client
Así es como se establece un Wallet Client:
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'
const walletClient = createWalletClient({
chain: mainnet,transport: custom(window.ethereum)
})
Así es como se utilizan las Wallet Actions:
// Get's the user address
const [address] = await walletClient.getAddresses()
// Sends a transaction
const hash = await walletClient.sendTransaction({
account: address,
to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
value: parseEther('0.001')
})
Funciones de wallet disponibles:
- requestAddresses (Las wallets como Metamask pueden necesitar que un usuario haga requestAddresses primero)
- switchChain
- signMessage
- getPermissions
- sendTransaction
- Más en la Documentación de Wallet Actions
Demostraremos cómo usar sendTransaction y getAddresses en este tutorial.
Opcional
Puedes extender el Wallet Client con Public Actions. Esto te ayuda a evitar el manejo de varios clients. El siguiente fragmento de código extiende el wallet client con public actions.
import { createWalletClient, http, publicActions } from 'viem'
import { mainnet } from 'viem/chains'
const extendedClient = createWalletClient({
chain: mainnet,
transport: http()
}).extend(publicActions)
// Public Action
const block = await extendedClient.getBlockNumber()
// Wallet Action
const [address] = await extendedClient.getAddresses();
Test Client
Test Client proporciona una interfaz para suplantar cuentas (shadow accounts), minar bloques y simular transacciones a través de un nodo de prueba local como Anvil o Hardhat. No discutiremos esto en detalle, pero puedes leer más en la Documentación de Test Client.
Transport
Solo podemos pasar un medio de transport (el protocolo que usaremos para conectarnos a la blockchain) a la vez; aquí hay un ejemplo de cómo se puede usar cada transport.
HTTP
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const transport = http('https://eth-mainnet.g.alchemy.com/v2/...')
const client = createPublicClient({
chain: mainnet,
transport,
})
El transport recurrirá a una URL de RPC pública si no se proporciona una url. Se recomienda pasar una URL de RPC autenticada para minimizar problemas con la limitación de tasa (rate-limiting).
WebSocket
import { createPublicClient, webSocket } from 'viem'
import { mainnet } from 'viem/chains'
const transport = webSocket('wss://eth-mainnet.g.alchemy.com/v2/...')
const client = createPublicClient({
chain: mainnet,
transport,
})
El transport recurrirá a una URL de RPC pública por las mismas razones anteriores.
Nota cómo en ambos ejemplos anteriores, el transport se define por el tipo de URL especificada para el transport. La primera url es una url HTTPS, la segunda es una url WSS.
Custom (EIP-1193) (Estaremos usando este)
Este transport se utiliza para integrarse con wallets inyectadas que proporcionan un proveedor EIP-1193, como WalletConnect, Coinbase SDK y Metamask.
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'
const client = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum)
})
Fallback
Este transport admite múltiples Transports. Si un transport falla, recurrirá al siguiente método de transport indicado. En el siguiente ejemplo, si Alchemy falla, recurrirá a Infura como fallback.
import { createPublicClient, fallback, http } from 'viem'
import { mainnet } from 'viem/chains'
const alchemy = http('https://eth-mainnet.g.alchemy.com/v2/...')
const infura = http('https://mainnet.infura.io/v3/...')
const client = createPublicClient({
chain: mainnet,
transport: fallback([alchemy, infura]),
})
Chains
Viem proporciona chains populares compatibles con la EVM a través de la biblioteca viem/chains, tales como: Polygon, Optimism, Avalanche, y más en la Documentación de Chains de Viem.
Puedes cambiar de chains pasándolas como argumento, ej. polygonMumbai.
import { createPublicClient, http } from 'viem'
import { polygonMumbai } from 'viem/chains'
const client = createPublicClient({
chain: polygonMumbai,
transport: http(),
})
También puedes construir tu propio objeto chain que herede el tipo Chain (Créditos: Viem.sh)
import { Chain } from 'viem'
export const avalanche = {
id: 43_114,
name: 'Avalanche',
network: 'avalanche',
nativeCurrency: {
decimals: 18,
name: 'Avalanche',
symbol: 'AVAX',
},
rpcUrls: {
public: {
https: ['https://api.avax.network/ext/bc/C/rpc']
},
default: {
https: ['https://api.avax.network/ext/bc/C/rpc']
},
},
blockExplorers: {
etherscan: {
name: 'SnowTrace',
url: 'https://snowtrace.io'
},
default: {
name: 'SnowTrace',
url: 'https://snowtrace.io'
},
},
contracts: {multicall3: {
address: '0xca11bde05977b3631167028862be2a173976ca11',
blockCreated: 11_907_934,
},
},
} as const satisfies Chain
Recuerda, solo se puede asignar una chain a un client a la vez.
Empezando: Configurar Client y Transport con React + Viem
Por simplicidad, recomendamos usar polygonMumbai o Sepolia como tu red de prueba.
Paso 1: Crear un Proyecto de Next.js e Instalar Viem
Primero crea tu proyecto de Next.js con
npx create-next-app@latest myapp
Marca [yes] en lo siguiente:
- Typescript
- ESLint
- Tailwind
- App Router (preferiblemente)
Abre tu proyecto en vscode.
Instala viem con uno de los siguientes comandos:
npm i viem
pnpm i viem
yarn add viem
Paso 2: Configurar Client y Transport
En el directorio app, crea dos nuevos archivos:
- client.ts
- walletButton.tsx
Tu directorio app debería verse así:
app
├── client.ts
├── globals.css
├── layout.tsx
├── page.tsx
└── walletButton.tsx
client.ts
Inicializaremos el Client y Transport en un archivo de TypeScript separado. Adelante, copia y pega los siguientes códigos en client.ts.
// client.ts
import { createWalletClient, createPublicClient, custom, http } from "viem";
import { polygonMumbai, mainnet } from "viem/chains";
import "viem/window";
// Instantiate Public Client
const publicClient = createPublicClient({
chain: mainnet,
transport: http(),
});
// Instantiate Wallet Clientconst walletClient = createWalletClient({
chain: polygonMumbai,
transport: custom(window.ethereum),
});
Esto inevitablemente lanzará un error de tipo, window.ethereum podría ser undefined ya que algunos navegadores como Safari no soportan el objeto window.ethereum.
Podemos manejar el error comprobando si window.ethereum está presente o undefined.
// client.ts
import { createWalletClient, createPublicClient, custom, http } from "viem";
import { polygonMumbai } from "viem/chains";
import "viem/window";
export function ConnectWalletClient() {
// Check for window.ethereum
let transport;
if (window.ethereum) {
transport = custom(window.ethereum);
} else {
const errorMessage ="MetaMask or another web3 wallet is not installed. Please install one to proceed.";
throw new Error(errorMessage);
}
// Delcalre a Wallet Client
const walletClient = createWalletClient({
chain: polygonMumbai,
transport: transport,
});
return walletClient;
}
export function ConnectPublicClient() {
// Check for window.ethereum
let transport;
if (window.ethereum) {
transport = custom(window.ethereum);
} else {
const errorMessage ="MetaMask or another web3 wallet is not installed. Please install one to proceed.";
throw new Error(errorMessage);
}
// Delcare a Public Client
const publicClient = createPublicClient({
chain: polygonMumbai,
transport: transport,
});
return publicClient;
}
Esto aún te permitirá abrir el sitio web en un navegador que no soporte window.ethereum.
Se recomienda mantener consistentes las chains para el walletClient y el publicClient, o de lo contrario podrías encontrarte con errores de chains incompatibles.
Parte 1: Conectar a la Wallet Web3 con React + Viem
Esta sección demuestra cómo el Client de Viem se conecta a tu wallet web3.
Paso 2: Crear un Botón que se Conecte a la Wallet Web3
walletButton.tsx
Ahora crearemos un componente de cliente que maneja la lógica de conexión a tu wallet web3.
El botón instancia un walletClient y solicita la dirección de la wallet del usuario; si la wallet no está ya conectada, le pedirá que lo haga, y finalmente mostrará su dirección.
Hay mucho código aquí, pero céntrate en la función handleClick().
// walletButton.tsx
"use client";
import { useState } from "react";
import { ConnectWalletClient, ConnectPublicClient } from "./client";
export default function WalletButton() {
//State variables for address & balance
const [address, setAddress] = useState<string | null>(null);
const [balance, setBalance] = useState<BigInt>(BigInt(0));
// Function requests connection and retrieves the address of wallet
// Then it retrievies the balance of the address
// Finally it updates the value for address & balance variable
async function handleClick() {
try {
// Instantiate a Wallet & Public Client
const walletClient = ConnectWalletClient();
const publicClient = ConnectPublicClient();
// Performs Wallet Action to retrieve wallet address
const [address] = await walletClient.getAddresses();
// Performs Public Action to retrieve address balance
const balance = await publicClient.getBalance({ address });
// Update values for address & balance state variable
setAddress(address);
setBalance(balance);
} catch (error) {
// Error handling
alert(`Transaction failed: ${error}`);
}
}
// Unimportant Section Below / Nice to Have UI
return (
<>
<Status address={address} balance={balance} />
<button className="px-8 py-2 rounded-md bg-[#1e2124] flex flex-row items-center justify-center border border-[#1e2124] hover:border hover:border-indigo-600 shadow-md shadow-indigo-500/10"
onClick={handleClick}
>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/36/MetaMask_Fox.svg" alt="MetaMask Fox" style={{ width: "25px", height: "25px" }} />
<h1 className="mx-auto">Connect Wallet</h1>
</button></>);}
// Displays the wallet address once it’s successfuly connected
// You do not have to read it, it's just frontend stuff
function Status({
address,
balance,}: {
address: string | null;
balance: BigInt;
}) {
if (!address) {
return (
<div className="flex items-center">
<div className="border bg-red-600 border-red-600 rounded-full w-1.5 h-1.5 mr-2">
</div>
<div>Disconnected</div>
</div>);
}
return (
<div className="flex items-center w-full">
<div className="border bg-green-500 border-green-500 rounded-full w-1.5 h-1.5 mr-2"></div>
<div className="text-xs md:text-xs">{address} <br /> Balance: {balance.toString()}</div>
</div>
);
}
Paso 3: Insertar el componente walletButton
page.tsx
Lo que queda es diseñar la página principal e importar el componente WalletButton. Hemos comentado algo de código que añadirás más tarde.
import WalletButton from "./walletButton";
// import MintButton from "./mintButton";
// import SendButton from "./sendButton";
export default function Home() {
return (
<main className="min-h-screen">
<div className="flex flex-col items-center justify-center h-screen ">
<a href="https://rareskills.io" target="_blank" className="text-white font-bold text-3xl hover:text-[#0044CC]" > Viem.sh </a>
<div className="h-[300px] min-w-[150px] flex flex-col justify-between backdrop-blur-2xl bg-[#290330]/30 rounded-lg mx-auto p-7 text-white border border-purple-950">
<WalletButton />
{/* <SendButton />
<MintButton /> */}
</div>
<a href="https://rareskills.io" target="_blank" className="text-white font-bold text-3xl hover:text-[#0044CC]" > Rareskills.io </a>
</div>
</main>
);
}
globals.css
Una interfaz de fondo agradable, reemplaza globals.css con los códigos a continuación.
@tailwind base;@tailwind components;@tailwind utilities;
body {
background-color: #0c002e;
background-image: radial-gradient(
at 100% 100%,rgb(84, 2, 103) 0px,
transparent 50%),
radial-gradient(at 0% 0%, rgb(97, 0, 118) 0px, transparent 50%);}
Paso 4: Ejecutar el sitio web y probarlo
Cuando hagas clic en el botón, debería iniciar una conexión con tu wallet. Una vez que hayas aprobado, debería verse así:
npm run dev

Después de hacer clic en el botón, se mostrará lo siguiente:

Parte 2: Transferir Criptomonedas con React + Viem
Ahora que nuestra wallet está conectada, podemos empezar a transferir criptomonedas. Utilizaremos la wallet action sendTransaction.
- Consigue algo de Matic del Fauce de Matic
Paso 5: Añadir la función para transferir criptomonedas
Crea un nuevo archivo tsx sendButton.tsx en el directorio app
app
├── client.ts
├── globals.css
├── layout.tsx
├── page.tsx
├── sendButton.tsx
└── walletButton.tsx
sendButton.tsx
Crearemos un botón que inicie la acción sendTransaction. Viem hace que sea muy sencillo hacerlo. El flujo lógico debería ser similar al de walletButton.tsx, instanciar el walletClient y realizar acciones de Wallet Client.
"use client";
import { parseEther } from "viem";
import { ConnectWalletClient} from "./client";
export default function SendButton() {
//Send Transaction Function
async function handleClick() {
try {
// Declare wallet client
const walletClient = ConnectWalletClient();
// Get the main wallet address
const [address] = await walletClient.getAddresses();
// sendTransaction is a Wallet action.
// It returns the transaction hash
// requires 3 parameters to transfer cryptocurrency,
// account, to and value
const hash = await walletClient.sendTransaction({
account: address,
to: "Account_Address",
value: parseEther("0.001"), // send 0.001 matic
});
// Display the transaction hash in an alert
alert(`Transaction successful. Transaction Hash: ${hash}`);
} catch (error) {
// Handle Error
alert(`Transaction failed: ${error}`);
}
}
return (
<button
className="py-2.5 px-2 rounded-md bg-[#1e2124] flex flex-row items-center justify-center border border-[#1e2124] hover:border hover:border-indigo-600 shadow-md shadow-indigo-500/10"
onClick={handleClick}>
Send Transaction
</button>
);
}
Paso 6: Insertar el Componente sendButton
page.tsx
Descomenta las líneas relacionadas con el componente sendButton.
import WalletButton from "./walletButton";
import SendButton from "./sendButton";
// import MintButton from "./mintButton";
export default function Home() {
return (
<main className="min-h-screen">
<div className="flex flex-col items-center justify-center h-screen ">
<a href="https://rareskills.io" target="_blank" className="text-white font-bold text-3xl hover:text-[#0044CC]" > Viem.sh </a>
<div className="h-[300px] min-w-[150px] flex flex-col justify-between backdrop-blur-2xl bg-[#290330]/30 rounded-lg mx-auto p-7 text-white border border-purple-950">
<WalletButton />
<SendButton />
{/* <MintButton /> */}
</div>
<a href="https://rareskills.io" target="_blank" className="text-white font-bold text-3xl hover:text-[#0044CC]" > Rareskills.io </a>
</div>
</main>
);
}
¡Tu navegador debería verse así ahora!

Parte 3: Mintear un NFT con React + Viem
Esta sección discutirá cómo interactuar con un Smart Contract y dará un ejemplo mediante el minteo de un NFT.
Para interactuar con un contrato inteligente, necesitamos dos cosas:
- La Dirección del Contrato (Contract Address)
- El ABI del Contrato (Contract ABI)
Para este ejemplo, lo demostraremos con el contrato de Rareskills que tiene una función Mint que realmente no hace nada más que rastrear el número de veces que minteas.
- Dirección del Contrato de Rareskills: 0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23
- ABI del Contrato de Rareskills
Siéntete libre de usar tu propio contrato.
Paso 7: Añadir funcionalidad para interactuar con el Smart Contract
Crea dos nuevos archivos abi.ts y mintButton.tsx
app
├── abi.ts
├── client.ts
├── globals.css
├── layout.tsx
├── mintButton.tsx
├── page.tsx
├── sendButton.tsx
└── walletButton.tsx
abi.ts
Copia y pega el ABI del Contrato de Rareskills o el tuyo.
// abi.ts
export const wagmiAbi = [...contract abi...] as const;
Recuerda seguir este formato exacto y no olvides el “as const;” al final.
Métodos Contract Instance y Contract Action
Método Contract Instance
//Contract Instance
const contract = getContract({
address: "0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23",
abi: wagmiAbi,
publicClient,
walletClient,
});
La función getContract crea nuestra instancia de contrato contract. Al crearla, somos capaces de invocar métodos del contrato, escuchar eventos, etc. Este es un método más simple ya que no tenemos que pasar repetidamente las propiedades address y abi para realizar una acción de contrato.
Parámetros:
- address
- abi
- publicClient (optional)
- walletClient (optional)
Estamos obligados a pasar los parámetros address y abi. Pasar publicClient y walletClient es opcional, pero nos permite acceder a un conjunto de métodos de contrato dependiendo del tipo de client.
Métodos de contrato disponibles para publicClient:
Métodos de contrato disponibles para walletClient:
En general, llamar a un método Contract Instance sigue el siguiente formato:
// function
contract.(estimateGas|read|simulate|write).(functionName)(args, options)
// event
contract.(createEventFilter|watchEvent).(eventName)(args, options)
Llamar a Métodos de Contrato usando Contract Instance
// Read Contract symbol
const symbol = await contract.read.symbol();
// Read Contract name
const name = await contract.read.name();
// Call mint method
const result = await contract.write.mint({account: address});
Los ejemplos anteriores invocan los métodos de contrato read y write a través de la instancia contract. Si estás usando TypeScript, autocompletará sugerencias de los métodos de contrato disponibles.
Los métodos read.symbol() y read.name() son muy directos. Por otro lado, la función write
const result = await contract.write.mint({account: address});
toma {account: address} como su parámetro requerido y todos los demás son opcionales. Si te encuentras con problemas sobre qué parámetros añadir, pasa el ratón sobre la palabra clave “mint()”; VS Code debería darte una pista.
Método Contract Action — la forma Tediosa
El código en la sección anterior es azúcar sintáctico para lo siguiente. Incluimos esta sección para mostrarte qué está pasando tras bambalinas (bajo el capó).
Este código obtiene el totalSupply del contrato:
const totalSupply = await publicClient.readContract({
address: '0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23',
abi: wagmiAbi,
functionName: 'totalSupply',
})
¿Tedioso, verdad? Tienes que pasar la address y el abi de forma recurrente.
Este es el equivalente de llamar a la función mint del ejemplo anterior usando el método Contract Action. Si tiene éxito, devuelve el hash de la transacción.
const hash = await walletClient.writeContract({
address: "0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23",
abi: wagmiAbi,
functionName: "mint",
account,
});
Para mantener el tamaño del bundle al mínimo, usa Contract Action; mientras que la instancia de Contract proporciona más funciones, incrementa el uso de memoria.
mintButton.tsx
Para demostrar una transacción que cambia el estado (state-changing), crearemos un botón que invoque la función mint del Smart Contract, así como también consultaremos su name, symbol y totalSupply.
Utilizamos tanto el método Contract Instance como Contract Action para mostrar cómo se implementa.
"use client";
import { formatEther, getContract } from "viem";
import { wagmiAbi } from "./abi";
import { ConnectWalletClient, ConnectPublicClient } from "./client";
export default function MintButton() {
// Function to Interact With Smart Contract
async function handleClick() {
// Declare Client
const walletClient = ConnectWalletClient();
const publicClient = ConnectPublicClient();
// Create a Contract Instance
// Pass publicClient to perform Public Client Contract Methods
// Pass walletClient to perform Wallet Client Contract Methods
const contract = getContract({
address: "0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23",
abi: wagmiAbi,
publicClient,
walletClient,
});
// Reads the view state function symbol via Contract Instance method
const symbol = await contract.read.symbol();
// Reads the view state function name via Contract Instance method
const name = await contract.read.name();
// Reads the view state function symbol via Contract Action method
const totalSupply = await publicClient.readContract({
address: '0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23',
abi: wagmiAbi,
functionName: 'totalSupply',
})
// Format ether converts BigInt(Wei) to String(Ether)
const totalSupplyInEther = formatEther(totalSupply);
alert(`Symbol: ${symbol}\nName: ${name}\ntotalSupply: ${totalSupplyInEther}`);
try {
// Declare Wallet Client and Retrieve wallet address
const client = walletClient;
const [address] = await client.getAddresses();
// Writes the state-changin function mint via Contract Instance method.
const result = await contract.write.mint({
account: address
});
alert(`${result} ${name}`);
} catch (error) {
// Handle any errors that occur during the transaction
alert(`Transaction failed: ${error}`);
}}
return (
<>
<button
className="py-2.5 px-2 rounded-md bg-[#1e2124] flex flex-row items-center justify-center border border-[#1e2124] hover:border hover:border-indigo-600 shadow-md shadow-indigo-500/10"
onClick={handleClick}>
<svg
className="w-4 h-4 mr-2 -ml-1 text-[#626890]"
aria-hidden="true"
focusable="false"
data-prefix="fab"
data-icon="ethereum"
role="img"
xmlns="https://www.w3.org/2000/svg"
viewBox="0 0 320 512">
<path
fill="currentColor"
d="M311.9 260.8L160 353.6 8 260.8 160 0l151.9 260.8zM160 383.4L8 290.6 160 512l152-221.4-152 92.8z">
</path>
</svg>
<h1 className="text-center">Mint</h1>
</button>
</>
);
}
¡Felicidades por terminar este tutorial! Tu producto final debería verse así:

Autoría
Este artículo fue coescrito por Aymeric Taylor (LinkedIn, X), un pasante de investigación en RareSkills.
Publicado originalmente el 10 de agosto de 2023