Saltar al contenido

Cómo implementar la API de Gas de MetaMask en una Dapp de venta de entradas

En este blog, exploramos cómo implementar la MetaMask Gas API en una dApp de ticketing existente creada con Next.js y ShadcnUI.

6 min de lectura
Cómo implementar la API de Gas de MetaMask en una Dapp de venta de entradas

Comprender el precio del gas antes de comprar entradas para eventos basados en blockchain es fundamental tanto para la transparencia de costos como para una planificación financiera eficaz. En el contexto de las transacciones en blockchain, el término "gas" hace referencia a la tarifa necesaria para ejecutar con éxito una transacción o un contrato en la blockchain.

Esta tarifa varía según la demanda de la red. Al conocer el precio del gas con antelación, los compradores de entradas pueden tomar decisiones más informadas sobre el costo total de su compra. Esto es especialmente importante, ya que la tarifa de gas puede incrementar considerablemente el costo total de la entrada, en particular durante períodos de alta congestión en la red.

En este tutorial, implementaremos la API de Gas de MetaMask en una dapp de venta de entradas para eventos ya existente, lo que permitirá a los usuarios ver los precios del gas mientras compran entradas.

Requisitos previos y dependencias

Para el framework de frontend utilizaremos Next.js y, para la interfaz de usuario, ShadcnUI. Instalemos los siguientes componentes de ShadcnUI:

npx shadcn-ui@latest add hover-card
npm i swr
Copiar

El código anterior añadirá el componente hover-card que usaremos para mostrar los precios del gas e instalará la biblioteca de hooks de React SWR. La biblioteca Stale-While-Revalidate (SWR) facilita la obtención de datos desde internet. Funciona utilizando primero los datos guardados anteriormente, luego solicitando datos nuevos y, finalmente, reemplazando los datos antiguos con la información actualizada. Esto hace que los sitios web sean más rápidos y estén más actualizados.

A continuación, instalaremos el componente react-circular-progressbar para mostrar indicadores de progreso circulares, útiles para visualizar el avance de tareas o estados de carga en aplicaciones web. Es personalizable y está implementado con SVG para gráficos de alta calidad.

npm i react-circular-progressbar

Implementación de la API de Gas

En el directorio app/web, crearemos la siguiente carpeta: app/api/gas. Dentro de ella, crearemos un archivo route.ts y añadiremos el siguiente código:

import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const chainId = searchParams.get("chainId");

  const Auth = Buffer.from(
    process.env.INFURA_API_KEY + ":" + process.env.INFURA_API_SECRET
  ).toString("base64");

  const gasPricesResp = await fetch(
    `https://gas.api.infura.io/networks/${chainId}/suggestedGasFees`,
    {
      headers: {
        Authorization: `Basic ${Auth}`,
      },
    }
  );

  const baseFeePercentileRes = await fetch(
    `https://gas.api.infura.io/networks/${chainId}/baseFeePercentile`,
    {
      headers: {
        Authorization: `Basic ${Auth}`,
      },
    }
  );

  const gasPricesData = await gasPricesResp.json();
  const baseFeePercentileData = await baseFeePercentileRes.json();

  return NextResponse.json({
    estimatedBaseFee: gasPricesData.estimatedBaseFee,
    baseFeeTrend: gasPricesData.baseFeeTrend,
    baseFeePercentile: baseFeePercentileData.baseFeePercentile,
  });
}
Copiar

Veamos qué hicimos aquí:

  1. Extracción de parámetros de consulta:- const { searchParams } = new URL(request.url);: Extrae los parámetros de búsqueda de la URL de la solicitud.

  • const chainId = searchParams.get("chainId");: Recupera el parámetro chainId de la URL, que se utiliza para especificar la red de Ethereum.

  1. Configuración de la autorización:- Las variables de entorno INFURA_API_KEY e INFURA_API_SECRET se combinan en un Buffer y luego se codifican en una cadena Base64. Esta cadena se utiliza para la autenticación HTTP básica al realizar solicitudes a la API de Infura.

  2. Obtención de precios de gas y datos de percentil de tarifa base:- Se realizan dos solicitudes fetch asíncronas a la API de Infura:- gasPricesResp obtiene las tarifas de gas sugeridas para la red de Ethereum especificada (chainId).

  • baseFeePercentileRes obtiene el percentil de la tarifa base para la misma red.

  • El encabezado Authorization se establece con las credenciales codificadas en Base64.

  1. Análisis y devolución de datos:- Las respuestas de ambas solicitudes fetch se convierten a JSON.

  • NextResponse.json({...}) crea y devuelve una respuesta JSON con los siguientes datos:- estimatedBaseFee: La tarifa base estimada para las transacciones.

  • baseFeeTrend: La tendencia de la tarifa base (por ejemplo, en aumento o en descenso).

  • baseFeePercentile: La distribución por percentiles de las tarifas base.

Creación de la interfaz de usuario

Crearemos un componente llamado GasFeeCard.tsx y añadiremos el siguiente código:


import useSWR, { mutate } from "swr";
import {
  CircularProgressbarWithChildren,
  buildStyles,
} from "react-circular-progressbar";
import "react-circular-progressbar/dist/styles.css";
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card";
import { useEffect, useState } from "react";
import { ArrowUp, ArrowDown, InfoIcon } from "lucide-react";
import { useSDK } from "@metamask/sdk-react";

const REFETCH_INTERVAL = 10000;
const STEP_INTERVAL = 1000;
const NETWORK_LATENCY = 2000; // rough estimate of network latency to consider when refetching

// format the gas price nicely for small values
const formatGweiValues = (value: string) => {
  const gwei = parseFloat(value);

  if (gwei < 0.01) {
    return gwei.toExponential(2);
  } else {
    return gwei.toFixed(2);
  }
};

export function GasFeeCard() {
  const { chainId } = useSDK();

  const [countdown, setCountdown] = useState(REFETCH_INTERVAL);
  const [currentChainId, setCurrentChainId] = useState(chainId);

  useEffect(() => {
    if (chainId !== currentChainId) {
      setCurrentChainId(chainId);
      mutate("gas-data");
      if (countdown < NETWORK_LATENCY) {
        setCountdown(REFETCH_INTERVAL + NETWORK_LATENCY);
      }
      setCountdown(REFETCH_INTERVAL);
    }
  }, [countdown, chainId, currentChainId, mutate]);

  useEffect(() => {
    const interval = setInterval(() => {
      setCountdown((oldCountdown) => {
        if (oldCountdown <= 0) {
          mutate("gas-data");
          return REFETCH_INTERVAL;
        } else {
          return oldCountdown - STEP_INTERVAL;
        }
      });
    }, STEP_INTERVAL);

    return () => clearInterval(interval);
  }, [chainId]);

  const fetcher = () =>
    fetch(`/api/gas?&chainId=${parseInt(chainId || "0xe704")}`).then((res) =>
      res.json()
    );

  const {
    data: gasData,
    error: gasDataError,
    isLoading: gasDataLoading,
  } = useSWR("gas-data", fetcher);

  if (gasDataLoading) {
    return <p>loading</p>;
  }

  if (gasDataError) {
    return <p className="text-red-500">error</p>;
  }

  const trendingUp = gasData?.baseFeeTrend === "up";
  const trendingDown = gasData?.baseFeeTrend === "down";

  return (
    <HoverCard>
      <HoverCardTrigger asChild>
        <div className="cursor-pointer">
          <div className="flex space-x-1">
            <div className="w-6 h-6">
              <CircularProgressbarWithChildren
                strokeWidth={12}
                styles={buildStyles({
                  pathColor: trendingUp ? "red" : "green",
                })}
                value={
                  ((REFETCH_INTERVAL - countdown) / REFETCH_INTERVAL) * 100
                }
              >
                <>
                  {trendingUp && (
                    <ArrowUp
                      className="animate-bounceUp"
                      color="red"
                      size={14}
                    />
                  )}
                  {trendingDown && (
                    <ArrowDown
                      className="animate-bounceDown"
                      color="green "
                      size={14}
                    />
                  )}
                </>
              </CircularProgressbarWithChildren>
            </div>
            <p>
              ~{formatGweiValues(gasData?.estimatedBaseFee)}
              Gwei
            </p>
            <InfoIcon size={14} />
          </div>
        </div>
      </HoverCardTrigger>
      <HoverCardContent className="p-0 w-80">
        <div className="p-2 space-y-4">
          <p>
            This is an <strong>estimated base gas fee.</strong>
          </p>
          <p>
            50% of the historical base fees are less then or equal to{" "}
            <strong>
              ~{formatGweiValues(gasData?.baseFeePercentile)} Gwei
            </strong>
          </p>
        </div>
      </HoverCardContent>
    </HoverCard>
  );
}
Copiar

