mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-04-22 03:07: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",
|
"name": "pezkuwi-telegram-miniapp",
|
||||||
"version": "1.0.169",
|
"version": "1.0.193",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pezkuwi-telegram-miniapp",
|
"name": "pezkuwi-telegram-miniapp",
|
||||||
"version": "1.0.169",
|
"version": "1.0.193",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pezkuwi/api": "^16.5.36",
|
"@pezkuwi/api": "^16.5.36",
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"js-sha3": "^0.9.3",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@@ -7060,6 +7061,12 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"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": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pezkuwi-telegram-miniapp",
|
"name": "pezkuwi-telegram-miniapp",
|
||||||
"version": "1.0.193",
|
"version": "1.0.194",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
|
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
|
||||||
"author": "Pezkuwichain Team",
|
"author": "Pezkuwichain Team",
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"js-sha3": "^0.9.3",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) {
|
|||||||
const [region, setRegion] = useState<Region | ''>('');
|
const [region, setRegion] = useState<Region | ''>('');
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [profession, setProfession] = useState('');
|
const [profession, setProfession] = useState('');
|
||||||
const [referralCode, setReferralCode] = useState('');
|
const [referrerAddress, setReferrerAddress] = useState('');
|
||||||
const [consent, setConsent] = useState(false);
|
const [consent, setConsent] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) {
|
|||||||
region: region as Region,
|
region: region as Region,
|
||||||
email,
|
email,
|
||||||
profession,
|
profession,
|
||||||
referralCode: referralCode || undefined,
|
referrerAddress: referrerAddress || undefined,
|
||||||
walletAddress,
|
walletAddress,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
@@ -319,15 +319,15 @@ export function CitizenForm({ walletAddress, onSubmit }: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Referral Code */}
|
{/* Referrer Address */}
|
||||||
<div>
|
<div>
|
||||||
<label className={labelClass}>{t('citizen.referralCode')}</label>
|
<label className={labelClass}>{t('citizen.referrerAddress')}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={referralCode}
|
value={referrerAddress}
|
||||||
onChange={(e) => setReferralCode(e.target.value)}
|
onChange={(e) => setReferrerAddress(e.target.value)}
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
placeholder={t('citizen.referralCodePlaceholder')}
|
placeholder={t('citizen.referrerPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,16 +11,15 @@ import { useTelegram } from '@/hooks/useTelegram';
|
|||||||
import { useWallet } from '@/contexts/WalletContext';
|
import { useWallet } from '@/contexts/WalletContext';
|
||||||
import type { CitizenshipData } from '@/lib/citizenship';
|
import type { CitizenshipData } from '@/lib/citizenship';
|
||||||
import {
|
import {
|
||||||
generateCommitmentHash,
|
calculateIdentityHash,
|
||||||
generateNullifierHash,
|
|
||||||
saveCitizenshipLocally,
|
saveCitizenshipLocally,
|
||||||
uploadToIPFS,
|
uploadToIPFS,
|
||||||
submitCitizenshipApplication,
|
applyCitizenship,
|
||||||
} from '@/lib/citizenship';
|
} from '@/lib/citizenship';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
citizenshipData: CitizenshipData;
|
citizenshipData: CitizenshipData;
|
||||||
onSuccess: (blockHash?: string) => void;
|
onSuccess: (identityHash: string, blockHash?: string) => void;
|
||||||
onError: (error: string) => void;
|
onError: (error: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,23 +31,24 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
|
|||||||
const { peopleApi, keypair } = useWallet();
|
const { peopleApi, keypair } = useWallet();
|
||||||
|
|
||||||
const [state, setState] = useState<ProcessingState>('preparing');
|
const [state, setState] = useState<ProcessingState>('preparing');
|
||||||
const [ipfsCid, setIpfsCid] = useState<string>('');
|
const [identityHash, setIdentityHash] = useState<string>('');
|
||||||
|
|
||||||
// Prepare data on mount
|
// Prepare data on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prepare = async () => {
|
const prepare = async () => {
|
||||||
try {
|
try {
|
||||||
// Generate commitment hash
|
// Mock IPFS upload
|
||||||
generateCommitmentHash(citizenshipData);
|
const ipfsCid = await uploadToIPFS(citizenshipData);
|
||||||
generateNullifierHash(citizenshipData.walletAddress, citizenshipData.timestamp);
|
|
||||||
|
// Calculate identity hash (keccak256)
|
||||||
|
const hash = calculateIdentityHash(citizenshipData.fullName, citizenshipData.email, [
|
||||||
|
ipfsCid,
|
||||||
|
]);
|
||||||
|
setIdentityHash(hash);
|
||||||
|
|
||||||
// Save encrypted data locally
|
// Save encrypted data locally
|
||||||
saveCitizenshipLocally(citizenshipData);
|
saveCitizenshipLocally(citizenshipData);
|
||||||
|
|
||||||
// Mock IPFS upload
|
|
||||||
const cid = await uploadToIPFS(citizenshipData);
|
|
||||||
setIpfsCid(cid);
|
|
||||||
|
|
||||||
// Small delay to show animation
|
// Small delay to show animation
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
@@ -72,18 +72,16 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
|
|||||||
hapticImpact('medium');
|
hapticImpact('medium');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await submitCitizenshipApplication(
|
const result = await applyCitizenship(
|
||||||
peopleApi,
|
peopleApi,
|
||||||
keypair,
|
keypair,
|
||||||
citizenshipData.fullName,
|
identityHash,
|
||||||
citizenshipData.email,
|
citizenshipData.referrerAddress || null
|
||||||
ipfsCid,
|
|
||||||
`Citizenship application - ${citizenshipData.region}`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
hapticNotification('success');
|
hapticNotification('success');
|
||||||
onSuccess(result.blockHash);
|
onSuccess(identityHash, result.blockHash);
|
||||||
} else {
|
} else {
|
||||||
hapticNotification('error');
|
hapticNotification('error');
|
||||||
onError(result.error || t('citizen.submissionFailed'));
|
onError(result.error || t('citizen.submissionFailed'));
|
||||||
@@ -96,7 +94,7 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
|
|||||||
peopleApi,
|
peopleApi,
|
||||||
keypair,
|
keypair,
|
||||||
citizenshipData,
|
citizenshipData,
|
||||||
ipfsCid,
|
identityHash,
|
||||||
hapticImpact,
|
hapticImpact,
|
||||||
hapticNotification,
|
hapticNotification,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
@@ -124,6 +122,9 @@ export function CitizenProcessing({ citizenshipData, onSuccess, onError }: Props
|
|||||||
{state === 'preparing' && (
|
{state === 'preparing' && (
|
||||||
<p className="text-sm text-muted-foreground">{citizenshipData.fullName}</p>
|
<p className="text-sm text-muted-foreground">{citizenshipData.fullName}</p>
|
||||||
)}
|
)}
|
||||||
|
{state === 'ready' && (
|
||||||
|
<p className="text-xs text-muted-foreground">{t('citizen.depositRequired')}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sign Button */}
|
{/* Sign Button */}
|
||||||
|
|||||||
@@ -1,27 +1,24 @@
|
|||||||
/**
|
/**
|
||||||
* Citizen Success Screen
|
* Citizen Success Screen
|
||||||
* Shows after successful citizenship application submission
|
* 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 { useTranslation } from '@/i18n';
|
||||||
import { useTelegram } from '@/hooks/useTelegram';
|
import { useTelegram } from '@/hooks/useTelegram';
|
||||||
import { formatAddress } from '@/lib/wallet-service';
|
import { formatAddress } from '@/lib/wallet-service';
|
||||||
import { generateCitizenNumber } from '@/lib/citizenship';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
address: string;
|
address: string;
|
||||||
|
identityHash: string;
|
||||||
onOpenApp: () => void;
|
onOpenApp: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CitizenSuccess({ address, onOpenApp }: Props) {
|
export function CitizenSuccess({ address, identityHash, onOpenApp }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { hapticImpact } = useTelegram();
|
const { hapticImpact } = useTelegram();
|
||||||
|
|
||||||
// Generate a citizen number based on address
|
|
||||||
const citizenNumber = generateCitizenNumber(address, 42, 0);
|
|
||||||
const citizenId = `#42-0-${citizenNumber}`;
|
|
||||||
|
|
||||||
const handleOpenApp = () => {
|
const handleOpenApp = () => {
|
||||||
hapticImpact('medium');
|
hapticImpact('medium');
|
||||||
onOpenApp();
|
onOpenApp();
|
||||||
@@ -36,15 +33,18 @@ export function CitizenSuccess({ address, onOpenApp }: Props) {
|
|||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div className="text-center space-y-2">
|
<div className="text-center space-y-2">
|
||||||
<h1 className="text-2xl font-bold">{t('citizen.successTitle')}</h1>
|
<h1 className="text-2xl font-bold">{t('citizen.applicationSubmitted')}</h1>
|
||||||
<p className="text-muted-foreground">{t('citizen.successSubtitle')}</p>
|
<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>
|
</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 className="w-full max-w-sm bg-muted/50 rounded-2xl p-5 space-y-4 border border-border">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">{t('citizen.citizenId')}</p>
|
<p className="text-xs text-muted-foreground">{t('citizen.identityHash')}</p>
|
||||||
<p className="text-xl font-mono font-bold text-primary">{citizenId}</p>
|
<p className="text-sm font-mono break-all">{formatAddress(identityHash)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">{t('citizen.walletAddress')}</p>
|
<p className="text-xs text-muted-foreground">{t('citizen.walletAddress')}</p>
|
||||||
@@ -52,6 +52,11 @@ export function CitizenSuccess({ address, onOpenApp }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Info Note */}
|
||||||
|
<p className="text-xs text-muted-foreground text-center max-w-sm">
|
||||||
|
{t('citizen.applicationInfo')}
|
||||||
|
</p>
|
||||||
|
|
||||||
{/* Open App Button */}
|
{/* Open App Button */}
|
||||||
<button
|
<button
|
||||||
onClick={handleOpenApp}
|
onClick={handleOpenApp}
|
||||||
|
|||||||
@@ -608,8 +608,8 @@ const ar: Translations = {
|
|||||||
emailPlaceholder: 'name@mail.com',
|
emailPlaceholder: 'name@mail.com',
|
||||||
profession: 'المهنة',
|
profession: 'المهنة',
|
||||||
professionPlaceholder: 'أدخل مهنتك',
|
professionPlaceholder: 'أدخل مهنتك',
|
||||||
referralCode: 'رمز الإحالة (اختياري)',
|
referrerAddress: 'عنوان المُحيل (اختياري)',
|
||||||
referralCodePlaceholder: 'أدخل رمز الإحالة',
|
referrerPlaceholder: 'أدخل عنوان SS58 - اتركه فارغاً للتعيين التلقائي',
|
||||||
consentCheckbox: 'بياناتي محمية بـ ZK-proof، فقط الهاش يُخزن على البلوكشين',
|
consentCheckbox: 'بياناتي محمية بـ ZK-proof، فقط الهاش يُخزن على البلوكشين',
|
||||||
submit: 'إرسال',
|
submit: 'إرسال',
|
||||||
sign: 'وقّع',
|
sign: 'وقّع',
|
||||||
@@ -617,9 +617,11 @@ const ar: Translations = {
|
|||||||
preparingData: 'جاري تحضير البيانات...',
|
preparingData: 'جاري تحضير البيانات...',
|
||||||
readyToSign: 'جاهز للتوقيع',
|
readyToSign: 'جاهز للتوقيع',
|
||||||
signingTx: 'جاري التوقيع...',
|
signingTx: 'جاري التوقيع...',
|
||||||
successTitle: 'أصبحت مواطناً في بزكوي!',
|
applicationSubmitted: 'تم استلام طلبك!',
|
||||||
successSubtitle: 'مرحباً بك في وطنك الرقمي!',
|
pendingReferral: 'في انتظار موافقة المُحيل',
|
||||||
citizenId: 'رقم المواطنة',
|
identityHash: 'هاش الهوية',
|
||||||
|
applicationInfo: 'بعد موافقة المُحيل، يمكنك التأكيد',
|
||||||
|
depositRequired: '١ HEZ وديعة مطلوبة',
|
||||||
walletAddress: 'عنوان المحفظة',
|
walletAddress: 'عنوان المحفظة',
|
||||||
fillAllFields: 'يرجى ملء جميع الحقول',
|
fillAllFields: 'يرجى ملء جميع الحقول',
|
||||||
acceptConsent: 'يرجى تحديد مربع الموافقة',
|
acceptConsent: 'يرجى تحديد مربع الموافقة',
|
||||||
@@ -628,6 +630,7 @@ const ar: Translations = {
|
|||||||
submissionFailed: 'فشل الإرسال',
|
submissionFailed: 'فشل الإرسال',
|
||||||
alreadyPending: 'لديك طلب قيد الانتظار',
|
alreadyPending: 'لديك طلب قيد الانتظار',
|
||||||
alreadyApproved: 'مواطنتك معتمدة بالفعل!',
|
alreadyApproved: 'مواطنتك معتمدة بالفعل!',
|
||||||
|
insufficientBalance: 'رصيد غير كافٍ (١ HEZ وديعة مطلوبة)',
|
||||||
selectLanguage: 'اختر اللغة',
|
selectLanguage: 'اختر اللغة',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -610,8 +610,8 @@ const ckb: Translations = {
|
|||||||
emailPlaceholder: 'ناو@mail.com',
|
emailPlaceholder: 'ناو@mail.com',
|
||||||
profession: 'پیشە',
|
profession: 'پیشە',
|
||||||
professionPlaceholder: 'پیشەکەت بنووسە',
|
professionPlaceholder: 'پیشەکەت بنووسە',
|
||||||
referralCode: 'کۆدی ڕیفێڕاڵ (ئارەزوومەندانە)',
|
referrerAddress: 'ناونیشانی ڕیفێڕەر (ئارەزوومەندانە)',
|
||||||
referralCodePlaceholder: 'کۆدی ڕیفێڕاڵ بنووسە',
|
referrerPlaceholder: 'ناونیشانی SS58 بنووسە - بەتاڵ بهێڵە بۆ دیاریکردنی ئۆتۆماتیک',
|
||||||
consentCheckbox: 'داتاکانم بە ZK-proof پارێزراون، تەنها هاش لە بلۆکچەین تۆمار دەکرێت',
|
consentCheckbox: 'داتاکانم بە ZK-proof پارێزراون، تەنها هاش لە بلۆکچەین تۆمار دەکرێت',
|
||||||
submit: 'ناردن',
|
submit: 'ناردن',
|
||||||
sign: 'واژووبکە',
|
sign: 'واژووبکە',
|
||||||
@@ -619,9 +619,11 @@ const ckb: Translations = {
|
|||||||
preparingData: 'داتا ئامادە دەکرێت...',
|
preparingData: 'داتا ئامادە دەکرێت...',
|
||||||
readyToSign: 'ئامادەیە بۆ واژوو',
|
readyToSign: 'ئامادەیە بۆ واژوو',
|
||||||
signingTx: 'واژوو دەکرێت...',
|
signingTx: 'واژوو دەکرێت...',
|
||||||
successTitle: 'بوویت بە هاوڵاتی پێزکووی!',
|
applicationSubmitted: 'داواکارییەکەت وەرگیرا!',
|
||||||
successSubtitle: 'بەخێر بێیت بۆ نیشتمانی دیجیتاڵت!',
|
pendingReferral: 'چاوەڕوانی پەسەندکردنی ڕیفێڕەر',
|
||||||
citizenId: 'ناسنامەی هاوڵاتی',
|
identityHash: 'هاشی ناسنامە',
|
||||||
|
applicationInfo: 'کاتێک ڕیفێڕەر پەسەند بکات، دەتوانیت confirm بکەیت',
|
||||||
|
depositRequired: '١ HEZ ئەمانەت پێویستە',
|
||||||
walletAddress: 'ناونیشانی جزدان',
|
walletAddress: 'ناونیشانی جزدان',
|
||||||
fillAllFields: 'تکایە هەموو خانەکان پڕ بکەرەوە',
|
fillAllFields: 'تکایە هەموو خانەکان پڕ بکەرەوە',
|
||||||
acceptConsent: 'تکایە خانەی ڕەزامەندی نیشان بدە',
|
acceptConsent: 'تکایە خانەی ڕەزامەندی نیشان بدە',
|
||||||
@@ -630,6 +632,7 @@ const ckb: Translations = {
|
|||||||
submissionFailed: 'ناردن سەرنەکەوت',
|
submissionFailed: 'ناردن سەرنەکەوت',
|
||||||
alreadyPending: 'داواکارییەکی چاوەڕوانت هەیە',
|
alreadyPending: 'داواکارییەکی چاوەڕوانت هەیە',
|
||||||
alreadyApproved: 'هاوڵاتیبوونت پێشتر پەسەند کراوە!',
|
alreadyApproved: 'هاوڵاتیبوونت پێشتر پەسەند کراوە!',
|
||||||
|
insufficientBalance: 'باڵانسی پێویست نییە (١ HEZ ئەمانەت پێویستە)',
|
||||||
selectLanguage: 'زمان هەڵبژێرە',
|
selectLanguage: 'زمان هەڵبژێرە',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -609,8 +609,8 @@ const en: Translations = {
|
|||||||
emailPlaceholder: 'name@mail.com',
|
emailPlaceholder: 'name@mail.com',
|
||||||
profession: 'Profession',
|
profession: 'Profession',
|
||||||
professionPlaceholder: 'Enter your profession',
|
professionPlaceholder: 'Enter your profession',
|
||||||
referralCode: 'Referral Code (Optional)',
|
referrerAddress: 'Referrer Address (Optional)',
|
||||||
referralCodePlaceholder: 'Enter referral code',
|
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',
|
consentCheckbox: 'My data is protected with ZK-proof, only a hash is stored on the blockchain',
|
||||||
submit: 'Submit',
|
submit: 'Submit',
|
||||||
sign: 'Sign',
|
sign: 'Sign',
|
||||||
@@ -618,9 +618,11 @@ const en: Translations = {
|
|||||||
preparingData: 'Preparing data...',
|
preparingData: 'Preparing data...',
|
||||||
readyToSign: 'Ready to sign',
|
readyToSign: 'Ready to sign',
|
||||||
signingTx: 'Signing...',
|
signingTx: 'Signing...',
|
||||||
successTitle: 'You are now a Pezkuwi Citizen!',
|
applicationSubmitted: 'Your application has been submitted!',
|
||||||
successSubtitle: 'Welcome to your digital homeland!',
|
pendingReferral: 'Waiting for referrer approval',
|
||||||
citizenId: 'Citizen ID',
|
identityHash: 'Identity Hash',
|
||||||
|
applicationInfo: 'Once your referrer approves, you can confirm',
|
||||||
|
depositRequired: '1 HEZ deposit required',
|
||||||
walletAddress: 'Wallet Address',
|
walletAddress: 'Wallet Address',
|
||||||
fillAllFields: 'Please fill in all fields',
|
fillAllFields: 'Please fill in all fields',
|
||||||
acceptConsent: 'Please accept the consent checkbox',
|
acceptConsent: 'Please accept the consent checkbox',
|
||||||
@@ -629,6 +631,7 @@ const en: Translations = {
|
|||||||
submissionFailed: 'Submission failed',
|
submissionFailed: 'Submission failed',
|
||||||
alreadyPending: 'You already have a pending application',
|
alreadyPending: 'You already have a pending application',
|
||||||
alreadyApproved: 'Your citizenship is already approved!',
|
alreadyApproved: 'Your citizenship is already approved!',
|
||||||
|
insufficientBalance: 'Insufficient balance (1 HEZ deposit required)',
|
||||||
selectLanguage: 'Select language',
|
selectLanguage: 'Select language',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -609,8 +609,8 @@ const fa: Translations = {
|
|||||||
emailPlaceholder: 'name@mail.com',
|
emailPlaceholder: 'name@mail.com',
|
||||||
profession: 'شغل',
|
profession: 'شغل',
|
||||||
professionPlaceholder: 'شغل خود را وارد کنید',
|
professionPlaceholder: 'شغل خود را وارد کنید',
|
||||||
referralCode: 'کد معرف (اختیاری)',
|
referrerAddress: 'آدرس معرف (اختیاری)',
|
||||||
referralCodePlaceholder: 'کد معرف را وارد کنید',
|
referrerPlaceholder: 'آدرس SS58 وارد کنید - خالی بگذارید برای تخصیص خودکار',
|
||||||
consentCheckbox: 'دادههای من با ZK-proof محافظت میشوند، فقط هش در بلاکچین ثبت میشود',
|
consentCheckbox: 'دادههای من با ZK-proof محافظت میشوند، فقط هش در بلاکچین ثبت میشود',
|
||||||
submit: 'ارسال',
|
submit: 'ارسال',
|
||||||
sign: 'امضا کنید',
|
sign: 'امضا کنید',
|
||||||
@@ -618,9 +618,11 @@ const fa: Translations = {
|
|||||||
preparingData: 'آمادهسازی دادهها...',
|
preparingData: 'آمادهسازی دادهها...',
|
||||||
readyToSign: 'آماده برای امضا',
|
readyToSign: 'آماده برای امضا',
|
||||||
signingTx: 'در حال امضا...',
|
signingTx: 'در حال امضا...',
|
||||||
successTitle: 'شما شهروند پزکوی شدید!',
|
applicationSubmitted: 'درخواست شما ثبت شد!',
|
||||||
successSubtitle: 'به سرزمین دیجیتالتان خوش آمدید!',
|
pendingReferral: 'در انتظار تأیید معرف',
|
||||||
citizenId: 'شناسه شهروندی',
|
identityHash: 'هش هویت',
|
||||||
|
applicationInfo: 'پس از تأیید معرف، میتوانید تأیید نهایی کنید',
|
||||||
|
depositRequired: '۱ HEZ سپرده لازم است',
|
||||||
walletAddress: 'آدرس کیف پول',
|
walletAddress: 'آدرس کیف پول',
|
||||||
fillAllFields: 'لطفاً همه فیلدها را پر کنید',
|
fillAllFields: 'لطفاً همه فیلدها را پر کنید',
|
||||||
acceptConsent: 'لطفاً کادر رضایت را علامت بزنید',
|
acceptConsent: 'لطفاً کادر رضایت را علامت بزنید',
|
||||||
@@ -629,6 +631,7 @@ const fa: Translations = {
|
|||||||
submissionFailed: 'ارسال ناموفق',
|
submissionFailed: 'ارسال ناموفق',
|
||||||
alreadyPending: 'درخواست در انتظار دارید',
|
alreadyPending: 'درخواست در انتظار دارید',
|
||||||
alreadyApproved: 'شهروندی شما قبلاً تأیید شده!',
|
alreadyApproved: 'شهروندی شما قبلاً تأیید شده!',
|
||||||
|
insufficientBalance: 'موجودی ناکافی (۱ HEZ سپرده لازم است)',
|
||||||
selectLanguage: 'انتخاب زبان',
|
selectLanguage: 'انتخاب زبان',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -634,8 +634,8 @@ const krd: Translations = {
|
|||||||
emailPlaceholder: 'navê@mail.com',
|
emailPlaceholder: 'navê@mail.com',
|
||||||
profession: 'Pîşeya Te',
|
profession: 'Pîşeya Te',
|
||||||
professionPlaceholder: 'Pîşeya xwe binivîse',
|
professionPlaceholder: 'Pîşeya xwe binivîse',
|
||||||
referralCode: 'Koda Referral (Opsiyonel)',
|
referrerAddress: 'Navnîşana Referrer (Opsiyonel)',
|
||||||
referralCodePlaceholder: 'Koda referral binivîse',
|
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',
|
consentCheckbox: 'Daneyên min bi ZK-proof ewle ne, tenê hash li blockchain tê tomarkirin',
|
||||||
submit: 'Bişîne',
|
submit: 'Bişîne',
|
||||||
sign: 'Îmze Bike',
|
sign: 'Îmze Bike',
|
||||||
@@ -643,9 +643,11 @@ const krd: Translations = {
|
|||||||
preparingData: 'Dane têne amadekirin...',
|
preparingData: 'Dane têne amadekirin...',
|
||||||
readyToSign: 'Ji bo îmzekirinê amade ye',
|
readyToSign: 'Ji bo îmzekirinê amade ye',
|
||||||
signingTx: 'Tê îmzekirin...',
|
signingTx: 'Tê îmzekirin...',
|
||||||
successTitle: 'Welatiyê Pezkuwî bûn!',
|
applicationSubmitted: 'Serlêdana te hat qebûlkirin!',
|
||||||
successSubtitle: 'Serî hatî welatê xwe yê dîjîtal!',
|
pendingReferral: 'Li benda pejirandina referrer',
|
||||||
citizenId: 'Nasnameya Welatî',
|
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',
|
walletAddress: 'Navnîşana Cûzdan',
|
||||||
fillAllFields: 'Ji kerema xwe hemû qadan tije bike',
|
fillAllFields: 'Ji kerema xwe hemû qadan tije bike',
|
||||||
acceptConsent: 'Ji kerema xwe qutiya pejirandinê nîşan bide',
|
acceptConsent: 'Ji kerema xwe qutiya pejirandinê nîşan bide',
|
||||||
@@ -654,6 +656,7 @@ const krd: Translations = {
|
|||||||
submissionFailed: 'Serlêdan neserketî',
|
submissionFailed: 'Serlêdan neserketî',
|
||||||
alreadyPending: 'Serlêdanek te ya li bendê heye',
|
alreadyPending: 'Serlêdanek te ya li bendê heye',
|
||||||
alreadyApproved: 'Welatîbûna te berê hatiye pejirandin!',
|
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',
|
selectLanguage: 'Ziman hilbijêre',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -610,8 +610,8 @@ const tr: Translations = {
|
|||||||
emailPlaceholder: 'isim@mail.com',
|
emailPlaceholder: 'isim@mail.com',
|
||||||
profession: 'Meslek',
|
profession: 'Meslek',
|
||||||
professionPlaceholder: 'Mesleğinizi girin',
|
professionPlaceholder: 'Mesleğinizi girin',
|
||||||
referralCode: 'Referans Kodu (Opsiyonel)',
|
referrerAddress: 'Referrer Adresi (Opsiyonel)',
|
||||||
referralCodePlaceholder: 'Referans kodunu girin',
|
referrerPlaceholder: 'SS58 adresi girin - boş bırakılırsa otomatik atanır',
|
||||||
consentCheckbox: "Verilerim ZK-proof ile güvende, sadece hash blockchain'e kaydedilir",
|
consentCheckbox: "Verilerim ZK-proof ile güvende, sadece hash blockchain'e kaydedilir",
|
||||||
submit: 'Gönder',
|
submit: 'Gönder',
|
||||||
sign: 'İmzala',
|
sign: 'İmzala',
|
||||||
@@ -619,9 +619,11 @@ const tr: Translations = {
|
|||||||
preparingData: 'Veriler hazırlanıyor...',
|
preparingData: 'Veriler hazırlanıyor...',
|
||||||
readyToSign: 'İmzalanmaya hazır',
|
readyToSign: 'İmzalanmaya hazır',
|
||||||
signingTx: 'İmzalanıyor...',
|
signingTx: 'İmzalanıyor...',
|
||||||
successTitle: 'Pezkuwi Vatandaşı Oldunuz!',
|
applicationSubmitted: 'Başvurunuz alındı!',
|
||||||
successSubtitle: 'Dijital vatanınıza hoş geldiniz!',
|
pendingReferral: 'Referrer onayı bekleniyor',
|
||||||
citizenId: 'Vatandaş No',
|
identityHash: 'Kimlik Hash',
|
||||||
|
applicationInfo: 'Referrer onayladıktan sonra confirm edebilirsiniz',
|
||||||
|
depositRequired: '1 HEZ depozito gerekli',
|
||||||
walletAddress: 'Cüzdan Adresi',
|
walletAddress: 'Cüzdan Adresi',
|
||||||
fillAllFields: 'Lütfen tüm alanları doldurun',
|
fillAllFields: 'Lütfen tüm alanları doldurun',
|
||||||
acceptConsent: 'Lütfen onay kutusunu işaretleyin',
|
acceptConsent: 'Lütfen onay kutusunu işaretleyin',
|
||||||
@@ -630,6 +632,7 @@ const tr: Translations = {
|
|||||||
submissionFailed: 'Başvuru başarısız',
|
submissionFailed: 'Başvuru başarısız',
|
||||||
alreadyPending: 'Bekleyen bir başvurunuz var',
|
alreadyPending: 'Bekleyen bir başvurunuz var',
|
||||||
alreadyApproved: 'Vatandaşlığınız zaten onaylanmış!',
|
alreadyApproved: 'Vatandaşlığınız zaten onaylanmış!',
|
||||||
|
insufficientBalance: 'Yetersiz bakiye (1 HEZ depozito gerekli)',
|
||||||
selectLanguage: 'Dil seçin',
|
selectLanguage: 'Dil seçin',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+9
-6
@@ -616,8 +616,8 @@ export interface Translations {
|
|||||||
emailPlaceholder: string;
|
emailPlaceholder: string;
|
||||||
profession: string;
|
profession: string;
|
||||||
professionPlaceholder: string;
|
professionPlaceholder: string;
|
||||||
referralCode: string;
|
referrerAddress: string;
|
||||||
referralCodePlaceholder: string;
|
referrerPlaceholder: string;
|
||||||
// Consent
|
// Consent
|
||||||
consentCheckbox: string;
|
consentCheckbox: string;
|
||||||
// Buttons
|
// Buttons
|
||||||
@@ -628,11 +628,13 @@ export interface Translations {
|
|||||||
preparingData: string;
|
preparingData: string;
|
||||||
readyToSign: string;
|
readyToSign: string;
|
||||||
signingTx: string;
|
signingTx: string;
|
||||||
// Success
|
// Application submitted
|
||||||
successTitle: string;
|
applicationSubmitted: string;
|
||||||
successSubtitle: string;
|
pendingReferral: string;
|
||||||
citizenId: string;
|
identityHash: string;
|
||||||
walletAddress: string;
|
walletAddress: string;
|
||||||
|
applicationInfo: string;
|
||||||
|
depositRequired: string;
|
||||||
// Errors
|
// Errors
|
||||||
fillAllFields: string;
|
fillAllFields: string;
|
||||||
acceptConsent: string;
|
acceptConsent: string;
|
||||||
@@ -641,6 +643,7 @@ export interface Translations {
|
|||||||
submissionFailed: string;
|
submissionFailed: string;
|
||||||
alreadyPending: string;
|
alreadyPending: string;
|
||||||
alreadyApproved: string;
|
alreadyApproved: string;
|
||||||
|
insufficientBalance: string;
|
||||||
// Language selector
|
// Language selector
|
||||||
selectLanguage: string;
|
selectLanguage: string;
|
||||||
};
|
};
|
||||||
|
|||||||
+31
-121
@@ -4,12 +4,13 @@
|
|||||||
* Uses native KeyringPair signing (no browser extension)
|
* Uses native KeyringPair signing (no browser extension)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { keccak256 } from 'js-sha3';
|
||||||
import type { ApiPromise } from '@pezkuwi/api';
|
import type { ApiPromise } from '@pezkuwi/api';
|
||||||
import type { KeyringPair } from '@pezkuwi/keyring/types';
|
import type { KeyringPair } from '@pezkuwi/keyring/types';
|
||||||
|
|
||||||
// ── Type Definitions ────────────────────────────────────────────────
|
// ── 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';
|
export type Region = 'bakur' | 'basur' | 'rojava' | 'rojhelat' | 'diaspora' | 'kurdistan_a_sor';
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ export interface CitizenshipData {
|
|||||||
region: Region;
|
region: Region;
|
||||||
email: string;
|
email: string;
|
||||||
profession: string;
|
profession: string;
|
||||||
referralCode?: string;
|
referrerAddress?: string;
|
||||||
walletAddress: string;
|
walletAddress: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
@@ -41,28 +42,18 @@ export interface CitizenshipResult {
|
|||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
blockHash?: string;
|
blockHash?: string;
|
||||||
|
identityHash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Hash Generation ─────────────────────────────────────────────────
|
// ── Identity Hash (Keccak-256) ──────────────────────────────────────
|
||||||
|
|
||||||
function simpleHash(str: string): number {
|
export function calculateIdentityHash(name: string, email: string, documentCids: string[]): string {
|
||||||
let hash = 0;
|
const data = JSON.stringify({
|
||||||
for (let i = 0; i < str.length; i++) {
|
name: name.trim().toLowerCase(),
|
||||||
const char = str.charCodeAt(i);
|
email: email.trim().toLowerCase(),
|
||||||
hash = (hash << 5) - hash + char;
|
documents: documentCids.sort(),
|
||||||
hash = hash & hash;
|
});
|
||||||
}
|
return '0x' + keccak256(data);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Encryption & Storage ────────────────────────────────────────────
|
// ── Encryption & Storage ────────────────────────────────────────────
|
||||||
@@ -99,9 +90,12 @@ export async function uploadToIPFS(_data: CitizenshipData): Promise<string> {
|
|||||||
return mockCID;
|
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 {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if (!(api?.query as any)?.identityKyc) {
|
if (!(api?.query as any)?.identityKyc) {
|
||||||
@@ -117,101 +111,34 @@ export async function getKycStatus(api: ApiPromise, address: string): Promise<Ky
|
|||||||
|
|
||||||
const statusStr = status.toString();
|
const statusStr = status.toString();
|
||||||
if (statusStr === 'Approved') return 'Approved';
|
if (statusStr === 'Approved') return 'Approved';
|
||||||
if (statusStr === 'Pending') return 'Pending';
|
if (statusStr === 'PendingReferral') return 'PendingReferral';
|
||||||
if (statusStr === 'Rejected') return 'Rejected';
|
if (statusStr === 'ReferrerApproved') return 'ReferrerApproved';
|
||||||
|
|
||||||
return 'NotStarted';
|
return 'NotStarted';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Citizenship] Error fetching KYC status:', error);
|
console.error('[Citizenship] Error fetching status:', error);
|
||||||
return 'NotStarted';
|
return 'NotStarted';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Blockchain Submission ───────────────────────────────────────────
|
// ── Blockchain Submission ───────────────────────────────────────────
|
||||||
|
|
||||||
export async function submitCitizenshipApplication(
|
export async function applyCitizenship(
|
||||||
api: ApiPromise,
|
api: ApiPromise,
|
||||||
keypair: KeyringPair,
|
keypair: KeyringPair,
|
||||||
name: string,
|
identityHash: string,
|
||||||
email: string,
|
referrerAddress: string | null = null
|
||||||
ipfsCid: string,
|
|
||||||
notes: string = 'Citizenship application via Telegram MiniApp'
|
|
||||||
): Promise<CitizenshipResult> {
|
): Promise<CitizenshipResult> {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const tx = api.tx as 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' };
|
return { success: false, error: 'Identity KYC pallet not available' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const address = keypair.address;
|
const result = await new Promise<CitizenshipResult>((resolve) => {
|
||||||
|
|
||||||
// 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) => {
|
|
||||||
tx.identityKyc
|
tx.identityKyc
|
||||||
.setIdentity(name, email)
|
.applyForCitizenship(identityHash, referrerAddress)
|
||||||
.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)
|
|
||||||
.signAndSend(
|
.signAndSend(
|
||||||
keypair,
|
keypair,
|
||||||
{ nonce: -1 },
|
{ nonce: -1 },
|
||||||
@@ -229,25 +156,25 @@ export async function submitCitizenshipApplication(
|
|||||||
}) => {
|
}) => {
|
||||||
if (status.isInBlock || status.isFinalized) {
|
if (status.isInBlock || status.isFinalized) {
|
||||||
if (dispatchError) {
|
if (dispatchError) {
|
||||||
let errorMessage = 'KYC application failed';
|
let errorMessage = 'Citizenship application failed';
|
||||||
if (dispatchError.isModule) {
|
if (dispatchError.isModule) {
|
||||||
const decoded = api.registry.findMetaError(
|
const decoded = api.registry.findMetaError(
|
||||||
dispatchError.asModule as Parameters<typeof api.registry.findMetaError>[0]
|
dispatchError.asModule as Parameters<typeof api.registry.findMetaError>[0]
|
||||||
);
|
);
|
||||||
errorMessage = `${decoded.section}.${decoded.name}`;
|
errorMessage = `${decoded.section}.${decoded.name}`;
|
||||||
}
|
}
|
||||||
resolve({ success: false, error: errorMessage });
|
resolve({ success: false, error: errorMessage, identityHash });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const blockHash = status.asFinalized?.toString() || status.asInBlock?.toString();
|
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) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
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 { t, lang, setLang } = useTranslation();
|
||||||
const [showLangMenu, setShowLangMenu] = useState(false);
|
const [showLangMenu, setShowLangMenu] = useState(false);
|
||||||
const [citizenshipData, setCitizenshipData] = useState<CitizenshipData | null>(null);
|
const [citizenshipData, setCitizenshipData] = useState<CitizenshipData | null>(null);
|
||||||
|
const [identityHash, setIdentityHash] = useState<string>('');
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Determine initial step based on wallet state
|
// Determine initial step based on wallet state
|
||||||
@@ -87,7 +88,8 @@ export function CitizenPage() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Processing result
|
// Processing result
|
||||||
const handleSuccess = useCallback(() => {
|
const handleSuccess = useCallback((hash: string) => {
|
||||||
|
setIdentityHash(hash);
|
||||||
setStep('success');
|
setStep('success');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -186,7 +188,11 @@ export function CitizenPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{step === 'success' && address && (
|
{step === 'success' && address && (
|
||||||
<CitizenSuccess address={address} onOpenApp={handleOpenApp} />
|
<CitizenSuccess
|
||||||
|
address={address}
|
||||||
|
identityHash={identityHash}
|
||||||
|
onOpenApp={handleOpenApp}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.193",
|
"version": "1.0.194",
|
||||||
"buildTime": "2026-02-14T18:08:28.138Z",
|
"buildTime": "2026-02-14T19:00:32.936Z",
|
||||||
"buildNumber": 1771092508139
|
"buildNumber": 1771095632937
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user