Building a Real-time EVM Gas Fee Calculator with React and Wagmi

Building a Real-time EVM Gas Fee Calculator with React and Wagmi

February 10, 2025

16 min read

Building a Real-time EVM Gas Fee Calculator with React and Wagmi

When making a transaction on an EVM chain such as Ethereum, you need to specify three key parameters: gas limit, max fee per gas, and max priority fee per gas. These parameters are crucial for determining transaction costs and priority. In this post, we'll explore how to efficiently calculate these values using Viem and Wagmi libraries. You can experiment with the live demo here, and review the complete source code here.

Fee estimation
Fee estimation

Building the Fee Calculator Interface

Let's explore a practical implementation of gas fee calculation by building a simple web page. We'll create a user interface that displays different components of the gas fee and analyze the code step by step to understand the underlying mechanics.

import { PageMetaTags } from "@lib/next-ui/metadata/PageMetaTags"
import styled from "styled-components"
import { centeredContentColumn } from "@lib/ui/css/centeredContentColumn"
import { verticalPadding } from "@lib/ui/css/verticalPadding"
import { WebsiteNavigation } from "@lib/ui/website/navigation/WebsiteNavigation"
import { ProductLogo } from "../product/ProductLogo"
import { VStack } from "@lib/ui/css/stack"
import { MaxPriorityFeePerGas } from "./maxPriorityFeePerGas/MaxPriorityFeePerGas"
import { MaxFeePerGas } from "./maxFeePerGas/MaxFeePerGas"
import { BaseFee } from "./baseFee/BaseFee"
import { MaxFee } from "./maxFee/MaxFee"

export const PageContainer = styled.div`
  ${centeredContentColumn({
    contentMaxWidth: 520,
  })}

  ${verticalPadding(80)}
`

export const FeePage = () => (
  <>
    <PageMetaTags
      title="EVM Gas Fee Calculator | Real-time Gas Price Monitoring"
      description="Monitor real-time Ethereum gas fees, including max priority fees, base fees, and max fees per gas. Get accurate estimates for your EVM transactions."
    />

    <WebsiteNavigation logo={<ProductLogo />}>
      <PageContainer>
        <VStack gap={40}>
          <MaxFee />
          <MaxPriorityFeePerGas />
          <MaxFeePerGas />
          <BaseFee />
        </VStack>
      </PageContainer>
    </WebsiteNavigation>
  </>
)

Understanding Priority Fees

Since the MaxFee component combines all fee components into a final value, we'll examine it last. Let's start by understanding the maxPriorityFeePerGas parameter, which is a crucial part of the overall transaction fee structure.

import { VStack } from "@lib/ui/css/stack"
import { PriorityOptions } from "./options/PriorityOptions"
import { FeeSection } from "../FeeSection"
import { MaxPriorityFeePerGasTitle } from "./MaxPriorityFeePerGasTitle"
import { FeeChart } from "./chart/FeeChart"

export const MaxPriorityFeePerGas = () => {
  return (
    <FeeSection title={<MaxPriorityFeePerGasTitle />}>
      <VStack gap={40}>
        <PriorityOptions />
        <FeeChart />
      </VStack>
    </FeeSection>
  )
}

Priority Fee Levels

MaxPriorityFeePerGas, often referred to as the "priority fee," is the extra tip users include in their transactions to incentivize miners to process them faster. Unlike the base fee—which is determined by the network to reflect current demand—the maxPriorityFeePerGas is set by the user to ensure quicker transaction confirmations, especially during periods of high congestion.

In most Web3 applications, when initiating a transaction, users are presented with priority options for their transaction fees. This feature allows users to strategically choose between lower fees for non-urgent transactions and higher fees when faster confirmation is needed. The priority selection directly influences the maxPriorityFeePerGas parameter, giving users control over their transaction costs and confirmation speed.

export const feePriorities = ["low", "medium", "high"] as const

export type FeePriority = (typeof feePriorities)[number]

export const feePriorityPercentiles: Record<FeePriority, number> = {
  low: 10,
  medium: 50,
  high: 90,
}

export const defaultFeePriority = "medium" as const

Historical Fee Analysis

