first commit

Made-with: Cursor
This commit is contained in:
Michilis
2026-04-01 02:46:53 +00:00
commit 76210db03d
126 changed files with 20208 additions and 0 deletions

159
frontend/lib/nostr.ts Normal file
View File

@@ -0,0 +1,159 @@
import { generateSecretKey, getPublicKey as getPubKeyFromSecret } from "nostr-tools/pure";
declare global {
interface Window {
nostr?: {
getPublicKey(): Promise<string>;
signEvent(event: any): Promise<any>;
getRelays?(): Promise<Record<string, { read: boolean; write: boolean }>>;
};
}
}
export type BunkerSignerInterface = {
getPublicKey(): Promise<string>;
signEvent(event: any): Promise<any>;
close(): Promise<void>;
};
export function hasNostrExtension(): boolean {
return typeof window !== "undefined" && !!window.nostr;
}
export async function getPublicKey(): Promise<string> {
if (!window.nostr) throw new Error("No Nostr extension found");
return window.nostr.getPublicKey();
}
export async function signEvent(event: any): Promise<any> {
if (!window.nostr) throw new Error("No Nostr extension found");
return window.nostr.signEvent(event);
}
export function createAuthEvent(pubkey: string, challenge: string) {
return {
kind: 22242,
created_at: Math.floor(Date.now() / 1000),
tags: [
["relay", ""],
["challenge", challenge],
],
content: "",
pubkey,
};
}
export function shortenPubkey(pubkey: string): string {
if (!pubkey) return "";
return `${pubkey.slice(0, 8)}...${pubkey.slice(-8)}`;
}
export interface NostrProfile {
name?: string;
picture?: string;
about?: string;
nip05?: string;
displayName?: string;
}
const DEFAULT_RELAYS = [
"wss://relay.damus.io",
"wss://nos.lol",
"wss://relay.nostr.band",
];
export async function publishEvent(signedEvent: any): Promise<void> {
const { SimplePool } = await import("nostr-tools/pool");
let relayUrls: string[] = DEFAULT_RELAYS;
try {
if (window.nostr?.getRelays) {
const ext = await window.nostr.getRelays();
const write = Object.entries(ext)
.filter(([, p]) => (p as any).write)
.map(([url]) => url);
if (write.length > 0) relayUrls = write;
}
} catch {}
const pool = new SimplePool();
try {
await Promise.allSettled(pool.publish(relayUrls, signedEvent));
} finally {
pool.close(relayUrls);
}
}
export async function fetchNostrProfile(
pubkey: string,
relayUrls: string[] = DEFAULT_RELAYS
): Promise<NostrProfile> {
const { SimplePool } = await import("nostr-tools/pool");
const pool = new SimplePool();
try {
const event = await pool.get(relayUrls, {
kinds: [0],
authors: [pubkey],
});
if (!event?.content) return {};
const meta = JSON.parse(event.content);
return {
name: meta.name || meta.display_name,
displayName: meta.display_name,
picture: meta.picture,
about: meta.about,
nip05: meta.nip05,
};
} catch {
return {};
} finally {
pool.close(relayUrls);
}
}
// NIP-46: External signer (bunker:// URI)
export async function createBunkerSigner(
input: string
): Promise<{ signer: BunkerSignerInterface; pubkey: string }> {
const { BunkerSigner, parseBunkerInput } = await import("nostr-tools/nip46");
const bp = await parseBunkerInput(input);
if (!bp) throw new Error("Invalid bunker URI or NIP-05 identifier");
const clientSecretKey = generateSecretKey();
const signer = BunkerSigner.fromBunker(clientSecretKey, bp);
await signer.connect();
const pubkey = await signer.getPublicKey();
return { signer, pubkey };
}
// NIP-46: Generate a nostrconnect:// URI for QR display
export async function generateNostrConnectSetup(
relayUrls: string[] = DEFAULT_RELAYS.slice(0, 2)
): Promise<{ uri: string; clientSecretKey: Uint8Array }> {
const { createNostrConnectURI } = await import("nostr-tools/nip46");
const clientSecretKey = generateSecretKey();
const clientPubkey = getPubKeyFromSecret(clientSecretKey);
const secretBytes = crypto.getRandomValues(new Uint8Array(8));
const secret = Array.from(secretBytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
const uri = createNostrConnectURI({
clientPubkey,
relays: relayUrls,
secret,
name: "Belgian Bitcoin Embassy",
});
return { uri, clientSecretKey };
}
// NIP-46: Wait for a remote signer to connect via nostrconnect:// URI
export async function waitForNostrConnectSigner(
clientSecretKey: Uint8Array,
uri: string,
signal?: AbortSignal
): Promise<{ signer: BunkerSignerInterface; pubkey: string }> {
const { BunkerSigner } = await import("nostr-tools/nip46");
const signer = await BunkerSigner.fromURI(clientSecretKey, uri, {}, signal);
const pubkey = await signer.getPublicKey();
return { signer, pubkey };
}