Improve explorer subsystem posture and wallet visibility

This commit is contained in:
defiQUG
2026-04-13 21:35:36 -07:00
parent b5a2e0c0a4
commit 251e37bd05
8 changed files with 315 additions and 3 deletions

View File

@@ -10,7 +10,14 @@ import Link from 'next/link'
import { Explain, useUiMode } from '@/components/common/UiModeContext'
import { accessApi, type WalletAccessSession } from '@/services/api/access'
import EntityBadge from '@/components/common/EntityBadge'
import { addressesApi, type AddressInfo, type TransactionSummary } from '@/services/api/addresses'
import {
addressesApi,
type AddressInfo,
type AddressTokenBalance,
type AddressTokenTransfer,
type TransactionSummary,
} from '@/services/api/addresses'
import { formatRelativeAge, formatTokenAmount } from '@/utils/format'
import {
isWatchlistEntry,
readWatchlistFromStorage,
@@ -42,6 +49,8 @@ export default function WalletPage(props: WalletPageProps) {
const [watchlistEntries, setWatchlistEntries] = useState<string[]>([])
const [addressInfo, setAddressInfo] = useState<AddressInfo | null>(null)
const [recentAddressTransactions, setRecentAddressTransactions] = useState<TransactionSummary[]>([])
const [tokenBalances, setTokenBalances] = useState<AddressTokenBalance[]>([])
const [tokenTransfers, setTokenTransfers] = useState<AddressTokenTransfer[]>([])
useEffect(() => {
if (typeof window === 'undefined') return
@@ -107,6 +116,8 @@ export default function WalletPage(props: WalletPageProps) {
if (!walletSession?.address) {
setAddressInfo(null)
setRecentAddressTransactions([])
setTokenBalances([])
setTokenTransfers([])
return () => {
cancelled = true
}
@@ -115,16 +126,41 @@ export default function WalletPage(props: WalletPageProps) {
Promise.all([
addressesApi.getSafe(138, walletSession.address),
addressesApi.getTransactionsSafe(138, walletSession.address, 1, 3),
addressesApi.getTokenBalancesSafe(walletSession.address),
addressesApi.getTokenTransfersSafe(walletSession.address, 1, 4),
])
.then(([infoResponse, transactionsResponse]) => {
.then(([infoResponse, transactionsResponse, balancesResponse, transfersResponse]) => {
if (cancelled) return
setAddressInfo(infoResponse.ok ? infoResponse.data : null)
setRecentAddressTransactions(transactionsResponse.ok ? transactionsResponse.data : [])
setTokenBalances(
balancesResponse.ok
? [...balancesResponse.data]
.filter((balance) => {
try {
return BigInt(balance.value || '0') > 0n
} catch {
return Boolean(balance.value)
}
})
.sort((left, right) => {
try {
return Number(BigInt(right.value || '0') - BigInt(left.value || '0'))
} catch {
return 0
}
})
.slice(0, 4)
: [],
)
setTokenTransfers(transfersResponse.ok ? transfersResponse.data : [])
})
.catch(() => {
if (cancelled) return
setAddressInfo(null)
setRecentAddressTransactions([])
setTokenBalances([])
setTokenTransfers([])
})
return () => {
@@ -320,6 +356,108 @@ export default function WalletPage(props: WalletPageProps) {
))
)}
</div>
<div className="mt-6 grid gap-4 xl:grid-cols-[0.9fr_1.1fr]">
<div className="rounded-2xl border border-gray-200 bg-gray-50/70 p-4 dark:border-gray-800 dark:bg-gray-900/40">
<div className="flex items-start justify-between gap-3">
<div>
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
Visible Token Balances
</div>
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">
{mode === 'guided'
? 'These are the first visible non-zero token balances currently indexed for your connected wallet.'
: 'Indexed non-zero token balances for this wallet.'}
</div>
</div>
<Link href={`/addresses/${walletSession.address}`} className="text-sm font-medium text-primary-600 hover:underline">
Full address detail
</Link>
</div>
<div className="mt-4 space-y-3">
{tokenBalances.length === 0 ? (
<div className="text-sm text-gray-600 dark:text-gray-400">
No indexed token balances are currently visible for this wallet.
</div>
) : (
tokenBalances.map((balance) => (
<Link
key={balance.token_address}
href={`/tokens/${balance.token_address}`}
className="block rounded-xl border border-gray-200 bg-white/80 px-4 py-3 hover:border-primary-300 hover:bg-primary-50/60 dark:border-gray-700 dark:bg-black/10 dark:hover:border-primary-700 dark:hover:bg-primary-950/20"
>
<div className="flex items-start justify-between gap-3">
<div>
<div className="font-semibold text-gray-900 dark:text-white">
{balance.token_symbol || balance.token_name || 'Token'}
</div>
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{balance.token_type || 'token'} · {balance.token_name || balance.token_address}
</div>
</div>
<div className="text-right text-sm font-medium text-gray-900 dark:text-white">
{formatTokenAmount(balance.value, balance.token_decimals, balance.token_symbol, 6)}
</div>
</div>
</Link>
))
)}
</div>
</div>
<div className="rounded-2xl border border-gray-200 bg-gray-50/70 p-4 dark:border-gray-800 dark:bg-gray-900/40">
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
Recent Token Transfers
</div>
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">
{mode === 'guided'
? 'Use these token transfers to jump directly into recent visible asset movement for the connected wallet.'
: 'Recent indexed token transfer activity for this wallet.'}
</div>
<div className="mt-4 space-y-3">
{tokenTransfers.length === 0 ? (
<div className="text-sm text-gray-600 dark:text-gray-400">
No recent token transfers are currently visible for this connected wallet.
</div>
) : (
tokenTransfers.map((transfer) => {
const incoming = transfer.to_address.toLowerCase() === walletSession.address.toLowerCase()
const counterparty = incoming ? transfer.from_address : transfer.to_address
return (
<div
key={`${transfer.transaction_hash}-${transfer.token_address}`}
className="rounded-xl border border-gray-200 bg-white/80 px-4 py-3 dark:border-gray-700 dark:bg-black/10"
>
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div>
<div className="font-semibold text-gray-900 dark:text-white">
{incoming ? 'Incoming' : 'Outgoing'} {transfer.token_symbol || 'token'} transfer
</div>
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">
{formatTokenAmount(transfer.value, transfer.token_decimals, transfer.token_symbol, 6)}
</div>
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Counterparty: {counterparty.slice(0, 6)}...{counterparty.slice(-4)} · {formatRelativeAge(transfer.timestamp)}
</div>
</div>
<div className="flex flex-wrap gap-3 text-sm">
<Link href={`/transactions/${transfer.transaction_hash}`} className="text-primary-600 hover:underline">
Open tx
</Link>
<Link href={`/addresses/${counterparty}`} className="text-primary-600 hover:underline">
Counterparty
</Link>
</div>
</div>
</div>
)
})
)}
</div>
</div>
</div>
</div>
) : null}
</div>