To determine the maxPriorityFeePerGas value, we analyze the historical priority fees paid by users in the last 10 blocks. This approach gives us a reliable estimate based on recent network activity. We fetch this data using Wagmi's useFeeHistory hook, which retrieves fee information for different priority levels (low, medium, high). Each priority level corresponds to a specific percentile of fees paid - 10th percentile for low priority, 50th for medium, and 90th for high. This statistical approach ensures our estimates reflect the actual market conditions for transaction inclusion.

import { feePriorityPercentiles } from "../maxPriorityFeePerGas/core/FeePriority"

import { useFeeHistory } from "wagmi"
import { feePriorities } from "../maxPriorityFeePerGas/core/FeePriority"
import { useTransformQueryData } from "@lib/ui/query/hooks/useTransformQueryData"
import { GetFeeHistoryReturnType } from "viem"
import { shouldBePresent } from "@lib/utils/assert/shouldBePresent"
import { arraysToRecord } from "@lib/utils/array/arraysToRecord"

export type PriorityFeeTimeseries = Record<
  (typeof feePriorities)[number],
  bigint[]
>

const transform = ({
  reward,
}: GetFeeHistoryReturnType): PriorityFeeTimeseries =>
  arraysToRecord(
    feePriorities,
    shouldBePresent(reward).reduce(
      (acc, curr) => {
        return acc.map((value, index) => [...value, curr[index]])
      },
      feePriorities.map(() => [] as bigint[]),
    ),
  )

export const usePriorityFeeTimeseriesQuery = () => {
  const query = useFeeHistory({
    blockCount: 10,
    rewardPercentiles: feePriorities.map((key) => feePriorityPercentiles[key]),
  })

  return useTransformQueryData(query, transform)
}

The Wagmi hook returns a nested array structure representing fee history data. Each inner array corresponds to a specific priority level (low, medium, high). To make this data more accessible and type-safe, we transform it into a structured record. The resulting format is a Record where each key is a priority level (e.g., "low", "medium", "high") and the corresponding value is an array of bigint values. This array contains the historical priority fees for that level, making it easier to analyze trends and calculate estimates for each priority tier.

To handle the transformation of query results efficiently and safely, we've created a custom hook called useTransformQueryData. This hook is designed to transform raw query data into a more useful format while preserving the query's error handling capabilities. It's particularly useful when working with complex data structures like our fee history, where we need to reshape the data without losing the original query's metadata. The hook uses React's useMemo to optimize performance by memoizing the transformed result:

import { useMemo } from "react"
import { Query } from "../Query"

type QueryBase<T> = Pick<Query<T>, "data" | "error">

export const useTransformQueryData = <
  TInput,
  TOutput,
  TExtra extends object = {},
>(
  queryResult: QueryBase<TInput> & TExtra,
  transform: (data: TInput) => TOutput,
): QueryBase<TOutput> & Omit<TExtra, keyof QueryBase<TOutput>> => {
  return useMemo(() => {
    try {
      return {
        ...queryResult,
        data:
          queryResult.data !== undefined
            ? transform(queryResult.data)
            : undefined,
      }
    } catch (error) {
      return {
        ...queryResult,
        data: undefined,
        error,
      }
    }
  }, [queryResult, transform])
}

To display both historical and current priority fees, we need two data transformations. First, we use the hook to transform the raw fee history into a chart-friendly format. Second, to show the current fee for each priority level, we calculate the average of the historical values. This is accomplished by applying useTransformQueryData again, this time using recordMap to compute averages across each priority level's time series data. This dual transformation approach gives us both the temporal view needed for the chart and the current snapshot needed for immediate fee estimates.

import { recordMap } from "@lib/utils/record/recordMap"
import {
  PriorityFeeTimeseries,
  usePriorityFeeTimeseriesQuery,
} from "./usePriorityFeeTimeseriesQuery"
import { useTransformQueryData } from "@lib/ui/query/hooks/useTransformQueryData"
import { bigintAverage } from "@lib/utils/math/bigint/bigintAverage"

const transform = (timeseries: PriorityFeeTimeseries) =>
  recordMap(timeseries, bigintAverage)

export const usePriorityFeesQuery = () => {
  const query = usePriorityFeeTimeseriesQuery()

  return useTransformQueryData(query, transform)
}

For cases where we only need the fee for a specific priority level (defaulting to medium), the usePriorityFeeQuery hook provides a streamlined way to access a single priority fee value. This hook builds on top of usePriorityFeesQuery but extracts just the requested priority level's fee:

