mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-08-10 07:43:33 +00:00
fix contract, add emojis to user display
This commit is contained in:
parent
7a9e8ccd78
commit
539e118e30
@ -31,6 +31,7 @@ interface IZKVerifier {
|
|||||||
contract ZKPassportVerifier {
|
contract ZKPassportVerifier {
|
||||||
// Structure to store user verification data
|
// Structure to store user verification data
|
||||||
struct Verification {
|
struct Verification {
|
||||||
|
bool initialized; // Whether the user has set their verification data
|
||||||
bool adult; // Whether user is 18+
|
bool adult; // Whether user is 18+
|
||||||
string country; // User's country of nationality
|
string country; // User's country of nationality
|
||||||
string gender; // User's gender
|
string gender; // User's gender
|
||||||
@ -39,8 +40,9 @@ contract ZKPassportVerifier {
|
|||||||
// Mapping from user address to their verification data
|
// Mapping from user address to their verification data
|
||||||
mapping(address => Verification) public verifications;
|
mapping(address => Verification) public verifications;
|
||||||
|
|
||||||
// Mapping to track used unique identifiers
|
// Mapping to track used unique identifiers per wallet
|
||||||
mapping(bytes32 => bool) public usedUniqueIdentifiers;
|
// This prevents cross-wallet correlation while maintaining Sybil resistance
|
||||||
|
mapping(address => mapping(bytes32 => bool)) public walletUniqueIdentifiers;
|
||||||
|
|
||||||
// Address of the ZKVerifier contract
|
// Address of the ZKVerifier contract
|
||||||
IZKVerifier public zkVerifier;
|
IZKVerifier public zkVerifier;
|
||||||
@ -81,14 +83,47 @@ contract ZKPassportVerifier {
|
|||||||
// Revert if proof is not valid
|
// Revert if proof is not valid
|
||||||
require(verified, "Proof verification failed");
|
require(verified, "Proof verification failed");
|
||||||
|
|
||||||
// Check if this unique identifier has already been used
|
// Always enforce wallet-scoped uniqueness
|
||||||
require(!usedUniqueIdentifiers[uniqueIdentifier], "Unique identifier already used");
|
require(!walletUniqueIdentifiers[msg.sender][uniqueIdentifier], "Unique identifier already used by this wallet");
|
||||||
|
|
||||||
// Mark this unique identifier as used
|
// Mark this unique identifier as used by this wallet
|
||||||
usedUniqueIdentifiers[uniqueIdentifier] = true;
|
walletUniqueIdentifiers[msg.sender][uniqueIdentifier] = true;
|
||||||
|
|
||||||
// Store the verification data
|
// Store the verification data
|
||||||
verifications[msg.sender] = Verification({
|
verifications[msg.sender] = Verification({
|
||||||
|
initialized: true,
|
||||||
|
adult: adult,
|
||||||
|
country: country,
|
||||||
|
gender: gender
|
||||||
|
});
|
||||||
|
|
||||||
|
emit VerificationUpdated(msg.sender, adult, country, gender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice Update verification data without requiring a new proof
|
||||||
|
* @param adult Whether the sender is 18+
|
||||||
|
* @param country The sender's country of nationality
|
||||||
|
* @param gender The sender's gender
|
||||||
|
*/
|
||||||
|
function updateVerification(
|
||||||
|
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");
|
||||||
|
|
||||||
|
// Always enforce wallet-scoped uniqueness
|
||||||
|
require(walletUniqueIdentifiers[msg.sender][uniqueIdentifier], "Unique identifier already used by this wallet");
|
||||||
|
|
||||||
|
// Update the verification data
|
||||||
|
verifications[msg.sender] = Verification({
|
||||||
|
initialized: true,
|
||||||
adult: adult,
|
adult: adult,
|
||||||
country: country,
|
country: country,
|
||||||
gender: gender
|
gender: gender
|
||||||
@ -106,9 +141,9 @@ contract ZKPassportVerifier {
|
|||||||
*/
|
*/
|
||||||
function getVerification(address user)
|
function getVerification(address user)
|
||||||
external view
|
external view
|
||||||
returns (bool, string memory, string memory)
|
returns (bool, bool, string memory, string memory)
|
||||||
{
|
{
|
||||||
Verification storage verification = verifications[user];
|
Verification storage verification = verifications[user];
|
||||||
return (verification.adult, verification.country, verification.gender);
|
return (verification.initialized, verification.adult, verification.country, verification.gender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13,7 +13,7 @@ export function AuthorDisplay({
|
|||||||
className = '',
|
className = '',
|
||||||
showBadge = true,
|
showBadge = true,
|
||||||
}: AuthorDisplayProps) {
|
}: AuthorDisplayProps) {
|
||||||
const { displayName, callSign, ensName, ordinalDetails } =
|
const { displayName, callSign, ensName, ordinalDetails, countryFlag, ageEmoji, genderEmoji } =
|
||||||
useUserDisplay(address);
|
useUserDisplay(address);
|
||||||
|
|
||||||
// Only show a badge if the author has ENS, Ordinal, or Call Sign
|
// Only show a badge if the author has ENS, Ordinal, or Call Sign
|
||||||
@ -21,7 +21,12 @@ export function AuthorDisplay({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center gap-1.5 ${className}`}>
|
<div className={`flex items-center gap-1.5 ${className}`}>
|
||||||
<span className="text-xs text-muted-foreground">{displayName}</span>
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{displayName}
|
||||||
|
{countryFlag && <span className="ml-1">{countryFlag}</span>}
|
||||||
|
{ageEmoji && <span className="ml-1">{ageEmoji}</span>}
|
||||||
|
{genderEmoji && <span className="ml-1">{genderEmoji}</span>}
|
||||||
|
</span>
|
||||||
|
|
||||||
{shouldShowBadge && (
|
{shouldShowBadge && (
|
||||||
<Badge
|
<Badge
|
||||||
|
|||||||
@ -12,6 +12,9 @@ export interface UserDisplayInfo {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
identityProviders: IdentityProvider[] | null;
|
identityProviders: IdentityProvider[] | null;
|
||||||
|
countryFlag: string | null;
|
||||||
|
ageEmoji: string | null;
|
||||||
|
genderEmoji: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,6 +32,9 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
|
|||||||
isLoading: true,
|
isLoading: true,
|
||||||
error: null,
|
error: null,
|
||||||
identityProviders: null,
|
identityProviders: null,
|
||||||
|
countryFlag: null,
|
||||||
|
ageEmoji: null,
|
||||||
|
genderEmoji: null,
|
||||||
});
|
});
|
||||||
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
||||||
|
|
||||||
@ -85,6 +91,10 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
|
|||||||
displayPreference: null,
|
displayPreference: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
identityProviders: null,
|
||||||
|
countryFlag: null,
|
||||||
|
ageEmoji: null,
|
||||||
|
genderEmoji: null,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -95,6 +105,32 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
|
|||||||
if (identity) {
|
if (identity) {
|
||||||
const displayName = userIdentityService.getDisplayName(address);
|
const displayName = userIdentityService.getDisplayName(address);
|
||||||
|
|
||||||
|
// Extract country, adult, and gender claims from identity providers
|
||||||
|
let countryFlag = null;
|
||||||
|
let ageEmoji = null;
|
||||||
|
let genderEmoji = null;
|
||||||
|
|
||||||
|
if (identity.identityProviders) {
|
||||||
|
for (const provider of identity.identityProviders) {
|
||||||
|
if (provider.type === 'zkpassport') {
|
||||||
|
for (const claim of provider.claims) {
|
||||||
|
if (claim.key === 'country' && claim.verified && typeof claim.value === 'string') {
|
||||||
|
// Convert country code to flag emoji
|
||||||
|
countryFlag = getCountryFlag(claim.value);
|
||||||
|
}
|
||||||
|
if (claim.key === 'adult' && claim.verified && typeof claim.value === 'boolean') {
|
||||||
|
ageEmoji = claim.value ? '🧓' : '👶';
|
||||||
|
}
|
||||||
|
if (claim.key === 'gender' && claim.verified && typeof claim.value === 'string') {
|
||||||
|
// Map gender to emoji
|
||||||
|
genderEmoji = claim.value.toLowerCase() === 'm' ? '♂️' :
|
||||||
|
claim.value.toLowerCase() === 'f' ? '♀️' : '⚧️';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setDisplayInfo({
|
setDisplayInfo({
|
||||||
displayName,
|
displayName,
|
||||||
callSign: identity.callSign || null,
|
callSign: identity.callSign || null,
|
||||||
@ -107,6 +143,9 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
identityProviders: identity.identityProviders || null,
|
identityProviders: identity.identityProviders || null,
|
||||||
|
countryFlag,
|
||||||
|
ageEmoji,
|
||||||
|
genderEmoji,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setDisplayInfo({
|
setDisplayInfo({
|
||||||
@ -121,6 +160,9 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
identityProviders: null,
|
identityProviders: null,
|
||||||
|
countryFlag: null,
|
||||||
|
ageEmoji: null,
|
||||||
|
genderEmoji: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -137,6 +179,10 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
|
|||||||
displayPreference: null,
|
displayPreference: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
identityProviders: null,
|
||||||
|
countryFlag: null,
|
||||||
|
ageEmoji: null,
|
||||||
|
genderEmoji: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -161,6 +207,17 @@ export function useEnhancedUserDisplay(address: string): UserDisplayInfo {
|
|||||||
verificationInfo,
|
verificationInfo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Helper function to convert country code to flag emoji
|
||||||
|
function getCountryFlag(countryCode: string): string {
|
||||||
|
// Convert country code to flag emoji using regional indicator symbols
|
||||||
|
// For example, 'US' -> '🇺🇸'
|
||||||
|
return countryCode
|
||||||
|
.toUpperCase()
|
||||||
|
.replace(/./g, char =>
|
||||||
|
String.fromCodePoint(127397 + char.charCodeAt(0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return displayInfo;
|
return displayInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { MessageService } from './MessageService';
|
|||||||
import messageManager from '@/lib/waku';
|
import messageManager from '@/lib/waku';
|
||||||
import { localDatabase } from '@/lib/database/LocalDatabase';
|
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||||
import { WalletManager } from '@/lib/wallet';
|
import { WalletManager } from '@/lib/wallet';
|
||||||
|
import { getVerification } from '../zkPassport';
|
||||||
|
|
||||||
export interface UserIdentity {
|
export interface UserIdentity {
|
||||||
address: string;
|
address: string;
|
||||||
@ -393,6 +394,7 @@ export class UserIdentityService {
|
|||||||
displayPreference,
|
displayPreference,
|
||||||
lastUpdated: timestamp,
|
lastUpdated: timestamp,
|
||||||
verificationStatus: EVerificationStatus.WALLET_UNCONNECTED,
|
verificationStatus: EVerificationStatus.WALLET_UNCONNECTED,
|
||||||
|
identityProviders: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -711,4 +713,187 @@ export class UserIdentityService {
|
|||||||
|
|
||||||
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ZKPassport claims with multi-layer cache support
|
||||||
|
*/
|
||||||
|
async getZKPassportClaims(address: string): Promise<Claim[] | null> {
|
||||||
|
// 1. Check in-memory cache first (fastest)
|
||||||
|
if (this.userIdentityCache[address]?.identityProviders) {
|
||||||
|
const zkPassportProvider = this.userIdentityCache[address].identityProviders?.find(p => p.type === 'zkpassport');
|
||||||
|
if (zkPassportProvider) {
|
||||||
|
return zkPassportProvider.claims;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check LocalDatabase persistence (warm start)
|
||||||
|
const persisted = localDatabase.cache.userIdentities[address];
|
||||||
|
if (persisted?.identityProviders) {
|
||||||
|
const zkPassportProvider = persisted.identityProviders.find(p => p.type === 'zkpassport');
|
||||||
|
if (zkPassportProvider) {
|
||||||
|
// Restore in memory cache
|
||||||
|
this.userIdentityCache[address] = {
|
||||||
|
...persisted,
|
||||||
|
identityProviders: persisted.identityProviders
|
||||||
|
};
|
||||||
|
return zkPassportProvider.claims;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Check Waku message cache (network cache)
|
||||||
|
const cacheServiceData = messageManager.messageCache.userIdentities[address];
|
||||||
|
if (cacheServiceData?.identityProviders) {
|
||||||
|
const zkPassportProvider = cacheServiceData.identityProviders.find(p => p.type === 'zkpassport');
|
||||||
|
if (zkPassportProvider) {
|
||||||
|
// Store in internal cache for future use
|
||||||
|
this.userIdentityCache[address] = {
|
||||||
|
...cacheServiceData,
|
||||||
|
identityProviders: cacheServiceData.identityProviders
|
||||||
|
};
|
||||||
|
return zkPassportProvider.claims;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Fetch from blockchain (source of truth)
|
||||||
|
return this.resolveZKPassportClaims(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force fresh resolution of ZKPassport claims (bypass caches)
|
||||||
|
*/
|
||||||
|
async getZKPassportClaimsFresh(address: string): Promise<Claim[] | null> {
|
||||||
|
return this.resolveZKPassportClaims(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve ZKPassport claims from blockchain contract with TTL caching
|
||||||
|
*/
|
||||||
|
private async resolveZKPassportClaims(address: string): Promise<Claim[] | null> {
|
||||||
|
try {
|
||||||
|
// Check if we have a recent cached version
|
||||||
|
const cached = this.userIdentityCache[address]?.identityProviders?.find(p => p.type === 'zkpassport');
|
||||||
|
const now = Date.now();
|
||||||
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes TTL
|
||||||
|
|
||||||
|
// If we have a recent cache and it's still valid, return it
|
||||||
|
if (cached && cached.verifiedAt && (now - cached.verifiedAt) < CACHE_TTL) {
|
||||||
|
return cached.claims;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update identity with claims (this updates all cache layers)
|
||||||
|
this.updateUserIdentityWithZKPassportClaims(address, claims);
|
||||||
|
|
||||||
|
return claims;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to resolve ZKPassport claims:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user identity with ZKPassport claims (updates all cache layers)
|
||||||
|
*/
|
||||||
|
updateUserIdentityWithZKPassportClaims(address: string, claims: Claim[]): void {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
// Initialize identity if it doesn't exist
|
||||||
|
if (!this.userIdentityCache[address]) {
|
||||||
|
this.userIdentityCache[address] = {
|
||||||
|
ensName: undefined,
|
||||||
|
ordinalDetails: undefined,
|
||||||
|
callSign: undefined,
|
||||||
|
displayPreference: EDisplayPreference.WALLET_ADDRESS,
|
||||||
|
lastUpdated: timestamp,
|
||||||
|
verificationStatus: EVerificationStatus.WALLET_CONNECTED,
|
||||||
|
identityProviders: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or update ZKPassport provider with TTL
|
||||||
|
const zkPassportProvider: IdentityProvider = {
|
||||||
|
type: 'zkpassport',
|
||||||
|
verifiedAt: timestamp,
|
||||||
|
expiresAt: timestamp + 24 * 60 * 60 * 1000, // 24 hours validity
|
||||||
|
claims: [...claims]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace or add provider
|
||||||
|
const existingProviderIndex = this.userIdentityCache[address].identityProviders!.findIndex(
|
||||||
|
p => p.type === 'zkpassport'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingProviderIndex >= 0) {
|
||||||
|
this.userIdentityCache[address].identityProviders![existingProviderIndex] = zkPassportProvider;
|
||||||
|
} else {
|
||||||
|
this.userIdentityCache[address].identityProviders!.push(zkPassportProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last updated timestamp
|
||||||
|
this.userIdentityCache[address].lastUpdated = timestamp;
|
||||||
|
|
||||||
|
// Update verification status if user has verified claims
|
||||||
|
if (claims.some(c => c.verified)) {
|
||||||
|
this.userIdentityCache[address].verificationStatus = EVerificationStatus.ENS_ORDINAL_VERIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify listeners that the user identity has been updated
|
||||||
|
this.notifyRefreshListeners(address);
|
||||||
|
|
||||||
|
// Persist to IndexedDB
|
||||||
|
localDatabase.upsertUserIdentity(address, {
|
||||||
|
identityProviders: this.userIdentityCache[address].identityProviders,
|
||||||
|
lastUpdated: timestamp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch resolve multiple user identities for post processing
|
||||||
|
*/
|
||||||
|
async resolveMultipleUsers(addresses: string[]): Promise<Map<string, UserIdentity>> {
|
||||||
|
const result = new Map<string, UserIdentity>();
|
||||||
|
|
||||||
|
// Process all resolutions in parallel
|
||||||
|
await Promise.all(
|
||||||
|
addresses.map(async (address) => {
|
||||||
|
const identity = await this.getUserIdentity(address);
|
||||||
|
if (identity) {
|
||||||
|
result.set(address, identity);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { BrowserProvider, Contract } from 'ethers';
|
import { BrowserProvider, Contract } from 'ethers';
|
||||||
import { config } from '@/lib/wallet/config';
|
import { config } from '@/lib/wallet/config';
|
||||||
// Contract configuration - these should be moved to environment variables in production
|
// Contract configuration - these should be moved to environment variables in production
|
||||||
export const CONTRACT_ADDRESS = "0xaA649E71A6d7347742e3642AAe209d580913f021"; // Hardhat default deploy address
|
export const CONTRACT_ADDRESS = "0x1753dbd9f4bb6473ee2905b2db183760B95be475"; // Hardhat default deploy address
|
||||||
const CONTRACT_ABI = [
|
const CONTRACT_ABI = [
|
||||||
{
|
{
|
||||||
"inputs": [
|
"inputs": [
|
||||||
@ -57,17 +57,22 @@ const CONTRACT_ABI = [
|
|||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"internalType": "bool",
|
"internalType": "bool",
|
||||||
"name": "",
|
"name": "initialized",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"internalType": "bool",
|
||||||
|
"name": "adult",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"internalType": "string",
|
"internalType": "string",
|
||||||
"name": "",
|
"name": "country",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"internalType": "string",
|
"internalType": "string",
|
||||||
"name": "",
|
"name": "gender",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -151,21 +156,77 @@ const CONTRACT_ABI = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
|
||||||
"internalType": "bytes32",
|
|
||||||
"name": "",
|
|
||||||
"type": "bytes32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "usedUniqueIdentifiers",
|
|
||||||
"outputs": [
|
|
||||||
{
|
{
|
||||||
"internalType": "bool",
|
"internalType": "bool",
|
||||||
"name": "",
|
"name": "adult",
|
||||||
"type": "bool"
|
"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"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateMutability": "view",
|
"name": "updateVerification",
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -197,6 +258,30 @@ const CONTRACT_ABI = [
|
|||||||
"stateMutability": "view",
|
"stateMutability": "view",
|
||||||
"type": "function"
|
"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": [],
|
"inputs": [],
|
||||||
"name": "zkVerifier",
|
"name": "zkVerifier",
|
||||||
@ -449,6 +534,7 @@ export const submitVerificationToContract = async (
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const signer = await getSigner();
|
const signer = await getSigner();
|
||||||
|
console.log(signer)
|
||||||
if (!signer) {
|
if (!signer) {
|
||||||
setProgress('Failed to connect to wallet');
|
setProgress('Failed to connect to wallet');
|
||||||
return null;
|
return null;
|
||||||
@ -496,13 +582,130 @@ export const getVerification = async (address: string): Promise<{ adult: boolean
|
|||||||
try {
|
try {
|
||||||
const provider = new BrowserProvider(window.ethereum as any);
|
const provider = new BrowserProvider(window.ethereum as any);
|
||||||
const contract = new Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider) as unknown as {
|
const contract = new Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider) as unknown as {
|
||||||
getVerification: (address: string) => Promise<[boolean, string, string]>;
|
getVerification: (address: string) => Promise<[boolean, boolean, string, string]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [adult, country, gender] = await contract.getVerification(address);
|
const [initialized, adult, country, gender] = await contract.getVerification(address);
|
||||||
|
if (!initialized) {
|
||||||
|
return null; // No verification data set for this user
|
||||||
|
}
|
||||||
return { adult, country, gender };
|
return { adult, country, gender };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching verification data:', error);
|
console.error('Error fetching verification data:', error);
|
||||||
return null;
|
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<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();
|
||||||
|
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<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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<Claim[] | null> => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { ContractVerificationButton } from '@/components/ui/contract-verification-button';
|
import { ContractVerificationButton } from '@/components/ui/contract-verification-button';
|
||||||
import { CONTRACT_ADDRESS, getVerification, submitVerificationToContract } from '@/lib/zkPassport';
|
import { CONTRACT_ADDRESS, getVerification, submitVerificationToContract, updateVerification } from '@/lib/zkPassport';
|
||||||
import { verifyWithZKPassport, ZKPassportVerificationOptions } from '@/lib/zkPassport';
|
import { verifyWithZKPassport, ZKPassportVerificationOptions } from '@/lib/zkPassport';
|
||||||
import { UserIdentityService } from '@/lib/services/UserIdentityService';
|
import { UserIdentityService } from '@/lib/services/UserIdentityService';
|
||||||
import { useForum } from '@/contexts/useForum';
|
import { useForum } from '@/contexts/useForum';
|
||||||
@ -795,22 +795,43 @@ export default function ProfilePage() {
|
|||||||
const countryClaim = userInfo.identityProviders?.flatMap(p => p.claims).find(c => c.key === 'country');
|
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 genderClaim = userInfo.identityProviders?.flatMap(p => p.claims).find(c => c.key === 'gender');
|
||||||
|
|
||||||
|
// Check if verification already exists
|
||||||
const tx = await submitVerificationToContract(
|
const existingVerification = await getVerification(address!);
|
||||||
|
const hasExistingVerification = existingVerification && (
|
||||||
|
existingVerification.adult !== undefined ||
|
||||||
|
existingVerification.country !== '' ||
|
||||||
|
existingVerification.gender !== ''
|
||||||
|
);
|
||||||
|
console.log('Existing verification:', existingVerification, hasExistingVerification);
|
||||||
|
|
||||||
|
let tx;
|
||||||
|
if (hasExistingVerification) {
|
||||||
|
// Use updateVerification for existing verifications
|
||||||
|
tx = await updateVerification(
|
||||||
adulthoodClaim?.value as boolean || false,
|
adulthoodClaim?.value as boolean || false,
|
||||||
countryClaim?.value as string || '',
|
countryClaim?.value as string || '',
|
||||||
genderClaim?.value as string || '',
|
genderClaim?.value as string || '',
|
||||||
proof,
|
proof,
|
||||||
setProgress
|
setProgress
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
if (tx) {
|
// Use submitVerificationToContract for new verifications
|
||||||
toast({
|
tx = await submitVerificationToContract(
|
||||||
title: 'Verification Submitted',
|
adulthoodClaim?.value as boolean || false,
|
||||||
description: 'Your verification has been submitted to the contract.',
|
countryClaim?.value as string || '',
|
||||||
});
|
genderClaim?.value as string || '',
|
||||||
}
|
proof,
|
||||||
return tx;
|
setProgress
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
toast({
|
||||||
|
title: 'Verification Submitted',
|
||||||
|
description: 'Your verification has been submitted to the contract.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return tx;
|
||||||
}}
|
}}
|
||||||
isVerifying={isVerifying}
|
isVerifying={isVerifying}
|
||||||
verificationType="adult"
|
verificationType="adult"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user