feat: update Be Citizen to new applyForCitizenship API

- Single tx (applyForCitizenship) instead of 2-step setIdentity+applyForKyc
- Keccak-256 identity hash via js-sha3
- Referral code replaced with referrer SS58 address
- Success screen shows pending referral status instead of citizen ID
- Updated all 6 translation files with new keys
This commit is contained in:
2026-02-14 22:00:32 +03:00
parent 59d4f3e6a1
commit f864ed6804
15 changed files with 154 additions and 203 deletions
+7 -7
View File
@@ -38,7 +38,7 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) {
const [region, setRegion] = useState<Region | ''>('');
const [email, setEmail] = useState('');
const [profession, setProfession] = useState('');
const [referralCode, setReferralCode] = useState('');
const [referrerAddress, setReferrerAddress] = useState('');
const [consent, setConsent] = useState(false);
const [error, setError] = useState('');
@@ -120,7 +120,7 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) {
region: region as Region,
email,
profession,
referralCode: referralCode || undefined,
referrerAddress: referrerAddress || undefined,
walletAddress,
timestamp: Date.now(),
};
@@ -319,15 +319,15 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) {
/>
</div>
{/* Referral Code */}
{/* Referrer Address */}
<div>
<label className={labelClass}>{t('citizen.referralCode')}</label>
<label className={labelClass}>{t('citizen.referrerAddress')}</label>
<input
type="text"
value={referralCode}
onChange={(e) => setReferralCode(e.target.value)}
value={referrerAddress}
onChange={(e) => setReferrerAddress(e.target.value)}
className={inputClass}
placeholder={t('citizen.referralCodePlaceholder')}
placeholder={t('citizen.referrerPlaceholder')}
/>
</div>
+20 -19
View File
@@ -11,16 +11,15 @@ import { useTelegram } from '@/hooks/useTelegram';
import { useWallet } from '@/contexts/WalletContext';
import type { CitizenshipData } from '@/lib/citizenship';
import {
generateCommitmentHash,
generateNullifierHash,
calculateIdentityHash,
saveCitizenshipLocally,
uploadToIPFS,
submitCitizenshipApplication,
applyCitizenship,
} from '@/lib/citizenship';
interface Props {
citizenshipData: CitizenshipData;
onSuccess: (blockHash?: string) => void;
onSuccess: (identityHash: string, blockHash?: string) => void;
onError: (error: string) => void;
}
@@ -32,23 +31,24 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
const { peopleApi, keypair } = useWallet();
const [state, setState] = useState<ProcessingState>('preparing');
const [ipfsCid, setIpfsCid] = useState<string>('');
const [identityHash, setIdentityHash] = useState<string>('');
// Prepare data on mount
useEffect(() => {
const prepare = async () => {
try {
// Generate commitment hash
generateCommitmentHash(citizenshipData);
generateNullifierHash(citizenshipData.walletAddress, citizenshipData.timestamp);
// Mock IPFS upload
const ipfsCid = await uploadToIPFS(citizenshipData);
// Calculate identity hash (keccak256)
const hash = calculateIdentityHash(citizenshipData.fullName, citizenshipData.email, [
ipfsCid,
]);
setIdentityHash(hash);
// Save encrypted data locally
saveCitizenshipLocally(citizenshipData);
// Mock IPFS upload
const cid = await uploadToIPFS(citizenshipData);
setIpfsCid(cid);
// Small delay to show animation
await new Promise((resolve) => setTimeout(resolve, 1500));
@@ -72,18 +72,16 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
hapticImpact('medium');
try {
const result = await submitCitizenshipApplication(
const result = await applyCitizenship(
peopleApi,
keypair,
citizenshipData.fullName,
citizenshipData.email,
ipfsCid,
`Citizenship application - ${citizenshipData.region}`
identityHash,
citizenshipData.referrerAddress || null
);
if (result.success) {
hapticNotification('success');
onSuccess(result.blockHash);
onSuccess(identityHash, result.blockHash);
} else {
hapticNotification('error');
onError(result.error || t('citizen.submissionFailed'));
@@ -96,7 +94,7 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
peopleApi,
keypair,
citizenshipData,
ipfsCid,
identityHash,
hapticImpact,
hapticNotification,
onSuccess,
@@ -124,6 +122,9 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
{state === 'preparing' && (
<p className="text-sm text-muted-foreground">{citizenshipData.fullName}</p>
)}
{state === 'ready' && (
<p className="text-xs text-muted-foreground">{t('citizen.depositRequired')}</p>
)}
</div>
{/* Sign Button */}
+17 -12
View File
@@ -1,27 +1,24 @@
/**
* Citizen Success Screen
* Shows after successful citizenship application submission
* Displays pending referral status instead of final approval
*/
import { CheckCircle } from 'lucide-react';
import { CheckCircle, Clock } from 'lucide-react';
import { useTranslation } from '@/i18n';
import { useTelegram } from '@/hooks/useTelegram';
import { formatAddress } from '@/lib/wallet-service';
import { generateCitizenNumber } from '@/lib/citizenship';
interface Props {
address: string;
identityHash: string;
onOpenApp: () => void;
}
export function CitizenSuccess({ address, onOpenApp }: Props) {
export function CitizenSuccess({ address, identityHash, onOpenApp }: Props) {
const { t } = useTranslation();
const { hapticImpact } = useTelegram();
// Generate a citizen number based on address
const citizenNumber = generateCitizenNumber(address, 42, 0);
const citizenId = `#42-0-${citizenNumber}`;
const handleOpenApp = () => {
hapticImpact('medium');
onOpenApp();
@@ -36,15 +33,18 @@ export function CitizenSuccess({ address, onOpenApp }: Props) {
{/* Title */}
<div className="text-center space-y-2">
<h1 className="text-2xl font-bold">{t('citizen.successTitle')}</h1>
<p className="text-muted-foreground">{t('citizen.successSubtitle')}</p>
<h1 className="text-2xl font-bold">{t('citizen.applicationSubmitted')}</h1>
<div className="flex items-center justify-center gap-2 text-yellow-500">
<Clock className="w-4 h-4" />
<p className="text-sm">{t('citizen.pendingReferral')}</p>
</div>
</div>
{/* Citizen ID Card */}
{/* Application Info Card */}
<div className="w-full max-w-sm bg-muted/50 rounded-2xl p-5 space-y-4 border border-border">
<div>
<p className="text-xs text-muted-foreground">{t('citizen.citizenId')}</p>
<p className="text-xl font-mono font-bold text-primary">{citizenId}</p>
<p className="text-xs text-muted-foreground">{t('citizen.identityHash')}</p>
<p className="text-sm font-mono break-all">{formatAddress(identityHash)}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">{t('citizen.walletAddress')}</p>
@@ -52,6 +52,11 @@ export function CitizenSuccess({ address, onOpenApp }: Props) {
</div>
</div>
{/* Info Note */}
<p className="text-xs text-muted-foreground text-center max-w-sm">
{t('citizen.applicationInfo')}
</p>
{/* Open App Button */}
<button
onClick={handleOpenApp}