mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 21:03:09 +00:00
verify and store on chain
This commit is contained in:
parent
0d357ad64a
commit
7a9e8ccd78
114
contracts/ZKPassportVerifier.sol
Normal file
114
contracts/ZKPassportVerifier.sol
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
25
hardhat.config.js
Normal file
25
hardhat.config.js
Normal file
@ -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;
|
||||
92
src/components/ui/contract-verification-button.tsx
Normal file
92
src/components/ui/contract-verification-button.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Loader2, Send } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface ContractVerificationButtonProps {
|
||||
onVerify: () => Promise<string | null>;
|
||||
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<string | null>(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 (
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
onClick={handleVerify}
|
||||
disabled={isVerifying || txStatus === 'pending'}
|
||||
className="w-full bg-cyber-accent hover:bg-cyber-accent/80 text-black font-mono"
|
||||
>
|
||||
{txStatus === 'pending' ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
{getButtonText()}
|
||||
</>
|
||||
) : txStatus === 'success' ? (
|
||||
<>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
{getButtonText()}
|
||||
</>
|
||||
) : txStatus === 'error' ? (
|
||||
<>
|
||||
<span className="mr-2">⚠</span>
|
||||
{getButtonText()}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
{getButtonText()}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{txStatus === 'success' && txHash && (
|
||||
<div className="text-center text-xs text-cyber-neutral bg-cyber-dark/30 p-2 rounded border border-cyber-muted/30 font-mono">
|
||||
TX: <a
|
||||
href={`https://sepolia.etherscan.io/tx/${txHash}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-cyber-accent hover:underline"
|
||||
>
|
||||
{txHash.slice(0, 20)}...{txHash.slice(-18)}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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<ZKPassportVerificationResult | null> => {
|
||||
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<ZKPassportVerificationResult | null> => {
|
||||
export const verifyWithZKPassport = async (
|
||||
options: ZKPassportVerificationOptions,
|
||||
setProgress: (status: string) => void,
|
||||
setUrl: (url: string) => void,
|
||||
setProof: (proof: ProofResult) => void,
|
||||
): Promise<ZKPassportVerificationResult | null> => {
|
||||
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<ZKPassportVerificationResult | null> => {
|
||||
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<any | null> => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 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<string | null> => {
|
||||
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<any>;
|
||||
setVerification: (adult: boolean, country: string, gender: string, verifierParams: SolidityVerifierParameters) => Promise<any>;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
@ -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<string>('');
|
||||
const [isVerifying, setIsVerifying] = useState<boolean>(false);
|
||||
const [verificationType, setVerificationType] = useState<'adult' | 'country' | 'gender' | null>(null);
|
||||
const [proof, setProof] = useState<ProofResult | null>(null);
|
||||
const [verificationOptions, setVerificationOptions] = useState<ZKPassportVerificationOptions>({
|
||||
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() {
|
||||
</main>
|
||||
|
||||
{/* Identity Verification Section */}
|
||||
<div className="max-w-md mx-auto mt-8 p-6 bg-cyber-muted/20 border border-cyber-muted/30 rounded-lg">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Identity Verification</h2>
|
||||
<p className="text-cyber-neutral mb-4">
|
||||
Verify your identity to enhance your profile with verifiable claims.
|
||||
</p>
|
||||
|
||||
{/* Verification Buttons */}
|
||||
<div className="space-y-3 mb-6">
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setVerificationType('adult');
|
||||
setIsVerifying(true);
|
||||
try {
|
||||
const result = await verifyAdulthood(setProgress, setUrl);
|
||||
if (result && result.claims && result.claims.length > 0 && userIdentityService) {
|
||||
if (result.uniqueIdentifier && result.claims[0]?.value !== undefined) {
|
||||
userIdentityService.updateUserIdentityWithAdulthood(
|
||||
address!,
|
||||
result.uniqueIdentifier,
|
||||
result.claims[0].value
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Adulthood verification failed:', error);
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
setVerificationType(null);
|
||||
}
|
||||
}}
|
||||
disabled={isVerifying}
|
||||
className="w-full bg-cyber-accent hover:bg-cyber-accent/80 text-black font-mono"
|
||||
>
|
||||
{isVerifying && verificationType === 'adult' ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Verifying...
|
||||
</>
|
||||
) : (
|
||||
'Verify Adulthood (18+)'
|
||||
)}
|
||||
</Button>
|
||||
{userInfo.identityProviders && userInfo.identityProviders.some(p => p.type === 'zkpassport') && (
|
||||
<ContractVerificationButton
|
||||
onVerify={async () => {
|
||||
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"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setVerificationType('country');
|
||||
setIsVerifying(true);
|
||||
try {
|
||||
const result = await discloseCountry(setProgress, setUrl);
|
||||
if (result && result.claims && result.claims.length > 0 && userIdentityService) {
|
||||
if (result.uniqueIdentifier && result.claims[0]?.value !== undefined) {
|
||||
userIdentityService.updateUserIdentityWithCountry(
|
||||
address!,
|
||||
result.uniqueIdentifier,
|
||||
result.claims[0].value
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Country disclosure failed:', error);
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
setVerificationType(null);
|
||||
}
|
||||
}}
|
||||
disabled={isVerifying}
|
||||
className="w-full bg-cyber-accent hover:bg-cyber-accent/80 text-black font-mono"
|
||||
>
|
||||
{isVerifying && verificationType === 'country' ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Verifying...
|
||||
</>
|
||||
) : (
|
||||
'Disclose Country'
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setVerificationType('gender');
|
||||
setIsVerifying(true);
|
||||
try {
|
||||
const result = await discloseGender(setProgress, setUrl);
|
||||
if (result && result.claims && result.claims.length > 0 && userIdentityService) {
|
||||
if (result.uniqueIdentifier && result.claims[0]?.value !== undefined) {
|
||||
userIdentityService.updateUserIdentityWithGender(
|
||||
address!,
|
||||
result.uniqueIdentifier,
|
||||
result.claims[0].value
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Gender disclosure failed:', error);
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
setVerificationType(null);
|
||||
}
|
||||
}}
|
||||
disabled={isVerifying}
|
||||
className="w-full bg-cyber-accent hover:bg-cyber-accent/80 text-black font-mono"
|
||||
>
|
||||
{isVerifying && verificationType === 'gender' ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Verifying...
|
||||
</>
|
||||
) : (
|
||||
'Disclose Gender'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Verification Status */}
|
||||
{userInfo.identityProviders && userInfo.identityProviders.some(p => p.type === 'zkpassport') && (
|
||||
<div className="space-y-3 mb-6">
|
||||
<h3 className="text-sm font-medium text-cyber-neutral uppercase tracking-wide">
|
||||
Verified Claims
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{userInfo.identityProviders.flatMap(p => p.claims).map((claim, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 bg-cyber-dark/50 border border-cyber-muted/30 rounded-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
<span className="text-sm text-cyber-light capitalize">
|
||||
{claim.key}
|
||||
</span>
|
||||
<div className="max-w-4xl mx-auto mt-8 mb-8">
|
||||
<Card className="content-card">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between text-white">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="h-5 w-5 text-cyber-accent" />
|
||||
Identity Verification
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{/* Left Column: Verification Status */}
|
||||
<div className="space-y-6">
|
||||
<p className="text-cyber-neutral">
|
||||
Verify your identity to enhance your profile with verifiable claims.
|
||||
</p>
|
||||
|
||||
{/* Verification Status */}
|
||||
{userInfo.identityProviders && userInfo.identityProviders.some(p => p.type === 'zkpassport') && (
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-medium text-cyber-neutral uppercase tracking-wide">
|
||||
Verified (and <a className="text-cyber-accent hover:underline" href={`https://sepolia.etherscan.io/address/${CONTRACT_ADDRESS}`} target='_blank'>recorded</a>) Claims
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{userInfo.identityProviders.flatMap(p => p.claims).map((claim, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 bg-cyber-dark/50 border border-cyber-muted/30 rounded-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
<span className="text-sm text-cyber-light capitalize">
|
||||
{claim.key}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm text-cyber-accent font-mono">
|
||||
{typeof claim.value === 'boolean' ? (claim.value ? 'Yes' : 'No') : claim.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Column: Verification Controls */}
|
||||
<div className="space-y-6">
|
||||
{/* Verification Toggles */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="verifyAdulthood"
|
||||
checked={verificationOptions.verifyAdulthood}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<label htmlFor="verifyAdulthood" className="text-sm text-cyber-neutral">
|
||||
Verify Adulthood (18+)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="verifyCountry"
|
||||
checked={verificationOptions.verifyCountry}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<label htmlFor="verifyCountry" className="text-sm text-cyber-neutral">
|
||||
Disclose Country
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="verifyGender"
|
||||
checked={verificationOptions.verifyGender}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<label htmlFor="verifyGender" className="text-sm text-cyber-neutral">
|
||||
Disclose Gender
|
||||
</label>
|
||||
</div>
|
||||
<span className="text-sm text-cyber-accent font-mono">
|
||||
{typeof claim.value === 'boolean' ? (claim.value ? 'Yes' : 'No') : claim.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Verification Button */}
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
// Set verification type based on selected options
|
||||
if (verificationOptions.verifyAdulthood) setVerificationType('adult');
|
||||
else if (verificationOptions.verifyCountry) setVerificationType('country');
|
||||
else if (verificationOptions.verifyGender) setVerificationType('gender');
|
||||
|
||||
setIsVerifying(true);
|
||||
try {
|
||||
const result = await verifyWithZKPassport(verificationOptions, setProgress, setUrl, setProof);
|
||||
if (result && result.claims && result.claims.length > 0 && userIdentityService) {
|
||||
// Update all verified claims
|
||||
result.claims.forEach(claim => {
|
||||
if (claim.key === 'adult' && claim.value !== undefined) {
|
||||
userIdentityService.updateUserIdentityWithAdulthood(
|
||||
address!,
|
||||
result.uniqueIdentifier,
|
||||
claim.value
|
||||
);
|
||||
} else if (claim.key === 'country' && claim.value !== undefined) {
|
||||
userIdentityService.updateUserIdentityWithCountry(
|
||||
address!,
|
||||
result.uniqueIdentifier,
|
||||
claim.value
|
||||
);
|
||||
} else if (claim.key === 'gender' && claim.value !== undefined) {
|
||||
userIdentityService.updateUserIdentityWithGender(
|
||||
address!,
|
||||
result.uniqueIdentifier,
|
||||
claim.value
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Verification failed:', error);
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
setVerificationType(null);
|
||||
}
|
||||
}}
|
||||
disabled={isVerifying || (!verificationOptions.verifyAdulthood && !verificationOptions.verifyCountry && !verificationOptions.verifyGender)}
|
||||
className="w-full bg-cyber-accent hover:bg-cyber-accent/80 text-black font-mono"
|
||||
>
|
||||
{isVerifying ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Verifying...
|
||||
</>
|
||||
) : (
|
||||
'Verify Selected Claims'
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Contract Verification Button - only show if any claims exist */}
|
||||
{userInfo.identityProviders && proof && userInfo.identityProviders.some(p => p.type === 'zkpassport') && (
|
||||
<ContractVerificationButton
|
||||
onVerify={async () => {
|
||||
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"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Progress and QR Code */}
|
||||
{progress && (
|
||||
<p className="mt-4 text-sm text-cyber-neutral">{progress}</p>
|
||||
)}
|
||||
{url && (
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="text-center">
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-cyber-accent hover:underline font-medium"
|
||||
>
|
||||
Open verification in new tab
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex justify-center p-4 bg-white rounded-lg shadow-lg inline-block">
|
||||
<QRCodeCanvas value={url} size={200} level="H" includeMargin={true} />
|
||||
</div>
|
||||
<p className="text-xs text-cyber-neutral text-center">
|
||||
Scan this QR code to open the verification page on your mobile device
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress and QR Code */}
|
||||
{progress && (
|
||||
<p className="mt-4 text-sm text-cyber-neutral">{progress}</p>
|
||||
)}
|
||||
{url && (
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="text-center">
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-cyber-accent hover:underline font-medium"
|
||||
>
|
||||
Open verification in new tab
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex justify-center p-4 bg-white rounded-lg shadow-lg inline-block">
|
||||
<QRCodeCanvas value={url} size={200} level="H" includeMargin={true} />
|
||||
</div>
|
||||
<p className="text-xs text-cyber-neutral text-center">
|
||||
Scan this QR code to open the verification page on your mobile device
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<footer className="page-footer">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user