import { BrowserProvider, Contract } from 'ethers'; import { config } from '@/lib/wallet/config'; // Contract configuration - these should be moved to environment variables in production export const CONTRACT_ADDRESS = "0x1753dbd9f4bb6473ee2905b2db183760B95be475"; // Hardhat default deploy address const CONTRACT_ABI = [ { "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": "initialized", "type": "bool" }, { "internalType": "bool", "name": "adult", "type": "bool" }, { "internalType": "string", "name": "country", "type": "string" }, { "internalType": "string", "name": "gender", "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": "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": "updateVerification", "outputs": [], "stateMutability": "nonpayable", "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": [ { "internalType": "address", "name": "", "type": "address" }, { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "name": "walletUniqueIdentifiers", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "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; uniqueIdentifier: string; claims: Claim[]; } /** * Options for ZKPassport verification */ export interface ZKPassportVerificationOptions { verifyAdulthood: boolean; verifyCountry: boolean; verifyGender: boolean; } /** * Perform ZKPassport verification with selective disclosure based on provided options */ 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, 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, onGeneratingProof, onError, onProofGenerated, onReject, onRequestReceived } = queryBuilder.done(); setUrl(url); return new Promise((resolve, reject) => { try { console.log("Starting ZKPassport verification with options:", options); onRequestReceived(() => { setProgress("Request received, preparing for verification"); console.log("Request received, preparing for verification"); }); onGeneratingProof(() => { setProgress("Generating cryptographic proof"); console.log("Generating cryptographic proof"); }); onProofGenerated((proof: ProofResult) => { setProgress("Proof generated successfully"); console.log("Proof generated successfully"); setProof(proof); }); onReject(() => { setProgress("Verification request was rejected"); console.log("Verification request was rejected by the user"); resolve(null); }); onError((error) => { setProgress(`Verification error: ${error}`); console.error("Verification error", error); resolve(null); }); onResult(({ verified, uniqueIdentifier, result }) => { try { 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 }); } 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 verified with claims", claims); } else { setProgress("Verification failed"); resolve(null); } } catch (error) { console.error("Verification result processing error", error); setProgress(`Verification result processing error: ${error}`); resolve(null); } finally { setUrl(''); setProgress(''); } }); } catch (error) { console.error("ZKPassport verification exception", error); setProgress(`ZKPassport verification exception: ${error}`); reject(error); } }); } /** * Get a signer from the current wallet connection * @returns Promise resolving to an ethers Signer or null if unavailable */ const getSigner = async (): Promise => { try { // Get the provider from wagmi config const provider = new BrowserProvider(window.ethereum as any, {name: "sepolia", chainId: 11155111}); // Request account access await provider.send('eth_requestAccounts', []); // Explicitly switch to Sepolia network try { await provider.send('wallet_switchEthereumChain', [ { chainId: '0x' + (11155111).toString(16) } ]); } catch (switchError: any) { // If the network isn't added, add it if (switchError.code === 4902) { await provider.send('wallet_addEthereumChain', [ { chainId: '0x' + (11155111).toString(16), chainName: 'Sepolia Test Network', nativeCurrency: { name: 'Ethereum', symbol: 'ETH', decimals: 18 }, rpcUrls: ['https://eth-sepolia.api.onfinality.io/public'], blockExplorerUrls: ['https://sepolia.etherscan.io'] } ]); } else { throw switchError; } } return await provider.getSigner(); } catch (error) { console.error('Failed to get signer:', error); return null; } }; /** * 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 * @param setProgress Function to update progress status * @returns Promise resolving to transaction hash on success, null on failure */ 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(); console.log(signer) if (!signer) { setProgress('Failed to connect to wallet'); return null; } setProgress('Connecting to contract...'); if (!signer) { setProgress('Failed to get signer'); return null; } const contract = new Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer) as unknown as { setVerification: (adult: boolean, country: string, gender: string, verifierParams: SolidityVerifierParameters) => Promise; }; setProgress('Submitting verification data to blockchain...'); const tx = await contract.setVerification(adult, country, gender, verifierParams); setProgress('Waiting for blockchain confirmation...'); const receipt = await tx.wait(); if (receipt && receipt.hash) { setProgress('Verification successfully recorded on blockchain!'); return receipt.hash; } else { setProgress('Transaction completed but no hash received'); return null; } } catch (error: any) { console.error('Error submitting verification:', error); if (error.message) { setProgress(`Error: ${error.message}`); } else { setProgress('Failed to submit verification to contract'); } 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, boolean, string, string]>; }; const [initialized, adult, country, gender] = await contract.getVerification(address); if (!initialized) { return null; // No verification data set for this user } return { adult, country, gender }; } catch (error) { console.error('Error fetching verification data:', error); return null; } }; /** * Update verification data for a user without requiring a new proof * @param adult Whether the user is 18+ * @param country The user's country of nationality * @param gender The user's gender * @param setProgress Function to update progress status * @returns Promise resolving to transaction hash on success, null on failure */ export const updateVerification = 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(); if (!signer) { setProgress('Failed to connect to wallet'); return null; } setProgress('Connecting to contract...'); if (!signer) { setProgress('Failed to get signer'); return null; } const contract = new Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer) as unknown as { updateVerification: (adult: boolean, country: string, gender: string, verifierParams: SolidityVerifierParameters) => Promise; }; setProgress('Updating verification data on blockchain...'); const tx = await contract.updateVerification(adult, country, gender, verifierParams); setProgress('Waiting for blockchain confirmation...'); const receipt = await tx.wait(); if (receipt && receipt.hash) { setProgress('Verification successfully updated on blockchain!'); return receipt.hash; } else { setProgress('Transaction completed but no hash received'); return null; } } catch (error: any) { console.error('Error updating verification:', error); if (error.message) { setProgress(`Error: ${error.message}`); } else { setProgress('Failed to update verification on contract'); } return null; } }; /** * Fetch ZKPassport claims for a user with proper typing * @param address The wallet address of the user to fetch claims for * @returns Promise resolving to claims array or null if not found */ export const fetchZKPassportClaims = async (address: string): Promise => { try { const claimsData = await getVerification(address); if (!claimsData) return null; const claims: Claim[] = []; // Process adult claim if (claimsData.adult !== undefined) { claims.push({ key: 'adult', value: claimsData.adult, verified: true }); } // Process country claim if (claimsData.country) { claims.push({ key: 'country', value: claimsData.country, verified: true }); } // Process gender claim if (claimsData.gender) { claims.push({ key: 'gender', value: claimsData.gender, verified: true }); } return claims.length > 0 ? claims : null; } catch (error) { console.error('Error fetching ZKPassport claims:', error); return null; } };