Zum Inhalt springen

So implementierst du die MetaMask Gas API in einer Event-Ticketing-Dapp

In diesem Blog haben wir die Implementierung der MetaMask Gas API in eine bestehende Ticketing-dApp untersucht, die mit Next.js und ShadcnUI erstellt wurde.

6 Min. Lesezeit
So implementierst du die MetaMask Gas API in einer Event-Ticketing-Dapp

Das Verständnis des Gaspreises vor dem Kauf von blockchain-basierten Veranstaltungstickets ist entscheidend für Kostentransparenz und effektive Finanzplanung. Im Kontext von Blockchain-Transaktionen bezeichnet „Gas" die Gebühr, die erforderlich ist, um eine Transaktion erfolgreich durchzuführen oder einen Vertrag auf der Blockchain auszuführen.

Diese Gebühr schwankt je nach Netzwerkauslastung. Wenn Ticketkäufer den Gaspreis im Voraus kennen, können sie fundiertere Entscheidungen über die Gesamtkosten ihres Kaufs treffen. Dies ist besonders wichtig, da die Gasgebühr die Gesamtkosten des Tickets erheblich erhöhen kann – insbesondere in Zeiten hoher Netzwerküberlastung.

In diesem Tutorial implementieren wir die MetaMask Gas API in eine bestehende Event-Ticketing-Dapp und ermöglichen es Nutzern, die Gaspreise beim Kauf von Tickets für Veranstaltungen einzusehen.

Voraussetzungen und Abhängigkeiten

Als Frontend-Framework verwenden wir Next.js und für die Benutzeroberfläche ShadcnUI. Installieren wir die folgenden ShadcnUI-Komponenten:

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

Der obige Code fügt die Hover-Card-Komponente hinzu, die wir zur Anzeige der Gaspreise verwenden werden, und installiert die SWR React Hooks-Bibliothek. Die Stale-While-Revalidate (SWR)-Bibliothek unterstützt das Abrufen von Daten aus dem Internet. Sie funktioniert, indem sie zunächst bereits gespeicherte alte Daten verwendet, dann neue Daten anfordert und schließlich die alten Daten durch die neuen Informationen ersetzt. Dies macht Websites schneller und aktueller.

Als Nächstes installieren wir die Komponente react-circular-progressbar, um kreisförmige Fortschrittsanzeigen darzustellen. Diese sind nützlich, um den Fortschritt von Aufgaben oder Ladezuständen in Webanwendungen anzuzeigen. Die Komponente ist anpassbar und wird mithilfe von SVG für hochwertige Grafiken implementiert.

npm i react-circular-progressbar

Implementierung der Gas API

Im Verzeichnis app/web erstellen wir den folgenden Ordner: app/api/gas. Darin legen wir eine Datei route.ts an und fügen den folgenden Code hinzu:

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,
  });
}
Kopieren

Schauen wir uns an, was wir hier gemacht haben:

  1. Extrahieren von Query-Parametern:- const { searchParams } = new URL(request.url);: Extrahiert die Suchparameter aus der Anfrage-URL.

  • const chainId = searchParams.get("chainId");: Ruft den Parameter chainId aus der URL ab, der zur Angabe des Ethereum-Netzwerks verwendet wird.

  1. Einrichten der Autorisierung:- Die Umgebungsvariablen INFURA_API_KEY und INFURA_API_SECRET werden in einem Buffer zusammengeführt und anschließend in einen Base64-String kodiert. Dieser String wird für die einfache HTTP-Authentifizierung bei Anfragen an die Infura API verwendet.

  2. Abrufen von Gaspreisen und Base-Fee-Perzentil-Daten:- Es werden zwei asynchrone Fetch-Anfragen an die Infura API gestellt:- gasPricesResp ruft empfohlene Gasgebühren für das angegebene Ethereum-Netzwerk (chainId) ab.

  • baseFeePercentileRes ruft das Base-Fee-Perzentil für dasselbe Netzwerk ab.

  • Der Authorization-Header wird mit den Base64-kodierten Anmeldedaten gesetzt.

  1. Verarbeiten und Zurückgeben der Daten:- Die Antworten beider Fetch-Anfragen werden in JSON umgewandelt.

  • NextResponse.json({...}) erstellt und gibt eine JSON-Antwort zurück, die folgende Daten enthält:- estimatedBaseFee: Die geschätzte Basisgebühr für Transaktionen.

  • baseFeeTrend: Der Trend der Basisgebühr (z. B. steigend, fallend).

  • baseFeePercentile: Die Perzentilverteilung der Basisgebühren.

