Beginner friendly Solana project using NextJS and Tailwind
Jumping into the vast world of Web3 technologies can be a daunting task especially if you are just starting to here I have a simple project idea to ease you in.
We are going to create a simple application to fetch solana wallet’s balance and transaction history using solana’s library @solana/web3.js
and NextJs as the full stack solution.This is a step-by-step guide but the source code can be found here on Github.
Here is the sample UI that you should see after we go through this tutorial
Initialising the project
- Set up the development environment: Ensure you have Node.js and npm installed on your machine.
- Create a new Next.js project: Open a terminal or command prompt. Run npx create-next-app to create a new Next.js project.
Follow the prompts (say yes for tailwind prompts) to set up your project. - Install necessary dependencies: In your project directory, run
npm install @solana/web3.js
to install the Solana Web3.js library.
Creating API routes
If you want to learn more about NextJs and how routing and the project structure works you can read more about it here since it is outside the scope of this article but the basics is that you can create api routes in pages/api
directory of your project.
Let us start by creating our first API route. In the pages/api
directory of your project, create a new Typescript file balance.ts
.
We use Next.js to create the Solana wallet balance checker API by importing the necessary libraries and configuring the network and conversion constants. The handler method handles requests by checking for POST requests and collecting the wallet address from the request body. We retrieve the wallet’s balance in lamports
and convert it to SOL using web3.Connection. The formatted balance is then returned in the form of a JSON response. In the event of an error, an error response with the status code 500 is sent.
import web3 from '@solana/web3.js';
import { NextApiRequest, NextApiResponse } from 'next';
const network = 'https://api.mainnet-beta.solana.com'; // or use a testnet if desired
const LAMPORTS_PER_SOL = 1000000000; // Number of lamports in one SOL
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method Not Allowed' });
}
const { walletAddress } = req.body;
try {
const connection = new web3.Connection(network, 'confirmed');
const publicKey = new web3.PublicKey(walletAddress);
const balance = await connection.getBalance(publicKey);
const solBalance = balance / LAMPORTS_PER_SOL; // Convert balance to SOL
const formattedBalance = solBalance.toFixed(2); // Format balance to two decimal places
return res.status(200).json({ balance: formattedBalance });
} catch (error) {
console.error('Error fetching wallet balance:', error);
return res.status(500).json({ error: 'Internal Server Error' });
}
}
Perfect! Congratulations on creating your first API route. Now let us create the second API for /api/transactionHistory
. Again before we dive into the code here is what we are going to do.
This API retrieves account information as well as the past ten transactions for a Solana wallet. It validates the POST request and retrieves the wallet address from the request body. We utilise @solana/web3.js
to connect to the Solana blockchain and retrieve the necessary data. A JSON response is delivered with the collected account information and transaction history. In the event of an error, an error response with the status code 500 is sent. This API makes it easier to obtain Solana wallet information, making it excellent for blockchain integrations.
import { NextApiRequest, NextApiResponse } from 'next';
import { Connection, PublicKey } from '@solana/web3.js';
const network = 'https://api.mainnet-beta.solana.com'; // or use a testnet if desired
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method Not Allowed' });
}
const { walletAddress } = req.body;
try {
const connection = new Connection(network, 'confirmed');
const publicKey = new PublicKey(walletAddress);
// Fetch account info
const account = await connection.getAccountInfo(publicKey);
// Fetch transaction history
const transactions = await connection.getConfirmedSignaturesForAddress2(publicKey, {
limit: 10,
});
return res.status(200).json({ account, transactions });
} catch (error) {
console.error('Error fetching account and transaction history:', error);
return res.status(500).json({ error: 'Internal Server Error' });
}
}
Great now we can move to designing the frontend of the code. Again I am keeping it simple here but sky is the limit!
Building the frontend
In app/page.tsx
I have created a basic UI that takes in the wallet address as the input. Upon clicking the search you can fetch the balance and transactions. Here is the code for button
<button
onClick={handleCheckBalance}
className="px-6 bg-blue-500 text-white font-semibold rounded-r-md focus:outline-none hover:bg-blue-600"
>
Check Balance
</button>
Here is the handleCheckBalance
function
const handleCheckBalance = async () => {
const response = await fetch('/api/balance', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ walletAddress }),
});
const data = await response.json();
setBalance(data.balance);
const transactionResponse = await fetch('/api/transactionHistory', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ walletAddress }),
});
const transactionData = await transactionResponse.json();
setTransactions(transactionData.transactions || []);
};
Here we are fetching the data from /api/balance
and /api/transactionHistory
. After fetching the data we are setting balance and transaction using useState
. Let us join all the pieces and set up a working page.
"use client";
import { useState } from 'react';
export default function BalanceChecker() {
const [walletAddress, setWalletAddress] = useState('');
const [balance, setBalance] = useState(null);
const [transactions, setTransactions] = useState([]); // Explicitly typed as any[] or Transaction[]
const handleCheckBalance = async () => {
const response = await fetch('/api/balance', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ walletAddress }),
});
const data = await response.json();
setBalance(data.balance);
const transactionResponse = await fetch('/api/transactionHistory', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ walletAddress }),
});
const transactionData = await transactionResponse.json();
setTransactions(transactionData.transactions || []);
};
const formatBlockTime = (timestamp) => {
const date = new Date(timestamp * 1000);
return date.toLocaleString();
};
return (
<div className="max-w-md mx-auto flex flex-col items-center justify-center">
<h1 className="text-2xl font-bold mb-6">Solana Wallet Balance Checker</h1>
<div className="mb-6 flex">
<input
id="walletAddress"
type="text"
value={walletAddress}
onChange={(e) => setWalletAddress(e.target.value)}
className="w-96 px-4 py-3 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Search"
/>
<button
onClick={handleCheckBalance}
className="px-6 bg-blue-500 text-white font-semibold rounded-r-md focus:outline-none hover:bg-blue-600"
>
Check Balance
</button>
</div>
{balance && (
<div className="mt-8">
<p className="text-xl font-medium">Wallet Balance:</p>
<p className="text-4xl font-semibold text-green-600">{balance} SOL</p>
</div>
)}
{transactions.length > 0 && (
<div className="my-8">
<p className="text-xl font-medium">Transaction History:</p>
<table className="w-full table-auto border-collapse border border-gray-500">
<thead>
<tr className="bg-gray-100">
<th className="px-4 py-2">Signature</th>
<th className="px-4 py-2">Block Time</th>
<th className="px-4 py-2">Slot</th>
</tr>
</thead>
<tbody>
{transactions.map((transaction, index) => (
<tr key={index}>
<td className="border px-4 py-2">{transaction.signature}</td>
<td className="border px-4 py-2">{formatBlockTime(transaction.blockTime)}</td>
<td className="border px-4 py-2">{transaction.slot}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
In conclusion, this beginner-friendly Solana project using Next.js and Tailwind provides a great introduction to the world of Web3 technologies. We have successfully built a simple application that allows us to fetch a Solana wallet’s balance and transaction history using the Solana Web3.js library and Next.js as the full-stack solution.
Throughout this tutorial, we covered how to set up the development environment, create API routes to fetch wallet balance and transaction history, and design a basic UI using Tailwind CSS. By following these step-by-step instructions, you now have a working application that can interact with the Solana blockchain and retrieve essential wallet information.
Remember, this project is just the beginning of your journey into the vast world of Web3 technologies. You can further enhance this application by adding more features, exploring other blockchain networks, or integrating it into larger-scale blockchain projects.
If you want to explore further or delve deeper into Solana development, be sure to check out the Solana documentation and community resources. There’s a wealth of information and support available to help you continue your Web3 development journey.
I hope this tutorial has sparked your interest and empowered you to take on more ambitious blockchain projects. Happy coding and exploring the fascinating world of Web3!