Compare commits

..

2 commits

Author SHA1 Message Date
d802ab0bb5 Merge pull request 'refactor: remove @nostr-dev-kit/ndk from the daemon entirely (#44)' (#45) from fix/44-remove-ndk into dev
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
Reviewed-on: #45
2026-06-27 00:49:17 +00:00
056c52cb5b refactor: remove @nostr-dev-kit/ndk from the daemon entirely (#44)
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
Follow-up to #43, which swapped the relay transport off NDK. NDK no longer
touched the relay/reconnect path but lingered in non-transport helpers; this
removes it from the daemon completely. The daemon and main-entry bundles now
contain zero `@nostr-dev-kit/ndk` references.

Ported to nostr-tools:
- run.ts getKeys — `NDKPrivateKeySigner(nsec).user().npub` -> getPublicKey +
  nip19.npubEncode (via secretKeyBytes).
- admin/commands/create_new_key.ts, create_account.ts — key generation
  (generate / import / existing-npub) -> generateSecretKey / getPublicKey /
  nip19; private-key hex via Buffer.
- lib/profile.ts (setupSkeletonProfile) — kind:0/3/10002 publish via a throwaway
  RelayPool + finalizeEvent; NDKUserProfile -> local SkeletonProfile type.
- admin/index.ts notifyAdminsOfNewConnection + utils/dm-user.ts — the one-shot
  boot DM (kind:4 nip04) via a throwaway RelayPool.
- commands/start.ts nip89announcement — kind:31990 NIP-89 handler via finalizeEvent
  + RelayPool, nip05 check via nostr-tools nip05.queryProfile. (Dropped the
  fetch-existing-d-tag step: this code always uses the default d="24133", so a
  re-publish replaces the prior addressable event — same effect.)
- config/index.ts — default-admin-key generation -> generateSecretKey + hex.
- acl/index.ts, authorize.ts — type-only imports (NostrEvent/NIP46Method/Hexpubkey)
  -> nostr-tools Event / local nip46 types / string.

Also fixes a latent bug #43 surfaced here: authorize.ts's requestAuthorization
called `param.rawEvent()`, but since #43 the signer passes a plain event object
(no .rawEvent), so a sign_event approval would have recorded "[object Object]".
The nostr-tools Event type caught it; now it JSON.stringifies the object.

`@nostr-dev-kit/ndk` stays in package.json ONLY for the standalone CLI
(src/client.ts, a NIP-46 *client* — the inverse component), per #44's carve-out.
Porting the CLI to drop the dependency entirely can be a small follow-up.

Tests: lifecycle 7 / relay 2 / nip46 1 / admin 2 green; daemon + main bundles
NDK-free (0 refs); tsc at the pre-existing baseline (3 unrelated authorize.ts /
web/authorize.ts errors).

Refs: #44, #43, #42, #41
2026-06-27 02:41:22 +02:00
11 changed files with 208 additions and 176 deletions

View file

@ -3,9 +3,15 @@ import { DomainConfig, IConfig, getCurrentConfig, saveCurrentConfig } from '../c
import { decryptNsec } from '../config/keys.js'; import { decryptNsec } from '../config/keys.js';
import { fork } from 'child_process'; import { fork } from 'child_process';
import { resolve } from 'path'; import { resolve } from 'path';
import NDK, { NDKAppHandlerEvent, NDKKind, NDKPrivateKeySigner, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk'; import { finalizeEvent, getPublicKey, nip05, nip19 } from 'nostr-tools';
import { RelayPool } from '../daemon/lib/relay-pool.js';
import { secretKeyBytes } from '../daemon/nip46/transport.js';
import { debug } from 'console'; import { debug } from 'console';
// NIP-89 application-handler event kind, and the handled NIP-46 client kind.
const APP_HANDLER_KIND = 31990;
const NOSTR_CONNECT_KIND_STR = '24133';
interface IOpts { interface IOpts {
keys: string[]; keys: string[];
verbose: boolean; verbose: boolean;
@ -16,29 +22,26 @@ interface IOpts {
async function nip89announcement(configData: IConfig) { async function nip89announcement(configData: IConfig) {
const domains = configData.domains as Record<string, DomainConfig>; const domains = configData.domains as Record<string, DomainConfig>;
if (!domains) return; if (!domains) return;
const sk = secretKeyBytes(configData.admin.key);
const signerPubkey = getPublicKey(sk);
for (const [ domain, config ] of Object.entries(domains)) { for (const [ domain, config ] of Object.entries(domains)) {
const hasNip89 = !!config.nip89; if (!config.nip89) continue;
if (!hasNip89) continue;
const signer = new NDKPrivateKeySigner(configData.admin.key); const profile = config.nip89.profile;
const signerUser = await signer.user(); const relays = config.nip89.relays;
const nip05addr = `_@${domain}`;
const profile = config.nip89!.profile;
const relays = config.nip89!.relays;
const nip05 = `_@${domain}`;
const ndk = new NDK({explicitRelayUrls: relays});
// make sure the nip05 correctly points to this pubkey // make sure the nip05 correctly points to this pubkey
const uservianip05 = await NDKUser.fromNip05(nip05, ndk); const resolved = await nip05.queryProfile(nip05addr).catch(() => null);
if (!uservianip05 || uservianip05.pubkey !== signerUser.pubkey) { if (!resolved || resolved.pubkey !== signerPubkey) {
console.log(`${nip05} does not point to this nsecbunker's key`); console.log(`${nip05addr} does not point to this nsecbunker's key`);
if (uservianip05) { if (resolved) {
console.log(`${nip05} points to ${uservianip05.pubkey} instead of ${signerUser.pubkey}`) console.log(`${nip05addr} points to ${resolved.pubkey} instead of ${signerPubkey}`)
} else { } else {
console.log(`${nip05} needs to point to ${signerUser.pubkey}`) console.log(`${nip05addr} needs to point to ${signerPubkey}`)
} }
continue continue
} }
@ -55,52 +58,47 @@ async function nip89announcement(configData: IConfig) {
const hasWallet = !!config.wallet; const hasWallet = !!config.wallet;
const hasNostrdress = !!config.wallet?.lnbits?.nostdressUrl; const hasNostrdress = !!config.wallet?.lnbits?.nostdressUrl;
ndk.signer = signer; // kind:31990 (NIP-89) is addressable by (kind, author, d-tag); this code
ndk.connect(5000).then(async () => { // always uses the default d-tag "24133", so publishing replaces the prior
const event = new NDKAppHandlerEvent(ndk, { // announcement — the same effect the old fetch-existing-d-tag dance had.
tags: [ const tags: string[][] = [
[ "alt", "This is an nsecBunker announcement" ] ["alt", "This is an nsecBunker announcement"],
] ["d", NOSTR_CONNECT_KIND_STR],
} as NostrEvent); ["k", NOSTR_CONNECT_KIND_STR],
];
const operator = config.nip89!.operator; const operator = config.nip89.operator;
if (operator) { if (operator) {
try { try {
const opUser = new NDKUser({npub: operator}); tags.push(["p", nip19.decode(operator).data as string]);
event.tags.push(["p", opUser.pubkey]); } catch { /* ignore a bad operator npub */ }
} 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()]);
}
profile.nip05 = nip05;
event.content = JSON.stringify(profile);
event.tags.push(["k", NDKKind.NostrConnect.toString()])
if (hasWallet && hasNostrdress) { if (hasWallet && hasNostrdress) {
// add wallet and zaps feature tags tags.push(["f", "wallet"]);
event.tags.push(["f", "wallet"]); tags.push(["f", "zaps"]);
event.tags.push(["f", "zaps"]); }
profile.nip05 = nip05addr;
const event = finalizeEvent(
{
kind: APP_HANDLER_KIND,
created_at: Math.floor(Date.now() / 1000),
tags,
content: JSON.stringify(profile),
},
sk,
);
const pool = new RelayPool(relays, {});
pool.start();
await new Promise((r) => setTimeout(r, 3000));
try {
await pool.publish(event);
debug(`✅ Published NIP-89 announcement for ${domain}`);
} catch (e: any) {
console.log(`❌ Failed to publish NIP-89 announcement for ${domain}!`, e.message);
} finally {
pool.stop();
} }
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); }
})
} }
} }

View file

@ -1,10 +1,10 @@
import { readFileSync, writeFileSync } from 'fs'; import { readFileSync, writeFileSync } from 'fs';
import { NDKPrivateKeySigner, NDKUserProfile } from '@nostr-dev-kit/ndk'; import { generateSecretKey } from 'nostr-tools';
import { IAdminOpts } from '../daemon/admin'; import { IAdminOpts } from '../daemon/admin';
import { version } from '../../package.json'; import { version } from '../../package.json';
const generatedKey = NDKPrivateKeySigner.generate(); const generatedKeyHex = Buffer.from(generateSecretKey()).toString('hex');
export type LNBitsWalletConfig = { export type LNBitsWalletConfig = {
url: string, url: string,
@ -54,7 +54,7 @@ const defaultConfig: IConfig = {
adminRelays: [ adminRelays: [
"wss://relay.nsecbunker.com" "wss://relay.nsecbunker.com"
], ],
key: generatedKey.privateKey!, key: generatedKeyHex,
notifyAdminsOnBoot: true, notifyAdminsOnBoot: true,
}, },
database: 'sqlite://nsecbunker.db', database: 'sqlite://nsecbunker.db',

View file

@ -1,7 +1,8 @@
import { Hexpubkey, NDKPrivateKeySigner, NDKUserProfile } from "@nostr-dev-kit/ndk";
import { AdminRpcRequest } from "../types.js"; import { AdminRpcRequest } from "../types.js";
import AdminInterface from ".."; import AdminInterface from "..";
import { nip19 } from 'nostr-tools'; import { nip19, generateSecretKey, getPublicKey } from 'nostr-tools';
import type { SkeletonProfile } from "../../lib/profile.js";
type Hexpubkey = string;
import { setupSkeletonProfile } from "../../lib/profile"; import { setupSkeletonProfile } from "../../lib/profile";
import { IConfig, getCurrentConfig, saveCurrentConfig } from "../../../config"; import { IConfig, getCurrentConfig, saveCurrentConfig } from "../../../config";
import { readFileSync, writeFileSync } from "fs"; import { readFileSync, writeFileSync } from "fs";
@ -161,20 +162,20 @@ export async function createAccountReal(
await validate(currentConfig, username, domain, email); await validate(currentConfig, username, domain, email);
const nip05 = `${username}@${domain}`; const nip05 = `${username}@${domain}`;
const key = NDKPrivateKeySigner.generate(); const sk = generateSecretKey();
const profile: NDKUserProfile = { const pubkey = getPublicKey(sk);
const npub = nip19.npubEncode(pubkey);
const profile: SkeletonProfile = {
display_name: username, display_name: username,
name: username, name: username,
nip05, nip05,
...(domainConfig.defaultProfile || {}) ...(domainConfig.defaultProfile || {})
}; };
const generatedUser = await key.user(); debug(`Created user ${npub} for ${nip05}`);
debug(`Created user ${generatedUser.npub} for ${nip05}`);
// Add NIP-05 // Add NIP-05
await addNip05(currentConfig, username, domain, generatedUser.pubkey); await addNip05(currentConfig, username, domain, pubkey);
debug(`Added NIP-05 for ${nip05}`); debug(`Added NIP-05 for ${nip05}`);
@ -182,7 +183,7 @@ export async function createAccountReal(
if (domainConfig.wallet) { if (domainConfig.wallet) {
generateWallet( generateWallet(
domainConfig.wallet, domainConfig.wallet,
username, domain, generatedUser.npub username, domain, npub
).then((lnaddress) => { ).then((lnaddress) => {
debug(`wallet for ${nip05}`, {lnaddress}); debug(`wallet for ${nip05}`, {lnaddress});
if (lnaddress) profile.lud16 = lnaddress; if (lnaddress) profile.lud16 = lnaddress;
@ -190,23 +191,23 @@ export async function createAccountReal(
debug(`error generating wallet for ${nip05}`, e); debug(`error generating wallet for ${nip05}`, e);
}).finally(() => { }).finally(() => {
debug(`saving profile for ${nip05}`, profile); debug(`saving profile for ${nip05}`, profile);
setupSkeletonProfile(key, profile, email); setupSkeletonProfile(sk, profile, email);
}) })
} else { } else {
debug(`no wallet configuration for ${domain}`); debug(`no wallet configuration for ${domain}`);
// Create user profile // Create user profile
setupSkeletonProfile(key, profile, email); setupSkeletonProfile(sk, profile, email);
} }
const keyName = nip05; const keyName = nip05;
const nsec = key.nsec; const nsec = nip19.nsecEncode(sk);
currentConfig.keys[keyName] = { key: key.privateKey }; currentConfig.keys[keyName] = { key: Buffer.from(sk).toString('hex') };
saveCurrentConfig(admin.configFile, currentConfig); saveCurrentConfig(admin.configFile, currentConfig);
await admin.loadNsec!(keyName, nsec); await admin.loadNsec!(keyName, nsec);
await prisma.key.create({ data: { keyName, pubkey: generatedUser.pubkey } }); await prisma.key.create({ data: { keyName, pubkey } });
// Immediately grant access to the creator key // Immediately grant access to the creator key
// This means that the client creating this account can immediately // This means that the client creating this account can immediately
@ -219,8 +220,8 @@ export async function createAccountReal(
// request's kind so the response goes back on the same channel the // request's kind so the response goes back on the same channel the
// client subscribed for. Filed as part of aiolabs/nsecbunkerd#7 // client subscribed for. Filed as part of aiolabs/nsecbunkerd#7
// diagnosis 2026-05-27. // diagnosis 2026-05-27.
const originalKind = req.event.kind!; const originalKind = req.event.kind;
return admin.rpc.sendResponse(req.id, req.pubkey, generatedUser.pubkey, originalKind); return admin.rpc.sendResponse(req.id, req.pubkey, pubkey, originalKind);
} catch (e: any) { } catch (e: any) {
console.trace('error', e); console.trace('error', e);
const originalKind = req.event.kind!; const originalKind = req.event.kind!;

View file

@ -1,4 +1,4 @@
import NDK, { NDKEvent, NDKPrivateKeySigner, type NostrEvent } from "@nostr-dev-kit/ndk"; import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools";
import { AdminRpcRequest } from "../types.js"; import { AdminRpcRequest } from "../types.js";
import AdminInterface from "../index.js"; import AdminInterface from "../index.js";
import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js"; import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js";
@ -6,6 +6,7 @@ import { saveEncrypted } from "../../../commands/add.js";
import { getCurrentConfig } from "../../../config/index.js"; import { getCurrentConfig } from "../../../config/index.js";
import { decryptNsec } from "../../../config/keys.js"; import { decryptNsec } from "../../../config/keys.js";
import { setupSkeletonProfile } from "../../lib/profile.js"; import { setupSkeletonProfile } from "../../lib/profile.js";
import { secretKeyBytes } from "../../nip46/transport.js";
export default async function createNewKey(admin: AdminInterface, req: AdminRpcRequest) { export default async function createNewKey(admin: AdminInterface, req: AdminRpcRequest) {
const [ keyName, passphrase, _nsec ] = req.params as [ string, string, string? ]; const [ keyName, passphrase, _nsec ] = req.params as [ string, string, string? ];
@ -44,27 +45,26 @@ export default async function createNewKey(admin: AdminInterface, req: AdminRpcR
`decrypt it; refusing to overwrite (${e.message})`, `decrypt it; refusing to overwrite (${e.message})`,
); );
} }
const existingUser = await new NDKPrivateKeySigner(existingNsec).user(); const existingNpub = nip19.npubEncode(getPublicKey(secretKeyBytes(existingNsec)));
const result = JSON.stringify({ npub: existingUser.npub }); const result = JSON.stringify({ npub: existingNpub });
return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND);
} }
let key; let sk: Uint8Array;
if (_nsec) { if (_nsec) {
// NDK 3.x's `NDKPrivateKeySigner` accepts nsec1 or hex directly // secretKeyBytes accepts nsec1 or hex directly.
// (see core/src/signers/private-key/index.ts `@ai-guardrail`). sk = secretKeyBytes(_nsec);
key = new NDKPrivateKeySigner(_nsec);
} else { } else {
key = NDKPrivateKeySigner.generate(); sk = generateSecretKey();
setupSkeletonProfile(key); setupSkeletonProfile(sk);
console.log(`setting up skeleton profile for ${keyName}`); console.log(`setting up skeleton profile for ${keyName}`);
} }
const user = await key.user(); const npub = nip19.npubEncode(getPublicKey(sk));
const nsec = key.nsec; const nsec = nip19.nsecEncode(sk);
await saveEncrypted( await saveEncrypted(
admin.configFile, admin.configFile,
@ -76,7 +76,7 @@ export default async function createNewKey(admin: AdminInterface, req: AdminRpcR
await admin.loadNsec(keyName, nsec); await admin.loadNsec(keyName, nsec);
const result = JSON.stringify({ const result = JSON.stringify({
npub: user.npub, npub,
}); });
return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND); return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND);

View file

@ -1,4 +1,3 @@
import NDK, { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { getPublicKey, nip19 } from 'nostr-tools'; import { getPublicKey, nip19 } from 'nostr-tools';
import createDebug from 'debug'; import createDebug from 'debug';
import { Key, KeyUser } from '../run'; import { Key, KeyUser } from '../run';
@ -122,19 +121,19 @@ class AdminInterface {
/** /**
* Boot-time DM to the admin npubs. One-shot, best-effort notification over * Boot-time DM to the admin npubs. One-shot, best-effort notification over
* public relays not part of the reconnect-sensitive RPC path, so it still * public relays via a throwaway RelayPool (aiolabs/nsecbunkerd#44).
* uses a throwaway NDK + the existing dmUser helper. (#42 leaves this on NDK.)
*/ */
private async notifyAdminsOfNewConnection(connectionString: string) { private async notifyAdminsOfNewConnection(connectionString: string) {
const blastrNdk = new NDK({ const sk = secretKeyBytes(this.adminNsec);
explicitRelayUrls: ['wss://blastr.f7z.xyz', 'wss://nostr.mutinywallet.com'], const pool = new RelayPool(['wss://blastr.f7z.xyz', 'wss://nostr.mutinywallet.com'], {});
signer: new NDKPrivateKeySigner(this.adminNsec), pool.start();
}); // Give the connections a moment to come up before publishing.
await blastrNdk.connect(2500); await new Promise((r) => setTimeout(r, 2500));
for (const npub of this.npubs || []) { for (const npub of this.npubs || []) {
dmUser(blastrNdk, npub, `nsecBunker has started; use ${connectionString} to connect to it and unlock your key(s)`); await dmUser(sk, npub, `nsecBunker has started; use ${connectionString} to connect to it and unlock your key(s)`, pool);
} }
pool.stop();
} }
/** /**

View file

@ -1,4 +1,5 @@
import { Hexpubkey, NDKEvent, NostrEvent } from "@nostr-dev-kit/ndk"; import type { Event as NDKEvent } from "nostr-tools";
type Hexpubkey = string;
import type { Backend } from "./backend"; import type { Backend } from "./backend";
import prisma from "../db"; import prisma from "../db";
import type { Request } from "@prisma/client"; import type { Request } from "@prisma/client";
@ -59,8 +60,11 @@ async function createRecord(
) { ) {
let params: string | undefined; let params: string | undefined;
if (typeof param === 'object' && param !== null && 'rawEvent' in param) { // `param` for sign_event is the parsed event object the signer passed to the
params = JSON.stringify(param.rawEvent()); // ACL (a plain event since #43, no longer an NDKEvent with .rawEvent());
// for the encrypt/decrypt methods it's the payload string.
if (typeof param === 'object' && param !== null) {
params = JSON.stringify(param);
} else if (param) { } else if (param) {
params = param.toString(); params = param.toString();
} }

View file

@ -1,4 +1,5 @@
import type { NostrEvent, NIP46Method } from '@nostr-dev-kit/ndk'; import type { Event as NostrEvent } from 'nostr-tools';
import type { NIP46Method } from '../../nip46/types.js';
import prisma from '../../../db.js'; import prisma from '../../../db.js';
import { liveWhere } from './lifecycle.js'; import { liveWhere } from './lifecycle.js';

View file

@ -1,6 +1,7 @@
import NDK, { NDKEvent, NDKPrivateKeySigner, NostrEvent, type NDKUserProfile } from "@nostr-dev-kit/ndk"; import { finalizeEvent, getPublicKey } from "nostr-tools";
import * as CryptoJS from 'crypto-js'; import * as CryptoJS from 'crypto-js';
import createDebug from "debug"; import createDebug from "debug";
import { RelayPool } from "./relay-pool.js";
const debug = createDebug("nsecbunker:profile"); const debug = createDebug("nsecbunker:profile");
@ -12,14 +13,27 @@ const explicitRelayUrls = [
"wss://nostr.mutinywallet.com" "wss://nostr.mutinywallet.com"
]; ];
/** The handful of profile (kind:0) fields the skeleton sets. */
export interface SkeletonProfile {
display_name?: string;
about?: string;
website?: string;
image?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[k: string]: any;
}
/** /**
* Setup a skeleton profile for a new key since * Setup a skeleton profile for a new key since the experience of a completely
* the experience of a completely empty profile * empty profile is pretty bad when logging in with Coracle.
* is pretty bad when logging in with Coracle.
* *
* Ported off NDK onto nostr-tools + a throwaway RelayPool (aiolabs/nsecbunkerd#44).
* Fire-and-forget, like before: the caller doesn't await it.
*
* @param sk - the new key's secret bytes
* @param email - if provided, will fetch the gravatar * @param email - if provided, will fetch the gravatar
*/ */
export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: NDKUserProfile, email?: string) { export async function setupSkeletonProfile(sk: Uint8Array, profile?: SkeletonProfile, email?: string) {
const rand = Math.random().toString(36).substring(7); const rand = Math.random().toString(36).substring(7);
profile ??= {}; profile ??= {};
profile.display_name ??= 'New User via nsecBunker'; profile.display_name ??= 'New User via nsecBunker';
@ -39,39 +53,37 @@ export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: N
} }
} }
const user = await key.user(); const pubkey = getPublicKey(sk);
const ndk = new NDK({ const now = () => Math.floor(Date.now() / 1000);
explicitRelayUrls,
signer: key
});
await ndk.connect(2500); const pool = new RelayPool(explicitRelayUrls, {});
user.ndk = ndk; pool.start();
// Give the connections a moment to come up (NDK used connect(2500)).
await new Promise((r) => setTimeout(r, 2500));
let event = new NDKEvent(ndk, { try {
kind: 0, await pool.publish(
content: JSON.stringify(profile), finalizeEvent({ kind: 0, created_at: now(), tags: [], content: JSON.stringify(profile) }, sk),
pubkey: user.pubkey, );
} as NostrEvent); await pool.publish(
await event.sign(key); finalizeEvent(
{
const t = await event.publish();
debug(`Published to ${t.size} relays`);
event = new NDKEvent(ndk, {
kind: 3, kind: 3,
created_at: now(),
tags: [ tags: [
['p', 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52'], ['p', 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52'],
['p', user.pubkey], ['p', pubkey],
], ],
pubkey: user.pubkey, content: '',
} as NostrEvent); },
await event.sign(key); sk,
debug(`follow list event`, event.rawEvent()); ),
await event.publish(); );
await pool.publish(
const relays = new NDKEvent(ndk, { finalizeEvent(
{
kind: 10002, kind: 10002,
created_at: now(),
tags: [ tags: [
['r', 'wss://purplepag.es'], ['r', 'wss://purplepag.es'],
['r', 'wss://relay.f7z.io'], ['r', 'wss://relay.f7z.io'],
@ -80,8 +92,15 @@ export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: N
['r', 'wss://relay.nostr.band'], ['r', 'wss://relay.nostr.band'],
['r', 'wss://relay.primal.net'], ['r', 'wss://relay.primal.net'],
], ],
pubkey: user.pubkey, content: '',
} as NostrEvent); },
await relays.sign(key); sk,
await relays.publish(); ),
);
debug('published skeleton profile (kind 0/3/10002)');
} catch (e) {
debug('error publishing skeleton profile', e);
} finally {
pool.stop();
}
} }

View file

@ -2,7 +2,7 @@
* Local NIP-46 types (aiolabs/nsecbunkerd#42). * Local NIP-46 types (aiolabs/nsecbunkerd#42).
* *
* These replace the identically-named types we used to import from * These replace the identically-named types we used to import from
* `@nostr-dev-kit/ndk`, so the daemon's signing path no longer depends on NDK. * NDK, so the daemon's signing path no longer depends on NDK.
* Kept structurally identical to NDK's so the ACL callback * Kept structurally identical to NDK's so the ACL callback
* (`signingAuthorizationCallback`) and the admin layer don't have to change. * (`signingAuthorizationCallback`) and the admin layer don't have to change.
*/ */

View file

@ -1,8 +1,8 @@
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; import { nip19, getPublicKey, utils as nostrUtils } from 'nostr-tools';
import { nip19, utils as nostrUtils } from 'nostr-tools';
import { Backend } from './backend/index.js'; import { Backend } from './backend/index.js';
import { applyToken } from './backend/token-store.js'; import { applyToken } from './backend/token-store.js';
import { RelayPool } from './lib/relay-pool.js'; import { RelayPool } from './lib/relay-pool.js';
import { secretKeyBytes } from './nip46/transport.js';
import type { Nip46PermitCallback, Nip46PermitCallbackParams } from './nip46/types.js'; import type { Nip46PermitCallback, Nip46PermitCallbackParams } from './nip46/types.js';
import { checkIfPubkeyAllowed, recordSigning } from './lib/acl/index.js'; import { checkIfPubkeyAllowed, recordSigning } from './lib/acl/index.js';
import AdminInterface from './admin/index.js'; import AdminInterface from './admin/index.js';
@ -38,10 +38,10 @@ function getKeys(config: DaemonConfig) {
const keys: Key[] = []; const keys: Key[] = [];
for (const [name, nsec] of Object.entries(config.keys)) { for (const [name, nsec] of Object.entries(config.keys)) {
const user = await new NDKPrivateKeySigner(nsec).user(); const npub = nip19.npubEncode(getPublicKey(secretKeyBytes(nsec)));
const key = { const key = {
name, name,
npub: user.npub, npub,
userCount: await prisma.keyUser.count({ where: { keyName: name } }), userCount: await prisma.keyUser.count({ where: { keyName: name } }),
tokenCount: await prisma.token.count({ where: { keyName: name } }) tokenCount: await prisma.token.count({ where: { keyName: name } })
}; };

View file

@ -1,23 +1,33 @@
import NDK, { NDKUser, NDKEvent, NostrEvent } from "@nostr-dev-kit/ndk"; import { finalizeEvent, nip04, nip19 } from "nostr-tools";
import type { RelayPool } from "../daemon/lib/relay-pool.js";
export async function dmUser(ndk: NDK, recipient: NDKUser | string, content: string): Promise<NDKEvent> { /**
let targetUser; * Send a kind-4 NIP-04 DM to `recipient` (npub or hex) as `sk`, published via an
* already-started `pool`. Ported off NDK (aiolabs/nsecbunkerd#44) used only for
if (typeof recipient === 'string') { * the one-shot boot notification to admins.
targetUser = new NDKUser({ npub: recipient }); */
} else if (recipient instanceof NDKUser) { export async function dmUser(
targetUser = recipient; sk: Uint8Array,
} recipient: string,
content: string,
const event = new NDKEvent(ndk, { kind: 4, content } as NostrEvent); pool: RelayPool,
event.tag(targetUser); ): Promise<void> {
await event.encrypt(targetUser); const recipientHex = recipient.startsWith("npub1")
await event.sign(); ? (nip19.decode(recipient).data as string)
: recipient;
const ciphertext = nip04.encrypt(sk, recipientHex, content);
const event = finalizeEvent(
{
kind: 4,
created_at: Math.floor(Date.now() / 1000),
tags: [["p", recipientHex]],
content: ciphertext,
},
sk,
);
try { try {
await event.publish(); await pool.publish(event);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
return event;
} }