Erstellen der Benutzeroberfläche

Wir erstellen eine Komponente namens GasFeeCard.tsx und fügen den folgenden Code ein:


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>
  );
}
Kopieren

Schauen wir uns an, was wir hier gemacht haben:

  1. Konstanten:- REFETCH_INTERVAL, STEP_INTERVAL, NETWORK_LATENCY: Konstanten zur Verwaltung des Datenabrufintervalls, des Schrittintervalls für Countdown-Aktualisierungen und der geschätzten Netzwerklatenz.

  2. Hilfsfunktion:- formatGweiValues: Eine Funktion zur Formatierung von Gaspreisen in ein lesbares Format – entweder in Exponentialschreibweise für kleine Werte oder in Festkommaschreibweise für größere Werte.

  3. GasFeeCard-Komponente:- Verwendet useSDK, um die aktuelle Ethereum-chainId abzurufen.

  • Die Zustandsvariablen countdown und currentChainId werden initialisiert.

  • Der erste useEffect-Hook aktualisiert die aktuelle Chain-ID und löst ein erneutes Abrufen der Daten (mutate) aus, wenn sich die Chain-ID ändert. Außerdem setzt er den Countdown-Timer zurück.

  • Der zweite useEffect richtet ein Intervall ein, das den Countdown dekrementiert und einen erneuten Datenabruf auslöst, wenn der Countdown null erreicht. Das Intervall wird beim Unmount bereinigt.

  • fetcher: Eine Funktion, die Gasdaten von einem serverseitigen API-Endpunkt abruft und die aktuelle Chain-ID als Query-Parameter übergibt.

  • useSWR: Dieser Hook wird verwendet, um Gasdaten mithilfe der fetcher-Funktion abzurufen, und liefert Daten-, Lade- und Fehlerzustände.

  1. Rendering-Logik:- Die Komponente rendert Lade- und Fehlerzustände bedingt.

  • trendingUp und trendingDown sind boolesche Werte, die durch den baseFeeTrend aus den abgerufenen Gasdaten bestimmt werden.

  • Die Hauptbenutzeroberfläche besteht aus einer HoverCard mit einem CircularProgressbarWithChildren, der den Countdown-Fortschritt anzeigt, sowie einem Pfeilsymbol, das den Gaspreistend anzeigt. Der geschätzte Basispreis wird in Gwei angezeigt.

  • Der HoverCardContent zeigt detailliertere Informationen über die geschätzte Basisgebühr und das historische Base-Fee-Perzentil.

  1. Styling:- Der Code verwendet Tailwind-CSS-Klassen für das Styling (z. B. text-red-500, cursor-pointer).

  • Der CircularProgressbarWithChildren ist so gestaltet, dass er die Farbe basierend auf dem Gaspreistend ändert (Rot für steigend, Grün für fallend).

Wir haben außerdem die Symbole ArrowUp, ArrowDown und InfoIcon aus lucide-react verwendet. Jetzt müssen wir diese Komponente nur noch in die Komponente GetTicket.tsx importieren, damit sie unterhalb der Gesamtticketpreise angezeigt wird.

Weiterentwicklung mit der MetaMask Gas API

Das Bewusstsein für den Gaspreis ermöglicht es Käufern, den kostengünstigsten Zeitpunkt für ihren Kauf zu wählen und potenziell Geld zu sparen, indem sie in Zeiten niedrigerer Gaspreise Transaktionen durchführen. Für eine Event-Ticketing-Plattform erhöht die Bereitstellung dieser Informationen die Transparenz und Kundenzufriedenheit, da Nutzer das Gefühl haben, ihre Ausgaben besser kontrollieren zu können und unerwartete Gebühren zu vermeiden.

Wir haben die Gas API in eine bestehende Event-Ticketing-Dapp implementiert. Den Code für dieses Tutorial findest du hier.

Ich empfehle außerdem, die folgenden Ressourcen zu prüfen:

Gas API Docs: https://docs.infura.io/infura-expansion-apis/gas-api

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

Viel Erfolg beim Entwickeln 🚀

KI-übersetzt. Kann Fehler enthalten. Bitte überprüfen Sie die Informationen stets.

Bewerten Sie die Übersetzung

Dieser Artikel wurde verfasst von: