From 243403652ebf4cea5cba5af0703425eec06142b4 Mon Sep 17 00:00:00 2001 From: Danish Arora <35004822+danisharora099@users.noreply.github.com> Date: Fri, 12 Sep 2025 16:03:12 +0530 Subject: [PATCH] feat: add operator ordinal validation (#23) * chore: add ordiscan * feat: add ordinal check * chore: fix CSS + useMemo * chore: show inscription ID on /profile --- package-lock.json | 19 ++++- package.json | 1 + src/components/CellList.tsx | 2 +- src/components/CommentCard.tsx | 7 +- src/components/CreateCellDialog.tsx | 6 +- src/components/Header.tsx | 5 +- src/components/PostCard.tsx | 5 +- src/components/PostDetail.tsx | 2 +- src/components/PostList.tsx | 5 +- src/components/ui/ShareButton.tsx | 8 +-- src/components/ui/wallet-wizard.tsx | 67 ++++++++---------- src/contexts/AuthContext.tsx | 92 +++++++++++++------------ src/lib/database/LocalDatabase.ts | 14 ++-- src/lib/services/Ordinal/index.ts | 54 --------------- src/lib/services/Ordinal/types.ts | 25 ------- src/lib/services/Ordinals.ts | 47 +++++++++++++ src/lib/services/UserIdentityService.ts | 49 ++++++++++++- src/lib/wallet/index.ts | 16 +++++ src/pages/DebugPage.tsx | 36 +++++++--- src/pages/ProfilePage.tsx | 4 +- 20 files changed, 266 insertions(+), 198 deletions(-) delete mode 100644 src/lib/services/Ordinal/index.ts delete mode 100644 src/lib/services/Ordinal/types.ts create mode 100644 src/lib/services/Ordinals.ts diff --git a/package-lock.json b/package-lock.json index ff6707a..2584d9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "input-otp": "^1.2.4", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", + "ordiscan": "^1.3.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", @@ -12609,6 +12610,18 @@ "node": ">= 0.8.0" } }, + "node_modules/ordiscan": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ordiscan/-/ordiscan-1.3.0.tgz", + "integrity": "sha512-IV8yayKGIRtfkI3rQ1gu+aKQ6UmNHXB910qUuWrraCcuk6U/YkE+6X8Bh+jW2P8L8/lOfYA6DLVeKCVPkj8c6g==", + "license": "MIT", + "dependencies": { + "zod": "^3.24.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/ox": { "version": "0.6.9", "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", @@ -15898,9 +15911,9 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index dfef15d..fc3115e 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "input-otp": "^1.2.4", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", + "ordiscan": "^1.3.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", diff --git a/src/components/CellList.tsx b/src/components/CellList.tsx index d9a3d70..576baa9 100644 --- a/src/components/CellList.tsx +++ b/src/components/CellList.tsx @@ -127,7 +127,7 @@ const CellItem: React.FC<{ cell: Cell }> = ({ cell }) => { diff --git a/src/components/CommentCard.tsx b/src/components/CommentCard.tsx index 020c054..1a8823b 100644 --- a/src/components/CommentCard.tsx +++ b/src/components/CommentCard.tsx @@ -129,9 +129,12 @@ const CommentCard: React.FC = ({
50 ? '...' : '')} + title={ + comment.content.substring(0, 50) + + (comment.content.length > 50 ? '...' : '') + } /> Create New Cell
- + { e.preventDefault()} + onSelect={e => e.preventDefault()} className="flex items-center space-x-2 text-orange-400 focus:text-orange-400" > @@ -306,7 +306,8 @@ const Header = () => { Clear Local Database - This will permanently delete all locally stored data including: + This will permanently delete all locally stored + data including:
• Posts and comments
• User identities and preferences
• Bookmarks and votes diff --git a/src/components/PostCard.tsx b/src/components/PostCard.tsx index bc33a7d..662e3e3 100644 --- a/src/components/PostCard.tsx +++ b/src/components/PostCard.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import { ArrowUp, ArrowDown, MessageSquare, Clipboard } from 'lucide-react'; +import { ArrowUp, ArrowDown, MessageSquare } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import { Post } from '@/types/forum'; import { @@ -70,7 +70,6 @@ const PostCard: React.FC = ({ post, commentCount = 0 }) => { await toggleBookmark(); }; - return (
@@ -180,7 +179,7 @@ const PostCard: React.FC = ({ post, commentCount = 0 }) => { )} diff --git a/src/components/PostDetail.tsx b/src/components/PostDetail.tsx index a121a80..944f8e5 100644 --- a/src/components/PostDetail.tsx +++ b/src/components/PostDetail.tsx @@ -255,7 +255,7 @@ const PostDetail = () => { showText={true} /> diff --git a/src/components/PostList.tsx b/src/components/PostList.tsx index c8fb2f0..1811db6 100644 --- a/src/components/PostList.tsx +++ b/src/components/PostList.tsx @@ -7,7 +7,7 @@ import { usePermissions, useUserVotes, useAuth, - usePostComments, + useForumData, } from '@/hooks'; import { EVerificationStatus } from '@/types/identity'; import { Button } from '@/components/ui/button'; @@ -56,6 +56,7 @@ const PostList = () => { const { canPost, canVote, canModerate } = usePermissions(); const userVotes = useUserVotes(); const { currentUser, verificationStatus } = useAuth(); + const { commentsByPost } = useForumData(); const [newPostTitle, setNewPostTitle] = useState(''); const [newPostContent, setNewPostContent] = useState(''); @@ -339,7 +340,7 @@ const PostList = () => { - {usePostComments(post.id).totalCount} comments + {commentsByPost[post.id]?.length || 0} comments - {showText && ( - Share - )} + {showText && Share} ); -} \ No newline at end of file +} diff --git a/src/components/ui/wallet-wizard.tsx b/src/components/ui/wallet-wizard.tsx index f1b29a7..9c3f9a0 100644 --- a/src/components/ui/wallet-wizard.tsx +++ b/src/components/ui/wallet-wizard.tsx @@ -55,21 +55,9 @@ export function WalletWizard({ onOpenChange(false); }; - // Business logic: determine step status based on current wizard step + // Consolidated step status logic const getStepStatus = (step: WizardStep) => { - if (step < currentStep) { - return 'complete'; - } else if (step === currentStep) { - return 'current'; - } else { - return 'disabled'; - } - }; - - const renderStepIcon = (step: WizardStep) => { - const status = getStepStatus(step); - - // Check if step is actually completed based on auth state + // Check actual completion status first const isActuallyComplete = (step: WizardStep): boolean => { switch (step) { case 1: @@ -83,7 +71,19 @@ export function WalletWizard({ } }; - if (status === 'complete' || isActuallyComplete(step)) { + if (isActuallyComplete(step)) { + return 'complete'; + } else if (step === currentStep) { + return 'current'; + } else { + return 'disabled'; + } + }; + + const renderStepIcon = (step: WizardStep) => { + const status = getStepStatus(step); + + if (status === 'complete') { return ; } else if (status === 'current') { return ; @@ -107,7 +107,7 @@ export function WalletWizard({ return ( - + Setup Your Account @@ -116,21 +116,16 @@ export function WalletWizard({ {/* Progress Indicator */} -
- {[1, 2, 3].map(step => ( +
+ {[1, 2, 3].map((step, index) => (
-
+
{renderStepIcon(step as WizardStep)}
- {step < 3 && ( + {index < 2 && (
- {/* Step Content - Fixed height container */} -
+ {/* Step Content - Flexible height container */} +
{currentStep === 1 && ( handleStepComplete(1)} @@ -185,7 +176,7 @@ export function WalletWizard({
{/* Footer */} -
+

Step {currentStep} of 3

{currentStep > 1 && ( diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 870cc44..cd531e9 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -14,6 +14,8 @@ import { } from '@/lib/delegation'; import { localDatabase } from '@/lib/database/LocalDatabase'; import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react'; +import { MessageService } from '@/lib/services/MessageService'; +import { UserIdentityService } from '@/lib/services/UserIdentityService'; interface AuthContextType { currentUser: User | null; @@ -56,6 +58,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { // Create manager instances const delegationManager = useMemo(() => new DelegationManager(), []); + const messageService = useMemo( + () => new MessageService(delegationManager), + [delegationManager] + ); + const userIdentityService = useMemo( + () => new UserIdentityService(messageService), + [messageService] + ); // Create wallet manager when we have all dependencies useEffect(() => { @@ -89,51 +99,44 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { } }; - // Helper function for ownership verification + // Helper function for ownership verification (via UserIdentityService) const verifyUserOwnership = async (user: User): Promise => { - if (user.walletType === 'bitcoin') { - // TODO: revert when the API is ready - // const response = await ordinalApi.getOperatorDetails(user.address); - // const hasOperators = response.has_operators; - const hasOperators = true; - - return { - ...user, - ordinalDetails: hasOperators - ? { ordinalId: 'mock', ordinalDetails: 'Mock ordinal for testing' } - : undefined, - verificationStatus: hasOperators - ? EVerificationStatus.ENS_ORDINAL_VERIFIED - : EVerificationStatus.WALLET_CONNECTED, - lastChecked: Date.now(), - }; - } else if (user.walletType === 'ethereum') { - try { - const walletInfo = WalletManager.hasInstance() - ? await WalletManager.getInstance().getWalletInfo() - : null; - const hasENS = !!walletInfo?.ensName; - const ensName = walletInfo?.ensName; - - return { - ...user, - ensDetails: hasENS && ensName ? { ensName } : undefined, - verificationStatus: hasENS - ? EVerificationStatus.ENS_ORDINAL_VERIFIED - : EVerificationStatus.WALLET_CONNECTED, - lastChecked: Date.now(), - }; - } catch (error) { - console.error('Error verifying ENS ownership:', error); + try { + // Force fresh resolution to ensure API call happens during verification + const identity = await userIdentityService.getUserIdentityFresh( + user.address + ); + if (!identity) { return { ...user, ensDetails: undefined, + ordinalDetails: undefined, verificationStatus: EVerificationStatus.WALLET_CONNECTED, lastChecked: Date.now(), }; } - } else { - throw new Error('Unknown wallet type'); + + return { + ...user, + ensDetails: identity.ensName + ? { ensName: identity.ensName } + : undefined, + ordinalDetails: identity.ordinalDetails, + verificationStatus: identity.verificationStatus, + lastChecked: Date.now(), + }; + } catch (error) { + console.error( + 'Error verifying ownership via UserIdentityService:', + error + ); + return { + ...user, + ensDetails: undefined, + ordinalDetails: undefined, + verificationStatus: EVerificationStatus.WALLET_CONNECTED, + lastChecked: Date.now(), + }; } }; @@ -212,12 +215,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { EVerificationStatus.ENS_ORDINAL_VERIFIED ); await saveUser(updatedUser); - await localDatabase.upsertUserIdentity(updatedUser.address, { - ensName: walletInfo.ensName, - verificationStatus: - EVerificationStatus.ENS_ORDINAL_VERIFIED, - lastUpdated: Date.now(), - }); + await localDatabase.upsertUserIdentity( + updatedUser.address, + { + ensName: walletInfo.ensName, + verificationStatus: + EVerificationStatus.ENS_ORDINAL_VERIFIED, + lastUpdated: Date.now(), + } + ); } else { setCurrentUser(newUser); setVerificationStatus(EVerificationStatus.WALLET_CONNECTED); diff --git a/src/lib/database/LocalDatabase.ts b/src/lib/database/LocalDatabase.ts index dcacd6f..3d4658e 100644 --- a/src/lib/database/LocalDatabase.ts +++ b/src/lib/database/LocalDatabase.ts @@ -121,7 +121,7 @@ export class LocalDatabase { public async clearAll(): Promise { // Clear in-memory cache this.clear(); - + // Clear all IndexedDB stores if (!this.db) return; @@ -140,7 +140,7 @@ export class LocalDatabase { ]; const tx = this.db.transaction(storeNames, 'readwrite'); - + await Promise.all( storeNames.map(storeName => { return new Promise((resolve, reject) => { @@ -632,7 +632,8 @@ export class LocalDatabase { record: Partial & { lastUpdated?: number } ): Promise { const existing: UserIdentityCache[string] = - this.cache.userIdentities[address] || { + this.cache.userIdentities[address] || + ({ ensName: undefined, ordinalDetails: undefined, callSign: undefined, @@ -644,12 +645,15 @@ export class LocalDatabase { // Casting below ensures the object satisfies the interface at compile time. lastUpdated: 0, verificationStatus: EVerificationStatus.WALLET_UNCONNECTED, - } as unknown as UserIdentityCache[string]; + } as unknown as UserIdentityCache[string]); const merged: UserIdentityCache[string] = { ...existing, ...record, - lastUpdated: Math.max(existing.lastUpdated ?? 0, record.lastUpdated ?? Date.now()), + lastUpdated: Math.max( + existing.lastUpdated ?? 0, + record.lastUpdated ?? Date.now() + ), } as UserIdentityCache[string]; this.cache.userIdentities[address] = merged; diff --git a/src/lib/services/Ordinal/index.ts b/src/lib/services/Ordinal/index.ts deleted file mode 100644 index 12ae4b0..0000000 --- a/src/lib/services/Ordinal/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { OrdinalApiResponse } from './types'; - -const BASE_URL = 'https://dashboard.logos.co/api/operators/wallet'; - -export class OrdinalAPI { - /** - * Fetches Ordinal operator details for a given Bitcoin address. - * @param address - The Bitcoin address to query. - * @returns A promise that resolves with the API response. - */ - async getOperatorDetails(address: string): Promise { - if (import.meta.env.VITE_OPCHAN_MOCK_ORDINAL_CHECK === 'true') { - console.log( - `[DEV] Bypassing ordinal verification for address: ${address}` - ); - return { - has_operators: true, - error_message: '', - data: [], - }; - } - - const url = `${BASE_URL}/${address}/detail/`; - - try { - const response = await fetch(url, { - method: 'GET', - headers: { Accept: 'application/json' }, - }); - - if (!response.ok) { - const errorBody = await response.text().catch(() => ''); - throw new Error( - `HTTP error! status: ${response.status}, message: ${errorBody || response.statusText}` - ); - } - - const data: OrdinalApiResponse = await response.json(); - - if (data.error_message) { - console.warn( - `API returned an error message for address ${address}: ${data.error_message}` - ); - } - return data; - } catch (error) { - console.error( - `Failed to fetch ordinal details for address ${address}:`, - error - ); - throw error; - } - } -} diff --git a/src/lib/services/Ordinal/types.ts b/src/lib/services/Ordinal/types.ts deleted file mode 100644 index ca19ab4..0000000 --- a/src/lib/services/Ordinal/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface OrdinalDetail { - name: string; - archetype_name: string; - comp: string; - background: string; - skin: string; - helmet: string; - jacket: string; - image_200_url: string; - image_200_jpeg_url: string; - image_400_url: string; - image_400_jpeg_url: string; - image_1024_url: string; - image_1024_jpeg_url: string; - image_2048_url: string; - image_2048_jpeg_url: string; - image_pixalated_url: string; - mp4_url: string; -} - -export interface OrdinalApiResponse { - has_operators: boolean; - error_message: string; - data: OrdinalDetail[]; -} diff --git a/src/lib/services/Ordinals.ts b/src/lib/services/Ordinals.ts new file mode 100644 index 0000000..9513d71 --- /dev/null +++ b/src/lib/services/Ordinals.ts @@ -0,0 +1,47 @@ +import { Ordiscan, Inscription } from 'ordiscan'; +const API_KEY = import.meta.env.VITE_ORDISCAN_API; + +class Ordinals { + private static instance: Ordinals | null = null; + private ordiscan: Ordiscan; + private readonly PARENT_INSCRIPTION_ID = + 'add60add0325f7c82e80d4852a8b8d5c46dbde4317e76fe4def2e718dd84b87ci0'; + + private constructor(ordiscan: Ordiscan) { + this.ordiscan = ordiscan; + } + + static getInstance(): Ordinals { + if (!Ordinals.instance) { + Ordinals.instance = new Ordinals(new Ordiscan(API_KEY)); + } + return Ordinals.instance; + } + + /** + * Get Ordinal details for a Bitcoin address + */ + async getOrdinalDetails(address: string): Promise { + const inscriptions = await this.ordiscan.address.getInscriptions({ + address, + }); + if (inscriptions.length > 0) { + if ( + inscriptions.some( + inscription => + inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID + ) + ) { + return inscriptions.filter( + inscription => + inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID + ); + } else { + return null; + } + } + return null; + } +} + +export const ordinals = Ordinals.getInstance(); diff --git a/src/lib/services/UserIdentityService.ts b/src/lib/services/UserIdentityService.ts index d477c87..26619e6 100644 --- a/src/lib/services/UserIdentityService.ts +++ b/src/lib/services/UserIdentityService.ts @@ -8,6 +8,7 @@ import { import { MessageService } from './MessageService'; import messageManager from '@/lib/waku'; import { localDatabase } from '@/lib/database/LocalDatabase'; +import { WalletManager } from '@/lib/wallet'; export interface UserIdentity { address: string; @@ -155,6 +156,29 @@ export class UserIdentityService { return identity; } + /** + * Force a fresh identity resolution bypassing caches and LocalDatabase. + * Useful for explicit verification flows where we must hit upstream resolvers. + */ + async getUserIdentityFresh(address: string): Promise { + if (import.meta.env?.DEV) { + console.debug('UserIdentityService: fresh resolve requested'); + } + const identity = await this.resolveUserIdentity(address); + if (identity) { + // Update in-memory cache to reflect the fresh result + this.userIdentityCache[address] = { + ensName: identity.ensName, + ordinalDetails: identity.ordinalDetails, + callSign: identity.callSign, + displayPreference: identity.displayPreference, + lastUpdated: identity.lastUpdated, + verificationStatus: identity.verificationStatus, + }; + } + return identity; + } + /** * Get all cached user identities */ @@ -329,8 +353,19 @@ export class UserIdentityService { address: string ): Promise<{ ordinalId: string; ordinalDetails: string } | null> { try { - //TODO: add Ordinal API call - console.log('resolveOrdinalDetails', address); + if (address.startsWith('0x')) { + return null; + } + + const inscriptions = await WalletManager.resolveOperatorOrdinals(address); + if (Array.isArray(inscriptions) && inscriptions.length > 0) { + const first = inscriptions[0]!; + return { + ordinalId: first.inscription_id, + ordinalDetails: + first.parent_inscription_id || 'Operator badge present', + }; + } return null; } catch (error) { console.error('Failed to resolve Ordinal details:', error); @@ -375,12 +410,22 @@ export class UserIdentityService { */ private mapVerificationStatus(status: string): EVerificationStatus { switch (status) { + // Legacy message-cache statuses case 'verified-basic': return EVerificationStatus.WALLET_CONNECTED; case 'verified-owner': return EVerificationStatus.ENS_ORDINAL_VERIFIED; case 'verifying': return EVerificationStatus.WALLET_CONNECTED; // Temporary state during verification + + // Enum string values persisted in LocalDatabase + case EVerificationStatus.WALLET_UNCONNECTED: + return EVerificationStatus.WALLET_UNCONNECTED; + case EVerificationStatus.WALLET_CONNECTED: + return EVerificationStatus.WALLET_CONNECTED; + case EVerificationStatus.ENS_ORDINAL_VERIFIED: + return EVerificationStatus.ENS_ORDINAL_VERIFIED; + default: return EVerificationStatus.WALLET_UNCONNECTED; } diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index e5a05ab..90d0f8b 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -1,5 +1,6 @@ import { UseAppKitAccountReturn } from '@reown/appkit/react'; import { AppKit } from '@reown/appkit'; +import { ordinals } from '@/lib/services/Ordinals'; import { getEnsName, verifyMessage as verifyEthereumMessage, @@ -8,6 +9,7 @@ import { ChainNamespace } from '@reown/appkit-common'; import { config } from './config'; import { Provider } from '@reown/appkit-controllers'; import { WalletInfo, ActiveWallet } from './types'; +import { Inscription } from 'ordiscan'; export class WalletManager { private static instance: WalletManager | null = null; @@ -95,6 +97,20 @@ export class WalletManager { } } + /** + * Resolve Ordinal details for a Bitcoin address + */ + static async resolveOperatorOrdinals( + address: string + ): Promise { + try { + return await ordinals.getOrdinalDetails(address); + } catch (error) { + console.warn('Failed to resolve Ordinal details:', error); + return null; + } + } + /** * Get the currently active wallet */ diff --git a/src/pages/DebugPage.tsx b/src/pages/DebugPage.tsx index f151ba3..d4483f1 100644 --- a/src/pages/DebugPage.tsx +++ b/src/pages/DebugPage.tsx @@ -15,7 +15,9 @@ export default function DebugPage() { useEffect(() => { // Subscribe to inbound messages from reliable channel unsubscribeRef.current = messageManager.onMessageReceived(msg => { - setMessages(prev => [{ receivedAt: Date.now(), message: msg }, ...prev].slice(0, 500)); + setMessages(prev => + [{ receivedAt: Date.now(), message: msg }, ...prev].slice(0, 500) + ); }); return () => { @@ -47,7 +49,9 @@ export default function DebugPage() { Total received: {messages.length}
-
+
{Object.values(MessageType).map(t => (
- {t}: {typeCounts[t] || 0} + {t}:{' '} + {typeCounts[t] || 0}
))}
-
-
Recent messages
+
+
+ Recent messages +
ID / Author
Msg Timestamp
{messages.map(m => ( - +
{formatTs(m.receivedAt)}
-
{m.message.type}
+
+ {m.message.type} +
- {m.message.id} — {m.message.author} + {m.message.id} —{' '} + {m.message.author} +
+
+ {formatTs(m.message.timestamp)}
-
{formatTs(m.message.timestamp)}
))}
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 8fcfba0..e4b60f8 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -277,7 +277,9 @@ export default function ProfilePage() { {userInfo.displayName}
- {currentUser.ensDetails?.ensName || 'No ENS name'} + {userInfo.ordinalDetails || currentUser.ordinalDetails?.ordinalDetails + ? `Ordinal: ${userInfo.ordinalDetails || currentUser.ordinalDetails?.ordinalDetails}` + : currentUser.ensDetails?.ensName || 'No ENS name'}
{getVerificationIcon()}