14 changed files with 316 additions and 58 deletions
@ -0,0 +1,75 @@
@@ -0,0 +1,75 @@
|
||||
import { ExtendedKind, FAST_READ_RELAY_URLS, PROFILE_FETCH_RELAY_URLS } from '@/constants' |
||||
import { normalizeUrl, isWebsocketUrl } from '@/lib/url' |
||||
import { queryService } from '@/services/client.service' |
||||
import type { Event } from 'nostr-tools' |
||||
|
||||
const BADGE_AWARD_KIND = 8 |
||||
|
||||
function addRelayUrl(out: Set<string>, raw: string | undefined, blocked: Set<string>) { |
||||
if (!raw?.trim()) return |
||||
const n = normalizeUrl(raw.trim()) || raw.trim() |
||||
if (!n || !isWebsocketUrl(n) || blocked.has(n)) return |
||||
out.add(n) |
||||
} |
||||
|
||||
/** |
||||
* Relay pool for NIP-58 definition + award fetches: profile mirrors, optional `e`-tag hint from kind 30008, |
||||
* then app profile/fast-read fallbacks. Issuer definitions often live off default “fast read” relays only. |
||||
*/ |
||||
export function mergeNip58BadgeRelayPool( |
||||
profileRelayUrls: string[], |
||||
awardRelayHint: string | undefined, |
||||
blockedRelays: string[] |
||||
): string[] { |
||||
const blocked = new Set(blockedRelays.map((u) => normalizeUrl(u) || u).filter(Boolean)) |
||||
const out = new Set<string>() |
||||
for (const u of profileRelayUrls) addRelayUrl(out, u, blocked) |
||||
addRelayUrl(out, awardRelayHint, blocked) |
||||
for (const u of PROFILE_FETCH_RELAY_URLS) addRelayUrl(out, u, blocked) |
||||
for (const u of FAST_READ_RELAY_URLS) addRelayUrl(out, u, blocked) |
||||
return [...out] |
||||
} |
||||
|
||||
export async function fetchNip58BadgeDefinition( |
||||
issuerPubkey: string, |
||||
dTag: string, |
||||
relayUrls: string[] |
||||
): Promise<Event | undefined> { |
||||
if (!relayUrls.length) return undefined |
||||
const hexPk = issuerPubkey.toLowerCase() |
||||
const events = await queryService.fetchEvents( |
||||
relayUrls, |
||||
{ |
||||
authors: [hexPk], |
||||
kinds: [ExtendedKind.BADGE_DEFINITION], |
||||
'#d': [dTag] |
||||
}, |
||||
{ |
||||
replaceableRace: true, |
||||
eoseTimeout: 4000, |
||||
globalTimeout: 22_000, |
||||
firstRelayResultGraceMs: false |
||||
} |
||||
) |
||||
const match = events.filter((e) => { |
||||
if (e.pubkey.toLowerCase() !== hexPk) return false |
||||
const d = e.tags.find((t) => t[0] === 'd')?.[1] |
||||
return d === dTag |
||||
}) |
||||
return match.sort((a, b) => b.created_at - a.created_at)[0] |
||||
} |
||||
|
||||
export async function fetchNip58BadgeAward(awardId: string, relayUrls: string[]): Promise<Event | undefined> { |
||||
if (!relayUrls.length || !/^[a-f0-9]{64}$/i.test(awardId)) return undefined |
||||
const events = await queryService.fetchEvents( |
||||
relayUrls, |
||||
{ ids: [awardId.toLowerCase()], kinds: [BADGE_AWARD_KIND] }, |
||||
{ |
||||
immediateReturn: true, |
||||
eoseTimeout: 4000, |
||||
globalTimeout: 18_000, |
||||
firstRelayResultGraceMs: false |
||||
} |
||||
) |
||||
return events.find((e) => e.id.toLowerCase() === awardId.toLowerCase()) |
||||
} |
||||
Loading…
Reference in new issue