feat(nostr): NIP-05 verification badge with 24h cache (M11.6)

Verify NIP-05 identifiers by fetching .well-known/nostr.json from
the domain. Results cached in localStorage for 24 hours. Green
checkmark badge shown next to verified NIP-05 on note cards.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 00:23:52 +00:00
co-authored by Claude Opus 4.6
parent 8ff3d1298e
commit 49c9f71aa2
2 changed files with 103 additions and 2 deletions
@@ -143,7 +143,16 @@
<span class="text-xs font-semibold truncate text-white/80">
{{ note.authorName ?? 'anon' }}
</span>
<span v-if="note.nip05" class="text-[9px] truncate text-purple-400/60">
<span v-if="note.nip05" class="text-[9px] truncate text-purple-400/60 flex items-center gap-0.5">
<svg
v-if="nip05Status[note.id] === true"
class="w-2.5 h-2.5 text-emerald-400 shrink-0"
fill="currentColor"
viewBox="0 0 20 20"
:title="note.nip05"
>
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
{{ note.nip05 }}
</span>
<span class="text-[9px] ml-auto shrink-0 text-white/20">
@@ -209,11 +218,12 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue'
import { ref, computed, reactive, onMounted, nextTick, watch } from 'vue'
import NostrDMs from './NostrDMs.vue'
import NostrRelayManager from './NostrRelayManager.vue'
import NostrProfileEditor from './NostrProfileEditor.vue'
import ZapDialog from './ZapDialog.vue'
import { useNip05Verification } from '@/composables/useNip05Verification'
import { useNostr, type NostrNote, type PublishResult } from '@/composables/useNostr'
import { useNostrIdentity } from '@/composables/useNostrIdentity'
@@ -221,6 +231,7 @@ defineEmits<{ selectNote: [note: NostrNote] }>()
const { events: notes, isConnected, relayStates, connect, publishEvent } = useNostr()
const { isLoggedIn, signEvent } = useNostrIdentity()
const { verifyNip05 } = useNip05Verification()
const activeSubTab = ref<'feed' | 'dms' | 'relays' | 'profile'>('feed')
const subTabs = [
@@ -249,6 +260,8 @@ function openZap(note: NostrNote) {
zapOpen.value = true
}
const nip05Status = reactive<Record<string, boolean | null>>({})
const noteKinds = [
{ id: 1, label: 'Notes' },
{ id: 30023, label: 'Articles' },
@@ -280,6 +293,18 @@ const filteredNotes = computed(() => {
return result
})
// Verify NIP-05 for notes that have it
watch(filteredNotes, (visibleNotes) => {
for (const note of visibleNotes) {
if (note.nip05 && nip05Status[note.id] === undefined) {
nip05Status[note.id] = null
verifyNip05(note.nip05, note.pubkey).then(result => {
nip05Status[note.id] = result
})
}
}
}, { immediate: true })
async function publishNote() {
if (!composeText.value.trim() || isPublishing.value) return