mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-04-21 23:37:55 +00:00
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:
Generated
+9
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pezkuwi-telegram-miniapp",
|
||||
"version": "1.0.169",
|
||||
"version": "1.0.193",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pezkuwi-telegram-miniapp",
|
||||
"version": "1.0.169",
|
||||
"version": "1.0.193",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pezkuwi/api": "^16.5.36",
|
||||
@@ -20,6 +20,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"js-sha3": "^0.9.3",
|
||||
"lucide-react": "^0.462.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18.3.1",
|
||||
@@ -7060,6 +7061,12 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sha3": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz",
|
||||
"integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pezkuwi-telegram-miniapp",
|
||||
"version": "1.0.193",
|
||||
"version": "1.0.194",
|
||||
"type": "module",
|
||||
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
|
||||
"author": "Pezkuwichain Team",
|
||||
@@ -48,6 +48,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"js-sha3": "^0.9.3",
|
||||
"lucide-react": "^0.462.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -608,8 +608,8 @@ const ar: Translations = {
|
||||
emailPlaceholder: 'name@mail.com',
|
||||
profession: 'المهنة',
|
||||
professionPlaceholder: 'أدخل مهنتك',
|
||||
referralCode: 'رمز الإحالة (اختياري)',
|
||||
referralCodePlaceholder: 'أدخل رمز الإحالة',
|
||||
referrerAddress: 'عنوان المُحيل (اختياري)',
|
||||
referrerPlaceholder: 'أدخل عنوان SS58 - اتركه فارغاً للتعيين التلقائي',
|
||||
consentCheckbox: 'بياناتي محمية بـ ZK-proof، فقط الهاش يُخزن على البلوكشين',
|
||||
submit: 'إرسال',
|
||||
sign: 'وقّع',
|
||||
@@ -617,9 +617,11 @@ const ar: Translations = {
|
||||
preparingData: 'جاري تحضير البيانات...',
|
||||
readyToSign: 'جاهز للتوقيع',
|
||||
signingTx: 'جاري التوقيع...',
|
||||
successTitle: 'أصبحت مواطناً في بزكوي!',
|
||||
successSubtitle: 'مرحباً بك في وطنك الرقمي!',
|
||||
citizenId: 'رقم المواطنة',
|
||||
applicationSubmitted: 'تم استلام طلبك!',
|
||||
pendingReferral: 'في انتظار موافقة المُحيل',
|
||||
identityHash: 'هاش الهوية',
|
||||
applicationInfo: 'بعد موافقة المُحيل، يمكنك التأكيد',
|
||||
depositRequired: '١ HEZ وديعة مطلوبة',
|
||||
walletAddress: 'عنوان المحفظة',
|
||||
fillAllFields: 'يرجى ملء جميع الحقول',
|
||||
acceptConsent: 'يرجى تحديد مربع الموافقة',
|
||||
@@ -628,6 +630,7 @@ const ar: Translations = {
|
||||
submissionFailed: 'فشل الإرسال',
|
||||
alreadyPending: 'لديك طلب قيد الانتظار',
|
||||
alreadyApproved: 'مواطنتك معتمدة بالفعل!',
|
||||
insufficientBalance: 'رصيد غير كافٍ (١ HEZ وديعة مطلوبة)',
|
||||
selectLanguage: 'اختر اللغة',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -610,8 +610,8 @@ const ckb: Translations = {
|
||||
emailPlaceholder: 'ناو@mail.com',
|
||||
profession: 'پیشە',
|
||||
professionPlaceholder: 'پیشەکەت بنووسە',
|
||||
referralCode: 'کۆدی ڕیفێڕاڵ (ئارەزوومەندانە)',
|
||||
referralCodePlaceholder: 'کۆدی ڕیفێڕاڵ بنووسە',
|
||||
referrerAddress: 'ناونیشانی ڕیفێڕەر (ئارەزوومەندانە)',
|
||||
referrerPlaceholder: 'ناونیشانی SS58 بنووسە - بەتاڵ بهێڵە بۆ دیاریکردنی ئۆتۆماتیک',
|
||||
consentCheckbox: 'داتاکانم بە ZK-proof پارێزراون، تەنها هاش لە بلۆکچەین تۆمار دەکرێت',
|
||||
submit: 'ناردن',
|
||||
sign: 'واژووبکە',
|
||||
@@ -619,9 +619,11 @@ const ckb: Translations = {
|
||||
preparingData: 'داتا ئامادە دەکرێت...',
|
||||
readyToSign: 'ئامادەیە بۆ واژوو',
|
||||
signingTx: 'واژوو دەکرێت...',
|
||||
successTitle: 'بوویت بە هاوڵاتی پێزکووی!',
|
||||
successSubtitle: 'بەخێر بێیت بۆ نیشتمانی دیجیتاڵت!',
|
||||
citizenId: 'ناسنامەی هاوڵاتی',
|
||||
applicationSubmitted: 'داواکارییەکەت وەرگیرا!',
|
||||
pendingReferral: 'چاوەڕوانی پەسەندکردنی ڕیفێڕەر',
|
||||
identityHash: 'هاشی ناسنامە',
|
||||
applicationInfo: 'کاتێک ڕیفێڕەر پەسەند بکات، دەتوانیت confirm بکەیت',
|
||||
depositRequired: '١ HEZ ئەمانەت پێویستە',
|
||||
walletAddress: 'ناونیشانی جزدان',
|
||||
fillAllFields: 'تکایە هەموو خانەکان پڕ بکەرەوە',
|
||||
acceptConsent: 'تکایە خانەی ڕەزامەندی نیشان بدە',
|
||||
@@ -630,6 +632,7 @@ const ckb: Translations = {
|
||||
submissionFailed: 'ناردن سەرنەکەوت',
|
||||
alreadyPending: 'داواکارییەکی چاوەڕوانت هەیە',
|
||||
alreadyApproved: 'هاوڵاتیبوونت پێشتر پەسەند کراوە!',
|
||||
insufficientBalance: 'باڵانسی پێویست نییە (١ HEZ ئەمانەت پێویستە)',
|
||||
selectLanguage: 'زمان هەڵبژێرە',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -609,8 +609,8 @@ const en: Translations = {
|
||||
emailPlaceholder: 'name@mail.com',
|
||||
profession: 'Profession',
|
||||
professionPlaceholder: 'Enter your profession',
|
||||
referralCode: 'Referral Code (Optional)',
|
||||
referralCodePlaceholder: 'Enter referral code',
|
||||
referrerAddress: 'Referrer Address (Optional)',
|
||||
referrerPlaceholder: 'Enter SS58 address - leave empty for auto-assign',
|
||||
consentCheckbox: 'My data is protected with ZK-proof, only a hash is stored on the blockchain',
|
||||
submit: 'Submit',
|
||||
sign: 'Sign',
|
||||
@@ -618,9 +618,11 @@ const en: Translations = {
|
||||
preparingData: 'Preparing data...',
|
||||
readyToSign: 'Ready to sign',
|
||||
signingTx: 'Signing...',
|
||||
successTitle: 'You are now a Pezkuwi Citizen!',
|
||||
successSubtitle: 'Welcome to your digital homeland!',
|
||||
citizenId: 'Citizen ID',
|
||||
applicationSubmitted: 'Your application has been submitted!',
|
||||
pendingReferral: 'Waiting for referrer approval',
|
||||
identityHash: 'Identity Hash',
|
||||
applicationInfo: 'Once your referrer approves, you can confirm',
|
||||
depositRequired: '1 HEZ deposit required',
|
||||
walletAddress: 'Wallet Address',
|
||||
fillAllFields: 'Please fill in all fields',
|
||||
acceptConsent: 'Please accept the consent checkbox',
|
||||
@@ -629,6 +631,7 @@ const en: Translations = {
|
||||
submissionFailed: 'Submission failed',
|
||||
alreadyPending: 'You already have a pending application',
|
||||
alreadyApproved: 'Your citizenship is already approved!',
|
||||
insufficientBalance: 'Insufficient balance (1 HEZ deposit required)',
|
||||
selectLanguage: 'Select language',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -609,8 +609,8 @@ const fa: Translations = {
|
||||
emailPlaceholder: 'name@mail.com',
|
||||
profession: 'شغل',
|
||||
professionPlaceholder: 'شغل خود را وارد کنید',
|
||||
referralCode: 'کد معرف (اختیاری)',
|
||||
referralCodePlaceholder: 'کد معرف را وارد کنید',
|
||||
referrerAddress: 'آدرس معرف (اختیاری)',
|
||||
referrerPlaceholder: 'آدرس SS58 وارد کنید - خالی بگذارید برای تخصیص خودکار',
|
||||
consentCheckbox: 'دادههای من با ZK-proof محافظت میشوند، فقط هش در بلاکچین ثبت میشود',
|
||||
submit: 'ارسال',
|
||||
sign: 'امضا کنید',
|
||||
@@ -618,9 +618,11 @@ const fa: Translations = {
|
||||
preparingData: 'آمادهسازی دادهها...',
|
||||
readyToSign: 'آماده برای امضا',
|
||||
signingTx: 'در حال امضا...',
|
||||
successTitle: 'شما شهروند پزکوی شدید!',
|
||||
successSubtitle: 'به سرزمین دیجیتالتان خوش آمدید!',
|
||||
citizenId: 'شناسه شهروندی',
|
||||
applicationSubmitted: 'درخواست شما ثبت شد!',
|
||||
pendingReferral: 'در انتظار تأیید معرف',
|
||||
identityHash: 'هش هویت',
|
||||
applicationInfo: 'پس از تأیید معرف، میتوانید تأیید نهایی کنید',
|
||||
depositRequired: '۱ HEZ سپرده لازم است',
|
||||
walletAddress: 'آدرس کیف پول',
|
||||
fillAllFields: 'لطفاً همه فیلدها را پر کنید',
|
||||
acceptConsent: 'لطفاً کادر رضایت را علامت بزنید',
|
||||
@@ -629,6 +631,7 @@ const fa: Translations = {
|
||||
submissionFailed: 'ارسال ناموفق',
|
||||
alreadyPending: 'درخواست در انتظار دارید',
|
||||
alreadyApproved: 'شهروندی شما قبلاً تأیید شده!',
|
||||
insufficientBalance: 'موجودی ناکافی (۱ HEZ سپرده لازم است)',
|
||||
selectLanguage: 'انتخاب زبان',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -634,8 +634,8 @@ const krd: Translations = {
|
||||
emailPlaceholder: 'navê@mail.com',
|
||||
profession: 'Pîşeya Te',
|
||||
professionPlaceholder: 'Pîşeya xwe binivîse',
|
||||
referralCode: 'Koda Referral (Opsiyonel)',
|
||||
referralCodePlaceholder: 'Koda referral binivîse',
|
||||
referrerAddress: 'Navnîşana Referrer (Opsiyonel)',
|
||||
referrerPlaceholder: 'Navnîşana SS58 binivîse - vala bimîne otomatîk tê danîn',
|
||||
consentCheckbox: 'Daneyên min bi ZK-proof ewle ne, tenê hash li blockchain tê tomarkirin',
|
||||
submit: 'Bişîne',
|
||||
sign: 'Îmze Bike',
|
||||
@@ -643,9 +643,11 @@ const krd: Translations = {
|
||||
preparingData: 'Dane têne amadekirin...',
|
||||
readyToSign: 'Ji bo îmzekirinê amade ye',
|
||||
signingTx: 'Tê îmzekirin...',
|
||||
successTitle: 'Welatiyê Pezkuwî bûn!',
|
||||
successSubtitle: 'Serî hatî welatê xwe yê dîjîtal!',
|
||||
citizenId: 'Nasnameya Welatî',
|
||||
applicationSubmitted: 'Serlêdana te hat qebûlkirin!',
|
||||
pendingReferral: 'Li benda pejirandina referrer',
|
||||
identityHash: 'Hash-a Nasnameyê',
|
||||
applicationInfo: 'Dema referrer pejirîne, hûn dikarin confirm bikin',
|
||||
depositRequired: '1 HEZ depozîto pêwîst e',
|
||||
walletAddress: 'Navnîşana Cûzdan',
|
||||
fillAllFields: 'Ji kerema xwe hemû qadan tije bike',
|
||||
acceptConsent: 'Ji kerema xwe qutiya pejirandinê nîşan bide',
|
||||
@@ -654,6 +656,7 @@ const krd: Translations = {
|
||||
submissionFailed: 'Serlêdan neserketî',
|
||||
alreadyPending: 'Serlêdanek te ya li bendê heye',
|
||||
alreadyApproved: 'Welatîbûna te berê hatiye pejirandin!',
|
||||
insufficientBalance: 'Balansa têr nîne (1 HEZ depozîto pêwîst e)',
|
||||
selectLanguage: 'Ziman hilbijêre',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -610,8 +610,8 @@ const tr: Translations = {
|
||||
emailPlaceholder: 'isim@mail.com',
|
||||
profession: 'Meslek',
|
||||
professionPlaceholder: 'Mesleğinizi girin',
|
||||
referralCode: 'Referans Kodu (Opsiyonel)',
|
||||
referralCodePlaceholder: 'Referans kodunu girin',
|
||||
referrerAddress: 'Referrer Adresi (Opsiyonel)',
|
||||
referrerPlaceholder: 'SS58 adresi girin - boş bırakılırsa otomatik atanır',
|
||||
consentCheckbox: "Verilerim ZK-proof ile güvende, sadece hash blockchain'e kaydedilir",
|
||||
submit: 'Gönder',
|
||||
sign: 'İmzala',
|
||||
@@ -619,9 +619,11 @@ const tr: Translations = {
|
||||
preparingData: 'Veriler hazırlanıyor...',
|
||||
readyToSign: 'İmzalanmaya hazır',
|
||||
signingTx: 'İmzalanıyor...',
|
||||
successTitle: 'Pezkuwi Vatandaşı Oldunuz!',
|
||||
successSubtitle: 'Dijital vatanınıza hoş geldiniz!',
|
||||
citizenId: 'Vatandaş No',
|
||||
applicationSubmitted: 'Başvurunuz alındı!',
|
||||
pendingReferral: 'Referrer onayı bekleniyor',
|
||||
identityHash: 'Kimlik Hash',
|
||||
applicationInfo: 'Referrer onayladıktan sonra confirm edebilirsiniz',
|
||||
depositRequired: '1 HEZ depozito gerekli',
|
||||
walletAddress: 'Cüzdan Adresi',
|
||||
fillAllFields: 'Lütfen tüm alanları doldurun',
|
||||
acceptConsent: 'Lütfen onay kutusunu işaretleyin',
|
||||
@@ -630,6 +632,7 @@ const tr: Translations = {
|
||||
submissionFailed: 'Başvuru başarısız',
|
||||
alreadyPending: 'Bekleyen bir başvurunuz var',
|
||||
alreadyApproved: 'Vatandaşlığınız zaten onaylanmış!',
|
||||
insufficientBalance: 'Yetersiz bakiye (1 HEZ depozito gerekli)',
|
||||
selectLanguage: 'Dil seçin',
|
||||
},
|
||||
};
|
||||
|
||||
+9
-6
@@ -616,8 +616,8 @@ export interface Translations {
|
||||
emailPlaceholder: string;
|
||||
profession: string;
|
||||
professionPlaceholder: string;
|
||||
referralCode: string;
|
||||
referralCodePlaceholder: string;
|
||||
referrerAddress: string;
|
||||
referrerPlaceholder: string;
|
||||
// Consent
|
||||
consentCheckbox: string;
|
||||
// Buttons
|
||||
@@ -628,11 +628,13 @@ export interface Translations {
|
||||
preparingData: string;
|
||||
readyToSign: string;
|
||||
signingTx: string;
|
||||
// Success
|
||||
successTitle: string;
|
||||
successSubtitle: string;
|
||||
citizenId: string;
|
||||
// Application submitted
|
||||
applicationSubmitted: string;
|
||||
pendingReferral: string;
|
||||
identityHash: string;
|
||||
walletAddress: string;
|
||||
applicationInfo: string;
|
||||
depositRequired: string;
|
||||
// Errors
|
||||
fillAllFields: string;
|
||||
acceptConsent: string;
|
||||
@@ -641,6 +643,7 @@ export interface Translations {
|
||||
submissionFailed: string;
|
||||
alreadyPending: string;
|
||||
alreadyApproved: string;
|
||||
insufficientBalance: string;
|
||||
// Language selector
|
||||
selectLanguage: string;
|
||||
};
|
||||
|
||||
+31
-121
@@ -4,12 +4,13 @@
|
||||
* Uses native KeyringPair signing (no browser extension)
|
||||
*/
|
||||
|
||||
import { keccak256 } from 'js-sha3';
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { KeyringPair } from '@pezkuwi/keyring/types';
|
||||
|
||||
// ── Type Definitions ────────────────────────────────────────────────
|
||||
|
||||
export type KycStatus = 'NotStarted' | 'Pending' | 'Approved' | 'Rejected';
|
||||
export type CitizenshipStatus = 'NotStarted' | 'PendingReferral' | 'ReferrerApproved' | 'Approved';
|
||||
|
||||
export type Region = 'bakur' | 'basur' | 'rojava' | 'rojhelat' | 'diaspora' | 'kurdistan_a_sor';
|
||||
|
||||
@@ -32,7 +33,7 @@ export interface CitizenshipData {
|
||||
region: Region;
|
||||
email: string;
|
||||
profession: string;
|
||||
referralCode?: string;
|
||||
referrerAddress?: string;
|
||||
walletAddress: string;
|
||||
timestamp: number;
|
||||
}
|
||||
@@ -41,28 +42,18 @@ export interface CitizenshipResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
blockHash?: string;
|
||||
identityHash?: string;
|
||||
}
|
||||
|
||||
// ── Hash Generation ─────────────────────────────────────────────────
|
||||
// ── Identity Hash (Keccak-256) ──────────────────────────────────────
|
||||
|
||||
function simpleHash(str: string): number {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
export function generateCommitmentHash(data: CitizenshipData): string {
|
||||
const str = JSON.stringify(data);
|
||||
return simpleHash(str).toString(16);
|
||||
}
|
||||
|
||||
export function generateNullifierHash(address: string, timestamp: number): string {
|
||||
const str = address + timestamp.toString();
|
||||
return 'nullifier_' + simpleHash(str).toString(16);
|
||||
export function calculateIdentityHash(name: string, email: string, documentCids: string[]): string {
|
||||
const data = JSON.stringify({
|
||||
name: name.trim().toLowerCase(),
|
||||
email: email.trim().toLowerCase(),
|
||||
documents: documentCids.sort(),
|
||||
});
|
||||
return '0x' + keccak256(data);
|
||||
}
|
||||
|
||||
// ── Encryption & Storage ────────────────────────────────────────────
|
||||
@@ -99,9 +90,12 @@ export async function uploadToIPFS(_data: CitizenshipData): Promise<string> {
|
||||
return mockCID;
|
||||
}
|
||||
|
||||
// ── KYC Status ──────────────────────────────────────────────────────
|
||||
// ── Citizenship Status ──────────────────────────────────────────────
|
||||
|
||||
export async function getKycStatus(api: ApiPromise, address: string): Promise<KycStatus> {
|
||||
export async function getCitizenshipStatus(
|
||||
api: ApiPromise,
|
||||
address: string
|
||||
): Promise<CitizenshipStatus> {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (!(api?.query as any)?.identityKyc) {
|
||||
@@ -117,101 +111,34 @@ export async function getKycStatus(api: ApiPromise, address: string): Promise<Ky
|
||||
|
||||
const statusStr = status.toString();
|
||||
if (statusStr === 'Approved') return 'Approved';
|
||||
if (statusStr === 'Pending') return 'Pending';
|
||||
if (statusStr === 'Rejected') return 'Rejected';
|
||||
if (statusStr === 'PendingReferral') return 'PendingReferral';
|
||||
if (statusStr === 'ReferrerApproved') return 'ReferrerApproved';
|
||||
|
||||
return 'NotStarted';
|
||||
} catch (error) {
|
||||
console.error('[Citizenship] Error fetching KYC status:', error);
|
||||
console.error('[Citizenship] Error fetching status:', error);
|
||||
return 'NotStarted';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Blockchain Submission ───────────────────────────────────────────
|
||||
|
||||
export async function submitCitizenshipApplication(
|
||||
export async function applyCitizenship(
|
||||
api: ApiPromise,
|
||||
keypair: KeyringPair,
|
||||
name: string,
|
||||
email: string,
|
||||
ipfsCid: string,
|
||||
notes: string = 'Citizenship application via Telegram MiniApp'
|
||||
identityHash: string,
|
||||
referrerAddress: string | null = null
|
||||
): Promise<CitizenshipResult> {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const tx = api.tx as any;
|
||||
if (!tx?.identityKyc?.setIdentity || !tx?.identityKyc?.applyForKyc) {
|
||||
if (!tx?.identityKyc?.applyForCitizenship) {
|
||||
return { success: false, error: 'Identity KYC pallet not available' };
|
||||
}
|
||||
|
||||
const address = keypair.address;
|
||||
|
||||
// Check for pending application
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const pendingApp = await (api.query as any).identityKyc.pendingKycApplications(address);
|
||||
if (!pendingApp.isEmpty) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'You already have a pending citizenship application.',
|
||||
};
|
||||
}
|
||||
|
||||
// Check if already approved
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const kycStatus = await (api.query as any).identityKyc.kycStatuses(address);
|
||||
if (kycStatus.toString() === 'Approved') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Your citizenship is already approved!',
|
||||
};
|
||||
}
|
||||
|
||||
const cidString = String(ipfsCid);
|
||||
if (!cidString || cidString === 'undefined') {
|
||||
return { success: false, error: 'Invalid IPFS CID' };
|
||||
}
|
||||
|
||||
// Step 1: Set identity
|
||||
const identityResult = await new Promise<CitizenshipResult>((resolve) => {
|
||||
const result = await new Promise<CitizenshipResult>((resolve) => {
|
||||
tx.identityKyc
|
||||
.setIdentity(name, email)
|
||||
.signAndSend(
|
||||
keypair,
|
||||
{ nonce: -1 },
|
||||
({
|
||||
status,
|
||||
dispatchError,
|
||||
}: {
|
||||
status: { isInBlock: boolean; isFinalized: boolean };
|
||||
dispatchError?: { isModule: boolean; asModule: unknown; toString: () => string };
|
||||
}) => {
|
||||
if (status.isInBlock || status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMessage = 'Identity transaction failed';
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(
|
||||
dispatchError.asModule as Parameters<typeof api.registry.findMetaError>[0]
|
||||
);
|
||||
errorMessage = `${decoded.section}.${decoded.name}`;
|
||||
}
|
||||
resolve({ success: false, error: errorMessage });
|
||||
return;
|
||||
}
|
||||
resolve({ success: true });
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch((error: Error) => resolve({ success: false, error: error.message }));
|
||||
});
|
||||
|
||||
if (!identityResult.success) {
|
||||
return identityResult;
|
||||
}
|
||||
|
||||
// Step 2: Apply for KYC
|
||||
const kycResult = await new Promise<CitizenshipResult>((resolve) => {
|
||||
tx.identityKyc
|
||||
.applyForKyc(cidString, notes)
|
||||
.applyForCitizenship(identityHash, referrerAddress)
|
||||
.signAndSend(
|
||||
keypair,
|
||||
{ nonce: -1 },
|
||||
@@ -229,25 +156,25 @@ export async function submitCitizenshipApplication(
|
||||
}) => {
|
||||
if (status.isInBlock || status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMessage = 'KYC application failed';
|
||||
let errorMessage = 'Citizenship application failed';
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(
|
||||
dispatchError.asModule as Parameters<typeof api.registry.findMetaError>[0]
|
||||
);
|
||||
errorMessage = `${decoded.section}.${decoded.name}`;
|
||||
}
|
||||
resolve({ success: false, error: errorMessage });
|
||||
resolve({ success: false, error: errorMessage, identityHash });
|
||||
return;
|
||||
}
|
||||
const blockHash = status.asFinalized?.toString() || status.asInBlock?.toString();
|
||||
resolve({ success: true, blockHash });
|
||||
resolve({ success: true, blockHash, identityHash });
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch((error: Error) => resolve({ success: false, error: error.message }));
|
||||
.catch((error: Error) => resolve({ success: false, error: error.message, identityHash }));
|
||||
});
|
||||
|
||||
return kycResult;
|
||||
return result;
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -255,20 +182,3 @@ export async function submitCitizenshipApplication(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ── Citizen Number Generation ───────────────────────────────────────
|
||||
|
||||
export function generateCitizenNumber(
|
||||
ownerAddress: string,
|
||||
collectionId: number,
|
||||
itemId: number
|
||||
): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < ownerAddress.length; i++) {
|
||||
hash = (hash << 5) - hash + ownerAddress.charCodeAt(i);
|
||||
hash = hash & hash;
|
||||
}
|
||||
hash += collectionId * 1000 + itemId;
|
||||
hash = Math.abs(hash);
|
||||
return (hash % 1000000).toString().padStart(6, '0');
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export function CitizenPage() {
|
||||
const { t, lang, setLang } = useTranslation();
|
||||
const [showLangMenu, setShowLangMenu] = useState(false);
|
||||
const [citizenshipData, setCitizenshipData] = useState<CitizenshipData | null>(null);
|
||||
const [identityHash, setIdentityHash] = useState<string>('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Determine initial step based on wallet state
|
||||
@@ -87,7 +88,8 @@ export function CitizenPage() {
|
||||
}, []);
|
||||
|
||||
// Processing result
|
||||
const handleSuccess = useCallback(() => {
|
||||
const handleSuccess = useCallback((hash: string) => {
|
||||
setIdentityHash(hash);
|
||||
setStep('success');
|
||||
}, []);
|
||||
|
||||
@@ -186,7 +188,11 @@ export function CitizenPage() {
|
||||
)}
|
||||
|
||||
{step === 'success' && address && (
|
||||
<CitizenSuccess address={address} onOpenApp={handleOpenApp} />
|
||||
<CitizenSuccess
|
||||
address={address}
|
||||
identityHash={identityHash}
|
||||
onOpenApp={handleOpenApp}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
</main>
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.0.193",
|
||||
"buildTime": "2026-02-14T18:08:28.138Z",
|
||||
"buildNumber": 1771092508139
|
||||
"version": "1.0.194",
|
||||
"buildTime": "2026-02-14T19:00:32.936Z",
|
||||
"buildNumber": 1771095632937
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user