Integrating Blockscout API in Your DApp: Fetching Account Balance, Transactions, and Address Details
Introduction:
Blockchain applications thrive on transparency and traceability, and the Blockscout API makes it easy to integrate detailed blockchain data into your DApps. In this guide, we’ll walk through using the Blockscout API to fetch essential data for an account, including the account address, balance, and both internal and external transactions. We'll also provide a sample login using an ENS domain like "vitalik.eth" to show how it all comes together.
Prerequisites:
Basic knowledge of JavaScript and web3.
A Blockscout API key.
Code setup for fetching data (if you’re starting from scratch, you can refer to the sample code in my GitHub repository).
1. Setting Up Blockscout API
Before we jump into the code, let’s set up the Blockscout API in our DApp. Blockscout provides a range of endpoints for retrieving information like account details, balances, and transaction history, which we’ll leverage throughout this guide.
2. Logging in with ENS Domains
To access data for specific Ethereum Name Service (ENS) domains, we’ll implement a basic login function. For example, logging in with “vitalik.eth” can give us access to all linked transaction details, balances, and more.
This is the domain name blockscout API https://eth.blockscout.com/api/v2/search/check-redirect?q=${domainName}
'use client'
import { useState, useEffect } from 'react'
import { ArrowRight, Loader2 } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
export default function Login() {
const [copied, setCopied] = useState(false)
const [inputValue, setInputValue] = useState('')
const [domainData, setDomainData] = useState(null)
const [loading, setLoading] = useState(false)
let navigate = useNavigate();
useEffect(() => {
const accountAddress = localStorage.getItem("Address")
if (accountAddress) {
// Navigate to home (Note: navigation logic should be handled by your routing system)
navigate('/home');
return;
console.log('Navigating to home')
}
}, [])
const getDomainData = async (domainName) => {
setLoading(true)
try {
const response = await fetch(
`https://eth.blockscout.com/api/v2/search/check-redirect?q=${domainName}`
)
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
}
const data = await response.json()
console.log('API Response:', data)
setDomainData(data)
const resolvedAddress = data.parameter
if (resolvedAddress) {
localStorage.setItem("Address", resolvedAddress)
navigate('/home');
// Navigate to home (Note: navigation logic should be handled by your routing system)
console.log('Navigating to home')
} else {
alert("No resolved address found for this domain.")
}
} catch (error) {
console.error('Error fetching data:', error)
alert("Error fetching the domain data. Please check the domain name and try again.")
return;
} finally {
setLoading(false)
}
}
const handleInputChange = (e) => {
setInputValue(e.target.value)
localStorage.setItem("Name", e.target.value)
}
const handleSearch = () => {
if (inputValue === '') {
alert("Please fill the domain name!")
return
}
getDomainData(inputValue)
}
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-purple-400 to-indigo-600 p-4">
<div className="w-full max-w-md">
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden transform transition-all hover:scale-105">
<div className="p-8">
<h2 className="text-3xl font-extrabold text-center text-gray-900 mb-6">Login with ETH Domain</h2>
<div className="mb-6">
<div className="relative">
<input
className="w-full px-4 py-3 rounded-lg border-2 border-gray-300 focus:border-purple-500 focus:ring focus:ring-purple-200 transition duration-200 ease-in-out text-lg"
type="text"
id="myInput"
value={inputValue}
placeholder="Enter your domain (e.g., vitalik.eth)"
onChange={handleInputChange}
/>
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<ArrowRight className="h-5 w-5 text-gray-400" />
</div>
</div>
</div>
<button
className={`w-full py-3 px-4 bg-gradient-to-r from-purple-500 to-indigo-600 text-white rounded-lg text-lg font-semibold shadow-md hover:shadow-lg transition duration-300 ease-in-out transform hover:-translate-y-1 ${loading ? 'opacity-75 cursor-not-allowed' : ''}`}
onClick={handleSearch}
disabled={loading}
>
{loading ? (
<span className="flex items-center justify-center">
<Loader2 className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" />
Processing...
</span>
) : (
'LOGIN'
)}
</button>
</div>
</div>
</div>
</div>
)
}
This function validates the ENS domain and returns the associated account address. Replace resolveENS
with your ENS resolution logic.
3. Fetching Account Balance
Once we have the account address, let’s fetch the balance using the Blockscout API.
This is the Balance blockscout API
https://eth.blockscout.com/api?module=account&action=eth_get_balance&address=${accountAddress}&tag=latest
'use client'
import React, { useEffect, useState } from 'react'
import { Transition } from '@headlessui/react'
import { X, DollarSign, LogOut, Droplet } from 'lucide-react'
function SideMenu({ isOpen, setIsOpen, logout, address }) {
const [balance, setBalance] = useState(0)
const name = localStorage.getItem("Name") || "User"
const accountAddress = localStorage.getItem("Address")
const checkBalance = async () => {
try {
const response = await fetch(
`https://eth.blockscout.com/api?module=account&action=eth_get_balance&address=${accountAddress}&tag=latest`
)
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
}
const data = await response.json()
if (data.result === 'Max rate limit reached') {
console.log("Rate limit reached")
return
}
const balance = Number(data.result) / 10**18
setBalance(balance)
} catch (error) {
console.error('Error fetching balance:', error)
}
}
useEffect(() => {
if (isOpen) {
checkBalance()
}
}, [isOpen])
const handleLogout = () => {
logout()
setIsOpen(false)
}
const openFaucet = () => {
window.open('https://www.alchemy.com/faucets/base-sepolia', '_blank')
}
return (
<Transition show={isOpen} as={React.Fragment}>
<div className="fixed inset-0 overflow-hidden z-50">
<Transition.Child
as={React.Fragment}
enter="ease-in-out duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-500"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onClick={() => setIsOpen(false)} />
</Transition.Child>
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex">
<Transition.Child
as={React.Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<div className="w-screen max-w-md">
<div className="h-full flex flex-col py-6 bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 shadow-xl overflow-y-scroll">
<div className="px-4 sm:px-6">
<div className="flex items-start justify-between">
<h2 className="text-2xl font-extrabold text-white">{name}</h2>
<div className="ml-3 h-7 flex items-center">
<button
className="bg-transparent rounded-md text-white hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-white"
onClick={() => setIsOpen(false)}
>
<span className="sr-only">Close panel</span>
<X className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div className="mt-6 relative flex-1 px-4 sm:px-6">
<div className="absolute inset-0 px-4 sm:px-6">
<div className="h-full flex flex-col justify-between">
<div>
<div className="bg-white rounded-2xl p-6 mb-6">
<h3 className="text-lg font-medium text-purple-600 mb-2">Wallet Address</h3>
<p className="text-sm text-gray-600 break-all">{accountAddress}</p>
</div>
<div className="bg-white rounded-2xl p-6 mb-6">
<h3 className="text-lg font-medium text-purple-600 mb-2">Balance</h3>
<div className="flex items-center">
<span className="text-2xl font-bold text-gray-900">{balance.toFixed(4)} ETH</span>
</div>
</div>
</div>
<div className="space-y-4">
<button
onClick={openFaucet}
className="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-4 rounded-full transition duration-300 ease-in-out flex items-center justify-center"
>
<Droplet className="h-5 w-5 mr-2" />
Get Faucet
</button>
<button
onClick={handleLogout}
className="w-full bg-pink-500 hover:bg-pink-600 text-white font-bold py-3 px-4 rounded-full transition duration-300 ease-in-out flex items-center justify-center"
>
<LogOut className="h-5 w-5 mr-2" />
Logout
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</Transition.Child>
</div>
</div>
</Transition>
)
}
export default SideMenu
In this function, we use the account
module to get the current balance for a given address, allowing users to view their funds at a glance.
4. Retrieving Normal and Internal Transactions
The Blockscout API also allows us to differentiate between normal and internal transactions, providing a full history of account activity.
Normal Transactions
Normal transactions are those initiated directly by users. Here’s a sample code snippet to retrieve them:
This is Normal txn Blockscout API
https://eth.blockscout.com/api?module=account&action=txlist&address=${address}&page=1&offset=10&sort=asc
'use client'
import { useState, useEffect } from 'react'
import { ArrowUpRight, Loader } from 'lucide-react'
export default function NormalTransaction({ address }) {
const [transactions, setTransactions] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const getData = async () => {
if (!address) return
setLoading(true)
try {
const response = await fetch(
`https://eth.blockscout.com/api?module=account&action=txlist&address=${address}&page=1&offset=10&sort=asc`
)
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
}
const data = await response.json()
if (data.result === 'Max rate limit reached') {
console.log("Rate limit reached")
return
}
setTransactions(data.result)
} catch (error) {
console.error('Error fetching data:', error)
} finally {
setLoading(false)
}
}
getData()
}, [address])
const truncateAddress = (address) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`
}
return (
<div className="min-h-screen bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 py-12 px-4 sm:px-6 lg:px-8 font-sans">
<div className="max-w-7xl mx-auto">
<h2 className="text-4xl md:text-6xl font-extrabold text-center mb-12 text-white pb-2 tracking-tight leading-none">
Normal Transactions
</h2>
{loading ? (
<div className="flex justify-center items-center h-64">
<Loader className="w-16 h-16 text-white animate-spin" />
</div>
) : (
<div className="bg-gray-900 bg-opacity-70 backdrop-filter backdrop-blur-lg rounded-3xl shadow-2xl overflow-hidden border border-gray-700">
{transactions.length > 0 ? (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-700">
<thead className="bg-gray-800">
<tr>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
Block
</th>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
Time
</th>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
Transaction
</th>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
From
</th>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
To
</th>
</tr>
</thead>
<tbody className="bg-gray-900 divide-y divide-gray-700">
{transactions.map((transaction, index) => (
<tr key={transaction.hash} className={`${index % 2 === 0 ? 'bg-gray-800' : 'bg-gray-900'} hover:bg-gray-700 transition-colors duration-200`}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-300">{transaction.blockNumber}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{new Date(parseInt(transaction.timeStamp) * 1000).toLocaleString()}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a
href={`https://eth.blockscout.com/tx/${transaction.hash}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-bold bg-blue-600 text-white hover:bg-blue-700 transition-colors duration-200 shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
>
View
<ArrowUpRight className="ml-1 h-4 w-4" />
</a>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-400 font-mono">{truncateAddress(transaction.from)}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-400 font-mono">{truncateAddress(transaction.to)}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p className="text-3xl font-bold text-gray-300 py-12 text-center">No transactions available.</p>
)}
</div>
)}
</div>
</div>
)
}
Internal Transactions
Internal transactions typically represent interactions within smart contracts, such as transfers. Here’s a sample to fetch them:
This is Internal txn blockscout API
https://eth.blockscout.com/api?module=account&action=txlistinternal&address=${accountAddress}&page=1&offset=10&sort=asc
'use client'
import { useState, useEffect } from 'react'
import { ArrowUpRight, Loader } from 'lucide-react'
export default function InternalTransaction() {
const [txns, setTxns] = useState([])
const [loading, setLoading] = useState(true)
const accountAddress = localStorage.getItem("Address")
useEffect(() => {
const getData2 = async () => {
if (!accountAddress) return
setLoading(true)
try {
const response = await fetch(
`https://eth.blockscout.com/api?module=account&action=txlistinternal&address=${accountAddress}&page=1&offset=10&sort=asc`
)
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
}
const data = await response.json()
console.log('API Response:', data)
if (data.result === 'Max rate limit reached') {
console.log("Rate limit reached")
return
}
setTxns(data.result)
} catch (error) {
console.error('Error fetching data:', error)
} finally {
setLoading(false)
}
}
getData2()
}, [accountAddress])
const truncateAddress = (address) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`
}
return (
<div className="min-h-screen bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 py-12 px-4 sm:px-6 lg:px-8 font-sans">
<div className="max-w-7xl mx-auto">
<h2 className="text-4xl md:text-6xl font-extrabold text-center mb-12 text-white pb-2 tracking-tight leading-none">
Internal Transactions
</h2>
{loading ? (
<div className="flex justify-center items-center h-64">
<Loader className="w-16 h-16 text-white animate-spin" />
</div>
) : (
<div className="bg-gray-900 bg-opacity-70 backdrop-filter backdrop-blur-lg rounded-3xl shadow-2xl overflow-hidden border border-gray-700">
{txns.length > 0 ? (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-700">
<thead className="bg-gray-800">
<tr>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
Block
</th>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
Time
</th>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
Transaction
</th>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
From
</th>
<th scope="col" className="px-6 py-4 text-left text-xs font-bold text-gray-300 uppercase tracking-wider">
To
</th>
</tr>
</thead>
<tbody className="bg-gray-900 divide-y divide-gray-700">
{txns.map((txn, index) => (
<tr key={txn.hash} className={`${index % 2 === 0 ? 'bg-gray-800' : 'bg-gray-900'} hover:bg-gray-700 transition-colors duration-200`}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-300">{txn.blockNumber}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{new Date(parseInt(txn.timeStamp) * 1000).toLocaleString()}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a
href={`https://eth.blockscout.com/tx/${txn.transactionHash}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center px-3 py-1 rounded-full text-sm font-bold bg-blue-600 text-white hover:bg-blue-700 transition-colors duration-200 shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
>
View
<ArrowUpRight className="ml-1 h-4 w-4" />
</a>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-400 font-mono">{truncateAddress(txn.from)}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-400 font-mono">{truncateAddress(txn.to)}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p className="text-3xl font-bold text-gray-300 py-12 text-center">OOPS! No transactions available.</p>
)}
</div>
)}
</div>
</div>
)
}
6. Summary and Next Steps
Integrating Blockscout API into your DApp opens up possibilities to enrich user experience with real-time data on balances, transactions, and ENS domains. Feel free to customize these examples further by exploring the Blockscout API documentation for additional data points.
Resources
Thank you for reading! If you found this guide helpful, share it with your network and start implementing Blockscout in your DApps today.