From 77d8848a01a87bfe02a41508f206e8c40dc4bc55 Mon Sep 17 00:00:00 2001 From: Pablo Fernandez Date: Tue, 2 Jan 2024 12:21:56 +0000 Subject: [PATCH] improvements around nip89 announcement --- README.md | 24 ++++++++++++++ src/commands/start.ts | 77 ++++++++++++++++++++++++++++++++++++++++++- src/config/index.ts | 5 +++ 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 28b5495..efa3de3 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,18 @@ To enable this you'll need to configure a few things on your `nsecbunker.json` c "your-domain-here": { "nip05": "/your-nip05-nostr.json-file", // The location where NIP-05 entries to your domain are stored + "nip89": { + "profile": { // a kind:0-like profile + "name": "my cool nsecbunker instance", // The name of your nsecBunker instance + "about": "...", + }, + "operator": "npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft", // (optional) npub of the operator of this nsecbunker + "relays": [ // list of relays where to publush the nip89 announcement + "https://relay.damus.io", + "https://pyramid.fiatjaf.com" + ] + } + // Wallet configuration (optional) "wallet": { "lnbits": { @@ -142,6 +154,18 @@ For this to work you'll need to run, in addition to `nsecbunkerd`, an lnbits ins - [ ] TODO: Add NWC support +When booting up, the nsecbunkerd will publish a NIP-89 announcement (`kind:31990`), which is the way clients find out about your nsecbunker. + +When a bunker provides a wallet and zapping service (`wallet` and `nostdressUrl` are configured), it will add tags: +```json +{ + "tags": [ + [ "f", "wallet" ], + [ "f", "zaps" ] + ] +} +``` + # Authors * [pablof7z](nostr:npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft) diff --git a/src/commands/start.ts b/src/commands/start.ts index 7e6619f..8b0e75d 100644 --- a/src/commands/start.ts +++ b/src/commands/start.ts @@ -1,8 +1,10 @@ import readline from 'readline'; -import { getCurrentConfig, saveCurrentConfig } from '../config/index.js'; +import { DomainConfig, IConfig, getCurrentConfig, saveCurrentConfig } from '../config/index.js'; import { decryptNsec } from '../config/keys.js'; import { fork } from 'child_process'; import { resolve } from 'path'; +import NDK, { NDKAppHandlerEvent, NDKKind, NDKPrivateKeySigner, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk'; +import { debug } from 'console'; interface IOpts { keys: string[]; @@ -11,6 +13,77 @@ interface IOpts { adminNpubs: string[]; } +async function nip89announcement(configData: IConfig) { + const domains = configData.domains as Record; + for (const [ domain, config ] of Object.entries(domains)) { + const hasNip89 = !!config.nip89; + if (!hasNip89) continue; + + const profile = config.nip89!.profile; + const relays = config.nip89!.relays; + + if (!profile) { + console.log(`❌ No NIP-89 profile in configuration of ${domain}!`); + continue + } + + if (!relays || relays.length === 0) { + console.log(`❌ No relays in NIP-89 configuration of ${domain}!`); + continue + } + + const hasWallet = !!config.wallet; + const hasNostrdress = !!config.wallet?.lnbits?.nostdressUrl; + + const ndk = new NDK({explicitRelayUrls: relays}); + ndk.signer = new NDKPrivateKeySigner(configData.admin.key); + ndk.connect(5000).then(async () => { + const event = new NDKAppHandlerEvent(ndk, { + tags: [ + [ "alt", "This is an nsecBunker announcement" ] + ] + } as NostrEvent); + + const operator = config.nip89!.operator; + if (operator) { + try { + const opUser = new NDKUser({npub: operator}); + event.tags.push(["p", opUser.pubkey]); + } catch {} + } + + try { + const user = await ndk.signer!.user(); + const existingEvent = await ndk.fetchEvent({ + authors: [user.pubkey], + kinds: [NDKKind.AppHandler], + "#k": [NDKKind.NostrConnect.toString()] + }); + + if (existingEvent) { + debug(`🔍 Found existing NIP-89 announcement for ${domain}:`, existingEvent.encode()); + // update existing event + const dTag = existingEvent.tagValue("d"); + event.tags.push(["d", dTag!]) + } else { + debug(`🔍 No existing NIP-89 announcement for ${domain} found.`); + event.tags.push(["d", NDKKind.NostrConnect.toString()]); + } + + event.content = JSON.stringify(profile); + event.tags.push(["k", NDKKind.NostrConnect.toString()]) + if (hasWallet && hasNostrdress) { + // add wallet and zaps feature tags + event.tags.push(["f", "wallet"]); + event.tags.push(["f", "zaps"]); + } + await event.publish(); + debug(`✅ Published NIP-89 announcement for ${domain}:`, event.encode()); + } catch(e: any) { console.log(`❌ Failed to publish NIP-89 announcement for ${domain}!`, e.message); } + }) + } +} + /** * This command starts the nsecbunkerd process with an (optional) * admin interface over websockets or redis. @@ -24,6 +97,8 @@ export async function start(opts: IOpts) { await saveCurrentConfig(opts.config, configData); + nip89announcement(configData); + if (opts.verbose) { configData.verbose = opts.verbose; } diff --git a/src/config/index.ts b/src/config/index.ts index a3ee162..5622fa0 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -18,6 +18,11 @@ export interface IWalletConfig { export interface DomainConfig { nip05: string; + nip89?: { + profile: Record; + operator?: string; + relays: string[]; + }, wallet?: IWalletConfig; defaultProfile?: Record; };