From 97d7926659070bcd4a643a784b9495eee26cc717 Mon Sep 17 00:00:00 2001 From: Danish Arora Date: Fri, 12 Sep 2025 15:55:20 +0530 Subject: [PATCH] feat: add ordinal check --- src/contexts/AuthContext.tsx | 72 ++++++++++++------------- src/lib/services/Ordinals.ts | 36 +++++++++++++ src/lib/services/UserIdentityService.ts | 49 ++++++++++++++++- src/lib/wallet/index.ts | 14 +++++ 4 files changed, 132 insertions(+), 39 deletions(-) create mode 100644 src/lib/services/Ordinals.ts diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 870cc44..39ab060 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,39 @@ 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(), + }; } }; diff --git a/src/lib/services/Ordinals.ts b/src/lib/services/Ordinals.ts new file mode 100644 index 0000000..4a76fe8 --- /dev/null +++ b/src/lib/services/Ordinals.ts @@ -0,0 +1,36 @@ +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(); \ No newline at end of file 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..1d7bdf6 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,18 @@ 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 */