import { useTransformQueryData } from "@lib/ui/query/hooks/useTransformQueryData"
import {
  defaultFeePriority,
  FeePriority,
} from "../maxPriorityFeePerGas/core/FeePriority"
import { useCallback } from "react"
import { usePriorityFeesQuery } from "./usePriorityFeesQuery"

export const usePriorityFeeQuery = (
  priority: FeePriority = defaultFeePriority,
) => {
  const query = usePriorityFeesQuery()

  return useTransformQueryData(
    query,
    useCallback((result) => result[priority], [priority]),
  )
}

Having established our query infrastructure for priority fees, let's examine the first component in our implementation: the MaxPriorityFeePerGasTitle. This component serves as a section header that dynamically displays the current average priority fee. It leverages the MatchQuery component from RadzionKit for handling the asynchronous data state - rendering a Spinner during loading and the formatted fee value once available.

import { gwei } from "@lib/chain/evm/utils/gwei"
import { fromChainAmount } from "@lib/chain/utils/fromChainAmount"
import { Spinner } from "@lib/ui/loaders/Spinner"
import { MatchQuery } from "@lib/ui/query/components/MatchQuery"
import { formatAmount } from "@lib/utils/formatAmount"
import { usePriorityFeeQuery } from "../queries/usePriorityFeeQuery"
import { Text } from "@lib/ui/text"

export const MaxPriorityFeePerGasTitle = () => {
  const query = usePriorityFeeQuery()

  return (
    <>
      maxPriorityFeePerGas{" "}
      <MatchQuery
        value={query}
        pending={() => <Spinner />}
        success={(value) => (
          <Text as="span" color="contrast">
            {" = "}
            {formatAmount(fromChainAmount(value, gwei.decimals))} {gwei.name}
          </Text>
        )}
      />
    </>
  )
}

The next crucial component in our implementation is the PriorityOptions, which provides a visual representation of priority fee options. This component displays three priority levels (low, medium, high), each with its own color indicator and current fee value.

import styled from "styled-components"
import { feePriorities } from "../core/FeePriority"
import { HStack } from "@lib/ui/css/stack"
import { gwei } from "@lib/chain/evm/utils/gwei"
import { getFeePriorityColor } from "../utils/getFeePriorityColor"
import { Text } from "@lib/ui/text"
import { useTheme } from "styled-components"
import { formatAmount } from "@lib/utils/formatAmount"
import { fromChainAmount } from "@lib/chain/utils/fromChainAmount"
import { sameDimensions } from "@lib/ui/css/sameDimensions"
import { round } from "@lib/ui/css/round"
import { capitalizeFirstLetter } from "@lib/utils/capitalizeFirstLetter"
import { usePriorityFeesQuery } from "../../queries/usePriorityFeesQuery"
import { Spinner } from "@lib/ui/loaders/Spinner"
import { MatchQuery } from "@lib/ui/query/components/MatchQuery"

const Identifier = styled.div`
  ${sameDimensions(8)}
  ${round}
`

export const PriorityOptions = () => {
  const theme = useTheme()
  const query = usePriorityFeesQuery()

  return (
    <HStack
      fullWidth
      justifyContent="space-between"
      wrap="wrap"
      alignItems="center"
      gap={20}
    >
      {feePriorities.map((priority) => (
        <HStack alignItems="center" gap={8} key={priority}>
          <HStack alignItems="center" gap={6}>
            <Identifier
              style={{
                background: getFeePriorityColor(theme, priority).toCssValue(),
              }}
            />
            <Text color="supporting">{capitalizeFirstLetter(priority)}</Text>
          </HStack>
          <MatchQuery
            value={query}
            pending={() => <Spinner />}
            success={(value) => (
              <Text>
                {formatAmount(fromChainAmount(value[priority], gwei.decimals))}{" "}
                {gwei.name}
              </Text>
            )}
          />
        </HStack>
      ))}
    </HStack>
  )
}

To better understand fee trends, we can visualize how priority fees have fluctuated across the last 10 blocks using an interactive chart.

import { Spinner } from "@lib/ui/loaders/Spinner"
import { usePriorityFeeTimeseriesQuery } from "../../queries/usePriorityFeeTimeseriesQuery"
import { FeeChartContent } from "./FeeChartContent"
import { MatchQuery } from "@lib/ui/query/components/MatchQuery"
import { recordMap } from "@lib/utils/record/recordMap"

