From 7a9e8ccd78c28df825e5d6a25e399df10a2bd8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pavl=C3=ADn?= Date: Fri, 19 Sep 2025 15:13:41 +0200 Subject: [PATCH] verify and store on chain --- contracts/ZKPassportVerifier.sol | 114 ++++ hardhat.config.js | 25 + .../ui/contract-verification-button.tsx | 92 +++ src/lib/zkPassport.ts | 596 ++++++++++-------- src/pages/ProfilePage.tsx | 429 +++++++------ 5 files changed, 805 insertions(+), 451 deletions(-) create mode 100644 contracts/ZKPassportVerifier.sol create mode 100644 hardhat.config.js create mode 100644 src/components/ui/contract-verification-button.tsx diff --git a/contracts/ZKPassportVerifier.sol b/contracts/ZKPassportVerifier.sol new file mode 100644 index 0000000..f1979e0 --- /dev/null +++ b/contracts/ZKPassportVerifier.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +struct ProofVerificationParams { + bytes32 vkeyHash; + bytes proof; + bytes32[] publicInputs; + bytes committedInputs; + uint256[] committedInputCounts; + uint256 validityPeriodInSeconds; + string domain; + string scope; + bool devMode; +} + +interface IZKPassportVerifier { + // Verify the proof + function verifyProof(ProofVerificationParams calldata params) external returns (bool verified, bytes32 uniqueIdentifier); + + function verifyScopes(bytes32[] calldata publicInputs, string calldata domain, string calldata scope) external view returns (bool); +} + +interface IZKVerifier { + function verifyProof(ProofVerificationParams calldata params) external returns (bool verified, bytes32 uniqueIdentifier); +} + +/** + * @title ZKPassportVerifier + * @notice Simplified contract to store verification outputs for adult status, country, and gender + */ +contract ZKPassportVerifier { + // Structure to store user verification data + struct Verification { + bool adult; // Whether user is 18+ + string country; // User's country of nationality + string gender; // User's gender + } + + // Mapping from user address to their verification data + mapping(address => Verification) public verifications; + + // Mapping to track used unique identifiers + mapping(bytes32 => bool) public usedUniqueIdentifiers; + + // Address of the ZKVerifier contract + IZKVerifier public zkVerifier; + + /** + * @notice Constructor that sets the ZKVerifier contract address + * @param _zkVerifier Address of the ZKVerifier contract + */ + constructor(address _zkVerifier) { + require(_zkVerifier != address(0), "Invalid ZKVerifier address"); + zkVerifier = IZKVerifier(_zkVerifier); + } + + // Event emitted when verification data is updated + event VerificationUpdated( + address indexed user, + bool adult, + string country, + string gender + ); + + /** + * @notice Update verification data for the sender + * @param adult Whether the sender is 18+ + * @param country The sender's country of nationality + * @param gender The sender's gender + * @param params Proof verification parameters + */ + function setVerification( + bool adult, + string calldata country, + string calldata gender, + ProofVerificationParams calldata params + ) external { + // Verify the proof first using the ZKVerifier contract + (bool verified, bytes32 uniqueIdentifier) = zkVerifier.verifyProof(params); + + // Revert if proof is not valid + require(verified, "Proof verification failed"); + + // Check if this unique identifier has already been used + require(!usedUniqueIdentifiers[uniqueIdentifier], "Unique identifier already used"); + + // Mark this unique identifier as used + usedUniqueIdentifiers[uniqueIdentifier] = true; + + // Store the verification data + verifications[msg.sender] = Verification({ + adult: adult, + country: country, + gender: gender + }); + + emit VerificationUpdated(msg.sender, adult, country, gender); + } + + /** + * @notice Get verification data for a user + * @param user The address of the user + * @return adult Whether the user is 18+ + * @return country The user's country of nationality + * @return gender The user's gender + */ + function getVerification(address user) + external view + returns (bool, string memory, string memory) + { + Verification storage verification = verifications[user]; + return (verification.adult, verification.country, verification.gender); + } +} \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js new file mode 100644 index 0000000..576d102 --- /dev/null +++ b/hardhat.config.js @@ -0,0 +1,25 @@ +import "@nomicfoundation/hardhat-toolbox"; +import dotenv from "dotenv"; +dotenv.config(); + +const config = { + solidity: "0.8.20", + networks: { + sepolia: { + url: "https://eth-sepolia.api.onfinality.io/public", + chainId: 11155111, + accounts: [`${process.env.PRIVATE_KEY}`] + } + }, + etherscan: { + apiKey: process.env.ETHERSCAN_API_KEY + }, + paths: { + sources: "./contracts", + tests: "./test", + cache: "./cache", + artifacts: "./artifacts" + }, +}; + +export default config; \ No newline at end of file diff --git a/src/components/ui/contract-verification-button.tsx b/src/components/ui/contract-verification-button.tsx new file mode 100644 index 0000000..7e004bf --- /dev/null +++ b/src/components/ui/contract-verification-button.tsx @@ -0,0 +1,92 @@ +import { Button } from '@/components/ui/button'; +import { Loader2, Send } from 'lucide-react'; +import { useState } from 'react'; + +interface ContractVerificationButtonProps { + onVerify: () => Promise; + isVerifying: boolean; + verificationType: 'adult' | 'country' | 'gender'; +} + +export function ContractVerificationButton({ + onVerify, + isVerifying, +}: ContractVerificationButtonProps) { + const [txStatus, setTxStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle'); + const [txHash, setTxHash] = useState(null); + + const handleVerify = async () => { + setTxStatus('pending'); + setTxHash(null); + try { + const hash = await onVerify(); + console.log(hash) + if (hash) { + console.log("Setting TX hash") + setTxHash(hash); + setTxStatus('success'); + } else { + setTxStatus('error'); + } + setTimeout(() => { + setTxStatus('idle'); + setTxHash(null); + }, 60000); + } catch (error) { + setTxStatus('error'); + setTimeout(() => setTxStatus('idle'), 3000); + } + }; + + const getButtonText = () => { + if (txStatus === 'pending') return 'Recording...'; + if (txStatus === 'success') return txHash ? 'Recorded on chain!' : 'Success!'; + if (txStatus === 'error') return 'Error'; + return `Record Verified Claims`; + }; + + return ( +
+ + + {txStatus === 'success' && txHash && ( +
+ TX: + {txHash.slice(0, 20)}...{txHash.slice(-18)} + +
+ )} +
+ ); +} diff --git a/src/lib/zkPassport.ts b/src/lib/zkPassport.ts index c89ae3a..44ab718 100644 --- a/src/lib/zkPassport.ts +++ b/src/lib/zkPassport.ts @@ -1,63 +1,222 @@ import { BrowserProvider, Contract } from 'ethers'; import { config } from '@/lib/wallet/config'; // Contract configuration - these should be moved to environment variables in production -const CONTRACT_ADDRESS = "0x971B0B5de23C63160602a3fbe68e96166Fc11D1A"; // Hardhat default deploy address +export const CONTRACT_ADDRESS = "0xaA649E71A6d7347742e3642AAe209d580913f021"; // Hardhat default deploy address const CONTRACT_ABI = [ - { - "inputs": [ - { - "internalType": "bool", - "name": "adult", - "type": "bool" - }, - { - "internalType": "string", - "name": "country", - "type": "string" - }, - { - "internalType": "string", - "name": "gender", - "type": "string" - } - ], - "name": "setVerification", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "user", - "type": "address" - } - ], - "name": "getVerification", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "string", - "name": "", - "type": "string" - }, - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } -]; -import { EU_COUNTRIES, ZKPassport } from '@zkpassport/sdk'; + { + "inputs": [ + { + "internalType": "address", + "name": "_zkVerifier", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "adult", + "type": "bool" + }, + { + "indexed": false, + "internalType": "string", + "name": "country", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "gender", + "type": "string" + } + ], + "name": "VerificationUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getVerification", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "string", + "name": "", + "type": "string" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "adult", + "type": "bool" + }, + { + "internalType": "string", + "name": "country", + "type": "string" + }, + { + "internalType": "string", + "name": "gender", + "type": "string" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "vkeyHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + }, + { + "internalType": "bytes32[]", + "name": "publicInputs", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "committedInputs", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "committedInputCounts", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "validityPeriodInSeconds", + "type": "uint256" + }, + { + "internalType": "string", + "name": "domain", + "type": "string" + }, + { + "internalType": "string", + "name": "scope", + "type": "string" + }, + { + "internalType": "bool", + "name": "devMode", + "type": "bool" + } + ], + "internalType": "struct ProofVerificationParams", + "name": "params", + "type": "tuple" + } + ], + "name": "setVerification", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "usedUniqueIdentifiers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "verifications", + "outputs": [ + { + "internalType": "bool", + "name": "adult", + "type": "bool" + }, + { + "internalType": "string", + "name": "country", + "type": "string" + }, + { + "internalType": "string", + "name": "gender", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "zkVerifier", + "outputs": [ + { + "internalType": "contract IZKVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ]; + +const devMode = true; +const proofMode = "compressed-evm"; //"fast" +import { EU_COUNTRIES, ProofResult, SolidityVerifierParameters, ZKPassport } from '@zkpassport/sdk'; import { Claim } from '@/types/identity'; +import { V } from 'vitest/dist/chunks/environment.d.cL3nLXbE.js'; export interface ZKPassportVerificationResult { verified: boolean; @@ -66,112 +225,55 @@ export interface ZKPassportVerificationResult { } /** - * Verify that the user is an adult (18+ years old) + * Options for ZKPassport verification */ -export const verifyAdulthood = async (setProgress: (status:string) => void, setUrl: (url:string) => void): Promise => { - const zkPassport = new ZKPassport(); - - const queryBuilder = await zkPassport.request({ - name: "OpChan", - logo: "https://zkpassport.id/logo.png", - purpose: "Prove you are 18+ years old", - scope: "adult", - }); - - const { - url, - onResult, - onGeneratingProof, - onError, - onProofGenerated, - onReject, - onRequestReceived - } = queryBuilder.gte("age", 18).done(); - - setUrl(url); - - return new Promise((resolve, reject) => { - try { - console.log("Starting adulthood verification with zkPassport"); - onRequestReceived(() => { - setProgress("Request received, preparing for age verification"); - console.log("Request received, preparing for age verification"); - }); - - onGeneratingProof(() => { - setProgress("Generating cryptographic proof of age"); - console.log("Generating cryptographic proof of age"); - }); - - onProofGenerated(() => { - setProgress("Age proof generated successfully"); - console.log("Age proof generated successfully"); - }); - - onReject(() => { - setProgress("Age verification request was rejected"); - console.log("Age verification request was rejected by the user"); - resolve(null); - }); - - onError((error) => { - setProgress(`Age verification error: ${error}`); - console.error("Age verification error", error); - resolve(null); - }); - - onResult(({ verified, uniqueIdentifier, result }) => { - try { - console.log("Adulthood verification result", verified, result); - if (verified) { - const claims: Claim[] = [ - { - key: "adult", - value: result.age?.gte?.result, - verified: true - } - ]; - - resolve({ - verified: true, - uniqueIdentifier: uniqueIdentifier || '', - claims - }); - console.log("User is verified as adult", claims); - } else { - setProgress("Age verification failed"); - resolve(null); - } - } catch (error) { - console.error("Adulthood verification result processing error", error); - setProgress(`Adulthood verification result processing error: ${error}`); - resolve(null); - } finally { - setUrl(''); - setProgress(''); - } - }); - } catch (error) { - console.error("Adulthood verification exception", error); - setProgress(`Adulthood verification exception: ${error}`); - reject(error); - } - }); +export interface ZKPassportVerificationOptions { + verifyAdulthood: boolean; + verifyCountry: boolean; + verifyGender: boolean; } /** - * Disclose the user's country of nationality + * Perform ZKPassport verification with selective disclosure based on provided options */ -export const discloseCountry = async (setProgress: (status:string) => void, setUrl: (url:string) => void): Promise => { +export const verifyWithZKPassport = async ( + options: ZKPassportVerificationOptions, + setProgress: (status: string) => void, + setUrl: (url: string) => void, + setProof: (proof: ProofResult) => void, +): Promise => { const zkPassport = new ZKPassport(); + // Build purpose message based on selected verifications + const selectedVerifications = []; + if (options.verifyAdulthood) selectedVerifications.push("being 18+"); + if (options.verifyCountry) selectedVerifications.push("your country of nationality"); + if (options.verifyGender) selectedVerifications.push("your gender"); + + const purpose = selectedVerifications.length > 0 + ? `Verify ${selectedVerifications.join(", ")}` + : "Verify your identity"; + const queryBuilder = await zkPassport.request({ name: "OpChan", logo: "https://zkpassport.id/logo.png", - purpose: "Verify your country of nationality", - scope: "country", + purpose, + scope: "identity", + devMode: devMode, + mode: proofMode, }); + // Conditionally add verification requirements based on options + if (options.verifyAdulthood) { + queryBuilder.gte("age", 18); + } + if (options.verifyCountry) { + queryBuilder.disclose("nationality"); + } + if (options.verifyGender) { + queryBuilder.disclose("gender"); + } + const { url, onResult, @@ -180,159 +282,84 @@ export const discloseCountry = async (setProgress: (status:string) => void, setU onProofGenerated, onReject, onRequestReceived - } = queryBuilder.disclose("nationality").done(); + } = queryBuilder.done(); setUrl(url); return new Promise((resolve, reject) => { try { - console.log("Starting country disclosure with zkPassport"); + console.log("Starting ZKPassport verification with options:", options); onRequestReceived(() => { - setProgress("Request received, preparing for country disclosure"); - console.log("Request received, preparing for country disclosure"); + setProgress("Request received, preparing for verification"); + console.log("Request received, preparing for verification"); }); onGeneratingProof(() => { - setProgress("Generating cryptographic proof of country"); - console.log("Generating cryptographic proof of country"); + setProgress("Generating cryptographic proof"); + console.log("Generating cryptographic proof"); }); - onProofGenerated(() => { - setProgress("Country proof generated successfully"); - console.log("Country proof generated successfully"); + onProofGenerated((proof: ProofResult) => { + setProgress("Proof generated successfully"); + console.log("Proof generated successfully"); + setProof(proof); }); onReject(() => { - setProgress("Country disclosure request was rejected"); - console.log("Country disclosure request was rejected by the user"); + setProgress("Verification request was rejected"); + console.log("Verification request was rejected by the user"); resolve(null); }); onError((error) => { - setProgress(`Country disclosure error: ${error}`); - console.error("Country disclosure error", error); + setProgress(`Verification error: ${error}`); + console.error("Verification error", error); resolve(null); }); onResult(({ verified, uniqueIdentifier, result }) => { try { - console.log("Country disclosure result", verified, result); - if (verified && result.nationality?.disclose?.result) { - const claims: Claim[] = [ - { + console.log("ZKPassport verification result", verified, result); + if (verified) { + const claims: Claim[] = []; + + if (options.verifyAdulthood && result.age?.gte?.result) { + claims.push({ + key: "adult", + value: result.age.gte.result, + verified: true + }); + } + + if (options.verifyCountry && result.nationality?.disclose?.result) { + claims.push({ key: "country", value: result.nationality.disclose.result, verified: true - } - ]; - - resolve({ - verified: true, - uniqueIdentifier: uniqueIdentifier || '', - claims - }); - console.log("User country disclosed", claims); - } else { - setProgress("Country disclosure failed"); - resolve(null); - } - } catch (error) { - console.error("Country disclosure result processing error", error); - setProgress(`Country disclosure result processing error: ${error}`); - resolve(null); - } finally { - setUrl(''); - setProgress(''); - } - }); - } catch (error) { - console.error("Country disclosure exception", error); - setProgress(`Country disclosure exception: ${error}`); - reject(error); - } - }); -} + }); + } -/** - * Disclose the user's gender - */ -export const discloseGender = async (setProgress: (status:string) => void, setUrl: (url:string) => void): Promise => { - const zkPassport = new ZKPassport(); - - const queryBuilder = await zkPassport.request({ - name: "OpChan", - logo: "https://zkpassport.id/logo.png", - purpose: "Verify your gender", - scope: "gender", - }); - - const { - url, - onResult, - onGeneratingProof, - onError, - onProofGenerated, - onReject, - onRequestReceived - } = queryBuilder.disclose("gender").done(); - - setUrl(url); - - return new Promise((resolve, reject) => { - try { - console.log("Starting gender disclosure with zkPassport"); - onRequestReceived(() => { - setProgress("Request received, preparing for gender disclosure"); - console.log("Request received, preparing for gender disclosure"); - }); - - onGeneratingProof(() => { - setProgress("Generating cryptographic proof of gender"); - console.log("Generating cryptographic proof of gender"); - }); - - onProofGenerated(() => { - setProgress("Gender proof generated successfully"); - console.log("Gender proof generated successfully"); - }); - - onReject(() => { - setProgress("Gender disclosure request was rejected"); - console.log("Gender disclosure request was rejected by the user"); - resolve(null); - }); - - onError((error) => { - setProgress(`Gender disclosure error: ${error}`); - console.error("Gender disclosure error", error); - resolve(null); - }); - - onResult(({ verified, uniqueIdentifier, result }) => { - try { - console.log("Gender disclosure result", verified, result); - if (verified && result.gender?.disclose?.result) { - const claims: Claim[] = [ - { + if (options.verifyGender && result.gender?.disclose?.result) { + claims.push({ key: "gender", value: result.gender.disclose.result, verified: true - } - ]; - + }); + } + resolve({ verified: true, uniqueIdentifier: uniqueIdentifier || '', claims }); - console.log("User gender disclosed", claims); + console.log("User verified with claims", claims); } else { - setProgress("Gender disclosure failed"); + setProgress("Verification failed"); resolve(null); } } catch (error) { - console.error("Gender disclosure result processing error", error); - setProgress(`Gender disclosure result processing error: ${error}`); + console.error("Verification result processing error", error); + setProgress(`Verification result processing error: ${error}`); resolve(null); } finally { setUrl(''); @@ -340,8 +367,8 @@ export const discloseGender = async (setProgress: (status:string) => void, setUr } }); } catch (error) { - console.error("Gender disclosure exception", error); - setProgress(`Gender disclosure exception: ${error}`); + console.error("ZKPassport verification exception", error); + setProgress(`ZKPassport verification exception: ${error}`); reject(error); } }); @@ -392,7 +419,7 @@ const getSigner = async (): Promise => { }; /** - * Submit verification data to the blockchain contract + * Record verified claims on the blockchain * @param adult Whether the user is 18+ * @param country The user's country of nationality * @param gender The user's gender @@ -403,9 +430,22 @@ export const submitVerificationToContract = async ( adult: boolean, country: string, gender: string, + proof: ProofResult, setProgress: (status: string) => void ): Promise => { setProgress('Initializing blockchain connection...'); + const zkPassport = new ZKPassport(); + + + // Get verification parameters + const verifierParams = zkPassport.getSolidityVerifierParameters({ + proof: proof, + // Use the same scope as the one you specified with the request function + scope: "identity", + // Enable dev mode if you want to use mock passports, otherwise keep it false + devMode: true, + }); + try { const signer = await getSigner(); @@ -420,11 +460,11 @@ export const submitVerificationToContract = async ( return null; } const contract = new Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer) as unknown as { - setVerification: (adult: boolean, country: string, gender: string) => Promise; + setVerification: (adult: boolean, country: string, gender: string, verifierParams: SolidityVerifierParameters) => Promise; }; setProgress('Submitting verification data to blockchain...'); - const tx = await contract.setVerification(adult, country, gender); + const tx = await contract.setVerification(adult, country, gender, verifierParams); setProgress('Waiting for blockchain confirmation...'); const receipt = await tx.wait(); @@ -445,4 +485,24 @@ export const submitVerificationToContract = async ( } return null; } +} + +/** + * Fetch verification data for a user from the ZKPassport verifier contract + * @param address The wallet address of the user to fetch verification data for + * @returns Promise resolving to an object containing adult status, country, and gender, or null if not found + */ +export const getVerification = async (address: string): Promise<{ adult: boolean; country: string; gender: string } | null> => { + try { + const provider = new BrowserProvider(window.ethereum as any); + const contract = new Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider) as unknown as { + getVerification: (address: string) => Promise<[boolean, string, string]>; + }; + + const [adult, country, gender] = await contract.getVerification(address); + return { adult, country, gender }; + } catch (error) { + console.error('Error fetching verification data:', error); + return null; + } }; \ No newline at end of file diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index b2af42d..bdd01c8 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -8,8 +8,8 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { ContractVerificationButton } from '@/components/ui/contract-verification-button'; -import { submitVerificationToContract } from '@/lib/zkPassport'; -import { verifyAdulthood, discloseCountry, discloseGender } from '@/lib/zkPassport'; +import { CONTRACT_ADDRESS, getVerification, submitVerificationToContract } from '@/lib/zkPassport'; +import { verifyWithZKPassport, ZKPassportVerificationOptions } from '@/lib/zkPassport'; import { UserIdentityService } from '@/lib/services/UserIdentityService'; import { useForum } from '@/contexts/useForum'; import { QRCodeCanvas } from 'qrcode.react'; @@ -40,6 +40,7 @@ import { } from 'lucide-react'; import { EDisplayPreference, EVerificationStatus } from '@/types/identity'; import { useToast } from '@/hooks/use-toast'; +import { ProofResult } from '@zkpassport/sdk'; export default function ProfilePage() { const { updateProfile } = useUserActions(); @@ -73,7 +74,48 @@ export default function ProfilePage() { const [progress, setProgress] = useState(''); const [isVerifying, setIsVerifying] = useState(false); const [verificationType, setVerificationType] = useState<'adult' | 'country' | 'gender' | null>(null); + const [proof, setProof] = useState(null); + const [verificationOptions, setVerificationOptions] = useState({ + verifyAdulthood: false, + verifyCountry: false, + verifyGender: false + }); const { userIdentityService } = useForum(); + + // Load verification data from contract on component mount + useEffect(() => { + const loadVerificationData = async () => { + if (address) { + const verificationData = await getVerification(address); + if (verificationData && userIdentityService) { + // Update user identity with data from contract + if (verificationData.adult) { + userIdentityService.updateUserIdentityWithAdulthood( + address, + '', // uniqueIdentifier not available from contract + verificationData.adult + ); + } + if (verificationData.country) { + userIdentityService.updateUserIdentityWithCountry( + address, + '', // uniqueIdentifier not available from contract + verificationData.country + ); + } + if (verificationData.gender) { + userIdentityService.updateUserIdentityWithGender( + address, + '', // uniqueIdentifier not available from contract + verificationData.gender + ); + } + } + } + }; + + loadVerificationData(); + }, [address, userIdentityService]); // Initialize and update local state when user data changes useEffect(() => { @@ -599,190 +641,211 @@ export default function ProfilePage() { {/* Identity Verification Section */} -
-

