Files
explorer-monorepo/frontend/src/pages/addresses/index.tsx
defiQUG f46bd213ba refactor: rename SolaceScanScout to Solace and update related configurations
- Updated branding from "SolaceScanScout" to "Solace" across various files including deployment scripts, API responses, and documentation.
- Changed default base URL for Playwright tests and updated security headers to reflect the new branding.
- Enhanced README and API documentation to include new authentication endpoints and product access details.

This refactor aligns the project branding and improves clarity in the API documentation.
2026-04-10 12:52:17 -07:00

191 lines
6.5 KiB
TypeScript

import type { GetServerSideProps } from 'next'
import Link from 'next/link'
import { useEffect, useMemo, useState } from 'react'
import { useRouter } from 'next/router'
import { Card, Address } from '@/libs/frontend-ui-primitives'
import { transactionsApi, Transaction } from '@/services/api/transactions'
import { readWatchlistFromStorage } from '@/utils/watchlist'
import PageIntro from '@/components/common/PageIntro'
import { fetchPublicJson } from '@/utils/publicExplorer'
import { normalizeTransaction } from '@/services/api/blockscout'
function normalizeAddress(value: string) {
const trimmed = value.trim()
return /^0x[a-fA-F0-9]{40}$/.test(trimmed) ? trimmed : ''
}
interface AddressesPageProps {
initialRecentTransactions: Transaction[]
}
function serializeRecentTransactions(transactions: Transaction[]): Transaction[] {
return JSON.parse(
JSON.stringify(
transactions.map((transaction) => ({
hash: transaction.hash,
block_number: transaction.block_number,
from_address: transaction.from_address,
to_address: transaction.to_address ?? null,
})),
),
) as Transaction[]
}
export default function AddressesPage({ initialRecentTransactions }: AddressesPageProps) {
const router = useRouter()
const chainId = parseInt(process.env.NEXT_PUBLIC_CHAIN_ID || '138')
const [query, setQuery] = useState('')
const [recentTransactions, setRecentTransactions] = useState<Transaction[]>(initialRecentTransactions)
const [watchlist, setWatchlist] = useState<string[]>([])
useEffect(() => {
if (initialRecentTransactions.length > 0) {
setRecentTransactions(initialRecentTransactions)
return
}
let active = true
transactionsApi.listSafe(chainId, 1, 20)
.then(({ ok, data }) => {
if (active && ok) {
setRecentTransactions(data)
}
})
.catch(() => {
if (active) {
setRecentTransactions([])
}
})
return () => {
active = false
}
}, [chainId, initialRecentTransactions])
useEffect(() => {
if (typeof window === 'undefined') {
return
}
try {
setWatchlist(readWatchlistFromStorage(window.localStorage))
} catch {
setWatchlist([])
}
}, [])
const activeAddresses = useMemo(() => {
const seen = new Set<string>()
const addresses: string[] = []
for (const tx of recentTransactions) {
for (const candidate of [tx.from_address, tx.to_address]) {
if (!candidate) continue
const normalized = candidate.toLowerCase()
if (seen.has(normalized)) continue
seen.add(normalized)
addresses.push(candidate)
if (addresses.length >= 12) return addresses
}
}
return addresses
}, [recentTransactions])
const handleOpenAddress = (event: React.FormEvent) => {
event.preventDefault()
const normalized = normalizeAddress(query)
if (!normalized) return
router.push(`/addresses/${normalized}`)
}
return (
<div className="container mx-auto px-4 py-6 sm:py-8">
<PageIntro
eyebrow="Address Discovery"
title="Addresses"
description="Open any Chain 138 address directly, revisit saved watchlist entries, or branch into recent activity discovered from indexed transactions."
actions={[
{ href: '/watchlist', label: 'Open watchlist' },
{ href: '/transactions', label: 'Recent transactions' },
{ href: '/search', label: 'Search explorer' },
]}
/>
<Card className="mb-6" title="Open An Address">
<form onSubmit={handleOpenAddress} className="flex flex-col gap-3 md:flex-row">
<input
type="text"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="0x..."
className="flex-1 rounded-lg border border-gray-300 px-4 py-2 focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
<button
type="submit"
disabled={!normalizeAddress(query)}
className="rounded-lg bg-primary-600 px-6 py-2 text-white hover:bg-primary-700 disabled:cursor-not-allowed disabled:opacity-50"
>
Open address
</button>
</form>
<p className="mt-3 text-sm text-gray-600 dark:text-gray-400">
Open any Chain 138 address directly, or jump into your saved watchlist below.
</p>
</Card>
<div className="grid gap-6 lg:grid-cols-2">
<Card title="Saved Watchlist">
{watchlist.length === 0 ? (
<p className="text-sm text-gray-600 dark:text-gray-400">
No saved addresses yet. Address detail pages let you add entries to the shared explorer watchlist.
</p>
) : (
<div className="space-y-3">
{watchlist.map((entry) => (
<Link key={entry} href={`/addresses/${entry}`} className="block text-primary-600 hover:underline">
<Address address={entry} showCopy={false} />
</Link>
))}
<div className="pt-2">
<Link href="/watchlist" className="text-sm text-primary-600 hover:underline">
Open the full watchlist
</Link>
</div>
</div>
)}
</Card>
<Card title="Recently Active Addresses">
{activeAddresses.length === 0 ? (
<p className="text-sm text-gray-600 dark:text-gray-400">
Recent address activity is unavailable right now. You can still open an address directly above.
</p>
) : (
<div className="space-y-3">
{activeAddresses.map((entry) => (
<Link key={entry} href={`/addresses/${entry}`} className="block text-primary-600 hover:underline">
<Address address={entry} showCopy={false} />
</Link>
))}
</div>
)}
</Card>
</div>
</div>
)
}
export const getServerSideProps: GetServerSideProps<AddressesPageProps> = async () => {
const chainId = Number(process.env.NEXT_PUBLIC_CHAIN_ID || '138')
const transactionsResult = await fetchPublicJson<{ items?: unknown[] }>('/api/v2/transactions?page=1&page_size=20').catch(() => null)
const initialRecentTransactions = Array.isArray(transactionsResult?.items)
? transactionsResult.items.map((item) => normalizeTransaction(item as never, chainId))
: []
return {
props: {
initialRecentTransactions: serializeRecentTransactions(initialRecentTransactions),
},
}
}