export const FeeChart = () => {
  const query = usePriorityFeeTimeseriesQuery()

  return (
    <MatchQuery
      value={query}
      pending={() => <Spinner />}
      success={(value) => {
        return (
          <FeeChartContent
            value={recordMap(value, (array) => array.map(Number))}
          />
        )
      }}
    />
  )
}

The FeeChartContent component handles the visualization of priority fee trends using our custom chart implementation. This component leverages various utility functions and styled components to create a responsive line chart. It normalizes the fee data arrays, generates appropriate Y-axis labels in Gwei units, and renders three overlapping line charts - one for each priority level (low, medium, high). Each line chart is color-coded to match its corresponding priority level and includes a gradient fill for better visual distinction. The chart is wrapped in an ElementSizeAware component to ensure proper responsiveness, and it includes horizontal grid lines for easier value comparison. For a detailed explanation of the chart implementation itself, you can refer to a dedicated line chart blog post at /blog/linechart.

import { ElementSizeAware } from "@lib/ui/base/ElementSizeAware"
import { VStack } from "@lib/ui/css/stack"
import { normalizeDataArrays } from "@lib/utils/math/normalizeDataArrays"
import { generateYLabels } from "@lib/ui/charts/utils/generateYLabels"
import { ChartYAxis } from "@lib/ui/charts/ChartYAxis"
import { useTheme } from "styled-components"
import { ChartHorizontalGridLines } from "@lib/ui/charts/ChartHorizontalGridLines"
import { LineChart } from "@lib/ui/charts/LineChart"
import { ValueProp } from "@lib/ui/props"
import { formatUnits } from "viem"
import { feePriorities, FeePriority } from "../core/FeePriority"
import { feeChartConfig } from "./config"
import { ChartSlice } from "@lib/ui/charts/ChartSlice"
import { ChartLabel } from "@lib/ui/charts/ChartLabel"
import { getFeePriorityColor } from "../utils/getFeePriorityColor"
import { TakeWholeSpaceAbsolutely } from "@lib/ui/css/takeWholeSpaceAbsolutely"
import { gwei } from "@lib/chain/evm/utils/gwei"

type FeeChartProps = ValueProp<Record<FeePriority, number[]>>

export const FeeChartContent = ({ value }: FeeChartProps) => {
  const theme = useTheme()
  const yLabels = generateYLabels({ data: Object.values(value).flat() })

  const normalized = normalizeDataArrays({
    ...value,
    yLabels,
  })

  return (
    <ElementSizeAware
      render={({ setElement, size }) => {
        const contentWidth = size
          ? size.width - feeChartConfig.yLabelsWidth
          : undefined

        return (
          <VStack flexGrow gap={20} ref={setElement}>
            <ChartSlice yLabelsWidth={feeChartConfig.yLabelsWidth}>
              <ChartYAxis
                renderLabel={(index) => (
                  <ChartLabel key={index}>
                    {formatUnits(BigInt(yLabels[index]), gwei.decimals)}
                  </ChartLabel>
                )}
                data={normalized.yLabels}
              />
              <VStack
                style={{
                  position: "relative",
                  minHeight: feeChartConfig.chartHeight,
                }}
                fullWidth
              >
                {contentWidth &&
                  feePriorities.map((key) => {
                    const data = normalized[key]
                    return (
                      <TakeWholeSpaceAbsolutely key={key}>
                        <LineChart
                          key={key}
                          dataPointsConnectionKind="sharp"
                          fillKind={"gradient"}
                          data={data}
                          width={contentWidth}
                          height={feeChartConfig.chartHeight}
                          color={getFeePriorityColor(theme, key)}
                        />
                      </TakeWholeSpaceAbsolutely>
                    )
                  })}
                <ChartHorizontalGridLines data={normalized.yLabels} />
              </VStack>
            </ChartSlice>
          </VStack>
        )
      }}
    />
  )
}

Visualizing Fee Trends

To better understand fee trends, we can visualize how priority fees have fluctuated across the last 10 blocks using an interactive chart.

Maximum Fee Per Gas

