mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-03 05:13:09 +00:00
feat: anonymous interactions
This commit is contained in:
parent
dd13ef6b2e
commit
88012154d3
@ -44,8 +44,10 @@ const FeedSidebar: React.FC = () => {
|
||||
|
||||
// Ethereum wallet with ENS
|
||||
if (currentUser.walletType === 'ethereum') {
|
||||
if (currentUser.ensName && verificationStatus === 'verified-owner') {
|
||||
if (currentUser.ensName && (verificationStatus === 'verified-owner' || currentUser.ensOwnership)) {
|
||||
return <Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">✓ Owns ENS: {currentUser.ensName}</Badge>;
|
||||
} else if (verificationStatus === 'verified-basic') {
|
||||
return <Badge className="bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">✓ Connected Wallet</Badge>;
|
||||
} else {
|
||||
return <Badge variant="outline">Read-only (No ENS detected)</Badge>;
|
||||
}
|
||||
@ -53,8 +55,10 @@ const FeedSidebar: React.FC = () => {
|
||||
|
||||
// Bitcoin wallet with Ordinal
|
||||
if (currentUser.walletType === 'bitcoin') {
|
||||
if (verificationStatus === 'verified-owner') {
|
||||
if (verificationStatus === 'verified-owner' || currentUser.ordinalOwnership) {
|
||||
return <Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">✓ Owns Ordinal</Badge>;
|
||||
} else if (verificationStatus === 'verified-basic') {
|
||||
return <Badge className="bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">✓ Connected Wallet</Badge>;
|
||||
} else {
|
||||
return <Badge variant="outline">Read-only (No Ordinal detected)</Badge>;
|
||||
}
|
||||
@ -62,6 +66,8 @@ const FeedSidebar: React.FC = () => {
|
||||
|
||||
// Fallback cases
|
||||
switch (verificationStatus) {
|
||||
case 'verified-basic':
|
||||
return <Badge className="bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">✓ Connected Wallet</Badge>;
|
||||
case 'verified-none':
|
||||
return <Badge variant="outline">Read Only</Badge>;
|
||||
case 'verifying':
|
||||
|
||||
@ -18,8 +18,7 @@ const Header = () => {
|
||||
verifyOwnership,
|
||||
delegateKey,
|
||||
isDelegationValid,
|
||||
delegationTimeRemaining,
|
||||
isWalletAvailable
|
||||
delegationTimeRemaining
|
||||
} = useAuth();
|
||||
const { isNetworkConnected, isRefreshing } = useForum();
|
||||
const location = useLocation();
|
||||
@ -85,8 +84,10 @@ const Header = () => {
|
||||
return 'Verifying...';
|
||||
case 'verified-none':
|
||||
return 'Read-Only Access';
|
||||
case 'verified-owner':
|
||||
case 'verified-basic':
|
||||
return isDelegationValid() ? 'Full Access' : 'Setup Key';
|
||||
case 'verified-owner':
|
||||
return isDelegationValid() ? 'Premium Access' : 'Setup Key';
|
||||
default:
|
||||
return 'Setup Account';
|
||||
}
|
||||
@ -100,6 +101,8 @@ const Header = () => {
|
||||
return <RefreshCw className="w-3 h-3 animate-spin" />;
|
||||
case 'verified-none':
|
||||
return <CircleSlash className="w-3 h-3" />;
|
||||
case 'verified-basic':
|
||||
return isDelegationValid() ? <CheckCircle className="w-3 h-3" /> : <Key className="w-3 h-3" />;
|
||||
case 'verified-owner':
|
||||
return isDelegationValid() ? <CheckCircle className="w-3 h-3" /> : <Key className="w-3 h-3" />;
|
||||
default:
|
||||
@ -115,6 +118,8 @@ const Header = () => {
|
||||
return 'outline';
|
||||
case 'verified-none':
|
||||
return 'secondary';
|
||||
case 'verified-basic':
|
||||
return isDelegationValid() ? 'default' : 'outline';
|
||||
case 'verified-owner':
|
||||
return isDelegationValid() ? 'default' : 'outline';
|
||||
default:
|
||||
|
||||
@ -84,12 +84,12 @@ const PostDetail = () => {
|
||||
};
|
||||
|
||||
const handleVotePost = async (isUpvote: boolean) => {
|
||||
if (verificationStatus !== 'verified-owner') return;
|
||||
if (verificationStatus !== 'verified-owner' && verificationStatus !== 'verified-basic' && !currentUser?.ensOwnership && !currentUser?.ordinalOwnership) return;
|
||||
await votePost(post.id, isUpvote);
|
||||
};
|
||||
|
||||
const handleVoteComment = async (commentId: string, isUpvote: boolean) => {
|
||||
if (verificationStatus !== 'verified-owner') return;
|
||||
if (verificationStatus !== 'verified-owner' && verificationStatus !== 'verified-basic' && !currentUser?.ensOwnership && !currentUser?.ordinalOwnership) return;
|
||||
await voteComment(commentId, isUpvote);
|
||||
};
|
||||
|
||||
@ -185,7 +185,7 @@ const PostDetail = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{verificationStatus === 'verified-owner' ? (
|
||||
{(verificationStatus === 'verified-owner' || verificationStatus === 'verified-basic' || currentUser?.ensOwnership || currentUser?.ordinalOwnership) ? (
|
||||
<div className="mb-8">
|
||||
<form onSubmit={handleCreateComment}>
|
||||
<div className="flex gap-2">
|
||||
@ -219,7 +219,7 @@ const PostDetail = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-8 p-3 border border-muted rounded-sm bg-secondary/30 text-center">
|
||||
<p className="text-sm mb-2">Connect wallet and verify Ordinal ownership to comment</p>
|
||||
<p className="text-sm mb-2">Connect wallet and verify ownership to comment</p>
|
||||
<Button asChild size="sm">
|
||||
<Link to="/">Go to Home</Link>
|
||||
</Button>
|
||||
|
||||
@ -4,6 +4,7 @@ import { Shield, Crown } from 'lucide-react';
|
||||
import { UserVerificationStatus } from '@/lib/forum/types';
|
||||
import { getEnsName } from '@wagmi/core';
|
||||
import { config } from '@/lib/identity/wallets/appkit';
|
||||
import { OrdinalAPI } from '@/lib/identity/ordinal';
|
||||
|
||||
interface AuthorDisplayProps {
|
||||
address: string;
|
||||
@ -20,20 +21,52 @@ export function AuthorDisplay({
|
||||
}: AuthorDisplayProps) {
|
||||
const userStatus = userVerificationStatus?.[address];
|
||||
const [resolvedEns, setResolvedEns] = React.useState<string | undefined>(undefined);
|
||||
const [resolvedOrdinal, setResolvedOrdinal] = React.useState<boolean | undefined>(undefined);
|
||||
|
||||
// Heuristics for address types
|
||||
const isEthereumAddress = address.startsWith('0x') && address.length === 42;
|
||||
const isBitcoinAddress = !isEthereumAddress; // simple heuristic for our context
|
||||
|
||||
// Lazily resolve ENS name for Ethereum addresses if not provided
|
||||
React.useEffect(() => {
|
||||
const isEthereumAddress = address.startsWith('0x') && address.length === 42;
|
||||
let cancelled = false;
|
||||
if (!userStatus?.ensName && isEthereumAddress) {
|
||||
getEnsName(config, { address: address as `0x${string}` })
|
||||
.then((name) => setResolvedEns(name || undefined))
|
||||
.catch(() => setResolvedEns(undefined));
|
||||
.then((name) => { if (!cancelled) setResolvedEns(name || undefined); })
|
||||
.catch(() => { if (!cancelled) setResolvedEns(undefined); });
|
||||
} else {
|
||||
setResolvedEns(userStatus?.ensName);
|
||||
}
|
||||
return () => { cancelled = true; };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [address]);
|
||||
}, [address, isEthereumAddress, userStatus?.ensName]);
|
||||
|
||||
const hasENS = userStatus?.hasENS || Boolean(resolvedEns) || Boolean(userStatus?.ensName);
|
||||
const hasOrdinal = userStatus?.hasOrdinal || false;
|
||||
// Lazily check Ordinal ownership for Bitcoin addresses if not provided
|
||||
React.useEffect(() => {
|
||||
let cancelled = false;
|
||||
const run = async () => {
|
||||
console.log({
|
||||
isBitcoinAddress, userStatus
|
||||
})
|
||||
if (isBitcoinAddress) {
|
||||
try {
|
||||
const api = new OrdinalAPI();
|
||||
const res = await api.getOperatorDetails(address);
|
||||
if (!cancelled) setResolvedOrdinal(Boolean(res?.has_operators));
|
||||
} catch {
|
||||
if (!cancelled) setResolvedOrdinal(undefined);
|
||||
}
|
||||
} else {
|
||||
setResolvedOrdinal(userStatus?.hasOrdinal);
|
||||
}
|
||||
};
|
||||
run();
|
||||
return () => { cancelled = true; };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [address, isBitcoinAddress, userStatus?.hasOrdinal]);
|
||||
|
||||
const hasENS = Boolean(userStatus?.hasENS) || Boolean(resolvedEns) || Boolean(userStatus?.ensName);
|
||||
const hasOrdinal = Boolean(userStatus?.hasOrdinal) || Boolean(resolvedOrdinal);
|
||||
|
||||
// Only show a badge if the author has ENS or Ordinal ownership (not for basic verification)
|
||||
const shouldShowBadge = showBadge && (hasENS || hasOrdinal);
|
||||
|
||||
@ -36,7 +36,7 @@ export function VerificationStep({
|
||||
const [verificationResult, setVerificationResult] = React.useState<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
details?: any;
|
||||
details?: { ensName?: string; ensAvatar?: string } | boolean | { id: string; details: string };
|
||||
} | null>(null);
|
||||
|
||||
const handleVerify = async () => {
|
||||
@ -62,8 +62,8 @@ export function VerificationStep({
|
||||
setVerificationResult({
|
||||
success: false,
|
||||
message: walletType === 'bitcoin'
|
||||
? "No Ordinal ownership found. You'll have read-only access."
|
||||
: "No ENS ownership found. You'll have read-only access."
|
||||
? "No Ordinal ownership found. You can still participate in the forum with your connected wallet!"
|
||||
: "No ENS ownership found. You can still participate in the forum with your connected wallet!"
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@ -81,7 +81,7 @@ export function VerificationStep({
|
||||
};
|
||||
|
||||
const getVerificationType = () => {
|
||||
return walletType === 'bitcoin' ? 'Bitcoin Ordinal' : 'ENS Domain';
|
||||
return walletType === 'bitcoin' ? 'Bitcoin Ordinal' : 'Ethereum ENS';
|
||||
};
|
||||
|
||||
const getVerificationIcon = () => {
|
||||
@ -94,9 +94,9 @@ export function VerificationStep({
|
||||
|
||||
const getVerificationDescription = () => {
|
||||
if (walletType === 'bitcoin') {
|
||||
return "Verify that you own Bitcoin Ordinals to get full posting and voting access.";
|
||||
return "Verify your Bitcoin Ordinal ownership to unlock premium features. If you don't own any Ordinals, you can still participate in the forum with your connected wallet.";
|
||||
} else {
|
||||
return "Verify that you own an ENS domain to get full posting and voting access.";
|
||||
return "Verify your Ethereum ENS ownership to unlock premium features. If you don't own any ENS, you can still participate in the forum with your connected wallet.";
|
||||
}
|
||||
};
|
||||
|
||||
@ -128,9 +128,9 @@ export function VerificationStep({
|
||||
{verificationResult.details && (
|
||||
<div className="text-xs text-neutral-400">
|
||||
{walletType === 'bitcoin' ? (
|
||||
<p>Ordinal ID: {verificationResult.details.id}</p>
|
||||
<p>Ordinal ID: {typeof verificationResult.details === 'object' && 'id' in verificationResult.details ? verificationResult.details.id : 'Verified'}</p>
|
||||
) : (
|
||||
<p>ENS Name: {verificationResult.details.ensName}</p>
|
||||
<p>ENS Name: {typeof verificationResult.details === 'object' && 'ensName' in verificationResult.details ? verificationResult.details.ensName : 'Verified'}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -39,7 +39,7 @@ export function WalletWizard({
|
||||
setCurrentStep(1); // Start at connection step if not authenticated
|
||||
} else if (isAuthenticated && (verificationStatus === 'unverified' || verificationStatus === 'verifying')) {
|
||||
setCurrentStep(2); // Start at verification step if authenticated but not verified
|
||||
} else if (isAuthenticated && (verificationStatus === 'verified-owner' || verificationStatus === 'verified-none') && !isDelegationValid()) {
|
||||
} else if (isAuthenticated && (verificationStatus === 'verified-owner' || verificationStatus === 'verified-basic' || verificationStatus === 'verified-none') && !isDelegationValid()) {
|
||||
setCurrentStep(3); // Start at delegation step if verified but no valid delegation
|
||||
} else {
|
||||
setCurrentStep(3); // Default to step 3 if everything is complete
|
||||
@ -66,34 +66,25 @@ export function WalletWizard({
|
||||
};
|
||||
|
||||
const getStepStatus = (step: WizardStep) => {
|
||||
// Step 1: Wallet connection - completed when authenticated
|
||||
if (step === 1) {
|
||||
if (isAuthenticated) return "completed";
|
||||
if (currentStep === step) return "current";
|
||||
return "pending";
|
||||
return isAuthenticated ? 'complete' : 'current';
|
||||
} else if (step === 2) {
|
||||
if (!isAuthenticated) return 'disabled';
|
||||
if (verificationStatus === 'unverified' || verificationStatus === 'verifying') return 'current';
|
||||
if (verificationStatus === 'verified-owner' || verificationStatus === 'verified-basic' || verificationStatus === 'verified-none') return 'complete';
|
||||
return 'disabled';
|
||||
} else if (step === 3) {
|
||||
if (!isAuthenticated || (verificationStatus !== 'verified-owner' && verificationStatus !== 'verified-basic' && verificationStatus !== 'verified-none')) return 'disabled';
|
||||
if (isDelegationValid()) return 'complete';
|
||||
return 'current';
|
||||
}
|
||||
|
||||
// Step 2: Verification - completed when verified (either owner or none)
|
||||
if (step === 2) {
|
||||
if (verificationStatus === 'verified-owner' || verificationStatus === 'verified-none') return "completed";
|
||||
if (currentStep === step) return "current";
|
||||
return "pending";
|
||||
}
|
||||
|
||||
// Step 3: Key delegation - completed when delegation is valid AND authenticated
|
||||
if (step === 3) {
|
||||
if (isAuthenticated && isDelegationValid()) return "completed";
|
||||
if (currentStep === step) return "current";
|
||||
return "pending";
|
||||
}
|
||||
|
||||
return "pending";
|
||||
return 'disabled';
|
||||
};
|
||||
|
||||
const renderStepIcon = (step: WizardStep) => {
|
||||
const status = getStepStatus(step);
|
||||
|
||||
if (status === "completed") {
|
||||
if (status === "complete") {
|
||||
return <CheckCircle className="h-5 w-5 text-green-500" />;
|
||||
} else if (status === "current") {
|
||||
return <Loader2 className="h-5 w-5 text-blue-500 animate-spin" />;
|
||||
@ -130,7 +121,7 @@ export function WalletWizard({
|
||||
<span className={`text-sm ${
|
||||
getStepStatus(step as WizardStep) === "current"
|
||||
? "text-blue-500 font-medium"
|
||||
: getStepStatus(step as WizardStep) === "completed"
|
||||
: getStepStatus(step as WizardStep) === "complete"
|
||||
? "text-green-500"
|
||||
: "text-gray-400"
|
||||
}`}>
|
||||
@ -139,7 +130,7 @@ export function WalletWizard({
|
||||
</div>
|
||||
{step < 3 && (
|
||||
<div className={`w-8 h-px mx-2 ${
|
||||
getStepStatus(step as WizardStep) === "completed"
|
||||
getStepStatus(step as WizardStep) === "complete"
|
||||
? "bg-green-500"
|
||||
: "bg-gray-600"
|
||||
}`} />
|
||||
|
||||
@ -6,7 +6,7 @@ import { OpchanMessage } from '@/types';
|
||||
import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react';
|
||||
import { DelegationDuration } from '@/lib/identity/signatures/key-delegation';
|
||||
|
||||
export type VerificationStatus = 'unverified' | 'verified-none' | 'verified-owner' | 'verifying';
|
||||
export type VerificationStatus = 'unverified' | 'verified-none' | 'verified-basic' | 'verified-owner' | 'verifying';
|
||||
|
||||
interface AuthContextType {
|
||||
currentUser: User | null;
|
||||
@ -76,13 +76,39 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const newUser: User = {
|
||||
address,
|
||||
walletType: isBitcoinConnected ? 'bitcoin' : 'ethereum',
|
||||
verificationStatus: 'unverified',
|
||||
verificationStatus: 'verified-basic', // Connected wallets get basic verification by default
|
||||
lastChecked: Date.now(),
|
||||
};
|
||||
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('unverified');
|
||||
authServiceRef.current.saveUser(newUser);
|
||||
// For Ethereum wallets, try to check ENS ownership immediately
|
||||
if (isEthereumConnected) {
|
||||
authServiceRef.current.getWalletInfo().then((walletInfo) => {
|
||||
if (walletInfo?.ensName) {
|
||||
const updatedUser = {
|
||||
...newUser,
|
||||
ensOwnership: true,
|
||||
ensName: walletInfo.ensName,
|
||||
verificationStatus: 'verified-owner' as const,
|
||||
};
|
||||
setCurrentUser(updatedUser);
|
||||
setVerificationStatus('verified-owner');
|
||||
authServiceRef.current.saveUser(updatedUser);
|
||||
} else {
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('verified-basic');
|
||||
authServiceRef.current.saveUser(newUser);
|
||||
}
|
||||
}).catch(() => {
|
||||
// Fallback to basic verification if ENS check fails
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('verified-basic');
|
||||
authServiceRef.current.saveUser(newUser);
|
||||
});
|
||||
} else {
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('verified-basic');
|
||||
authServiceRef.current.saveUser(newUser);
|
||||
}
|
||||
|
||||
const chainName = isBitcoinConnected ? 'Bitcoin' : 'Ethereum';
|
||||
const displayName = `${address.slice(0, 6)}...${address.slice(-4)}`;
|
||||
@ -95,7 +121,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const verificationType = isBitcoinConnected ? 'Ordinal ownership' : 'ENS ownership';
|
||||
toast({
|
||||
title: "Action Required",
|
||||
description: `Please verify your ${verificationType} and delegate a signing key for better UX.`,
|
||||
description: `You can participate in the forum now! Verify your ${verificationType} for premium features and delegate a signing key for better UX.`,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@ -126,9 +152,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const getVerificationStatus = (user: User): VerificationStatus => {
|
||||
if (user.walletType === 'bitcoin') {
|
||||
return user.ordinalOwnership ? 'verified-owner' : 'verified-none';
|
||||
return user.ordinalOwnership ? 'verified-owner' : 'verified-basic';
|
||||
} else if (user.walletType === 'ethereum') {
|
||||
return user.ensOwnership ? 'verified-owner' : 'verified-none';
|
||||
return user.ensOwnership ? 'verified-owner' : 'verified-basic';
|
||||
}
|
||||
return 'unverified';
|
||||
};
|
||||
@ -169,18 +195,18 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
if (updatedUser.walletType === 'bitcoin' && updatedUser.ordinalOwnership) {
|
||||
toast({
|
||||
title: "Ordinal Verified",
|
||||
description: "You now have full access. We recommend delegating a key for better UX.",
|
||||
description: "You now have premium access with higher relevance bonuses. We recommend delegating a key for better UX.",
|
||||
});
|
||||
} else if (updatedUser.walletType === 'ethereum' && updatedUser.ensOwnership) {
|
||||
toast({
|
||||
title: "ENS Verified",
|
||||
description: "You now have full access. We recommend delegating a key for better UX.",
|
||||
description: "You now have premium access with higher relevance bonuses. We recommend delegating a key for better UX.",
|
||||
});
|
||||
} else {
|
||||
const verificationType = updatedUser.walletType === 'bitcoin' ? 'Ordinal Operators' : 'ENS domain';
|
||||
toast({
|
||||
title: "Read-Only Access",
|
||||
description: `No ${verificationType} found. You have read-only access.`,
|
||||
title: "Basic Access Granted",
|
||||
description: `No ${verificationType} found, but you can still participate in the forum with your connected wallet.`,
|
||||
variant: "default",
|
||||
});
|
||||
}
|
||||
|
||||
@ -21,6 +21,8 @@ import { getDataFromCache } from '@/lib/forum/transformers';
|
||||
import { RelevanceCalculator } from '@/lib/forum/relevance';
|
||||
import { UserVerificationStatus } from '@/lib/forum/types';
|
||||
import { AuthService } from '@/lib/identity/services/AuthService';
|
||||
import { getEnsName } from '@wagmi/core';
|
||||
import { config } from '@/lib/identity/wallets/appkit';
|
||||
|
||||
interface ForumContextType {
|
||||
cells: Cell[];
|
||||
@ -138,21 +140,55 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
// Create generic user object for other addresses
|
||||
allUsers.push({
|
||||
address,
|
||||
walletType: 'bitcoin', // Default, will be updated if we have more info
|
||||
walletType: address.startsWith('0x') ? 'ethereum' : 'bitcoin',
|
||||
verificationStatus: 'unverified'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const userVerificationStatus = relevanceCalculator.buildUserVerificationStatus(allUsers);
|
||||
const initialStatus = relevanceCalculator.buildUserVerificationStatus(allUsers);
|
||||
|
||||
// Transform data with relevance calculation
|
||||
const { cells, posts, comments } = getDataFromCache(verifyFn, userVerificationStatus);
|
||||
// Transform data with relevance calculation (initial pass)
|
||||
const { cells, posts, comments } = getDataFromCache(verifyFn, initialStatus);
|
||||
|
||||
setCells(cells);
|
||||
setPosts(posts);
|
||||
setComments(comments);
|
||||
setUserVerificationStatus(userVerificationStatus);
|
||||
setUserVerificationStatus(initialStatus);
|
||||
|
||||
// Enrich: resolve ENS for ethereum addresses asynchronously and update
|
||||
(async () => {
|
||||
const targets = allUsers.filter(u => u.walletType === 'ethereum' && !u.ensOwnership);
|
||||
if (targets.length === 0) return;
|
||||
const lookups = await Promise.all(targets.map(async (u) => {
|
||||
try {
|
||||
const name = await getEnsName(config, { address: u.address as `0x${string}` });
|
||||
return { address: u.address, ensName: name || undefined };
|
||||
} catch {
|
||||
return { address: u.address, ensName: undefined };
|
||||
}
|
||||
}));
|
||||
const ensByAddress = new Map<string, string | undefined>(lookups.map(l => [l.address, l.ensName]));
|
||||
const enrichedUsers: User[] = allUsers.map(u => {
|
||||
const ensName = ensByAddress.get(u.address);
|
||||
if (ensName) {
|
||||
return {
|
||||
...u,
|
||||
walletType: 'ethereum',
|
||||
ensOwnership: true,
|
||||
ensName,
|
||||
verificationStatus: 'verified-owner'
|
||||
} as User;
|
||||
}
|
||||
return u;
|
||||
});
|
||||
const enrichedStatus = relevanceCalculator.buildUserVerificationStatus(enrichedUsers);
|
||||
const transformed = getDataFromCache(verifyFn, enrichedStatus);
|
||||
setCells(transformed.cells);
|
||||
setPosts(transformed.posts);
|
||||
setComments(transformed.comments);
|
||||
setUserVerificationStatus(enrichedStatus);
|
||||
})();
|
||||
}, [authService, isAuthenticated, currentUser]);
|
||||
|
||||
const handleRefreshData = async () => {
|
||||
|
||||
@ -35,7 +35,22 @@ export const createPost = async (
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
toast({
|
||||
title: 'Authentication Required',
|
||||
description: 'You need to verify Ordinal ownership to post.',
|
||||
description: 'You need to connect your wallet to post.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(currentUser.ensOwnership || currentUser.ordinalOwnership);
|
||||
const isVerified = currentUser.verificationStatus === 'verified-owner' ||
|
||||
currentUser.verificationStatus === 'verified-basic' ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (!isVerified && (currentUser.verificationStatus === 'unverified' || currentUser.verificationStatus === 'verifying')) {
|
||||
toast({
|
||||
title: 'Verification Required',
|
||||
description: 'Please complete wallet verification to post.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return null;
|
||||
@ -92,7 +107,22 @@ export const createComment = async (
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
toast({
|
||||
title: 'Authentication Required',
|
||||
description: 'You need to verify Ordinal ownership to comment.',
|
||||
description: 'You need to connect your wallet to comment.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(currentUser.ensOwnership || currentUser.ordinalOwnership);
|
||||
const isVerified = currentUser.verificationStatus === 'verified-owner' ||
|
||||
currentUser.verificationStatus === 'verified-basic' ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (!isVerified && (currentUser.verificationStatus === 'unverified' || currentUser.verificationStatus === 'verifying')) {
|
||||
toast({
|
||||
title: 'Verification Required',
|
||||
description: 'Please complete wallet verification to comment.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return null;
|
||||
@ -210,7 +240,22 @@ export const vote = async (
|
||||
if (!isAuthenticated || !currentUser) {
|
||||
toast({
|
||||
title: 'Authentication Required',
|
||||
description: 'You need to verify Ordinal ownership to vote.',
|
||||
description: 'You need to connect your wallet to vote.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if user has basic verification or better, or owns ENS/Ordinal
|
||||
const hasENSOrOrdinal = !!(currentUser.ensOwnership || currentUser.ordinalOwnership);
|
||||
const isVerified = currentUser.verificationStatus === 'verified-owner' ||
|
||||
currentUser.verificationStatus === 'verified-basic' ||
|
||||
hasENSOrOrdinal;
|
||||
|
||||
if (!isVerified && (currentUser.verificationStatus === 'unverified' || currentUser.verificationStatus === 'verifying')) {
|
||||
toast({
|
||||
title: 'Verification Required',
|
||||
description: 'Please complete wallet verification to vote.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return false;
|
||||
|
||||
@ -14,7 +14,8 @@ export class RelevanceCalculator {
|
||||
COMMENT: 0.5
|
||||
};
|
||||
|
||||
private static readonly VERIFICATION_BONUS = 1.25; // 25% increase
|
||||
private static readonly VERIFICATION_BONUS = 1.25; // 25% increase for ENS/Ordinal owners
|
||||
private static readonly BASIC_VERIFICATION_BONUS = 1.1; // 10% increase for basic verified users
|
||||
private static readonly VERIFIED_UPVOTE_BONUS = 0.1;
|
||||
private static readonly VERIFIED_COMMENTER_BONUS = 0.05;
|
||||
|
||||
@ -201,10 +202,10 @@ export class RelevanceCalculator {
|
||||
|
||||
|
||||
/**
|
||||
* Check if a user is verified (has ENS or ordinal ownership)
|
||||
* Check if a user is verified (has ENS or ordinal ownership, or basic verification)
|
||||
*/
|
||||
isUserVerified(user: User): boolean {
|
||||
return !!(user.ensOwnership || user.ordinalOwnership);
|
||||
return !!(user.ensOwnership || user.ordinalOwnership || user.verificationStatus === 'verified-basic');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -218,7 +219,8 @@ export class RelevanceCalculator {
|
||||
isVerified: this.isUserVerified(user),
|
||||
hasENS: !!user.ensOwnership,
|
||||
hasOrdinal: !!user.ordinalOwnership,
|
||||
ensName: user.ensName
|
||||
ensName: user.ensName,
|
||||
verificationStatus: user.verificationStatus
|
||||
};
|
||||
});
|
||||
|
||||
@ -245,22 +247,31 @@ export class RelevanceCalculator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply author verification bonus
|
||||
* Apply verification bonus for verified authors
|
||||
*/
|
||||
private applyAuthorVerificationBonus(
|
||||
score: number,
|
||||
authorAddress: string,
|
||||
score: number,
|
||||
authorAddress: string,
|
||||
userVerificationStatus: UserVerificationStatus
|
||||
): { bonus: number; isVerified: boolean } {
|
||||
const authorStatus = userVerificationStatus[authorAddress];
|
||||
const isVerified = authorStatus?.isVerified || false;
|
||||
|
||||
if (isVerified) {
|
||||
const bonus = score * (RelevanceCalculator.VERIFICATION_BONUS - 1);
|
||||
return { bonus, isVerified };
|
||||
if (!isVerified) {
|
||||
return { bonus: 0, isVerified: false };
|
||||
}
|
||||
|
||||
return { bonus: 0, isVerified };
|
||||
|
||||
// Apply different bonuses based on verification level
|
||||
let bonus = 0;
|
||||
if (authorStatus?.verificationStatus === 'verified-owner') {
|
||||
// Full bonus for ENS/Ordinal owners
|
||||
bonus = score * (RelevanceCalculator.VERIFICATION_BONUS - 1);
|
||||
} else if (authorStatus?.verificationStatus === 'verified-basic') {
|
||||
// Lower bonus for basic verified users
|
||||
bonus = score * (RelevanceCalculator.BASIC_VERIFICATION_BONUS - 1);
|
||||
}
|
||||
|
||||
return { bonus, isVerified: true };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -22,5 +22,6 @@ export interface RelevanceScoreDetails {
|
||||
hasENS: boolean;
|
||||
hasOrdinal: boolean;
|
||||
ensName?: string;
|
||||
verificationStatus?: 'unverified' | 'verified-none' | 'verified-basic' | 'verified-owner' | 'verifying';
|
||||
};
|
||||
}
|
||||
@ -9,7 +9,7 @@ export class OrdinalAPI {
|
||||
* @returns A promise that resolves with the API response.
|
||||
*/
|
||||
async getOperatorDetails(address: string): Promise<OrdinalApiResponse> {
|
||||
|
||||
|
||||
if (import.meta.env.VITE_OPCHAN_MOCK_ORDINAL_CHECK === 'true') {
|
||||
console.log(`[DEV] Bypassing ordinal verification for address: ${address}`);
|
||||
return {
|
||||
|
||||
@ -182,6 +182,7 @@ export class AuthService {
|
||||
const updatedUser = {
|
||||
...user,
|
||||
ordinalOwnership: hasOperators,
|
||||
verificationStatus: hasOperators ? 'verified-owner' : 'verified-basic',
|
||||
lastChecked: Date.now(),
|
||||
};
|
||||
|
||||
@ -206,6 +207,7 @@ export class AuthService {
|
||||
...user,
|
||||
ensOwnership: hasENS,
|
||||
ensName: ensName,
|
||||
verificationStatus: hasENS ? 'verified-owner' : 'verified-basic',
|
||||
lastChecked: Date.now(),
|
||||
};
|
||||
|
||||
@ -216,11 +218,12 @@ export class AuthService {
|
||||
} catch (error) {
|
||||
console.error('Error verifying ENS ownership:', error);
|
||||
|
||||
// Fall back to no ENS ownership on error
|
||||
// Fall back to basic verification on error
|
||||
const updatedUser = {
|
||||
...user,
|
||||
ensOwnership: false,
|
||||
ensName: undefined,
|
||||
verificationStatus: 'verified-basic',
|
||||
lastChecked: Date.now(),
|
||||
};
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ export interface User {
|
||||
ensAvatar?: string;
|
||||
ensOwnership?: boolean;
|
||||
|
||||
verificationStatus: 'unverified' | 'verified-none' | 'verified-owner' | 'verifying';
|
||||
verificationStatus: 'unverified' | 'verified-none' | 'verified-basic' | 'verified-owner' | 'verifying';
|
||||
|
||||
signature?: string;
|
||||
lastChecked?: number;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user