Veamos qué hicimos aquí:

  1. Constantes:- REFETCH_INTERVAL, STEP_INTERVAL, NETWORK_LATENCY: Constantes para gestionar el intervalo de obtención de datos, el intervalo de paso para las actualizaciones de la cuenta regresiva y la latencia estimada de la red.

  2. Función utilitaria:- formatGweiValues: Una función para formatear los valores del precio del gas en un formato legible, utilizando notación exponencial para valores pequeños o notación de punto fijo para valores más grandes.

  3. Componente GasFeeCard:- Usa useSDK para obtener el chainId actual de Ethereum.

  • Se inicializan las variables de estado countdown y currentChainId.

  • El primer hook useEffect actualiza el ID de cadena actual y activa la nueva obtención de datos (mutate) cuando el ID de cadena cambia. También reinicia el temporizador de cuenta regresiva.

  • El segundo useEffect configura un intervalo que decrementa la cuenta regresiva y activa una nueva obtención de datos cuando esta llega a cero. Limpia el intervalo al desmontar el componente.

  • fetcher: Una función que obtiene datos de gas desde un endpoint de API del lado del servidor, pasando el ID de cadena actual como parámetro de consulta.

  • useSWR: Este hook se utiliza para obtener datos de gas mediante la función fetcher y proporciona los estados de datos, carga y error.

  1. Lógica de renderizado:- El componente renderiza condicionalmente los estados de carga y error.

  • trendingUp y trendingDown son valores booleanos determinados por el baseFeeTrend de los datos de gas obtenidos.

  • La interfaz principal consiste en un HoverCard con un CircularProgressbarWithChildren que indica el progreso de la cuenta regresiva y un ícono de flecha que muestra la tendencia del precio del gas. Muestra la tarifa base estimada en Gwei.

  • El HoverCardContent muestra información más detallada sobre la tarifa base estimada y el percentil histórico de la tarifa base.

  1. Estilos:- El código utiliza clases de Tailwind CSS para los estilos (por ejemplo, text-red-500, cursor-pointer).

  • El CircularProgressbarWithChildren cambia de color según la tendencia del precio del gas (rojo para valores en aumento, verde para valores en descenso).

También utilizamos los íconos ArrowUp, ArrowDown e InfoIcon de lucide-react. Ahora solo queda importar este componente en el componente GetTicket.tsx para que se muestre debajo del precio total de las entradas.

Continúa desarrollando con la API de Gas de MetaMask

Conocer el precio del gas permite a los compradores elegir el momento más rentable para realizar su compra, pudiendo ahorrar dinero al realizar transacciones durante períodos de precios de gas más bajos. Para una plataforma de venta de entradas para eventos, ofrecer esta información mejora la transparencia y la satisfacción del cliente, ya que los usuarios sienten mayor control sobre su gasto y pueden evitar cargos inesperados.

Hemos implementado la API de Gas en una dapp de venta de entradas para eventos ya existente. Puedes encontrar el código de este tutorial aquí.

También te recomiendo consultar los siguientes recursos:

Documentación de la API de Gas: https://docs.infura.io/infura-expansion-apis/gas-api

Plantilla de la API de Gas: https://github.com/meowyx/GasAPI-Template

¡Feliz desarrollo! 🚀

Traducido por IA. Puede contener errores. Por favor, verifique siempre la información.

Califica la traducción

Este artículo fue escrito por: