How to Implement the MetaMask Gas API in an Event Ticketing Dapp
In this blog, we explored implementing the MetaMask Gas API into an existing ticketing dApp built with Next.js and ShadcnUI.
Understanding the gas price before purchasing blockchain-based event tickets is crucial for both cost transparency and effective financial planning. In the context of blockchain transactions, 'gas' refers to the fee required to successfully conduct a transaction or execute a contract on the blockchain.
This fee fluctuates based on network demand. By knowing the gas price in advance, ticket buyers can make more informed decisions about the total cost of their purchase. This is especially important as the gas fee can significantly add to the overall cost of the ticket, particularly during times of high network congestion.
In this tutorial, we will implement the MetaMask Gas API in an existing event ticketing dapp, enabling users to see the gas prices while buying tickets for events.
Prerequisites and dependencies
- A valid Web3 API key and API key secret.
- Node.js and either npm or yarn installed.
- Familiarity with Next.js and React.
For the frontend framework, we will use Next.js, and for the UI, we will use ShadcnUI. Let’s install the following ShadcnUI components:
npx shadcn-ui@latest add hover-card
npm i swr
The above code will add the hover-card component that we will use for displaying the gas prices and install the SWR React hooks library. The Stale-While-Revalidate (SWR) library helps with fetching data from the internet. It works by first using old data it has saved, then requesting new data, and finally replacing the old data with this new information. This makes websites faster and more up-to-date.
Next, we'll install the react-circular-progressbar component to display circular progress indicators, which are useful for showing the progress of tasks or loading states in web applications. It's customizable and implemented using SVG for high-quality graphics.
npm i react-circular-progressbar
Implementing the Gas API
In the app/web directory, let's create the following folder: app/api/gas. Inside, let's make a route.ts file and add the following code:
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,
});
}
Let’s see what we did here:
- Extracting Query Parameters:
const { searchParams } = new URL(request.url);
: Extracts the search parameters from the request URL.const chainId = searchParams.get("chainId");
: Retrieves thechainId
parameter from the URL, which is used to specify the Ethereum network.
- Setting Up Authorization:
- Environment variables
INFURA_API_KEY
andINFURA_API_SECRET
are combined into a Buffer, then encoded into a Base64 string. This string is used for basic HTTP authentication when making requests to the Infura API.
- Environment variables
- Fetching Gas Prices and Base Fee Percentile Data:
- Two asynchronous fetch requests are made to the Infura API:
gasPricesResp
fetches suggested gas fees for the specified Ethereum network (chainId
).baseFeePercentileRes
fetches the base fee percentile for the same network.
- The
Authorization
header is set with the Base64-encoded credentials.
- Two asynchronous fetch requests are made to the Infura API:
- Parsing and Returning Data:
- The responses from both fetch requests are converted to JSON.
NextResponse.json({...})
creates and returns a JSON response containing the following data:estimatedBaseFee
: The estimated base fee for transactions.baseFeeTrend
: The trend of the base fee (e.g., increasing, decreasing).baseFeePercentile
: The percentile distribution of base fees.
Creating the user interface
We will create a component called GasFeeCard.tsx
and add the following code into it:
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>
);
}
Let’s take a look at what we did here:
- Constants:
REFETCH_INTERVAL
,STEP_INTERVAL
,NETWORK_LATENCY
: Constants for managing the data fetching interval, the step interval for countdown updates, and estimated network latency.
- Utility Function:
formatGweiValues
: A function to format gas price values into a readable format, using either exponential notation for small values or fixed-point notation for larger values.
- GasFeeCard Component:
- Uses
useSDK
to get the current EthereumchainId
. - State variables
countdown
andcurrentChainId
are initialized. - The first
useEffect
hook updates the current chain ID and triggers data re-fetching (mutate
) when the chain ID changes. It also resets the countdown timer. - The second
useEffect
sets up an interval that decrements the countdown and triggers a data re-fetch when the countdown reaches zero. It cleans up the interval on unmount. fetcher
: A function that fetches gas data from a server-side API endpoint, passing the current chain ID as a query parameter.useSWR
: This hook is used to fetch gas data using thefetcher
function and provides data, loading, and error states.
- Uses
- Rendering Logic:
- The component conditionally renders loading and error states.
trendingUp
andtrendingDown
are booleans determined by thebaseFeeTrend
from the fetched gas data.- The main UI consists of a
HoverCard
with aCircularProgressbarWithChildren
indicating the countdown progress and an arrow icon showing the gas price trend. It displays the estimated base fee in Gwei. - The
HoverCardContent
shows more detailed information about the estimated base fee and the historical base fee percentile.
- Styling:
- The code uses Tailwind CSS classes for styling (e.g.,
text-red-500
,cursor-pointer
). - The
CircularProgressbarWithChildren
is styled to change color based on the gas price trend (red for increasing, green for decreasing).
- The code uses Tailwind CSS classes for styling (e.g.,
We also used ArrowUp, ArrowDown, InfoIcon icons from lucide-react. Now, all that is left is to import this component into the GetTicket.tsx component, so it will display below the total ticket prices.
Continue building with the MetaMask Gas API
Awareness of the gas price enables buyers to choose the most cost-effective time to make their purchase, potentially saving money by transacting during periods of lower gas prices. For an event ticketing platform, providing this information enhances transparency and customer satisfaction, as users feel more in control of their spending and can avoid unexpected fees.
We have implemented the Gas API into an existing event ticketing dapp. You can find the code for this tutorial here
I also recommend checking the following resources:
Gas API Docs: https://docs.infura.io/infura-expansion-apis/gas-api
Gas API Template: https://github.com/meowyx/GasAPI-Template
Happy Building 🚀
Keep reading our latest stories
Developers, security news, and more