Identity Verification

-

- Verify your identity to enhance your profile with verifiable claims. -

- - {/* Verification Buttons */} -
-
- - {userInfo.identityProviders && userInfo.identityProviders.some(p => p.type === 'zkpassport') && ( - { - const adulthoodClaim = userInfo.identityProviders?.flatMap(p => p.claims).find(c => c.key === 'adult'); - const countryClaim = userInfo.identityProviders?.flatMap(p => p.claims).find(c => c.key === 'country'); - const genderClaim = userInfo.identityProviders?.flatMap(p => p.claims).find(c => c.key === 'gender'); - - if (adulthoodClaim) { - await submitVerificationToContract( - adulthoodClaim.value as boolean, - countryClaim?.value as string || '', - genderClaim?.value as string || '', - setProgress - ); - } - }} - isVerifying={isVerifying} - verificationType="adult" - /> - )} -
- - - - -
- - {/* Verification Status */} - {userInfo.identityProviders && userInfo.identityProviders.some(p => p.type === 'zkpassport') && ( -
-

- Verified Claims -

-
- {userInfo.identityProviders.flatMap(p => p.claims).map((claim, index) => ( -
-
- - - {claim.key} - +
+ + + +
+ + Identity Verification +
+
+
+ +
+ {/* Left Column: Verification Status */} +
+

+ Verify your identity to enhance your profile with verifiable claims. +

+ + {/* Verification Status */} + {userInfo.identityProviders && userInfo.identityProviders.some(p => p.type === 'zkpassport') && ( +
+

+ Verified (and recorded) Claims +

+
+ {userInfo.identityProviders.flatMap(p => p.claims).map((claim, index) => ( +
+
+ + + {claim.key} + +
+ + {typeof claim.value === 'boolean' ? (claim.value ? 'Yes' : 'No') : claim.value} + +
+ ))} +
+
+ )} +
+ + {/* Right Column: Verification Controls */} +
+ {/* Verification Toggles */} +
+
+ setVerificationOptions({...verificationOptions, verifyAdulthood: e.target.checked})} + disabled={isVerifying} + className="w-4 h-4 text-cyber-accent bg-cyber-dark border-cyber-muted/30 rounded focus:ring-cyber-accent focus:ring-2" + /> + +
+ +
+ setVerificationOptions({...verificationOptions, verifyCountry: e.target.checked})} + disabled={isVerifying} + className="w-4 h-4 text-cyber-accent bg-cyber-dark border-cyber-muted/30 rounded focus:ring-cyber-accent focus:ring-2" + /> + +
+ +
+ setVerificationOptions({...verificationOptions, verifyGender: e.target.checked})} + disabled={isVerifying} + className="w-4 h-4 text-cyber-accent bg-cyber-dark border-cyber-muted/30 rounded focus:ring-cyber-accent focus:ring-2" + /> +
- - {typeof claim.value === 'boolean' ? (claim.value ? 'Yes' : 'No') : claim.value} -
- ))} + + {/* Verification Button */} +
+ + + {/* Contract Verification Button - only show if any claims exist */} + {userInfo.identityProviders && proof && userInfo.identityProviders.some(p => p.type === 'zkpassport') && ( + { + const adulthoodClaim = userInfo.identityProviders?.flatMap(p => p.claims).find(c => c.key === 'adult'); + const countryClaim = userInfo.identityProviders?.flatMap(p => p.claims).find(c => c.key === 'country'); + const genderClaim = userInfo.identityProviders?.flatMap(p => p.claims).find(c => c.key === 'gender'); + + + const tx = await submitVerificationToContract( + adulthoodClaim?.value as boolean || false, + countryClaim?.value as string || '', + genderClaim?.value as string || '', + proof, + setProgress + ); + + if (tx) { + toast({ + title: 'Verification Submitted', + description: 'Your verification has been submitted to the contract.', + }); + } + return tx; + }} + isVerifying={isVerifying} + verificationType="adult" + /> + )} +
+ + {/* Progress and QR Code */} + {progress && ( +

{progress}

+ )} + {url && ( +
+ +
+ +
+

+ Scan this QR code to open the verification page on your mobile device +

+
+ )} +
-
- )} - - {/* Progress and QR Code */} - {progress && ( -

{progress}

- )} - {url && ( -
- -
- -
-

- Scan this QR code to open the verification page on your mobile device -

-
- )} + +