React Hooks
The useCredDAO hook library provides type-safe, cached data fetching and mutations.
Installation
Hooks are included in the frontend package. Import from:
import {
useFairScore,
useVotingPower,
useProposals,
useTransaction,
useToast
} from '@/hooks/useCredDAO'Query Hooks
useFairScore
Fetch FairScore data for a wallet:
const { data, loading, error, refetch } = useFairScore(walletAddress)
// Auto-uses connected wallet if no address provided
const { data } = useFairScore()
// data structure
interface FairScoreData {
wallet: string
fairscore_base: number
social_score: number
fairscore: number
tier: 'bronze' | 'silver' | 'gold' | 'platinum'
badges: Array<{
id: string
label: string
description: string
tier: string
}>
features?: {
active_days?: number
tx_count?: number
lst_percentile_score?: number
native_sol_percentile?: number
wallet_age_days?: number
}
}useVotingPower
Calculate voting power breakdown:
const { votingPower, quadraticBase, reputationMultiplier, loading, error } =
useVotingPower(tokenBalance)
// votingPower = quadraticBase * reputationMultiplier
// quadraticBase = √(tokenBalance)
// reputationMultiplier = 1 + fairscore/50useProposals
Fetch proposals with optimistic updates:
const { data, loading, error, optimisticProposals, refetch } = useProposals({
state: 'active',
limit: 20
})
// optimisticProposals includes pending local changesuseProposal
Fetch single proposal with pending vote:
const { data, pendingVote, loading, error } = useProposal(proposalId)
// pendingVote shows user's pending vote before confirmation
if (pendingVote) {
console.log(`Pending: ${pendingVote.vote} with ${pendingVote.votingPower} power`)
}useDelegates
Fetch delegate leaderboard:
const { data, loading, error } = useDelegates(20)
// data structure
interface Delegate {
wallet: string
fairscore: number
tier: string
participationRate: number
delegationEfficiency: number
delegatedPower: number
proposalsVoted: number
}useAnalytics
Fetch governance analytics:
const { data, loading, error } = useAnalytics()
// Auto-refreshes every 5 minutesMutation Hooks
useCreateProposal
Create proposal with optimistic update:
const { mutate, loading, error, data } = useCreateProposal()
await mutate({
title: 'Treasury Transfer',
description: 'Transfer 1000 USDC...',
type: 'standard',
proposerWallet: wallet.publicKey.toBase58()
})useCastVote
Cast vote with optimistic feedback:
const { mutate, loading, error } = useCastVote()
await mutate({
proposalId: 'prop_xxx',
voterWallet: wallet.publicKey.toBase58(),
vote: 'for',
votingPower: 150
})Transaction Hook
useTransaction
Handle Solana transactions with status tracking:
const { status, signature, error, execute, reset } = useTransaction()
// status: 'idle' | 'preparing' | 'signing' | 'confirming' | 'confirmed' | 'failed'
const handleVote = async () => {
const sig = await execute(async () => {
const tx = await createVoteTransaction()
await wallet.sendTransaction(tx, connection)
return { signature: tx.signature }
})
if (sig) {
toast.success('Vote confirmed!')
}
}Utility Functions
withRetry
Exponential backoff retry wrapper:
import { withRetry } from '@/hooks/useCredDAO'
const result = await withRetry(
() => fetchFromAPI(),
{ maxRetries: 3, baseDelayMs: 200 }
)globalCache
Stale-while-revalidate cache:
import { globalCache } from '@/hooks/useCredDAO'
// Get cached value
const cached = globalCache.get<FairScoreData>('fairscore:wallet')
// Set with TTL
globalCache.set('fairscore:wallet', data, 5 * 60 * 1000)
// Invalidate pattern
globalCache.invalidate('proposals:')Optimistic Updates
OptimisticProvider
Wrap your app to enable optimistic updates:
import { OptimisticProvider } from '@/hooks/useCredDAO'
<OptimisticProvider>
<App />
</OptimisticProvider>useOptimistic
Access optimistic state directly:
const {
state,
addPendingVote,
confirmVote,
rollbackVote,
addPendingProposal,
confirmProposal,
rollbackProposal
} = useOptimistic()
// state contains pending votes and proposals
state.pendingVotes
state.pendingProposals
state.pendingDelegationsCustom Query Hook
useQuery
Build custom queries with caching:
const { data, loading, error, refetch, isValidating } = useQuery(
'custom-key',
() => fetchCustomData(),
{
ttl: 5 * 60 * 1000, // 5 minute cache
refreshInterval: 60000, // Auto-refresh every minute
enabled: !!walletAddress // Conditional fetching
}
)Admin Hooks
useAdminAuth
Admin wallet signature authentication:
import { useAdminAuth } from '@/hooks/useAdminAuth'
const {
isAuthenticated,
isAdmin,
adminRole,
token,
authenticate,
logout
} = useAdminAuth()
// Call authenticate to sign message
await authenticate()
// Use token for admin requests
fetch('/api/admin/stats', {
headers: { Authorization: `Bearer ${token}` }
})useInfiniteScroll
Paginated infinite scroll:
import { useInfiniteScroll, usePaginatedQuery } from '@/hooks/useInfiniteScroll'
const { items, hasMore, loading, fetchMore } = usePaginatedQuery(
async (offset, limit) => {
const res = await fetch(`/api/proposals?offset=${offset}&limit=${limit}`)
const data = await res.json()
return { items: data.proposals, hasMore: data.hasMore, total: data.total }
},
20
)
const { loadMoreRef, isFetching } = useInfiniteScroll(fetchMore)
// Add ref to load more trigger
<div ref={loadMoreRef} />