+ {/* Account Details */}
+
+
+ Account
+
+ {selectedAccount.meta.name || 'Unnamed'}
+
+
+
+ Address
+
+ {selectedAccount.address.slice(0, 8)}...{selectedAccount.address.slice(-8)}
+
+
-
- Address
-
- {selectedAccount.address.slice(0, 8)}...{selectedAccount.address.slice(-8)}
-
-
-
-
-
- Trust Score
-
-
- {trustScore}
-
+
+ {/* Scores from Blockchain */}
+
+
Scores from Blockchain
+ {loadingScores ? (
+
Loading scores...
+ ) : (
+
+ {/* Score Grid */}
+
+
+
+
+ Trust
+
+
{scores.trustScore}
+
+
+
+
+ Referral
+
+
{scores.referralScore}
+
+
+
+
+ Staking
+
+
{scores.stakingScore}
+
+
+
+
+ {/* Total Score */}
+
+
+ Total Score
+
+ {scores.totalScore}
+
+
+
+
+ )}
diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx
index c099a0e9..6804b701 100644
--- a/src/components/AppLayout.tsx
+++ b/src/components/AppLayout.tsx
@@ -21,7 +21,7 @@ import { TreasuryOverview } from './treasury/TreasuryOverview';
import { FundingProposal } from './treasury/FundingProposal';
import { SpendingHistory } from './treasury/SpendingHistory';
import { MultiSigApproval } from './treasury/MultiSigApproval';
-import { Github, FileText, ExternalLink, Shield, Award, User, FileEdit, Users2, MessageSquare, ShieldCheck, Wifi, WifiOff, Wallet, DollarSign, PiggyBank, History, Key, TrendingUp, ArrowRightLeft, Lock, LogIn, LayoutDashboard, Settings, UserCog, Repeat } from 'lucide-react';
+import { Github, FileText, ExternalLink, Shield, Award, User, FileEdit, Users2, MessageSquare, ShieldCheck, Wifi, WifiOff, Wallet, DollarSign, PiggyBank, History, Key, TrendingUp, ArrowRightLeft, Lock, LogIn, LayoutDashboard, Settings, UserCog, Repeat, Users } from 'lucide-react';
import GovernanceInterface from './GovernanceInterface';
import RewardDistribution from './RewardDistribution';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
@@ -100,6 +100,14 @@ const AppLayout: React.FC = () => {
Wallet
+
navigate('/be-citizen')}
+ className="text-cyan-300 hover:text-cyan-100 transition-colors flex items-center gap-1 text-sm font-semibold"
+ >
+
+ Be Citizen
+
+
{/* Governance Dropdown */}
@@ -212,13 +220,22 @@ const AppLayout: React.FC = () => {
>
) : (
-
navigate('/login')}
- className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2 text-sm"
- >
-
- Login
-
+ <>
+
navigate('/be-citizen')}
+ className="text-cyan-300 hover:text-cyan-100 transition-colors flex items-center gap-1 text-sm font-semibold"
+ >
+
+ Be Citizen
+
+
navigate('/login')}
+ className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2 text-sm"
+ >
+
+ Login
+
+ >
)}
{
+ const roleLower = role.toLowerCase();
+
+ if (roleLower.includes('hemwelatî') || roleLower.includes('welati') || roleLower.includes('citizen')) {
+ return ;
+ }
+ if (roleLower.includes('leader') || roleLower.includes('chief')) {
+ return ;
+ }
+ if (roleLower.includes('elder') || roleLower.includes('wise')) {
+ return ;
+ }
+ return ;
+};
+
+// Color scheme for different roles
+const getRoleBadgeColor = (role: string) => {
+ const roleLower = role.toLowerCase();
+
+ if (roleLower.includes('hemwelatî') || roleLower.includes('welati') || roleLower.includes('citizen')) {
+ return 'bg-cyan-500/10 text-cyan-500 border-cyan-500/30';
+ }
+ if (roleLower.includes('leader') || roleLower.includes('chief')) {
+ return 'bg-yellow-500/10 text-yellow-500 border-yellow-500/30';
+ }
+ if (roleLower.includes('elder') || roleLower.includes('wise')) {
+ return 'bg-purple-500/10 text-purple-500 border-purple-500/30';
+ }
+ return 'bg-green-500/10 text-green-500 border-green-500/30';
+};
+
+export const NftList: React.FC = () => {
+ const { api, isApiReady, selectedAccount } = usePolkadot();
+ const [tikis, setTikis] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchTikis = async () => {
+ if (!api || !isApiReady || !selectedAccount) {
+ setLoading(false);
+ return;
+ }
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ const userTikis = await getUserTikis(api, selectedAccount.address);
+ setTikis(userTikis);
+ } catch (err) {
+ console.error('Error fetching Tikis:', err);
+ setError('Failed to load NFTs');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchTikis();
+ }, [api, isApiReady, selectedAccount]);
+
+ if (loading) {
+ return (
+
+
+ Your NFTs (Tikis)
+ Your Tiki collection
+
+
+
+
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+ Your NFTs (Tikis)
+ Your Tiki collection
+
+
+
+
+
+ );
+ }
+
+ if (tikis.length === 0) {
+ return (
+
+
+ Your NFTs (Tikis)
+ Your Tiki collection
+
+
+
+
+
No NFTs yet
+
+ Complete your citizenship application to receive your Welati Tiki NFT
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ Your NFTs (Tikiler)
+
+ Your Tiki collection ({tikis.length} total)
+
+
+
+ {tikis.map((tiki, index) => (
+
+
+ {/* Icon */}
+
+ {getTikiIcon(tiki.role)}
+
+
+ {/* Info */}
+
+
+
+ Tiki #{tiki.id}
+
+
+ {tiki.role === 'Hemwelatî' ? 'Welati' : tiki.role}
+
+
+
+ {/* Metadata if available */}
+ {tiki.metadata && typeof tiki.metadata === 'object' && (
+
+ {Object.entries(tiki.metadata).map(([key, value]) => (
+
+ {key}: {' '}
+ {String(value)}
+
+ ))}
+
+ )}
+
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/src/components/citizenship/CitizenshipModal.tsx b/src/components/citizenship/CitizenshipModal.tsx
new file mode 100644
index 00000000..3e77b59d
--- /dev/null
+++ b/src/components/citizenship/CitizenshipModal.tsx
@@ -0,0 +1,50 @@
+import React, { useState } from 'react';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { ExistingCitizenAuth } from './ExistingCitizenAuth';
+import { NewCitizenApplication } from './NewCitizenApplication';
+
+interface CitizenshipModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export const CitizenshipModal: React.FC = ({ isOpen, onClose }) => {
+ const [activeTab, setActiveTab] = useState<'existing' | 'new'>('existing');
+
+ return (
+
+
+
+
+ 🏛️ Digital Kurdistan Citizenship
+
+
+ Join the Digital Kurdistan State as a citizen or authenticate your existing citizenship
+
+
+
+ setActiveTab(v as 'existing' | 'new')} className="w-full">
+
+ I am Already a Citizen
+ I Want to Become a Citizen
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/citizenship/ExistingCitizenAuth.tsx b/src/components/citizenship/ExistingCitizenAuth.tsx
new file mode 100644
index 00000000..445fc89e
--- /dev/null
+++ b/src/components/citizenship/ExistingCitizenAuth.tsx
@@ -0,0 +1,237 @@
+import React, { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Loader2, CheckCircle, AlertTriangle, Shield } from 'lucide-react';
+import { usePolkadot } from '@/contexts/PolkadotContext';
+import { verifyNftOwnership } from '@/lib/citizenship-workflow';
+import { generateAuthChallenge, signChallenge, verifySignature, saveCitizenSession } from '@/lib/citizenship-crypto';
+import type { AuthChallenge } from '@/lib/citizenship-crypto';
+
+interface ExistingCitizenAuthProps {
+ onClose: () => void;
+}
+
+export const ExistingCitizenAuth: React.FC = ({ onClose }) => {
+ const { api, isApiReady, selectedAccount, connectWallet } = usePolkadot();
+
+ const [tikiNumber, setTikiNumber] = useState('');
+ const [step, setStep] = useState<'input' | 'verifying' | 'signing' | 'success' | 'error'>('input');
+ const [error, setError] = useState(null);
+ const [challenge, setChallenge] = useState(null);
+
+ const handleVerifyNFT = async () => {
+ if (!api || !isApiReady || !selectedAccount) {
+ setError('Please connect your wallet first');
+ return;
+ }
+
+ if (!tikiNumber.trim()) {
+ setError('Please enter your Welati Tiki NFT number');
+ return;
+ }
+
+ setError(null);
+ setStep('verifying');
+
+ try {
+ // Verify NFT ownership
+ const ownsNFT = await verifyNftOwnership(api, tikiNumber, selectedAccount.address);
+
+ if (!ownsNFT) {
+ setError(`NFT #${tikiNumber} not found in your wallet or not a Welati Tiki`);
+ setStep('error');
+ return;
+ }
+
+ // Generate challenge for signature
+ const authChallenge = generateAuthChallenge(tikiNumber);
+ setChallenge(authChallenge);
+ setStep('signing');
+ } catch (err) {
+ console.error('Verification error:', err);
+ setError('Failed to verify NFT ownership');
+ setStep('error');
+ }
+ };
+
+ const handleSignChallenge = async () => {
+ if (!selectedAccount || !challenge) {
+ setError('Missing authentication data');
+ return;
+ }
+
+ setError(null);
+
+ try {
+ // Sign the challenge
+ const signature = await signChallenge(selectedAccount, challenge);
+
+ // Verify signature (self-verification for demonstration)
+ const isValid = await verifySignature(signature, challenge, selectedAccount.address);
+
+ if (!isValid) {
+ setError('Signature verification failed');
+ setStep('error');
+ return;
+ }
+
+ // Save session
+ const session = {
+ tikiNumber,
+ walletAddress: selectedAccount.address,
+ sessionToken: signature, // In production, use proper JWT
+ lastAuthenticated: Date.now(),
+ expiresAt: Date.now() + (24 * 60 * 60 * 1000) // 24 hours
+ };
+
+ await saveCitizenSession(session);
+
+ setStep('success');
+
+ // Redirect to citizen dashboard after 2 seconds
+ setTimeout(() => {
+ // TODO: Navigate to citizen dashboard
+ onClose();
+ window.location.href = '/dashboard'; // Or use router.push('/dashboard')
+ }, 2000);
+ } catch (err) {
+ console.error('Signature error:', err);
+ setError('Failed to sign authentication challenge');
+ setStep('error');
+ }
+ };
+
+ const handleConnectWallet = async () => {
+ try {
+ await connectWallet();
+ } catch (err) {
+ setError('Failed to connect wallet');
+ }
+ };
+
+ return (
+
+
+
+
+
+ Authenticate as Citizen
+
+
+ Enter your Welati Tiki NFT number to authenticate
+
+
+
+ {/* Step 1: Enter NFT Number */}
+ {step === 'input' && (
+ <>
+
+
Welati Tiki NFT Number
+
setTikiNumber(e.target.value)}
+ onKeyDown={(e) => e.key === 'Enter' && handleVerifyNFT()}
+ />
+
+ This is your unique citizen ID number received after KYC approval
+
+
+
+ {!selectedAccount ? (
+
+ Connect Wallet First
+
+ ) : (
+
+ Verify NFT Ownership
+
+ )}
+ >
+ )}
+
+ {/* Step 2: Verifying */}
+ {step === 'verifying' && (
+
+
+
Verifying NFT ownership on blockchain...
+
+ )}
+
+ {/* Step 3: Sign Challenge */}
+ {step === 'signing' && (
+ <>
+
+
+
+ NFT ownership verified! Now sign to prove you control this wallet.
+
+
+
+
+
Authentication Challenge:
+
+ {challenge?.nonce}
+
+
+
+
+ Sign Message to Authenticate
+
+ >
+ )}
+
+ {/* Step 4: Success */}
+ {step === 'success' && (
+
+
+
Authentication Successful!
+
+ Welcome back, Citizen #{tikiNumber}
+
+
+ Redirecting to citizen dashboard...
+
+
+ )}
+
+ {/* Error State */}
+ {error && (
+
+
+ {error}
+
+ )}
+
+ {step === 'error' && (
+ { setStep('input'); setError(null); }} variant="outline" className="w-full">
+ Try Again
+
+ )}
+
+
+
+ {/* Security Info */}
+
+
+
+
+
+ Security Information
+
+
+ • Your NFT number is cryptographically verified on-chain
+ • Signature proves you control the wallet without revealing private keys
+ • Session expires after 24 hours for your security
+ • No personal data is transmitted or stored on-chain
+
+
+
+
+
+ );
+};
diff --git a/src/components/citizenship/NewCitizenApplication.tsx b/src/components/citizenship/NewCitizenApplication.tsx
new file mode 100644
index 00000000..ba0c3f62
--- /dev/null
+++ b/src/components/citizenship/NewCitizenApplication.tsx
@@ -0,0 +1,538 @@
+import React, { useState, useEffect } from 'react';
+import { useForm } from 'react-hook-form';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Checkbox } from '@/components/ui/checkbox';
+import { Loader2, AlertTriangle, CheckCircle, User, Users as UsersIcon, MapPin, Briefcase, Mail, Clock } from 'lucide-react';
+import { usePolkadot } from '@/contexts/PolkadotContext';
+import type { CitizenshipData, Region, MaritalStatus } from '@/lib/citizenship-workflow';
+import { FOUNDER_ADDRESS, submitKycApplication, subscribeToKycApproval, getKycStatus } from '@/lib/citizenship-workflow';
+import { generateCommitmentHash, generateNullifierHash, encryptData, saveLocalCitizenshipData, uploadToIPFS } from '@/lib/citizenship-crypto';
+
+interface NewCitizenApplicationProps {
+ onClose: () => void;
+}
+
+type FormData = Omit;
+
+export const NewCitizenApplication: React.FC = ({ onClose }) => {
+ const { api, isApiReady, selectedAccount, connectWallet } = usePolkadot();
+ const { register, handleSubmit, watch, setValue, formState: { errors } } = useForm();
+
+ const [submitting, setSubmitting] = useState(false);
+ const [submitted, setSubmitted] = useState(false);
+ const [waitingForApproval, setWaitingForApproval] = useState(false);
+ const [kycApproved, setKycApproved] = useState(false);
+ const [error, setError] = useState(null);
+ const [agreed, setAgreed] = useState(false);
+ const [checkingStatus, setCheckingStatus] = useState(false);
+
+ const maritalStatus = watch('maritalStatus');
+ const childrenCount = watch('childrenCount');
+
+ // Check KYC status on mount
+ useEffect(() => {
+ const checkKycStatus = async () => {
+ if (!api || !isApiReady || !selectedAccount) {
+ return;
+ }
+
+ setCheckingStatus(true);
+ try {
+ const status = await getKycStatus(api, selectedAccount.address);
+ console.log('Current KYC Status:', status);
+
+ if (status === 'Approved') {
+ console.log('KYC already approved! Redirecting to dashboard...');
+ setKycApproved(true);
+
+ // Redirect to dashboard after 2 seconds
+ setTimeout(() => {
+ onClose();
+ window.location.href = '/dashboard';
+ }, 2000);
+ } else if (status === 'Pending') {
+ // If pending, show the waiting screen
+ setWaitingForApproval(true);
+ }
+ } catch (err) {
+ console.error('Error checking KYC status:', err);
+ } finally {
+ setCheckingStatus(false);
+ }
+ };
+
+ checkKycStatus();
+ }, [api, isApiReady, selectedAccount, onClose]);
+
+ // Subscribe to KYC approval events
+ useEffect(() => {
+ if (!api || !isApiReady || !selectedAccount || !waitingForApproval) {
+ return;
+ }
+
+ console.log('Setting up KYC approval listener for:', selectedAccount.address);
+
+ const unsubscribe = subscribeToKycApproval(
+ api,
+ selectedAccount.address,
+ () => {
+ console.log('KYC Approved! Redirecting to dashboard...');
+ setKycApproved(true);
+ setWaitingForApproval(false);
+
+ // Redirect to citizen dashboard after 2 seconds
+ setTimeout(() => {
+ onClose();
+ window.location.href = '/dashboard';
+ }, 2000);
+ },
+ (error) => {
+ console.error('KYC approval subscription error:', error);
+ setError(`Failed to monitor approval status: ${error}`);
+ }
+ );
+
+ return () => {
+ if (unsubscribe) {
+ unsubscribe();
+ }
+ };
+ }, [api, isApiReady, selectedAccount, waitingForApproval, onClose]);
+
+ const onSubmit = async (data: FormData) => {
+ if (!api || !isApiReady || !selectedAccount) {
+ setError('Please connect your wallet first');
+ return;
+ }
+
+ if (!agreed) {
+ setError('Please agree to the terms');
+ return;
+ }
+
+ setError(null);
+ setSubmitting(true);
+
+ try {
+ // Check KYC status before submitting
+ const currentStatus = await getKycStatus(api, selectedAccount.address);
+
+ if (currentStatus === 'Approved') {
+ setError('Your KYC has already been approved! Redirecting to dashboard...');
+ setKycApproved(true);
+ setTimeout(() => {
+ onClose();
+ window.location.href = '/dashboard';
+ }, 2000);
+ return;
+ }
+
+ if (currentStatus === 'Pending') {
+ setError('You already have a pending KYC application. Please wait for admin approval.');
+ setWaitingForApproval(true);
+ return;
+ }
+
+ // Prepare complete citizenship data
+ const citizenshipData: CitizenshipData = {
+ ...data,
+ walletAddress: selectedAccount.address,
+ timestamp: Date.now(),
+ referralCode: data.referralCode || FOUNDER_ADDRESS // Auto-assign to founder if empty
+ };
+
+ // Generate commitment and nullifier hashes
+ const commitmentHash = await generateCommitmentHash(citizenshipData);
+ const nullifierHash = await generateNullifierHash(selectedAccount.address, citizenshipData.timestamp);
+
+ console.log('Commitment Hash:', commitmentHash);
+ console.log('Nullifier Hash:', nullifierHash);
+
+ // Encrypt data
+ const encryptedData = await encryptData(citizenshipData, selectedAccount.address);
+
+ // Save to local storage (backup)
+ await saveLocalCitizenshipData(citizenshipData, selectedAccount.address);
+
+ // Upload to IPFS
+ const ipfsCid = await uploadToIPFS(encryptedData);
+
+ console.log('IPFS CID:', ipfsCid);
+ console.log('IPFS CID type:', typeof ipfsCid);
+ console.log('IPFS CID value:', JSON.stringify(ipfsCid));
+
+ // Ensure ipfsCid is a string
+ const cidString = String(ipfsCid);
+ if (!cidString || cidString === 'undefined' || cidString === '[object Object]') {
+ throw new Error(`Invalid IPFS CID: ${cidString}`);
+ }
+
+ // Submit to blockchain
+ console.log('Submitting KYC application to blockchain...');
+ const result = await submitKycApplication(
+ api,
+ selectedAccount,
+ citizenshipData.fullName,
+ citizenshipData.email,
+ cidString,
+ `Citizenship application for ${citizenshipData.fullName}`
+ );
+
+ if (!result.success) {
+ setError(result.error || 'Failed to submit KYC application to blockchain');
+ setSubmitting(false);
+ return;
+ }
+
+ console.log('✅ KYC application submitted to blockchain');
+ console.log('Block hash:', result.blockHash);
+
+ // Move to waiting for approval state
+ setSubmitted(true);
+ setSubmitting(false);
+ setWaitingForApproval(true);
+
+ } catch (err) {
+ console.error('Submission error:', err);
+ setError('Failed to submit citizenship application');
+ setSubmitting(false);
+ }
+ };
+
+ if (!selectedAccount) {
+ return (
+
+
+ Connect Wallet Required
+
+ You need to connect your wallet to apply for citizenship
+
+
+
+
+ Connect Wallet
+
+
+
+ );
+ }
+
+ // KYC Approved - Success state
+ if (kycApproved) {
+ return (
+
+
+
+ KYC Approved!
+
+ Congratulations! Your citizenship application has been approved. Redirecting to citizen dashboard...
+
+
+
+ );
+ }
+
+ // Waiting for approval - Loading state
+ if (waitingForApproval) {
+ return (
+
+
+ {/* Animated Loader with Halos */}
+
+ {/* Outer halo */}
+
+ {/* Middle halo */}
+
+ {/* Inner spinning sun */}
+
+
+
+
+
+
+
+
Waiting for Admin Approval
+
+ Your application has been submitted to the blockchain and is waiting for admin approval.
+ This page will automatically update when your citizenship is approved.
+
+
+
+ {/* Status steps */}
+
+
+
+ Application encrypted and stored on IPFS
+
+
+
+ Transaction submitted to blockchain
+
+
+
+ Waiting for admin to approve KYC...
+
+
+
+ Receive Welati Tiki NFT
+
+
+
+ {/* Info */}
+
+
+ Note: Do not close this page. The system is monitoring the blockchain
+ for approval events in real-time. You will be automatically redirected once approved.
+
+
+
+
+ );
+ }
+
+ // Initial submission success (before blockchain confirmation)
+ if (submitted && !waitingForApproval) {
+ return (
+
+
+
+ Processing Application...
+
+ Encrypting your data and submitting to the blockchain. Please wait...
+
+
+
+ );
+ }
+
+ return (
+
+ );
+};
diff --git a/src/components/wallet/WalletModal.tsx b/src/components/wallet/WalletModal.tsx
index baa6e314..54f3845c 100644
--- a/src/components/wallet/WalletModal.tsx
+++ b/src/components/wallet/WalletModal.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
-import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award } from 'lucide-react';
+import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award, Users, TrendingUp, Shield } from 'lucide-react';
import {
Dialog,
DialogContent,
@@ -10,6 +10,7 @@ import {
import { Button } from '@/components/ui/button';
import { usePolkadot } from '@/contexts/PolkadotContext';
import { formatAddress } from '@/lib/wallet';
+import { getAllScores, type UserScores } from '@/lib/scores';
interface WalletModalProps {
isOpen: boolean;
@@ -29,7 +30,14 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) =>
} = usePolkadot();
const [copied, setCopied] = useState(false);
- const [trustScore, setTrustScore] = useState('-');
+ const [scores, setScores] = useState({
+ trustScore: 0,
+ referralScore: 0,
+ stakingScore: 0,
+ tikiScore: 0,
+ totalScore: 0
+ });
+ const [loadingScores, setLoadingScores] = useState(false);
const handleCopyAddress = () => {
if (selectedAccount?.address) {
@@ -53,39 +61,39 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) =>
onClose();
};
- // Fetch trust score from blockchain
+ // Fetch all scores from blockchain
useEffect(() => {
- const fetchTrustScore = async () => {
- console.log('🔍 Fetching trust score...', {
- hasApi: !!api,
- isApiReady,
- hasAccount: !!selectedAccount,
- address: selectedAccount?.address
- });
-
+ const fetchAllScores = async () => {
if (!api || !isApiReady || !selectedAccount?.address) {
- console.log('⚠️ Cannot fetch trust score - missing requirements');
- setTrustScore('-');
+ setScores({
+ trustScore: 0,
+ referralScore: 0,
+ stakingScore: 0,
+ tikiScore: 0,
+ totalScore: 0
+ });
return;
}
+ setLoadingScores(true);
try {
- console.log('📡 Querying api.query.trust.trustScores...');
- const score = await api.query.trust.trustScores(selectedAccount.address);
- const scoreStr = score.toString();
- setTrustScore(scoreStr);
- console.log('✅ Trust score fetched successfully:', scoreStr);
+ const userScores = await getAllScores(api, selectedAccount.address);
+ setScores(userScores);
} catch (err) {
- console.error('❌ Failed to fetch trust score:', err);
- console.error('Error details:', {
- message: err instanceof Error ? err.message : String(err),
- stack: err instanceof Error ? err.stack : undefined
+ console.error('Failed to fetch scores:', err);
+ setScores({
+ trustScore: 0,
+ referralScore: 0,
+ stakingScore: 0,
+ tikiScore: 0,
+ totalScore: 0
});
- setTrustScore('-');
+ } finally {
+ setLoadingScores(false);
}
};
- fetchTrustScore();
+ fetchAllScores();
}, [api, isApiReady, selectedAccount]);
return (
@@ -164,12 +172,48 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) =>
-
Trust Score
-
-
-
- {trustScore}
-
+
Scores from Blockchain
+ {loadingScores ? (
+
Loading scores...
+ ) : (
+
+
+
+
+ Trust
+
+
{scores.trustScore}
+
+
+
+
+ Referral
+
+
{scores.referralScore}
+
+
+
+
+ Staking
+
+
{scores.stakingScore}
+
+
+
+ )}
+
+
+ Total Score
+
+ {loadingScores ? '...' : scores.totalScore}
+
+
diff --git a/src/contexts/PolkadotContext.tsx b/src/contexts/PolkadotContext.tsx
index 53280aa7..2ac38644 100644
--- a/src/contexts/PolkadotContext.tsx
+++ b/src/contexts/PolkadotContext.tsx
@@ -23,7 +23,7 @@ interface PolkadotProviderProps {
export const PolkadotProvider: React.FC
= ({
children,
- endpoint = 'wss://beta.pezkuwichain.io' // Beta testnet RPC
+ endpoint = 'wss://beta-rpc.pezkuwi.art' // Beta testnet RPC
}) => {
const [api, setApi] = useState(null);
const [isApiReady, setIsApiReady] = useState(false);
diff --git a/src/lib/citizenship-crypto.ts b/src/lib/citizenship-crypto.ts
new file mode 100644
index 00000000..d081acb3
--- /dev/null
+++ b/src/lib/citizenship-crypto.ts
@@ -0,0 +1,404 @@
+// ========================================
+// Citizenship Crypto Utilities
+// ========================================
+// Handles encryption, hashing, signatures for citizenship data
+
+import { web3FromAddress } from '@polkadot/extension-dapp';
+import { stringToHex, hexToU8a, u8aToHex, stringToU8a } from '@polkadot/util';
+import { decodeAddress, signatureVerify, cryptoWaitReady } from '@polkadot/util-crypto';
+import type { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';
+import type { CitizenshipData } from './citizenship-workflow';
+
+// ========================================
+// HASHING FUNCTIONS
+// ========================================
+
+/**
+ * Generate SHA-256 hash from data
+ */
+export async function generateHash(data: string): Promise {
+ const encoder = new TextEncoder();
+ const dataBuffer = encoder.encode(data);
+ const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
+ const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
+ return `0x${hashHex}`;
+}
+
+/**
+ * Generate commitment hash from citizenship data
+ */
+export async function generateCommitmentHash(
+ data: CitizenshipData
+): Promise {
+ const dataString = JSON.stringify({
+ fullName: data.fullName,
+ fatherName: data.fatherName,
+ grandfatherName: data.grandfatherName,
+ motherName: data.motherName,
+ tribe: data.tribe,
+ maritalStatus: data.maritalStatus,
+ childrenCount: data.childrenCount,
+ children: data.children,
+ region: data.region,
+ email: data.email,
+ profession: data.profession,
+ referralCode: data.referralCode,
+ walletAddress: data.walletAddress,
+ timestamp: data.timestamp
+ });
+
+ return generateHash(dataString);
+}
+
+/**
+ * Generate nullifier hash (prevents double-registration)
+ */
+export async function generateNullifierHash(
+ walletAddress: string,
+ timestamp: number
+): Promise {
+ const nullifierData = `${walletAddress}-${timestamp}-nullifier`;
+ return generateHash(nullifierData);
+}
+
+// ========================================
+// ENCRYPTION / DECRYPTION (AES-GCM)
+// ========================================
+
+/**
+ * Derive encryption key from wallet address
+ * NOTE: For MVP, we use a deterministic key. For production, use proper key derivation
+ */
+async function deriveEncryptionKey(walletAddress: string): Promise {
+ // Create a deterministic seed from wallet address
+ const seed = await generateHash(walletAddress);
+
+ // Convert hex to ArrayBuffer
+ const keyMaterial = hexToU8a(seed).slice(0, 32); // 256-bit key
+
+ // Import as AES-GCM key
+ return crypto.subtle.importKey(
+ 'raw',
+ keyMaterial,
+ { name: 'AES-GCM', length: 256 },
+ false,
+ ['encrypt', 'decrypt']
+ );
+}
+
+/**
+ * Encrypt citizenship data
+ */
+export async function encryptData(
+ data: CitizenshipData,
+ walletAddress: string
+): Promise {
+ try {
+ const key = await deriveEncryptionKey(walletAddress);
+
+ // Generate random IV (Initialization Vector)
+ const iv = crypto.getRandomValues(new Uint8Array(12));
+
+ // Encrypt data
+ const encoder = new TextEncoder();
+ const dataBuffer = encoder.encode(JSON.stringify(data));
+
+ const encryptedBuffer = await crypto.subtle.encrypt(
+ { name: 'AES-GCM', iv },
+ key,
+ dataBuffer
+ );
+
+ // Combine IV + encrypted data
+ const combined = new Uint8Array(iv.length + encryptedBuffer.byteLength);
+ combined.set(iv, 0);
+ combined.set(new Uint8Array(encryptedBuffer), iv.length);
+
+ // Convert to hex
+ return u8aToHex(combined);
+ } catch (error) {
+ console.error('Encryption error:', error);
+ throw new Error('Failed to encrypt data');
+ }
+}
+
+/**
+ * Decrypt citizenship data
+ */
+export async function decryptData(
+ encryptedHex: string,
+ walletAddress: string
+): Promise {
+ try {
+ const key = await deriveEncryptionKey(walletAddress);
+
+ // Convert hex to Uint8Array
+ const combined = hexToU8a(encryptedHex);
+
+ // Extract IV and encrypted data
+ const iv = combined.slice(0, 12);
+ const encryptedData = combined.slice(12);
+
+ // Decrypt
+ const decryptedBuffer = await crypto.subtle.decrypt(
+ { name: 'AES-GCM', iv },
+ key,
+ encryptedData
+ );
+
+ // Convert to string and parse JSON
+ const decoder = new TextDecoder();
+ const decryptedString = decoder.decode(decryptedBuffer);
+
+ return JSON.parse(decryptedString) as CitizenshipData;
+ } catch (error) {
+ console.error('Decryption error:', error);
+ throw new Error('Failed to decrypt data');
+ }
+}
+
+// ========================================
+// SIGNATURE GENERATION & VERIFICATION
+// ========================================
+
+export interface AuthChallenge {
+ nonce: string; // Random UUID
+ timestamp: number; // Current timestamp
+ tikiNumber: string; // NFT number to prove
+ expiresAt: number; // Expiry timestamp (5 min)
+}
+
+/**
+ * Generate authentication challenge
+ */
+export function generateAuthChallenge(tikiNumber: string): AuthChallenge {
+ const now = Date.now();
+ const nonce = crypto.randomUUID();
+
+ return {
+ nonce,
+ timestamp: now,
+ tikiNumber,
+ expiresAt: now + (5 * 60 * 1000) // 5 minutes
+ };
+}
+
+/**
+ * Format challenge message for signing
+ */
+export function formatChallengeMessage(challenge: AuthChallenge): string {
+ return `Prove ownership of Welati Tiki #${challenge.tikiNumber}
+
+Nonce: ${challenge.nonce}
+Timestamp: ${challenge.timestamp}
+Expires: ${new Date(challenge.expiresAt).toISOString()}
+
+By signing this message, you prove you control the wallet that owns this Tiki NFT.`;
+}
+
+/**
+ * Sign authentication challenge with wallet
+ */
+export async function signChallenge(
+ account: InjectedAccountWithMeta,
+ challenge: AuthChallenge
+): Promise {
+ try {
+ await cryptoWaitReady();
+
+ const injector = await web3FromAddress(account.address);
+ const signRaw = injector?.signer?.signRaw;
+
+ if (!signRaw) {
+ throw new Error('Signer not available');
+ }
+
+ const message = formatChallengeMessage(challenge);
+
+ const { signature } = await signRaw({
+ address: account.address,
+ data: stringToHex(message),
+ type: 'bytes'
+ });
+
+ return signature;
+ } catch (error) {
+ console.error('Signature error:', error);
+ throw new Error('Failed to sign challenge');
+ }
+}
+
+/**
+ * Verify signature
+ */
+export async function verifySignature(
+ signature: string,
+ challenge: AuthChallenge,
+ expectedAddress: string
+): Promise {
+ try {
+ await cryptoWaitReady();
+
+ // Check if challenge has expired
+ if (Date.now() > challenge.expiresAt) {
+ console.warn('Challenge has expired');
+ return false;
+ }
+
+ const message = formatChallengeMessage(challenge);
+ const messageU8a = stringToU8a(message);
+ const signatureU8a = hexToU8a(signature);
+ const publicKey = decodeAddress(expectedAddress);
+
+ const result = signatureVerify(messageU8a, signatureU8a, publicKey);
+
+ return result.isValid;
+ } catch (error) {
+ console.error('Verification error:', error);
+ return false;
+ }
+}
+
+// ========================================
+// LOCAL STORAGE UTILITIES
+// ========================================
+
+const STORAGE_KEY_PREFIX = 'pezkuwi_citizen_';
+
+export interface CitizenSession {
+ tikiNumber: string;
+ walletAddress: string;
+ sessionToken: string; // JWT-like token
+ encryptedDataCID?: string; // IPFS CID
+ lastAuthenticated: number; // Timestamp
+ expiresAt: number; // Session expiry (24h)
+}
+
+/**
+ * Save encrypted citizen session to localStorage
+ */
+export async function saveCitizenSession(session: CitizenSession): Promise {
+ try {
+ const sessionJson = JSON.stringify(session);
+ const sessionKey = `${STORAGE_KEY_PREFIX}session`;
+
+ // For MVP, store plainly. For production, encrypt with device key
+ localStorage.setItem(sessionKey, sessionJson);
+ } catch (error) {
+ console.error('Error saving session:', error);
+ throw new Error('Failed to save session');
+ }
+}
+
+/**
+ * Load citizen session from localStorage
+ */
+export function loadCitizenSession(): CitizenSession | null {
+ try {
+ const sessionKey = `${STORAGE_KEY_PREFIX}session`;
+ const sessionJson = localStorage.getItem(sessionKey);
+
+ if (!sessionJson) {
+ return null;
+ }
+
+ const session = JSON.parse(sessionJson) as CitizenSession;
+
+ // Check if session has expired
+ if (Date.now() > session.expiresAt) {
+ clearCitizenSession();
+ return null;
+ }
+
+ return session;
+ } catch (error) {
+ console.error('Error loading session:', error);
+ return null;
+ }
+}
+
+/**
+ * Clear citizen session from localStorage
+ */
+export function clearCitizenSession(): void {
+ try {
+ const sessionKey = `${STORAGE_KEY_PREFIX}session`;
+ localStorage.removeItem(sessionKey);
+ } catch (error) {
+ console.error('Error clearing session:', error);
+ }
+}
+
+/**
+ * Save encrypted citizenship data to localStorage (backup)
+ */
+export async function saveLocalCitizenshipData(
+ data: CitizenshipData,
+ walletAddress: string
+): Promise {
+ try {
+ const encrypted = await encryptData(data, walletAddress);
+ const dataKey = `${STORAGE_KEY_PREFIX}data_${walletAddress}`;
+
+ localStorage.setItem(dataKey, encrypted);
+ } catch (error) {
+ console.error('Error saving citizenship data:', error);
+ throw new Error('Failed to save citizenship data');
+ }
+}
+
+/**
+ * Load encrypted citizenship data from localStorage
+ */
+export async function loadLocalCitizenshipData(
+ walletAddress: string
+): Promise {
+ try {
+ const dataKey = `${STORAGE_KEY_PREFIX}data_${walletAddress}`;
+ const encrypted = localStorage.getItem(dataKey);
+
+ if (!encrypted) {
+ return null;
+ }
+
+ return await decryptData(encrypted, walletAddress);
+ } catch (error) {
+ console.error('Error loading citizenship data:', error);
+ return null;
+ }
+}
+
+// ========================================
+// IPFS UTILITIES (Placeholder)
+// ========================================
+
+/**
+ * Upload encrypted data to IPFS
+ * NOTE: This is a placeholder. Implement with actual IPFS client (Pinata, Web3.Storage, etc.)
+ */
+export async function uploadToIPFS(encryptedData: string): Promise {
+ // TODO: Implement actual IPFS upload
+ // For MVP, we can use Pinata API or Web3.Storage
+
+ console.warn('IPFS upload not yet implemented. Using mock CID.');
+
+ // Mock CID for development
+ const mockCid = `Qm${Math.random().toString(36).substring(2, 15)}`;
+
+ return mockCid;
+}
+
+/**
+ * Fetch encrypted data from IPFS
+ * NOTE: This is a placeholder. Implement with actual IPFS client
+ */
+export async function fetchFromIPFS(cid: string): Promise {
+ // TODO: Implement actual IPFS fetch
+ // For MVP, use public IPFS gateways or dedicated service
+
+ console.warn('IPFS fetch not yet implemented. Returning mock data.');
+
+ // Mock encrypted data
+ return '0x000000000000000000000000';
+}
diff --git a/src/lib/citizenship-workflow.ts b/src/lib/citizenship-workflow.ts
new file mode 100644
index 00000000..02fb259c
--- /dev/null
+++ b/src/lib/citizenship-workflow.ts
@@ -0,0 +1,624 @@
+// ========================================
+// Citizenship Workflow Library
+// ========================================
+// Handles citizenship verification, status checks, and workflow logic
+
+import type { ApiPromise } from '@polkadot/api';
+import { web3FromAddress } from '@polkadot/extension-dapp';
+import type { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';
+
+// ========================================
+// TYPE DEFINITIONS
+// ========================================
+
+export type KycStatus = 'NotStarted' | 'Pending' | 'Approved' | 'Rejected';
+
+export type Region =
+ | 'bakur' // North (Turkey)
+ | 'basur' // South (Iraq)
+ | 'rojava' // West (Syria)
+ | 'rojhelat' // East (Iran)
+ | 'diaspora' // Diaspora
+ | 'kurdistan_a_sor'; // Red Kurdistan (Armenia/Azerbaijan)
+
+export type MaritalStatus = 'zewici' | 'nezewici'; // Married / Unmarried
+
+export interface ChildInfo {
+ name: string;
+ birthYear: number;
+}
+
+export interface CitizenshipData {
+ // Personal Identity
+ fullName: string;
+ fatherName: string;
+ grandfatherName: string;
+ motherName: string;
+
+ // Tribal Affiliation
+ tribe: string;
+
+ // Family Status
+ maritalStatus: MaritalStatus;
+ childrenCount?: number;
+ children?: ChildInfo[];
+
+ // Geographic Origin
+ region: Region;
+
+ // Contact & Profession
+ email: string;
+ profession: string;
+
+ // Referral
+ referralCode?: string;
+
+ // Metadata
+ walletAddress: string;
+ timestamp: number;
+}
+
+export interface CitizenshipCommitment {
+ commitmentHash: string; // SHA256 hash of all data
+ nullifierHash: string; // Prevents double-registration
+ ipfsCid: string; // IPFS CID of encrypted data
+ publicKey: string; // User's encryption public key
+ timestamp: number;
+}
+
+export interface TikiInfo {
+ id: string;
+ role: string;
+ metadata?: any;
+}
+
+export interface CitizenshipStatus {
+ kycStatus: KycStatus;
+ hasCitizenTiki: boolean;
+ tikiNumber?: string;
+ stakingScoreTracking: boolean;
+ ipfsCid?: string;
+ nextAction: 'APPLY_KYC' | 'CLAIM_TIKI' | 'START_TRACKING' | 'COMPLETE';
+}
+
+// ========================================
+// KYC STATUS CHECKS
+// ========================================
+
+/**
+ * Get KYC status for a wallet address
+ */
+export async function getKycStatus(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.identityKyc) {
+ console.warn('Identity KYC pallet not available');
+ return 'NotStarted';
+ }
+
+ const status = await api.query.identityKyc.kycStatuses(address);
+
+ if (status.isEmpty) {
+ return 'NotStarted';
+ }
+
+ const statusStr = status.toString();
+
+ // Map on-chain status to our type
+ if (statusStr === 'Approved') return 'Approved';
+ if (statusStr === 'Pending') return 'Pending';
+ if (statusStr === 'Rejected') return 'Rejected';
+
+ return 'NotStarted';
+ } catch (error) {
+ console.error('Error fetching KYC status:', error);
+ return 'NotStarted';
+ }
+}
+
+/**
+ * Check if user has pending KYC application
+ */
+export async function hasPendingApplication(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.identityKyc?.pendingKycApplications) {
+ return false;
+ }
+
+ const application = await api.query.identityKyc.pendingKycApplications(address);
+ return !application.isEmpty;
+ } catch (error) {
+ console.error('Error checking pending application:', error);
+ return false;
+ }
+}
+
+// ========================================
+// TIKI / CITIZENSHIP CHECKS
+// ========================================
+
+/**
+ * Get all Tiki roles for a user
+ */
+// Tiki enum mapping from pallet-tiki
+const TIKI_ROLES = [
+ 'Hemwelatî', 'Parlementer', 'SerokiMeclise', 'Serok', 'Wezir', 'EndameDiwane', 'Dadger',
+ 'Dozger', 'Hiquqnas', 'Noter', 'Xezinedar', 'Bacgir', 'GerinendeyeCavkaniye', 'OperatorêTorê',
+ 'PisporêEwlehiyaSîber', 'GerinendeyeDaneye', 'Berdevk', 'Qeydkar', 'Balyoz', 'Navbeynkar',
+ 'ParêzvaneÇandî', 'Mufetîs', 'KalîteKontrolker', 'Mela', 'Feqî', 'Perwerdekar', 'Rewsenbîr',
+ 'RêveberêProjeyê', 'SerokêKomele', 'ModeratorêCivakê', 'Axa', 'Pêseng', 'Sêwirmend', 'Hekem', 'Mamoste',
+ 'Bazargan',
+ 'SerokWeziran', 'WezireDarayiye', 'WezireParez', 'WezireDad', 'WezireBelaw', 'WezireTend', 'WezireAva', 'WezireCand'
+];
+
+export async function getUserTikis(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.tiki?.userTikis) {
+ console.warn('Tiki pallet not available');
+ return [];
+ }
+
+ const tikis = await api.query.tiki.userTikis(address);
+
+ if (tikis.isEmpty) {
+ return [];
+ }
+
+ // userTikis returns a BoundedVec of Tiki enum values (as indices)
+ const tikiIndices = tikis.toJSON() as number[];
+
+ return tikiIndices.map((index, i) => ({
+ id: `${index}`,
+ role: TIKI_ROLES[index] || `Unknown Role (${index})`,
+ metadata: {}
+ }));
+ } catch (error) {
+ console.error('Error fetching user tikis:', error);
+ return [];
+ }
+}
+
+/**
+ * Check if user has Welati (Citizen) Tiki
+ * Backend checks for "Hemwelatî" (actual blockchain role name)
+ */
+export async function hasCitizenTiki(
+ api: ApiPromise,
+ address: string
+): Promise<{ hasTiki: boolean; tikiNumber?: string }> {
+ try {
+ const tikis = await getUserTikis(api, address);
+
+ const citizenTiki = tikis.find(t =>
+ t.role.toLowerCase() === 'hemwelatî' ||
+ t.role.toLowerCase() === 'welati' ||
+ t.role.toLowerCase() === 'citizen'
+ );
+
+ return {
+ hasTiki: !!citizenTiki,
+ tikiNumber: citizenTiki?.id
+ };
+ } catch (error) {
+ console.error('Error checking citizen tiki:', error);
+ return { hasTiki: false };
+ }
+}
+
+/**
+ * Verify NFT ownership by NFT number
+ */
+export async function verifyNftOwnership(
+ api: ApiPromise,
+ nftNumber: string,
+ walletAddress: string
+): Promise {
+ try {
+ const tikis = await getUserTikis(api, walletAddress);
+
+ return tikis.some(tiki =>
+ tiki.id === nftNumber &&
+ (
+ tiki.role.toLowerCase() === 'hemwelatî' ||
+ tiki.role.toLowerCase() === 'welati' ||
+ tiki.role.toLowerCase() === 'citizen'
+ )
+ );
+ } catch (error) {
+ console.error('Error verifying NFT ownership:', error);
+ return false;
+ }
+}
+
+// ========================================
+// STAKING SCORE TRACKING
+// ========================================
+
+/**
+ * Check if staking score tracking has been started
+ */
+export async function isStakingScoreTracking(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.stakingScore?.stakingStartBlock) {
+ console.warn('Staking score pallet not available');
+ return false;
+ }
+
+ const startBlock = await api.query.stakingScore.stakingStartBlock(address);
+ return !startBlock.isNone;
+ } catch (error) {
+ console.error('Error checking staking score tracking:', error);
+ return false;
+ }
+}
+
+/**
+ * Check if user is staking
+ */
+export async function isStaking(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.staking?.ledger) {
+ return false;
+ }
+
+ const ledger = await api.query.staking.ledger(address);
+ return !ledger.isNone;
+ } catch (error) {
+ console.error('Error checking staking status:', error);
+ return false;
+ }
+}
+
+// ========================================
+// COMPREHENSIVE CITIZENSHIP STATUS
+// ========================================
+
+/**
+ * Get complete citizenship status and next action needed
+ */
+export async function getCitizenshipStatus(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api || !address) {
+ return {
+ kycStatus: 'NotStarted',
+ hasCitizenTiki: false,
+ stakingScoreTracking: false,
+ nextAction: 'APPLY_KYC'
+ };
+ }
+
+ // Fetch all status in parallel
+ const [kycStatus, citizenCheck, stakingTracking] = await Promise.all([
+ getKycStatus(api, address),
+ hasCitizenTiki(api, address),
+ isStakingScoreTracking(api, address)
+ ]);
+
+ const kycApproved = kycStatus === 'Approved';
+ const hasTiki = citizenCheck.hasTiki;
+
+ // Determine next action
+ let nextAction: CitizenshipStatus['nextAction'];
+
+ if (!kycApproved) {
+ nextAction = 'APPLY_KYC';
+ } else if (!hasTiki) {
+ nextAction = 'CLAIM_TIKI';
+ } else if (!stakingTracking) {
+ nextAction = 'START_TRACKING';
+ } else {
+ nextAction = 'COMPLETE';
+ }
+
+ return {
+ kycStatus,
+ hasCitizenTiki: hasTiki,
+ tikiNumber: citizenCheck.tikiNumber,
+ stakingScoreTracking: stakingTracking,
+ nextAction
+ };
+ } catch (error) {
+ console.error('Error fetching citizenship status:', error);
+ return {
+ kycStatus: 'NotStarted',
+ hasCitizenTiki: false,
+ stakingScoreTracking: false,
+ nextAction: 'APPLY_KYC'
+ };
+ }
+}
+
+// ========================================
+// IPFS COMMITMENT RETRIEVAL
+// ========================================
+
+/**
+ * Get IPFS CID for citizen data
+ */
+export async function getCitizenDataCid(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.identityKyc?.identities) {
+ return null;
+ }
+
+ // Try to get from identity storage
+ // This assumes the pallet stores IPFS CID somewhere
+ // Adjust based on actual pallet storage structure
+ const identity = await api.query.identityKyc.identities(address);
+
+ if (identity.isNone) {
+ return null;
+ }
+
+ const identityData = identity.unwrap().toJSON() as any;
+
+ // Try different possible field names
+ return identityData.ipfsCid ||
+ identityData.cid ||
+ identityData.dataCid ||
+ null;
+ } catch (error) {
+ console.error('Error fetching citizen data CID:', error);
+ return null;
+ }
+}
+
+// ========================================
+// REFERRAL VALIDATION
+// ========================================
+
+/**
+ * Validate referral code
+ */
+export async function validateReferralCode(
+ api: ApiPromise,
+ referralCode: string
+): Promise {
+ try {
+ if (!referralCode || referralCode.trim() === '') {
+ return true; // Empty is valid (will use founder)
+ }
+
+ // Check if referral code exists in trust pallet
+ if (!api?.query?.trust?.referrals) {
+ return false;
+ }
+
+ // Referral code could be an address or custom code
+ // For now, check if it's a valid address format
+ // TODO: Implement proper referral code lookup
+
+ return referralCode.length > 0;
+ } catch (error) {
+ console.error('Error validating referral code:', error);
+ return false;
+ }
+}
+
+// ========================================
+// BLOCKCHAIN TRANSACTIONS
+// ========================================
+
+/**
+ * Submit KYC application to blockchain
+ * This is a two-step process:
+ * 1. Set identity (name, email)
+ * 2. Apply for KYC (IPFS CID, notes)
+ */
+export async function submitKycApplication(
+ api: ApiPromise,
+ account: InjectedAccountWithMeta,
+ name: string,
+ email: string,
+ ipfsCid: string,
+ notes: string = 'Citizenship application'
+): Promise<{ success: boolean; error?: string; blockHash?: string }> {
+ try {
+ if (!api?.tx?.identityKyc?.setIdentity || !api?.tx?.identityKyc?.applyForKyc) {
+ return { success: false, error: 'Identity KYC pallet not available' };
+ }
+
+ // Check if user already has a pending KYC application
+ const pendingApp = await api.query.identityKyc.pendingKycApplications(account.address);
+ if (!pendingApp.isEmpty) {
+ console.log('⚠️ User already has a pending KYC application');
+ return {
+ success: false,
+ error: 'You already have a pending citizenship application. Please wait for approval.'
+ };
+ }
+
+ // Check if user is already approved
+ const kycStatus = await api.query.identityKyc.kycStatuses(account.address);
+ if (kycStatus.toString() === 'Approved') {
+ console.log('✅ User KYC is already approved');
+ return {
+ success: false,
+ error: 'Your citizenship application is already approved!'
+ };
+ }
+
+ // Get the injector for signing
+ const injector = await web3FromAddress(account.address);
+
+ // Debug logging
+ console.log('=== submitKycApplication Debug ===');
+ console.log('account.address:', account.address);
+ console.log('name:', name);
+ console.log('email:', email);
+ console.log('ipfsCid:', ipfsCid);
+ console.log('notes:', notes);
+ console.log('===================================');
+
+ // Ensure ipfsCid is a string
+ const cidString = String(ipfsCid);
+ if (!cidString || cidString === 'undefined' || cidString === '[object Object]') {
+ return { success: false, error: `Invalid IPFS CID received: ${cidString}` };
+ }
+
+ // Step 1: Set identity first
+ console.log('Step 1: Setting identity...');
+ const identityResult = await new Promise<{ success: boolean; error?: string }>((resolve, reject) => {
+ api.tx.identityKyc
+ .setIdentity(name, email)
+ .signAndSend(account.address, { signer: injector.signer }, ({ status, dispatchError, events }) => {
+ console.log('Identity transaction status:', status.type);
+
+ if (status.isInBlock || status.isFinalized) {
+ if (dispatchError) {
+ let errorMessage = 'Identity transaction failed';
+ if (dispatchError.isModule) {
+ const decoded = api.registry.findMetaError(dispatchError.asModule);
+ errorMessage = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
+ } else {
+ errorMessage = dispatchError.toString();
+ }
+ console.error('Identity transaction error:', errorMessage);
+ resolve({ success: false, error: errorMessage });
+ return;
+ }
+
+ // Check for IdentitySet event
+ const identitySetEvent = events.find(({ event }) =>
+ event.section === 'identityKyc' && event.method === 'IdentitySet'
+ );
+
+ if (identitySetEvent) {
+ console.log('✅ Identity set successfully');
+ resolve({ success: true });
+ } else {
+ resolve({ success: true }); // Still consider it success if in block
+ }
+ }
+ })
+ .catch((error) => {
+ console.error('Failed to sign and send identity transaction:', error);
+ reject(error);
+ });
+ });
+
+ if (!identityResult.success) {
+ return identityResult;
+ }
+
+ // Step 2: Apply for KYC
+ console.log('Step 2: Applying for KYC...');
+ const result = await new Promise<{ success: boolean; error?: string; blockHash?: string }>((resolve, reject) => {
+ api.tx.identityKyc
+ .applyForKyc([cidString], notes)
+ .signAndSend(account.address, { signer: injector.signer }, ({ status, dispatchError, events }) => {
+ console.log('Transaction status:', status.type);
+
+ if (status.isInBlock || status.isFinalized) {
+ if (dispatchError) {
+ let errorMessage = 'Transaction failed';
+
+ if (dispatchError.isModule) {
+ const decoded = api.registry.findMetaError(dispatchError.asModule);
+ errorMessage = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
+ } else {
+ errorMessage = dispatchError.toString();
+ }
+
+ console.error('Transaction error:', errorMessage);
+ resolve({ success: false, error: errorMessage });
+ return;
+ }
+
+ // Check for KycApplied event
+ const kycAppliedEvent = events.find(({ event }) =>
+ event.section === 'identityKyc' && event.method === 'KycApplied'
+ );
+
+ if (kycAppliedEvent) {
+ console.log('✅ KYC Application submitted successfully');
+ resolve({
+ success: true,
+ blockHash: status.asInBlock.toString()
+ });
+ } else {
+ console.warn('Transaction included but KycApplied event not found');
+ resolve({ success: true });
+ }
+ }
+ })
+ .catch((error) => {
+ console.error('Failed to sign and send transaction:', error);
+ reject(error);
+ });
+ });
+
+ return result;
+ } catch (error: any) {
+ console.error('Error submitting KYC application:', error);
+ return {
+ success: false,
+ error: error.message || 'Failed to submit KYC application'
+ };
+ }
+}
+
+/**
+ * Subscribe to KYC approval events for an address
+ */
+export function subscribeToKycApproval(
+ api: ApiPromise,
+ address: string,
+ onApproved: () => void,
+ onError?: (error: string) => void
+): () => void {
+ try {
+ if (!api?.query?.system?.events) {
+ console.error('Cannot subscribe to events: system.events not available');
+ if (onError) onError('Event subscription not available');
+ return () => {};
+ }
+
+ const unsubscribe = api.query.system.events((events) => {
+ events.forEach((record) => {
+ const { event } = record;
+
+ if (event.section === 'identityKyc' && event.method === 'KycApproved') {
+ const [approvedAddress] = event.data;
+
+ if (approvedAddress.toString() === address) {
+ console.log('✅ KYC Approved for:', address);
+ onApproved();
+ }
+ }
+ });
+ });
+
+ return unsubscribe as unknown as () => void;
+ } catch (error: any) {
+ console.error('Error subscribing to KYC approval:', error);
+ if (onError) onError(error.message || 'Failed to subscribe to approval events');
+ return () => {};
+ }
+}
+
+// ========================================
+// FOUNDER ADDRESS
+// ========================================
+
+export const FOUNDER_ADDRESS = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; // Satoshi Qazi Muhammed
diff --git a/src/lib/scores.ts b/src/lib/scores.ts
new file mode 100644
index 00000000..99cbf29e
--- /dev/null
+++ b/src/lib/scores.ts
@@ -0,0 +1,355 @@
+// ========================================
+// Score Systems Integration
+// ========================================
+// Centralized score fetching from blockchain pallets
+
+import type { ApiPromise } from '@polkadot/api';
+
+// ========================================
+// TYPE DEFINITIONS
+// ========================================
+
+export interface UserScores {
+ trustScore: number;
+ referralScore: number;
+ stakingScore: number;
+ tikiScore: number;
+ totalScore: number;
+}
+
+export interface TrustScoreDetails {
+ totalScore: number;
+ stakingPoints: number;
+ referralPoints: number;
+ tikiPoints: number;
+ activityPoints: number;
+ historyLength: number;
+}
+
+// ========================================
+// TRUST SCORE (pallet_trust)
+// ========================================
+
+/**
+ * Fetch user's trust score from blockchain
+ * pallet_trust::TrustScores storage
+ */
+export async function getTrustScore(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.trust) {
+ console.warn('Trust pallet not available');
+ return 0;
+ }
+
+ const score = await api.query.trust.trustScores(address);
+
+ if (score.isEmpty) {
+ return 0;
+ }
+
+ return Number(score.toString());
+ } catch (error) {
+ console.error('Error fetching trust score:', error);
+ return 0;
+ }
+}
+
+/**
+ * Fetch detailed trust score breakdown
+ * pallet_trust::ScoreHistory storage
+ */
+export async function getTrustScoreDetails(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.trust) {
+ return null;
+ }
+
+ const totalScore = await getTrustScore(api, address);
+
+ // Get score history to show detailed breakdown
+ const historyResult = await api.query.trust.scoreHistory(address);
+
+ if (historyResult.isEmpty) {
+ return {
+ totalScore,
+ stakingPoints: 0,
+ referralPoints: 0,
+ tikiPoints: 0,
+ activityPoints: 0,
+ historyLength: 0
+ };
+ }
+
+ const history = historyResult.toJSON() as any[];
+
+ // Calculate points from history
+ // History format: [{blockNumber, score, reason}]
+ let stakingPoints = 0;
+ let referralPoints = 0;
+ let tikiPoints = 0;
+ let activityPoints = 0;
+
+ for (const entry of history) {
+ const reason = entry.reason || '';
+ const score = entry.score || 0;
+
+ if (reason.includes('Staking')) stakingPoints += score;
+ else if (reason.includes('Referral')) referralPoints += score;
+ else if (reason.includes('Tiki') || reason.includes('Role')) tikiPoints += score;
+ else activityPoints += score;
+ }
+
+ return {
+ totalScore,
+ stakingPoints,
+ referralPoints,
+ tikiPoints,
+ activityPoints,
+ historyLength: history.length
+ };
+ } catch (error) {
+ console.error('Error fetching trust score details:', error);
+ return null;
+ }
+}
+
+// ========================================
+// REFERRAL SCORE (pallet_trust)
+// ========================================
+
+/**
+ * Fetch user's referral score
+ * pallet_trust::ReferralScores storage
+ */
+export async function getReferralScore(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.trust?.referralScores) {
+ console.warn('Referral scores not available in trust pallet');
+ return 0;
+ }
+
+ const score = await api.query.trust.referralScores(address);
+
+ if (score.isEmpty) {
+ return 0;
+ }
+
+ return Number(score.toString());
+ } catch (error) {
+ console.error('Error fetching referral score:', error);
+ return 0;
+ }
+}
+
+/**
+ * Get referral count for user
+ * pallet_trust::Referrals storage
+ */
+export async function getReferralCount(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.trust?.referrals) {
+ return 0;
+ }
+
+ const referrals = await api.query.trust.referrals(address);
+
+ if (referrals.isEmpty) {
+ return 0;
+ }
+
+ const referralList = referrals.toJSON() as any[];
+ return Array.isArray(referralList) ? referralList.length : 0;
+ } catch (error) {
+ console.error('Error fetching referral count:', error);
+ return 0;
+ }
+}
+
+// ========================================
+// STAKING SCORE (pallet_staking_score)
+// ========================================
+
+/**
+ * Get staking score from pallet_staking_score
+ * This is already implemented in lib/staking.ts
+ * Re-exported here for consistency
+ */
+export async function getStakingScoreFromPallet(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api?.query?.stakingScore) {
+ console.warn('Staking score pallet not available');
+ return 0;
+ }
+
+ // Check if user has started score tracking
+ const scoreResult = await api.query.stakingScore.stakingStartBlock(address);
+
+ if (scoreResult.isNone) {
+ return 0;
+ }
+
+ // Get staking info from staking pallet
+ const ledger = await api.query.staking.ledger(address);
+
+ if (ledger.isNone) {
+ return 0;
+ }
+
+ const ledgerData = ledger.unwrap().toJSON() as any;
+ const stakedAmount = Number(ledgerData.total || 0) / 1e12; // Convert to HEZ
+
+ // Get duration
+ const startBlock = Number(scoreResult.unwrap().toString());
+ const currentBlock = Number((await api.query.system.number()).toString());
+ const durationInBlocks = currentBlock - startBlock;
+
+ // Calculate score based on amount and duration
+ // Amount-based score (20-50 points)
+ let amountScore = 20;
+ if (stakedAmount <= 100) amountScore = 20;
+ else if (stakedAmount <= 250) amountScore = 30;
+ else if (stakedAmount <= 750) amountScore = 40;
+ else amountScore = 50;
+
+ // Duration multiplier
+ const MONTH_IN_BLOCKS = 30 * 24 * 60 * 10; // ~30 days
+ let durationMultiplier = 1.0;
+
+ if (durationInBlocks >= 12 * MONTH_IN_BLOCKS) durationMultiplier = 2.0;
+ else if (durationInBlocks >= 6 * MONTH_IN_BLOCKS) durationMultiplier = 1.7;
+ else if (durationInBlocks >= 3 * MONTH_IN_BLOCKS) durationMultiplier = 1.4;
+ else if (durationInBlocks >= MONTH_IN_BLOCKS) durationMultiplier = 1.2;
+
+ return Math.min(100, Math.floor(amountScore * durationMultiplier));
+ } catch (error) {
+ console.error('Error fetching staking score:', error);
+ return 0;
+ }
+}
+
+// ========================================
+// TIKI SCORE (from lib/tiki.ts)
+// ========================================
+
+/**
+ * Calculate Tiki score from user's roles
+ * Import from lib/tiki.ts
+ */
+import { fetchUserTikis, calculateTikiScore } from './tiki';
+
+export async function getTikiScore(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ const tikis = await fetchUserTikis(api, address);
+ return calculateTikiScore(tikis);
+ } catch (error) {
+ console.error('Error fetching tiki score:', error);
+ return 0;
+ }
+}
+
+// ========================================
+// COMPREHENSIVE SCORE FETCHING
+// ========================================
+
+/**
+ * Fetch all scores for a user in one call
+ */
+export async function getAllScores(
+ api: ApiPromise,
+ address: string
+): Promise {
+ try {
+ if (!api || !address) {
+ return {
+ trustScore: 0,
+ referralScore: 0,
+ stakingScore: 0,
+ tikiScore: 0,
+ totalScore: 0
+ };
+ }
+
+ // Fetch all scores in parallel
+ const [trustScore, referralScore, stakingScore, tikiScore] = await Promise.all([
+ getTrustScore(api, address),
+ getReferralScore(api, address),
+ getStakingScoreFromPallet(api, address),
+ getTikiScore(api, address)
+ ]);
+
+ const totalScore = trustScore + referralScore + stakingScore + tikiScore;
+
+ return {
+ trustScore,
+ referralScore,
+ stakingScore,
+ tikiScore,
+ totalScore
+ };
+ } catch (error) {
+ console.error('Error fetching all scores:', error);
+ return {
+ trustScore: 0,
+ referralScore: 0,
+ stakingScore: 0,
+ tikiScore: 0,
+ totalScore: 0
+ };
+ }
+}
+
+// ========================================
+// SCORE DISPLAY HELPERS
+// ========================================
+
+/**
+ * Get color class based on score
+ */
+export function getScoreColor(score: number): string {
+ if (score >= 200) return 'text-purple-500';
+ if (score >= 150) return 'text-pink-500';
+ if (score >= 100) return 'text-blue-500';
+ if (score >= 70) return 'text-cyan-500';
+ if (score >= 40) return 'text-teal-500';
+ if (score >= 20) return 'text-green-500';
+ return 'text-gray-500';
+}
+
+/**
+ * Get score rating label
+ */
+export function getScoreRating(score: number): string {
+ if (score >= 250) return 'Legendary';
+ if (score >= 200) return 'Excellent';
+ if (score >= 150) return 'Very Good';
+ if (score >= 100) return 'Good';
+ if (score >= 70) return 'Average';
+ if (score >= 40) return 'Fair';
+ if (score >= 20) return 'Low';
+ return 'Very Low';
+}
+
+/**
+ * Format score for display
+ */
+export function formatScore(score: number): string {
+ return score.toFixed(0);
+}
diff --git a/src/pages/BeCitizen.tsx b/src/pages/BeCitizen.tsx
new file mode 100644
index 00000000..32e34a32
--- /dev/null
+++ b/src/pages/BeCitizen.tsx
@@ -0,0 +1,206 @@
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { CitizenshipModal } from '@/components/citizenship/CitizenshipModal';
+import { Shield, Users, Award, Globe, ChevronRight, ArrowLeft, Home } from 'lucide-react';
+
+const BeCitizen: React.FC = () => {
+ const navigate = useNavigate();
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ return (
+
+
+ {/* Back to Home Button */}
+
+
navigate('/')}
+ variant="outline"
+ className="bg-white/10 hover:bg-white/20 border-white/30 text-white"
+ >
+
+ Back to Home
+
+
+
+ {/* Hero Section */}
+
+
+ 🏛️ Digital Kurdistan
+
+
+ Bibe Welati / Be a Citizen
+
+
+ Join the Digital Kurdistan State as a sovereign citizen. Receive your Welati Tiki NFT and unlock governance, trust scoring, and community benefits.
+
+
+
+ {/* Benefits Grid */}
+
+
+
+
+ Privacy Protected
+
+ Your data is encrypted with ZK-proofs. Only hashes are stored on-chain.
+
+
+
+
+
+
+
+ Welati Tiki NFT
+
+ Receive your unique soulbound citizenship NFT after KYC approval.
+
+
+
+
+
+
+
+ Trust Scoring
+
+ Build trust through referrals, staking, and community contributions.
+
+
+
+
+
+
+
+ Governance Access
+
+ Participate in on-chain governance and shape the future of Digital Kurdistan.
+
+
+
+
+
+ {/* CTA Section */}
+
+
+
+
+
+
Ready to Join?
+
+ Whether you're already a citizen or want to become one, start your journey here.
+
+
+
+
setIsModalOpen(true)}
+ size="lg"
+ className="bg-gradient-to-r from-cyan-500 to-purple-600 hover:from-cyan-600 hover:to-purple-700 text-white font-semibold px-8 py-6 text-lg group"
+ >
+ Start Citizenship Process
+
+
+
+
+
+
+ Secure ZK-Proof Authentication
+
+
•
+
+
+
Soulbound NFT Citizenship
+
+
•
+
+
+ Decentralized Identity
+
+
+
+
+
+
+
+ {/* Process Overview */}
+
+
How It Works
+
+
+ {/* Existing Citizens */}
+
+
+
+ 1
+
+ Already a Citizen?
+
+
+ ✓ Enter your Welati Tiki NFT number
+ ✓ Verify NFT ownership on-chain
+ ✓ Sign authentication challenge
+ ✓ Access your citizen dashboard
+
+
+
+ {/* New Citizens */}
+
+
+
+ 2
+
+ New to Citizenship?
+
+
+ ✓ Fill detailed KYC application
+ ✓ Data encrypted with ZK-proofs
+ ✓ Submit for admin approval
+ ✓ Receive your Welati Tiki NFT
+
+
+
+ {/* After Citizenship */}
+
+
+
+ 3
+
+ Citizen Benefits
+
+
+ ✓ Trust score calculation enabled
+ ✓ Governance voting rights
+ ✓ Referral tree participation
+ ✓ Staking multiplier bonuses
+
+
+
+
+
+ {/* Security Notice */}
+
+
+
+
+
+
+
Privacy & Security
+
+ Your personal data is encrypted using AES-GCM with your wallet-derived keys.
+ Only commitment hashes are stored on the blockchain. Encrypted data is stored
+ on IPFS and locally on your device. No personal information is ever publicly visible.
+
+
+
+
+
+
+
+
+ {/* Citizenship Modal */}
+
setIsModalOpen(false)} />
+
+ );
+};
+
+export default BeCitizen;
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
index e47238e4..771f959c 100644
--- a/src/pages/Dashboard.tsx
+++ b/src/pages/Dashboard.tsx
@@ -7,9 +7,10 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useAuth } from '@/contexts/AuthContext';
import { usePolkadot } from '@/contexts/PolkadotContext';
import { supabase } from '@/lib/supabase';
-import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award } from 'lucide-react';
+import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle, ArrowLeft, Award, Users, TrendingUp } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { fetchUserTikis, calculateTikiScore, getPrimaryRole, getTikiDisplayName, getTikiColor, getTikiEmoji, getUserRoleCategories } from '@/lib/tiki';
+import { getAllScores, type UserScores } from '@/lib/scores';
export default function Dashboard() {
const { user } = useAuth();
@@ -19,13 +20,19 @@ export default function Dashboard() {
const [profile, setProfile] = useState(null);
const [loading, setLoading] = useState(true);
const [tikis, setTikis] = useState([]);
- const [tikiScore, setTikiScore] = useState(0);
- const [loadingTikis, setLoadingTikis] = useState(false);
+ const [scores, setScores] = useState({
+ trustScore: 0,
+ referralScore: 0,
+ stakingScore: 0,
+ tikiScore: 0,
+ totalScore: 0
+ });
+ const [loadingScores, setLoadingScores] = useState(false);
useEffect(() => {
fetchProfile();
if (selectedAccount && api && isApiReady) {
- fetchTikiData();
+ fetchScoresAndTikis();
}
}, [user, selectedAccount, api, isApiReady]);
@@ -85,18 +92,22 @@ export default function Dashboard() {
}
};
- const fetchTikiData = async () => {
+ const fetchScoresAndTikis = async () => {
if (!selectedAccount || !api) return;
- setLoadingTikis(true);
+ setLoadingScores(true);
try {
+ // Fetch all scores from blockchain (includes trust, referral, staking, tiki)
+ const allScores = await getAllScores(api, selectedAccount.address);
+ setScores(allScores);
+
+ // Also fetch tikis separately for role display (needed for role details)
const userTikis = await fetchUserTikis(api, selectedAccount.address);
setTikis(userTikis);
- setTikiScore(calculateTikiScore(userTikis));
} catch (error) {
- console.error('Error fetching tiki data:', error);
+ console.error('Error fetching scores and tikis:', error);
} finally {
- setLoadingTikis(false);
+ setLoadingScores(false);
}
};
@@ -167,7 +178,7 @@ export default function Dashboard() {
};
const getRoleDisplay = (): string => {
- if (loadingTikis) return 'Loading...';
+ if (loadingScores) return 'Loading...';
if (!selectedAccount) return 'Member';
if (tikis.length === 0) return 'Member';
@@ -248,12 +259,74 @@ export default function Dashboard() {
- Tiki Score
+ Total Score
- {loadingTikis ? '...' : tikiScore}
+ {loadingScores ? '...' : scores.totalScore}
+
+
+ Combined from all score types
+
+
+
+
+
+
+
+
+ Trust Score
+
+
+
+
+ {loadingScores ? '...' : scores.trustScore}
+
+
+ From pallet_trust
+
+
+
+
+
+
+ Referral Score
+
+
+
+
+ {loadingScores ? '...' : scores.referralScore}
+
+
+ From referral system
+
+
+
+
+
+
+ Staking Score
+
+
+
+
+ {loadingScores ? '...' : scores.stakingScore}
+
+
+ From pallet_staking_score
+
+
+
+
+
+
+ Tiki Score
+
+
+
+
+ {loadingScores ? '...' : scores.tikiScore}
{tikis.length} {tikis.length === 1 ? 'role' : 'roles'} assigned
@@ -347,13 +420,13 @@ export default function Dashboard() {
)}
- {selectedAccount && loadingTikis && (
+ {selectedAccount && loadingScores && (
Loading roles from blockchain...
)}
- {selectedAccount && !loadingTikis && tikis.length === 0 && (
+ {selectedAccount && !loadingScores && tikis.length === 0 && (
@@ -365,7 +438,7 @@ export default function Dashboard() {
)}
- {selectedAccount && !loadingTikis && tikis.length > 0 && (
+ {selectedAccount && !loadingScores && tikis.length > 0 && (
@@ -376,7 +449,7 @@ export default function Dashboard() {
Total Score:
- {tikiScore}
+ {scores.totalScore}
Categories:
diff --git a/src/pages/WalletDashboard.tsx b/src/pages/WalletDashboard.tsx
index 8b62567a..b8c79e3b 100644
--- a/src/pages/WalletDashboard.tsx
+++ b/src/pages/WalletDashboard.tsx
@@ -5,6 +5,7 @@ import { AccountBalance } from '@/components/AccountBalance';
import { TransferModal } from '@/components/TransferModal';
import { ReceiveModal } from '@/components/ReceiveModal';
import { TransactionHistory } from '@/components/TransactionHistory';
+import { NftList } from '@/components/NftList';
import { Button } from '@/components/ui/button';
import { ArrowUpRight, ArrowDownRight, History, ArrowLeft, Activity } from 'lucide-react';
@@ -104,6 +105,9 @@ const WalletDashboard: React.FC = () => {
+
+ {/* NFT Collection */}
+