Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-02-10 11:32:49 -08:00
parent aafcd913c2
commit 88bc76da91
815 changed files with 125522 additions and 264 deletions

View File

@@ -0,0 +1,156 @@
'use client'
import { useEffect, useState } from 'react'
import { useParams } from 'next/navigation'
import { Card } from '@/components/common/Card'
import { Address } from '@/components/blockchain/Address'
import { Table } from '@/components/common/Table'
interface AddressInfo {
address: string
chain_id: number
transaction_count: number
token_count: number
is_contract: boolean
label?: string
tags: string[]
}
interface Transaction {
hash: string
block_number: number
from_address: string
to_address?: string
value: string
status?: number
}
export default function AddressDetailPage() {
const params = useParams()
const address = (params?.address as string) ?? ''
const chainId = parseInt(process.env.NEXT_PUBLIC_CHAIN_ID || '138')
const [addressInfo, setAddressInfo] = useState<AddressInfo | null>(null)
const [transactions, setTransactions] = useState<Transaction[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
loadAddressInfo()
loadTransactions()
}, [address])
const loadAddressInfo = async () => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/addresses/${chainId}/${address}`
)
const data = await response.json()
setAddressInfo(data.data)
} catch (error) {
console.error('Failed to load address info:', error)
}
}
const loadTransactions = async () => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/transactions?chain_id=${chainId}&from_address=${address}&page=1&page_size=20`
)
const data = await response.json()
setTransactions(data.data || [])
} catch (error) {
console.error('Failed to load transactions:', error)
} finally {
setLoading(false)
}
}
if (loading) {
return <div className="p-8">Loading address...</div>
}
if (!addressInfo) {
return <div className="p-8">Address not found</div>
}
const transactionColumns = [
{
header: 'Hash',
accessor: (tx: Transaction) => (
<a href={`/transactions/${tx.hash}`} className="text-primary-600 hover:underline">
<Address address={tx.hash} truncate />
</a>
),
},
{
header: 'Block',
accessor: (tx: Transaction) => tx.block_number,
},
{
header: 'To',
accessor: (tx: Transaction) => tx.to_address ? <Address address={tx.to_address} truncate /> : 'Contract Creation',
},
{
header: 'Value',
accessor: (tx: Transaction) => {
const value = BigInt(tx.value)
const eth = Number(value) / 1e18
return eth > 0 ? `${eth.toFixed(4)} ETH` : '0 ETH'
},
},
{
header: 'Status',
accessor: (tx: Transaction) => (
<span className={tx.status === 1 ? 'text-green-600' : 'text-red-600'}>
{tx.status === 1 ? 'Success' : 'Failed'}
</span>
),
},
]
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-6">
{addressInfo.label || 'Address'}
</h1>
<Card title="Address Information" className="mb-6">
<div className="space-y-4">
<div>
<span className="font-semibold">Address:</span>
<Address address={addressInfo.address} className="ml-2" />
</div>
{addressInfo.tags.length > 0 && (
<div>
<span className="font-semibold">Tags:</span>
<div className="flex gap-2 mt-1">
{addressInfo.tags.map((tag, i) => (
<span key={i} className="px-2 py-1 bg-gray-200 dark:bg-gray-700 rounded text-sm">
{tag}
</span>
))}
</div>
</div>
)}
<div>
<span className="font-semibold">Transactions:</span>
<span className="ml-2">{addressInfo.transaction_count}</span>
</div>
<div>
<span className="font-semibold">Tokens:</span>
<span className="ml-2">{addressInfo.token_count}</span>
</div>
<div>
<span className="font-semibold">Type:</span>
<span className="ml-2">{addressInfo.is_contract ? 'Contract' : 'EOA'}</span>
</div>
</div>
</Card>
<Card title="Transactions">
<Table columns={transactionColumns} data={transactions} />
</Card>
</div>
)
}