From 29db43e4bd800be4b4ca8f85ba2db3cb9bf8c277 Mon Sep 17 00:00:00 2001 From: digitalbase Date: Fri, 12 Dec 2025 16:15:35 +0100 Subject: [PATCH 1/3] Disabled `noNonNullAssertion` as this is used in tests a lot --- biome.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/biome.json b/biome.json index d2bfeb2ff..75b8ff398 100644 --- a/biome.json +++ b/biome.json @@ -19,6 +19,9 @@ }, "correctness": { "noUnsafeOptionalChaining": "info" + }, + "style": { + "noNonNullAssertion": "off" } } }, From 5bcef400b4446d0983f3a3069e0962706100899f Mon Sep 17 00:00:00 2001 From: digitalbase Date: Fri, 12 Dec 2025 16:17:03 +0100 Subject: [PATCH 2/3] Updated tests to use fetchUser instead of getUser --- core/src/zapper/index.test.ts | 11 ++++------- core/src/zapper/nip57.test.ts | 8 ++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/core/src/zapper/index.test.ts b/core/src/zapper/index.test.ts index 5e00ae801..478e40168 100644 --- a/core/src/zapper/index.test.ts +++ b/core/src/zapper/index.test.ts @@ -32,10 +32,8 @@ describe("NDKZapper", () => { const event = new NDKEvent(); event.ndk = ndk; - it("uses the author pubkey when the target is the user", () => { - const user = ndk.getUser({ - pubkey: "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52", - }); + it("uses the author pubkey when the target is the user", async () => { + const user = await ndk.fetchUser("fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"); const splits = new NDKZapper(user, 1000).getZapSplits(); expect(splits).toEqual([ { @@ -74,7 +72,7 @@ describe("NDKZapper", () => { }); }); -describe("getZapMethod", () => { +describe("getZapMethod", async () => { let ndk: NDK; let signer: NDKPrivateKeySigner; let user: NDKUser; @@ -85,7 +83,7 @@ describe("getZapMethod", () => { }); signer = NDKPrivateKeySigner.generate(); ndk.signer = signer; - user = ndk.getUser({ pubkey: signer.pubkey }); + user = await ndk.fetchUser(signer.pubkey); user.ndk = ndk; }); @@ -130,7 +128,6 @@ describe("getZapMethod", () => { }); // Mock both profile and mint list fetching - const _fetchProfileMock = vi.fn().mockResolvedValue(profile); ndk.fetchEvent = vi.fn().mockImplementation((filter) => { if (filter.kinds?.[0] === 0) { return Promise.resolve(profileEvent); diff --git a/core/src/zapper/nip57.test.ts b/core/src/zapper/nip57.test.ts index 261f0ade7..882e919bc 100644 --- a/core/src/zapper/nip57.test.ts +++ b/core/src/zapper/nip57.test.ts @@ -9,7 +9,7 @@ describe("generateZapRequest", () => { test("creates valid NIP-57 zap request with required fields", async () => { const signer = NDKPrivateKeySigner.generate(); const ndk = new NDK({ signer }); - const user = ndk.getUser({ pubkey: "recipient-pubkey" }); + const user = await ndk.fetchUser("recipient-pubkey"); const lnUrlData = { callback: "https://example.com/lnurlp/callback", @@ -58,7 +58,7 @@ describe("generateZapRequest", () => { test("limits relays to maximum of 4", async () => { const signer = NDKPrivateKeySigner.generate(); const ndk = new NDK({ signer }); - const user = ndk.getUser({ pubkey: "recipient-pubkey" }); + const user = await ndk.fetchUser("recipient-pubkey"); const lnUrlData = { callback: "https://example.com/callback", @@ -116,7 +116,7 @@ describe("generateZapRequest", () => { test("handles empty comment", async () => { const signer = NDKPrivateKeySigner.generate(); const ndk = new NDK({ signer }); - const user = ndk.getUser({ pubkey: "recipient-pubkey" }); + const user = await ndk.fetchUser("recipient-pubkey"); const lnUrlData = { callback: "https://example.com/callback", @@ -136,7 +136,7 @@ describe("generateZapRequest", () => { test("only includes one p-tag", async () => { const signer = NDKPrivateKeySigner.generate(); const ndk = new NDK({ signer }); - const user = ndk.getUser({ pubkey: "recipient-pubkey" }); + const user = await ndk.fetchUser("recipient-pubkey"); const lnUrlData = { callback: "https://example.com/callback", From 7a778614565c494c80864a1c173c7a132114aed9 Mon Sep 17 00:00:00 2001 From: digitalbase Date: Fri, 12 Dec 2025 16:18:37 +0100 Subject: [PATCH 3/3] Added debugging to fetchProfile and getZapInfo (because dealing with a nasty bug) --- core/src/user/index.ts | 159 ++++++++++++++++++++++++++--------------- 1 file changed, 100 insertions(+), 59 deletions(-) diff --git a/core/src/user/index.ts b/core/src/user/index.ts index af8744334..cc5fa47aa 100644 --- a/core/src/user/index.ts +++ b/core/src/user/index.ts @@ -1,14 +1,16 @@ -import { nip19 } from "nostr-tools"; - -import { NDKEvent, type NDKTag, type NostrEvent } from "../events/index.js"; -import { NDKKind } from "../events/kinds/index.js"; -import { NDKCashuMintList } from "../events/kinds/nutzap/mint-list.js"; -import type { NDKFilter, NDKRelay, NDKZapMethod, NDKZapMethodInfo } from "../index.js"; -import type { NDK } from "../ndk/index.js"; -import { NDKSubscriptionCacheUsage, type NDKSubscriptionOptions } from "../subscription/index.js"; -import { follows } from "./follows.js"; -import { getNip05For } from "./nip05.js"; -import { type NDKUserProfile, profileFromEvent, serializeProfile } from "./profile.js"; +import createDebug from "debug"; +import {nip19} from "nostr-tools"; +import {NDKEvent, type NDKTag, type NostrEvent} from "../events/index.js"; +import {NDKKind} from "../events/kinds/index.js"; +import {NDKCashuMintList} from "../events/kinds/nutzap/mint-list.js"; +import type {NDKFilter, NDKRelay, NDKZapMethod, NDKZapMethodInfo} from "../index.js"; +import type {NDK} from "../ndk/index.js"; +import {NDKSubscriptionCacheUsage, type NDKSubscriptionOptions} from "../subscription/index.js"; +import {follows} from "./follows.js"; +import {getNip05For} from "./nip05.js"; +import {type NDKUserProfile, profileFromEvent, serializeProfile} from "./profile.js"; + +const d = createDebug("ndk:user"); export type Hexpubkey = string; @@ -125,6 +127,32 @@ export class NDKUser { return { "#p": [this.pubkey] }; } + /** + * Instantiate an NDKUser from a NIP-05 string + * @param nip05Id {string} The user's NIP-05 + * @param ndk {NDK} An NDK instance + * @param skipCache {boolean} Whether to skip the cache or not + * @returns {NDKUser | undefined} An NDKUser if one is found for the given NIP-05, undefined otherwise. + */ + static async fromNip05(nip05Id: string, ndk: NDK, skipCache = false): Promise { + if (!ndk) throw new Error("No NDK instance found"); + + const opts: RequestInit = {}; + + if (skipCache) opts.cache = "no-cache"; + const profile = await getNip05For(ndk, nip05Id, ndk?.httpFetch, opts); + + if (profile) { + const user = new NDKUser({ + pubkey: profile.pubkey, + relayUrls: profile.relays, + nip46Urls: profile.nip46, + }); + user.ndk = ndk; + return user; + } + } + /** * Gets NIP-57 and NIP-61 information that this user has signaled * @@ -133,6 +161,10 @@ export class NDKUser { async getZapInfo(timeoutMs?: number): Promise> { if (!this.ndk) throw new Error("No NDK instance found"); + d("Looking for zap info", { + pubkey: this.pubkey, + }); + const promiseWithTimeout = async (promise: Promise): Promise => { if (!timeoutMs) return promise; @@ -157,13 +189,23 @@ export class NDKUser { return undefined; } }; + + d("Fetching user Profile and mint event", { + pubkey: this.pubkey, + profile: await this.fetchProfile(), + }); + const [userProfile, mintListEvent] = await Promise.all([ - promiseWithTimeout(this.fetchProfile()), - promiseWithTimeout( - this.ndk.fetchEvent({ kinds: [NDKKind.CashuMintList], authors: [this.pubkey] }), - ), + this.fetchProfile(), + this.ndk.fetchEvent({kinds: [NDKKind.CashuMintList], authors: [this.pubkey]}), ]); + d("Fetched user Profile and mint event", { + pubkey: this.pubkey, + profile: userProfile, + mintListEvent: mintListEvent, + }); + const res: Map = new Map(); if (mintListEvent) { @@ -185,36 +227,6 @@ export class NDKUser { return res; } - /** - * Instantiate an NDKUser from a NIP-05 string - * @param nip05Id {string} The user's NIP-05 - * @param ndk {NDK} An NDK instance - * @param skipCache {boolean} Whether to skip the cache or not - * @returns {NDKUser | undefined} An NDKUser if one is found for the given NIP-05, undefined otherwise. - */ - static async fromNip05( - nip05Id: string, - ndk: NDK, - skipCache = false, - ): Promise { - if (!ndk) throw new Error("No NDK instance found"); - - const opts: RequestInit = {}; - - if (skipCache) opts.cache = "no-cache"; - const profile = await getNip05For(ndk, nip05Id, ndk?.httpFetch, opts); - - if (profile) { - const user = new NDKUser({ - pubkey: profile.pubkey, - relayUrls: profile.relays, - nip46Urls: profile.nip46, - }); - user.ndk = ndk; - return user; - } - } - /** * Fetch a user's profile * @param opts {NDKSubscriptionOptions} A set of NDKSubscriptionOptions @@ -227,13 +239,17 @@ export class NDKUser { ): Promise { if (!this.ndk) throw new Error("NDK not set"); - let setMetadataEvent: NDKEvent | null = null; + d("Fetching profile", { + pubkey: this.pubkey, + }); if ( this.ndk.cacheAdapter && (this.ndk.cacheAdapter.fetchProfile || this.ndk.cacheAdapter.fetchProfileSync) && opts?.cacheUsage !== NDKSubscriptionCacheUsage.ONLY_RELAY ) { + d("Doing cache adapter stuff", {}); + let profile: NDKUserProfile | null = null; if (this.ndk.cacheAdapter.fetchProfileSync) { @@ -243,7 +259,13 @@ export class NDKUser { } if (profile) { + d("Return profile from cache", { + pubkey: this.pubkey, + profile, + }); + this.profile = profile; + return profile; } } @@ -254,27 +276,48 @@ export class NDKUser { opts.groupable ??= true; opts.groupableDelay ??= 25; + let setMetadataEvent: NDKEvent | null = null; + if (!setMetadataEvent) { - setMetadataEvent = await this.ndk.fetchEvent( - { kinds: [0], authors: [this.pubkey] }, + d("Fetching Metadata event", { + pubkey: this.pubkey, + filters: {kinds: [0], authors: [this.pubkey]}, opts, - ); + }); + + setMetadataEvent = await this.ndk.fetchEvent({kinds: [0], authors: [this.pubkey]}, opts); } - if (!setMetadataEvent) return null; + d("Finished fetching metadata event", { + pubkey: this.pubkey, + event: setMetadataEvent, + }); + + if (!setMetadataEvent) { + d("Metadata event not found", { + pubkey: this.pubkey, + }); + + return null; + } // return the most recent profile this.profile = profileFromEvent(setMetadataEvent); - if ( - storeProfileEvent && - this.profile && - this.ndk.cacheAdapter && - this.ndk.cacheAdapter.saveProfile - ) { + if (storeProfileEvent && this.profile && this.ndk.cacheAdapter && this.ndk.cacheAdapter.saveProfile) { + d("Saving profile in cache adapter", { + pubkey: this.pubkey, + }); + this.ndk.cacheAdapter.saveProfile(this.pubkey, this.profile); } + d("Returning profile", { + pubkey: this.pubkey, + setMetadataEvent, + profile: this.profile, + }); + return this.profile; } @@ -415,9 +458,7 @@ export class NDKUser { } const usersToUnfollow = Array.isArray(user) ? user : [user]; - const unfollowPubkeys = new Set( - usersToUnfollow.map((u) => (typeof u === "string" ? u : u.pubkey)), - ); + const unfollowPubkeys = new Set(usersToUnfollow.map((u) => (typeof u === "string" ? u : u.pubkey))); const newUserFollowList = new Set(); let foundAny = false;