Overview
This guide provides comprehensive examples of using the ZKScore React SDK in real-world applications. All examples support both ZKS IDs and wallet addresses with automatic resolution.All examples automatically handle ZKS ID resolution, so you can use either ZKS IDs (like
alice.zks) or wallet addresses. The SDK will resolve them to the appropriate format.Complete Profile Page
Full User Profile Component
Copy
import React from 'react';
import {
useScore,
useIdentity,
useAchievements,
useScoreHistory
} from '@zkscore/sdk-react';
function UserProfile({ identity }) {
const { data: score, loading: scoreLoading, error: scoreError } = useScore(identity, {
includeBreakdown: true,
realTime: true
});
const { data: identityData, loading: identityLoading, error: identityError } = useIdentity(identity);
const { data: achievements, loading: achievementsLoading, error: achievementsError } = useAchievements(identity, {
limit: 10
});
const { data: history, loading: historyLoading, error: historyError } = useScoreHistory(identity, {
timeframe: '30d',
interval: 'daily'
});
const loading = scoreLoading || identityLoading || achievementsLoading || historyLoading;
const error = scoreError || identityError || achievementsError || historyError;
if (loading) return <div className="loading">Loading profile...</div>;
if (error) return <div className="error">Error: {error.message}</div>;
return (
<div className="user-profile">
<div className="profile-header">
<div className="identity-section">
<img
src={identityData?.metadata?.avatar}
alt={identityData?.name || identity}
className="avatar"
/>
<div className="identity-info">
<h1>{identityData?.name || identity}</h1>
<p className="address">{identityData?.address}</p>
<p className="bio">{identityData?.metadata?.bio}</p>
</div>
</div>
<div className="score-section">
<div className="score-display">
<h2>ZKScore</h2>
<div className="score-value">{score?.total || 0}</div>
<div className="score-breakdown">
<div className="breakdown-item">
<span>DeFi</span>
<span>{score?.breakdown?.defi || 0}</span>
</div>
<div className="breakdown-item">
<span>NFT</span>
<span>{score?.breakdown?.nft || 0}</span>
</div>
<div className="breakdown-item">
<span>Social</span>
<span>{score?.breakdown?.social || 0}</span>
</div>
</div>
</div>
</div>
</div>
<div className="profile-content">
<div className="achievements-section">
<h3>Recent Achievements</h3>
<div className="achievements-grid">
{achievements?.earned?.slice(0, 6).map(achievement => (
<div key={achievement.id} className="achievement-card">
<img src={achievement.icon} alt={achievement.name} />
<h4>{achievement.name}</h4>
<p>{achievement.description}</p>
<span className="rarity">{achievement.rarity}</span>
</div>
))}
</div>
</div>
<div className="history-section">
<h3>Score History (30 days)</h3>
<div className="score-chart">
{history?.map((point, index) => (
<div
key={index}
className="chart-bar"
style={{
height: `${(point.total / 1000) * 100}%`,
backgroundColor: point.total > 500 ? '#28a745' : '#ffc107'
}}
title={`${point.total} on ${new Date(point.timestamp).toLocaleDateString()}`}
/>
))}
</div>
</div>
</div>
</div>
);
}
export default UserProfile;
CSS Styling
Copy
.user-profile {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.profile-header {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
margin-bottom: 40px;
padding: 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
color: white;
}
.identity-section {
display: flex;
align-items: center;
gap: 20px;
}
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
border: 3px solid white;
}
.identity-info h1 {
margin: 0 0 8px 0;
font-size: 28px;
font-weight: 700;
}
.address {
font-family: 'Monaco', 'Menlo', monospace;
font-size: 14px;
opacity: 0.8;
margin: 0 0 8px 0;
}
.bio {
margin: 0;
opacity: 0.9;
}
.score-section {
display: flex;
align-items: center;
justify-content: center;
}
.score-display {
text-align: center;
}
.score-display h2 {
margin: 0 0 16px 0;
font-size: 18px;
opacity: 0.9;
}
.score-value {
font-size: 48px;
font-weight: 700;
margin-bottom: 20px;
}
.score-breakdown {
display: flex;
gap: 20px;
justify-content: center;
}
.breakdown-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.breakdown-item span:first-child {
font-size: 12px;
opacity: 0.8;
}
.breakdown-item span:last-child {
font-size: 16px;
font-weight: 600;
}
.profile-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
}
.achievements-section h3,
.history-section h3 {
margin: 0 0 20px 0;
font-size: 20px;
color: #333;
}
.achievements-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.achievement-card {
padding: 16px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
transition: transform 0.2s;
}
.achievement-card:hover {
transform: translateY(-2px);
}
.achievement-card img {
width: 48px;
height: 48px;
margin-bottom: 12px;
}
.achievement-card h4 {
margin: 0 0 8px 0;
font-size: 14px;
color: #333;
}
.achievement-card p {
margin: 0 0 8px 0;
font-size: 12px;
color: #666;
}
.rarity {
display: inline-block;
padding: 4px 8px;
background: #e3f2fd;
color: #1976d2;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
}
.score-chart {
display: flex;
align-items: end;
gap: 4px;
height: 200px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.chart-bar {
flex: 1;
min-height: 4px;
border-radius: 2px 2px 0 0;
transition: opacity 0.2s;
}
.chart-bar:hover {
opacity: 0.8;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
font-size: 18px;
color: #666;
}
.error {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
font-size: 18px;
color: #dc3545;
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 8px;
}
Leaderboard Component
Interactive Leaderboard
Copy
import React, { useState } from 'react';
import { useLeaderboard } from '@zkscore/sdk-react';
function Leaderboard({ category = 'all', metric = 'score' }) {
const [timeframe, setTimeframe] = useState('30d');
const [limit, setLimit] = useState(20);
const { data: leaderboard, loading, error } = useLeaderboard({
category,
metric,
timeframe,
limit
});
if (loading) return <div className="loading">Loading leaderboard...</div>;
if (error) return <div className="error">Error: {error.message}</div>;
return (
<div className="leaderboard">
<div className="leaderboard-header">
<h2>Top {category} {metric} Leaderboard</h2>
<div className="controls">
<select
value={timeframe}
onChange={(e) => setTimeframe(e.target.value)}
className="timeframe-select"
>
<option value="7d">Last 7 days</option>
<option value="30d">Last 30 days</option>
<option value="90d">Last 90 days</option>
<option value="1y">Last year</option>
<option value="all">All time</option>
</select>
<select
value={limit}
onChange={(e) => setLimit(Number(e.target.value))}
className="limit-select"
>
<option value={10}>Top 10</option>
<option value={20}>Top 20</option>
<option value={50}>Top 50</option>
<option value={100}>Top 100</option>
</select>
</div>
</div>
<div className="leaderboard-content">
<div className="leaderboard-list">
{leaderboard?.rankings?.map((entry, index) => (
<div key={entry.identity} className="leaderboard-item">
<div className="rank">
{index + 1}
</div>
<div className="user-info">
<div className="avatar">
<img
src={entry.avatar || '/default-avatar.png'}
alt={entry.identity}
/>
</div>
<div className="details">
<h3>{entry.identity}</h3>
<p>{entry.address}</p>
</div>
</div>
<div className="score">
<div className="score-value">{entry.score}</div>
<div className="score-label">{metric}</div>
</div>
<div className="trend">
{entry.trend > 0 ? '📈' : entry.trend < 0 ? '📉' : '➡️'}
</div>
</div>
))}
</div>
</div>
</div>
);
}
export default Leaderboard;
CSS for Leaderboard
Copy
.leaderboard {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.leaderboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.leaderboard-header h2 {
margin: 0;
font-size: 24px;
color: #333;
}
.controls {
display: flex;
gap: 12px;
}
.timeframe-select,
.limit-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
font-size: 14px;
}
.leaderboard-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.leaderboard-item {
display: grid;
grid-template-columns: 60px 1fr auto 40px;
align-items: center;
gap: 20px;
padding: 16px;
background: #f8f9fa;
border-radius: 12px;
transition: all 0.2s;
}
.leaderboard-item:hover {
background: #e9ecef;
transform: translateX(4px);
}
.rank {
font-size: 24px;
font-weight: 700;
color: #007bff;
text-align: center;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
}
.avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid #ddd;
}
.details h3 {
margin: 0 0 4px 0;
font-size: 16px;
color: #333;
}
.details p {
margin: 0;
font-size: 12px;
color: #666;
font-family: 'Monaco', 'Menlo', monospace;
}
.score {
text-align: center;
}
.score-value {
font-size: 20px;
font-weight: 700;
color: #28a745;
}
.score-label {
font-size: 12px;
color: #666;
text-transform: uppercase;
}
.trend {
font-size: 20px;
text-align: center;
}
Achievement Tracker
Real-time Achievement Tracker
Copy
import React, { useState, useEffect } from 'react';
import { useAchievements, useAchievementProgress } from '@zkscore/sdk-react';
function AchievementTracker({ identity }) {
const [selectedCategory, setSelectedCategory] = useState('all');
const [showProgress, setShowProgress] = useState(true);
const { data: achievements, loading, error } = useAchievements(identity, {
category: selectedCategory === 'all' ? undefined : selectedCategory,
realTime: true
});
const categories = ['all', 'defi', 'nft', 'social', 'trading', 'governance', 'gaming'];
if (loading) return <div className="loading">Loading achievements...</div>;
if (error) return <div className="error">Error: {error.message}</div>;
return (
<div className="achievement-tracker">
<div className="tracker-header">
<h2>Achievement Tracker</h2>
<div className="controls">
<div className="category-filter">
<label>Category:</label>
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
>
{categories.map(category => (
<option key={category} value={category}>
{category.charAt(0).toUpperCase() + category.slice(1)}
</option>
))}
</select>
</div>
<label className="progress-toggle">
<input
type="checkbox"
checked={showProgress}
onChange={(e) => setShowProgress(e.target.checked)}
/>
Show Progress
</label>
</div>
</div>
<div className="achievement-stats">
<div className="stat">
<div className="stat-value">{achievements?.earned?.length || 0}</div>
<div className="stat-label">Earned</div>
</div>
<div className="stat">
<div className="stat-value">{achievements?.claimable?.length || 0}</div>
<div className="stat-label">Claimable</div>
</div>
<div className="stat">
<div className="stat-value">{achievements?.inProgress?.length || 0}</div>
<div className="stat-label">In Progress</div>
</div>
<div className="stat">
<div className="stat-value">
{achievements?.earned?.reduce((sum, a) => sum + a.points, 0) || 0}
</div>
<div className="stat-label">Total Points</div>
</div>
</div>
<div className="achievement-sections">
{achievements?.earned && achievements.earned.length > 0 && (
<div className="achievement-section">
<h3>✅ Earned Achievements</h3>
<div className="achievement-grid">
{achievements.earned.map(achievement => (
<AchievementCard
key={achievement.id}
achievement={achievement}
status="earned"
/>
))}
</div>
</div>
)}
{achievements?.claimable && achievements.claimable.length > 0 && (
<div className="achievement-section">
<h3>🎁 Claimable Achievements</h3>
<div className="achievement-grid">
{achievements.claimable.map(achievement => (
<AchievementCard
key={achievement.id}
achievement={achievement}
status="claimable"
identity={identity}
/>
))}
</div>
</div>
)}
{achievements?.inProgress && achievements.inProgress.length > 0 && (
<div className="achievement-section">
<h3>🚀 In Progress</h3>
<div className="achievement-grid">
{achievements.inProgress.map(achievement => (
<AchievementCard
key={achievement.id}
achievement={achievement}
status="in-progress"
identity={identity}
showProgress={showProgress}
/>
))}
</div>
</div>
)}
</div>
</div>
);
}
function AchievementCard({ achievement, status, identity, showProgress = false }) {
const { data: progress } = useAchievementProgress(identity, achievement.id, {
enabled: showProgress && status === 'in-progress'
});
const getStatusIcon = () => {
switch (status) {
case 'earned': return '✅';
case 'claimable': return '🎁';
case 'in-progress': return '🚀';
default: return '⏳';
}
};
const getRarityColor = (rarity) => {
switch (rarity) {
case 'common': return '#6c757d';
case 'uncommon': return '#28a745';
case 'rare': return '#007bff';
case 'epic': return '#6f42c1';
case 'legendary': return '#fd7e14';
default: return '#6c757d';
}
};
return (
<div className={`achievement-card ${status}`}>
<div className="achievement-header">
<div className="achievement-icon">
<img src={achievement.icon} alt={achievement.name} />
</div>
<div className="achievement-status">
{getStatusIcon()}
</div>
</div>
<div className="achievement-content">
<h4>{achievement.name}</h4>
<p>{achievement.description}</p>
<div className="achievement-meta">
<span
className="rarity"
style={{ color: getRarityColor(achievement.rarity) }}
>
{achievement.rarity}
</span>
<span className="points">{achievement.points} pts</span>
</div>
{showProgress && progress && (
<div className="achievement-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress.percentage}%` }}
/>
</div>
<div className="progress-text">
{progress.current} / {progress.required} ({progress.percentage}%)
</div>
</div>
)}
{status === 'claimable' && (
<button className="claim-button">
Claim Achievement
</button>
)}
</div>
</div>
);
}
export default AchievementTracker;
Real-world Integration Examples
DeFi Dashboard
Copy
import React from 'react';
import { useScore, useScoreHistory, useAchievements } from '@zkscore/sdk-react';
function DeFiDashboard({ identity }) {
const { data: score } = useScore(identity, { includeBreakdown: true });
const { data: history } = useScoreHistory(identity, {
timeframe: '7d',
interval: 'daily'
});
const { data: achievements } = useAchievements(identity, {
category: 'defi'
});
const defiScore = score?.breakdown?.defi || 0;
const defiTrend = calculateTrend(history?.map(h => h.breakdown?.defi || 0));
return (
<div className="defi-dashboard">
<div className="dashboard-header">
<h1>DeFi Dashboard</h1>
<div className="score-overview">
<div className="score-display">
<span className="score-label">DeFi Score</span>
<span className="score-value">{defiScore}</span>
<span className={`score-trend ${defiTrend}`}>
{defiTrend === 'up' ? '📈' : defiTrend === 'down' ? '📉' : '➡️'}
</span>
</div>
</div>
</div>
<div className="dashboard-content">
<div className="metrics-grid">
<MetricCard
title="Total Value Locked"
value="$12,450"
change="+5.2%"
trend="up"
/>
<MetricCard
title="Active Protocols"
value="8"
change="+2"
trend="up"
/>
<MetricCard
title="Yield Generated"
value="$1,234"
change="+12.8%"
trend="up"
/>
<MetricCard
title="Risk Score"
value="Low"
change="-0.3"
trend="down"
/>
</div>
<div className="achievements-section">
<h3>DeFi Achievements</h3>
<div className="achievement-list">
{achievements?.earned?.slice(0, 5).map(achievement => (
<div key={achievement.id} className="achievement-item">
<img src={achievement.icon} alt={achievement.name} />
<div className="achievement-info">
<h4>{achievement.name}</h4>
<p>{achievement.description}</p>
</div>
<span className="points">{achievement.points} pts</span>
</div>
))}
</div>
</div>
</div>
</div>
);
}
function MetricCard({ title, value, change, trend }) {
return (
<div className="metric-card">
<h4>{title}</h4>
<div className="metric-value">{value}</div>
<div className={`metric-change ${trend}`}>
{change}
</div>
</div>
);
}
function calculateTrend(values) {
if (values.length < 2) return 'stable';
const first = values[0];
const last = values[values.length - 1];
if (last > first * 1.05) return 'up';
if (last < first * 0.95) return 'down';
return 'stable';
}
export default DeFiDashboard;
NFT Portfolio Tracker
Copy
import React from 'react';
import { useScore, useAchievements } from '@zkscore/sdk-react';
function NFTPortfolio({ identity }) {
const { data: score } = useScore(identity, { includeBreakdown: true });
const { data: achievements } = useAchievements(identity, {
category: 'nft'
});
const nftScore = score?.breakdown?.nft || 0;
const nftAchievements = achievements?.earned?.filter(a => a.category === 'nft') || [];
return (
<div className="nft-portfolio">
<div className="portfolio-header">
<h1>NFT Portfolio</h1>
<div className="nft-score">
<span className="score-label">NFT Score</span>
<span className="score-value">{nftScore}</span>
</div>
</div>
<div className="portfolio-content">
<div className="nft-stats">
<div className="stat-item">
<span className="stat-label">Collections</span>
<span className="stat-value">12</span>
</div>
<div className="stat-item">
<span className="stat-label">Total NFTs</span>
<span className="stat-value">47</span>
</div>
<div className="stat-item">
<span className="stat-label">Floor Value</span>
<span className="stat-value">2.4 ETH</span>
</div>
<div className="stat-item">
<span className="stat-label">Rarity Score</span>
<span className="stat-value">8.7/10</span>
</div>
</div>
<div className="nft-achievements">
<h3>NFT Achievements</h3>
<div className="achievement-grid">
{nftAchievements.map(achievement => (
<div key={achievement.id} className="nft-achievement">
<img src={achievement.icon} alt={achievement.name} />
<h4>{achievement.name}</h4>
<p>{achievement.description}</p>
<span className="rarity">{achievement.rarity}</span>
</div>
))}
</div>
</div>
</div>
</div>
);
}
export default NFTPortfolio;
Best Practices
1. Error Handling
Copy
import React from 'react';
import { useScore } from '@zkscore/sdk-react';
function ResilientScoreDisplay({ identity }) {
const { data: score, loading, error, refetch } = useScore(identity);
const handleRetry = () => {
refetch();
};
if (loading) {
return (
<div className="loading-state">
<div className="spinner" />
<p>Loading score...</p>
</div>
);
}
if (error) {
return (
<div className="error-state">
<div className="error-icon">⚠️</div>
<h3>Failed to load score</h3>
<p>{error.message}</p>
<button onClick={handleRetry} className="retry-button">
Try Again
</button>
</div>
);
}
return (
<div className="score-display">
<h2>ZKScore</h2>
<div className="score-value">{score?.total || 0}</div>
<div className="score-breakdown">
<div>DeFi: {score?.breakdown?.defi || 0}</div>
<div>NFT: {score?.breakdown?.nft || 0}</div>
<div>Social: {score?.breakdown?.social || 0}</div>
</div>
</div>
);
}
2. Performance Optimization
Copy
import React, { memo, useMemo } from 'react';
import { useScore, useAchievements } from '@zkscore/sdk-react';
const OptimizedUserProfile = memo(function UserProfile({ identity }) {
const { data: score } = useScore(identity, { includeBreakdown: true });
const { data: achievements } = useAchievements(identity, { limit: 10 });
const scoreDisplay = useMemo(() => {
if (!score) return 'Loading...';
return `Score: ${score.total}`;
}, [score]);
const achievementCount = useMemo(() => {
return achievements?.earned?.length || 0;
}, [achievements]);
return (
<div className="user-profile">
<h2>{identity}</h2>
<p>{scoreDisplay}</p>
<p>Achievements: {achievementCount}</p>
</div>
);
});
export default OptimizedUserProfile;
3. Responsive Design
Copy
import React from 'react';
import { useScore } from '@zkscore/sdk-react';
function ResponsiveScoreCard({ identity }) {
const { data: score, loading, error } = useScore(identity);
if (loading) return <div className="loading">Loading...</div>;
if (error) return <div className="error">Error: {error.message}</div>;
return (
<div className="responsive-score-card">
<div className="score-header">
<h2>ZKScore</h2>
<div className="score-value">{score?.total || 0}</div>
</div>
<div className="score-breakdown">
<div className="breakdown-item">
<span>DeFi</span>
<span>{score?.breakdown?.defi || 0}</span>
</div>
<div className="breakdown-item">
<span>NFT</span>
<span>{score?.breakdown?.nft || 0}</span>
</div>
<div className="breakdown-item">
<span>Social</span>
<span>{score?.breakdown?.social || 0}</span>
</div>
</div>
</div>
);
}
// CSS for responsive design
const styles = `
.responsive-score-card {
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin: 20px 0;
}
.score-header {
text-align: center;
margin-bottom: 20px;
}
.score-value {
font-size: 2.5rem;
font-weight: 700;
color: #007bff;
margin: 10px 0;
}
.score-breakdown {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 16px;
}
.breakdown-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px;
background: #f8f9fa;
border-radius: 8px;
}
.breakdown-item span:first-child {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.breakdown-item span:last-child {
font-size: 18px;
font-weight: 600;
color: #333;
}
@media (max-width: 768px) {
.score-value {
font-size: 2rem;
}
.score-breakdown {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.score-breakdown {
grid-template-columns: 1fr;
}
}
`;
export default ResponsiveScoreCard;
Related Documentation
- Getting Started - SDK setup and configuration
- Hooks Reference - Complete hooks API
- Components - Pre-built components
- JavaScript SDK - Core SDK documentation