Now that we understand how to calculate and visualize the priority fee, let's examine another crucial parameter in EVM transaction fees: the maxFeePerGas. This parameter represents the absolute maximum amount per unit of gas that a user is willing to pay for their transaction. It's calculated as the sum of the maxPriorityFeePerGas (the tip to validators) and the baseFee (the network's base cost). The MaxFeePerGas component below displays this relationship in a clear formula and provides additional context about how this parameter helps manage transaction costs:

import { ShyInfoBlock } from "@lib/ui/info/ShyInfoBlock"
import { Text } from "@lib/ui/text"
import { FeeSection } from "../FeeSection"
import { toPercents } from "@lib/utils/toPercents"
import { baseFeeMultiplier } from "../baseFee/config"

export const MaxFeePerGas = () => (
  <FeeSection
    title={
      <>
        maxFeePerGas
        <Text as="span" color="contrast">
          {" "}
          = maxPriorityFeePerGas + baseFee × {baseFeeMultiplier}
        </Text>
      </>
    }
  >
    <ShyInfoBlock>
      <Text color="supporting">
        We multiply the base fee by {baseFeeMultiplier} to account for the
        maximum allowed {toPercents(baseFeeMultiplier - 1)} increase in the base
        fee between blocks. This buffer helps ensure that your transaction
        remains competitive even if the network congestion causes the base fee
        to rise unexpectedly.
      </Text>
    </ShyInfoBlock>
  </FeeSection>
)

Base Fee Dynamics

In EVM-based networks, the baseFee is dynamically adjusted by the protocol based on network demand. When blocks are more than 50% full, the base fee increases by up to 12.5% per block, and when they're less than 50% full, it decreases by the same maximum percentage. This mechanism helps regulate network congestion by making transactions more expensive during high-demand periods and cheaper during low-demand periods. To account for this potential increase between blocks, web3 applications typically multiply the current baseFee by a safety factor. In our implementation, we use a multiplier of 1.125 (12.5%), which matches the maximum possible increase in base fee between blocks. This approach provides a buffer against base fee fluctuations, reducing the likelihood of transaction failures due to insufficient gas fees while the transaction is pending in the mempool.

export const baseFeeMultiplier = 1.125

Real-time Fee Monitoring

To monitor the current base fee, we've implemented the useBaseFeeQuery hook that leverages Wagmi's useBlock hook with the watch option enabled. This setup allows us to continuously track the base fee as new blocks are produced. The hook extracts the baseFeePerGas value from the latest block data, ensuring we always have access to the most up-to-date network fee information. Since the base fee is an optional parameter in some EVM chains, we use the shouldBePresent utility to handle type safety, ensuring our application only works with chains that implement EIP-1559 fee mechanics.

import { useBlock } from "wagmi"
import { useTransformQueryData } from "@lib/ui/query/hooks/useTransformQueryData"
import { shouldBePresent } from "@lib/utils/assert/shouldBePresent"
import { Block } from "viem"

const transform = (data: Block): bigint => {
  return shouldBePresent(data.baseFeePerGas)
}

export const useBaseFeeQuery = () => {
  const query = useBlock({
    watch: true,
  })

  return useTransformQueryData(query, transform)
}

Base Fee Component

The BaseFee component provides users with real-time visibility into the network's current base fee and explains its dynamic nature. Similar to our other fee components, it uses the FeeSection layout and displays the current base fee value in Gwei using the MatchQuery component for handling loading states. The component includes an informative description that helps users understand how the base fee automatically adjusts based on network demand, making it clear why transaction costs may vary over time.

import { FeeSection } from "../FeeSection"
import { MatchQuery } from "@lib/ui/query/components/MatchQuery"
import { Text } from "@lib/ui/text"
import { ShyInfoBlock } from "@lib/ui/info/ShyInfoBlock"
import { gwei } from "@lib/chain/evm/utils/gwei"
import { formatAmount } from "@lib/utils/formatAmount"
import { fromChainAmount } from "@lib/chain/utils/fromChainAmount"
import { useBaseFeeQuery } from "../queries/useBaseFeeQuery"
import { Spinner } from "@lib/ui/loaders/Spinner"

export const BaseFee = () => {
  const query = useBaseFeeQuery()

  return (
    <FeeSection
      title={
        <>
          baseFee
          <MatchQuery
            value={query}
            pending={() => <Spinner />}
            success={(value) => (
              <Text as="span" color="contrast">
                {" = "}
                {formatAmount(fromChainAmount(value, gwei.decimals))}{" "}
                {gwei.name}
              </Text>
            )}
          />
        </>
      }
    >
      <ShyInfoBlock>
        <Text color="supporting">
          The base fee is automatically determined by the network based on the
          demand for block space. When network activity increases, the base fee
          goes up to discourage congestion. When activity decreases, the base
          fee goes down to encourage network usage. This mechanism helps keep
          transaction fees at a reasonable level while ensuring network
          stability.
        </Text>
      </ShyInfoBlock>
    </FeeSection>
  )
}

Putting It All Together

Finally, let's bring all our fee calculations together in the MaxFee component to provide users with a practical example. This component calculates the total estimated cost for transferring 1 ETH on the Ethereum mainnet. It combines three key pieces of data: the current base fee (adjusted with our safety multiplier), the priority fee (using the medium priority level), and the estimated gas required for the transfer. Using Wagmi's useEstimateGas hook, we simulate the transaction to get an accurate gas estimate, while useAssetPriceQuery fetches the current ETH price to display the fee in USD. The component uses useTransformQueriesData to combine these data streams and calculate the total maximum fee, presenting it in both ETH and USD for better user understanding.

import { Text } from "@lib/ui/text"
import { formatAmount } from "@lib/utils/formatAmount"
import { useEstimateGas } from "wagmi"
import { MatchQuery } from "@lib/ui/query/components/MatchQuery"
import { useAssetPriceQuery } from "@lib/chain-ui/queries/useAssetPriceQuery"
import { toChainAmount } from "@lib/chain/utils/toChainAmount"
import { mainnet } from "viem/chains"
import { useBaseFeeQuery } from "../queries/useBaseFeeQuery"
import { useTransformQueriesData } from "@lib/ui/query/hooks/useTransformQueriesData"
import { usePriorityFeeQuery } from "../queries/usePriorityFeeQuery"
import { baseFeeMultiplier } from "../baseFee/config"
import { fromChainAmount } from "@lib/chain/utils/fromChainAmount"
import { Spinner } from "@lib/ui/loaders/Spinner"

const ethAmount = 1

export const MaxFee = () => {
  const baseFeeQuery = useBaseFeeQuery()
  const priorityFeeQuery = usePriorityFeeQuery()

  const gasQuery = useEstimateGas({
    account: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    value: toChainAmount(ethAmount, mainnet.nativeCurrency.decimals),
  })

  const priceQuery = useAssetPriceQuery({ id: "ethereum" })

  const maxFeeQuery = useTransformQueriesData(
    {
      baseFee: baseFeeQuery,
      priorityFee: priorityFeeQuery,
      gas: gasQuery,
    },
    ({ baseFee, priorityFee, gas }) =>
      fromChainAmount(
        (BigInt(Math.round(Number(baseFee) * baseFeeMultiplier)) +
          priorityFee) *
          gas,
        mainnet.nativeCurrency.decimals,
      ),
  )

  return (
    <Text
      color="contrast"
      height="l"
      as="h1"
      size={28}
      style={{ textTransform: "uppercase" }}
    >
      Estimated max fee to send {ethAmount} ETH:{" "}
      <MatchQuery
        value={maxFeeQuery}
        pending={() => <Spinner />}
        success={(maxFee) => {
          return (
            <>
              <Text as="span" color="primary">
                {formatAmount(maxFee)} ETH{" "}
                <MatchQuery
                  value={priceQuery}
                  success={(price) => {
                    return <>≈ ${formatAmount(price * maxFee)}</>
                  }}
                />
              </Text>
            </>
          )
        }}
      />
    </Text>
  )
}

Price Integration

To provide users with familiar cost references, we fetch the current Ethereum price from CoinGecko using the useAssetPriceQuery hook.

import { useQuery } from "@tanstack/react-query"
import {
  getAssetPrice,
  GetAssetPriceInput,
} from "../../chain/price/utils/getAssetPrice"

export const useAssetPriceQuery = (input: GetAssetPriceInput) => {
  return useQuery({
    queryKey: ["asset-price", input],
    queryFn: () => getAssetPrice(input),
  })
}

By combining Viem and Wagmi's powerful hooks with real-time data from the Ethereum network, we've created a comprehensive gas fee calculator that helps users make informed decisions about their transaction costs. The application not only provides current fee estimates but also visualizes historical trends and offers different priority levels to suit various transaction needs. This implementation demonstrates how modern web3 libraries can be leveraged to create intuitive interfaces for complex blockchain interactions.