Database & State
1. State Management
1.1 Server State (React Query)
Used for caching and fetching data from API routes.
Pattern in client components:
const { data, isLoading, error } = useQuery({
queryKey: ['scan', address],
queryFn: () => fetch(`/api/v1/scan/${address}`).then(r => r.json()),
enabled: !!address,
})
1.2 Wallet State (Wagmi)
Wagmi automatically manages wallet state:
- Connection status
- Connected address
- Chain ID
- Balance
Usage:
import { useAccount, useConnect, useDisconnect } from 'wagmi'
const { address, isConnected, chain } = useAccount()
const { connect } = useConnect()
const { disconnect } = useDisconnect()
1.3 Server Components
Dashboard pages use server components (default in Next.js App Router):
- Direct database access via Prisma
- No client-side JavaScript for data fetching
- Faster initial page load
1.4 Local UI State
Modals, form inputs, and toggles use React useState.
2. Database Schema
2.1 Entity Relationship
erDiagram
Address ||--o{ Report : "has"
Report ||--o{ Vote : "receives"
Address ||--o{ ContractScan : "scanned by"
Address ||--o{ AddressTag : "tagged with"
Address ||--o{ ExternalSource : "sourced from"
Address ||--o{ Watchlist : "watched in"
Address ||--o{ EnsRecord : "resolved to"
Address ||--o{ ContractSignature : "contains"
Address {
string id "cuid"
string address
string status
int riskScore
string category
}
Report {
string reporterAddr
string reason
string status
int votesFor
int votesAgainst
}
Vote {
string voteType
string voterAddr
}
ContractScan {
int riskScore
string riskLevel
string patterns
boolean isProxy
}
AddressTag {
string tag
string taggedBy
}
ExternalSource {
string source
string confidence
json rawData
}
Watchlist {
string userAddress
}
EnsRecord {
string ensName
string fullName
string avatar
}
ContractSignature {
string selector "0x..."
string functionName
string signature
boolean isMalicious
}
UserProfile {
string address
int reputation
int reportsVerified
int tagsSubmitted
}
ScamDomain {
string domain
string category
int riskScore
string source
}
SyncLog {
string source
string status
int records
string error
}
OnchainVoteEvent {
int chainId
string contractAddress
string txHash
int logIndex
int blockNumber
string reporterAddress
string targetId
string targetType "ADDRESS/ENS/DOMAIN"
string reasonHash
boolean isScam
int blockTimestamp
}
2.2 Enums
| Enum | Values |
|---|---|
AddressStatus |
LEGIT, SCAM, SUSPICIOUS, UNKNOWN |
AddressCategory |
DEFI, NFT, BRIDGE, DEX, LENDING, PHISHING, DRAINER, AIRDROP_SCAM, RUGPULL, IMPOSTER, OTHER |
AddressType |
EOA, SMART_CONTRACT, PROXY, FACTORY |
ContractType |
DEX, NFT, TOKEN_20, TOKEN_721, TOKEN_1155, BRIDGE, LENDING, STAKING, YIELD, GOVERNANCE, MULTISIG, AIRDROP, MINTER, DRAINER, PHISHING, IMPOSTER, ROUTER, VAULT, FACTORY, OTHER |
DataSource |
COMMUNITY, SCANNER, EXTERNAL, SEED, ADMIN |
ReportStatus |
PENDING, VERIFIED, REJECTED, DISPUTED |
VoteType |
FOR, AGAINST |
RiskLevel |
LOW, MEDIUM, HIGH, CRITICAL |
TargetType |
ADDRESS, ENS, DOMAIN |
2.3 Key Indexes
The Address table has comprehensive indexing:
status,category,riskScore,source,chain(individual)(source, chain, status)— composite for filtered queries(status, riskScore)— scam filtering(addressType),(contractType)— type filtering(firstSeenAt),(lastSeenAt)— time-based queries(status, firstSeenAt)— recent scams
ContractScan indexes: riskLevel, createdAt, addressId, bytecodeHash (similarity), isProxy, checkerAddress
OnchainVoteEvent indexes: (txHash, logIndex) unique, (chainId, blockNumber), (targetId, targetType), reporterAddress, reasonHash
2.4 Prisma Client (lib/prisma.ts)
Uses PostgreSQL adapter (@prisma/adapter-pg) for connection pooling:
const pool = new pg.Pool({
connectionString: process.env.DIRECT_URL ?? process.env.DATABASE_URL,
})
const adapter = new PrismaPg(pool)
const prisma = new PrismaClient({ adapter })
Singleton pattern — prevents multiple instances during development (hot reload).