/** * NDK User and Profile Handling * * Examples from: src/queries/profiles.tsx, src/components/Profile.tsx */ import NDK, { NDKUser, NDKUserProfile } from '@nostr-dev-kit/ndk' import { nip19 } from 'nostr-tools' // ============================================================ // FETCH PROFILE BY NPUB // ============================================================ const fetchProfileByNpub = async (ndk: NDK, npub: string): Promise => { try { // Get user object from npub const user = ndk.getUser({ npub }) // Fetch profile from relays const profile = await user.fetchProfile() return profile } catch (error) { console.error('Failed to fetch profile:', error) return null } } // ============================================================ // FETCH PROFILE BY HEX PUBKEY // ============================================================ const fetchProfileByPubkey = async (ndk: NDK, pubkey: string): Promise => { try { const user = ndk.getUser({ hexpubkey: pubkey }) const profile = await user.fetchProfile() return profile } catch (error) { console.error('Failed to fetch profile:', error) return null } } // ============================================================ // FETCH PROFILE BY NIP-05 // ============================================================ const fetchProfileByNip05 = async (ndk: NDK, nip05: string): Promise => { try { // Resolve NIP-05 identifier to user const user = await ndk.getUserFromNip05(nip05) if (!user) { console.log('User not found for NIP-05:', nip05) return null } // Fetch profile const profile = await user.fetchProfile() return profile } catch (error) { console.error('Failed to fetch profile by NIP-05:', error) return null } } // ============================================================ // FETCH PROFILE BY ANY IDENTIFIER // ============================================================ const fetchProfileByIdentifier = async ( ndk: NDK, identifier: string ): Promise<{ profile: NDKUserProfile | null; user: NDKUser | null }> => { try { // Check if it's a NIP-05 (contains @) if (identifier.includes('@')) { const user = await ndk.getUserFromNip05(identifier) if (!user) return { profile: null, user: null } const profile = await user.fetchProfile() return { profile, user } } // Check if it's an npub if (identifier.startsWith('npub')) { const user = ndk.getUser({ npub: identifier }) const profile = await user.fetchProfile() return { profile, user } } // Assume it's a hex pubkey const user = ndk.getUser({ hexpubkey: identifier }) const profile = await user.fetchProfile() return { profile, user } } catch (error) { console.error('Failed to fetch profile:', error) return { profile: null, user: null } } } // ============================================================ // GET CURRENT USER // ============================================================ const getCurrentUser = async (ndk: NDK): Promise => { if (!ndk.signer) { console.log('No signer set') return null } try { const user = await ndk.signer.user() return user } catch (error) { console.error('Failed to get current user:', error) return null } } // ============================================================ // PROFILE DATA STRUCTURE // ============================================================ interface ProfileData { // Standard fields name?: string displayName?: string display_name?: string picture?: string image?: string banner?: string about?: string // Contact nip05?: string lud06?: string // LNURL lud16?: string // Lightning address // Social website?: string // Raw data [key: string]: any } // ============================================================ // EXTRACT PROFILE INFO // ============================================================ const extractProfileInfo = (profile: NDKUserProfile | null) => { if (!profile) { return { displayName: 'Anonymous', avatar: null, bio: null, lightningAddress: null, nip05: null } } return { displayName: profile.displayName || profile.display_name || profile.name || 'Anonymous', avatar: profile.picture || profile.image || null, banner: profile.banner || null, bio: profile.about || null, lightningAddress: profile.lud16 || profile.lud06 || null, nip05: profile.nip05 || null, website: profile.website || null } } // ============================================================ // UPDATE PROFILE // ============================================================ import { NDKEvent } from '@nostr-dev-kit/ndk' const updateProfile = async (ndk: NDK, profileData: Partial) => { if (!ndk.signer) { throw new Error('No signer available') } // Get current profile const currentUser = await ndk.signer.user() const currentProfile = await currentUser.fetchProfile() // Merge with new data const updatedProfile = { ...currentProfile, ...profileData } // Create kind 0 (metadata) event const event = new NDKEvent(ndk) event.kind = 0 event.content = JSON.stringify(updatedProfile) event.tags = [] await event.sign() await event.publish() console.log('✅ Profile updated') return event.id } // ============================================================ // BATCH FETCH PROFILES // ============================================================ const fetchMultipleProfiles = async ( ndk: NDK, pubkeys: string[] ): Promise> => { const profiles = new Map() // Fetch all profiles in parallel await Promise.all( pubkeys.map(async (pubkey) => { try { const user = ndk.getUser({ hexpubkey: pubkey }) const profile = await user.fetchProfile() profiles.set(pubkey, profile) } catch (error) { console.error(`Failed to fetch profile for ${pubkey}:`, error) profiles.set(pubkey, null) } }) ) return profiles } // ============================================================ // CONVERT BETWEEN FORMATS // ============================================================ const convertPubkeyFormats = (identifier: string) => { try { // If it's npub, convert to hex if (identifier.startsWith('npub')) { const decoded = nip19.decode(identifier) if (decoded.type === 'npub') { return { hex: decoded.data as string, npub: identifier } } } // If it's hex, convert to npub if (/^[0-9a-f]{64}$/.test(identifier)) { return { hex: identifier, npub: nip19.npubEncode(identifier) } } throw new Error('Invalid pubkey format') } catch (error) { console.error('Format conversion failed:', error) return null } } // ============================================================ // REACT HOOK FOR PROFILE // ============================================================ import { useQuery } from '@tanstack/react-query' import { useEffect, useState } from 'react' function useProfile(ndk: NDK | null, npub: string | undefined) { return useQuery({ queryKey: ['profile', npub], queryFn: async () => { if (!ndk || !npub) throw new Error('NDK or npub missing') return await fetchProfileByNpub(ndk, npub) }, enabled: !!ndk && !!npub, staleTime: 5 * 60 * 1000, // 5 minutes cacheTime: 30 * 60 * 1000 // 30 minutes }) } // ============================================================ // REACT COMPONENT EXAMPLE // ============================================================ interface ProfileDisplayProps { ndk: NDK pubkey: string } function ProfileDisplay({ ndk, pubkey }: ProfileDisplayProps) { const [profile, setProfile] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { const loadProfile = async () => { setLoading(true) try { const user = ndk.getUser({ hexpubkey: pubkey }) const fetchedProfile = await user.fetchProfile() setProfile(fetchedProfile) } catch (error) { console.error('Failed to load profile:', error) } finally { setLoading(false) } } loadProfile() }, [ndk, pubkey]) if (loading) { return
Loading profile...
} const info = extractProfileInfo(profile) return (
{info.avatar && {info.displayName}}

{info.displayName}

{info.bio &&

{info.bio}

} {info.nip05 && ✓ {info.nip05}} {info.lightningAddress && ⚡ {info.lightningAddress}}
) } // ============================================================ // FOLLOW/UNFOLLOW USER // ============================================================ const followUser = async (ndk: NDK, pubkeyToFollow: string) => { if (!ndk.signer) { throw new Error('No signer available') } // Fetch current contact list (kind 3) const currentUser = await ndk.signer.user() const contactListFilter = { kinds: [3], authors: [currentUser.pubkey] } const existingEvents = await ndk.fetchEvents(contactListFilter) const existingContactList = existingEvents.size > 0 ? Array.from(existingEvents)[0] : null // Get existing p tags const existingPTags = existingContactList ? existingContactList.tags.filter(tag => tag[0] === 'p') : [] // Check if already following const alreadyFollowing = existingPTags.some(tag => tag[1] === pubkeyToFollow) if (alreadyFollowing) { console.log('Already following this user') return } // Create new contact list with added user const event = new NDKEvent(ndk) event.kind = 3 event.content = existingContactList?.content || '' event.tags = [ ...existingPTags, ['p', pubkeyToFollow] ] await event.sign() await event.publish() console.log('✅ Now following user') } // ============================================================ // USAGE EXAMPLE // ============================================================ async function profileExample(ndk: NDK) { // Fetch by different identifiers const profile1 = await fetchProfileByNpub(ndk, 'npub1...') const profile2 = await fetchProfileByNip05(ndk, 'user@domain.com') const profile3 = await fetchProfileByPubkey(ndk, 'hex pubkey...') // Extract display info const info = extractProfileInfo(profile1) console.log('Display name:', info.displayName) console.log('Avatar:', info.avatar) // Update own profile await updateProfile(ndk, { name: 'My Name', about: 'My bio', picture: 'https://example.com/avatar.jpg', lud16: 'me@getalby.com' }) // Follow someone await followUser(ndk, 'pubkey to follow') } export { fetchProfileByNpub, fetchProfileByPubkey, fetchProfileByNip05, fetchProfileByIdentifier, getCurrentUser, extractProfileInfo, updateProfile, fetchMultipleProfiles, convertPubkeyFormats, useProfile, followUser }