Build a Blockchain explorer with Solana and Next.js
Tags: blockchain, solana, nextjs
Published: 2023-03-12 21:38:58

by [Samaila Bala](https://blog.openreplay.com/authors/samaila-bala)

A blockchain explorer is a tool that allows users to search, view, and verify the contents of a blockchain. Many blockchain explorers, such as Bitcoin, Ethereum, and Solana, are available.

The Solana Blockchain is a high-performance blockchain platform that supports large-scale decentralized applications. Some use cases for the Solana blockchain include decentralized finance (DeFi), non-fungible tokens (NFTs), gaming, and social media.

This article will look at building a blockchain explorer for the Solana blockchain using Next.js.


## Setting up a Next.js Project

We will be building a Next.js application to interface with the Solana Blockchain. Next.js is a framework for building React applications. It is a popular choice for building React applications as it comes with so many features out of the box. This includes:

- File-based routing
- Server-side rendering
- Static site generation
- Automatic code splitting

To create a Next.js app, ensure you have Node.js v14.16.0 or newer installed on your machine. Once that is confirmed, open a terminal and run the code below.

```shell
npx create-next-app@latest
```

The above command bootstraps a Next.js application. You will be asked to 
- Provide a name for the application 
- Choose between Typescript and Javascript for bootstrapping the application
- Installing Eslint

After installation, navigate to the directory of the newly created application and run the code below to start the application.

```shell
npm run dev
```

Open a browser and navigate to http://localhost:3000 to view the application.

![Next.js Welcome Page](https://blog.openreplay.com/images/build-a-blockchain-explorer-with-solana-and-next-js/images/TSWFBaF.png)


To style this project, we will use [Tailwind CSS](https://tailwindcss.com/), a utility CSS framework. Run the code below to add Tailwind CSS to the project.

```shell
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

The above command will install the Tailwind CSS library and create a `tailwind.config.js` file. Open the file in a code editor and replace the `content` property. Your config should be similar to the code below.

```javascript
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};
```

The `content` block helps tell tailwind the file directories to look for tailwind styles. Next, navigate to the styles directory and open the `global.css` file. Add the following imports at the top of the file.

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

We can now use tailwind in our project. Navigate to the `index.js` file in our `pages` directory and replace the code with the code below.


```jsx
import Head from "next/head";

export default function Home() {
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className="w-full h-full max-w-2xl p-6 flex flex-col items-center justify-between gap-6 mx-auto relative">
        <h1 className="text-2xl">Solana Blockchain Explorer</h1>
      </main>
    </>
  );
}
```

![Solana Blockchain Home page](https://blog.openreplay.com/images/build-a-blockchain-explorer-with-solana-and-next-js/images/s_51D34FAA89697243694AA782113F5488E594A5E1334181D40F85ACD4D7743A85_1672601231390_image.png)



## Building out Components

Before getting the transaction history, let's take a minute to build out the components we will use to display the transaction information.

Run the code below to install the libraries we will need

```shell
npm i axios date-fns
```
This will install [Axios](https://axios-http.com/), a promise-based data fetching library for JavaScript, and [date-fns](https://date-fns.org/), a library for manipulating JavaScript dates.

After installation, navigate to the root directory and create a `components` directory. In the `components` directory, create a `TransactionList.js` file and paste the code below.


```jsx
import React from "react";
import { fromUnixTime, format, formatDistanceToNow } from "date-fns";
import Link from "next/link";

const TransactionList = ({ transactionList, balance }) => {
  return (
    <div className="first-line:overflow-hidden transition-all duration-300 max-h-fit w-full h-full">
      {balance && (
        <h2 className="flex justify-between text-lg mb-4">
          Balance: <span>◎{balance}</span>
        </h2>
      )}
      {transactionList?.length > 0 && (
        <div className="overflow-x-auto">
          <table className="w-full border-spacing-x-4 -ml-4 border-separate">
            <thead className="text-left">
              <tr>
                <th className="font-medium">Signature</th>
                <th className="font-medium">Block</th>
                <th className="font-medium">Age</th>
                <th className="font-medium">Status</th>
              </tr>
            </thead>
            <tbody>
              {transactionList.map((transaction) => (
                <tr key={transaction?.signature}>
                  <td className="truncate max-w-[230px] text-blue-600 hover:underline">
                    <Link href={`/transaction/${transaction?.signature}`}>
                      {transaction?.signature}
                    </Link>
                  </td>
                  <td>{transaction?.slot}</td>
                  <td
                    className="whitespace-nowrap"
                    title={format(
                      fromUnixTime(transaction?.blockTime),
                      "MMMM d, yyyy 'at' HH:mm:ss OOOO"
                    )}
                  >
                    {formatDistanceToNow(fromUnixTime(transaction?.blockTime), {
                      includeSeconds: true,
                    })}
                  </td>
                  <td>
                    <span
                      className={`inline-block px-2 py-1 rounded-full text-xs font-bold leading-none text-white ${
                        transaction?.confirmationStatus === "finalized"
                          ? "bg-green-500"
                          : "bg-yellow-400"
                      }`}
                    >
                      {transaction?.confirmationStatus}
                    </span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
      {transactionList?.length <= 0 && (
        <div className="text-center">No transaction to display</div>
      )}
    </div>
  );
};

export default TransactionList;

```
This component will be used to display a list of transactions that will be gotten from the Solana blockchain. It takes a `transactionList` and `balance` as props and displays them on the UI.

In the `components` directory, create another file called `SearchTransactionForm.js` and paste the code below.

```jsx
import React from "react";

const SearchTransactionForm = ({
  handleFormSubmit,
  address,
  loading,
  setAddress,
  errorMessage,
}) => {
  return (
    <form onSubmit={handleFormSubmit} className="flex flex-wrap w-full">
      <label htmlFor="address" className="w-full shrink-0 text-lg mb-2">
        Transaction address
      </label>
      <input
        type="text"
        name="address"
        value={address}
        onChange={(event) => setAddress(event.target.value)}
        className="w-3/4 border-2 border-r-0 border-gray-500 h-12 rounded-l-lg px-4 focus:outline-none focus:border-blue-600 disabled:bg-gray-500 transition-colors duration-150"
        placeholder="CHrNmjoRzaGCL..."
        disabled={loading}
        required
      />
      <button
        type="submit"
        disabled={loading}
        className="flex-grow bg-blue-600 flex items-center justify-center rounded-r-lg text-white text-sm hover:bg-blue-900 disabled:bg-gray-500 transition-colors duration-150"
      >
        Search
      </button>
      {errorMessage && (
        <p className="text-red-600 text-base my-1">{errorMessage}</p>
      )}
    </form>
  );
};

export default SearchTransactionForm;

```
This component displays the Search input form. It allows the user to enter an address and submit, which is expected to return the list of transactions.

Lastly, paste the code below to create another file called `TransactionListDetail.js`.

```jsx
import React from "react";
import { fromUnixTime, format } from "date-fns";

const TransactionListDetail = ({ loading, transactionData }) => {
  return (
    <div className="w-full">
      {!loading && transactionData && (
        <div className="rounded-lg border max-w-xl overflow-x-auto mx-auto">
          <table className="table-auto w-full border-collapse p-4">
            <tbody className="overflow-x-scroll">
              <tr className="border-b">
                <td className="font-medium text-sm p-4">Signature</td>
                <td className="p-4">
                  {transactionData.transaction.signatures[0]}
                </td>
              </tr>
              <tr className="border-b">
                <td className="font-medium text-sm p-4">Timestamp</td>
                <td className="p-4">
                  {format(
                    fromUnixTime(transactionData?.blockTime),
                    "MMMM d, yyyy 'at' HH:mm:ss OOOO"
                  )}
                </td>
              </tr>
              <tr className="border-b">
                <td className="font-medium text-sm p-4">Recent Blockhash</td>
                <td className="p-4">
                  {transactionData.transaction.message.recentBlockhash}
                </td>
              </tr>
              <tr className="border-b">
                <td className="font-medium text-sm p-4">Slot</td>
                <td className="p-4">
                  {Intl.NumberFormat().format(transactionData.slot)}
                </td>
              </tr>
              <tr className="border-b">
                <td className="font-medium text-sm p-4">Fee</td>
                <td className="p-4">
                  ◎{transactionData.meta.fee / 1_000_000_000}
                </td>
              </tr>
              <tr className="border-b">
                <td className="font-medium text-sm p-4">Amount</td>
                <td className="p-4">
                  ◎
                  {transactionData.transaction.message.instructions[0].parsed
                    .info.lamports / 1_000_000_000}
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      )}
      {!loading && !transactionData && (
        <p className="text-center">No transaction to display</p>
      )}
    </div>
  );
};

export default TransactionListDetail;

```

We will use this component to display the details of a particular transaction. It will accept the `transactionData` as props and use its details to display on the UI.




<h2>Session Replay for Developers</h2> 
<p><em>
Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data
</em></p>
<img alt="OpenReplay" width="768" height="400" src="https://blog.openreplay.com/assets/overview_Z17Qdyg.png" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em>





## Get Transaction History from Solana

Now that we have our Next.js app up and running, the next step is adding Solana to our application. Luckily, Solana provides a well-maintained javascript library for interfacing with the Solana blockchain called `@solana/web3.js`. Run the code below to install the library.

```shell
npm install @solana/web3.js
```

After installation, go to `pages/api` and create a `transactions.js` file. We will be using Next.js API routes to fetch the user transactions. This allows us to separate the Solana config and business logic from the client. Open the `transactions.js` file and paste the code below.

```javascript
import * as solanaWeb3 from "@solana/web3.js";

const DEV_NET = solanaWeb3.clusterApiUrl("devnet");
const solanaConnection = new solanaWeb3.Connection(DEV_NET);

const getAddressInfo = async (address, numTx = 3) => {
  const pubKey = new solanaWeb3.PublicKey(address);
  const transactionList = await solanaConnection.getSignaturesForAddress(
    pubKey,
    { limit: numTx }
  );
  const accountBalance = await solanaConnection.getBalance(pubKey);

  return { transactionList, accountBalance };
};

const handler = async (req, res) => {
  const queryAddress = req.query?.address;
  if (!queryAddress) {
    return res.status(401).json({
      message: "Invalid address",
    });
  }
  try {
    const { accountBalance, transactionList } = await getAddressInfo(
      queryAddress
    );
    return res.status(200).json({ transactionList, accountBalance });
  } catch (error) {
    console.log(error);
    return res.status(500).json({
      message: "Something went wrong. Please try again later",
    });
  }
};

export default handler;

```

We must import the library to use Solana in our `transactions.js` file. After that, we create a connection to a Solana RPC Node.

```javascript
const DEV_NET = solanaWeb3.clusterApiUrl('devnet');
const solanaConnection = new solanaWeb3.Connection(DEV_NET);
```

A Solana RPC (Remote Procedural Call) node is a node that responds to requests about the network and allows users to submit transactions. Solana maintains some publicly available [Nodes](https://docs.solana.com/cluster/rpc-endpoints), which includes the `DEV_NET`. We will create a connection to the `DEV_NET` RPC Node, allowing us to get the transaction history and balance of an address transacted on the Node.


The next step is to create a function `getAddressInfo` to get the info we need from the Solana RPC Node. The function accepts an address and a number of transactions to get, which has been set to a default number of 3. To get transactions and perform most operations with `@solana/web3.js`, we will need a public key, a general identifier on Solana. A public key can be generated from a base58 encoded string, buffer, Uint8Array, number, and an array of numbers. We generate our public key from the user address, a base58 encoded string.

```javascript
const pubKey = new solanaWeb3.PublicKey(address);
```

To get the list of transactions, we use the `getSignaturesForAddress` method, which returns the list of transactions. The method requires a `publicKey` and an optional object for pagination.

```javascript
const transactionList = await solanaConnection.getSignaturesForAddress(pubKey, { limit: numTx });
```

The `getBalance` method returns the user balance and requires a public key.

```javascript
const accountBalance = await solanaConnection.getBalance(pubKey);
```

The `handler` function ties everything together and returns the details in a presentable way that can be displayed to the client.

Run the code below in a terminal to install libraries that will help us fetch and format the transactions properly.


Now return to the `index.js` file and paste the code below.

```jsx
import Head from "next/head";
import { useState } from "react";
import axios from "axios";
import TransactionList from "../components/TransactionList";
import SearchTransactionForm from "../components/SearchTransactionForm";

export default function Home() {
  const [loading, setLoading] = useState(false);
  const [transactionList, setTransactionList] = useState([]);
  const [balance, setBalance] = useState(null);
  const [address, setAddress] = useState("");
  const [errorMessage, setErrorMessage] = useState("");

  const handleFormSubmit = async (event) => {
    try {
      event.preventDefault();
      setLoading(true);
      setErrorMessage("");

      const response = await axios.get(`/api/transactions/?address=${address}`);
      if (response.status === 200) {
        setTransactionList(response.data.transactionList);
        const accountBalanceText = response.data.accountBalance;
        const accountBalance = parseInt(accountBalanceText) / 1_000_000_000;

        accountBalance && setBalance(accountBalance);
      }
    } catch (error) {
      console.log("client", error);
      setErrorMessage(
        error?.response.data?.message ||
          "Unable to fetch transactions. Please try again later."
      );
    } finally {
    }

    setLoading(false);
  };
  return (
    <>
      <Head>
        <title>Solana Blockchain Explorer</title>
      </Head>
      <main className="w-full h-full max-w-2xl p-6 flex flex-col items-center justify-between gap-6 mx-auto relative">
        <h1 className="text-2xl">Solana Blockchain Explorer</h1>
        <SearchTransactionForm
          handleFormSubmit={handleFormSubmit}
          address={address}
          setAddress={setAddress}
          loading={loading}
          errorMessage={errorMessage}
        />

        <TransactionList transactionList={transactionList} balance={balance} />

        {loading && (
          <div className="absolute inset-0 bg-white/70 flex items-center justify-center">
            Loading
          </div>
        )}
      </main>
    </>
  );
}

```

What we've done on this page is tie everything together. We display the `SearchTransactionForm` component created earlier to collect the address from the user. When the user submits the form, the `handleFormSubmit` function is called, which calls the `transactions` API we created earlier, passing the `address` as a parameter. If the search is successful, the API request returns the `transactionData` and `balance` which are passed as `props` to the `TransactionList` component to be displayed.

Save and reload your browser. You can now enter a Solana address and click the search button to fetch the transaction history. You should get a result similar to the screenshot below.


![Solana Blockchain Explorer with Transactions ](https://blog.openreplay.com/images/build-a-blockchain-explorer-with-solana-and-next-js/images/s_51D34FAA89697243694AA782113F5488E594A5E1334181D40F85ACD4D7743A85_1672652298608_image.png)


## Get a Single transaction

We've looked at how to get a transaction list from the Solana web3 library and display it. In this section, we will look at how to get the details of a single transaction. Navigate to the `api` directory and create a `transaction.js` file. Open the file and paste the code below.

```javascript
import * as solanaWeb3 from "@solana/web3.js";

const DEV_NET = solanaWeb3.clusterApiUrl("devnet");
const solanaConnection = new solanaWeb3.Connection(DEV_NET);

const handler = async (req, res) => {
  const transactionHash = req.body.transactionHash;
  if (!transactionHash) {
    return res.status(401).json({
      error: "Invalid transaction hash",
    });
  }
  try {
    const transaction = await solanaConnection.getParsedTransaction(
      transactionHash
    );
    return res.status(200).json(transaction);
  } catch (error) {
    console.log("Error:", error);
    return res.status(500).json({
      error: "Server error",
    });
  }
};

export default handler;

```

To get the details of a single transaction, we use the `getParsedTransaction` method, which requires a transaction hash. The transaction hash is obtained from the request body, which is provided to the `handler` function by Next.js. Depending on the result, we return a response to the client.

The next step is building a page to display the transaction details obtained from the API. Create a `transaction` directory in the `pages` directory. Navigate to the `transaction` directory and create a file called `[id].js`. This page is a dynamic route; whenever a user visits `/transaction/gm12` or `transaction/12gm`, this page will be rendered in the browser. Open the file in your code editor and paste the code below.

```jsx
import Head from "next/head";
import { useState, useEffect } from "react";
import axios from "axios";
import { useRouter } from "next/router";
import TransactionListDetail from "../../components/TransactionListDetail";

export default function TransactionDetail() {
  const [loading, setLoading] = useState(false);
  const [transactionData, setTransactionData] = useState();
  const [errorMessage, setErrorMessage] = useState("");
  const router = useRouter();

  useEffect(() => {
    const getTransaction = async () => {
      try {
        setLoading(true);
        setErrorMessage("");

        const response = await axios.post("/api/transaction", {
          transactionHash: router.query?.id,
        });

        if (response.status === 200) {
          setTransactionData(response.data.transaction);
        }
      } catch (error) {
        setErrorMessage(
          error?.response.data?.message ||
            "Unable to fetch transaction. Please try again later."
        );
      } finally {
        setLoading(false);
      }
    };

    getTransaction();
  }, [router.query?.id]);

  return (
    <>
      <Head>
        <title>Solana Blockchain Explorer: Transaction</title>
      </Head>
      <main className="w-full h-full p-6 flex flex-col items-center justify-between gap-6 mx-auto relative">
        <h1 className="text-2xl">Transaction</h1>
        {errorMessage && (
          <p className="text-red-600 text-base text-center my-1">
            {errorMessage}
          </p>
        )}

        <TransactionListDetail
          loading={loading}
          transactionData={transactionData}
        />

        {loading && (
          <div className="absolute inset-0 bg-white/70 flex items-center justify-center">
            Loading
          </div>
        )}
      </main>
    </>
  );
}

```
We are doing something similar to what we did on the homepage, but instead of taking input from the user and passing it to a function that calls the API, we are grabbing the input we need from the URL. When a user visits the `/transaction/[id]` route, the `getTransaction` function is called. The function requests the `/api/transaction` endpoint with the transaction hash, which is gotten from the route. If the request is successful, it returns the data, which is displayed on the page. An appropriate error message is also shown on the page if there is an error fetching the transaction details.

Now visit the app in your browser, and search for an address. When the results show up, click on a transaction to open the details on the transaction page.

![Demo of Solana Blockchain Explorer](https://blog.openreplay.com/images/build-a-blockchain-explorer-with-solana-and-next-js/images/s_51D34FAA89697243694AA782113F5488E594A5E1334181D40F85ACD4D7743A85_1672826907771_solana.gif)


## Conclusion

In this article, we've looked at creating a blockchain explorer on the Solana blockchain. A blockchain explorer has many uses, and it can be part of a web3 app that allows users to view their transaction history, and it can also be a standalone app like the one we built above.

The code for this project is available [here](https://github.com/sa-ma/solana-blockchain-explorer). You can also view a working example [here](https://solana-blockchain-explorer-one.vercel.app/).




[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)