diff --git a/lite/README.md b/lite/README.md
new file mode 100644
index 0000000..30a070b
--- /dev/null
+++ b/lite/README.md
@@ -0,0 +1,260 @@
+# NOSTR_LOGIN_LITE
+
+A minimal, dependency-light replacement for the current auth/UI stack that preserves all login methods and window.nostr surface.
+
+## Features
+
+- ✅ Single file distributable (`nostr-login-lite.bundle.js`)
+- ✅ All major login methods: Extension, Local key, Read-only
+- ✅ Compatible window.nostr facade
+- ✅ NIP-04 and NIP-44 encryption (local)
+- ✅ Minimal vanilla JavaScript modal
+- ✅ No bulky frameworks (no Stencil/Tailwind)
+- ✅ Just ~50KB gzipped (minus nostr-tools)
+
+## Installation
+
+```html
+
+
+
+
+
+```
+
+## Quick Start
+
+```javascript
+// Initialize with default options
+await window.NOSTR_LOGIN_LITE.init({
+ theme: 'light',
+ relays: ['wss://relay.damus.io'],
+ methods: {
+ extension: true,
+ local: true,
+ readonly: true
+ }
+});
+
+// Launch login modal
+window.NOSTR_LOGIN_LITE.launch();
+```
+
+## API Reference
+
+### Initialization
+```javascript
+// Initialize the library
+await window.NOSTR_LOGIN_LITE.init(options)
+
+// Options
+{
+ theme: 'light' | 'dark',
+ darkMode: boolean,
+ relays: string[], // Default relays for NIP-46
+ methods: {
+ connect: boolean, // NIP-46 providers (coming)
+ extension: boolean, // Browser extensions
+ local: boolean, // Local key generation
+ readonly: boolean, // Read-only mode
+ otp: boolean // OTP/DM (coming)
+ }
+}
+```
+
+### User Interface
+```javascript
+// Launch authentication modal
+window.NOSTR_LOGIN_LITE.launch(startScreen);
+
+// Available screens
+'login' // Auth selection
+'signup' // Account creation
+'switch' // Account switching
+
+// Logout current user
+window.NOSTR_LOGIN_LITE.logout();
+
+// Set theme
+window.NOSTR_LOGIN_LITE.setDarkMode(dark: boolean);
+```
+
+### Programmatic Auth
+```javascript
+// Set authentication manually
+window.NOSTR_LOGIN_LITE.setAuth({
+ type: 'login' | 'signup' | 'logout',
+ method: 'extension' | 'local' | 'readonly' | 'connect' | 'otp',
+ pubkey: string, // Public key
+ secret: string // Private key (optional)
+});
+
+// Cancel ongoing auth flow
+window.NOSTR_LOGIN_LITE.cancelNeedAuth();
+```
+
+### window.nostr Compatibility
+
+All standard window.nostr methods work transparently:
+
+```javascript
+// Get current user's public key
+const pubkey = await window.nostr.getPublicKey();
+
+// Sign an event
+const signed = await window.nostr.signEvent({
+ kind: 1,
+ content: 'Hello Nostr!',
+ tags: [],
+ created_at: Date.now()
+});
+
+// NIP-04 encrypt/decrypt
+const encrypted = await window.nostr.nip04.encrypt(recipientPubkey, 'secret');
+const decrypted = await window.nostr.nip04.decrypt(senderPubkey, encrypted);
+
+// NIP-44 encrypt/decrypt (if available in nostr-tools)
+const encrypted = await window.nostr.nip44.encrypt(recipientPubkey, 'secret');
+const decrypted = await window.nostr.nip44.decrypt(senderPubkey, encrypted);
+```
+
+## Events
+
+Listen for authentication events:
+
+```javascript
+// Auth state changes
+window.addEventListener('nlAuth', (event) => {
+ console.log(event.detail);
+ // { type: 'login', pubkey: '...', method: 'local' }
+});
+
+// Dark mode changes
+window.addEventListener('nlDarkMode', (event) => {
+ document.body.classList.toggle('dark', event.detail.dark);
+});
+
+// Auth URLs (for NIP-46)
+window.addEventListener('nlAuthUrl', (event) => {
+ console.log('Auth URL:', event.detail.url);
+});
+
+// Logout events
+window.addEventListener('nlLogout', () => {
+ console.log('User logged out');
+});
+```
+
+## Architecture
+
+```
+NOSTR_LOGIN_LITE/
+├── nostr-login-lite.bundle.js # Single distributable
+├── core/
+│ ├── nip46-client.js # NIP-46 transport over websockets
+│ └── {other components} # Future components
+├── ui/
+│ └── modal.js # Vanilla JS modal
+└── README.md # This file
+```
+
+### Key Components
+
+- **Modal**: Lightweight modal using vanilla CSS
+- **Store**: localStorage helpers for accounts/relays
+- **LocalSigner**: Wraps nostr-tools for local signing
+- **ExtensionBridge**: Detects and bridges browser extensions
+- **NIP46Client**: Handles remote signing (NIP-46)
+- **Relays**: Default relay management
+
+## Browser Support
+
+- Modern browsers with ES2018+ support
+- WebWorkers (for NIP-46)
+- localStorage (for account storage)
+
+## Dependencies
+
+This project uses a local copy of nostr-tools instead of loading from CDN:
+
+- **nostr.bundle.js**: Local nostr-tools bundle (~200KB)
+- **nostr-login-lite.bundle.js**: This library (~50KB gzipped)
+
+## Size Comparison
+
+```
+nostr-tools ~200KB (served locally)
+NOSTR_LOGIN_LITE ~50KB (gzipped)
+Total: ~250KB
+
+vs.
+
+Full nostr-login: ~2.5MB+ framework dependencies
+```
+
+## Future Roadmap ⚠️
+
+The following features are planned but not yet implemented:
+
+- **NIP-46 Connect**: External signer integration
+- **OTP/DM**: Server-side OTP delivery
+- **Connect Wallet**: Wallet integration
+- **Advanced UI**: Multi-screen flows
+- **NIP-05 Profiles**: User discovery
+- **Backup/Restore**: Key backup functionality
+
+## Development
+
+To work on the source files:
+
+```bash
+# Edit individual components
+lite/core/nip46-client.js
+lite/ui/modal.js
+lite/nostr-login-lite.js
+
+# Run bundler to create distribution
+node lite/bundler.js
+
+# Start dev server (from project root)
+python3 -m http.server 8000
+
+# Open test page
+open http://localhost:8000/examples/simple-demo.html
+```
+
+### Local Bundle Setup
+
+The project uses a local copy of nostr-tools:
+- `lite/nostr.bundle.js` - Local nostr-tools bundle (serves from this location)
+- `nostr-tools/` - Reference directory (excluded from git, not used in runtime)
+- Examples load from: `../lite/nostr.bundle.js`
+
+## Examples
+
+See `examples/` directory:
+- `simple-demo.html` - Basic functionality demo
+- `full-test.html` - Comprehensive test suite
+- `test-lite.html` - Minimal functionality test
+
+## Migration from Full nostr-login
+
+The lite version provides the same API surface, but is much smaller:
+
+```javascript
+// Before (full library)
+import { init, launch, logout } from 'nostr-login';
+
+// After (lite version)
+await window.NOSTR_LOGIN_LITE.init();
+window.NOSTR_LOGIN_LITE.launch();
+window.NOSTR_LOGIN_LITE.logout();
+```
+
+## License
+
+Same license as the original nostr-login project.
+
+## Contributing
+
+This is meant to be lightweight and focused. Contributions should maintain the minimal footprint while extending functionality.
\ No newline at end of file
diff --git a/lite/bundler-clean.js b/lite/bundler-clean.js
new file mode 100644
index 0000000..b7388c9
--- /dev/null
+++ b/lite/bundler-clean.js
@@ -0,0 +1,97 @@
+/**
+ * Clean bundler for NOSTR_LOGIN_LITE
+ * Removes problematic files and recreates bundle
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+async function createCleanBundle() {
+ // First, remove the old bundle if it exists
+ const outputPath = path.join(__dirname, 'nostr-login-lite.bundle.js');
+ try {
+ if (fs.existsSync(outputPath)) {
+ fs.unlinkSync(outputPath);
+ }
+ } catch (e) {
+ console.log('No old bundle to remove');
+ }
+
+ const mainFile = path.join(__dirname, 'nostr-login-lite.js');
+ const nip46File = path.join(__dirname, 'core/nip46-client.js');
+ const modalFile = path.join(__dirname, 'ui/modal.js');
+
+ // Start with a clean header
+ let bundle = `/**
+ * NOSTR_LOGIN_LITE
+ * Single-file Nostr authentication library
+ * Generated on: ${new Date().toISOString()}
+ */
+
+`;
+
+ // Add section markers and combine files
+ const files = [
+ { path: modalFile, name: 'modal.js' },
+ { path: nip46File, name: 'nip46-client.js' },
+ { path: mainFile, name: 'nostr-login-lite.js' }
+ ];
+
+ for (const file of files) {
+ if (fs.existsSync(file.path)) {
+ const content = fs.readFileSync(file.path, 'utf8');
+
+ bundle += `\n// ======================================\n`;
+ bundle += `// ${file.name}\n`;
+ bundle += `// ======================================\n\n`;
+
+ // Clean the content by removing initial header comments
+ let lines = content.split('\n');
+ let contentStartIndex = 0;
+
+ // Skip the first 10 lines if they contain file headers
+ for (let i = 0; i < Math.min(10, lines.length); i++) {
+ const line = lines[i].trim();
+ if (line.startsWith('/**') || line.startsWith('*') || line.startsWith('/*') || line.startsWith('//') ||
+ line.includes('Copyright') || line.includes('@license') || line.includes('Licensed') || line.includes('©')) {
+ contentStartIndex = i + 1;
+ }
+ }
+
+ if (contentStartIndex > 0) {
+ lines = lines.slice(contentStartIndex);
+ }
+
+ bundle += lines.join('\n');
+ bundle += '\n\n';
+
+ console.log(`Added ${file.name}`);
+ } else {
+ console.warn(`File not found: ${file.path}`);
+ }
+ }
+
+ // Write the bundled file
+ fs.writeFileSync(outputPath, bundle, 'utf8');
+
+ const sizeKB = (bundle.length / 1024).toFixed(2);
+ console.log(`\n✅ Clean bundle created: ${outputPath}`);
+ console.log(`📏 Bundle size: ${sizeKB} KB`);
+ console.log(`📄 Total lines: ${bundle.split('\n').length}`);
+
+ // Verify the bundle starts correctly
+ const firstLines = bundle.split('\n').slice(0, 20).join('\n');
+ console.log('\n📋 First 20 lines:');
+ console.log(firstLines);
+
+ return bundle;
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = { createCleanBundle };
+}
+
+// Run if called directly
+if (typeof require !== 'undefined' && require.main === module) {
+ createCleanBundle().catch(console.error);
+}
\ No newline at end of file
diff --git a/lite/bundler.js b/lite/bundler.js
new file mode 100644
index 0000000..8069c05
--- /dev/null
+++ b/lite/bundler.js
@@ -0,0 +1,86 @@
+/**
+ * Simple bundler for NOSTR_LOGIN_LITE
+ * Combines all files into a single distributable script
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+async function bundleLite() {
+ const mainFile = path.join(__dirname, 'nostr-login-lite.js');
+ const nip46File = path.join(__dirname, 'core/nip46-client.js');
+ const modalFile = path.join(__dirname, 'ui/modal.js');
+
+ let bundle = `/**
+ * NOSTR_LOGIN_LITE
+ * Single-file Nostr authentication library
+ * Generated on: ${new Date().toISOString()}
+ */
+
+// ======================================
+// Core Classes and Components
+// ======================================
+`;
+
+ // Read and combine files
+ const files = [modalFile, nip46File, mainFile];
+
+ for (const file of files) {
+ if (fs.existsSync(file)) {
+ let content = fs.readFileSync(file, 'utf8');
+
+ // Skip the initial comment and license if present
+ let lines = content.split('\n');
+
+ // Find and skip complete JSDoc blocks at the beginning
+ let skipUntil = 0;
+ let inJSDocBlock = false;
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i].trim();
+
+ if (line.startsWith('/**')) {
+ inJSDocBlock = true;
+ skipUntil = i;
+ } else if (inJSDocBlock && line.startsWith('*/')) {
+ skipUntil = i;
+ break;
+ } else if (i < 10 && (line.startsWith('const') || line.startsWith('class') || line.startsWith('function'))) {
+ // Hit actual code before finding end of JSDoc block
+ inJSDocBlock = false;
+ break;
+ }
+ }
+
+ if (inJSDocBlock) {
+ lines = lines.slice(skipUntil + 1); // Skip the entire JSDoc block
+ } else {
+ // Fallback to old filtering (skip comment-like lines in first 10)
+ lines = lines.filter((line, index) => {
+ return index >= 10 || !line.trim().startsWith('*') && !line.trim().startsWith('//');
+ });
+ }
+
+ bundle += '\n// ======================================\n';
+ bundle += `// ${path.basename(file)}\n`;
+ bundle += '// ======================================\n\n';
+ bundle += lines.join('\n');
+ bundle += '\n\n';
+ }
+ }
+
+ // Write the bundled file
+ const outputPath = path.join(__dirname, 'nostr-login-lite.bundle.js');
+ fs.writeFileSync(outputPath, bundle);
+
+ console.log('Bundle created:', outputPath);
+ console.log('Bundle size:', (bundle.length / 1024).toFixed(2), 'KB');
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = { bundleLite };
+}
+
+// Run if called directly
+if (typeof require !== 'undefined' && require.main === module) {
+ bundleLite().catch(console.error);
+}
\ No newline at end of file
diff --git a/lite/core/nip46-client.js b/lite/core/nip46-client.js
new file mode 100644
index 0000000..208f1c0
--- /dev/null
+++ b/lite/core/nip46-client.js
@@ -0,0 +1,398 @@
+/**
+ * NOSTR NIP-46 Client Implementation
+ * Minimal RPC over NostrTools.SimplePool for NOSTR_LOGIN_LITE
+ */
+
+class NIP46Client {
+ constructor() {
+ this.pool = null;
+ this.localSk = null;
+ this.localPk = null;
+ this.remotePk = null;
+ this.relays = [];
+ this.sub = null;
+ this.pendingRequests = {};
+ this.useNip44 = false;
+ this.iframeOrigin = null;
+ this.iframePort = null;
+ }
+
+ init(localSk, remotePk, relays, iframeOrigin) {
+ // Create SimplePool
+ this.pool = new window.NostrTools.SimplePool();
+
+ // Setup keys
+ this.localSk = localSk;
+ if (this.localSk) {
+ this.localPk = window.NostrTools.getPublicKey(this.localSk);
+ }
+
+ this.remotePk = remotePk;
+ this.relays = [...relays];
+
+ // Store iframe origin for future use
+ this.iframeOrigin = iframeOrigin;
+
+ console.log('NIP46Client initialized for', this.remotePk ? 'remote signer' : 'listening mode');
+ }
+
+ setUseNip44(use) {
+ this.useNip44 = use;
+ }
+
+ subscribeReplies() {
+ if (!this.pool || !this.localPk) return;
+
+ // Subscribe to replies to our pubkey on kind 24133 (NIP-46 methods)
+ this.sub = this.pool.sub(this.relays, [{
+ kinds: [24133],
+ '#p': [this.localPk]
+ }]);
+
+ this.sub.on('event', (event) => this.onEvent(event));
+ this.sub.on('eose', () => {
+ console.log('NIP-46 subscription caught up');
+ });
+
+ console.log('Subscribed to NIP-46 replies on relays:', this.relays);
+ }
+
+ unsubscribe() {
+ if (this.sub) {
+ this.sub.unsub();
+ this.sub = null;
+ }
+ }
+
+ async onEvent(event) {
+ console.log('NIP-46 event received:', event);
+
+ try {
+ const parsed = await this.parseEvent(event);
+ if (parsed) {
+ if (parsed.id && this.pendingRequests[parsed.id]) {
+ // Handle response
+ const handler = this.pendingRequests[parsed.id];
+ delete this.pendingRequests[parsed.id];
+
+ if (parsed.result !== undefined) {
+ handler.resolve(parsed.result);
+ } else if (parsed.error) {
+ handler.reject(new Error(parsed.error));
+ } else {
+ handler.reject(new Error('Invalid response format'));
+ }
+ } else if (parsed.method === 'auth_url') {
+ // Handle auth_url emissions (deduplication required)
+ this.emitAuthUrlIfNeeded(parsed.params[0]);
+ }
+ }
+ } catch (error) {
+ console.error('Error processing NIP-46 event:', error);
+ }
+ }
+
+ emitAuthUrlIfNeeded(url) {
+ // Deduplicate auth_url emissions - only emit if not recently shown
+ const lastUrl = sessionStorage.getItem('nl-last-auth-url');
+ if (lastUrl === url) {
+ console.log('Auth URL already shown, skipping duplicate:', url);
+ return;
+ }
+
+ sessionStorage.setItem('nl-last-auth-url', url);
+ console.log('New auth URL:', url);
+
+ // Emit event for UI
+ window.dispatchEvent(new CustomEvent('nlAuthUrl', { detail: { url } }));
+ }
+
+ async parseEvent(event) {
+ try {
+ let content = event.content;
+
+ // Determine encryption method based on content structure
+ if (content.length > 44) {
+ // Likely NIP-44 (encrypted)
+ if (this.localSk && event.pubkey) {
+ try {
+ content = window.NostrTools.nip44?.decrypt(this.localSk, event.pubkey, content);
+ } catch (e) {
+ console.warn('NIP-44 decryption failed, trying NIP-04...');
+ content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content);
+ }
+ }
+ } else {
+ // Likely NIP-04
+ if (this.localSk && event.pubkey) {
+ content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content);
+ }
+ }
+
+ const payload = JSON.parse(content);
+ console.log('Decrypted NIP-46 payload:', payload);
+
+ return {
+ id: payload.id,
+ method: payload.method,
+ params: payload.params,
+ result: payload.result,
+ error: payload.error,
+ event: event
+ };
+
+ } catch (e) {
+ console.error('Failed to parse event:', e);
+ return null;
+ }
+ }
+
+ async listen(nostrConnectSecret) {
+ return new Promise((resolve, reject) => {
+ if (!this.localPk) {
+ reject(new Error('No local pubkey available for listening'));
+ return;
+ }
+
+ // Subscribe to unsolicited events to our pubkey
+ let foundSecretOrAck = false;
+
+ const listenSub = this.pool.sub(this.relays, [{
+ kinds: [24133],
+ '#p': [this.localPk]
+ }]);
+
+ listenSub.on('event', async (event) => {
+ try {
+ const parsed = await this.parseEvent(event);
+ if (parsed && parsed.method === 'connect') {
+ // Accept if it's an ack or matches our secret
+ const [userPubkey, token] = parsed.params || [];
+
+ if (token === '' && parsed.result === 'ack') {
+ // Ack received
+ foundSecretOrAck = true;
+ listenSub.unsub();
+ resolve(event.pubkey);
+ } else if (token === nostrConnectSecret) {
+ // Secret match
+ foundSecretOrAck = true;
+ listenSub.unsub();
+ resolve(event.pubkey);
+ }
+ }
+ } catch (error) {
+ console.error('Error in listen mode:', error);
+ }
+ });
+
+ // Timeout after 5 minutes
+ setTimeout(() => {
+ if (!foundSecretOrAck) {
+ listenSub.unsub();
+ reject(new Error('Listen timeout - no signer connected'));
+ }
+ }, 300000);
+ });
+ }
+
+ async connect(token, perms) {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const result = await this.sendRequest(
+ this.remotePk,
+ 'connect',
+ [this.localPk, token || '', perms || ''],
+ 24133,
+ (response) => {
+ if (response === 'ack') {
+ resolve(true);
+ } else {
+ reject(new Error('Connection not acknowledged'));
+ }
+ }
+ );
+
+ // Set 30 second timeout
+ setTimeout(() => reject(new Error('Connection timeout')), 30000);
+
+ } catch (error) {
+ reject(error);
+ }
+ });
+ }
+
+ async initUserPubkey(hint) {
+ if (hint) {
+ this.remotePk = hint;
+ return hint;
+ }
+
+ if (!this.remotePk) {
+ // Request get_public_key
+ return new Promise(async (resolve, reject) => {
+ try {
+ const pubkey = await this.sendRequest(
+ this.remotePk,
+ 'get_public_key',
+ [],
+ 24133
+ );
+ this.remotePk = pubkey;
+ resolve(pubkey);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ }
+
+ return this.remotePk;
+ }
+
+ async sendRequest(remotePubkey, method, params, kind = 24133, cb) {
+ if (!this.pool || !this.localSk || !this.localPk) {
+ throw new Error('NIP46Client not properly initialized');
+ }
+
+ if (!remotePubkey) {
+ throw new Error('No remote pubkey specified');
+ }
+
+ const id = this._generateId();
+
+ // Create request event
+ const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
+
+ console.log('Sending NIP-46 request:', { id, method, params });
+
+ // Publish to relays
+ const pubs = await this.pool.publish(this.relays, event);
+ console.log('Published to relays, waiting for response...');
+
+ return new Promise((resolve, reject) => {
+ // Set timeout
+ const timeout = setTimeout(() => {
+ console.error('NIP-46 request timeout for id:', id);
+ delete this.pendingRequests[id];
+ reject(new Error(`Request timeout for ${method}`));
+ }, 60000); // 1 minute timeout
+
+ // Store handler
+ this.pendingRequests[id] = {
+ resolve: (result) => {
+ clearTimeout(timeout);
+ resolve(result);
+ },
+ reject: (error) => {
+ clearTimeout(timeout);
+ reject(error);
+ },
+ timestamp: Date.now()
+ };
+
+ // If callback provided, override resolve
+ if (cb) {
+ const originalResolve = this.pendingRequests[id].resolve;
+ this.pendingRequests[id].resolve = (result) => {
+ cb(result);
+ originalResolve(result);
+ };
+ }
+ });
+ }
+
+ async createRequestEvent(id, remotePubkey, method, params, kind = 24133) {
+ let content = JSON.stringify({
+ id,
+ method,
+ params
+ });
+
+ // Choose encryption method
+ let encrypted = content;
+ if (method !== 'create_account') {
+ // Use NIP-44 for non-account creation methods if available
+ if (this.useNip44 && window.NostrTools.nip44) {
+ encrypted = window.NostrTools.nip44.encrypt(this.localSk, remotePubkey, content);
+ } else {
+ // Fallback to NIP-04
+ encrypted = await window.NostrTools.nip04.encrypt(this.localSk, remotePubkey, content);
+ }
+ }
+
+ // Create event structure
+ const event = {
+ kind: kind,
+ content: encrypted,
+ tags: [
+ ['p', remotePubkey]
+ ],
+ created_at: Math.floor(Date.now() / 1000),
+ pubkey: this.localPk,
+ id: '', // Will be set by finalizeEvent
+ sig: '' // Will be set by finalizeEvent
+ };
+
+ // Sign the event
+ const signedEvent = window.NostrTools.finalizeEvent(event, this.localSk);
+
+ return signedEvent;
+ }
+
+ _generateId() {
+ return 'nl-' + Date.now() + '-' + Math.random().toString(36).substring(2, 15);
+ }
+
+ setWorkerIframePort(port) {
+ this.iframePort = port;
+
+ // Set up postMessage routing if needed
+ if (this.iframePort && this.iframeOrigin) {
+ this.iframePort.onmessage = (event) => {
+ if (event.origin !== this.iframeOrigin) {
+ console.warn('Ignoring message from unknown origin:', event.origin);
+ return;
+ }
+
+ console.log('Received iframe message:', event.data);
+ // Handle iframe messages
+ };
+
+ // Send keepalive
+ setInterval(() => {
+ if (this.iframePort) {
+ try {
+ this.iframePort.postMessage({ type: 'ping' });
+ } catch (e) {
+ console.warn('Iframe port closed');
+ this.iframePort = null;
+ }
+ }
+ }, 30000); // 30 seconds
+ }
+ }
+
+ teardown() {
+ this.unsubscribe();
+
+ if (this.iframePort) {
+ try {
+ this.iframePort.close();
+ } catch (e) {
+ console.warn('Error closing iframe port:', e);
+ }
+ this.iframePort = null;
+ }
+
+ if (this.pool) {
+ this.pool.close(this.relays);
+ this.pool = null;
+ }
+
+ // Clear all pending requests
+ for (const id in this.pendingRequests) {
+ this.pendingRequests[id].reject(new Error('Client teardown'));
+ }
+ this.pendingRequests = {};
+ }
+}
\ No newline at end of file
diff --git a/lite/nostr-login-lite.bundle.js b/lite/nostr-login-lite.bundle.js
new file mode 100644
index 0000000..6f55a95
--- /dev/null
+++ b/lite/nostr-login-lite.bundle.js
@@ -0,0 +1,2339 @@
+/**
+ * NOSTR_LOGIN_LITE
+ * Single-file Nostr authentication library
+ * Generated on: 2025-09-13T13:03:05.960Z
+ */
+
+// ======================================
+// Core Classes and Components
+// ======================================
+
+// ======================================
+// modal.js
+// ======================================
+
+
+class Modal {
+ constructor(options) {
+ this.options = options;
+ this.container = null;
+ this.isVisible = false;
+ this.currentScreen = null;
+
+ // Initialize modal container and styles
+ this._initModal();
+ }
+
+ _initModal() {
+ // Create modal container
+ this.container = document.createElement('div');
+ this.container.id = 'nl-modal';
+ this.container.style.cssText = `
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.75);
+ display: none;
+ z-index: 10000;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ `;
+
+ // Create modal content
+ const modalContent = document.createElement('div');
+ modalContent.style.cssText = `
+ position: relative;
+ background: white;
+ width: 90%;
+ max-width: 400px;
+ margin: 50px auto;
+ border-radius: 12px;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+ max-height: 600px;
+ overflow: hidden;
+ `;
+
+ // Header
+ const modalHeader = document.createElement('div');
+ modalHeader.style.cssText = `
+ padding: 20px 24px 0 24px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ `;
+
+ const modalTitle = document.createElement('h2');
+ modalTitle.textContent = 'Nostr Login';
+ modalTitle.style.cssText = `
+ margin: 0;
+ font-size: 24px;
+ font-weight: 600;
+ color: #1f2937;
+ `;
+
+ const closeButton = document.createElement('button');
+ closeButton.innerHTML = '×';
+ closeButton.onclick = () => this.close();
+ closeButton.style.cssText = `
+ background: none;
+ border: none;
+ font-size: 28px;
+ color: #6b7280;
+ cursor: pointer;
+ padding: 0;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ `;
+ closeButton.onmouseover = () => closeButton.style.background = '#f3f4f6';
+ closeButton.onmouseout = () => closeButton.style.background = 'none';
+
+ modalHeader.appendChild(modalTitle);
+ modalHeader.appendChild(closeButton);
+
+ // Body
+ this.modalBody = document.createElement('div');
+ this.modalBody.style.cssText = `
+ padding: 24px;
+ overflow-y: auto;
+ max-height: 500px;
+ `;
+
+ modalContent.appendChild(modalHeader);
+ modalContent.appendChild(this.modalBody);
+ this.container.appendChild(modalContent);
+
+ // Add to body
+ document.body.appendChild(this.container);
+
+ // Click outside to close
+ this.container.onclick = (e) => {
+ if (e.target === this.container) {
+ this.close();
+ }
+ };
+
+ // Update theme
+ this.updateTheme();
+ }
+
+ updateTheme() {
+ const isDark = this.options?.darkMode;
+ const modalContent = this.container.querySelector(':nth-child(1)');
+ const title = this.container.querySelector('h2');
+
+ if (isDark) {
+ modalContent.style.background = '#1f2937';
+ title.style.color = 'white';
+ } else {
+ modalContent.style.background = 'white';
+ title.style.color = '#1f2937';
+ }
+ }
+
+ open(opts = {}) {
+ this.currentScreen = opts.startScreen;
+ this.isVisible = true;
+ this.container.style.display = 'block';
+
+ // Render login options
+ this._renderLoginOptions();
+ }
+
+ close() {
+ this.isVisible = false;
+ this.container.style.display = 'none';
+ this.modalBody.innerHTML = '';
+ }
+
+ _renderLoginOptions() {
+ this.modalBody.innerHTML = '';
+
+ const options = [];
+
+ // Extension option
+ if (this.options?.methods?.extension !== false) {
+ options.push({
+ type: 'extension',
+ title: 'Browser Extension',
+ description: 'Use your browser extension',
+ icon: '🔌'
+ });
+ }
+
+ // Local key option
+ if (this.options?.methods?.local !== false) {
+ options.push({
+ type: 'local',
+ title: 'Local Key',
+ description: 'Create or import your own key',
+ icon: '🔑'
+ });
+ }
+
+ // Nostr Connect option
+ if (this.options?.methods?.connect !== false) {
+ options.push({
+ type: 'connect',
+ title: 'Nostr Connect',
+ description: 'Connect with external signer',
+ icon: '🌐'
+ });
+ }
+
+ // Read-only option
+ if (this.options?.methods?.readonly !== false) {
+ options.push({
+ type: 'readonly',
+ title: 'Read Only',
+ description: 'Browse without signing',
+ icon: '👁️'
+ });
+ }
+
+ // OTP/DM option
+ if (this.options?.methods?.otp !== false && this.options?.otp) {
+ options.push({
+ type: 'otp',
+ title: 'DM/OTP',
+ description: 'Receive OTP via DM',
+ icon: '📱'
+ });
+ }
+
+ // Render each option
+ options.forEach(option => {
+ const button = document.createElement('button');
+ button.onclick = () => this._handleOptionClick(option.type);
+ button.style.cssText = `
+ display: flex;
+ align-items: center;
+ width: 100%;
+ padding: 16px;
+ margin-bottom: 12px;
+ background: ${this.options?.darkMode ? '#374151' : 'white'};
+ border: 1px solid ${this.options?.darkMode ? '#4b5563' : '#d1d5db'};
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s;
+ `;
+ button.onmouseover = () => {
+ button.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
+ };
+ button.onmouseout = () => {
+ button.style.boxShadow = 'none';
+ };
+
+ const iconDiv = document.createElement('div');
+ iconDiv.textContent = option.icon;
+ iconDiv.style.cssText = `
+ font-size: 24px;
+ margin-right: 16px;
+ width: 24px;
+ text-align: center;
+ `;
+
+ const contentDiv = document.createElement('div');
+ contentDiv.style.cssText = 'flex: 1; text-align: left;';
+
+ const titleDiv = document.createElement('div');
+ titleDiv.textContent = option.title;
+ titleDiv.style.cssText = `
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: ${this.options?.darkMode ? 'white' : '#1f2937'};
+ `;
+
+ const descDiv = document.createElement('div');
+ descDiv.textContent = option.description;
+ descDiv.style.cssText = `
+ font-size: 14px;
+ color: ${this.options?.darkMode ? '#9ca3af' : '#6b7280'};
+ `;
+
+ contentDiv.appendChild(titleDiv);
+ contentDiv.appendChild(descDiv);
+
+ button.appendChild(iconDiv);
+ button.appendChild(contentDiv);
+ this.modalBody.appendChild(button);
+ });
+ }
+
+ _handleOptionClick(type) {
+ console.log('Selected login type:', type);
+
+ // Handle different login types
+ switch (type) {
+ case 'extension':
+ this._handleExtension();
+ break;
+ case 'local':
+ this._showLocalKeyScreen();
+ break;
+ case 'connect':
+ this._showConnectScreen();
+ break;
+ case 'readonly':
+ this._handleReadonly();
+ break;
+ case 'otp':
+ this._showOtpScreen();
+ break;
+ }
+ }
+
+ _handleExtension() {
+ // Detect all available real extensions
+ const availableExtensions = this._detectAllExtensions();
+
+ if (availableExtensions.length === 0) {
+ console.log('Modal: No real extensions found');
+ this._showExtensionRequired();
+ } else if (availableExtensions.length === 1) {
+ console.log('Modal detected single extension:', availableExtensions[0].name);
+ this._tryExtensionLogin(availableExtensions[0].extension);
+ } else {
+ console.log('Modal detected multiple extensions:', availableExtensions.map(e => e.name));
+ this._showExtensionChoice(availableExtensions);
+ }
+ }
+
+ _detectAllExtensions() {
+ const extensions = [];
+
+ // Check navigator.nostr (NIP-07 standard location)
+ if (window.navigator?.nostr && typeof window.navigator.nostr.getPublicKey === 'function') {
+ extensions.push({
+ name: 'navigator.nostr',
+ displayName: 'Standard Extension (navigator.nostr)',
+ icon: '🌐',
+ extension: window.navigator.nostr
+ });
+ }
+
+ // Check webln.nostr (Alby WebLN)
+ if (window.webln?.nostr && typeof window.webln.nostr.getPublicKey === 'function') {
+ extensions.push({
+ name: 'webln.nostr',
+ displayName: 'Alby WebLN Extension',
+ icon: '⚡',
+ extension: window.webln.nostr
+ });
+ }
+
+ // Check alby.nostr (Alby direct)
+ if (window.alby?.nostr && typeof window.alby.nostr.getPublicKey === 'function') {
+ extensions.push({
+ name: 'alby.nostr',
+ displayName: 'Alby Extension (Direct)',
+ icon: '🐝',
+ extension: window.alby.nostr
+ });
+ }
+
+ // Check nos2x
+ if (window.nos2x && typeof window.nos2x.getPublicKey === 'function') {
+ extensions.push({
+ name: 'nos2x',
+ displayName: 'nos2x Extension',
+ icon: '🔌',
+ extension: window.nos2x
+ });
+ }
+
+ // Check window.nostr but make sure it's not our library
+ if (window.nostr && typeof window.nostr.getPublicKey === 'function') {
+ const isRealExtension = (
+ typeof window.nostr._hexToUint8Array !== 'function' && // Our library has this method
+ window.nostr.constructor.name !== 'Object' // Real extensions usually have proper constructors
+ );
+
+ if (isRealExtension) {
+ // Don't add if we already detected it via another path
+ const alreadyDetected = extensions.some(ext => ext.extension === window.nostr);
+ if (!alreadyDetected) {
+ extensions.push({
+ name: 'window.nostr',
+ displayName: 'Extension (window.nostr)',
+ icon: '🔑',
+ extension: window.nostr
+ });
+ }
+ }
+ }
+
+ return extensions;
+ }
+
+ _showExtensionChoice(extensions) {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Choose Browser Extension';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
+
+ const description = document.createElement('p');
+ description.textContent = `Found ${extensions.length} Nostr extensions. Choose which one to use:`;
+ description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(description);
+
+ // Create button for each extension
+ extensions.forEach((ext, index) => {
+ const button = document.createElement('button');
+ button.onclick = () => this._tryExtensionLogin(ext.extension);
+ button.style.cssText = `
+ display: flex;
+ align-items: center;
+ width: 100%;
+ padding: 16px;
+ margin-bottom: 12px;
+ background: ${this.options?.darkMode ? '#374151' : 'white'};
+ border: 1px solid ${this.options?.darkMode ? '#4b5563' : '#d1d5db'};
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s;
+ text-align: left;
+ `;
+
+ button.onmouseover = () => {
+ button.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
+ button.style.transform = 'translateY(-1px)';
+ };
+ button.onmouseout = () => {
+ button.style.boxShadow = 'none';
+ button.style.transform = 'none';
+ };
+
+ const iconDiv = document.createElement('div');
+ iconDiv.textContent = ext.icon;
+ iconDiv.style.cssText = `
+ font-size: 24px;
+ margin-right: 16px;
+ width: 24px;
+ text-align: center;
+ `;
+
+ const contentDiv = document.createElement('div');
+ contentDiv.style.cssText = 'flex: 1;';
+
+ const nameDiv = document.createElement('div');
+ nameDiv.textContent = ext.displayName;
+ nameDiv.style.cssText = `
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: ${this.options?.darkMode ? 'white' : '#1f2937'};
+ `;
+
+ const pathDiv = document.createElement('div');
+ pathDiv.textContent = ext.name;
+ pathDiv.style.cssText = `
+ font-size: 12px;
+ color: ${this.options?.darkMode ? '#9ca3af' : '#6b7280'};
+ font-family: monospace;
+ `;
+
+ contentDiv.appendChild(nameDiv);
+ contentDiv.appendChild(pathDiv);
+
+ button.appendChild(iconDiv);
+ button.appendChild(contentDiv);
+ this.modalBody.appendChild(button);
+ });
+
+ // Add back button
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back to Login Options';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 20px;';
+
+ this.modalBody.appendChild(backButton);
+ }
+
+ async _tryExtensionLogin(extensionObj) {
+ try {
+ // Show loading state
+ this.modalBody.innerHTML = '
🔄 Connecting to extension...
';
+
+ // Get pubkey from extension
+ const pubkey = await extensionObj.getPublicKey();
+ console.log('Extension provided pubkey:', pubkey);
+
+ // Set extension method with the extension object
+ this._setAuthMethod('extension', { pubkey, extension: extensionObj });
+
+ } catch (error) {
+ console.error('Extension login failed:', error);
+ this._showError(`Extension login failed: ${error.message}`);
+ }
+ }
+
+ _showLocalKeyScreen() {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Local Key';
+ title.style.cssText = 'margin: 0 0 20px 0; font-size: 18px; font-weight: 600;';
+
+ const createButton = document.createElement('button');
+ createButton.textContent = 'Create New Key';
+ createButton.onclick = () => this._createLocalKey();
+ createButton.style.cssText = this._getButtonStyle();
+
+ const importButton = document.createElement('button');
+ importButton.textContent = 'Import Existing Key';
+ importButton.onclick = () => this._showImportKeyForm();
+ importButton.style.cssText = this._getButtonStyle() + 'margin-top: 12px;';
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = `
+ display: block;
+ margin-top: 20px;
+ padding: 12px;
+ background: #6b7280;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ `;
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(createButton);
+ this.modalBody.appendChild(importButton);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _createLocalKey() {
+ try {
+ const sk = window.NostrTools.generateSecretKey();
+ const pk = window.NostrTools.getPublicKey(sk);
+ const nsec = window.NostrTools.nip19.nsecEncode(sk);
+ const npub = window.NostrTools.nip19.npubEncode(pk);
+
+ this._showKeyDisplay(pk, nsec, 'created');
+ } catch (error) {
+ this._showError('Failed to create key: ' + error.message);
+ }
+ }
+
+ _showImportKeyForm() {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Import Local Key';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
+
+ const description = document.createElement('p');
+ description.textContent = 'Enter your secret key in either nsec or hex format:';
+ description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
+
+ const textarea = document.createElement('textarea');
+ textarea.placeholder = 'Enter your secret key:\n• nsec1... (bech32 format)\n• 64-character hex string';
+ textarea.style.cssText = `
+ width: 100%;
+ height: 100px;
+ padding: 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ margin-bottom: 12px;
+ resize: none;
+ font-family: monospace;
+ font-size: 14px;
+ box-sizing: border-box;
+ `;
+
+ // Add real-time format detection
+ const formatHint = document.createElement('div');
+ formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
+
+ textarea.oninput = () => {
+ const value = textarea.value.trim();
+ if (!value) {
+ formatHint.textContent = '';
+ return;
+ }
+
+ const format = this._detectKeyFormat(value);
+ if (format === 'nsec') {
+ formatHint.textContent = '✅ Valid nsec format detected';
+ formatHint.style.color = '#059669';
+ } else if (format === 'hex') {
+ formatHint.textContent = '✅ Valid hex format detected';
+ formatHint.style.color = '#059669';
+ } else {
+ formatHint.textContent = '❌ Invalid key format - must be nsec1... or 64-character hex';
+ formatHint.style.color = '#dc2626';
+ }
+ };
+
+ const importButton = document.createElement('button');
+ importButton.textContent = 'Import Key';
+ importButton.onclick = () => this._importLocalKey(textarea.value);
+ importButton.style.cssText = this._getButtonStyle();
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._showLocalKeyScreen();
+ backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(description);
+ this.modalBody.appendChild(textarea);
+ this.modalBody.appendChild(formatHint);
+ this.modalBody.appendChild(importButton);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _detectKeyFormat(keyValue) {
+ const trimmed = keyValue.trim();
+
+ // Check for nsec format
+ if (trimmed.startsWith('nsec1') && trimmed.length === 63) {
+ try {
+ window.NostrTools.nip19.decode(trimmed);
+ return 'nsec';
+ } catch {
+ return 'invalid';
+ }
+ }
+
+ // Check for hex format (64 characters, valid hex)
+ if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) {
+ return 'hex';
+ }
+
+ return 'invalid';
+ }
+
+ _importLocalKey(keyValue) {
+ try {
+ const trimmed = keyValue.trim();
+ if (!trimmed) {
+ throw new Error('Please enter a secret key');
+ }
+
+ const format = this._detectKeyFormat(trimmed);
+ let sk;
+
+ if (format === 'nsec') {
+ // Decode nsec format - this returns Uint8Array
+ const decoded = window.NostrTools.nip19.decode(trimmed);
+ if (decoded.type !== 'nsec') {
+ throw new Error('Invalid nsec format');
+ }
+ sk = decoded.data; // This is already Uint8Array
+ } else if (format === 'hex') {
+ // Convert hex string to Uint8Array
+ sk = this._hexToUint8Array(trimmed);
+ // Test that it's a valid secret key by trying to get public key
+ window.NostrTools.getPublicKey(sk);
+ } else {
+ throw new Error('Invalid key format. Please enter either nsec1... or 64-character hex string');
+ }
+
+ // Generate public key and encoded formats
+ const pk = window.NostrTools.getPublicKey(sk);
+ const nsec = window.NostrTools.nip19.nsecEncode(sk);
+ const npub = window.NostrTools.nip19.npubEncode(pk);
+
+ this._showKeyDisplay(pk, nsec, 'imported');
+ } catch (error) {
+ this._showError('Invalid key: ' + error.message);
+ }
+ }
+
+ _hexToUint8Array(hex) {
+ // Convert hex string to Uint8Array
+ if (hex.length % 2 !== 0) {
+ throw new Error('Invalid hex string length');
+ }
+ const bytes = new Uint8Array(hex.length / 2);
+ for (let i = 0; i < bytes.length; i++) {
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
+ }
+ return bytes;
+ }
+
+ _showKeyDisplay(pubkey, nsec, action) {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = `Key ${action} successfully!`;
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #059669;';
+
+ const warningDiv = document.createElement('div');
+ warningDiv.textContent = '⚠️ Save your secret key securely!';
+ warningDiv.style.cssText = 'background: #fef3c7; color: #92400e; padding: 12px; border-radius: 6px; margin-bottom: 16px; font-size: 14px;';
+
+ const nsecDiv = document.createElement('div');
+ nsecDiv.innerHTML = `Your Secret Key:
${nsec}`;
+ nsecDiv.style.cssText = 'margin-bottom: 16px; font-size: 14px;';
+
+ const npubDiv = document.createElement('div');
+ npubDiv.innerHTML = `Your Public Key:
${window.NostrTools.nip19.npubEncode(pubkey)}`;
+ npubDiv.style.cssText = 'margin-bottom: 16px; font-size: 14px;';
+
+ const continueButton = document.createElement('button');
+ continueButton.textContent = 'Continue';
+ continueButton.onclick = () => this._setAuthMethod('local', { secret: nsec, pubkey });
+ continueButton.style.cssText = this._getButtonStyle();
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(warningDiv);
+ this.modalBody.appendChild(nsecDiv);
+ this.modalBody.appendChild(npubDiv);
+ this.modalBody.appendChild(continueButton);
+ }
+
+ _setAuthMethod(method, options = {}) {
+ // Emit auth method selection
+ const event = new CustomEvent('nlMethodSelected', {
+ detail: { method, ...options }
+ });
+ window.dispatchEvent(event);
+
+ this.close();
+ }
+
+ _showError(message) {
+ this.modalBody.innerHTML = '';
+
+ const errorDiv = document.createElement('div');
+ errorDiv.style.cssText = 'background: #fee2e2; color: #dc2626; padding: 16px; border-radius: 6px; margin-bottom: 16px;';
+ errorDiv.innerHTML = `Error: ${message}`;
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary');
+
+ this.modalBody.appendChild(errorDiv);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _showExtensionRequired() {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Browser Extension Required';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
+
+ const message = document.createElement('p');
+ message.textContent = 'Please install a Nostr browser extension like Alby or getflattr and refresh the page.';
+ message.style.cssText = 'margin-bottom: 20px; color: #6b7280;';
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary');
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(message);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _showConnectScreen() {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Connect to NIP-46 Remote Signer';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
+
+ const description = document.createElement('p');
+ description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.';
+ description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
+
+ const formGroup = document.createElement('div');
+ formGroup.style.cssText = 'margin-bottom: 20px;';
+
+ const label = document.createElement('label');
+ label.textContent = 'Bunker Public Key:';
+ label.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;';
+
+ const pubkeyInput = document.createElement('input');
+ pubkeyInput.type = 'text';
+ pubkeyInput.placeholder = 'bunker://pubkey?relay=..., bunker:hex, hex, or npub...';
+ pubkeyInput.style.cssText = `
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ margin-bottom: 12px;
+ font-family: monospace;
+ box-sizing: border-box;
+ `;
+
+ const urlLabel = document.createElement('label');
+ urlLabel.textContent = 'Remote URL (optional):';
+ urlLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;';
+
+ const urlInput = document.createElement('input');
+ urlInput.type = 'url';
+ urlInput.placeholder = 'ws://localhost:8080 (default)';
+ urlInput.style.cssText = `
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ margin-bottom: 16px;
+ box-sizing: border-box;
+ `;
+
+ // Pre-fill with our bunker config if available
+ if (window.NIP46_BUNKER_CONFIG) {
+ pubkeyInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.pubkey;
+ urlInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.url;
+ }
+
+ const connectButton = document.createElement('button');
+ connectButton.textContent = 'Connect to Bunker';
+ connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value, urlInput.value);
+ connectButton.style.cssText = this._getButtonStyle();
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
+
+ formGroup.appendChild(label);
+ formGroup.appendChild(pubkeyInput);
+ formGroup.appendChild(urlLabel);
+ formGroup.appendChild(urlInput);
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(description);
+ this.modalBody.appendChild(formGroup);
+ this.modalBody.appendChild(connectButton);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _handleNip46Connect(bunkerPubkey, bunkerUrl) {
+ if (!bunkerPubkey || !bunkerPubkey.length) {
+ this._showError('Bunker pubkey is required');
+ return;
+ }
+
+ this._showNip46Connecting(bunkerPubkey, bunkerUrl);
+ this._performNip46Connect(bunkerPubkey, bunkerUrl);
+ }
+
+ _showNip46Connecting(bunkerPubkey, bunkerUrl) {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Connecting to Remote Signer...';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #059669;';
+
+ const description = document.createElement('p');
+ description.textContent = 'Establishing secure connection to your remote signer.';
+ description.style.cssText = 'margin-bottom: 20px; color: #6b7280;';
+
+ // Normalize bunker pubkey for display (= show original format if bunker: prefix)
+ const displayPubkey = bunkerPubkey.startsWith('bunker:') || bunkerPubkey.startsWith('npub') || bunkerPubkey.length === 64 ? bunkerPubkey : bunkerPubkey;
+
+ const bunkerInfo = document.createElement('div');
+ bunkerInfo.style.cssText = 'background: #f1f5f9; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;';
+ bunkerInfo.innerHTML = `
+ Connecting to bunker:
+ Pubkey: ${displayPubkey}
+ Relay: ${bunkerUrl || 'ws://localhost:8080'}
+ If this relay is offline, the bunker server may be unavailable.
+ `;
+
+ const connectingDiv = document.createElement('div');
+ connectingDiv.style.cssText = 'text-align: center; color: #6b7280;';
+ connectingDiv.innerHTML = `
+ ⏳
+ Please wait while we establish the connection...
+ This may take a few seconds
+ `;
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(description);
+ this.modalBody.appendChild(bunkerInfo);
+ this.modalBody.appendChild(connectingDiv);
+ }
+
+ async _performNip46Connect(bunkerPubkey, bunkerUrl) {
+ try {
+ console.log('Starting NIP-46 connection to bunker:', bunkerPubkey, bunkerUrl);
+
+ // Check if nostr-tools NIP-46 is available
+ if (!window.NostrTools?.nip46) {
+ throw new Error('nostr-tools NIP-46 module not available');
+ }
+
+ // Use nostr-tools to parse bunker input - this handles all formats correctly
+ console.log('Parsing bunker input with nostr-tools...');
+ const bunkerPointer = await window.NostrTools.nip46.parseBunkerInput(bunkerPubkey);
+
+ if (!bunkerPointer) {
+ throw new Error('Unable to parse bunker connection string or resolve NIP-05 identifier');
+ }
+
+ console.log('Parsed bunker pointer:', bunkerPointer);
+
+ // Create local client keypair for this session
+ const localSecretKey = window.NostrTools.generateSecretKey();
+ console.log('Generated local client keypair for NIP-46 session');
+
+ // Use nostr-tools BunkerSigner.fromBunker() for bunker:// connections
+ console.log('Creating nip46 BunkerSigner...');
+ const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, {
+ onauth: (url) => {
+ console.log('Received auth URL from bunker:', url);
+ // Open auth URL in popup or redirect
+ window.open(url, '_blank', 'width=600,height=800');
+ }
+ });
+
+ console.log('NIP-46 BunkerSigner created successfully');
+
+ // Attempt initial ping to verify connection
+ console.log('Testing bunker connection with ping...');
+ await signer.ping();
+ console.log('NIP-46 ping successful - bunker is reachable');
+
+ // Try to connect (this may trigger auth flow)
+ console.log('Attempting NIP-46 connect...');
+ await signer.connect();
+ console.log('NIP-46 connect successful');
+
+ // Get the user's public key from the bunker
+ console.log('Getting public key from bunker...');
+ const userPubkey = await signer.getPublicKey();
+ console.log('NIP-46 user public key:', userPubkey);
+
+ // Store the NIP-46 authentication info
+ const nip46Info = {
+ pubkey: userPubkey,
+ signer: {
+ method: 'nip46',
+ remotePubkey: bunkerPointer.pubkey,
+ bunkerSigner: signer,
+ secret: bunkerPointer.secret,
+ relays: bunkerPointer.relays
+ }
+ };
+
+ console.log('NOSTR_LOGIN_LITE NIP-46 connection established successfully!');
+
+ // Set as current auth method
+ this._setAuthMethod('nip46', nip46Info);
+ return;
+
+ } catch (error) {
+ console.error('NIP-46 connection failed:', error);
+ this._showNip46Error(error.message);
+ }
+ }
+
+ _showNip46Error(message) {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Connection Failed';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #dc2626;';
+
+ const errorMsg = document.createElement('p');
+ errorMsg.textContent = `Unable to connect to remote signer: ${message}`;
+ errorMsg.style.cssText = 'margin-bottom: 20px; color: #6b7280;';
+
+ const retryButton = document.createElement('button');
+ retryButton.textContent = 'Try Again';
+ retryButton.onclick = () => this._showConnectScreen();
+ retryButton.style.cssText = this._getButtonStyle();
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back to Options';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(errorMsg);
+ this.modalBody.appendChild(retryButton);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _handleReadonly() {
+ // Set read-only mode
+ this._setAuthMethod('readonly');
+ }
+
+ _showOtpScreen() {
+ // Placeholder for OTP functionality
+ this._showError('OTP/DM not yet implemented - coming soon!');
+ }
+
+ _getButtonStyle(type = 'primary') {
+ const baseStyle = `
+ display: block;
+ width: 100%;
+ padding: 12px;
+ border: none;
+ border-radius: 8px;
+ font-size: 16px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+ `;
+
+ if (type === 'primary') {
+ return baseStyle + `
+ background: #3b82f6;
+ color: white;
+ `;
+ } else {
+ return baseStyle + `
+ background: #6b7280;
+ color: white;
+ `;
+ }
+ }
+
+ // Public API
+ static init(options) {
+ if (Modal.instance) return Modal.instance;
+ Modal.instance = new Modal(options);
+ return Modal.instance;
+ }
+
+ static getInstance() {
+ return Modal.instance;
+ }
+}
+
+// Initialize global instance
+let modalInstance = null;
+
+window.addEventListener('load', () => {
+ modalInstance = new Modal();
+});
+
+
+// ======================================
+// nip46-client.js
+// ======================================
+
+
+class NIP46Client {
+ constructor() {
+ this.pool = null;
+ this.localSk = null;
+ this.localPk = null;
+ this.remotePk = null;
+ this.relays = [];
+ this.sub = null;
+ this.pendingRequests = {};
+ this.useNip44 = false;
+ this.iframeOrigin = null;
+ this.iframePort = null;
+ }
+
+ init(localSk, remotePk, relays, iframeOrigin) {
+ // Create SimplePool
+ this.pool = new window.NostrTools.SimplePool();
+
+ // Setup keys
+ this.localSk = localSk;
+ if (this.localSk) {
+ this.localPk = window.NostrTools.getPublicKey(this.localSk);
+ }
+
+ this.remotePk = remotePk;
+ this.relays = [...relays];
+
+ // Store iframe origin for future use
+ this.iframeOrigin = iframeOrigin;
+
+ console.log('NIP46Client initialized for', this.remotePk ? 'remote signer' : 'listening mode');
+ }
+
+ setUseNip44(use) {
+ this.useNip44 = use;
+ }
+
+ subscribeReplies() {
+ if (!this.pool || !this.localPk) return;
+
+ // Subscribe to replies to our pubkey on kind 24133 (NIP-46 methods)
+ this.sub = this.pool.sub(this.relays, [{
+ kinds: [24133],
+ '#p': [this.localPk]
+ }]);
+
+ this.sub.on('event', (event) => this.onEvent(event));
+ this.sub.on('eose', () => {
+ console.log('NIP-46 subscription caught up');
+ });
+
+ console.log('Subscribed to NIP-46 replies on relays:', this.relays);
+ }
+
+ unsubscribe() {
+ if (this.sub) {
+ this.sub.unsub();
+ this.sub = null;
+ }
+ }
+
+ async onEvent(event) {
+ console.log('NIP-46 event received:', event);
+
+ try {
+ const parsed = await this.parseEvent(event);
+ if (parsed) {
+ if (parsed.id && this.pendingRequests[parsed.id]) {
+ // Handle response
+ const handler = this.pendingRequests[parsed.id];
+ delete this.pendingRequests[parsed.id];
+
+ if (parsed.result !== undefined) {
+ handler.resolve(parsed.result);
+ } else if (parsed.error) {
+ handler.reject(new Error(parsed.error));
+ } else {
+ handler.reject(new Error('Invalid response format'));
+ }
+ } else if (parsed.method === 'auth_url') {
+ // Handle auth_url emissions (deduplication required)
+ this.emitAuthUrlIfNeeded(parsed.params[0]);
+ }
+ }
+ } catch (error) {
+ console.error('Error processing NIP-46 event:', error);
+ }
+ }
+
+ emitAuthUrlIfNeeded(url) {
+ // Deduplicate auth_url emissions - only emit if not recently shown
+ const lastUrl = sessionStorage.getItem('nl-last-auth-url');
+ if (lastUrl === url) {
+ console.log('Auth URL already shown, skipping duplicate:', url);
+ return;
+ }
+
+ sessionStorage.setItem('nl-last-auth-url', url);
+ console.log('New auth URL:', url);
+
+ // Emit event for UI
+ window.dispatchEvent(new CustomEvent('nlAuthUrl', { detail: { url } }));
+ }
+
+ async parseEvent(event) {
+ try {
+ let content = event.content;
+
+ // Determine encryption method based on content structure
+ if (content.length > 44) {
+ // Likely NIP-44 (encrypted)
+ if (this.localSk && event.pubkey) {
+ try {
+ content = window.NostrTools.nip44?.decrypt(this.localSk, event.pubkey, content);
+ } catch (e) {
+ console.warn('NIP-44 decryption failed, trying NIP-04...');
+ content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content);
+ }
+ }
+ } else {
+ // Likely NIP-04
+ if (this.localSk && event.pubkey) {
+ content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content);
+ }
+ }
+
+ const payload = JSON.parse(content);
+ console.log('Decrypted NIP-46 payload:', payload);
+
+ return {
+ id: payload.id,
+ method: payload.method,
+ params: payload.params,
+ result: payload.result,
+ error: payload.error,
+ event: event
+ };
+
+ } catch (e) {
+ console.error('Failed to parse event:', e);
+ return null;
+ }
+ }
+
+ async listen(nostrConnectSecret) {
+ return new Promise((resolve, reject) => {
+ if (!this.localPk) {
+ reject(new Error('No local pubkey available for listening'));
+ return;
+ }
+
+ // Subscribe to unsolicited events to our pubkey
+ let foundSecretOrAck = false;
+
+ const listenSub = this.pool.sub(this.relays, [{
+ kinds: [24133],
+ '#p': [this.localPk]
+ }]);
+
+ listenSub.on('event', async (event) => {
+ try {
+ const parsed = await this.parseEvent(event);
+ if (parsed && parsed.method === 'connect') {
+ // Accept if it's an ack or matches our secret
+ const [userPubkey, token] = parsed.params || [];
+
+ if (token === '' && parsed.result === 'ack') {
+ // Ack received
+ foundSecretOrAck = true;
+ listenSub.unsub();
+ resolve(event.pubkey);
+ } else if (token === nostrConnectSecret) {
+ // Secret match
+ foundSecretOrAck = true;
+ listenSub.unsub();
+ resolve(event.pubkey);
+ }
+ }
+ } catch (error) {
+ console.error('Error in listen mode:', error);
+ }
+ });
+
+ // Timeout after 5 minutes
+ setTimeout(() => {
+ if (!foundSecretOrAck) {
+ listenSub.unsub();
+ reject(new Error('Listen timeout - no signer connected'));
+ }
+ }, 300000);
+ });
+ }
+
+ async connect(token, perms) {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const result = await this.sendRequest(
+ this.remotePk,
+ 'connect',
+ [this.localPk, token || '', perms || ''],
+ 24133,
+ (response) => {
+ if (response === 'ack') {
+ resolve(true);
+ } else {
+ reject(new Error('Connection not acknowledged'));
+ }
+ }
+ );
+
+ // Set 30 second timeout
+ setTimeout(() => reject(new Error('Connection timeout')), 30000);
+
+ } catch (error) {
+ reject(error);
+ }
+ });
+ }
+
+ async initUserPubkey(hint) {
+ if (hint) {
+ this.remotePk = hint;
+ return hint;
+ }
+
+ if (!this.remotePk) {
+ // Request get_public_key
+ return new Promise(async (resolve, reject) => {
+ try {
+ const pubkey = await this.sendRequest(
+ this.remotePk,
+ 'get_public_key',
+ [],
+ 24133
+ );
+ this.remotePk = pubkey;
+ resolve(pubkey);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ }
+
+ return this.remotePk;
+ }
+
+ async sendRequest(remotePubkey, method, params, kind = 24133, cb) {
+ if (!this.pool || !this.localSk || !this.localPk) {
+ throw new Error('NIP46Client not properly initialized');
+ }
+
+ if (!remotePubkey) {
+ throw new Error('No remote pubkey specified');
+ }
+
+ const id = this._generateId();
+
+ // Create request event
+ const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
+
+ console.log('Sending NIP-46 request:', { id, method, params });
+
+ // Publish to relays
+ const pubs = await this.pool.publish(this.relays, event);
+ console.log('Published to relays, waiting for response...');
+
+ return new Promise((resolve, reject) => {
+ // Set timeout
+ const timeout = setTimeout(() => {
+ console.error('NIP-46 request timeout for id:', id);
+ delete this.pendingRequests[id];
+ reject(new Error(`Request timeout for ${method}`));
+ }, 60000); // 1 minute timeout
+
+ // Store handler
+ this.pendingRequests[id] = {
+ resolve: (result) => {
+ clearTimeout(timeout);
+ resolve(result);
+ },
+ reject: (error) => {
+ clearTimeout(timeout);
+ reject(error);
+ },
+ timestamp: Date.now()
+ };
+
+ // If callback provided, override resolve
+ if (cb) {
+ const originalResolve = this.pendingRequests[id].resolve;
+ this.pendingRequests[id].resolve = (result) => {
+ cb(result);
+ originalResolve(result);
+ };
+ }
+ });
+ }
+
+ async createRequestEvent(id, remotePubkey, method, params, kind = 24133) {
+ let content = JSON.stringify({
+ id,
+ method,
+ params
+ });
+
+ // Choose encryption method
+ let encrypted = content;
+ if (method !== 'create_account') {
+ // Use NIP-44 for non-account creation methods if available
+ if (this.useNip44 && window.NostrTools.nip44) {
+ encrypted = window.NostrTools.nip44.encrypt(this.localSk, remotePubkey, content);
+ } else {
+ // Fallback to NIP-04
+ encrypted = await window.NostrTools.nip04.encrypt(this.localSk, remotePubkey, content);
+ }
+ }
+
+ // Create event structure
+ const event = {
+ kind: kind,
+ content: encrypted,
+ tags: [
+ ['p', remotePubkey]
+ ],
+ created_at: Math.floor(Date.now() / 1000),
+ pubkey: this.localPk,
+ id: '', // Will be set by finalizeEvent
+ sig: '' // Will be set by finalizeEvent
+ };
+
+ // Sign the event
+ const signedEvent = window.NostrTools.finalizeEvent(event, this.localSk);
+
+ return signedEvent;
+ }
+
+ _generateId() {
+ return 'nl-' + Date.now() + '-' + Math.random().toString(36).substring(2, 15);
+ }
+
+ setWorkerIframePort(port) {
+ this.iframePort = port;
+
+ // Set up postMessage routing if needed
+ if (this.iframePort && this.iframeOrigin) {
+ this.iframePort.onmessage = (event) => {
+ if (event.origin !== this.iframeOrigin) {
+ console.warn('Ignoring message from unknown origin:', event.origin);
+ return;
+ }
+
+ console.log('Received iframe message:', event.data);
+ // Handle iframe messages
+ };
+
+ // Send keepalive
+ setInterval(() => {
+ if (this.iframePort) {
+ try {
+ this.iframePort.postMessage({ type: 'ping' });
+ } catch (e) {
+ console.warn('Iframe port closed');
+ this.iframePort = null;
+ }
+ }
+ }, 30000); // 30 seconds
+ }
+ }
+
+ teardown() {
+ this.unsubscribe();
+
+ if (this.iframePort) {
+ try {
+ this.iframePort.close();
+ } catch (e) {
+ console.warn('Error closing iframe port:', e);
+ }
+ this.iframePort = null;
+ }
+
+ if (this.pool) {
+ this.pool.close(this.relays);
+ this.pool = null;
+ }
+
+ // Clear all pending requests
+ for (const id in this.pendingRequests) {
+ this.pendingRequests[id].reject(new Error('Client teardown'));
+ }
+ this.pendingRequests = {};
+ }
+}
+
+
+// ======================================
+// nostr-login-lite.js
+// ======================================
+
+
+// Import NIP-46 client
+if (typeof NIP46Client === 'undefined') {
+ // Load NIP46Client if not already available (for non-bundled version)
+ const script = document.createElement('script');
+ script.src = './core/nip46-client.js';
+ document.head.appendChild(script);
+}
+
+// Global state
+const LiteState = {
+ initialized: false,
+ windowNostr: null,
+ options: null,
+ auth: null,
+ modal: null,
+ bus: null,
+ pool: null,
+ nip44Codec: null,
+ extensionBridge: null,
+ nip46Client: null
+};
+
+// Dependencies verification
+class Deps {
+ static ensureNostrToolsLoaded() {
+ if (typeof window === 'undefined') {
+ throw new Error('NOSTR_LOGIN_LITE must run in browser environment');
+ }
+
+ if (!window.NostrTools) {
+ throw new Error(
+ 'window.NostrTools is required but not loaded. ' +
+ 'Please include: '
+ );
+ }
+
+ // Verify required APIs
+ const required = ['SimplePool', 'getPublicKey', 'finalizeEvent', 'nip04'];
+ for (const api of required) {
+ if (!window.NostrTools[api]) {
+ throw new Error(`window.NostrTools.${api} is required but missing`);
+ }
+ }
+
+ // Check for key generation function (might be generateSecretKey or generatePrivateKey)
+ if (!window.NostrTools.generateSecretKey && !window.NostrTools.generatePrivateKey) {
+ throw new Error('window.NostrTools must have either generateSecretKey or generatePrivateKey');
+ }
+
+ return true;
+ }
+}
+
+// Event Bus for internal communication
+class Bus {
+ constructor() {
+ this.handlers = {};
+ }
+
+ on(event, handler) {
+ if (!this.handlers[event]) {
+ this.handlers[event] = [];
+ }
+ this.handlers[event].push(handler);
+ }
+
+ off(event, handler) {
+ if (!this.handlers[event]) return;
+ this.handlers[event] = this.handlers[event].filter(h => h !== handler);
+ }
+
+ emit(event, payload) {
+ if (!this.handlers[event]) return;
+ this.handlers[event].forEach(handler => {
+ try {
+ handler(payload);
+ } catch (e) {
+ console.error(`Error in event handler for ${event}:`, e);
+ }
+ });
+ }
+}
+
+// Storage helpers
+class Store {
+ static addAccount(info) {
+ const accounts = this.getAccounts();
+ // Remove existing account with same pubkey if present
+ const filtered = accounts.filter(acc => acc.pubkey !== info.pubkey);
+ filtered.push(info);
+ localStorage.setItem('nl_accounts', JSON.stringify(filtered));
+ }
+
+ static removeCurrentAccount() {
+ const current = this.getCurrent();
+ if (current && current.pubkey) {
+ const accounts = this.getAccounts();
+ const filtered = accounts.filter(acc => acc.pubkey !== current.pubkey);
+ localStorage.setItem('nl_accounts', JSON.stringify(filtered));
+ localStorage.removeItem('nl_current');
+ }
+ }
+
+ static getCurrent() {
+ try {
+ const stored = localStorage.getItem('nl_current');
+ return stored ? JSON.parse(stored) : null;
+ } catch (e) {
+ console.error('Error parsing current account:', e);
+ return null;
+ }
+ }
+
+ static setCurrent(info) {
+ localStorage.setItem('nl_current', JSON.stringify(info));
+ }
+
+ static getAccounts() {
+ try {
+ const stored = localStorage.getItem('nl_accounts');
+ return stored ? JSON.parse(stored) : [];
+ } catch (e) {
+ console.error('Error parsing accounts:', e);
+ return [];
+ }
+ }
+
+ static getRecents() {
+ // Return last 5 used accounts in reverse chronological order
+ const accounts = this.getAccounts().slice(-5).reverse();
+ return accounts;
+ }
+
+ static setItem(key, value) {
+ localStorage.setItem(`nl-${key}`, value);
+ }
+
+ static getItem(key) {
+ return localStorage.getItem(`nl-${key}`);
+ }
+
+ static async getIcon() {
+ // Simple default icon - could be extended to fetch from profile
+ return '🔑';
+ }
+}
+
+// Relay configuration helpers
+class Relays {
+ static getDefaultRelays(options) {
+ if (options?.relays) {
+ return this.normalize(options.relays);
+ }
+
+ // Default relays for fallbacks
+ return [
+ 'wss://relay.damus.io',
+ 'wss://relay.snort.social',
+ 'wss://nos.lol'
+ ];
+ }
+
+ static normalize(relays) {
+ return relays.map(relay => {
+ // Ensure wss:// prefix
+ if (relay.startsWith('ws://')) {
+ return relay.replace('ws://', 'wss://');
+ } else if (!relay.startsWith('wss://')) {
+ return `wss://${relay}`;
+ }
+ return relay;
+ }).filter(relay => {
+ // Remove duplicates and validate URLs
+ try {
+ new URL(relay);
+ return true;
+ } catch {
+ return false;
+ }
+ }).filter((relay, index, self) => self.indexOf(relay) === index); // dedupe
+ }
+}
+
+// Minimal NIP-44 codec fallback
+class Nip44 {
+ constructor() {
+ this.Nip44 = null;
+ // Initialize with existing codec if available
+ this.nip44Available = window.NostrTools?.nip44;
+ }
+
+ static encrypt(ourSk, theirPk, plaintext) {
+ if (window.NostrTools?.nip44?.encrypt) {
+ return window.NostrTools.nip44.encrypt(ourSk, theirPk, plaintext);
+ }
+
+ throw new Error('NIP-44 encryption not available. Please use nostr-tools@>=2.x or provide codec implementation.');
+ }
+
+ static decrypt(ourSk, theirPk, ciphertext) {
+ if (window.NostrTools?.nip44?.decrypt) {
+ return window.NostrTools.nip44.decrypt(ourSk, theirPk, ciphertext);
+ }
+
+ throw new Error('NIP-44 decryption not available. Please use nostr-tools@>=2.x or provide codec implementation.');
+ }
+}
+
+// LocalSigner wrapping window.NostrTools
+class LocalSigner {
+ constructor(sk) {
+ this.sk = sk;
+ // Generate pubkey from secret key
+ this.pk = this._getPubKey();
+ }
+
+ _getPubKey() {
+ const seckey = this.sk.startsWith('nsec') ?
+ window.NostrTools.nip19.decode(this.sk).data :
+ this.sk;
+ return window.NostrTools.getPublicKey(seckey);
+ }
+
+ pubkey() {
+ return this.pk;
+ }
+
+ async sign(event) {
+ // Prepare event for signing
+ const ev = { ...event };
+ ev.pubkey = this.pk;
+
+ // Generate event ID and sign
+ const signedEvent = await window.NostrTools.finalizeEvent(ev, this.sk);
+ return signedEvent;
+ }
+
+ async encrypt04(pubkey, plaintext) {
+ return await window.NostrTools.nip04.encrypt(this.sk, pubkey, plaintext);
+ }
+
+ async decrypt04(pubkey, ciphertext) {
+ return await window.NostrTools.nip04.decrypt(this.sk, pubkey, ciphertext);
+ }
+
+ async encrypt44(pubkey, plaintext) {
+ return Nip44.encrypt(this.sk, pubkey, plaintext);
+ }
+
+ async decrypt44(pubkey, ciphertext) {
+ return Nip44.decrypt(this.sk, pubkey, ciphertext);
+ }
+}
+
+// ExtensionBridge for detecting and managing browser extensions
+class ExtensionBridge {
+ constructor() {
+ this.checking = false;
+ this.checkInterval = null;
+ this.originalNostr = null;
+ this.foundExtension = null;
+ }
+
+ startChecking(nostrLite) {
+ if (this.checking) return;
+ this.checking = true;
+
+ const check = () => {
+ this.initExtension(nostrLite);
+ };
+
+ // Check immediately
+ check();
+
+ // Then check every 200ms for 30 seconds
+ this.checkInterval = setInterval(check, 200);
+
+ // Stop checking after 30 seconds
+ setTimeout(() => {
+ clearInterval(this.checkInterval);
+ this.checkInterval = null;
+ }, 30000);
+ }
+
+ initExtension(nostrLite, lastTry = false) {
+ const extension = window.nostr;
+
+ if (extension && !this.foundExtension) {
+ // Check if this is actually a real extension, not our own library
+ const isRealExtension = (
+ extension !== nostrLite && // Not the same object we're about to assign
+ extension !== windowNostr && // Not our windowNostr object
+ typeof extension._hexToUint8Array !== 'function' && // Our library has this internal method
+ extension.constructor.name !== 'Object' // Real extensions usually have proper constructors
+ );
+
+ if (isRealExtension) {
+ this.foundExtension = extension;
+
+ // Cache the extension and reassign window.nostr to our lite version
+ this.originalNostr = window.nostr;
+ window.nostr = nostrLite;
+
+ console.log('Real Nostr extension detected and bridged:', extension.constructor.name);
+
+ // If currently authenticated, reconcile state
+ if (LiteState.auth?.signer?.method === 'extension') {
+ this.reconcileExtension();
+ }
+ } else {
+ console.log('Skipping non-extension object on window.nostr:', extension.constructor.name);
+ }
+ }
+ }
+
+ hasExtension() {
+ return !!this.foundExtension;
+ }
+
+ async setExtensionReadPubkey(expectedPubkey = null) {
+ if (!this.foundExtension) return false;
+
+ try {
+ // Temporarily set window.nostr to extension
+ const temp = window.nostr;
+ window.nostr = this.foundExtension;
+
+ const pubkey = await this.foundExtension.getPublicKey();
+
+ // Restore our lite implementation
+ window.nostr = temp;
+
+ if (expectedPubkey && pubkey !== expectedPubkey) {
+ console.warn(`Extension pubkey ${pubkey} does not match expected ${expectedPubkey}`);
+ }
+
+ return pubkey;
+ } catch (e) {
+ console.error('Error reading extension pubkey:', e);
+ return null;
+ }
+ }
+
+ trySetForPubkey(expectedPubkey) {
+ if (!this.hasExtension()) return false;
+
+ this.setExtensionReadPubkey(expectedPubkey).then(pubkey => {
+ if (pubkey) {
+ LiteState.bus?.emit('extensionLogin', { pubkey });
+ }
+ });
+
+ return true;
+ }
+
+ setExtension() {
+ if (!this.foundExtension) return;
+ window.nostr = this.foundExtension;
+ this.setExtensionReadPubkey().then(pubkey => {
+ if (pubkey) {
+ LiteState.bus?.emit('extensionSet', { pubkey });
+ }
+ });
+ }
+
+ unset(nostrLite) {
+ window.nostr = nostrLite;
+ }
+
+ reconcileExtension() {
+ // Handle extension state changes
+ this.setExtensionReadPubkey().then(pubkey => {
+ if (pubkey) {
+ // Update current account if extension is the signer
+ const current = Store.getCurrent();
+ if (current && current.signer?.method === 'extension') {
+ const info = {
+ ...current,
+ pubkey,
+ signer: { method: 'extension' }
+ };
+ Store.setCurrent(info);
+ LiteState.bus?.emit('authStateUpdate', info);
+ }
+ }
+ });
+ }
+}
+
+// Main API surface
+class NostrLite {
+ static async init(options = {}) {
+ // Ensure dependencies are loaded
+ Deps.ensureNostrToolsLoaded();
+
+ // Prevent double initialization
+ if (LiteState.initialized) {
+ console.warn('NOSTR_LOGIN_LITE already initialized');
+ return;
+ }
+
+ // Initialize components
+ LiteState.bus = new Bus();
+ LiteState.extensionBridge = new ExtensionBridge();
+
+ // Initialize NIP-46 client
+ LiteState.nip46Client = new NIP46Client();
+
+ // Store options
+ LiteState.options = {
+ theme: 'light',
+ darkMode: false,
+ relays: Relays.getDefaultRelays(options),
+ methods: {
+ connect: true,
+ extension: true,
+ local: true,
+ readonly: true,
+ otp: true
+ },
+ otp: {},
+ ...options
+ };
+
+ // Start extension detection
+ LiteState.extensionBridge.startChecking(windowNostr);
+
+ // Setup auth methods
+ this._setupAuth();
+
+ // Initialize modal UI
+ // this._initModal();
+
+ console.log('NOSTR_LOGIN_LITE initialized with options:', LiteState.options);
+ LiteState.initialized = true;
+ }
+
+ static _setupAuth() {
+ // Set up event listeners for modal interactions
+ window.addEventListener('nlMethodSelected', (event) => {
+ this._handleMethodSelected(event.detail);
+ });
+
+ // Set up other auth-related event listeners
+ this._setupAuthEventListeners();
+
+ console.log('Auth system setup loaded');
+ }
+
+ static _setupAuthEventListeners() {
+ // Handle extension detection
+ this.bus?.on('extensionDetected', (extension) => {
+ console.log('Extension detected');
+ LiteState.extensionBridge.foundExtension = extension;
+ });
+
+ // Handle auth URL from NIP-46
+ window.addEventListener('nlAuthUrl', (event) => {
+ console.log('Auth URL received:', event.detail.url);
+ // Could show URL in modal or trigger external flow
+ });
+
+ // Handle logout events
+ window.addEventListener('nlLogout', () => {
+ console.log('Logout event received');
+ this.logout();
+ });
+ }
+
+ static _handleMethodSelected(detail) {
+ console.log('Method selected:', detail);
+
+ const { method, pubkey, secret, extension } = detail;
+
+ switch (method) {
+ case 'local':
+ if (secret && pubkey) {
+ // Set up local key authentication
+ const info = {
+ pubkey,
+ signer: { method: 'local', secret }
+ };
+ Store.setCurrent(info);
+ LiteState.bus?.emit('authStateUpdate', info);
+ this._dispatchAuthEvent('login', info);
+ }
+ break;
+
+ case 'extension':
+ if (pubkey && extension) {
+ // Store the extension object in the ExtensionBridge for future use
+ LiteState.extensionBridge.foundExtension = extension;
+ LiteState.extensionBridge.originalNostr = extension;
+
+ // Set up extension authentication
+ const info = {
+ pubkey,
+ signer: { method: 'extension' }
+ };
+ Store.setCurrent(info);
+ LiteState.bus?.emit('authStateUpdate', info);
+ this._dispatchAuthEvent('login', info);
+
+ console.log('Extension authentication set up successfully');
+ } else {
+ // Fallback to extension bridge detection
+ LiteState.bus?.emit('authMethodSelected', { method: 'extension' });
+ }
+ break;
+
+ case 'readonly':
+ // Set read-only mode
+ const readonlyInfo = {
+ pubkey: '',
+ signer: { method: 'readonly' }
+ };
+ Store.setCurrent(readonlyInfo);
+ LiteState.bus?.emit('authStateUpdate', readonlyInfo);
+ this._dispatchAuthEvent('login', readonlyInfo);
+ break;
+
+ case 'nip46':
+ if (secret && pubkey) {
+ // Set up NIP-46 remote signing
+ const info = {
+ pubkey,
+ signer: { method: 'nip46', ...secret }
+ };
+ Store.setCurrent(info);
+ LiteState.bus?.emit('authStateUpdate', info);
+ this._dispatchAuthEvent('login', info);
+ }
+ break;
+
+ default:
+ console.warn('Unhandled auth method:', method);
+ }
+ }
+
+ static _dispatchAuthEvent(type, info) {
+ const eventPayload = {
+ type,
+ info,
+ pubkey: info?.pubkey || '',
+ method: info?.signer?.method || '',
+ ...info
+ };
+
+ // Dispatch the event
+ window.dispatchEvent(new CustomEvent('nlAuth', { detail: eventPayload }));
+
+ this.bus?.emit('nlAuth', eventPayload);
+ }
+
+ static launch(startScreen) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized. Call init() first.');
+ }
+
+ console.log('Launch requested with screen:', startScreen);
+
+ // Initialize modal if needed
+ if (!LiteState.modal) {
+ // Import modal lazily
+ if (typeof Modal !== 'undefined') {
+ LiteState.modal = Modal.init(LiteState.options);
+ } else {
+ console.error('Modal component not available');
+ return;
+ }
+ }
+
+ // Open modal with specified screen
+ LiteState.modal.open({ startScreen });
+ }
+
+ static logout() {
+ if (!LiteState.initialized) return;
+
+ // Clear current account and state
+ Store.removeCurrentAccount();
+
+ // Reset internal state
+ LiteState.auth = null;
+
+ // Emit logout event
+ window.dispatchEvent(new CustomEvent('nlLogout'));
+ LiteState.bus?.emit('logout');
+
+ console.log('Logged out');
+ }
+
+ static setDarkMode(dark) {
+ if (!LiteState.options) return;
+
+ LiteState.options.darkMode = dark;
+ Store.setItem('darkMode', dark.toString());
+
+ // Update modal theme if initialized
+ if (LiteState.modal) {
+ // LiteState.modal.updateTheme();
+ }
+
+ window.dispatchEvent(new CustomEvent('nlDarkMode', { detail: { dark } }));
+ }
+
+ static setAuth(o) {
+ if (!o || !o.type) return;
+
+ console.log('setAuth called:', o);
+
+ // Validate request
+ if (!['login', 'signup', 'logout'].includes(o.type)) {
+ throw new Error(`Invalid auth type: ${o.type}`);
+ }
+
+ if (['login', 'signup'].includes(o.type) && !['connect', 'extension', 'local', 'otp', 'readOnly'].includes(o.method)) {
+ throw new Error(`Invalid auth method: ${o.method}`);
+ }
+
+ // Handle based on type
+ switch (o.type) {
+ case 'logout':
+ this.logout();
+ break;
+ default:
+ // Delegate to auth system - will be implemented
+ console.log('Auth delegation not yet implemented');
+ }
+ }
+
+ static cancelNeedAuth() {
+ // Cancel any ongoing auth flows
+ LiteState.bus?.emit('cancelAuth');
+ console.log('Auth flow cancelled');
+ }
+}
+
+// Initialize the window.nostr facade
+const windowNostr = {
+ async getPublicKey() {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (current && current.pubkey) {
+ return current.pubkey;
+ }
+
+ // Trigger auth flow
+ const authPromise = new Promise((resolve, reject) => {
+ const handleAuth = (event) => {
+ window.removeEventListener('nlAuth', handleAuth);
+ if (event.detail.type === 'login' && event.detail.pubkey) {
+ resolve(event.detail.pubkey);
+ } else {
+ reject(new Error('Authentication cancelled'));
+ }
+ };
+
+ window.addEventListener('nlAuth', handleAuth);
+
+ // Set timeout
+ setTimeout(() => {
+ window.removeEventListener('nlAuth', handleAuth);
+ reject(new Error('Authentication timeout'));
+ }, 300000); // 5 minutes
+ });
+
+ // Launch auth modal
+ NostrLite.launch('login');
+
+ return authPromise;
+ },
+
+ async signEvent(event) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ let current = Store.getCurrent();
+
+ // If no current account, trigger auth
+ if (!current) {
+ await window.nostr.getPublicKey(); // This will trigger auth
+ current = Store.getCurrent();
+ if (!current) {
+ throw new Error('Authentication failed');
+ }
+ }
+
+ // Route to appropriate signer
+ if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(this._hexToUint8Array(current.signer.secret));
+ return await signer.sign(event);
+ } else if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ const signedEvent = await bunkerSigner.signEvent(event);
+ return signedEvent;
+ } catch (error) {
+ console.error('NIP-46 signEvent failed:', error);
+ throw new Error(`NIP-46 signing failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'readonly') {
+ throw new Error('Cannot sign events in read-only mode');
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ // Route to extension
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ const signedEvent = await window.nostr.signEvent(event);
+ return signedEvent;
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for current account');
+ },
+
+ _hexToUint8Array(hex) {
+ // Convert hex string to Uint8Array
+ const bytes = new Uint8Array(hex.length / 2);
+ for (let i = 0; i < bytes.length; i++) {
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
+ }
+ return bytes;
+ },
+
+ nip04: {
+ async encrypt(pubkey, plaintext) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (!current) {
+ throw new Error('No authenticated user');
+ }
+
+ if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ return await bunkerSigner.nip04Encrypt(pubkey, plaintext);
+ } catch (error) {
+ console.error('NIP-46 nip04 encrypt failed:', error);
+ throw new Error(`NIP-46 encrypting failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(current.signer.secret);
+ return await signer.encrypt04(pubkey, plaintext);
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ return await window.nostr.nip04.encrypt(pubkey, plaintext);
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for NIP-04 encryption');
+ },
+
+ async decrypt(pubkey, ciphertext) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (!current) {
+ throw new Error('No authenticated user');
+ }
+
+ if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ return await bunkerSigner.nip04Decrypt(pubkey, ciphertext);
+ } catch (error) {
+ console.error('NIP-46 nip04 decrypt failed:', error);
+ throw new Error(`NIP-46 decrypting failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(current.signer.secret);
+ return await signer.decrypt04(pubkey, ciphertext);
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ return await window.nostr.nip04.decrypt(pubkey, ciphertext);
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for NIP-04 decryption');
+ }
+ },
+
+ nip44: {
+ async encrypt(pubkey, plaintext) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (!current) {
+ throw new Error('No authenticated user');
+ }
+
+ if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ return await bunkerSigner.nip44Encrypt(pubkey, plaintext);
+ } catch (error) {
+ console.error('NIP-46 nip44 encrypt failed:', error);
+ throw new Error(`NIP-46 encrypting failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(current.signer.secret);
+ return await signer.encrypt44(pubkey, plaintext);
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ // Use extension if it supports nip44
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ if (window.nostr.nip44) {
+ return await window.nostr.nip44.encrypt(pubkey, plaintext);
+ } else {
+ throw new Error('Extension does not support NIP-44');
+ }
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for NIP-44 encryption');
+ },
+
+ async decrypt(pubkey, ciphertext) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (!current) {
+ throw new Error('No authenticated user');
+ }
+
+ if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ return await bunkerSigner.nip44Decrypt(pubkey, ciphertext);
+ } catch (error) {
+ console.error('NIP-46 nip44 decrypt failed:', error);
+ throw new Error(`NIP-46 decrypting failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(current.signer.secret);
+ return await signer.decrypt44(pubkey, ciphertext);
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ if (window.nostr.nip44) {
+ return await window.nostr.nip44.decrypt(pubkey, ciphertext);
+ } else {
+ throw new Error('Extension does not support NIP-44');
+ }
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for NIP-44 decryption');
+ }
+ }
+};
+
+// Export the API
+window.NOSTR_LOGIN_LITE = {
+ init: NostrLite.init.bind(NostrLite),
+ launch: NostrLite.launch.bind(NostrLite),
+ logout: NostrLite.logout.bind(NostrLite),
+ setDarkMode: NostrLite.setDarkMode.bind(NostrLite),
+ setAuth: NostrLite.setAuth.bind(NostrLite),
+ cancelNeedAuth: NostrLite.cancelNeedAuth.bind(NostrLite),
+ // Expose internal components for debugging
+ get _extensionBridge() {
+ return LiteState.extensionBridge;
+ },
+ get _state() {
+ return LiteState;
+ }
+};
+
+// Set window.nostr facade properly (extensions will be handled by ExtensionBridge)
+if (typeof window !== 'undefined') {
+ window.nostr = windowNostr;
+
+ // Ensure all methods are properly exposed
+ console.log('NOSTR_LOGIN_LITE: window.nostr facade installed with methods:', Object.keys(windowNostr));
+}
+
+console.log('NOSTR_LOGIN_LITE loaded - use window.NOSTR_LOGIN_LITE.init(options) to initialize');
+
diff --git a/lite/nostr-login-lite.js b/lite/nostr-login-lite.js
new file mode 100644
index 0000000..7ce1839
--- /dev/null
+++ b/lite/nostr-login-lite.js
@@ -0,0 +1,918 @@
+/**
+ * NOSTR_LOGIN_LITE
+ * A minimal, dependency-light replacement for the current auth/UI stack
+ * Preserves all login methods and window.nostr surface
+ */
+
+// Import NIP-46 client
+if (typeof NIP46Client === 'undefined') {
+ // Load NIP46Client if not already available (for non-bundled version)
+ const script = document.createElement('script');
+ script.src = './core/nip46-client.js';
+ document.head.appendChild(script);
+}
+
+// Global state
+const LiteState = {
+ initialized: false,
+ windowNostr: null,
+ options: null,
+ auth: null,
+ modal: null,
+ bus: null,
+ pool: null,
+ nip44Codec: null,
+ extensionBridge: null,
+ nip46Client: null
+};
+
+// Dependencies verification
+class Deps {
+ static ensureNostrToolsLoaded() {
+ if (typeof window === 'undefined') {
+ throw new Error('NOSTR_LOGIN_LITE must run in browser environment');
+ }
+
+ if (!window.NostrTools) {
+ throw new Error(
+ 'window.NostrTools is required but not loaded. ' +
+ 'Please include: '
+ );
+ }
+
+ // Verify required APIs
+ const required = ['SimplePool', 'getPublicKey', 'finalizeEvent', 'nip04'];
+ for (const api of required) {
+ if (!window.NostrTools[api]) {
+ throw new Error(`window.NostrTools.${api} is required but missing`);
+ }
+ }
+
+ // Check for key generation function (might be generateSecretKey or generatePrivateKey)
+ if (!window.NostrTools.generateSecretKey && !window.NostrTools.generatePrivateKey) {
+ throw new Error('window.NostrTools must have either generateSecretKey or generatePrivateKey');
+ }
+
+ return true;
+ }
+}
+
+// Event Bus for internal communication
+class Bus {
+ constructor() {
+ this.handlers = {};
+ }
+
+ on(event, handler) {
+ if (!this.handlers[event]) {
+ this.handlers[event] = [];
+ }
+ this.handlers[event].push(handler);
+ }
+
+ off(event, handler) {
+ if (!this.handlers[event]) return;
+ this.handlers[event] = this.handlers[event].filter(h => h !== handler);
+ }
+
+ emit(event, payload) {
+ if (!this.handlers[event]) return;
+ this.handlers[event].forEach(handler => {
+ try {
+ handler(payload);
+ } catch (e) {
+ console.error(`Error in event handler for ${event}:`, e);
+ }
+ });
+ }
+}
+
+// Storage helpers
+class Store {
+ static addAccount(info) {
+ const accounts = this.getAccounts();
+ // Remove existing account with same pubkey if present
+ const filtered = accounts.filter(acc => acc.pubkey !== info.pubkey);
+ filtered.push(info);
+ localStorage.setItem('nl_accounts', JSON.stringify(filtered));
+ }
+
+ static removeCurrentAccount() {
+ const current = this.getCurrent();
+ if (current && current.pubkey) {
+ const accounts = this.getAccounts();
+ const filtered = accounts.filter(acc => acc.pubkey !== current.pubkey);
+ localStorage.setItem('nl_accounts', JSON.stringify(filtered));
+ localStorage.removeItem('nl_current');
+ }
+ }
+
+ static getCurrent() {
+ try {
+ const stored = localStorage.getItem('nl_current');
+ return stored ? JSON.parse(stored) : null;
+ } catch (e) {
+ console.error('Error parsing current account:', e);
+ return null;
+ }
+ }
+
+ static setCurrent(info) {
+ localStorage.setItem('nl_current', JSON.stringify(info));
+ }
+
+ static getAccounts() {
+ try {
+ const stored = localStorage.getItem('nl_accounts');
+ return stored ? JSON.parse(stored) : [];
+ } catch (e) {
+ console.error('Error parsing accounts:', e);
+ return [];
+ }
+ }
+
+ static getRecents() {
+ // Return last 5 used accounts in reverse chronological order
+ const accounts = this.getAccounts().slice(-5).reverse();
+ return accounts;
+ }
+
+ static setItem(key, value) {
+ localStorage.setItem(`nl-${key}`, value);
+ }
+
+ static getItem(key) {
+ return localStorage.getItem(`nl-${key}`);
+ }
+
+ static async getIcon() {
+ // Simple default icon - could be extended to fetch from profile
+ return '🔑';
+ }
+}
+
+// Relay configuration helpers
+class Relays {
+ static getDefaultRelays(options) {
+ if (options?.relays) {
+ return this.normalize(options.relays);
+ }
+
+ // Default relays for fallbacks
+ return [
+ 'wss://relay.damus.io',
+ 'wss://relay.snort.social',
+ 'wss://nos.lol'
+ ];
+ }
+
+ static normalize(relays) {
+ return relays.map(relay => {
+ // Ensure wss:// prefix
+ if (relay.startsWith('ws://')) {
+ return relay.replace('ws://', 'wss://');
+ } else if (!relay.startsWith('wss://')) {
+ return `wss://${relay}`;
+ }
+ return relay;
+ }).filter(relay => {
+ // Remove duplicates and validate URLs
+ try {
+ new URL(relay);
+ return true;
+ } catch {
+ return false;
+ }
+ }).filter((relay, index, self) => self.indexOf(relay) === index); // dedupe
+ }
+}
+
+// Minimal NIP-44 codec fallback
+class Nip44 {
+ constructor() {
+ this.Nip44 = null;
+ // Initialize with existing codec if available
+ this.nip44Available = window.NostrTools?.nip44;
+ }
+
+ static encrypt(ourSk, theirPk, plaintext) {
+ if (window.NostrTools?.nip44?.encrypt) {
+ return window.NostrTools.nip44.encrypt(ourSk, theirPk, plaintext);
+ }
+
+ throw new Error('NIP-44 encryption not available. Please use nostr-tools@>=2.x or provide codec implementation.');
+ }
+
+ static decrypt(ourSk, theirPk, ciphertext) {
+ if (window.NostrTools?.nip44?.decrypt) {
+ return window.NostrTools.nip44.decrypt(ourSk, theirPk, ciphertext);
+ }
+
+ throw new Error('NIP-44 decryption not available. Please use nostr-tools@>=2.x or provide codec implementation.');
+ }
+}
+
+// LocalSigner wrapping window.NostrTools
+class LocalSigner {
+ constructor(sk) {
+ this.sk = sk;
+ // Generate pubkey from secret key
+ this.pk = this._getPubKey();
+ }
+
+ _getPubKey() {
+ const seckey = this.sk.startsWith('nsec') ?
+ window.NostrTools.nip19.decode(this.sk).data :
+ this.sk;
+ return window.NostrTools.getPublicKey(seckey);
+ }
+
+ pubkey() {
+ return this.pk;
+ }
+
+ async sign(event) {
+ // Prepare event for signing
+ const ev = { ...event };
+ ev.pubkey = this.pk;
+
+ // Generate event ID and sign
+ const signedEvent = await window.NostrTools.finalizeEvent(ev, this.sk);
+ return signedEvent;
+ }
+
+ async encrypt04(pubkey, plaintext) {
+ return await window.NostrTools.nip04.encrypt(this.sk, pubkey, plaintext);
+ }
+
+ async decrypt04(pubkey, ciphertext) {
+ return await window.NostrTools.nip04.decrypt(this.sk, pubkey, ciphertext);
+ }
+
+ async encrypt44(pubkey, plaintext) {
+ return Nip44.encrypt(this.sk, pubkey, plaintext);
+ }
+
+ async decrypt44(pubkey, ciphertext) {
+ return Nip44.decrypt(this.sk, pubkey, ciphertext);
+ }
+}
+
+// ExtensionBridge for detecting and managing browser extensions
+class ExtensionBridge {
+ constructor() {
+ this.checking = false;
+ this.checkInterval = null;
+ this.originalNostr = null;
+ this.foundExtension = null;
+ }
+
+ startChecking(nostrLite) {
+ if (this.checking) return;
+ this.checking = true;
+
+ const check = () => {
+ this.initExtension(nostrLite);
+ };
+
+ // Check immediately
+ check();
+
+ // Then check every 200ms for 30 seconds
+ this.checkInterval = setInterval(check, 200);
+
+ // Stop checking after 30 seconds
+ setTimeout(() => {
+ clearInterval(this.checkInterval);
+ this.checkInterval = null;
+ }, 30000);
+ }
+
+ initExtension(nostrLite, lastTry = false) {
+ const extension = window.nostr;
+
+ if (extension && !this.foundExtension) {
+ // Check if this is actually a real extension, not our own library
+ const isRealExtension = (
+ extension !== nostrLite && // Not the same object we're about to assign
+ extension !== windowNostr && // Not our windowNostr object
+ typeof extension._hexToUint8Array !== 'function' && // Our library has this internal method
+ extension.constructor.name !== 'Object' // Real extensions usually have proper constructors
+ );
+
+ if (isRealExtension) {
+ this.foundExtension = extension;
+
+ // Cache the extension and reassign window.nostr to our lite version
+ this.originalNostr = window.nostr;
+ window.nostr = nostrLite;
+
+ console.log('Real Nostr extension detected and bridged:', extension.constructor.name);
+
+ // If currently authenticated, reconcile state
+ if (LiteState.auth?.signer?.method === 'extension') {
+ this.reconcileExtension();
+ }
+ } else {
+ console.log('Skipping non-extension object on window.nostr:', extension.constructor.name);
+ }
+ }
+ }
+
+ hasExtension() {
+ return !!this.foundExtension;
+ }
+
+ async setExtensionReadPubkey(expectedPubkey = null) {
+ if (!this.foundExtension) return false;
+
+ try {
+ // Temporarily set window.nostr to extension
+ const temp = window.nostr;
+ window.nostr = this.foundExtension;
+
+ const pubkey = await this.foundExtension.getPublicKey();
+
+ // Restore our lite implementation
+ window.nostr = temp;
+
+ if (expectedPubkey && pubkey !== expectedPubkey) {
+ console.warn(`Extension pubkey ${pubkey} does not match expected ${expectedPubkey}`);
+ }
+
+ return pubkey;
+ } catch (e) {
+ console.error('Error reading extension pubkey:', e);
+ return null;
+ }
+ }
+
+ trySetForPubkey(expectedPubkey) {
+ if (!this.hasExtension()) return false;
+
+ this.setExtensionReadPubkey(expectedPubkey).then(pubkey => {
+ if (pubkey) {
+ LiteState.bus?.emit('extensionLogin', { pubkey });
+ }
+ });
+
+ return true;
+ }
+
+ setExtension() {
+ if (!this.foundExtension) return;
+ window.nostr = this.foundExtension;
+ this.setExtensionReadPubkey().then(pubkey => {
+ if (pubkey) {
+ LiteState.bus?.emit('extensionSet', { pubkey });
+ }
+ });
+ }
+
+ unset(nostrLite) {
+ window.nostr = nostrLite;
+ }
+
+ reconcileExtension() {
+ // Handle extension state changes
+ this.setExtensionReadPubkey().then(pubkey => {
+ if (pubkey) {
+ // Update current account if extension is the signer
+ const current = Store.getCurrent();
+ if (current && current.signer?.method === 'extension') {
+ const info = {
+ ...current,
+ pubkey,
+ signer: { method: 'extension' }
+ };
+ Store.setCurrent(info);
+ LiteState.bus?.emit('authStateUpdate', info);
+ }
+ }
+ });
+ }
+}
+
+// Main API surface
+class NostrLite {
+ static async init(options = {}) {
+ // Ensure dependencies are loaded
+ Deps.ensureNostrToolsLoaded();
+
+ // Prevent double initialization
+ if (LiteState.initialized) {
+ console.warn('NOSTR_LOGIN_LITE already initialized');
+ return;
+ }
+
+ // Initialize components
+ LiteState.bus = new Bus();
+ LiteState.extensionBridge = new ExtensionBridge();
+
+ // Initialize NIP-46 client
+ LiteState.nip46Client = new NIP46Client();
+
+ // Store options
+ LiteState.options = {
+ theme: 'light',
+ darkMode: false,
+ relays: Relays.getDefaultRelays(options),
+ methods: {
+ connect: true,
+ extension: true,
+ local: true,
+ readonly: true,
+ otp: true
+ },
+ otp: {},
+ ...options
+ };
+
+ // Start extension detection
+ LiteState.extensionBridge.startChecking(windowNostr);
+
+ // Setup auth methods
+ this._setupAuth();
+
+ // Initialize modal UI
+ // this._initModal();
+
+ console.log('NOSTR_LOGIN_LITE initialized with options:', LiteState.options);
+ LiteState.initialized = true;
+ }
+
+ static _setupAuth() {
+ // Set up event listeners for modal interactions
+ window.addEventListener('nlMethodSelected', (event) => {
+ this._handleMethodSelected(event.detail);
+ });
+
+ // Set up other auth-related event listeners
+ this._setupAuthEventListeners();
+
+ console.log('Auth system setup loaded');
+ }
+
+ static _setupAuthEventListeners() {
+ // Handle extension detection
+ this.bus?.on('extensionDetected', (extension) => {
+ console.log('Extension detected');
+ LiteState.extensionBridge.foundExtension = extension;
+ });
+
+ // Handle auth URL from NIP-46
+ window.addEventListener('nlAuthUrl', (event) => {
+ console.log('Auth URL received:', event.detail.url);
+ // Could show URL in modal or trigger external flow
+ });
+
+ // Handle logout events
+ window.addEventListener('nlLogout', () => {
+ console.log('Logout event received');
+ this.logout();
+ });
+ }
+
+ static _handleMethodSelected(detail) {
+ console.log('Method selected:', detail);
+
+ const { method, pubkey, secret, extension } = detail;
+
+ switch (method) {
+ case 'local':
+ if (secret && pubkey) {
+ // Set up local key authentication
+ const info = {
+ pubkey,
+ signer: { method: 'local', secret }
+ };
+ Store.setCurrent(info);
+ LiteState.bus?.emit('authStateUpdate', info);
+ this._dispatchAuthEvent('login', info);
+ }
+ break;
+
+ case 'extension':
+ if (pubkey && extension) {
+ // Store the extension object in the ExtensionBridge for future use
+ LiteState.extensionBridge.foundExtension = extension;
+ LiteState.extensionBridge.originalNostr = extension;
+
+ // Set up extension authentication
+ const info = {
+ pubkey,
+ signer: { method: 'extension' }
+ };
+ Store.setCurrent(info);
+ LiteState.bus?.emit('authStateUpdate', info);
+ this._dispatchAuthEvent('login', info);
+
+ console.log('Extension authentication set up successfully');
+ } else {
+ // Fallback to extension bridge detection
+ LiteState.bus?.emit('authMethodSelected', { method: 'extension' });
+ }
+ break;
+
+ case 'readonly':
+ // Set read-only mode
+ const readonlyInfo = {
+ pubkey: '',
+ signer: { method: 'readonly' }
+ };
+ Store.setCurrent(readonlyInfo);
+ LiteState.bus?.emit('authStateUpdate', readonlyInfo);
+ this._dispatchAuthEvent('login', readonlyInfo);
+ break;
+
+ case 'nip46':
+ if (secret && pubkey) {
+ // Set up NIP-46 remote signing
+ const info = {
+ pubkey,
+ signer: { method: 'nip46', ...secret }
+ };
+ Store.setCurrent(info);
+ LiteState.bus?.emit('authStateUpdate', info);
+ this._dispatchAuthEvent('login', info);
+ }
+ break;
+
+ default:
+ console.warn('Unhandled auth method:', method);
+ }
+ }
+
+ static _dispatchAuthEvent(type, info) {
+ const eventPayload = {
+ type,
+ info,
+ pubkey: info?.pubkey || '',
+ method: info?.signer?.method || '',
+ ...info
+ };
+
+ // Dispatch the event
+ window.dispatchEvent(new CustomEvent('nlAuth', { detail: eventPayload }));
+
+ this.bus?.emit('nlAuth', eventPayload);
+ }
+
+ static launch(startScreen) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized. Call init() first.');
+ }
+
+ console.log('Launch requested with screen:', startScreen);
+
+ // Initialize modal if needed
+ if (!LiteState.modal) {
+ // Import modal lazily
+ if (typeof Modal !== 'undefined') {
+ LiteState.modal = Modal.init(LiteState.options);
+ } else {
+ console.error('Modal component not available');
+ return;
+ }
+ }
+
+ // Open modal with specified screen
+ LiteState.modal.open({ startScreen });
+ }
+
+ static logout() {
+ if (!LiteState.initialized) return;
+
+ // Clear current account and state
+ Store.removeCurrentAccount();
+
+ // Reset internal state
+ LiteState.auth = null;
+
+ // Emit logout event
+ window.dispatchEvent(new CustomEvent('nlLogout'));
+ LiteState.bus?.emit('logout');
+
+ console.log('Logged out');
+ }
+
+ static setDarkMode(dark) {
+ if (!LiteState.options) return;
+
+ LiteState.options.darkMode = dark;
+ Store.setItem('darkMode', dark.toString());
+
+ // Update modal theme if initialized
+ if (LiteState.modal) {
+ // LiteState.modal.updateTheme();
+ }
+
+ window.dispatchEvent(new CustomEvent('nlDarkMode', { detail: { dark } }));
+ }
+
+ static setAuth(o) {
+ if (!o || !o.type) return;
+
+ console.log('setAuth called:', o);
+
+ // Validate request
+ if (!['login', 'signup', 'logout'].includes(o.type)) {
+ throw new Error(`Invalid auth type: ${o.type}`);
+ }
+
+ if (['login', 'signup'].includes(o.type) && !['connect', 'extension', 'local', 'otp', 'readOnly'].includes(o.method)) {
+ throw new Error(`Invalid auth method: ${o.method}`);
+ }
+
+ // Handle based on type
+ switch (o.type) {
+ case 'logout':
+ this.logout();
+ break;
+ default:
+ // Delegate to auth system - will be implemented
+ console.log('Auth delegation not yet implemented');
+ }
+ }
+
+ static cancelNeedAuth() {
+ // Cancel any ongoing auth flows
+ LiteState.bus?.emit('cancelAuth');
+ console.log('Auth flow cancelled');
+ }
+}
+
+// Initialize the window.nostr facade
+const windowNostr = {
+ async getPublicKey() {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (current && current.pubkey) {
+ return current.pubkey;
+ }
+
+ // Trigger auth flow
+ const authPromise = new Promise((resolve, reject) => {
+ const handleAuth = (event) => {
+ window.removeEventListener('nlAuth', handleAuth);
+ if (event.detail.type === 'login' && event.detail.pubkey) {
+ resolve(event.detail.pubkey);
+ } else {
+ reject(new Error('Authentication cancelled'));
+ }
+ };
+
+ window.addEventListener('nlAuth', handleAuth);
+
+ // Set timeout
+ setTimeout(() => {
+ window.removeEventListener('nlAuth', handleAuth);
+ reject(new Error('Authentication timeout'));
+ }, 300000); // 5 minutes
+ });
+
+ // Launch auth modal
+ NostrLite.launch('login');
+
+ return authPromise;
+ },
+
+ async signEvent(event) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ let current = Store.getCurrent();
+
+ // If no current account, trigger auth
+ if (!current) {
+ await window.nostr.getPublicKey(); // This will trigger auth
+ current = Store.getCurrent();
+ if (!current) {
+ throw new Error('Authentication failed');
+ }
+ }
+
+ // Route to appropriate signer
+ if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(this._hexToUint8Array(current.signer.secret));
+ return await signer.sign(event);
+ } else if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ const signedEvent = await bunkerSigner.signEvent(event);
+ return signedEvent;
+ } catch (error) {
+ console.error('NIP-46 signEvent failed:', error);
+ throw new Error(`NIP-46 signing failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'readonly') {
+ throw new Error('Cannot sign events in read-only mode');
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ // Route to extension
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ const signedEvent = await window.nostr.signEvent(event);
+ return signedEvent;
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for current account');
+ },
+
+ _hexToUint8Array(hex) {
+ // Convert hex string to Uint8Array
+ const bytes = new Uint8Array(hex.length / 2);
+ for (let i = 0; i < bytes.length; i++) {
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
+ }
+ return bytes;
+ },
+
+ nip04: {
+ async encrypt(pubkey, plaintext) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (!current) {
+ throw new Error('No authenticated user');
+ }
+
+ if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ return await bunkerSigner.nip04Encrypt(pubkey, plaintext);
+ } catch (error) {
+ console.error('NIP-46 nip04 encrypt failed:', error);
+ throw new Error(`NIP-46 encrypting failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(current.signer.secret);
+ return await signer.encrypt04(pubkey, plaintext);
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ return await window.nostr.nip04.encrypt(pubkey, plaintext);
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for NIP-04 encryption');
+ },
+
+ async decrypt(pubkey, ciphertext) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (!current) {
+ throw new Error('No authenticated user');
+ }
+
+ if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ return await bunkerSigner.nip04Decrypt(pubkey, ciphertext);
+ } catch (error) {
+ console.error('NIP-46 nip04 decrypt failed:', error);
+ throw new Error(`NIP-46 decrypting failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(current.signer.secret);
+ return await signer.decrypt04(pubkey, ciphertext);
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ return await window.nostr.nip04.decrypt(pubkey, ciphertext);
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for NIP-04 decryption');
+ }
+ },
+
+ nip44: {
+ async encrypt(pubkey, plaintext) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (!current) {
+ throw new Error('No authenticated user');
+ }
+
+ if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ return await bunkerSigner.nip44Encrypt(pubkey, plaintext);
+ } catch (error) {
+ console.error('NIP-46 nip44 encrypt failed:', error);
+ throw new Error(`NIP-46 encrypting failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(current.signer.secret);
+ return await signer.encrypt44(pubkey, plaintext);
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ // Use extension if it supports nip44
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ if (window.nostr.nip44) {
+ return await window.nostr.nip44.encrypt(pubkey, plaintext);
+ } else {
+ throw new Error('Extension does not support NIP-44');
+ }
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for NIP-44 encryption');
+ },
+
+ async decrypt(pubkey, ciphertext) {
+ if (!LiteState.initialized) {
+ throw new Error('NOSTR_LOGIN_LITE not initialized');
+ }
+
+ const current = Store.getCurrent();
+ if (!current) {
+ throw new Error('No authenticated user');
+ }
+
+ if (current.signer?.method === 'nip46' && current.signer.bunkerSigner) {
+ // Route to NIP-46 remote signer
+ try {
+ const bunkerSigner = current.signer.bunkerSigner;
+ return await bunkerSigner.nip44Decrypt(pubkey, ciphertext);
+ } catch (error) {
+ console.error('NIP-46 nip44 decrypt failed:', error);
+ throw new Error(`NIP-46 decrypting failed: ${error.message}`);
+ }
+ } else if (current.signer?.method === 'local' && current.signer.secret) {
+ const signer = new LocalSigner(current.signer.secret);
+ return await signer.decrypt44(pubkey, ciphertext);
+ } else if (current.signer?.method === 'extension' && LiteState.extensionBridge?.hasExtension()) {
+ const temp = window.nostr;
+ window.nostr = LiteState.extensionBridge.foundExtension;
+ try {
+ if (window.nostr.nip44) {
+ return await window.nostr.nip44.decrypt(pubkey, ciphertext);
+ } else {
+ throw new Error('Extension does not support NIP-44');
+ }
+ } finally {
+ window.nostr = temp;
+ }
+ }
+
+ throw new Error('No suitable signer available for NIP-44 decryption');
+ }
+ }
+};
+
+// Export the API
+window.NOSTR_LOGIN_LITE = {
+ init: NostrLite.init.bind(NostrLite),
+ launch: NostrLite.launch.bind(NostrLite),
+ logout: NostrLite.logout.bind(NostrLite),
+ setDarkMode: NostrLite.setDarkMode.bind(NostrLite),
+ setAuth: NostrLite.setAuth.bind(NostrLite),
+ cancelNeedAuth: NostrLite.cancelNeedAuth.bind(NostrLite),
+ // Expose internal components for debugging
+ get _extensionBridge() {
+ return LiteState.extensionBridge;
+ },
+ get _state() {
+ return LiteState;
+ }
+};
+
+// Set window.nostr facade properly (extensions will be handled by ExtensionBridge)
+if (typeof window !== 'undefined') {
+ window.nostr = windowNostr;
+
+ // Ensure all methods are properly exposed
+ console.log('NOSTR_LOGIN_LITE: window.nostr facade installed with methods:', Object.keys(windowNostr));
+}
+
+console.log('NOSTR_LOGIN_LITE loaded - use window.NOSTR_LOGIN_LITE.init(options) to initialize');
\ No newline at end of file
diff --git a/lite/nostr.bundle.js b/lite/nostr.bundle.js
new file mode 100644
index 0000000..17634d1
--- /dev/null
+++ b/lite/nostr.bundle.js
@@ -0,0 +1,6860 @@
+"use strict";
+var NostrTools = (() => {
+ var __defProp = Object.defineProperty;
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+ var __getOwnPropNames = Object.getOwnPropertyNames;
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
+ var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+ };
+ var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+ };
+ var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
+
+ // index.ts
+ var nostr_tools_exports = {};
+ __export(nostr_tools_exports, {
+ Relay: () => Relay,
+ SimplePool: () => SimplePool,
+ finalizeEvent: () => finalizeEvent,
+ fj: () => fakejson_exports,
+ generateSecretKey: () => generateSecretKey,
+ getEventHash: () => getEventHash,
+ getFilterLimit: () => getFilterLimit,
+ getPublicKey: () => getPublicKey,
+ kinds: () => kinds_exports,
+ matchFilter: () => matchFilter,
+ matchFilters: () => matchFilters,
+ mergeFilters: () => mergeFilters,
+ nip04: () => nip04_exports,
+ nip05: () => nip05_exports,
+ nip10: () => nip10_exports,
+ nip11: () => nip11_exports,
+ nip13: () => nip13_exports,
+ nip17: () => nip17_exports,
+ nip18: () => nip18_exports,
+ nip19: () => nip19_exports,
+ nip21: () => nip21_exports,
+ nip25: () => nip25_exports,
+ nip27: () => nip27_exports,
+ nip28: () => nip28_exports,
+ nip30: () => nip30_exports,
+ nip39: () => nip39_exports,
+ nip42: () => nip42_exports,
+ nip44: () => nip44_exports,
+ nip47: () => nip47_exports,
+ nip54: () => nip54_exports,
+ nip57: () => nip57_exports,
+ nip59: () => nip59_exports,
+ nip98: () => nip98_exports,
+ parseReferences: () => parseReferences,
+ serializeEvent: () => serializeEvent,
+ sortEvents: () => sortEvents,
+ utils: () => utils_exports2,
+ validateEvent: () => validateEvent,
+ verifiedSymbol: () => verifiedSymbol,
+ verifyEvent: () => verifyEvent
+ });
+
+ // node_modules/@noble/curves/node_modules/@noble/hashes/esm/_assert.js
+ function number(n) {
+ if (!Number.isSafeInteger(n) || n < 0)
+ throw new Error(`Wrong positive integer: ${n}`);
+ }
+ function bytes(b, ...lengths) {
+ if (!(b instanceof Uint8Array))
+ throw new Error("Expected Uint8Array");
+ if (lengths.length > 0 && !lengths.includes(b.length))
+ throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
+ }
+ function hash(hash3) {
+ if (typeof hash3 !== "function" || typeof hash3.create !== "function")
+ throw new Error("Hash should be wrapped by utils.wrapConstructor");
+ number(hash3.outputLen);
+ number(hash3.blockLen);
+ }
+ function exists(instance, checkFinished = true) {
+ if (instance.destroyed)
+ throw new Error("Hash instance has been destroyed");
+ if (checkFinished && instance.finished)
+ throw new Error("Hash#digest() has already been called");
+ }
+ function output(out, instance) {
+ bytes(out);
+ const min = instance.outputLen;
+ if (out.length < min) {
+ throw new Error(`digestInto() expects output buffer of length at least ${min}`);
+ }
+ }
+
+ // node_modules/@noble/curves/node_modules/@noble/hashes/esm/crypto.js
+ var crypto = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : void 0;
+
+ // node_modules/@noble/curves/node_modules/@noble/hashes/esm/utils.js
+ var u8a = (a) => a instanceof Uint8Array;
+ var createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
+ var rotr = (word, shift) => word << 32 - shift | word >>> shift;
+ var isLE = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
+ if (!isLE)
+ throw new Error("Non little-endian hardware is not supported");
+ function utf8ToBytes(str) {
+ if (typeof str !== "string")
+ throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
+ return new Uint8Array(new TextEncoder().encode(str));
+ }
+ function toBytes(data) {
+ if (typeof data === "string")
+ data = utf8ToBytes(data);
+ if (!u8a(data))
+ throw new Error(`expected Uint8Array, got ${typeof data}`);
+ return data;
+ }
+ function concatBytes(...arrays) {
+ const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
+ let pad2 = 0;
+ arrays.forEach((a) => {
+ if (!u8a(a))
+ throw new Error("Uint8Array expected");
+ r.set(a, pad2);
+ pad2 += a.length;
+ });
+ return r;
+ }
+ var Hash = class {
+ clone() {
+ return this._cloneInto();
+ }
+ };
+ var toStr = {}.toString;
+ function wrapConstructor(hashCons) {
+ const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
+ const tmp = hashCons();
+ hashC.outputLen = tmp.outputLen;
+ hashC.blockLen = tmp.blockLen;
+ hashC.create = () => hashCons();
+ return hashC;
+ }
+ function randomBytes(bytesLength = 32) {
+ if (crypto && typeof crypto.getRandomValues === "function") {
+ return crypto.getRandomValues(new Uint8Array(bytesLength));
+ }
+ throw new Error("crypto.getRandomValues must be defined");
+ }
+
+ // node_modules/@noble/curves/node_modules/@noble/hashes/esm/_sha2.js
+ function setBigUint64(view, byteOffset, value, isLE4) {
+ if (typeof view.setBigUint64 === "function")
+ return view.setBigUint64(byteOffset, value, isLE4);
+ const _32n = BigInt(32);
+ const _u32_max = BigInt(4294967295);
+ const wh = Number(value >> _32n & _u32_max);
+ const wl = Number(value & _u32_max);
+ const h = isLE4 ? 4 : 0;
+ const l = isLE4 ? 0 : 4;
+ view.setUint32(byteOffset + h, wh, isLE4);
+ view.setUint32(byteOffset + l, wl, isLE4);
+ }
+ var SHA2 = class extends Hash {
+ constructor(blockLen, outputLen, padOffset, isLE4) {
+ super();
+ this.blockLen = blockLen;
+ this.outputLen = outputLen;
+ this.padOffset = padOffset;
+ this.isLE = isLE4;
+ this.finished = false;
+ this.length = 0;
+ this.pos = 0;
+ this.destroyed = false;
+ this.buffer = new Uint8Array(blockLen);
+ this.view = createView(this.buffer);
+ }
+ update(data) {
+ exists(this);
+ const { view, buffer, blockLen } = this;
+ data = toBytes(data);
+ const len = data.length;
+ for (let pos = 0; pos < len; ) {
+ const take = Math.min(blockLen - this.pos, len - pos);
+ if (take === blockLen) {
+ const dataView = createView(data);
+ for (; blockLen <= len - pos; pos += blockLen)
+ this.process(dataView, pos);
+ continue;
+ }
+ buffer.set(data.subarray(pos, pos + take), this.pos);
+ this.pos += take;
+ pos += take;
+ if (this.pos === blockLen) {
+ this.process(view, 0);
+ this.pos = 0;
+ }
+ }
+ this.length += data.length;
+ this.roundClean();
+ return this;
+ }
+ digestInto(out) {
+ exists(this);
+ output(out, this);
+ this.finished = true;
+ const { buffer, view, blockLen, isLE: isLE4 } = this;
+ let { pos } = this;
+ buffer[pos++] = 128;
+ this.buffer.subarray(pos).fill(0);
+ if (this.padOffset > blockLen - pos) {
+ this.process(view, 0);
+ pos = 0;
+ }
+ for (let i2 = pos; i2 < blockLen; i2++)
+ buffer[i2] = 0;
+ setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE4);
+ this.process(view, 0);
+ const oview = createView(out);
+ const len = this.outputLen;
+ if (len % 4)
+ throw new Error("_sha2: outputLen should be aligned to 32bit");
+ const outLen = len / 4;
+ const state = this.get();
+ if (outLen > state.length)
+ throw new Error("_sha2: outputLen bigger than state");
+ for (let i2 = 0; i2 < outLen; i2++)
+ oview.setUint32(4 * i2, state[i2], isLE4);
+ }
+ digest() {
+ const { buffer, outputLen } = this;
+ this.digestInto(buffer);
+ const res = buffer.slice(0, outputLen);
+ this.destroy();
+ return res;
+ }
+ _cloneInto(to) {
+ to || (to = new this.constructor());
+ to.set(...this.get());
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
+ to.length = length;
+ to.pos = pos;
+ to.finished = finished;
+ to.destroyed = destroyed;
+ if (length % blockLen)
+ to.buffer.set(buffer);
+ return to;
+ }
+ };
+
+ // node_modules/@noble/curves/node_modules/@noble/hashes/esm/sha256.js
+ var Chi = (a, b, c) => a & b ^ ~a & c;
+ var Maj = (a, b, c) => a & b ^ a & c ^ b & c;
+ var SHA256_K = /* @__PURE__ */ new Uint32Array([
+ 1116352408,
+ 1899447441,
+ 3049323471,
+ 3921009573,
+ 961987163,
+ 1508970993,
+ 2453635748,
+ 2870763221,
+ 3624381080,
+ 310598401,
+ 607225278,
+ 1426881987,
+ 1925078388,
+ 2162078206,
+ 2614888103,
+ 3248222580,
+ 3835390401,
+ 4022224774,
+ 264347078,
+ 604807628,
+ 770255983,
+ 1249150122,
+ 1555081692,
+ 1996064986,
+ 2554220882,
+ 2821834349,
+ 2952996808,
+ 3210313671,
+ 3336571891,
+ 3584528711,
+ 113926993,
+ 338241895,
+ 666307205,
+ 773529912,
+ 1294757372,
+ 1396182291,
+ 1695183700,
+ 1986661051,
+ 2177026350,
+ 2456956037,
+ 2730485921,
+ 2820302411,
+ 3259730800,
+ 3345764771,
+ 3516065817,
+ 3600352804,
+ 4094571909,
+ 275423344,
+ 430227734,
+ 506948616,
+ 659060556,
+ 883997877,
+ 958139571,
+ 1322822218,
+ 1537002063,
+ 1747873779,
+ 1955562222,
+ 2024104815,
+ 2227730452,
+ 2361852424,
+ 2428436474,
+ 2756734187,
+ 3204031479,
+ 3329325298
+ ]);
+ var IV = /* @__PURE__ */ new Uint32Array([
+ 1779033703,
+ 3144134277,
+ 1013904242,
+ 2773480762,
+ 1359893119,
+ 2600822924,
+ 528734635,
+ 1541459225
+ ]);
+ var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
+ var SHA256 = class extends SHA2 {
+ constructor() {
+ super(64, 32, 8, false);
+ this.A = IV[0] | 0;
+ this.B = IV[1] | 0;
+ this.C = IV[2] | 0;
+ this.D = IV[3] | 0;
+ this.E = IV[4] | 0;
+ this.F = IV[5] | 0;
+ this.G = IV[6] | 0;
+ this.H = IV[7] | 0;
+ }
+ get() {
+ const { A, B, C, D, E, F, G, H } = this;
+ return [A, B, C, D, E, F, G, H];
+ }
+ set(A, B, C, D, E, F, G, H) {
+ this.A = A | 0;
+ this.B = B | 0;
+ this.C = C | 0;
+ this.D = D | 0;
+ this.E = E | 0;
+ this.F = F | 0;
+ this.G = G | 0;
+ this.H = H | 0;
+ }
+ process(view, offset) {
+ for (let i2 = 0; i2 < 16; i2++, offset += 4)
+ SHA256_W[i2] = view.getUint32(offset, false);
+ for (let i2 = 16; i2 < 64; i2++) {
+ const W15 = SHA256_W[i2 - 15];
+ const W2 = SHA256_W[i2 - 2];
+ const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
+ const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
+ SHA256_W[i2] = s1 + SHA256_W[i2 - 7] + s0 + SHA256_W[i2 - 16] | 0;
+ }
+ let { A, B, C, D, E, F, G, H } = this;
+ for (let i2 = 0; i2 < 64; i2++) {
+ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
+ const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i2] + SHA256_W[i2] | 0;
+ const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
+ const T2 = sigma0 + Maj(A, B, C) | 0;
+ H = G;
+ G = F;
+ F = E;
+ E = D + T1 | 0;
+ D = C;
+ C = B;
+ B = A;
+ A = T1 + T2 | 0;
+ }
+ A = A + this.A | 0;
+ B = B + this.B | 0;
+ C = C + this.C | 0;
+ D = D + this.D | 0;
+ E = E + this.E | 0;
+ F = F + this.F | 0;
+ G = G + this.G | 0;
+ H = H + this.H | 0;
+ this.set(A, B, C, D, E, F, G, H);
+ }
+ roundClean() {
+ SHA256_W.fill(0);
+ }
+ destroy() {
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
+ this.buffer.fill(0);
+ }
+ };
+ var sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
+
+ // node_modules/@noble/curves/esm/abstract/utils.js
+ var utils_exports = {};
+ __export(utils_exports, {
+ bitGet: () => bitGet,
+ bitLen: () => bitLen,
+ bitMask: () => bitMask,
+ bitSet: () => bitSet,
+ bytesToHex: () => bytesToHex,
+ bytesToNumberBE: () => bytesToNumberBE,
+ bytesToNumberLE: () => bytesToNumberLE,
+ concatBytes: () => concatBytes2,
+ createHmacDrbg: () => createHmacDrbg,
+ ensureBytes: () => ensureBytes,
+ equalBytes: () => equalBytes,
+ hexToBytes: () => hexToBytes,
+ hexToNumber: () => hexToNumber,
+ numberToBytesBE: () => numberToBytesBE,
+ numberToBytesLE: () => numberToBytesLE,
+ numberToHexUnpadded: () => numberToHexUnpadded,
+ numberToVarBytesBE: () => numberToVarBytesBE,
+ utf8ToBytes: () => utf8ToBytes2,
+ validateObject: () => validateObject
+ });
+ var _0n = BigInt(0);
+ var _1n = BigInt(1);
+ var _2n = BigInt(2);
+ var u8a2 = (a) => a instanceof Uint8Array;
+ var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i2) => i2.toString(16).padStart(2, "0"));
+ function bytesToHex(bytes4) {
+ if (!u8a2(bytes4))
+ throw new Error("Uint8Array expected");
+ let hex2 = "";
+ for (let i2 = 0; i2 < bytes4.length; i2++) {
+ hex2 += hexes[bytes4[i2]];
+ }
+ return hex2;
+ }
+ function numberToHexUnpadded(num) {
+ const hex2 = num.toString(16);
+ return hex2.length & 1 ? `0${hex2}` : hex2;
+ }
+ function hexToNumber(hex2) {
+ if (typeof hex2 !== "string")
+ throw new Error("hex string expected, got " + typeof hex2);
+ return BigInt(hex2 === "" ? "0" : `0x${hex2}`);
+ }
+ function hexToBytes(hex2) {
+ if (typeof hex2 !== "string")
+ throw new Error("hex string expected, got " + typeof hex2);
+ const len = hex2.length;
+ if (len % 2)
+ throw new Error("padded hex string expected, got unpadded hex of length " + len);
+ const array = new Uint8Array(len / 2);
+ for (let i2 = 0; i2 < array.length; i2++) {
+ const j = i2 * 2;
+ const hexByte = hex2.slice(j, j + 2);
+ const byte = Number.parseInt(hexByte, 16);
+ if (Number.isNaN(byte) || byte < 0)
+ throw new Error("Invalid byte sequence");
+ array[i2] = byte;
+ }
+ return array;
+ }
+ function bytesToNumberBE(bytes4) {
+ return hexToNumber(bytesToHex(bytes4));
+ }
+ function bytesToNumberLE(bytes4) {
+ if (!u8a2(bytes4))
+ throw new Error("Uint8Array expected");
+ return hexToNumber(bytesToHex(Uint8Array.from(bytes4).reverse()));
+ }
+ function numberToBytesBE(n, len) {
+ return hexToBytes(n.toString(16).padStart(len * 2, "0"));
+ }
+ function numberToBytesLE(n, len) {
+ return numberToBytesBE(n, len).reverse();
+ }
+ function numberToVarBytesBE(n) {
+ return hexToBytes(numberToHexUnpadded(n));
+ }
+ function ensureBytes(title, hex2, expectedLength) {
+ let res;
+ if (typeof hex2 === "string") {
+ try {
+ res = hexToBytes(hex2);
+ } catch (e) {
+ throw new Error(`${title} must be valid hex string, got "${hex2}". Cause: ${e}`);
+ }
+ } else if (u8a2(hex2)) {
+ res = Uint8Array.from(hex2);
+ } else {
+ throw new Error(`${title} must be hex string or Uint8Array`);
+ }
+ const len = res.length;
+ if (typeof expectedLength === "number" && len !== expectedLength)
+ throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`);
+ return res;
+ }
+ function concatBytes2(...arrays) {
+ const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
+ let pad2 = 0;
+ arrays.forEach((a) => {
+ if (!u8a2(a))
+ throw new Error("Uint8Array expected");
+ r.set(a, pad2);
+ pad2 += a.length;
+ });
+ return r;
+ }
+ function equalBytes(b1, b2) {
+ if (b1.length !== b2.length)
+ return false;
+ for (let i2 = 0; i2 < b1.length; i2++)
+ if (b1[i2] !== b2[i2])
+ return false;
+ return true;
+ }
+ function utf8ToBytes2(str) {
+ if (typeof str !== "string")
+ throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
+ return new Uint8Array(new TextEncoder().encode(str));
+ }
+ function bitLen(n) {
+ let len;
+ for (len = 0; n > _0n; n >>= _1n, len += 1)
+ ;
+ return len;
+ }
+ function bitGet(n, pos) {
+ return n >> BigInt(pos) & _1n;
+ }
+ var bitSet = (n, pos, value) => {
+ return n | (value ? _1n : _0n) << BigInt(pos);
+ };
+ var bitMask = (n) => (_2n << BigInt(n - 1)) - _1n;
+ var u8n = (data) => new Uint8Array(data);
+ var u8fr = (arr) => Uint8Array.from(arr);
+ function createHmacDrbg(hashLen, qByteLen, hmacFn) {
+ if (typeof hashLen !== "number" || hashLen < 2)
+ throw new Error("hashLen must be a number");
+ if (typeof qByteLen !== "number" || qByteLen < 2)
+ throw new Error("qByteLen must be a number");
+ if (typeof hmacFn !== "function")
+ throw new Error("hmacFn must be a function");
+ let v = u8n(hashLen);
+ let k = u8n(hashLen);
+ let i2 = 0;
+ const reset = () => {
+ v.fill(1);
+ k.fill(0);
+ i2 = 0;
+ };
+ const h = (...b) => hmacFn(k, v, ...b);
+ const reseed = (seed = u8n()) => {
+ k = h(u8fr([0]), seed);
+ v = h();
+ if (seed.length === 0)
+ return;
+ k = h(u8fr([1]), seed);
+ v = h();
+ };
+ const gen = () => {
+ if (i2++ >= 1e3)
+ throw new Error("drbg: tried 1000 values");
+ let len = 0;
+ const out = [];
+ while (len < qByteLen) {
+ v = h();
+ const sl = v.slice();
+ out.push(sl);
+ len += v.length;
+ }
+ return concatBytes2(...out);
+ };
+ const genUntil = (seed, pred) => {
+ reset();
+ reseed(seed);
+ let res = void 0;
+ while (!(res = pred(gen())))
+ reseed();
+ reset();
+ return res;
+ };
+ return genUntil;
+ }
+ var validatorFns = {
+ bigint: (val) => typeof val === "bigint",
+ function: (val) => typeof val === "function",
+ boolean: (val) => typeof val === "boolean",
+ string: (val) => typeof val === "string",
+ stringOrUint8Array: (val) => typeof val === "string" || val instanceof Uint8Array,
+ isSafeInteger: (val) => Number.isSafeInteger(val),
+ array: (val) => Array.isArray(val),
+ field: (val, object) => object.Fp.isValid(val),
+ hash: (val) => typeof val === "function" && Number.isSafeInteger(val.outputLen)
+ };
+ function validateObject(object, validators, optValidators = {}) {
+ const checkField = (fieldName, type, isOptional) => {
+ const checkVal = validatorFns[type];
+ if (typeof checkVal !== "function")
+ throw new Error(`Invalid validator "${type}", expected function`);
+ const val = object[fieldName];
+ if (isOptional && val === void 0)
+ return;
+ if (!checkVal(val, object)) {
+ throw new Error(`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`);
+ }
+ };
+ for (const [fieldName, type] of Object.entries(validators))
+ checkField(fieldName, type, false);
+ for (const [fieldName, type] of Object.entries(optValidators))
+ checkField(fieldName, type, true);
+ return object;
+ }
+
+ // node_modules/@noble/curves/esm/abstract/modular.js
+ var _0n2 = BigInt(0);
+ var _1n2 = BigInt(1);
+ var _2n2 = BigInt(2);
+ var _3n = BigInt(3);
+ var _4n = BigInt(4);
+ var _5n = BigInt(5);
+ var _8n = BigInt(8);
+ var _9n = BigInt(9);
+ var _16n = BigInt(16);
+ function mod(a, b) {
+ const result = a % b;
+ return result >= _0n2 ? result : b + result;
+ }
+ function pow(num, power, modulo) {
+ if (modulo <= _0n2 || power < _0n2)
+ throw new Error("Expected power/modulo > 0");
+ if (modulo === _1n2)
+ return _0n2;
+ let res = _1n2;
+ while (power > _0n2) {
+ if (power & _1n2)
+ res = res * num % modulo;
+ num = num * num % modulo;
+ power >>= _1n2;
+ }
+ return res;
+ }
+ function pow2(x, power, modulo) {
+ let res = x;
+ while (power-- > _0n2) {
+ res *= res;
+ res %= modulo;
+ }
+ return res;
+ }
+ function invert(number4, modulo) {
+ if (number4 === _0n2 || modulo <= _0n2) {
+ throw new Error(`invert: expected positive integers, got n=${number4} mod=${modulo}`);
+ }
+ let a = mod(number4, modulo);
+ let b = modulo;
+ let x = _0n2, y = _1n2, u = _1n2, v = _0n2;
+ while (a !== _0n2) {
+ const q = b / a;
+ const r = b % a;
+ const m = x - u * q;
+ const n = y - v * q;
+ b = a, a = r, x = u, y = v, u = m, v = n;
+ }
+ const gcd2 = b;
+ if (gcd2 !== _1n2)
+ throw new Error("invert: does not exist");
+ return mod(x, modulo);
+ }
+ function tonelliShanks(P) {
+ const legendreC = (P - _1n2) / _2n2;
+ let Q, S, Z;
+ for (Q = P - _1n2, S = 0; Q % _2n2 === _0n2; Q /= _2n2, S++)
+ ;
+ for (Z = _2n2; Z < P && pow(Z, legendreC, P) !== P - _1n2; Z++)
+ ;
+ if (S === 1) {
+ const p1div4 = (P + _1n2) / _4n;
+ return function tonelliFast(Fp2, n) {
+ const root = Fp2.pow(n, p1div4);
+ if (!Fp2.eql(Fp2.sqr(root), n))
+ throw new Error("Cannot find square root");
+ return root;
+ };
+ }
+ const Q1div2 = (Q + _1n2) / _2n2;
+ return function tonelliSlow(Fp2, n) {
+ if (Fp2.pow(n, legendreC) === Fp2.neg(Fp2.ONE))
+ throw new Error("Cannot find square root");
+ let r = S;
+ let g = Fp2.pow(Fp2.mul(Fp2.ONE, Z), Q);
+ let x = Fp2.pow(n, Q1div2);
+ let b = Fp2.pow(n, Q);
+ while (!Fp2.eql(b, Fp2.ONE)) {
+ if (Fp2.eql(b, Fp2.ZERO))
+ return Fp2.ZERO;
+ let m = 1;
+ for (let t2 = Fp2.sqr(b); m < r; m++) {
+ if (Fp2.eql(t2, Fp2.ONE))
+ break;
+ t2 = Fp2.sqr(t2);
+ }
+ const ge2 = Fp2.pow(g, _1n2 << BigInt(r - m - 1));
+ g = Fp2.sqr(ge2);
+ x = Fp2.mul(x, ge2);
+ b = Fp2.mul(b, g);
+ r = m;
+ }
+ return x;
+ };
+ }
+ function FpSqrt(P) {
+ if (P % _4n === _3n) {
+ const p1div4 = (P + _1n2) / _4n;
+ return function sqrt3mod4(Fp2, n) {
+ const root = Fp2.pow(n, p1div4);
+ if (!Fp2.eql(Fp2.sqr(root), n))
+ throw new Error("Cannot find square root");
+ return root;
+ };
+ }
+ if (P % _8n === _5n) {
+ const c1 = (P - _5n) / _8n;
+ return function sqrt5mod8(Fp2, n) {
+ const n2 = Fp2.mul(n, _2n2);
+ const v = Fp2.pow(n2, c1);
+ const nv = Fp2.mul(n, v);
+ const i2 = Fp2.mul(Fp2.mul(nv, _2n2), v);
+ const root = Fp2.mul(nv, Fp2.sub(i2, Fp2.ONE));
+ if (!Fp2.eql(Fp2.sqr(root), n))
+ throw new Error("Cannot find square root");
+ return root;
+ };
+ }
+ if (P % _16n === _9n) {
+ }
+ return tonelliShanks(P);
+ }
+ var FIELD_FIELDS = [
+ "create",
+ "isValid",
+ "is0",
+ "neg",
+ "inv",
+ "sqrt",
+ "sqr",
+ "eql",
+ "add",
+ "sub",
+ "mul",
+ "pow",
+ "div",
+ "addN",
+ "subN",
+ "mulN",
+ "sqrN"
+ ];
+ function validateField(field) {
+ const initial = {
+ ORDER: "bigint",
+ MASK: "bigint",
+ BYTES: "isSafeInteger",
+ BITS: "isSafeInteger"
+ };
+ const opts = FIELD_FIELDS.reduce((map, val) => {
+ map[val] = "function";
+ return map;
+ }, initial);
+ return validateObject(field, opts);
+ }
+ function FpPow(f, num, power) {
+ if (power < _0n2)
+ throw new Error("Expected power > 0");
+ if (power === _0n2)
+ return f.ONE;
+ if (power === _1n2)
+ return num;
+ let p = f.ONE;
+ let d = num;
+ while (power > _0n2) {
+ if (power & _1n2)
+ p = f.mul(p, d);
+ d = f.sqr(d);
+ power >>= _1n2;
+ }
+ return p;
+ }
+ function FpInvertBatch(f, nums) {
+ const tmp = new Array(nums.length);
+ const lastMultiplied = nums.reduce((acc, num, i2) => {
+ if (f.is0(num))
+ return acc;
+ tmp[i2] = acc;
+ return f.mul(acc, num);
+ }, f.ONE);
+ const inverted = f.inv(lastMultiplied);
+ nums.reduceRight((acc, num, i2) => {
+ if (f.is0(num))
+ return acc;
+ tmp[i2] = f.mul(acc, tmp[i2]);
+ return f.mul(acc, num);
+ }, inverted);
+ return tmp;
+ }
+ function nLength(n, nBitLength) {
+ const _nBitLength = nBitLength !== void 0 ? nBitLength : n.toString(2).length;
+ const nByteLength = Math.ceil(_nBitLength / 8);
+ return { nBitLength: _nBitLength, nByteLength };
+ }
+ function Field(ORDER, bitLen2, isLE4 = false, redef = {}) {
+ if (ORDER <= _0n2)
+ throw new Error(`Expected Field ORDER > 0, got ${ORDER}`);
+ const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen2);
+ if (BYTES > 2048)
+ throw new Error("Field lengths over 2048 bytes are not supported");
+ const sqrtP = FpSqrt(ORDER);
+ const f = Object.freeze({
+ ORDER,
+ BITS,
+ BYTES,
+ MASK: bitMask(BITS),
+ ZERO: _0n2,
+ ONE: _1n2,
+ create: (num) => mod(num, ORDER),
+ isValid: (num) => {
+ if (typeof num !== "bigint")
+ throw new Error(`Invalid field element: expected bigint, got ${typeof num}`);
+ return _0n2 <= num && num < ORDER;
+ },
+ is0: (num) => num === _0n2,
+ isOdd: (num) => (num & _1n2) === _1n2,
+ neg: (num) => mod(-num, ORDER),
+ eql: (lhs, rhs) => lhs === rhs,
+ sqr: (num) => mod(num * num, ORDER),
+ add: (lhs, rhs) => mod(lhs + rhs, ORDER),
+ sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
+ mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
+ pow: (num, power) => FpPow(f, num, power),
+ div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
+ sqrN: (num) => num * num,
+ addN: (lhs, rhs) => lhs + rhs,
+ subN: (lhs, rhs) => lhs - rhs,
+ mulN: (lhs, rhs) => lhs * rhs,
+ inv: (num) => invert(num, ORDER),
+ sqrt: redef.sqrt || ((n) => sqrtP(f, n)),
+ invertBatch: (lst) => FpInvertBatch(f, lst),
+ cmov: (a, b, c) => c ? b : a,
+ toBytes: (num) => isLE4 ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES),
+ fromBytes: (bytes4) => {
+ if (bytes4.length !== BYTES)
+ throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes4.length}`);
+ return isLE4 ? bytesToNumberLE(bytes4) : bytesToNumberBE(bytes4);
+ }
+ });
+ return Object.freeze(f);
+ }
+ function getFieldBytesLength(fieldOrder) {
+ if (typeof fieldOrder !== "bigint")
+ throw new Error("field order must be bigint");
+ const bitLength = fieldOrder.toString(2).length;
+ return Math.ceil(bitLength / 8);
+ }
+ function getMinHashLength(fieldOrder) {
+ const length = getFieldBytesLength(fieldOrder);
+ return length + Math.ceil(length / 2);
+ }
+ function mapHashToField(key, fieldOrder, isLE4 = false) {
+ const len = key.length;
+ const fieldLen = getFieldBytesLength(fieldOrder);
+ const minLen = getMinHashLength(fieldOrder);
+ if (len < 16 || len < minLen || len > 1024)
+ throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`);
+ const num = isLE4 ? bytesToNumberBE(key) : bytesToNumberLE(key);
+ const reduced = mod(num, fieldOrder - _1n2) + _1n2;
+ return isLE4 ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
+ }
+
+ // node_modules/@noble/curves/esm/abstract/curve.js
+ var _0n3 = BigInt(0);
+ var _1n3 = BigInt(1);
+ function wNAF(c, bits) {
+ const constTimeNegate = (condition, item) => {
+ const neg = item.negate();
+ return condition ? neg : item;
+ };
+ const opts = (W) => {
+ const windows = Math.ceil(bits / W) + 1;
+ const windowSize = 2 ** (W - 1);
+ return { windows, windowSize };
+ };
+ return {
+ constTimeNegate,
+ unsafeLadder(elm, n) {
+ let p = c.ZERO;
+ let d = elm;
+ while (n > _0n3) {
+ if (n & _1n3)
+ p = p.add(d);
+ d = d.double();
+ n >>= _1n3;
+ }
+ return p;
+ },
+ precomputeWindow(elm, W) {
+ const { windows, windowSize } = opts(W);
+ const points = [];
+ let p = elm;
+ let base = p;
+ for (let window = 0; window < windows; window++) {
+ base = p;
+ points.push(base);
+ for (let i2 = 1; i2 < windowSize; i2++) {
+ base = base.add(p);
+ points.push(base);
+ }
+ p = base.double();
+ }
+ return points;
+ },
+ wNAF(W, precomputes, n) {
+ const { windows, windowSize } = opts(W);
+ let p = c.ZERO;
+ let f = c.BASE;
+ const mask = BigInt(2 ** W - 1);
+ const maxNumber = 2 ** W;
+ const shiftBy = BigInt(W);
+ for (let window = 0; window < windows; window++) {
+ const offset = window * windowSize;
+ let wbits = Number(n & mask);
+ n >>= shiftBy;
+ if (wbits > windowSize) {
+ wbits -= maxNumber;
+ n += _1n3;
+ }
+ const offset1 = offset;
+ const offset2 = offset + Math.abs(wbits) - 1;
+ const cond1 = window % 2 !== 0;
+ const cond2 = wbits < 0;
+ if (wbits === 0) {
+ f = f.add(constTimeNegate(cond1, precomputes[offset1]));
+ } else {
+ p = p.add(constTimeNegate(cond2, precomputes[offset2]));
+ }
+ }
+ return { p, f };
+ },
+ wNAFCached(P, precomputesMap, n, transform) {
+ const W = P._WINDOW_SIZE || 1;
+ let comp = precomputesMap.get(P);
+ if (!comp) {
+ comp = this.precomputeWindow(P, W);
+ if (W !== 1) {
+ precomputesMap.set(P, transform(comp));
+ }
+ }
+ return this.wNAF(W, comp, n);
+ }
+ };
+ }
+ function validateBasic(curve) {
+ validateField(curve.Fp);
+ validateObject(curve, {
+ n: "bigint",
+ h: "bigint",
+ Gx: "field",
+ Gy: "field"
+ }, {
+ nBitLength: "isSafeInteger",
+ nByteLength: "isSafeInteger"
+ });
+ return Object.freeze({
+ ...nLength(curve.n, curve.nBitLength),
+ ...curve,
+ ...{ p: curve.Fp.ORDER }
+ });
+ }
+
+ // node_modules/@noble/curves/esm/abstract/weierstrass.js
+ function validatePointOpts(curve) {
+ const opts = validateBasic(curve);
+ validateObject(opts, {
+ a: "field",
+ b: "field"
+ }, {
+ allowedPrivateKeyLengths: "array",
+ wrapPrivateKey: "boolean",
+ isTorsionFree: "function",
+ clearCofactor: "function",
+ allowInfinityPoint: "boolean",
+ fromBytes: "function",
+ toBytes: "function"
+ });
+ const { endo, Fp: Fp2, a } = opts;
+ if (endo) {
+ if (!Fp2.eql(a, Fp2.ZERO)) {
+ throw new Error("Endomorphism can only be defined for Koblitz curves that have a=0");
+ }
+ if (typeof endo !== "object" || typeof endo.beta !== "bigint" || typeof endo.splitScalar !== "function") {
+ throw new Error("Expected endomorphism with beta: bigint and splitScalar: function");
+ }
+ }
+ return Object.freeze({ ...opts });
+ }
+ var { bytesToNumberBE: b2n, hexToBytes: h2b } = utils_exports;
+ var DER = {
+ Err: class DERErr extends Error {
+ constructor(m = "") {
+ super(m);
+ }
+ },
+ _parseInt(data) {
+ const { Err: E } = DER;
+ if (data.length < 2 || data[0] !== 2)
+ throw new E("Invalid signature integer tag");
+ const len = data[1];
+ const res = data.subarray(2, len + 2);
+ if (!len || res.length !== len)
+ throw new E("Invalid signature integer: wrong length");
+ if (res[0] & 128)
+ throw new E("Invalid signature integer: negative");
+ if (res[0] === 0 && !(res[1] & 128))
+ throw new E("Invalid signature integer: unnecessary leading zero");
+ return { d: b2n(res), l: data.subarray(len + 2) };
+ },
+ toSig(hex2) {
+ const { Err: E } = DER;
+ const data = typeof hex2 === "string" ? h2b(hex2) : hex2;
+ if (!(data instanceof Uint8Array))
+ throw new Error("ui8a expected");
+ let l = data.length;
+ if (l < 2 || data[0] != 48)
+ throw new E("Invalid signature tag");
+ if (data[1] !== l - 2)
+ throw new E("Invalid signature: incorrect length");
+ const { d: r, l: sBytes } = DER._parseInt(data.subarray(2));
+ const { d: s, l: rBytesLeft } = DER._parseInt(sBytes);
+ if (rBytesLeft.length)
+ throw new E("Invalid signature: left bytes after parsing");
+ return { r, s };
+ },
+ hexFromSig(sig) {
+ const slice = (s2) => Number.parseInt(s2[0], 16) & 8 ? "00" + s2 : s2;
+ const h = (num) => {
+ const hex2 = num.toString(16);
+ return hex2.length & 1 ? `0${hex2}` : hex2;
+ };
+ const s = slice(h(sig.s));
+ const r = slice(h(sig.r));
+ const shl = s.length / 2;
+ const rhl = r.length / 2;
+ const sl = h(shl);
+ const rl = h(rhl);
+ return `30${h(rhl + shl + 4)}02${rl}${r}02${sl}${s}`;
+ }
+ };
+ var _0n4 = BigInt(0);
+ var _1n4 = BigInt(1);
+ var _2n3 = BigInt(2);
+ var _3n2 = BigInt(3);
+ var _4n2 = BigInt(4);
+ function weierstrassPoints(opts) {
+ const CURVE = validatePointOpts(opts);
+ const { Fp: Fp2 } = CURVE;
+ const toBytes4 = CURVE.toBytes || ((_c, point, _isCompressed) => {
+ const a = point.toAffine();
+ return concatBytes2(Uint8Array.from([4]), Fp2.toBytes(a.x), Fp2.toBytes(a.y));
+ });
+ const fromBytes = CURVE.fromBytes || ((bytes4) => {
+ const tail = bytes4.subarray(1);
+ const x = Fp2.fromBytes(tail.subarray(0, Fp2.BYTES));
+ const y = Fp2.fromBytes(tail.subarray(Fp2.BYTES, 2 * Fp2.BYTES));
+ return { x, y };
+ });
+ function weierstrassEquation(x) {
+ const { a, b } = CURVE;
+ const x2 = Fp2.sqr(x);
+ const x3 = Fp2.mul(x2, x);
+ return Fp2.add(Fp2.add(x3, Fp2.mul(x, a)), b);
+ }
+ if (!Fp2.eql(Fp2.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx)))
+ throw new Error("bad generator point: equation left != right");
+ function isWithinCurveOrder(num) {
+ return typeof num === "bigint" && _0n4 < num && num < CURVE.n;
+ }
+ function assertGE(num) {
+ if (!isWithinCurveOrder(num))
+ throw new Error("Expected valid bigint: 0 < bigint < curve.n");
+ }
+ function normPrivateKeyToScalar(key) {
+ const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n } = CURVE;
+ if (lengths && typeof key !== "bigint") {
+ if (key instanceof Uint8Array)
+ key = bytesToHex(key);
+ if (typeof key !== "string" || !lengths.includes(key.length))
+ throw new Error("Invalid key");
+ key = key.padStart(nByteLength * 2, "0");
+ }
+ let num;
+ try {
+ num = typeof key === "bigint" ? key : bytesToNumberBE(ensureBytes("private key", key, nByteLength));
+ } catch (error) {
+ throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`);
+ }
+ if (wrapPrivateKey)
+ num = mod(num, n);
+ assertGE(num);
+ return num;
+ }
+ const pointPrecomputes = /* @__PURE__ */ new Map();
+ function assertPrjPoint(other) {
+ if (!(other instanceof Point2))
+ throw new Error("ProjectivePoint expected");
+ }
+ class Point2 {
+ constructor(px, py, pz) {
+ this.px = px;
+ this.py = py;
+ this.pz = pz;
+ if (px == null || !Fp2.isValid(px))
+ throw new Error("x required");
+ if (py == null || !Fp2.isValid(py))
+ throw new Error("y required");
+ if (pz == null || !Fp2.isValid(pz))
+ throw new Error("z required");
+ }
+ static fromAffine(p) {
+ const { x, y } = p || {};
+ if (!p || !Fp2.isValid(x) || !Fp2.isValid(y))
+ throw new Error("invalid affine point");
+ if (p instanceof Point2)
+ throw new Error("projective point not allowed");
+ const is0 = (i2) => Fp2.eql(i2, Fp2.ZERO);
+ if (is0(x) && is0(y))
+ return Point2.ZERO;
+ return new Point2(x, y, Fp2.ONE);
+ }
+ get x() {
+ return this.toAffine().x;
+ }
+ get y() {
+ return this.toAffine().y;
+ }
+ static normalizeZ(points) {
+ const toInv = Fp2.invertBatch(points.map((p) => p.pz));
+ return points.map((p, i2) => p.toAffine(toInv[i2])).map(Point2.fromAffine);
+ }
+ static fromHex(hex2) {
+ const P = Point2.fromAffine(fromBytes(ensureBytes("pointHex", hex2)));
+ P.assertValidity();
+ return P;
+ }
+ static fromPrivateKey(privateKey) {
+ return Point2.BASE.multiply(normPrivateKeyToScalar(privateKey));
+ }
+ _setWindowSize(windowSize) {
+ this._WINDOW_SIZE = windowSize;
+ pointPrecomputes.delete(this);
+ }
+ assertValidity() {
+ if (this.is0()) {
+ if (CURVE.allowInfinityPoint && !Fp2.is0(this.py))
+ return;
+ throw new Error("bad point: ZERO");
+ }
+ const { x, y } = this.toAffine();
+ if (!Fp2.isValid(x) || !Fp2.isValid(y))
+ throw new Error("bad point: x or y not FE");
+ const left = Fp2.sqr(y);
+ const right = weierstrassEquation(x);
+ if (!Fp2.eql(left, right))
+ throw new Error("bad point: equation left != right");
+ if (!this.isTorsionFree())
+ throw new Error("bad point: not in prime-order subgroup");
+ }
+ hasEvenY() {
+ const { y } = this.toAffine();
+ if (Fp2.isOdd)
+ return !Fp2.isOdd(y);
+ throw new Error("Field doesn't support isOdd");
+ }
+ equals(other) {
+ assertPrjPoint(other);
+ const { px: X1, py: Y1, pz: Z1 } = this;
+ const { px: X2, py: Y2, pz: Z2 } = other;
+ const U1 = Fp2.eql(Fp2.mul(X1, Z2), Fp2.mul(X2, Z1));
+ const U2 = Fp2.eql(Fp2.mul(Y1, Z2), Fp2.mul(Y2, Z1));
+ return U1 && U2;
+ }
+ negate() {
+ return new Point2(this.px, Fp2.neg(this.py), this.pz);
+ }
+ double() {
+ const { a, b } = CURVE;
+ const b3 = Fp2.mul(b, _3n2);
+ const { px: X1, py: Y1, pz: Z1 } = this;
+ let X3 = Fp2.ZERO, Y3 = Fp2.ZERO, Z3 = Fp2.ZERO;
+ let t0 = Fp2.mul(X1, X1);
+ let t1 = Fp2.mul(Y1, Y1);
+ let t2 = Fp2.mul(Z1, Z1);
+ let t3 = Fp2.mul(X1, Y1);
+ t3 = Fp2.add(t3, t3);
+ Z3 = Fp2.mul(X1, Z1);
+ Z3 = Fp2.add(Z3, Z3);
+ X3 = Fp2.mul(a, Z3);
+ Y3 = Fp2.mul(b3, t2);
+ Y3 = Fp2.add(X3, Y3);
+ X3 = Fp2.sub(t1, Y3);
+ Y3 = Fp2.add(t1, Y3);
+ Y3 = Fp2.mul(X3, Y3);
+ X3 = Fp2.mul(t3, X3);
+ Z3 = Fp2.mul(b3, Z3);
+ t2 = Fp2.mul(a, t2);
+ t3 = Fp2.sub(t0, t2);
+ t3 = Fp2.mul(a, t3);
+ t3 = Fp2.add(t3, Z3);
+ Z3 = Fp2.add(t0, t0);
+ t0 = Fp2.add(Z3, t0);
+ t0 = Fp2.add(t0, t2);
+ t0 = Fp2.mul(t0, t3);
+ Y3 = Fp2.add(Y3, t0);
+ t2 = Fp2.mul(Y1, Z1);
+ t2 = Fp2.add(t2, t2);
+ t0 = Fp2.mul(t2, t3);
+ X3 = Fp2.sub(X3, t0);
+ Z3 = Fp2.mul(t2, t1);
+ Z3 = Fp2.add(Z3, Z3);
+ Z3 = Fp2.add(Z3, Z3);
+ return new Point2(X3, Y3, Z3);
+ }
+ add(other) {
+ assertPrjPoint(other);
+ const { px: X1, py: Y1, pz: Z1 } = this;
+ const { px: X2, py: Y2, pz: Z2 } = other;
+ let X3 = Fp2.ZERO, Y3 = Fp2.ZERO, Z3 = Fp2.ZERO;
+ const a = CURVE.a;
+ const b3 = Fp2.mul(CURVE.b, _3n2);
+ let t0 = Fp2.mul(X1, X2);
+ let t1 = Fp2.mul(Y1, Y2);
+ let t2 = Fp2.mul(Z1, Z2);
+ let t3 = Fp2.add(X1, Y1);
+ let t4 = Fp2.add(X2, Y2);
+ t3 = Fp2.mul(t3, t4);
+ t4 = Fp2.add(t0, t1);
+ t3 = Fp2.sub(t3, t4);
+ t4 = Fp2.add(X1, Z1);
+ let t5 = Fp2.add(X2, Z2);
+ t4 = Fp2.mul(t4, t5);
+ t5 = Fp2.add(t0, t2);
+ t4 = Fp2.sub(t4, t5);
+ t5 = Fp2.add(Y1, Z1);
+ X3 = Fp2.add(Y2, Z2);
+ t5 = Fp2.mul(t5, X3);
+ X3 = Fp2.add(t1, t2);
+ t5 = Fp2.sub(t5, X3);
+ Z3 = Fp2.mul(a, t4);
+ X3 = Fp2.mul(b3, t2);
+ Z3 = Fp2.add(X3, Z3);
+ X3 = Fp2.sub(t1, Z3);
+ Z3 = Fp2.add(t1, Z3);
+ Y3 = Fp2.mul(X3, Z3);
+ t1 = Fp2.add(t0, t0);
+ t1 = Fp2.add(t1, t0);
+ t2 = Fp2.mul(a, t2);
+ t4 = Fp2.mul(b3, t4);
+ t1 = Fp2.add(t1, t2);
+ t2 = Fp2.sub(t0, t2);
+ t2 = Fp2.mul(a, t2);
+ t4 = Fp2.add(t4, t2);
+ t0 = Fp2.mul(t1, t4);
+ Y3 = Fp2.add(Y3, t0);
+ t0 = Fp2.mul(t5, t4);
+ X3 = Fp2.mul(t3, X3);
+ X3 = Fp2.sub(X3, t0);
+ t0 = Fp2.mul(t3, t1);
+ Z3 = Fp2.mul(t5, Z3);
+ Z3 = Fp2.add(Z3, t0);
+ return new Point2(X3, Y3, Z3);
+ }
+ subtract(other) {
+ return this.add(other.negate());
+ }
+ is0() {
+ return this.equals(Point2.ZERO);
+ }
+ wNAF(n) {
+ return wnaf.wNAFCached(this, pointPrecomputes, n, (comp) => {
+ const toInv = Fp2.invertBatch(comp.map((p) => p.pz));
+ return comp.map((p, i2) => p.toAffine(toInv[i2])).map(Point2.fromAffine);
+ });
+ }
+ multiplyUnsafe(n) {
+ const I = Point2.ZERO;
+ if (n === _0n4)
+ return I;
+ assertGE(n);
+ if (n === _1n4)
+ return this;
+ const { endo } = CURVE;
+ if (!endo)
+ return wnaf.unsafeLadder(this, n);
+ let { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
+ let k1p = I;
+ let k2p = I;
+ let d = this;
+ while (k1 > _0n4 || k2 > _0n4) {
+ if (k1 & _1n4)
+ k1p = k1p.add(d);
+ if (k2 & _1n4)
+ k2p = k2p.add(d);
+ d = d.double();
+ k1 >>= _1n4;
+ k2 >>= _1n4;
+ }
+ if (k1neg)
+ k1p = k1p.negate();
+ if (k2neg)
+ k2p = k2p.negate();
+ k2p = new Point2(Fp2.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
+ return k1p.add(k2p);
+ }
+ multiply(scalar) {
+ assertGE(scalar);
+ let n = scalar;
+ let point, fake;
+ const { endo } = CURVE;
+ if (endo) {
+ const { k1neg, k1, k2neg, k2 } = endo.splitScalar(n);
+ let { p: k1p, f: f1p } = this.wNAF(k1);
+ let { p: k2p, f: f2p } = this.wNAF(k2);
+ k1p = wnaf.constTimeNegate(k1neg, k1p);
+ k2p = wnaf.constTimeNegate(k2neg, k2p);
+ k2p = new Point2(Fp2.mul(k2p.px, endo.beta), k2p.py, k2p.pz);
+ point = k1p.add(k2p);
+ fake = f1p.add(f2p);
+ } else {
+ const { p, f } = this.wNAF(n);
+ point = p;
+ fake = f;
+ }
+ return Point2.normalizeZ([point, fake])[0];
+ }
+ multiplyAndAddUnsafe(Q, a, b) {
+ const G = Point2.BASE;
+ const mul3 = (P, a2) => a2 === _0n4 || a2 === _1n4 || !P.equals(G) ? P.multiplyUnsafe(a2) : P.multiply(a2);
+ const sum = mul3(this, a).add(mul3(Q, b));
+ return sum.is0() ? void 0 : sum;
+ }
+ toAffine(iz) {
+ const { px: x, py: y, pz: z } = this;
+ const is0 = this.is0();
+ if (iz == null)
+ iz = is0 ? Fp2.ONE : Fp2.inv(z);
+ const ax = Fp2.mul(x, iz);
+ const ay = Fp2.mul(y, iz);
+ const zz = Fp2.mul(z, iz);
+ if (is0)
+ return { x: Fp2.ZERO, y: Fp2.ZERO };
+ if (!Fp2.eql(zz, Fp2.ONE))
+ throw new Error("invZ was invalid");
+ return { x: ax, y: ay };
+ }
+ isTorsionFree() {
+ const { h: cofactor, isTorsionFree } = CURVE;
+ if (cofactor === _1n4)
+ return true;
+ if (isTorsionFree)
+ return isTorsionFree(Point2, this);
+ throw new Error("isTorsionFree() has not been declared for the elliptic curve");
+ }
+ clearCofactor() {
+ const { h: cofactor, clearCofactor } = CURVE;
+ if (cofactor === _1n4)
+ return this;
+ if (clearCofactor)
+ return clearCofactor(Point2, this);
+ return this.multiplyUnsafe(CURVE.h);
+ }
+ toRawBytes(isCompressed = true) {
+ this.assertValidity();
+ return toBytes4(Point2, this, isCompressed);
+ }
+ toHex(isCompressed = true) {
+ return bytesToHex(this.toRawBytes(isCompressed));
+ }
+ }
+ Point2.BASE = new Point2(CURVE.Gx, CURVE.Gy, Fp2.ONE);
+ Point2.ZERO = new Point2(Fp2.ZERO, Fp2.ONE, Fp2.ZERO);
+ const _bits = CURVE.nBitLength;
+ const wnaf = wNAF(Point2, CURVE.endo ? Math.ceil(_bits / 2) : _bits);
+ return {
+ CURVE,
+ ProjectivePoint: Point2,
+ normPrivateKeyToScalar,
+ weierstrassEquation,
+ isWithinCurveOrder
+ };
+ }
+ function validateOpts(curve) {
+ const opts = validateBasic(curve);
+ validateObject(opts, {
+ hash: "hash",
+ hmac: "function",
+ randomBytes: "function"
+ }, {
+ bits2int: "function",
+ bits2int_modN: "function",
+ lowS: "boolean"
+ });
+ return Object.freeze({ lowS: true, ...opts });
+ }
+ function weierstrass(curveDef) {
+ const CURVE = validateOpts(curveDef);
+ const { Fp: Fp2, n: CURVE_ORDER } = CURVE;
+ const compressedLen = Fp2.BYTES + 1;
+ const uncompressedLen = 2 * Fp2.BYTES + 1;
+ function isValidFieldElement(num) {
+ return _0n4 < num && num < Fp2.ORDER;
+ }
+ function modN2(a) {
+ return mod(a, CURVE_ORDER);
+ }
+ function invN(a) {
+ return invert(a, CURVE_ORDER);
+ }
+ const { ProjectivePoint: Point2, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({
+ ...CURVE,
+ toBytes(_c, point, isCompressed) {
+ const a = point.toAffine();
+ const x = Fp2.toBytes(a.x);
+ const cat = concatBytes2;
+ if (isCompressed) {
+ return cat(Uint8Array.from([point.hasEvenY() ? 2 : 3]), x);
+ } else {
+ return cat(Uint8Array.from([4]), x, Fp2.toBytes(a.y));
+ }
+ },
+ fromBytes(bytes4) {
+ const len = bytes4.length;
+ const head = bytes4[0];
+ const tail = bytes4.subarray(1);
+ if (len === compressedLen && (head === 2 || head === 3)) {
+ const x = bytesToNumberBE(tail);
+ if (!isValidFieldElement(x))
+ throw new Error("Point is not on curve");
+ const y2 = weierstrassEquation(x);
+ let y = Fp2.sqrt(y2);
+ const isYOdd = (y & _1n4) === _1n4;
+ const isHeadOdd = (head & 1) === 1;
+ if (isHeadOdd !== isYOdd)
+ y = Fp2.neg(y);
+ return { x, y };
+ } else if (len === uncompressedLen && head === 4) {
+ const x = Fp2.fromBytes(tail.subarray(0, Fp2.BYTES));
+ const y = Fp2.fromBytes(tail.subarray(Fp2.BYTES, 2 * Fp2.BYTES));
+ return { x, y };
+ } else {
+ throw new Error(`Point of length ${len} was invalid. Expected ${compressedLen} compressed bytes or ${uncompressedLen} uncompressed bytes`);
+ }
+ }
+ });
+ const numToNByteStr = (num) => bytesToHex(numberToBytesBE(num, CURVE.nByteLength));
+ function isBiggerThanHalfOrder(number4) {
+ const HALF = CURVE_ORDER >> _1n4;
+ return number4 > HALF;
+ }
+ function normalizeS(s) {
+ return isBiggerThanHalfOrder(s) ? modN2(-s) : s;
+ }
+ const slcNum = (b, from, to) => bytesToNumberBE(b.slice(from, to));
+ class Signature {
+ constructor(r, s, recovery) {
+ this.r = r;
+ this.s = s;
+ this.recovery = recovery;
+ this.assertValidity();
+ }
+ static fromCompact(hex2) {
+ const l = CURVE.nByteLength;
+ hex2 = ensureBytes("compactSignature", hex2, l * 2);
+ return new Signature(slcNum(hex2, 0, l), slcNum(hex2, l, 2 * l));
+ }
+ static fromDER(hex2) {
+ const { r, s } = DER.toSig(ensureBytes("DER", hex2));
+ return new Signature(r, s);
+ }
+ assertValidity() {
+ if (!isWithinCurveOrder(this.r))
+ throw new Error("r must be 0 < r < CURVE.n");
+ if (!isWithinCurveOrder(this.s))
+ throw new Error("s must be 0 < s < CURVE.n");
+ }
+ addRecoveryBit(recovery) {
+ return new Signature(this.r, this.s, recovery);
+ }
+ recoverPublicKey(msgHash) {
+ const { r, s, recovery: rec } = this;
+ const h = bits2int_modN(ensureBytes("msgHash", msgHash));
+ if (rec == null || ![0, 1, 2, 3].includes(rec))
+ throw new Error("recovery id invalid");
+ const radj = rec === 2 || rec === 3 ? r + CURVE.n : r;
+ if (radj >= Fp2.ORDER)
+ throw new Error("recovery id 2 or 3 invalid");
+ const prefix = (rec & 1) === 0 ? "02" : "03";
+ const R = Point2.fromHex(prefix + numToNByteStr(radj));
+ const ir = invN(radj);
+ const u1 = modN2(-h * ir);
+ const u2 = modN2(s * ir);
+ const Q = Point2.BASE.multiplyAndAddUnsafe(R, u1, u2);
+ if (!Q)
+ throw new Error("point at infinify");
+ Q.assertValidity();
+ return Q;
+ }
+ hasHighS() {
+ return isBiggerThanHalfOrder(this.s);
+ }
+ normalizeS() {
+ return this.hasHighS() ? new Signature(this.r, modN2(-this.s), this.recovery) : this;
+ }
+ toDERRawBytes() {
+ return hexToBytes(this.toDERHex());
+ }
+ toDERHex() {
+ return DER.hexFromSig({ r: this.r, s: this.s });
+ }
+ toCompactRawBytes() {
+ return hexToBytes(this.toCompactHex());
+ }
+ toCompactHex() {
+ return numToNByteStr(this.r) + numToNByteStr(this.s);
+ }
+ }
+ const utils = {
+ isValidPrivateKey(privateKey) {
+ try {
+ normPrivateKeyToScalar(privateKey);
+ return true;
+ } catch (error) {
+ return false;
+ }
+ },
+ normPrivateKeyToScalar,
+ randomPrivateKey: () => {
+ const length = getMinHashLength(CURVE.n);
+ return mapHashToField(CURVE.randomBytes(length), CURVE.n);
+ },
+ precompute(windowSize = 8, point = Point2.BASE) {
+ point._setWindowSize(windowSize);
+ point.multiply(BigInt(3));
+ return point;
+ }
+ };
+ function getPublicKey2(privateKey, isCompressed = true) {
+ return Point2.fromPrivateKey(privateKey).toRawBytes(isCompressed);
+ }
+ function isProbPub(item) {
+ const arr = item instanceof Uint8Array;
+ const str = typeof item === "string";
+ const len = (arr || str) && item.length;
+ if (arr)
+ return len === compressedLen || len === uncompressedLen;
+ if (str)
+ return len === 2 * compressedLen || len === 2 * uncompressedLen;
+ if (item instanceof Point2)
+ return true;
+ return false;
+ }
+ function getSharedSecret(privateA, publicB, isCompressed = true) {
+ if (isProbPub(privateA))
+ throw new Error("first arg must be private key");
+ if (!isProbPub(publicB))
+ throw new Error("second arg must be public key");
+ const b = Point2.fromHex(publicB);
+ return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed);
+ }
+ const bits2int = CURVE.bits2int || function(bytes4) {
+ const num = bytesToNumberBE(bytes4);
+ const delta = bytes4.length * 8 - CURVE.nBitLength;
+ return delta > 0 ? num >> BigInt(delta) : num;
+ };
+ const bits2int_modN = CURVE.bits2int_modN || function(bytes4) {
+ return modN2(bits2int(bytes4));
+ };
+ const ORDER_MASK = bitMask(CURVE.nBitLength);
+ function int2octets(num) {
+ if (typeof num !== "bigint")
+ throw new Error("bigint expected");
+ if (!(_0n4 <= num && num < ORDER_MASK))
+ throw new Error(`bigint expected < 2^${CURVE.nBitLength}`);
+ return numberToBytesBE(num, CURVE.nByteLength);
+ }
+ function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
+ if (["recovered", "canonical"].some((k) => k in opts))
+ throw new Error("sign() legacy options not supported");
+ const { hash: hash3, randomBytes: randomBytes3 } = CURVE;
+ let { lowS, prehash, extraEntropy: ent } = opts;
+ if (lowS == null)
+ lowS = true;
+ msgHash = ensureBytes("msgHash", msgHash);
+ if (prehash)
+ msgHash = ensureBytes("prehashed msgHash", hash3(msgHash));
+ const h1int = bits2int_modN(msgHash);
+ const d = normPrivateKeyToScalar(privateKey);
+ const seedArgs = [int2octets(d), int2octets(h1int)];
+ if (ent != null) {
+ const e = ent === true ? randomBytes3(Fp2.BYTES) : ent;
+ seedArgs.push(ensureBytes("extraEntropy", e));
+ }
+ const seed = concatBytes2(...seedArgs);
+ const m = h1int;
+ function k2sig(kBytes) {
+ const k = bits2int(kBytes);
+ if (!isWithinCurveOrder(k))
+ return;
+ const ik = invN(k);
+ const q = Point2.BASE.multiply(k).toAffine();
+ const r = modN2(q.x);
+ if (r === _0n4)
+ return;
+ const s = modN2(ik * modN2(m + r * d));
+ if (s === _0n4)
+ return;
+ let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n4);
+ let normS = s;
+ if (lowS && isBiggerThanHalfOrder(s)) {
+ normS = normalizeS(s);
+ recovery ^= 1;
+ }
+ return new Signature(r, normS, recovery);
+ }
+ return { seed, k2sig };
+ }
+ const defaultSigOpts = { lowS: CURVE.lowS, prehash: false };
+ const defaultVerOpts = { lowS: CURVE.lowS, prehash: false };
+ function sign(msgHash, privKey, opts = defaultSigOpts) {
+ const { seed, k2sig } = prepSig(msgHash, privKey, opts);
+ const C = CURVE;
+ const drbg = createHmacDrbg(C.hash.outputLen, C.nByteLength, C.hmac);
+ return drbg(seed, k2sig);
+ }
+ Point2.BASE._setWindowSize(8);
+ function verify(signature, msgHash, publicKey, opts = defaultVerOpts) {
+ const sg = signature;
+ msgHash = ensureBytes("msgHash", msgHash);
+ publicKey = ensureBytes("publicKey", publicKey);
+ if ("strict" in opts)
+ throw new Error("options.strict was renamed to lowS");
+ const { lowS, prehash } = opts;
+ let _sig = void 0;
+ let P;
+ try {
+ if (typeof sg === "string" || sg instanceof Uint8Array) {
+ try {
+ _sig = Signature.fromDER(sg);
+ } catch (derError) {
+ if (!(derError instanceof DER.Err))
+ throw derError;
+ _sig = Signature.fromCompact(sg);
+ }
+ } else if (typeof sg === "object" && typeof sg.r === "bigint" && typeof sg.s === "bigint") {
+ const { r: r2, s: s2 } = sg;
+ _sig = new Signature(r2, s2);
+ } else {
+ throw new Error("PARSE");
+ }
+ P = Point2.fromHex(publicKey);
+ } catch (error) {
+ if (error.message === "PARSE")
+ throw new Error(`signature must be Signature instance, Uint8Array or hex string`);
+ return false;
+ }
+ if (lowS && _sig.hasHighS())
+ return false;
+ if (prehash)
+ msgHash = CURVE.hash(msgHash);
+ const { r, s } = _sig;
+ const h = bits2int_modN(msgHash);
+ const is = invN(s);
+ const u1 = modN2(h * is);
+ const u2 = modN2(r * is);
+ const R = Point2.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine();
+ if (!R)
+ return false;
+ const v = modN2(R.x);
+ return v === r;
+ }
+ return {
+ CURVE,
+ getPublicKey: getPublicKey2,
+ getSharedSecret,
+ sign,
+ verify,
+ ProjectivePoint: Point2,
+ Signature,
+ utils
+ };
+ }
+
+ // node_modules/@noble/curves/node_modules/@noble/hashes/esm/hmac.js
+ var HMAC = class extends Hash {
+ constructor(hash3, _key) {
+ super();
+ this.finished = false;
+ this.destroyed = false;
+ hash(hash3);
+ const key = toBytes(_key);
+ this.iHash = hash3.create();
+ if (typeof this.iHash.update !== "function")
+ throw new Error("Expected instance of class which extends utils.Hash");
+ this.blockLen = this.iHash.blockLen;
+ this.outputLen = this.iHash.outputLen;
+ const blockLen = this.blockLen;
+ const pad2 = new Uint8Array(blockLen);
+ pad2.set(key.length > blockLen ? hash3.create().update(key).digest() : key);
+ for (let i2 = 0; i2 < pad2.length; i2++)
+ pad2[i2] ^= 54;
+ this.iHash.update(pad2);
+ this.oHash = hash3.create();
+ for (let i2 = 0; i2 < pad2.length; i2++)
+ pad2[i2] ^= 54 ^ 92;
+ this.oHash.update(pad2);
+ pad2.fill(0);
+ }
+ update(buf) {
+ exists(this);
+ this.iHash.update(buf);
+ return this;
+ }
+ digestInto(out) {
+ exists(this);
+ bytes(out, this.outputLen);
+ this.finished = true;
+ this.iHash.digestInto(out);
+ this.oHash.update(out);
+ this.oHash.digestInto(out);
+ this.destroy();
+ }
+ digest() {
+ const out = new Uint8Array(this.oHash.outputLen);
+ this.digestInto(out);
+ return out;
+ }
+ _cloneInto(to) {
+ to || (to = Object.create(Object.getPrototypeOf(this), {}));
+ const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
+ to = to;
+ to.finished = finished;
+ to.destroyed = destroyed;
+ to.blockLen = blockLen;
+ to.outputLen = outputLen;
+ to.oHash = oHash._cloneInto(to.oHash);
+ to.iHash = iHash._cloneInto(to.iHash);
+ return to;
+ }
+ destroy() {
+ this.destroyed = true;
+ this.oHash.destroy();
+ this.iHash.destroy();
+ }
+ };
+ var hmac = (hash3, key, message) => new HMAC(hash3, key).update(message).digest();
+ hmac.create = (hash3, key) => new HMAC(hash3, key);
+
+ // node_modules/@noble/curves/esm/_shortw_utils.js
+ function getHash(hash3) {
+ return {
+ hash: hash3,
+ hmac: (key, ...msgs) => hmac(hash3, key, concatBytes(...msgs)),
+ randomBytes
+ };
+ }
+ function createCurve(curveDef, defHash) {
+ const create = (hash3) => weierstrass({ ...curveDef, ...getHash(hash3) });
+ return Object.freeze({ ...create(defHash), create });
+ }
+
+ // node_modules/@noble/curves/esm/secp256k1.js
+ var secp256k1P = BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f");
+ var secp256k1N = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141");
+ var _1n5 = BigInt(1);
+ var _2n4 = BigInt(2);
+ var divNearest = (a, b) => (a + b / _2n4) / b;
+ function sqrtMod(y) {
+ const P = secp256k1P;
+ const _3n3 = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22);
+ const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88);
+ const b2 = y * y * y % P;
+ const b3 = b2 * b2 * y % P;
+ const b6 = pow2(b3, _3n3, P) * b3 % P;
+ const b9 = pow2(b6, _3n3, P) * b3 % P;
+ const b11 = pow2(b9, _2n4, P) * b2 % P;
+ const b22 = pow2(b11, _11n, P) * b11 % P;
+ const b44 = pow2(b22, _22n, P) * b22 % P;
+ const b88 = pow2(b44, _44n, P) * b44 % P;
+ const b176 = pow2(b88, _88n, P) * b88 % P;
+ const b220 = pow2(b176, _44n, P) * b44 % P;
+ const b223 = pow2(b220, _3n3, P) * b3 % P;
+ const t1 = pow2(b223, _23n, P) * b22 % P;
+ const t2 = pow2(t1, _6n, P) * b2 % P;
+ const root = pow2(t2, _2n4, P);
+ if (!Fp.eql(Fp.sqr(root), y))
+ throw new Error("Cannot find square root");
+ return root;
+ }
+ var Fp = Field(secp256k1P, void 0, void 0, { sqrt: sqrtMod });
+ var secp256k1 = createCurve({
+ a: BigInt(0),
+ b: BigInt(7),
+ Fp,
+ n: secp256k1N,
+ Gx: BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"),
+ Gy: BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"),
+ h: BigInt(1),
+ lowS: true,
+ endo: {
+ beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),
+ splitScalar: (k) => {
+ const n = secp256k1N;
+ const a1 = BigInt("0x3086d221a7d46bcde86c90e49284eb15");
+ const b1 = -_1n5 * BigInt("0xe4437ed6010e88286f547fa90abfe4c3");
+ const a2 = BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8");
+ const b2 = a1;
+ const POW_2_128 = BigInt("0x100000000000000000000000000000000");
+ const c1 = divNearest(b2 * k, n);
+ const c2 = divNearest(-b1 * k, n);
+ let k1 = mod(k - c1 * a1 - c2 * a2, n);
+ let k2 = mod(-c1 * b1 - c2 * b2, n);
+ const k1neg = k1 > POW_2_128;
+ const k2neg = k2 > POW_2_128;
+ if (k1neg)
+ k1 = n - k1;
+ if (k2neg)
+ k2 = n - k2;
+ if (k1 > POW_2_128 || k2 > POW_2_128) {
+ throw new Error("splitScalar: Endomorphism failed, k=" + k);
+ }
+ return { k1neg, k1, k2neg, k2 };
+ }
+ }
+ }, sha256);
+ var _0n5 = BigInt(0);
+ var fe = (x) => typeof x === "bigint" && _0n5 < x && x < secp256k1P;
+ var ge = (x) => typeof x === "bigint" && _0n5 < x && x < secp256k1N;
+ var TAGGED_HASH_PREFIXES = {};
+ function taggedHash(tag, ...messages) {
+ let tagP = TAGGED_HASH_PREFIXES[tag];
+ if (tagP === void 0) {
+ const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0)));
+ tagP = concatBytes2(tagH, tagH);
+ TAGGED_HASH_PREFIXES[tag] = tagP;
+ }
+ return sha256(concatBytes2(tagP, ...messages));
+ }
+ var pointToBytes = (point) => point.toRawBytes(true).slice(1);
+ var numTo32b = (n) => numberToBytesBE(n, 32);
+ var modP = (x) => mod(x, secp256k1P);
+ var modN = (x) => mod(x, secp256k1N);
+ var Point = secp256k1.ProjectivePoint;
+ var GmulAdd = (Q, a, b) => Point.BASE.multiplyAndAddUnsafe(Q, a, b);
+ function schnorrGetExtPubKey(priv) {
+ let d_ = secp256k1.utils.normPrivateKeyToScalar(priv);
+ let p = Point.fromPrivateKey(d_);
+ const scalar = p.hasEvenY() ? d_ : modN(-d_);
+ return { scalar, bytes: pointToBytes(p) };
+ }
+ function lift_x(x) {
+ if (!fe(x))
+ throw new Error("bad x: need 0 < x < p");
+ const xx = modP(x * x);
+ const c = modP(xx * x + BigInt(7));
+ let y = sqrtMod(c);
+ if (y % _2n4 !== _0n5)
+ y = modP(-y);
+ const p = new Point(x, y, _1n5);
+ p.assertValidity();
+ return p;
+ }
+ function challenge(...args) {
+ return modN(bytesToNumberBE(taggedHash("BIP0340/challenge", ...args)));
+ }
+ function schnorrGetPublicKey(privateKey) {
+ return schnorrGetExtPubKey(privateKey).bytes;
+ }
+ function schnorrSign(message, privateKey, auxRand = randomBytes(32)) {
+ const m = ensureBytes("message", message);
+ const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey);
+ const a = ensureBytes("auxRand", auxRand, 32);
+ const t = numTo32b(d ^ bytesToNumberBE(taggedHash("BIP0340/aux", a)));
+ const rand = taggedHash("BIP0340/nonce", t, px, m);
+ const k_ = modN(bytesToNumberBE(rand));
+ if (k_ === _0n5)
+ throw new Error("sign failed: k is zero");
+ const { bytes: rx, scalar: k } = schnorrGetExtPubKey(k_);
+ const e = challenge(rx, px, m);
+ const sig = new Uint8Array(64);
+ sig.set(rx, 0);
+ sig.set(numTo32b(modN(k + e * d)), 32);
+ if (!schnorrVerify(sig, m, px))
+ throw new Error("sign: Invalid signature produced");
+ return sig;
+ }
+ function schnorrVerify(signature, message, publicKey) {
+ const sig = ensureBytes("signature", signature, 64);
+ const m = ensureBytes("message", message);
+ const pub = ensureBytes("publicKey", publicKey, 32);
+ try {
+ const P = lift_x(bytesToNumberBE(pub));
+ const r = bytesToNumberBE(sig.subarray(0, 32));
+ if (!fe(r))
+ return false;
+ const s = bytesToNumberBE(sig.subarray(32, 64));
+ if (!ge(s))
+ return false;
+ const e = challenge(numTo32b(r), pointToBytes(P), m);
+ const R = GmulAdd(P, s, modN(-e));
+ if (!R || !R.hasEvenY() || R.toAffine().x !== r)
+ return false;
+ return true;
+ } catch (error) {
+ return false;
+ }
+ }
+ var schnorr = /* @__PURE__ */ (() => ({
+ getPublicKey: schnorrGetPublicKey,
+ sign: schnorrSign,
+ verify: schnorrVerify,
+ utils: {
+ randomPrivateKey: secp256k1.utils.randomPrivateKey,
+ lift_x,
+ pointToBytes,
+ numberToBytesBE,
+ bytesToNumberBE,
+ taggedHash,
+ mod
+ }
+ }))();
+
+ // node_modules/@noble/hashes/esm/crypto.js
+ var crypto2 = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : void 0;
+
+ // node_modules/@noble/hashes/esm/utils.js
+ var u8a3 = (a) => a instanceof Uint8Array;
+ var createView2 = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
+ var rotr2 = (word, shift) => word << 32 - shift | word >>> shift;
+ var isLE2 = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
+ if (!isLE2)
+ throw new Error("Non little-endian hardware is not supported");
+ var hexes2 = Array.from({ length: 256 }, (v, i2) => i2.toString(16).padStart(2, "0"));
+ function bytesToHex2(bytes4) {
+ if (!u8a3(bytes4))
+ throw new Error("Uint8Array expected");
+ let hex2 = "";
+ for (let i2 = 0; i2 < bytes4.length; i2++) {
+ hex2 += hexes2[bytes4[i2]];
+ }
+ return hex2;
+ }
+ function hexToBytes2(hex2) {
+ if (typeof hex2 !== "string")
+ throw new Error("hex string expected, got " + typeof hex2);
+ const len = hex2.length;
+ if (len % 2)
+ throw new Error("padded hex string expected, got unpadded hex of length " + len);
+ const array = new Uint8Array(len / 2);
+ for (let i2 = 0; i2 < array.length; i2++) {
+ const j = i2 * 2;
+ const hexByte = hex2.slice(j, j + 2);
+ const byte = Number.parseInt(hexByte, 16);
+ if (Number.isNaN(byte) || byte < 0)
+ throw new Error("Invalid byte sequence");
+ array[i2] = byte;
+ }
+ return array;
+ }
+ function utf8ToBytes3(str) {
+ if (typeof str !== "string")
+ throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
+ return new Uint8Array(new TextEncoder().encode(str));
+ }
+ function toBytes2(data) {
+ if (typeof data === "string")
+ data = utf8ToBytes3(data);
+ if (!u8a3(data))
+ throw new Error(`expected Uint8Array, got ${typeof data}`);
+ return data;
+ }
+ function concatBytes3(...arrays) {
+ const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
+ let pad2 = 0;
+ arrays.forEach((a) => {
+ if (!u8a3(a))
+ throw new Error("Uint8Array expected");
+ r.set(a, pad2);
+ pad2 += a.length;
+ });
+ return r;
+ }
+ var Hash2 = class {
+ clone() {
+ return this._cloneInto();
+ }
+ };
+ function wrapConstructor2(hashCons) {
+ const hashC = (msg) => hashCons().update(toBytes2(msg)).digest();
+ const tmp = hashCons();
+ hashC.outputLen = tmp.outputLen;
+ hashC.blockLen = tmp.blockLen;
+ hashC.create = () => hashCons();
+ return hashC;
+ }
+ function randomBytes2(bytesLength = 32) {
+ if (crypto2 && typeof crypto2.getRandomValues === "function") {
+ return crypto2.getRandomValues(new Uint8Array(bytesLength));
+ }
+ throw new Error("crypto.getRandomValues must be defined");
+ }
+
+ // core.ts
+ var verifiedSymbol = Symbol("verified");
+ var isRecord = (obj) => obj instanceof Object;
+ function validateEvent(event) {
+ if (!isRecord(event))
+ return false;
+ if (typeof event.kind !== "number")
+ return false;
+ if (typeof event.content !== "string")
+ return false;
+ if (typeof event.created_at !== "number")
+ return false;
+ if (typeof event.pubkey !== "string")
+ return false;
+ if (!event.pubkey.match(/^[a-f0-9]{64}$/))
+ return false;
+ if (!Array.isArray(event.tags))
+ return false;
+ for (let i2 = 0; i2 < event.tags.length; i2++) {
+ let tag = event.tags[i2];
+ if (!Array.isArray(tag))
+ return false;
+ for (let j = 0; j < tag.length; j++) {
+ if (typeof tag[j] !== "string")
+ return false;
+ }
+ }
+ return true;
+ }
+ function sortEvents(events) {
+ return events.sort((a, b) => {
+ if (a.created_at !== b.created_at) {
+ return b.created_at - a.created_at;
+ }
+ return a.id.localeCompare(b.id);
+ });
+ }
+
+ // node_modules/@noble/hashes/esm/_assert.js
+ function number2(n) {
+ if (!Number.isSafeInteger(n) || n < 0)
+ throw new Error(`Wrong positive integer: ${n}`);
+ }
+ function bool(b) {
+ if (typeof b !== "boolean")
+ throw new Error(`Expected boolean, not ${b}`);
+ }
+ function bytes2(b, ...lengths) {
+ if (!(b instanceof Uint8Array))
+ throw new Error("Expected Uint8Array");
+ if (lengths.length > 0 && !lengths.includes(b.length))
+ throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
+ }
+ function hash2(hash3) {
+ if (typeof hash3 !== "function" || typeof hash3.create !== "function")
+ throw new Error("Hash should be wrapped by utils.wrapConstructor");
+ number2(hash3.outputLen);
+ number2(hash3.blockLen);
+ }
+ function exists2(instance, checkFinished = true) {
+ if (instance.destroyed)
+ throw new Error("Hash instance has been destroyed");
+ if (checkFinished && instance.finished)
+ throw new Error("Hash#digest() has already been called");
+ }
+ function output2(out, instance) {
+ bytes2(out);
+ const min = instance.outputLen;
+ if (out.length < min) {
+ throw new Error(`digestInto() expects output buffer of length at least ${min}`);
+ }
+ }
+ var assert = {
+ number: number2,
+ bool,
+ bytes: bytes2,
+ hash: hash2,
+ exists: exists2,
+ output: output2
+ };
+ var assert_default = assert;
+
+ // node_modules/@noble/hashes/esm/_sha2.js
+ function setBigUint642(view, byteOffset, value, isLE4) {
+ if (typeof view.setBigUint64 === "function")
+ return view.setBigUint64(byteOffset, value, isLE4);
+ const _32n = BigInt(32);
+ const _u32_max = BigInt(4294967295);
+ const wh = Number(value >> _32n & _u32_max);
+ const wl = Number(value & _u32_max);
+ const h = isLE4 ? 4 : 0;
+ const l = isLE4 ? 0 : 4;
+ view.setUint32(byteOffset + h, wh, isLE4);
+ view.setUint32(byteOffset + l, wl, isLE4);
+ }
+ var SHA22 = class extends Hash2 {
+ constructor(blockLen, outputLen, padOffset, isLE4) {
+ super();
+ this.blockLen = blockLen;
+ this.outputLen = outputLen;
+ this.padOffset = padOffset;
+ this.isLE = isLE4;
+ this.finished = false;
+ this.length = 0;
+ this.pos = 0;
+ this.destroyed = false;
+ this.buffer = new Uint8Array(blockLen);
+ this.view = createView2(this.buffer);
+ }
+ update(data) {
+ assert_default.exists(this);
+ const { view, buffer, blockLen } = this;
+ data = toBytes2(data);
+ const len = data.length;
+ for (let pos = 0; pos < len; ) {
+ const take = Math.min(blockLen - this.pos, len - pos);
+ if (take === blockLen) {
+ const dataView = createView2(data);
+ for (; blockLen <= len - pos; pos += blockLen)
+ this.process(dataView, pos);
+ continue;
+ }
+ buffer.set(data.subarray(pos, pos + take), this.pos);
+ this.pos += take;
+ pos += take;
+ if (this.pos === blockLen) {
+ this.process(view, 0);
+ this.pos = 0;
+ }
+ }
+ this.length += data.length;
+ this.roundClean();
+ return this;
+ }
+ digestInto(out) {
+ assert_default.exists(this);
+ assert_default.output(out, this);
+ this.finished = true;
+ const { buffer, view, blockLen, isLE: isLE4 } = this;
+ let { pos } = this;
+ buffer[pos++] = 128;
+ this.buffer.subarray(pos).fill(0);
+ if (this.padOffset > blockLen - pos) {
+ this.process(view, 0);
+ pos = 0;
+ }
+ for (let i2 = pos; i2 < blockLen; i2++)
+ buffer[i2] = 0;
+ setBigUint642(view, blockLen - 8, BigInt(this.length * 8), isLE4);
+ this.process(view, 0);
+ const oview = createView2(out);
+ const len = this.outputLen;
+ if (len % 4)
+ throw new Error("_sha2: outputLen should be aligned to 32bit");
+ const outLen = len / 4;
+ const state = this.get();
+ if (outLen > state.length)
+ throw new Error("_sha2: outputLen bigger than state");
+ for (let i2 = 0; i2 < outLen; i2++)
+ oview.setUint32(4 * i2, state[i2], isLE4);
+ }
+ digest() {
+ const { buffer, outputLen } = this;
+ this.digestInto(buffer);
+ const res = buffer.slice(0, outputLen);
+ this.destroy();
+ return res;
+ }
+ _cloneInto(to) {
+ to || (to = new this.constructor());
+ to.set(...this.get());
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
+ to.length = length;
+ to.pos = pos;
+ to.finished = finished;
+ to.destroyed = destroyed;
+ if (length % blockLen)
+ to.buffer.set(buffer);
+ return to;
+ }
+ };
+
+ // node_modules/@noble/hashes/esm/sha256.js
+ var Chi2 = (a, b, c) => a & b ^ ~a & c;
+ var Maj2 = (a, b, c) => a & b ^ a & c ^ b & c;
+ var SHA256_K2 = new Uint32Array([
+ 1116352408,
+ 1899447441,
+ 3049323471,
+ 3921009573,
+ 961987163,
+ 1508970993,
+ 2453635748,
+ 2870763221,
+ 3624381080,
+ 310598401,
+ 607225278,
+ 1426881987,
+ 1925078388,
+ 2162078206,
+ 2614888103,
+ 3248222580,
+ 3835390401,
+ 4022224774,
+ 264347078,
+ 604807628,
+ 770255983,
+ 1249150122,
+ 1555081692,
+ 1996064986,
+ 2554220882,
+ 2821834349,
+ 2952996808,
+ 3210313671,
+ 3336571891,
+ 3584528711,
+ 113926993,
+ 338241895,
+ 666307205,
+ 773529912,
+ 1294757372,
+ 1396182291,
+ 1695183700,
+ 1986661051,
+ 2177026350,
+ 2456956037,
+ 2730485921,
+ 2820302411,
+ 3259730800,
+ 3345764771,
+ 3516065817,
+ 3600352804,
+ 4094571909,
+ 275423344,
+ 430227734,
+ 506948616,
+ 659060556,
+ 883997877,
+ 958139571,
+ 1322822218,
+ 1537002063,
+ 1747873779,
+ 1955562222,
+ 2024104815,
+ 2227730452,
+ 2361852424,
+ 2428436474,
+ 2756734187,
+ 3204031479,
+ 3329325298
+ ]);
+ var IV2 = new Uint32Array([
+ 1779033703,
+ 3144134277,
+ 1013904242,
+ 2773480762,
+ 1359893119,
+ 2600822924,
+ 528734635,
+ 1541459225
+ ]);
+ var SHA256_W2 = new Uint32Array(64);
+ var SHA2562 = class extends SHA22 {
+ constructor() {
+ super(64, 32, 8, false);
+ this.A = IV2[0] | 0;
+ this.B = IV2[1] | 0;
+ this.C = IV2[2] | 0;
+ this.D = IV2[3] | 0;
+ this.E = IV2[4] | 0;
+ this.F = IV2[5] | 0;
+ this.G = IV2[6] | 0;
+ this.H = IV2[7] | 0;
+ }
+ get() {
+ const { A, B, C, D, E, F, G, H } = this;
+ return [A, B, C, D, E, F, G, H];
+ }
+ set(A, B, C, D, E, F, G, H) {
+ this.A = A | 0;
+ this.B = B | 0;
+ this.C = C | 0;
+ this.D = D | 0;
+ this.E = E | 0;
+ this.F = F | 0;
+ this.G = G | 0;
+ this.H = H | 0;
+ }
+ process(view, offset) {
+ for (let i2 = 0; i2 < 16; i2++, offset += 4)
+ SHA256_W2[i2] = view.getUint32(offset, false);
+ for (let i2 = 16; i2 < 64; i2++) {
+ const W15 = SHA256_W2[i2 - 15];
+ const W2 = SHA256_W2[i2 - 2];
+ const s0 = rotr2(W15, 7) ^ rotr2(W15, 18) ^ W15 >>> 3;
+ const s1 = rotr2(W2, 17) ^ rotr2(W2, 19) ^ W2 >>> 10;
+ SHA256_W2[i2] = s1 + SHA256_W2[i2 - 7] + s0 + SHA256_W2[i2 - 16] | 0;
+ }
+ let { A, B, C, D, E, F, G, H } = this;
+ for (let i2 = 0; i2 < 64; i2++) {
+ const sigma1 = rotr2(E, 6) ^ rotr2(E, 11) ^ rotr2(E, 25);
+ const T1 = H + sigma1 + Chi2(E, F, G) + SHA256_K2[i2] + SHA256_W2[i2] | 0;
+ const sigma0 = rotr2(A, 2) ^ rotr2(A, 13) ^ rotr2(A, 22);
+ const T2 = sigma0 + Maj2(A, B, C) | 0;
+ H = G;
+ G = F;
+ F = E;
+ E = D + T1 | 0;
+ D = C;
+ C = B;
+ B = A;
+ A = T1 + T2 | 0;
+ }
+ A = A + this.A | 0;
+ B = B + this.B | 0;
+ C = C + this.C | 0;
+ D = D + this.D | 0;
+ E = E + this.E | 0;
+ F = F + this.F | 0;
+ G = G + this.G | 0;
+ H = H + this.H | 0;
+ this.set(A, B, C, D, E, F, G, H);
+ }
+ roundClean() {
+ SHA256_W2.fill(0);
+ }
+ destroy() {
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
+ this.buffer.fill(0);
+ }
+ };
+ var SHA224 = class extends SHA2562 {
+ constructor() {
+ super();
+ this.A = 3238371032 | 0;
+ this.B = 914150663 | 0;
+ this.C = 812702999 | 0;
+ this.D = 4144912697 | 0;
+ this.E = 4290775857 | 0;
+ this.F = 1750603025 | 0;
+ this.G = 1694076839 | 0;
+ this.H = 3204075428 | 0;
+ this.outputLen = 28;
+ }
+ };
+ var sha2562 = wrapConstructor2(() => new SHA2562());
+ var sha224 = wrapConstructor2(() => new SHA224());
+
+ // utils.ts
+ var utils_exports2 = {};
+ __export(utils_exports2, {
+ Queue: () => Queue,
+ QueueNode: () => QueueNode,
+ binarySearch: () => binarySearch,
+ bytesToHex: () => bytesToHex2,
+ hexToBytes: () => hexToBytes2,
+ insertEventIntoAscendingList: () => insertEventIntoAscendingList,
+ insertEventIntoDescendingList: () => insertEventIntoDescendingList,
+ normalizeURL: () => normalizeURL,
+ utf8Decoder: () => utf8Decoder,
+ utf8Encoder: () => utf8Encoder
+ });
+ var utf8Decoder = new TextDecoder("utf-8");
+ var utf8Encoder = new TextEncoder();
+ function normalizeURL(url) {
+ try {
+ if (url.indexOf("://") === -1)
+ url = "wss://" + url;
+ let p = new URL(url);
+ p.pathname = p.pathname.replace(/\/+/g, "/");
+ if (p.pathname.endsWith("/"))
+ p.pathname = p.pathname.slice(0, -1);
+ if (p.port === "80" && p.protocol === "ws:" || p.port === "443" && p.protocol === "wss:")
+ p.port = "";
+ p.searchParams.sort();
+ p.hash = "";
+ return p.toString();
+ } catch (e) {
+ throw new Error(`Invalid URL: ${url}`);
+ }
+ }
+ function insertEventIntoDescendingList(sortedArray, event) {
+ const [idx, found] = binarySearch(sortedArray, (b) => {
+ if (event.id === b.id)
+ return 0;
+ if (event.created_at === b.created_at)
+ return -1;
+ return b.created_at - event.created_at;
+ });
+ if (!found) {
+ sortedArray.splice(idx, 0, event);
+ }
+ return sortedArray;
+ }
+ function insertEventIntoAscendingList(sortedArray, event) {
+ const [idx, found] = binarySearch(sortedArray, (b) => {
+ if (event.id === b.id)
+ return 0;
+ if (event.created_at === b.created_at)
+ return -1;
+ return event.created_at - b.created_at;
+ });
+ if (!found) {
+ sortedArray.splice(idx, 0, event);
+ }
+ return sortedArray;
+ }
+ function binarySearch(arr, compare) {
+ let start = 0;
+ let end = arr.length - 1;
+ while (start <= end) {
+ const mid = Math.floor((start + end) / 2);
+ const cmp = compare(arr[mid]);
+ if (cmp === 0) {
+ return [mid, true];
+ }
+ if (cmp < 0) {
+ end = mid - 1;
+ } else {
+ start = mid + 1;
+ }
+ }
+ return [start, false];
+ }
+ var QueueNode = class {
+ value;
+ next = null;
+ prev = null;
+ constructor(message) {
+ this.value = message;
+ }
+ };
+ var Queue = class {
+ first;
+ last;
+ constructor() {
+ this.first = null;
+ this.last = null;
+ }
+ enqueue(value) {
+ const newNode = new QueueNode(value);
+ if (!this.last) {
+ this.first = newNode;
+ this.last = newNode;
+ } else if (this.last === this.first) {
+ this.last = newNode;
+ this.last.prev = this.first;
+ this.first.next = newNode;
+ } else {
+ newNode.prev = this.last;
+ this.last.next = newNode;
+ this.last = newNode;
+ }
+ return true;
+ }
+ dequeue() {
+ if (!this.first)
+ return null;
+ if (this.first === this.last) {
+ const target2 = this.first;
+ this.first = null;
+ this.last = null;
+ return target2.value;
+ }
+ const target = this.first;
+ this.first = target.next;
+ if (this.first) {
+ this.first.prev = null;
+ }
+ return target.value;
+ }
+ };
+
+ // pure.ts
+ var JS = class {
+ generateSecretKey() {
+ return schnorr.utils.randomPrivateKey();
+ }
+ getPublicKey(secretKey) {
+ return bytesToHex2(schnorr.getPublicKey(secretKey));
+ }
+ finalizeEvent(t, secretKey) {
+ const event = t;
+ event.pubkey = bytesToHex2(schnorr.getPublicKey(secretKey));
+ event.id = getEventHash(event);
+ event.sig = bytesToHex2(schnorr.sign(getEventHash(event), secretKey));
+ event[verifiedSymbol] = true;
+ return event;
+ }
+ verifyEvent(event) {
+ if (typeof event[verifiedSymbol] === "boolean")
+ return event[verifiedSymbol];
+ const hash3 = getEventHash(event);
+ if (hash3 !== event.id) {
+ event[verifiedSymbol] = false;
+ return false;
+ }
+ try {
+ const valid = schnorr.verify(event.sig, hash3, event.pubkey);
+ event[verifiedSymbol] = valid;
+ return valid;
+ } catch (err) {
+ event[verifiedSymbol] = false;
+ return false;
+ }
+ }
+ };
+ function serializeEvent(evt) {
+ if (!validateEvent(evt))
+ throw new Error("can't serialize event with wrong or missing properties");
+ return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]);
+ }
+ function getEventHash(event) {
+ let eventHash = sha2562(utf8Encoder.encode(serializeEvent(event)));
+ return bytesToHex2(eventHash);
+ }
+ var i = new JS();
+ var generateSecretKey = i.generateSecretKey;
+ var getPublicKey = i.getPublicKey;
+ var finalizeEvent = i.finalizeEvent;
+ var verifyEvent = i.verifyEvent;
+
+ // kinds.ts
+ var kinds_exports = {};
+ __export(kinds_exports, {
+ Application: () => Application,
+ BadgeAward: () => BadgeAward,
+ BadgeDefinition: () => BadgeDefinition,
+ BlockedRelaysList: () => BlockedRelaysList,
+ BookmarkList: () => BookmarkList,
+ Bookmarksets: () => Bookmarksets,
+ Calendar: () => Calendar,
+ CalendarEventRSVP: () => CalendarEventRSVP,
+ ChannelCreation: () => ChannelCreation,
+ ChannelHideMessage: () => ChannelHideMessage,
+ ChannelMessage: () => ChannelMessage,
+ ChannelMetadata: () => ChannelMetadata,
+ ChannelMuteUser: () => ChannelMuteUser,
+ ClassifiedListing: () => ClassifiedListing,
+ ClientAuth: () => ClientAuth,
+ CommunitiesList: () => CommunitiesList,
+ CommunityDefinition: () => CommunityDefinition,
+ CommunityPostApproval: () => CommunityPostApproval,
+ Contacts: () => Contacts,
+ CreateOrUpdateProduct: () => CreateOrUpdateProduct,
+ CreateOrUpdateStall: () => CreateOrUpdateStall,
+ Curationsets: () => Curationsets,
+ Date: () => Date2,
+ DirectMessageRelaysList: () => DirectMessageRelaysList,
+ DraftClassifiedListing: () => DraftClassifiedListing,
+ DraftLong: () => DraftLong,
+ Emojisets: () => Emojisets,
+ EncryptedDirectMessage: () => EncryptedDirectMessage,
+ EventDeletion: () => EventDeletion,
+ FileMetadata: () => FileMetadata,
+ FileServerPreference: () => FileServerPreference,
+ Followsets: () => Followsets,
+ GenericRepost: () => GenericRepost,
+ Genericlists: () => Genericlists,
+ GiftWrap: () => GiftWrap,
+ HTTPAuth: () => HTTPAuth,
+ Handlerinformation: () => Handlerinformation,
+ Handlerrecommendation: () => Handlerrecommendation,
+ Highlights: () => Highlights,
+ InterestsList: () => InterestsList,
+ Interestsets: () => Interestsets,
+ JobFeedback: () => JobFeedback,
+ JobRequest: () => JobRequest,
+ JobResult: () => JobResult,
+ Label: () => Label,
+ LightningPubRPC: () => LightningPubRPC,
+ LiveChatMessage: () => LiveChatMessage,
+ LiveEvent: () => LiveEvent,
+ LongFormArticle: () => LongFormArticle,
+ Metadata: () => Metadata,
+ Mutelist: () => Mutelist,
+ NWCWalletInfo: () => NWCWalletInfo,
+ NWCWalletRequest: () => NWCWalletRequest,
+ NWCWalletResponse: () => NWCWalletResponse,
+ NostrConnect: () => NostrConnect,
+ OpenTimestamps: () => OpenTimestamps,
+ Pinlist: () => Pinlist,
+ PrivateDirectMessage: () => PrivateDirectMessage,
+ ProblemTracker: () => ProblemTracker,
+ ProfileBadges: () => ProfileBadges,
+ PublicChatsList: () => PublicChatsList,
+ Reaction: () => Reaction,
+ RecommendRelay: () => RecommendRelay,
+ RelayList: () => RelayList,
+ Relaysets: () => Relaysets,
+ Report: () => Report,
+ Reporting: () => Reporting,
+ Repost: () => Repost,
+ Seal: () => Seal,
+ SearchRelaysList: () => SearchRelaysList,
+ ShortTextNote: () => ShortTextNote,
+ Time: () => Time,
+ UserEmojiList: () => UserEmojiList,
+ UserStatuses: () => UserStatuses,
+ Zap: () => Zap,
+ ZapGoal: () => ZapGoal,
+ ZapRequest: () => ZapRequest,
+ classifyKind: () => classifyKind,
+ isAddressableKind: () => isAddressableKind,
+ isEphemeralKind: () => isEphemeralKind,
+ isKind: () => isKind,
+ isRegularKind: () => isRegularKind,
+ isReplaceableKind: () => isReplaceableKind
+ });
+ function isRegularKind(kind) {
+ return 1e3 <= kind && kind < 1e4 || [1, 2, 4, 5, 6, 7, 8, 16, 40, 41, 42, 43, 44].includes(kind);
+ }
+ function isReplaceableKind(kind) {
+ return [0, 3].includes(kind) || 1e4 <= kind && kind < 2e4;
+ }
+ function isEphemeralKind(kind) {
+ return 2e4 <= kind && kind < 3e4;
+ }
+ function isAddressableKind(kind) {
+ return 3e4 <= kind && kind < 4e4;
+ }
+ function classifyKind(kind) {
+ if (isRegularKind(kind))
+ return "regular";
+ if (isReplaceableKind(kind))
+ return "replaceable";
+ if (isEphemeralKind(kind))
+ return "ephemeral";
+ if (isAddressableKind(kind))
+ return "parameterized";
+ return "unknown";
+ }
+ function isKind(event, kind) {
+ const kindAsArray = kind instanceof Array ? kind : [kind];
+ return validateEvent(event) && kindAsArray.includes(event.kind) || false;
+ }
+ var Metadata = 0;
+ var ShortTextNote = 1;
+ var RecommendRelay = 2;
+ var Contacts = 3;
+ var EncryptedDirectMessage = 4;
+ var EventDeletion = 5;
+ var Repost = 6;
+ var Reaction = 7;
+ var BadgeAward = 8;
+ var Seal = 13;
+ var PrivateDirectMessage = 14;
+ var GenericRepost = 16;
+ var ChannelCreation = 40;
+ var ChannelMetadata = 41;
+ var ChannelMessage = 42;
+ var ChannelHideMessage = 43;
+ var ChannelMuteUser = 44;
+ var OpenTimestamps = 1040;
+ var GiftWrap = 1059;
+ var FileMetadata = 1063;
+ var LiveChatMessage = 1311;
+ var ProblemTracker = 1971;
+ var Report = 1984;
+ var Reporting = 1984;
+ var Label = 1985;
+ var CommunityPostApproval = 4550;
+ var JobRequest = 5999;
+ var JobResult = 6999;
+ var JobFeedback = 7e3;
+ var ZapGoal = 9041;
+ var ZapRequest = 9734;
+ var Zap = 9735;
+ var Highlights = 9802;
+ var Mutelist = 1e4;
+ var Pinlist = 10001;
+ var RelayList = 10002;
+ var BookmarkList = 10003;
+ var CommunitiesList = 10004;
+ var PublicChatsList = 10005;
+ var BlockedRelaysList = 10006;
+ var SearchRelaysList = 10007;
+ var InterestsList = 10015;
+ var UserEmojiList = 10030;
+ var DirectMessageRelaysList = 10050;
+ var FileServerPreference = 10096;
+ var NWCWalletInfo = 13194;
+ var LightningPubRPC = 21e3;
+ var ClientAuth = 22242;
+ var NWCWalletRequest = 23194;
+ var NWCWalletResponse = 23195;
+ var NostrConnect = 24133;
+ var HTTPAuth = 27235;
+ var Followsets = 3e4;
+ var Genericlists = 30001;
+ var Relaysets = 30002;
+ var Bookmarksets = 30003;
+ var Curationsets = 30004;
+ var ProfileBadges = 30008;
+ var BadgeDefinition = 30009;
+ var Interestsets = 30015;
+ var CreateOrUpdateStall = 30017;
+ var CreateOrUpdateProduct = 30018;
+ var LongFormArticle = 30023;
+ var DraftLong = 30024;
+ var Emojisets = 30030;
+ var Application = 30078;
+ var LiveEvent = 30311;
+ var UserStatuses = 30315;
+ var ClassifiedListing = 30402;
+ var DraftClassifiedListing = 30403;
+ var Date2 = 31922;
+ var Time = 31923;
+ var Calendar = 31924;
+ var CalendarEventRSVP = 31925;
+ var Handlerrecommendation = 31989;
+ var Handlerinformation = 31990;
+ var CommunityDefinition = 34550;
+
+ // filter.ts
+ function matchFilter(filter, event) {
+ if (filter.ids && filter.ids.indexOf(event.id) === -1) {
+ return false;
+ }
+ if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) {
+ return false;
+ }
+ if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) {
+ return false;
+ }
+ for (let f in filter) {
+ if (f[0] === "#") {
+ let tagName = f.slice(1);
+ let values = filter[`#${tagName}`];
+ if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values.indexOf(v) !== -1))
+ return false;
+ }
+ }
+ if (filter.since && event.created_at < filter.since)
+ return false;
+ if (filter.until && event.created_at > filter.until)
+ return false;
+ return true;
+ }
+ function matchFilters(filters, event) {
+ for (let i2 = 0; i2 < filters.length; i2++) {
+ if (matchFilter(filters[i2], event)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ function mergeFilters(...filters) {
+ let result = {};
+ for (let i2 = 0; i2 < filters.length; i2++) {
+ let filter = filters[i2];
+ Object.entries(filter).forEach(([property, values]) => {
+ if (property === "kinds" || property === "ids" || property === "authors" || property[0] === "#") {
+ result[property] = result[property] || [];
+ for (let v = 0; v < values.length; v++) {
+ let value = values[v];
+ if (!result[property].includes(value))
+ result[property].push(value);
+ }
+ }
+ });
+ if (filter.limit && (!result.limit || filter.limit > result.limit))
+ result.limit = filter.limit;
+ if (filter.until && (!result.until || filter.until > result.until))
+ result.until = filter.until;
+ if (filter.since && (!result.since || filter.since < result.since))
+ result.since = filter.since;
+ }
+ return result;
+ }
+ function getFilterLimit(filter) {
+ if (filter.ids && !filter.ids.length)
+ return 0;
+ if (filter.kinds && !filter.kinds.length)
+ return 0;
+ if (filter.authors && !filter.authors.length)
+ return 0;
+ for (const [key, value] of Object.entries(filter)) {
+ if (key[0] === "#" && Array.isArray(value) && !value.length)
+ return 0;
+ }
+ return Math.min(
+ Math.max(0, filter.limit ?? Infinity),
+ filter.ids?.length ?? Infinity,
+ filter.authors?.length && filter.kinds?.every((kind) => isReplaceableKind(kind)) ? filter.authors.length * filter.kinds.length : Infinity,
+ filter.authors?.length && filter.kinds?.every((kind) => isAddressableKind(kind)) && filter["#d"]?.length ? filter.authors.length * filter.kinds.length * filter["#d"].length : Infinity
+ );
+ }
+
+ // fakejson.ts
+ var fakejson_exports = {};
+ __export(fakejson_exports, {
+ getHex64: () => getHex64,
+ getInt: () => getInt,
+ getSubscriptionId: () => getSubscriptionId,
+ matchEventId: () => matchEventId,
+ matchEventKind: () => matchEventKind,
+ matchEventPubkey: () => matchEventPubkey
+ });
+ function getHex64(json, field) {
+ let len = field.length + 3;
+ let idx = json.indexOf(`"${field}":`) + len;
+ let s = json.slice(idx).indexOf(`"`) + idx + 1;
+ return json.slice(s, s + 64);
+ }
+ function getInt(json, field) {
+ let len = field.length;
+ let idx = json.indexOf(`"${field}":`) + len + 3;
+ let sliced = json.slice(idx);
+ let end = Math.min(sliced.indexOf(","), sliced.indexOf("}"));
+ return parseInt(sliced.slice(0, end), 10);
+ }
+ function getSubscriptionId(json) {
+ let idx = json.slice(0, 22).indexOf(`"EVENT"`);
+ if (idx === -1)
+ return null;
+ let pstart = json.slice(idx + 7 + 1).indexOf(`"`);
+ if (pstart === -1)
+ return null;
+ let start = idx + 7 + 1 + pstart;
+ let pend = json.slice(start + 1, 80).indexOf(`"`);
+ if (pend === -1)
+ return null;
+ let end = start + 1 + pend;
+ return json.slice(start + 1, end);
+ }
+ function matchEventId(json, id) {
+ return id === getHex64(json, "id");
+ }
+ function matchEventPubkey(json, pubkey) {
+ return pubkey === getHex64(json, "pubkey");
+ }
+ function matchEventKind(json, kind) {
+ return kind === getInt(json, "kind");
+ }
+
+ // nip42.ts
+ var nip42_exports = {};
+ __export(nip42_exports, {
+ makeAuthEvent: () => makeAuthEvent
+ });
+ function makeAuthEvent(relayURL, challenge2) {
+ return {
+ kind: ClientAuth,
+ created_at: Math.floor(Date.now() / 1e3),
+ tags: [
+ ["relay", relayURL],
+ ["challenge", challenge2]
+ ],
+ content: ""
+ };
+ }
+
+ // helpers.ts
+ async function yieldThread() {
+ return new Promise((resolve) => {
+ const ch = new MessageChannel();
+ const handler = () => {
+ ch.port1.removeEventListener("message", handler);
+ resolve();
+ };
+ ch.port1.addEventListener("message", handler);
+ ch.port2.postMessage(0);
+ ch.port1.start();
+ });
+ }
+ var alwaysTrue = (t) => {
+ t[verifiedSymbol] = true;
+ return true;
+ };
+
+ // abstract-relay.ts
+ var SendingOnClosedConnection = class extends Error {
+ constructor(message, relay) {
+ super(`Tried to send message '${message} on a closed connection to ${relay}.`);
+ this.name = "SendingOnClosedConnection";
+ }
+ };
+ var AbstractRelay = class {
+ url;
+ _connected = false;
+ onclose = null;
+ onnotice = (msg) => console.debug(`NOTICE from ${this.url}: ${msg}`);
+ baseEoseTimeout = 4400;
+ connectionTimeout = 4400;
+ publishTimeout = 4400;
+ pingFrequency = 2e4;
+ pingTimeout = 2e4;
+ openSubs = /* @__PURE__ */ new Map();
+ enablePing;
+ connectionTimeoutHandle;
+ connectionPromise;
+ openCountRequests = /* @__PURE__ */ new Map();
+ openEventPublishes = /* @__PURE__ */ new Map();
+ ws;
+ incomingMessageQueue = new Queue();
+ queueRunning = false;
+ challenge;
+ authPromise;
+ serial = 0;
+ verifyEvent;
+ _WebSocket;
+ constructor(url, opts) {
+ this.url = normalizeURL(url);
+ this.verifyEvent = opts.verifyEvent;
+ this._WebSocket = opts.websocketImplementation || WebSocket;
+ this.enablePing = opts.enablePing;
+ }
+ static async connect(url, opts) {
+ const relay = new AbstractRelay(url, opts);
+ await relay.connect();
+ return relay;
+ }
+ closeAllSubscriptions(reason) {
+ for (let [_, sub] of this.openSubs) {
+ sub.close(reason);
+ }
+ this.openSubs.clear();
+ for (let [_, ep] of this.openEventPublishes) {
+ ep.reject(new Error(reason));
+ }
+ this.openEventPublishes.clear();
+ for (let [_, cr] of this.openCountRequests) {
+ cr.reject(new Error(reason));
+ }
+ this.openCountRequests.clear();
+ }
+ get connected() {
+ return this._connected;
+ }
+ async connect() {
+ if (this.connectionPromise)
+ return this.connectionPromise;
+ this.challenge = void 0;
+ this.authPromise = void 0;
+ this.connectionPromise = new Promise((resolve, reject) => {
+ this.connectionTimeoutHandle = setTimeout(() => {
+ reject("connection timed out");
+ this.connectionPromise = void 0;
+ this.onclose?.();
+ this.closeAllSubscriptions("relay connection timed out");
+ }, this.connectionTimeout);
+ try {
+ this.ws = new this._WebSocket(this.url);
+ } catch (err) {
+ clearTimeout(this.connectionTimeoutHandle);
+ reject(err);
+ return;
+ }
+ this.ws.onopen = () => {
+ clearTimeout(this.connectionTimeoutHandle);
+ this._connected = true;
+ if (this.enablePing) {
+ this.pingpong();
+ }
+ resolve();
+ };
+ this.ws.onerror = (ev) => {
+ clearTimeout(this.connectionTimeoutHandle);
+ reject(ev.message || "websocket error");
+ if (this._connected) {
+ this._connected = false;
+ this.connectionPromise = void 0;
+ this.onclose?.();
+ this.closeAllSubscriptions("relay connection errored");
+ }
+ };
+ this.ws.onclose = (ev) => {
+ clearTimeout(this.connectionTimeoutHandle);
+ reject(ev.message || "websocket closed");
+ if (this._connected) {
+ this._connected = false;
+ this.connectionPromise = void 0;
+ this.onclose?.();
+ this.closeAllSubscriptions("relay connection closed");
+ }
+ };
+ this.ws.onmessage = this._onmessage.bind(this);
+ });
+ return this.connectionPromise;
+ }
+ async waitForPingPong() {
+ return new Promise((res, err) => {
+ ;
+ this.ws && this.ws.on && this.ws.on("pong", () => res(true)) || err("ws can't listen for pong");
+ this.ws && this.ws.ping && this.ws.ping();
+ });
+ }
+ async waitForDummyReq() {
+ return new Promise((resolve, _) => {
+ const sub = this.subscribe([{ ids: ["a".repeat(64)] }], {
+ oneose: () => {
+ sub.close();
+ resolve(true);
+ },
+ eoseTimeout: this.pingTimeout + 1e3
+ });
+ });
+ }
+ async pingpong() {
+ if (this.ws?.readyState === 1) {
+ const result = await Promise.any([
+ this.ws && this.ws.ping && this.ws.on ? this.waitForPingPong() : this.waitForDummyReq(),
+ new Promise((res) => setTimeout(() => res(false), this.pingTimeout))
+ ]);
+ if (result) {
+ setTimeout(() => this.pingpong(), this.pingFrequency);
+ } else {
+ this.closeAllSubscriptions("pingpong timed out");
+ this._connected = false;
+ this.ws?.close();
+ this.onclose?.();
+ }
+ }
+ }
+ async runQueue() {
+ this.queueRunning = true;
+ while (true) {
+ if (false === this.handleNext()) {
+ break;
+ }
+ await yieldThread();
+ }
+ this.queueRunning = false;
+ }
+ handleNext() {
+ const json = this.incomingMessageQueue.dequeue();
+ if (!json) {
+ return false;
+ }
+ const subid = getSubscriptionId(json);
+ if (subid) {
+ const so = this.openSubs.get(subid);
+ if (!so) {
+ return;
+ }
+ const id = getHex64(json, "id");
+ const alreadyHave = so.alreadyHaveEvent?.(id);
+ so.receivedEvent?.(this, id);
+ if (alreadyHave) {
+ return;
+ }
+ }
+ try {
+ let data = JSON.parse(json);
+ switch (data[0]) {
+ case "EVENT": {
+ const so = this.openSubs.get(data[1]);
+ const event = data[2];
+ if (this.verifyEvent(event) && matchFilters(so.filters, event)) {
+ so.onevent(event);
+ }
+ return;
+ }
+ case "COUNT": {
+ const id = data[1];
+ const payload = data[2];
+ const cr = this.openCountRequests.get(id);
+ if (cr) {
+ cr.resolve(payload.count);
+ this.openCountRequests.delete(id);
+ }
+ return;
+ }
+ case "EOSE": {
+ const so = this.openSubs.get(data[1]);
+ if (!so)
+ return;
+ so.receivedEose();
+ return;
+ }
+ case "OK": {
+ const id = data[1];
+ const ok = data[2];
+ const reason = data[3];
+ const ep = this.openEventPublishes.get(id);
+ if (ep) {
+ clearTimeout(ep.timeout);
+ if (ok)
+ ep.resolve(reason);
+ else
+ ep.reject(new Error(reason));
+ this.openEventPublishes.delete(id);
+ }
+ return;
+ }
+ case "CLOSED": {
+ const id = data[1];
+ const so = this.openSubs.get(id);
+ if (!so)
+ return;
+ so.closed = true;
+ so.close(data[2]);
+ return;
+ }
+ case "NOTICE":
+ this.onnotice(data[1]);
+ return;
+ case "AUTH": {
+ this.challenge = data[1];
+ return;
+ }
+ }
+ } catch (err) {
+ return;
+ }
+ }
+ async send(message) {
+ if (!this.connectionPromise)
+ throw new SendingOnClosedConnection(message, this.url);
+ this.connectionPromise.then(() => {
+ this.ws?.send(message);
+ });
+ }
+ async auth(signAuthEvent) {
+ const challenge2 = this.challenge;
+ if (!challenge2)
+ throw new Error("can't perform auth, no challenge was received");
+ if (this.authPromise)
+ return this.authPromise;
+ this.authPromise = new Promise(async (resolve, reject) => {
+ try {
+ let evt = await signAuthEvent(makeAuthEvent(this.url, challenge2));
+ let timeout = setTimeout(() => {
+ let ep = this.openEventPublishes.get(evt.id);
+ if (ep) {
+ ep.reject(new Error("auth timed out"));
+ this.openEventPublishes.delete(evt.id);
+ }
+ }, this.publishTimeout);
+ this.openEventPublishes.set(evt.id, { resolve, reject, timeout });
+ this.send('["AUTH",' + JSON.stringify(evt) + "]");
+ } catch (err) {
+ console.warn("subscribe auth function failed:", err);
+ }
+ });
+ return this.authPromise;
+ }
+ async publish(event) {
+ const ret = new Promise((resolve, reject) => {
+ const timeout = setTimeout(() => {
+ const ep = this.openEventPublishes.get(event.id);
+ if (ep) {
+ ep.reject(new Error("publish timed out"));
+ this.openEventPublishes.delete(event.id);
+ }
+ }, this.publishTimeout);
+ this.openEventPublishes.set(event.id, { resolve, reject, timeout });
+ });
+ this.send('["EVENT",' + JSON.stringify(event) + "]");
+ return ret;
+ }
+ async count(filters, params) {
+ this.serial++;
+ const id = params?.id || "count:" + this.serial;
+ const ret = new Promise((resolve, reject) => {
+ this.openCountRequests.set(id, { resolve, reject });
+ });
+ this.send('["COUNT","' + id + '",' + JSON.stringify(filters).substring(1));
+ return ret;
+ }
+ subscribe(filters, params) {
+ const subscription = this.prepareSubscription(filters, params);
+ subscription.fire();
+ return subscription;
+ }
+ prepareSubscription(filters, params) {
+ this.serial++;
+ const id = params.id || (params.label ? params.label + ":" : "sub:") + this.serial;
+ const subscription = new Subscription(this, id, filters, params);
+ this.openSubs.set(id, subscription);
+ return subscription;
+ }
+ close() {
+ this.closeAllSubscriptions("relay connection closed by us");
+ this._connected = false;
+ this.ws?.close();
+ this.onclose?.();
+ }
+ _onmessage(ev) {
+ this.incomingMessageQueue.enqueue(ev.data);
+ if (!this.queueRunning) {
+ this.runQueue();
+ }
+ }
+ };
+ var Subscription = class {
+ relay;
+ id;
+ closed = false;
+ eosed = false;
+ filters;
+ alreadyHaveEvent;
+ receivedEvent;
+ onevent;
+ oneose;
+ onclose;
+ eoseTimeout;
+ eoseTimeoutHandle;
+ constructor(relay, id, filters, params) {
+ this.relay = relay;
+ this.filters = filters;
+ this.id = id;
+ this.alreadyHaveEvent = params.alreadyHaveEvent;
+ this.receivedEvent = params.receivedEvent;
+ this.eoseTimeout = params.eoseTimeout || relay.baseEoseTimeout;
+ this.oneose = params.oneose;
+ this.onclose = params.onclose;
+ this.onevent = params.onevent || ((event) => {
+ console.warn(
+ `onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`,
+ event
+ );
+ });
+ }
+ fire() {
+ this.relay.send('["REQ","' + this.id + '",' + JSON.stringify(this.filters).substring(1));
+ this.eoseTimeoutHandle = setTimeout(this.receivedEose.bind(this), this.eoseTimeout);
+ }
+ receivedEose() {
+ if (this.eosed)
+ return;
+ clearTimeout(this.eoseTimeoutHandle);
+ this.eosed = true;
+ this.oneose?.();
+ }
+ close(reason = "closed by caller") {
+ if (!this.closed && this.relay.connected) {
+ try {
+ this.relay.send('["CLOSE",' + JSON.stringify(this.id) + "]");
+ } catch (err) {
+ if (err instanceof SendingOnClosedConnection) {
+ } else {
+ throw err;
+ }
+ }
+ this.closed = true;
+ }
+ this.relay.openSubs.delete(this.id);
+ this.onclose?.(reason);
+ }
+ };
+
+ // relay.ts
+ var _WebSocket;
+ try {
+ _WebSocket = WebSocket;
+ } catch {
+ }
+ var Relay = class extends AbstractRelay {
+ constructor(url) {
+ super(url, { verifyEvent, websocketImplementation: _WebSocket });
+ }
+ static async connect(url) {
+ const relay = new Relay(url);
+ await relay.connect();
+ return relay;
+ }
+ };
+
+ // abstract-pool.ts
+ var AbstractSimplePool = class {
+ relays = /* @__PURE__ */ new Map();
+ seenOn = /* @__PURE__ */ new Map();
+ trackRelays = false;
+ verifyEvent;
+ enablePing;
+ trustedRelayURLs = /* @__PURE__ */ new Set();
+ _WebSocket;
+ constructor(opts) {
+ this.verifyEvent = opts.verifyEvent;
+ this._WebSocket = opts.websocketImplementation;
+ this.enablePing = opts.enablePing;
+ }
+ async ensureRelay(url, params) {
+ url = normalizeURL(url);
+ let relay = this.relays.get(url);
+ if (!relay) {
+ relay = new AbstractRelay(url, {
+ verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent,
+ websocketImplementation: this._WebSocket,
+ enablePing: this.enablePing
+ });
+ relay.onclose = () => {
+ this.relays.delete(url);
+ };
+ if (params?.connectionTimeout)
+ relay.connectionTimeout = params.connectionTimeout;
+ this.relays.set(url, relay);
+ }
+ await relay.connect();
+ return relay;
+ }
+ close(relays) {
+ relays.map(normalizeURL).forEach((url) => {
+ this.relays.get(url)?.close();
+ this.relays.delete(url);
+ });
+ }
+ subscribe(relays, filter, params) {
+ params.onauth = params.onauth || params.doauth;
+ const request = [];
+ for (let i2 = 0; i2 < relays.length; i2++) {
+ const url = normalizeURL(relays[i2]);
+ if (!request.find((r) => r.url === url)) {
+ request.push({ url, filter });
+ }
+ }
+ return this.subscribeMap(request, params);
+ }
+ subscribeMany(relays, filters, params) {
+ params.onauth = params.onauth || params.doauth;
+ const request = [];
+ const uniqUrls = [];
+ for (let i2 = 0; i2 < relays.length; i2++) {
+ const url = normalizeURL(relays[i2]);
+ if (uniqUrls.indexOf(url) === -1) {
+ for (let f = 0; f < filters.length; f++) {
+ request.push({ url, filter: filters[f] });
+ }
+ }
+ }
+ return this.subscribeMap(request, params);
+ }
+ subscribeMap(requests, params) {
+ params.onauth = params.onauth || params.doauth;
+ if (this.trackRelays) {
+ params.receivedEvent = (relay, id) => {
+ let set = this.seenOn.get(id);
+ if (!set) {
+ set = /* @__PURE__ */ new Set();
+ this.seenOn.set(id, set);
+ }
+ set.add(relay);
+ };
+ }
+ const _knownIds = /* @__PURE__ */ new Set();
+ const subs = [];
+ const eosesReceived = [];
+ let handleEose = (i2) => {
+ if (eosesReceived[i2])
+ return;
+ eosesReceived[i2] = true;
+ if (eosesReceived.filter((a) => a).length === requests.length) {
+ params.oneose?.();
+ handleEose = () => {
+ };
+ }
+ };
+ const closesReceived = [];
+ let handleClose = (i2, reason) => {
+ if (closesReceived[i2])
+ return;
+ handleEose(i2);
+ closesReceived[i2] = reason;
+ if (closesReceived.filter((a) => a).length === requests.length) {
+ params.onclose?.(closesReceived);
+ handleClose = () => {
+ };
+ }
+ };
+ const localAlreadyHaveEventHandler = (id) => {
+ if (params.alreadyHaveEvent?.(id)) {
+ return true;
+ }
+ const have = _knownIds.has(id);
+ _knownIds.add(id);
+ return have;
+ };
+ const allOpened = Promise.all(
+ requests.map(async ({ url, filter }, i2) => {
+ let relay;
+ try {
+ relay = await this.ensureRelay(url, {
+ connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0
+ });
+ } catch (err) {
+ handleClose(i2, err?.message || String(err));
+ return;
+ }
+ let subscription = relay.subscribe([filter], {
+ ...params,
+ oneose: () => handleEose(i2),
+ onclose: (reason) => {
+ if (reason.startsWith("auth-required: ") && params.onauth) {
+ relay.auth(params.onauth).then(() => {
+ relay.subscribe([filter], {
+ ...params,
+ oneose: () => handleEose(i2),
+ onclose: (reason2) => {
+ handleClose(i2, reason2);
+ },
+ alreadyHaveEvent: localAlreadyHaveEventHandler,
+ eoseTimeout: params.maxWait
+ });
+ }).catch((err) => {
+ handleClose(i2, `auth was required and attempted, but failed with: ${err}`);
+ });
+ } else {
+ handleClose(i2, reason);
+ }
+ },
+ alreadyHaveEvent: localAlreadyHaveEventHandler,
+ eoseTimeout: params.maxWait
+ });
+ subs.push(subscription);
+ })
+ );
+ return {
+ async close(reason) {
+ await allOpened;
+ subs.forEach((sub) => {
+ sub.close(reason);
+ });
+ }
+ };
+ }
+ subscribeEose(relays, filter, params) {
+ params.onauth = params.onauth || params.doauth;
+ const subcloser = this.subscribe(relays, filter, {
+ ...params,
+ oneose() {
+ subcloser.close("closed automatically on eose");
+ }
+ });
+ return subcloser;
+ }
+ subscribeManyEose(relays, filters, params) {
+ params.onauth = params.onauth || params.doauth;
+ const subcloser = this.subscribeMany(relays, filters, {
+ ...params,
+ oneose() {
+ subcloser.close("closed automatically on eose");
+ }
+ });
+ return subcloser;
+ }
+ async querySync(relays, filter, params) {
+ return new Promise(async (resolve) => {
+ const events = [];
+ this.subscribeEose(relays, filter, {
+ ...params,
+ onevent(event) {
+ events.push(event);
+ },
+ onclose(_) {
+ resolve(events);
+ }
+ });
+ });
+ }
+ async get(relays, filter, params) {
+ filter.limit = 1;
+ const events = await this.querySync(relays, filter, params);
+ events.sort((a, b) => b.created_at - a.created_at);
+ return events[0] || null;
+ }
+ publish(relays, event, options) {
+ return relays.map(normalizeURL).map(async (url, i2, arr) => {
+ if (arr.indexOf(url) !== i2) {
+ return Promise.reject("duplicate url");
+ }
+ let r = await this.ensureRelay(url);
+ return r.publish(event).catch(async (err) => {
+ if (err instanceof Error && err.message.startsWith("auth-required: ") && options?.onauth) {
+ await r.auth(options.onauth);
+ return r.publish(event);
+ }
+ throw err;
+ }).then((reason) => {
+ if (this.trackRelays) {
+ let set = this.seenOn.get(event.id);
+ if (!set) {
+ set = /* @__PURE__ */ new Set();
+ this.seenOn.set(event.id, set);
+ }
+ set.add(r);
+ }
+ return reason;
+ });
+ });
+ }
+ listConnectionStatus() {
+ const map = /* @__PURE__ */ new Map();
+ this.relays.forEach((relay, url) => map.set(url, relay.connected));
+ return map;
+ }
+ destroy() {
+ this.relays.forEach((conn) => conn.close());
+ this.relays = /* @__PURE__ */ new Map();
+ }
+ };
+
+ // pool.ts
+ var _WebSocket2;
+ try {
+ _WebSocket2 = WebSocket;
+ } catch {
+ }
+ var SimplePool = class extends AbstractSimplePool {
+ constructor(options) {
+ super({ verifyEvent, websocketImplementation: _WebSocket2, ...options });
+ }
+ };
+
+ // nip19.ts
+ var nip19_exports = {};
+ __export(nip19_exports, {
+ BECH32_REGEX: () => BECH32_REGEX,
+ Bech32MaxSize: () => Bech32MaxSize,
+ NostrTypeGuard: () => NostrTypeGuard,
+ decode: () => decode,
+ decodeNostrURI: () => decodeNostrURI,
+ encodeBytes: () => encodeBytes,
+ naddrEncode: () => naddrEncode,
+ neventEncode: () => neventEncode,
+ noteEncode: () => noteEncode,
+ nprofileEncode: () => nprofileEncode,
+ npubEncode: () => npubEncode,
+ nsecEncode: () => nsecEncode
+ });
+
+ // node_modules/@scure/base/lib/esm/index.js
+ function assertNumber(n) {
+ if (!Number.isSafeInteger(n))
+ throw new Error(`Wrong integer: ${n}`);
+ }
+ function chain(...args) {
+ const wrap = (a, b) => (c) => a(b(c));
+ const encode = Array.from(args).reverse().reduce((acc, i2) => acc ? wrap(acc, i2.encode) : i2.encode, void 0);
+ const decode2 = args.reduce((acc, i2) => acc ? wrap(acc, i2.decode) : i2.decode, void 0);
+ return { encode, decode: decode2 };
+ }
+ function alphabet(alphabet2) {
+ return {
+ encode: (digits) => {
+ if (!Array.isArray(digits) || digits.length && typeof digits[0] !== "number")
+ throw new Error("alphabet.encode input should be an array of numbers");
+ return digits.map((i2) => {
+ assertNumber(i2);
+ if (i2 < 0 || i2 >= alphabet2.length)
+ throw new Error(`Digit index outside alphabet: ${i2} (alphabet: ${alphabet2.length})`);
+ return alphabet2[i2];
+ });
+ },
+ decode: (input) => {
+ if (!Array.isArray(input) || input.length && typeof input[0] !== "string")
+ throw new Error("alphabet.decode input should be array of strings");
+ return input.map((letter) => {
+ if (typeof letter !== "string")
+ throw new Error(`alphabet.decode: not string element=${letter}`);
+ const index = alphabet2.indexOf(letter);
+ if (index === -1)
+ throw new Error(`Unknown letter: "${letter}". Allowed: ${alphabet2}`);
+ return index;
+ });
+ }
+ };
+ }
+ function join(separator = "") {
+ if (typeof separator !== "string")
+ throw new Error("join separator should be string");
+ return {
+ encode: (from) => {
+ if (!Array.isArray(from) || from.length && typeof from[0] !== "string")
+ throw new Error("join.encode input should be array of strings");
+ for (let i2 of from)
+ if (typeof i2 !== "string")
+ throw new Error(`join.encode: non-string input=${i2}`);
+ return from.join(separator);
+ },
+ decode: (to) => {
+ if (typeof to !== "string")
+ throw new Error("join.decode input should be string");
+ return to.split(separator);
+ }
+ };
+ }
+ function padding(bits, chr = "=") {
+ assertNumber(bits);
+ if (typeof chr !== "string")
+ throw new Error("padding chr should be string");
+ return {
+ encode(data) {
+ if (!Array.isArray(data) || data.length && typeof data[0] !== "string")
+ throw new Error("padding.encode input should be array of strings");
+ for (let i2 of data)
+ if (typeof i2 !== "string")
+ throw new Error(`padding.encode: non-string input=${i2}`);
+ while (data.length * bits % 8)
+ data.push(chr);
+ return data;
+ },
+ decode(input) {
+ if (!Array.isArray(input) || input.length && typeof input[0] !== "string")
+ throw new Error("padding.encode input should be array of strings");
+ for (let i2 of input)
+ if (typeof i2 !== "string")
+ throw new Error(`padding.decode: non-string input=${i2}`);
+ let end = input.length;
+ if (end * bits % 8)
+ throw new Error("Invalid padding: string should have whole number of bytes");
+ for (; end > 0 && input[end - 1] === chr; end--) {
+ if (!((end - 1) * bits % 8))
+ throw new Error("Invalid padding: string has too much padding");
+ }
+ return input.slice(0, end);
+ }
+ };
+ }
+ function normalize(fn) {
+ if (typeof fn !== "function")
+ throw new Error("normalize fn should be function");
+ return { encode: (from) => from, decode: (to) => fn(to) };
+ }
+ function convertRadix(data, from, to) {
+ if (from < 2)
+ throw new Error(`convertRadix: wrong from=${from}, base cannot be less than 2`);
+ if (to < 2)
+ throw new Error(`convertRadix: wrong to=${to}, base cannot be less than 2`);
+ if (!Array.isArray(data))
+ throw new Error("convertRadix: data should be array");
+ if (!data.length)
+ return [];
+ let pos = 0;
+ const res = [];
+ const digits = Array.from(data);
+ digits.forEach((d) => {
+ assertNumber(d);
+ if (d < 0 || d >= from)
+ throw new Error(`Wrong integer: ${d}`);
+ });
+ while (true) {
+ let carry = 0;
+ let done = true;
+ for (let i2 = pos; i2 < digits.length; i2++) {
+ const digit = digits[i2];
+ const digitBase = from * carry + digit;
+ if (!Number.isSafeInteger(digitBase) || from * carry / from !== carry || digitBase - digit !== from * carry) {
+ throw new Error("convertRadix: carry overflow");
+ }
+ carry = digitBase % to;
+ digits[i2] = Math.floor(digitBase / to);
+ if (!Number.isSafeInteger(digits[i2]) || digits[i2] * to + carry !== digitBase)
+ throw new Error("convertRadix: carry overflow");
+ if (!done)
+ continue;
+ else if (!digits[i2])
+ pos = i2;
+ else
+ done = false;
+ }
+ res.push(carry);
+ if (done)
+ break;
+ }
+ for (let i2 = 0; i2 < data.length - 1 && data[i2] === 0; i2++)
+ res.push(0);
+ return res.reverse();
+ }
+ var gcd = (a, b) => !b ? a : gcd(b, a % b);
+ var radix2carry = (from, to) => from + (to - gcd(from, to));
+ function convertRadix2(data, from, to, padding2) {
+ if (!Array.isArray(data))
+ throw new Error("convertRadix2: data should be array");
+ if (from <= 0 || from > 32)
+ throw new Error(`convertRadix2: wrong from=${from}`);
+ if (to <= 0 || to > 32)
+ throw new Error(`convertRadix2: wrong to=${to}`);
+ if (radix2carry(from, to) > 32) {
+ throw new Error(`convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}`);
+ }
+ let carry = 0;
+ let pos = 0;
+ const mask = 2 ** to - 1;
+ const res = [];
+ for (const n of data) {
+ assertNumber(n);
+ if (n >= 2 ** from)
+ throw new Error(`convertRadix2: invalid data word=${n} from=${from}`);
+ carry = carry << from | n;
+ if (pos + from > 32)
+ throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`);
+ pos += from;
+ for (; pos >= to; pos -= to)
+ res.push((carry >> pos - to & mask) >>> 0);
+ carry &= 2 ** pos - 1;
+ }
+ carry = carry << to - pos & mask;
+ if (!padding2 && pos >= from)
+ throw new Error("Excess padding");
+ if (!padding2 && carry)
+ throw new Error(`Non-zero padding: ${carry}`);
+ if (padding2 && pos > 0)
+ res.push(carry >>> 0);
+ return res;
+ }
+ function radix(num) {
+ assertNumber(num);
+ return {
+ encode: (bytes4) => {
+ if (!(bytes4 instanceof Uint8Array))
+ throw new Error("radix.encode input should be Uint8Array");
+ return convertRadix(Array.from(bytes4), 2 ** 8, num);
+ },
+ decode: (digits) => {
+ if (!Array.isArray(digits) || digits.length && typeof digits[0] !== "number")
+ throw new Error("radix.decode input should be array of strings");
+ return Uint8Array.from(convertRadix(digits, num, 2 ** 8));
+ }
+ };
+ }
+ function radix2(bits, revPadding = false) {
+ assertNumber(bits);
+ if (bits <= 0 || bits > 32)
+ throw new Error("radix2: bits should be in (0..32]");
+ if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32)
+ throw new Error("radix2: carry overflow");
+ return {
+ encode: (bytes4) => {
+ if (!(bytes4 instanceof Uint8Array))
+ throw new Error("radix2.encode input should be Uint8Array");
+ return convertRadix2(Array.from(bytes4), 8, bits, !revPadding);
+ },
+ decode: (digits) => {
+ if (!Array.isArray(digits) || digits.length && typeof digits[0] !== "number")
+ throw new Error("radix2.decode input should be array of strings");
+ return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding));
+ }
+ };
+ }
+ function unsafeWrapper(fn) {
+ if (typeof fn !== "function")
+ throw new Error("unsafeWrapper fn should be function");
+ return function(...args) {
+ try {
+ return fn.apply(null, args);
+ } catch (e) {
+ }
+ };
+ }
+ var base16 = chain(radix2(4), alphabet("0123456789ABCDEF"), join(""));
+ var base32 = chain(radix2(5), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), padding(5), join(""));
+ var base32hex = chain(radix2(5), alphabet("0123456789ABCDEFGHIJKLMNOPQRSTUV"), padding(5), join(""));
+ var base32crockford = chain(radix2(5), alphabet("0123456789ABCDEFGHJKMNPQRSTVWXYZ"), join(""), normalize((s) => s.toUpperCase().replace(/O/g, "0").replace(/[IL]/g, "1")));
+ var base64 = chain(radix2(6), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), padding(6), join(""));
+ var base64url = chain(radix2(6), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"), padding(6), join(""));
+ var genBase58 = (abc) => chain(radix(58), alphabet(abc), join(""));
+ var base58 = genBase58("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
+ var base58flickr = genBase58("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ");
+ var base58xrp = genBase58("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz");
+ var XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11];
+ var base58xmr = {
+ encode(data) {
+ let res = "";
+ for (let i2 = 0; i2 < data.length; i2 += 8) {
+ const block = data.subarray(i2, i2 + 8);
+ res += base58.encode(block).padStart(XMR_BLOCK_LEN[block.length], "1");
+ }
+ return res;
+ },
+ decode(str) {
+ let res = [];
+ for (let i2 = 0; i2 < str.length; i2 += 11) {
+ const slice = str.slice(i2, i2 + 11);
+ const blockLen = XMR_BLOCK_LEN.indexOf(slice.length);
+ const block = base58.decode(slice);
+ for (let j = 0; j < block.length - blockLen; j++) {
+ if (block[j] !== 0)
+ throw new Error("base58xmr: wrong padding");
+ }
+ res = res.concat(Array.from(block.slice(block.length - blockLen)));
+ }
+ return Uint8Array.from(res);
+ }
+ };
+ var BECH_ALPHABET = chain(alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), join(""));
+ var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059];
+ function bech32Polymod(pre) {
+ const b = pre >> 25;
+ let chk = (pre & 33554431) << 5;
+ for (let i2 = 0; i2 < POLYMOD_GENERATORS.length; i2++) {
+ if ((b >> i2 & 1) === 1)
+ chk ^= POLYMOD_GENERATORS[i2];
+ }
+ return chk;
+ }
+ function bechChecksum(prefix, words, encodingConst = 1) {
+ const len = prefix.length;
+ let chk = 1;
+ for (let i2 = 0; i2 < len; i2++) {
+ const c = prefix.charCodeAt(i2);
+ if (c < 33 || c > 126)
+ throw new Error(`Invalid prefix (${prefix})`);
+ chk = bech32Polymod(chk) ^ c >> 5;
+ }
+ chk = bech32Polymod(chk);
+ for (let i2 = 0; i2 < len; i2++)
+ chk = bech32Polymod(chk) ^ prefix.charCodeAt(i2) & 31;
+ for (let v of words)
+ chk = bech32Polymod(chk) ^ v;
+ for (let i2 = 0; i2 < 6; i2++)
+ chk = bech32Polymod(chk);
+ chk ^= encodingConst;
+ return BECH_ALPHABET.encode(convertRadix2([chk % 2 ** 30], 30, 5, false));
+ }
+ function genBech32(encoding) {
+ const ENCODING_CONST = encoding === "bech32" ? 1 : 734539939;
+ const _words = radix2(5);
+ const fromWords = _words.decode;
+ const toWords = _words.encode;
+ const fromWordsUnsafe = unsafeWrapper(fromWords);
+ function encode(prefix, words, limit2 = 90) {
+ if (typeof prefix !== "string")
+ throw new Error(`bech32.encode prefix should be string, not ${typeof prefix}`);
+ if (!Array.isArray(words) || words.length && typeof words[0] !== "number")
+ throw new Error(`bech32.encode words should be array of numbers, not ${typeof words}`);
+ const actualLength = prefix.length + 7 + words.length;
+ if (limit2 !== false && actualLength > limit2)
+ throw new TypeError(`Length ${actualLength} exceeds limit ${limit2}`);
+ prefix = prefix.toLowerCase();
+ return `${prefix}1${BECH_ALPHABET.encode(words)}${bechChecksum(prefix, words, ENCODING_CONST)}`;
+ }
+ function decode2(str, limit2 = 90) {
+ if (typeof str !== "string")
+ throw new Error(`bech32.decode input should be string, not ${typeof str}`);
+ if (str.length < 8 || limit2 !== false && str.length > limit2)
+ throw new TypeError(`Wrong string length: ${str.length} (${str}). Expected (8..${limit2})`);
+ const lowered = str.toLowerCase();
+ if (str !== lowered && str !== str.toUpperCase())
+ throw new Error(`String must be lowercase or uppercase`);
+ str = lowered;
+ const sepIndex = str.lastIndexOf("1");
+ if (sepIndex === 0 || sepIndex === -1)
+ throw new Error(`Letter "1" must be present between prefix and data only`);
+ const prefix = str.slice(0, sepIndex);
+ const _words2 = str.slice(sepIndex + 1);
+ if (_words2.length < 6)
+ throw new Error("Data must be at least 6 characters long");
+ const words = BECH_ALPHABET.decode(_words2).slice(0, -6);
+ const sum = bechChecksum(prefix, words, ENCODING_CONST);
+ if (!_words2.endsWith(sum))
+ throw new Error(`Invalid checksum in ${str}: expected "${sum}"`);
+ return { prefix, words };
+ }
+ const decodeUnsafe = unsafeWrapper(decode2);
+ function decodeToBytes(str) {
+ const { prefix, words } = decode2(str, false);
+ return { prefix, words, bytes: fromWords(words) };
+ }
+ return { encode, decode: decode2, decodeToBytes, decodeUnsafe, fromWords, fromWordsUnsafe, toWords };
+ }
+ var bech32 = genBech32("bech32");
+ var bech32m = genBech32("bech32m");
+ var utf8 = {
+ encode: (data) => new TextDecoder().decode(data),
+ decode: (str) => new TextEncoder().encode(str)
+ };
+ var hex = chain(radix2(4), alphabet("0123456789abcdef"), join(""), normalize((s) => {
+ if (typeof s !== "string" || s.length % 2)
+ throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`);
+ return s.toLowerCase();
+ }));
+ var CODERS = {
+ utf8,
+ hex,
+ base16,
+ base32,
+ base64,
+ base64url,
+ base58,
+ base58xmr
+ };
+ var coderTypeError = `Invalid encoding type. Available types: ${Object.keys(CODERS).join(", ")}`;
+
+ // nip19.ts
+ var NostrTypeGuard = {
+ isNProfile: (value) => /^nprofile1[a-z\d]+$/.test(value || ""),
+ isNEvent: (value) => /^nevent1[a-z\d]+$/.test(value || ""),
+ isNAddr: (value) => /^naddr1[a-z\d]+$/.test(value || ""),
+ isNSec: (value) => /^nsec1[a-z\d]{58}$/.test(value || ""),
+ isNPub: (value) => /^npub1[a-z\d]{58}$/.test(value || ""),
+ isNote: (value) => /^note1[a-z\d]+$/.test(value || ""),
+ isNcryptsec: (value) => /^ncryptsec1[a-z\d]+$/.test(value || "")
+ };
+ var Bech32MaxSize = 5e3;
+ var BECH32_REGEX = /[\x21-\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/;
+ function integerToUint8Array(number4) {
+ const uint8Array = new Uint8Array(4);
+ uint8Array[0] = number4 >> 24 & 255;
+ uint8Array[1] = number4 >> 16 & 255;
+ uint8Array[2] = number4 >> 8 & 255;
+ uint8Array[3] = number4 & 255;
+ return uint8Array;
+ }
+ function decodeNostrURI(nip19code) {
+ try {
+ if (nip19code.startsWith("nostr:"))
+ nip19code = nip19code.substring(6);
+ return decode(nip19code);
+ } catch (_err) {
+ return { type: "invalid", data: null };
+ }
+ }
+ function decode(code) {
+ let { prefix, words } = bech32.decode(code, Bech32MaxSize);
+ let data = new Uint8Array(bech32.fromWords(words));
+ switch (prefix) {
+ case "nprofile": {
+ let tlv = parseTLV(data);
+ if (!tlv[0]?.[0])
+ throw new Error("missing TLV 0 for nprofile");
+ if (tlv[0][0].length !== 32)
+ throw new Error("TLV 0 should be 32 bytes");
+ return {
+ type: "nprofile",
+ data: {
+ pubkey: bytesToHex2(tlv[0][0]),
+ relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []
+ }
+ };
+ }
+ case "nevent": {
+ let tlv = parseTLV(data);
+ if (!tlv[0]?.[0])
+ throw new Error("missing TLV 0 for nevent");
+ if (tlv[0][0].length !== 32)
+ throw new Error("TLV 0 should be 32 bytes");
+ if (tlv[2] && tlv[2][0].length !== 32)
+ throw new Error("TLV 2 should be 32 bytes");
+ if (tlv[3] && tlv[3][0].length !== 4)
+ throw new Error("TLV 3 should be 4 bytes");
+ return {
+ type: "nevent",
+ data: {
+ id: bytesToHex2(tlv[0][0]),
+ relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : [],
+ author: tlv[2]?.[0] ? bytesToHex2(tlv[2][0]) : void 0,
+ kind: tlv[3]?.[0] ? parseInt(bytesToHex2(tlv[3][0]), 16) : void 0
+ }
+ };
+ }
+ case "naddr": {
+ let tlv = parseTLV(data);
+ if (!tlv[0]?.[0])
+ throw new Error("missing TLV 0 for naddr");
+ if (!tlv[2]?.[0])
+ throw new Error("missing TLV 2 for naddr");
+ if (tlv[2][0].length !== 32)
+ throw new Error("TLV 2 should be 32 bytes");
+ if (!tlv[3]?.[0])
+ throw new Error("missing TLV 3 for naddr");
+ if (tlv[3][0].length !== 4)
+ throw new Error("TLV 3 should be 4 bytes");
+ return {
+ type: "naddr",
+ data: {
+ identifier: utf8Decoder.decode(tlv[0][0]),
+ pubkey: bytesToHex2(tlv[2][0]),
+ kind: parseInt(bytesToHex2(tlv[3][0]), 16),
+ relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []
+ }
+ };
+ }
+ case "nsec":
+ return { type: prefix, data };
+ case "npub":
+ case "note":
+ return { type: prefix, data: bytesToHex2(data) };
+ default:
+ throw new Error(`unknown prefix ${prefix}`);
+ }
+ }
+ function parseTLV(data) {
+ let result = {};
+ let rest = data;
+ while (rest.length > 0) {
+ let t = rest[0];
+ let l = rest[1];
+ let v = rest.slice(2, 2 + l);
+ rest = rest.slice(2 + l);
+ if (v.length < l)
+ throw new Error(`not enough data to read on TLV ${t}`);
+ result[t] = result[t] || [];
+ result[t].push(v);
+ }
+ return result;
+ }
+ function nsecEncode(key) {
+ return encodeBytes("nsec", key);
+ }
+ function npubEncode(hex2) {
+ return encodeBytes("npub", hexToBytes2(hex2));
+ }
+ function noteEncode(hex2) {
+ return encodeBytes("note", hexToBytes2(hex2));
+ }
+ function encodeBech32(prefix, data) {
+ let words = bech32.toWords(data);
+ return bech32.encode(prefix, words, Bech32MaxSize);
+ }
+ function encodeBytes(prefix, bytes4) {
+ return encodeBech32(prefix, bytes4);
+ }
+ function nprofileEncode(profile) {
+ let data = encodeTLV({
+ 0: [hexToBytes2(profile.pubkey)],
+ 1: (profile.relays || []).map((url) => utf8Encoder.encode(url))
+ });
+ return encodeBech32("nprofile", data);
+ }
+ function neventEncode(event) {
+ let kindArray;
+ if (event.kind !== void 0) {
+ kindArray = integerToUint8Array(event.kind);
+ }
+ let data = encodeTLV({
+ 0: [hexToBytes2(event.id)],
+ 1: (event.relays || []).map((url) => utf8Encoder.encode(url)),
+ 2: event.author ? [hexToBytes2(event.author)] : [],
+ 3: kindArray ? [new Uint8Array(kindArray)] : []
+ });
+ return encodeBech32("nevent", data);
+ }
+ function naddrEncode(addr) {
+ let kind = new ArrayBuffer(4);
+ new DataView(kind).setUint32(0, addr.kind, false);
+ let data = encodeTLV({
+ 0: [utf8Encoder.encode(addr.identifier)],
+ 1: (addr.relays || []).map((url) => utf8Encoder.encode(url)),
+ 2: [hexToBytes2(addr.pubkey)],
+ 3: [new Uint8Array(kind)]
+ });
+ return encodeBech32("naddr", data);
+ }
+ function encodeTLV(tlv) {
+ let entries = [];
+ Object.entries(tlv).reverse().forEach(([t, vs]) => {
+ vs.forEach((v) => {
+ let entry = new Uint8Array(v.length + 2);
+ entry.set([parseInt(t)], 0);
+ entry.set([v.length], 1);
+ entry.set(v, 2);
+ entries.push(entry);
+ });
+ });
+ return concatBytes3(...entries);
+ }
+
+ // references.ts
+ var mentionRegex = /\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b|#\[(\d+)\]/g;
+ function parseReferences(evt) {
+ let references = [];
+ for (let ref of evt.content.matchAll(mentionRegex)) {
+ if (ref[2]) {
+ try {
+ let { type, data } = decode(ref[1]);
+ switch (type) {
+ case "npub": {
+ references.push({
+ text: ref[0],
+ profile: { pubkey: data, relays: [] }
+ });
+ break;
+ }
+ case "nprofile": {
+ references.push({
+ text: ref[0],
+ profile: data
+ });
+ break;
+ }
+ case "note": {
+ references.push({
+ text: ref[0],
+ event: { id: data, relays: [] }
+ });
+ break;
+ }
+ case "nevent": {
+ references.push({
+ text: ref[0],
+ event: data
+ });
+ break;
+ }
+ case "naddr": {
+ references.push({
+ text: ref[0],
+ address: data
+ });
+ break;
+ }
+ }
+ } catch (err) {
+ }
+ } else if (ref[3]) {
+ let idx = parseInt(ref[3], 10);
+ let tag = evt.tags[idx];
+ if (!tag)
+ continue;
+ switch (tag[0]) {
+ case "p": {
+ references.push({
+ text: ref[0],
+ profile: { pubkey: tag[1], relays: tag[2] ? [tag[2]] : [] }
+ });
+ break;
+ }
+ case "e": {
+ references.push({
+ text: ref[0],
+ event: { id: tag[1], relays: tag[2] ? [tag[2]] : [] }
+ });
+ break;
+ }
+ case "a": {
+ try {
+ let [kind, pubkey, identifier] = tag[1].split(":");
+ references.push({
+ text: ref[0],
+ address: {
+ identifier,
+ pubkey,
+ kind: parseInt(kind, 10),
+ relays: tag[2] ? [tag[2]] : []
+ }
+ });
+ } catch (err) {
+ }
+ break;
+ }
+ }
+ }
+ }
+ return references;
+ }
+
+ // nip04.ts
+ var nip04_exports = {};
+ __export(nip04_exports, {
+ decrypt: () => decrypt2,
+ encrypt: () => encrypt2
+ });
+
+ // node_modules/@noble/ciphers/esm/_assert.js
+ function number3(n) {
+ if (!Number.isSafeInteger(n) || n < 0)
+ throw new Error(`positive integer expected, not ${n}`);
+ }
+ function bool2(b) {
+ if (typeof b !== "boolean")
+ throw new Error(`boolean expected, not ${b}`);
+ }
+ function isBytes(a) {
+ return a instanceof Uint8Array || a != null && typeof a === "object" && a.constructor.name === "Uint8Array";
+ }
+ function bytes3(b, ...lengths) {
+ if (!isBytes(b))
+ throw new Error("Uint8Array expected");
+ if (lengths.length > 0 && !lengths.includes(b.length))
+ throw new Error(`Uint8Array expected of length ${lengths}, not of length=${b.length}`);
+ }
+ function exists3(instance, checkFinished = true) {
+ if (instance.destroyed)
+ throw new Error("Hash instance has been destroyed");
+ if (checkFinished && instance.finished)
+ throw new Error("Hash#digest() has already been called");
+ }
+ function output3(out, instance) {
+ bytes3(out);
+ const min = instance.outputLen;
+ if (out.length < min) {
+ throw new Error(`digestInto() expects output buffer of length at least ${min}`);
+ }
+ }
+
+ // node_modules/@noble/ciphers/esm/utils.js
+ var u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
+ var u32 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
+ var createView3 = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
+ var isLE3 = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
+ if (!isLE3)
+ throw new Error("Non little-endian hardware is not supported");
+ function utf8ToBytes4(str) {
+ if (typeof str !== "string")
+ throw new Error(`string expected, got ${typeof str}`);
+ return new Uint8Array(new TextEncoder().encode(str));
+ }
+ function toBytes3(data) {
+ if (typeof data === "string")
+ data = utf8ToBytes4(data);
+ else if (isBytes(data))
+ data = data.slice();
+ else
+ throw new Error(`Uint8Array expected, got ${typeof data}`);
+ return data;
+ }
+ function checkOpts(defaults, opts) {
+ if (opts == null || typeof opts !== "object")
+ throw new Error("options must be defined");
+ const merged = Object.assign(defaults, opts);
+ return merged;
+ }
+ function equalBytes2(a, b) {
+ if (a.length !== b.length)
+ return false;
+ let diff = 0;
+ for (let i2 = 0; i2 < a.length; i2++)
+ diff |= a[i2] ^ b[i2];
+ return diff === 0;
+ }
+ var wrapCipher = (params, c) => {
+ Object.assign(c, params);
+ return c;
+ };
+ function setBigUint643(view, byteOffset, value, isLE4) {
+ if (typeof view.setBigUint64 === "function")
+ return view.setBigUint64(byteOffset, value, isLE4);
+ const _32n = BigInt(32);
+ const _u32_max = BigInt(4294967295);
+ const wh = Number(value >> _32n & _u32_max);
+ const wl = Number(value & _u32_max);
+ const h = isLE4 ? 4 : 0;
+ const l = isLE4 ? 0 : 4;
+ view.setUint32(byteOffset + h, wh, isLE4);
+ view.setUint32(byteOffset + l, wl, isLE4);
+ }
+
+ // node_modules/@noble/ciphers/esm/_polyval.js
+ var BLOCK_SIZE = 16;
+ var ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
+ var ZEROS32 = u32(ZEROS16);
+ var POLY = 225;
+ var mul2 = (s0, s1, s2, s3) => {
+ const hiBit = s3 & 1;
+ return {
+ s3: s2 << 31 | s3 >>> 1,
+ s2: s1 << 31 | s2 >>> 1,
+ s1: s0 << 31 | s1 >>> 1,
+ s0: s0 >>> 1 ^ POLY << 24 & -(hiBit & 1)
+ };
+ };
+ var swapLE = (n) => (n >>> 0 & 255) << 24 | (n >>> 8 & 255) << 16 | (n >>> 16 & 255) << 8 | n >>> 24 & 255 | 0;
+ function _toGHASHKey(k) {
+ k.reverse();
+ const hiBit = k[15] & 1;
+ let carry = 0;
+ for (let i2 = 0; i2 < k.length; i2++) {
+ const t = k[i2];
+ k[i2] = t >>> 1 | carry;
+ carry = (t & 1) << 7;
+ }
+ k[0] ^= -hiBit & 225;
+ return k;
+ }
+ var estimateWindow = (bytes4) => {
+ if (bytes4 > 64 * 1024)
+ return 8;
+ if (bytes4 > 1024)
+ return 4;
+ return 2;
+ };
+ var GHASH = class {
+ constructor(key, expectedLength) {
+ this.blockLen = BLOCK_SIZE;
+ this.outputLen = BLOCK_SIZE;
+ this.s0 = 0;
+ this.s1 = 0;
+ this.s2 = 0;
+ this.s3 = 0;
+ this.finished = false;
+ key = toBytes3(key);
+ bytes3(key, 16);
+ const kView = createView3(key);
+ let k0 = kView.getUint32(0, false);
+ let k1 = kView.getUint32(4, false);
+ let k2 = kView.getUint32(8, false);
+ let k3 = kView.getUint32(12, false);
+ const doubles = [];
+ for (let i2 = 0; i2 < 128; i2++) {
+ doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
+ ({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2(k0, k1, k2, k3));
+ }
+ const W = estimateWindow(expectedLength || 1024);
+ if (![1, 2, 4, 8].includes(W))
+ throw new Error(`ghash: wrong window size=${W}, should be 2, 4 or 8`);
+ this.W = W;
+ const bits = 128;
+ const windows = bits / W;
+ const windowSize = this.windowSize = 2 ** W;
+ const items = [];
+ for (let w = 0; w < windows; w++) {
+ for (let byte = 0; byte < windowSize; byte++) {
+ let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
+ for (let j = 0; j < W; j++) {
+ const bit = byte >>> W - j - 1 & 1;
+ if (!bit)
+ continue;
+ const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
+ s0 ^= d0, s1 ^= d1, s2 ^= d2, s3 ^= d3;
+ }
+ items.push({ s0, s1, s2, s3 });
+ }
+ }
+ this.t = items;
+ }
+ _updateBlock(s0, s1, s2, s3) {
+ s0 ^= this.s0, s1 ^= this.s1, s2 ^= this.s2, s3 ^= this.s3;
+ const { W, t, windowSize } = this;
+ let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
+ const mask = (1 << W) - 1;
+ let w = 0;
+ for (const num of [s0, s1, s2, s3]) {
+ for (let bytePos = 0; bytePos < 4; bytePos++) {
+ const byte = num >>> 8 * bytePos & 255;
+ for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
+ const bit = byte >>> W * bitPos & mask;
+ const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
+ o0 ^= e0, o1 ^= e1, o2 ^= e2, o3 ^= e3;
+ w += 1;
+ }
+ }
+ }
+ this.s0 = o0;
+ this.s1 = o1;
+ this.s2 = o2;
+ this.s3 = o3;
+ }
+ update(data) {
+ data = toBytes3(data);
+ exists3(this);
+ const b32 = u32(data);
+ const blocks = Math.floor(data.length / BLOCK_SIZE);
+ const left = data.length % BLOCK_SIZE;
+ for (let i2 = 0; i2 < blocks; i2++) {
+ this._updateBlock(b32[i2 * 4 + 0], b32[i2 * 4 + 1], b32[i2 * 4 + 2], b32[i2 * 4 + 3]);
+ }
+ if (left) {
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
+ this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
+ ZEROS32.fill(0);
+ }
+ return this;
+ }
+ destroy() {
+ const { t } = this;
+ for (const elm of t) {
+ elm.s0 = 0, elm.s1 = 0, elm.s2 = 0, elm.s3 = 0;
+ }
+ }
+ digestInto(out) {
+ exists3(this);
+ output3(out, this);
+ this.finished = true;
+ const { s0, s1, s2, s3 } = this;
+ const o32 = u32(out);
+ o32[0] = s0;
+ o32[1] = s1;
+ o32[2] = s2;
+ o32[3] = s3;
+ return out;
+ }
+ digest() {
+ const res = new Uint8Array(BLOCK_SIZE);
+ this.digestInto(res);
+ this.destroy();
+ return res;
+ }
+ };
+ var Polyval = class extends GHASH {
+ constructor(key, expectedLength) {
+ key = toBytes3(key);
+ const ghKey = _toGHASHKey(key.slice());
+ super(ghKey, expectedLength);
+ ghKey.fill(0);
+ }
+ update(data) {
+ data = toBytes3(data);
+ exists3(this);
+ const b32 = u32(data);
+ const left = data.length % BLOCK_SIZE;
+ const blocks = Math.floor(data.length / BLOCK_SIZE);
+ for (let i2 = 0; i2 < blocks; i2++) {
+ this._updateBlock(swapLE(b32[i2 * 4 + 3]), swapLE(b32[i2 * 4 + 2]), swapLE(b32[i2 * 4 + 1]), swapLE(b32[i2 * 4 + 0]));
+ }
+ if (left) {
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
+ this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
+ ZEROS32.fill(0);
+ }
+ return this;
+ }
+ digestInto(out) {
+ exists3(this);
+ output3(out, this);
+ this.finished = true;
+ const { s0, s1, s2, s3 } = this;
+ const o32 = u32(out);
+ o32[0] = s0;
+ o32[1] = s1;
+ o32[2] = s2;
+ o32[3] = s3;
+ return out.reverse();
+ }
+ };
+ function wrapConstructorWithKey(hashCons) {
+ const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes3(msg)).digest();
+ const tmp = hashCons(new Uint8Array(16), 0);
+ hashC.outputLen = tmp.outputLen;
+ hashC.blockLen = tmp.blockLen;
+ hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
+ return hashC;
+ }
+ var ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
+ var polyval = wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
+
+ // node_modules/@noble/ciphers/esm/aes.js
+ var BLOCK_SIZE2 = 16;
+ var BLOCK_SIZE32 = 4;
+ var EMPTY_BLOCK = new Uint8Array(BLOCK_SIZE2);
+ var POLY2 = 283;
+ function mul22(n) {
+ return n << 1 ^ POLY2 & -(n >> 7);
+ }
+ function mul(a, b) {
+ let res = 0;
+ for (; b > 0; b >>= 1) {
+ res ^= a & -(b & 1);
+ a = mul22(a);
+ }
+ return res;
+ }
+ var sbox = /* @__PURE__ */ (() => {
+ let t = new Uint8Array(256);
+ for (let i2 = 0, x = 1; i2 < 256; i2++, x ^= mul22(x))
+ t[i2] = x;
+ const box = new Uint8Array(256);
+ box[0] = 99;
+ for (let i2 = 0; i2 < 255; i2++) {
+ let x = t[255 - i2];
+ x |= x << 8;
+ box[t[i2]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
+ }
+ return box;
+ })();
+ var invSbox = /* @__PURE__ */ sbox.map((_, j) => sbox.indexOf(j));
+ var rotr32_8 = (n) => n << 24 | n >>> 8;
+ var rotl32_8 = (n) => n << 8 | n >>> 24;
+ function genTtable(sbox2, fn) {
+ if (sbox2.length !== 256)
+ throw new Error("Wrong sbox length");
+ const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
+ const T1 = T0.map(rotl32_8);
+ const T2 = T1.map(rotl32_8);
+ const T3 = T2.map(rotl32_8);
+ const T01 = new Uint32Array(256 * 256);
+ const T23 = new Uint32Array(256 * 256);
+ const sbox22 = new Uint16Array(256 * 256);
+ for (let i2 = 0; i2 < 256; i2++) {
+ for (let j = 0; j < 256; j++) {
+ const idx = i2 * 256 + j;
+ T01[idx] = T0[i2] ^ T1[j];
+ T23[idx] = T2[i2] ^ T3[j];
+ sbox22[idx] = sbox2[i2] << 8 | sbox2[j];
+ }
+ }
+ return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
+ }
+ var tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2));
+ var tableDecoding = /* @__PURE__ */ genTtable(invSbox, (s) => mul(s, 11) << 24 | mul(s, 13) << 16 | mul(s, 9) << 8 | mul(s, 14));
+ var xPowers = /* @__PURE__ */ (() => {
+ const p = new Uint8Array(16);
+ for (let i2 = 0, x = 1; i2 < 16; i2++, x = mul22(x))
+ p[i2] = x;
+ return p;
+ })();
+ function expandKeyLE(key) {
+ bytes3(key);
+ const len = key.length;
+ if (![16, 24, 32].includes(len))
+ throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${len}`);
+ const { sbox2 } = tableEncoding;
+ const k32 = u32(key);
+ const Nk = k32.length;
+ const subByte = (n) => applySbox(sbox2, n, n, n, n);
+ const xk = new Uint32Array(len + 28);
+ xk.set(k32);
+ for (let i2 = Nk; i2 < xk.length; i2++) {
+ let t = xk[i2 - 1];
+ if (i2 % Nk === 0)
+ t = subByte(rotr32_8(t)) ^ xPowers[i2 / Nk - 1];
+ else if (Nk > 6 && i2 % Nk === 4)
+ t = subByte(t);
+ xk[i2] = xk[i2 - Nk] ^ t;
+ }
+ return xk;
+ }
+ function expandKeyDecLE(key) {
+ const encKey = expandKeyLE(key);
+ const xk = encKey.slice();
+ const Nk = encKey.length;
+ const { sbox2 } = tableEncoding;
+ const { T0, T1, T2, T3 } = tableDecoding;
+ for (let i2 = 0; i2 < Nk; i2 += 4) {
+ for (let j = 0; j < 4; j++)
+ xk[i2 + j] = encKey[Nk - i2 - 4 + j];
+ }
+ encKey.fill(0);
+ for (let i2 = 4; i2 < Nk - 4; i2++) {
+ const x = xk[i2];
+ const w = applySbox(sbox2, x, x, x, x);
+ xk[i2] = T0[w & 255] ^ T1[w >>> 8 & 255] ^ T2[w >>> 16 & 255] ^ T3[w >>> 24];
+ }
+ return xk;
+ }
+ function apply0123(T01, T23, s0, s1, s2, s3) {
+ return T01[s0 << 8 & 65280 | s1 >>> 8 & 255] ^ T23[s2 >>> 8 & 65280 | s3 >>> 24 & 255];
+ }
+ function applySbox(sbox2, s0, s1, s2, s3) {
+ return sbox2[s0 & 255 | s1 & 65280] | sbox2[s2 >>> 16 & 255 | s3 >>> 16 & 65280] << 16;
+ }
+ function encrypt(xk, s0, s1, s2, s3) {
+ const { sbox2, T01, T23 } = tableEncoding;
+ let k = 0;
+ s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++];
+ const rounds = xk.length / 4 - 2;
+ for (let i2 = 0; i2 < rounds; i2++) {
+ const t02 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
+ const t12 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
+ const t22 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
+ const t32 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
+ s0 = t02, s1 = t12, s2 = t22, s3 = t32;
+ }
+ const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
+ const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
+ const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
+ const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
+ return { s0: t0, s1: t1, s2: t2, s3: t3 };
+ }
+ function decrypt(xk, s0, s1, s2, s3) {
+ const { sbox2, T01, T23 } = tableDecoding;
+ let k = 0;
+ s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++];
+ const rounds = xk.length / 4 - 2;
+ for (let i2 = 0; i2 < rounds; i2++) {
+ const t02 = xk[k++] ^ apply0123(T01, T23, s0, s3, s2, s1);
+ const t12 = xk[k++] ^ apply0123(T01, T23, s1, s0, s3, s2);
+ const t22 = xk[k++] ^ apply0123(T01, T23, s2, s1, s0, s3);
+ const t32 = xk[k++] ^ apply0123(T01, T23, s3, s2, s1, s0);
+ s0 = t02, s1 = t12, s2 = t22, s3 = t32;
+ }
+ const t0 = xk[k++] ^ applySbox(sbox2, s0, s3, s2, s1);
+ const t1 = xk[k++] ^ applySbox(sbox2, s1, s0, s3, s2);
+ const t2 = xk[k++] ^ applySbox(sbox2, s2, s1, s0, s3);
+ const t3 = xk[k++] ^ applySbox(sbox2, s3, s2, s1, s0);
+ return { s0: t0, s1: t1, s2: t2, s3: t3 };
+ }
+ function getDst(len, dst) {
+ if (!dst)
+ return new Uint8Array(len);
+ bytes3(dst);
+ if (dst.length < len)
+ throw new Error(`aes: wrong destination length, expected at least ${len}, got: ${dst.length}`);
+ return dst;
+ }
+ function ctrCounter(xk, nonce, src, dst) {
+ bytes3(nonce, BLOCK_SIZE2);
+ bytes3(src);
+ const srcLen = src.length;
+ dst = getDst(srcLen, dst);
+ const ctr3 = nonce;
+ const c32 = u32(ctr3);
+ let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
+ const src32 = u32(src);
+ const dst32 = u32(dst);
+ for (let i2 = 0; i2 + 4 <= src32.length; i2 += 4) {
+ dst32[i2 + 0] = src32[i2 + 0] ^ s0;
+ dst32[i2 + 1] = src32[i2 + 1] ^ s1;
+ dst32[i2 + 2] = src32[i2 + 2] ^ s2;
+ dst32[i2 + 3] = src32[i2 + 3] ^ s3;
+ let carry = 1;
+ for (let i3 = ctr3.length - 1; i3 >= 0; i3--) {
+ carry = carry + (ctr3[i3] & 255) | 0;
+ ctr3[i3] = carry & 255;
+ carry >>>= 8;
+ }
+ ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
+ }
+ const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32);
+ if (start < srcLen) {
+ const b32 = new Uint32Array([s0, s1, s2, s3]);
+ const buf = u8(b32);
+ for (let i2 = start, pos = 0; i2 < srcLen; i2++, pos++)
+ dst[i2] = src[i2] ^ buf[pos];
+ }
+ return dst;
+ }
+ function ctr32(xk, isLE4, nonce, src, dst) {
+ bytes3(nonce, BLOCK_SIZE2);
+ bytes3(src);
+ dst = getDst(src.length, dst);
+ const ctr3 = nonce;
+ const c32 = u32(ctr3);
+ const view = createView3(ctr3);
+ const src32 = u32(src);
+ const dst32 = u32(dst);
+ const ctrPos = isLE4 ? 0 : 12;
+ const srcLen = src.length;
+ let ctrNum = view.getUint32(ctrPos, isLE4);
+ let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
+ for (let i2 = 0; i2 + 4 <= src32.length; i2 += 4) {
+ dst32[i2 + 0] = src32[i2 + 0] ^ s0;
+ dst32[i2 + 1] = src32[i2 + 1] ^ s1;
+ dst32[i2 + 2] = src32[i2 + 2] ^ s2;
+ dst32[i2 + 3] = src32[i2 + 3] ^ s3;
+ ctrNum = ctrNum + 1 >>> 0;
+ view.setUint32(ctrPos, ctrNum, isLE4);
+ ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
+ }
+ const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32);
+ if (start < srcLen) {
+ const b32 = new Uint32Array([s0, s1, s2, s3]);
+ const buf = u8(b32);
+ for (let i2 = start, pos = 0; i2 < srcLen; i2++, pos++)
+ dst[i2] = src[i2] ^ buf[pos];
+ }
+ return dst;
+ }
+ var ctr = wrapCipher({ blockSize: 16, nonceLength: 16 }, function ctr2(key, nonce) {
+ bytes3(key);
+ bytes3(nonce, BLOCK_SIZE2);
+ function processCtr(buf, dst) {
+ const xk = expandKeyLE(key);
+ const n = nonce.slice();
+ const out = ctrCounter(xk, n, buf, dst);
+ xk.fill(0);
+ n.fill(0);
+ return out;
+ }
+ return {
+ encrypt: (plaintext, dst) => processCtr(plaintext, dst),
+ decrypt: (ciphertext, dst) => processCtr(ciphertext, dst)
+ };
+ });
+ function validateBlockDecrypt(data) {
+ bytes3(data);
+ if (data.length % BLOCK_SIZE2 !== 0) {
+ throw new Error(`aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size ${BLOCK_SIZE2}`);
+ }
+ }
+ function validateBlockEncrypt(plaintext, pcks5, dst) {
+ let outLen = plaintext.length;
+ const remaining = outLen % BLOCK_SIZE2;
+ if (!pcks5 && remaining !== 0)
+ throw new Error("aec/(cbc-ecb): unpadded plaintext with disabled padding");
+ const b = u32(plaintext);
+ if (pcks5) {
+ let left = BLOCK_SIZE2 - remaining;
+ if (!left)
+ left = BLOCK_SIZE2;
+ outLen = outLen + left;
+ }
+ const out = getDst(outLen, dst);
+ const o = u32(out);
+ return { b, o, out };
+ }
+ function validatePCKS(data, pcks5) {
+ if (!pcks5)
+ return data;
+ const len = data.length;
+ if (!len)
+ throw new Error(`aes/pcks5: empty ciphertext not allowed`);
+ const lastByte = data[len - 1];
+ if (lastByte <= 0 || lastByte > 16)
+ throw new Error(`aes/pcks5: wrong padding byte: ${lastByte}`);
+ const out = data.subarray(0, -lastByte);
+ for (let i2 = 0; i2 < lastByte; i2++)
+ if (data[len - i2 - 1] !== lastByte)
+ throw new Error(`aes/pcks5: wrong padding`);
+ return out;
+ }
+ function padPCKS(left) {
+ const tmp = new Uint8Array(16);
+ const tmp32 = u32(tmp);
+ tmp.set(left);
+ const paddingByte = BLOCK_SIZE2 - left.length;
+ for (let i2 = BLOCK_SIZE2 - paddingByte; i2 < BLOCK_SIZE2; i2++)
+ tmp[i2] = paddingByte;
+ return tmp32;
+ }
+ var ecb = wrapCipher({ blockSize: 16 }, function ecb2(key, opts = {}) {
+ bytes3(key);
+ const pcks5 = !opts.disablePadding;
+ return {
+ encrypt: (plaintext, dst) => {
+ bytes3(plaintext);
+ const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
+ const xk = expandKeyLE(key);
+ let i2 = 0;
+ for (; i2 + 4 <= b.length; ) {
+ const { s0, s1, s2, s3 } = encrypt(xk, b[i2 + 0], b[i2 + 1], b[i2 + 2], b[i2 + 3]);
+ o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3;
+ }
+ if (pcks5) {
+ const tmp32 = padPCKS(plaintext.subarray(i2 * 4));
+ const { s0, s1, s2, s3 } = encrypt(xk, tmp32[0], tmp32[1], tmp32[2], tmp32[3]);
+ o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3;
+ }
+ xk.fill(0);
+ return _out;
+ },
+ decrypt: (ciphertext, dst) => {
+ validateBlockDecrypt(ciphertext);
+ const xk = expandKeyDecLE(key);
+ const out = getDst(ciphertext.length, dst);
+ const b = u32(ciphertext);
+ const o = u32(out);
+ for (let i2 = 0; i2 + 4 <= b.length; ) {
+ const { s0, s1, s2, s3 } = decrypt(xk, b[i2 + 0], b[i2 + 1], b[i2 + 2], b[i2 + 3]);
+ o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3;
+ }
+ xk.fill(0);
+ return validatePCKS(out, pcks5);
+ }
+ };
+ });
+ var cbc = wrapCipher({ blockSize: 16, nonceLength: 16 }, function cbc2(key, iv, opts = {}) {
+ bytes3(key);
+ bytes3(iv, 16);
+ const pcks5 = !opts.disablePadding;
+ return {
+ encrypt: (plaintext, dst) => {
+ const xk = expandKeyLE(key);
+ const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
+ const n32 = u32(iv);
+ let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
+ let i2 = 0;
+ for (; i2 + 4 <= b.length; ) {
+ s0 ^= b[i2 + 0], s1 ^= b[i2 + 1], s2 ^= b[i2 + 2], s3 ^= b[i2 + 3];
+ ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
+ o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3;
+ }
+ if (pcks5) {
+ const tmp32 = padPCKS(plaintext.subarray(i2 * 4));
+ s0 ^= tmp32[0], s1 ^= tmp32[1], s2 ^= tmp32[2], s3 ^= tmp32[3];
+ ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
+ o[i2++] = s0, o[i2++] = s1, o[i2++] = s2, o[i2++] = s3;
+ }
+ xk.fill(0);
+ return _out;
+ },
+ decrypt: (ciphertext, dst) => {
+ validateBlockDecrypt(ciphertext);
+ const xk = expandKeyDecLE(key);
+ const n32 = u32(iv);
+ const out = getDst(ciphertext.length, dst);
+ const b = u32(ciphertext);
+ const o = u32(out);
+ let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
+ for (let i2 = 0; i2 + 4 <= b.length; ) {
+ const ps0 = s0, ps1 = s1, ps2 = s2, ps3 = s3;
+ s0 = b[i2 + 0], s1 = b[i2 + 1], s2 = b[i2 + 2], s3 = b[i2 + 3];
+ const { s0: o0, s1: o1, s2: o2, s3: o3 } = decrypt(xk, s0, s1, s2, s3);
+ o[i2++] = o0 ^ ps0, o[i2++] = o1 ^ ps1, o[i2++] = o2 ^ ps2, o[i2++] = o3 ^ ps3;
+ }
+ xk.fill(0);
+ return validatePCKS(out, pcks5);
+ }
+ };
+ });
+ function computeTag(fn, isLE4, key, data, AAD) {
+ const h = fn.create(key, data.length + (AAD?.length || 0));
+ if (AAD)
+ h.update(AAD);
+ h.update(data);
+ const num = new Uint8Array(16);
+ const view = createView3(num);
+ if (AAD)
+ setBigUint643(view, 0, BigInt(AAD.length * 8), isLE4);
+ setBigUint643(view, 8, BigInt(data.length * 8), isLE4);
+ h.update(num);
+ return h.digest();
+ }
+ var gcm = wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function gcm2(key, nonce, AAD) {
+ bytes3(nonce);
+ if (nonce.length === 0)
+ throw new Error("aes/gcm: empty nonce");
+ const tagLength = 16;
+ function _computeTag(authKey, tagMask, data) {
+ const tag = computeTag(ghash, false, authKey, data, AAD);
+ for (let i2 = 0; i2 < tagMask.length; i2++)
+ tag[i2] ^= tagMask[i2];
+ return tag;
+ }
+ function deriveKeys() {
+ const xk = expandKeyLE(key);
+ const authKey = EMPTY_BLOCK.slice();
+ const counter = EMPTY_BLOCK.slice();
+ ctr32(xk, false, counter, counter, authKey);
+ if (nonce.length === 12) {
+ counter.set(nonce);
+ } else {
+ const nonceLen = EMPTY_BLOCK.slice();
+ const view = createView3(nonceLen);
+ setBigUint643(view, 8, BigInt(nonce.length * 8), false);
+ ghash.create(authKey).update(nonce).update(nonceLen).digestInto(counter);
+ }
+ const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
+ return { xk, authKey, counter, tagMask };
+ }
+ return {
+ encrypt: (plaintext) => {
+ bytes3(plaintext);
+ const { xk, authKey, counter, tagMask } = deriveKeys();
+ const out = new Uint8Array(plaintext.length + tagLength);
+ ctr32(xk, false, counter, plaintext, out);
+ const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
+ out.set(tag, plaintext.length);
+ xk.fill(0);
+ return out;
+ },
+ decrypt: (ciphertext) => {
+ bytes3(ciphertext);
+ if (ciphertext.length < tagLength)
+ throw new Error(`aes/gcm: ciphertext less than tagLen (${tagLength})`);
+ const { xk, authKey, counter, tagMask } = deriveKeys();
+ const data = ciphertext.subarray(0, -tagLength);
+ const passedTag = ciphertext.subarray(-tagLength);
+ const tag = _computeTag(authKey, tagMask, data);
+ if (!equalBytes2(tag, passedTag))
+ throw new Error("aes/gcm: invalid ghash tag");
+ const out = ctr32(xk, false, counter, data);
+ authKey.fill(0);
+ tagMask.fill(0);
+ xk.fill(0);
+ return out;
+ }
+ };
+ });
+ var limit = (name, min, max) => (value) => {
+ if (!Number.isSafeInteger(value) || min > value || value > max)
+ throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`);
+ };
+ var siv = wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function siv2(key, nonce, AAD) {
+ const tagLength = 16;
+ const AAD_LIMIT = limit("AAD", 0, 2 ** 36);
+ const PLAIN_LIMIT = limit("plaintext", 0, 2 ** 36);
+ const NONCE_LIMIT = limit("nonce", 12, 12);
+ const CIPHER_LIMIT = limit("ciphertext", 16, 2 ** 36 + 16);
+ bytes3(nonce);
+ NONCE_LIMIT(nonce.length);
+ if (AAD) {
+ bytes3(AAD);
+ AAD_LIMIT(AAD.length);
+ }
+ function deriveKeys() {
+ const len = key.length;
+ if (len !== 16 && len !== 24 && len !== 32)
+ throw new Error(`key length must be 16, 24 or 32 bytes, got: ${len} bytes`);
+ const xk = expandKeyLE(key);
+ const encKey = new Uint8Array(len);
+ const authKey = new Uint8Array(16);
+ const n32 = u32(nonce);
+ let s0 = 0, s1 = n32[0], s2 = n32[1], s3 = n32[2];
+ let counter = 0;
+ for (const derivedKey of [authKey, encKey].map(u32)) {
+ const d32 = u32(derivedKey);
+ for (let i2 = 0; i2 < d32.length; i2 += 2) {
+ const { s0: o0, s1: o1 } = encrypt(xk, s0, s1, s2, s3);
+ d32[i2 + 0] = o0;
+ d32[i2 + 1] = o1;
+ s0 = ++counter;
+ }
+ }
+ xk.fill(0);
+ return { authKey, encKey: expandKeyLE(encKey) };
+ }
+ function _computeTag(encKey, authKey, data) {
+ const tag = computeTag(polyval, true, authKey, data, AAD);
+ for (let i2 = 0; i2 < 12; i2++)
+ tag[i2] ^= nonce[i2];
+ tag[15] &= 127;
+ const t32 = u32(tag);
+ let s0 = t32[0], s1 = t32[1], s2 = t32[2], s3 = t32[3];
+ ({ s0, s1, s2, s3 } = encrypt(encKey, s0, s1, s2, s3));
+ t32[0] = s0, t32[1] = s1, t32[2] = s2, t32[3] = s3;
+ return tag;
+ }
+ function processSiv(encKey, tag, input) {
+ let block = tag.slice();
+ block[15] |= 128;
+ return ctr32(encKey, true, block, input);
+ }
+ return {
+ encrypt: (plaintext) => {
+ bytes3(plaintext);
+ PLAIN_LIMIT(plaintext.length);
+ const { encKey, authKey } = deriveKeys();
+ const tag = _computeTag(encKey, authKey, plaintext);
+ const out = new Uint8Array(plaintext.length + tagLength);
+ out.set(tag, plaintext.length);
+ out.set(processSiv(encKey, tag, plaintext));
+ encKey.fill(0);
+ authKey.fill(0);
+ return out;
+ },
+ decrypt: (ciphertext) => {
+ bytes3(ciphertext);
+ CIPHER_LIMIT(ciphertext.length);
+ const tag = ciphertext.subarray(-tagLength);
+ const { encKey, authKey } = deriveKeys();
+ const plaintext = processSiv(encKey, tag, ciphertext.subarray(0, -tagLength));
+ const expectedTag = _computeTag(encKey, authKey, plaintext);
+ encKey.fill(0);
+ authKey.fill(0);
+ if (!equalBytes2(tag, expectedTag))
+ throw new Error("invalid polyval tag");
+ return plaintext;
+ }
+ };
+ });
+
+ // nip04.ts
+ function encrypt2(secretKey, pubkey, text) {
+ const privkey = secretKey instanceof Uint8Array ? bytesToHex2(secretKey) : secretKey;
+ const key = secp256k1.getSharedSecret(privkey, "02" + pubkey);
+ const normalizedKey = getNormalizedX(key);
+ let iv = Uint8Array.from(randomBytes2(16));
+ let plaintext = utf8Encoder.encode(text);
+ let ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);
+ let ctb64 = base64.encode(new Uint8Array(ciphertext));
+ let ivb64 = base64.encode(new Uint8Array(iv.buffer));
+ return `${ctb64}?iv=${ivb64}`;
+ }
+ function decrypt2(secretKey, pubkey, data) {
+ const privkey = secretKey instanceof Uint8Array ? bytesToHex2(secretKey) : secretKey;
+ let [ctb64, ivb64] = data.split("?iv=");
+ let key = secp256k1.getSharedSecret(privkey, "02" + pubkey);
+ let normalizedKey = getNormalizedX(key);
+ let iv = base64.decode(ivb64);
+ let ciphertext = base64.decode(ctb64);
+ let plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);
+ return utf8Decoder.decode(plaintext);
+ }
+ function getNormalizedX(key) {
+ return key.slice(1, 33);
+ }
+
+ // nip05.ts
+ var nip05_exports = {};
+ __export(nip05_exports, {
+ NIP05_REGEX: () => NIP05_REGEX,
+ isNip05: () => isNip05,
+ isValid: () => isValid,
+ queryProfile: () => queryProfile,
+ searchDomain: () => searchDomain,
+ useFetchImplementation: () => useFetchImplementation
+ });
+ var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/;
+ var isNip05 = (value) => NIP05_REGEX.test(value || "");
+ var _fetch;
+ try {
+ _fetch = fetch;
+ } catch (_) {
+ null;
+ }
+ function useFetchImplementation(fetchImplementation) {
+ _fetch = fetchImplementation;
+ }
+ async function searchDomain(domain, query = "") {
+ try {
+ const url = `https://${domain}/.well-known/nostr.json?name=${query}`;
+ const res = await _fetch(url, { redirect: "manual" });
+ if (res.status !== 200) {
+ throw Error("Wrong response code");
+ }
+ const json = await res.json();
+ return json.names;
+ } catch (_) {
+ return {};
+ }
+ }
+ async function queryProfile(fullname) {
+ const match = fullname.match(NIP05_REGEX);
+ if (!match)
+ return null;
+ const [, name = "_", domain] = match;
+ try {
+ const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
+ const res = await _fetch(url, { redirect: "manual" });
+ if (res.status !== 200) {
+ throw Error("Wrong response code");
+ }
+ const json = await res.json();
+ const pubkey = json.names[name];
+ return pubkey ? { pubkey, relays: json.relays?.[pubkey] } : null;
+ } catch (_e) {
+ return null;
+ }
+ }
+ async function isValid(pubkey, nip05) {
+ const res = await queryProfile(nip05);
+ return res ? res.pubkey === pubkey : false;
+ }
+
+ // nip10.ts
+ var nip10_exports = {};
+ __export(nip10_exports, {
+ parse: () => parse
+ });
+ function parse(event) {
+ const result = {
+ reply: void 0,
+ root: void 0,
+ mentions: [],
+ profiles: [],
+ quotes: []
+ };
+ let maybeParent;
+ let maybeRoot;
+ for (let i2 = event.tags.length - 1; i2 >= 0; i2--) {
+ const tag = event.tags[i2];
+ if (tag[0] === "e" && tag[1]) {
+ const [_, eTagEventId, eTagRelayUrl, eTagMarker, eTagAuthor] = tag;
+ const eventPointer = {
+ id: eTagEventId,
+ relays: eTagRelayUrl ? [eTagRelayUrl] : [],
+ author: eTagAuthor
+ };
+ if (eTagMarker === "root") {
+ result.root = eventPointer;
+ continue;
+ }
+ if (eTagMarker === "reply") {
+ result.reply = eventPointer;
+ continue;
+ }
+ if (eTagMarker === "mention") {
+ result.mentions.push(eventPointer);
+ continue;
+ }
+ if (!maybeParent) {
+ maybeParent = eventPointer;
+ } else {
+ maybeRoot = eventPointer;
+ }
+ result.mentions.push(eventPointer);
+ continue;
+ }
+ if (tag[0] === "q" && tag[1]) {
+ const [_, eTagEventId, eTagRelayUrl] = tag;
+ result.quotes.push({
+ id: eTagEventId,
+ relays: eTagRelayUrl ? [eTagRelayUrl] : []
+ });
+ }
+ if (tag[0] === "p" && tag[1]) {
+ result.profiles.push({
+ pubkey: tag[1],
+ relays: tag[2] ? [tag[2]] : []
+ });
+ continue;
+ }
+ }
+ if (!result.root) {
+ result.root = maybeRoot || maybeParent || result.reply;
+ }
+ if (!result.reply) {
+ result.reply = maybeParent || result.root;
+ }
+ ;
+ [result.reply, result.root].forEach((ref) => {
+ if (!ref)
+ return;
+ let idx = result.mentions.indexOf(ref);
+ if (idx !== -1) {
+ result.mentions.splice(idx, 1);
+ }
+ if (ref.author) {
+ let author = result.profiles.find((p) => p.pubkey === ref.author);
+ if (author && author.relays) {
+ if (!ref.relays) {
+ ref.relays = [];
+ }
+ author.relays.forEach((url) => {
+ if (ref.relays?.indexOf(url) === -1)
+ ref.relays.push(url);
+ });
+ author.relays = ref.relays;
+ }
+ }
+ });
+ result.mentions.forEach((ref) => {
+ if (ref.author) {
+ let author = result.profiles.find((p) => p.pubkey === ref.author);
+ if (author && author.relays) {
+ if (!ref.relays) {
+ ref.relays = [];
+ }
+ author.relays.forEach((url) => {
+ if (ref.relays.indexOf(url) === -1)
+ ref.relays.push(url);
+ });
+ author.relays = ref.relays;
+ }
+ }
+ });
+ return result;
+ }
+
+ // nip11.ts
+ var nip11_exports = {};
+ __export(nip11_exports, {
+ fetchRelayInformation: () => fetchRelayInformation,
+ useFetchImplementation: () => useFetchImplementation2
+ });
+ var _fetch2;
+ try {
+ _fetch2 = fetch;
+ } catch {
+ }
+ function useFetchImplementation2(fetchImplementation) {
+ _fetch2 = fetchImplementation;
+ }
+ async function fetchRelayInformation(url) {
+ return await (await fetch(url.replace("ws://", "http://").replace("wss://", "https://"), {
+ headers: { Accept: "application/nostr+json" }
+ })).json();
+ }
+
+ // nip13.ts
+ var nip13_exports = {};
+ __export(nip13_exports, {
+ fastEventHash: () => fastEventHash,
+ getPow: () => getPow,
+ minePow: () => minePow
+ });
+ function getPow(hex2) {
+ let count = 0;
+ for (let i2 = 0; i2 < 64; i2 += 8) {
+ const nibble = parseInt(hex2.substring(i2, i2 + 8), 16);
+ if (nibble === 0) {
+ count += 32;
+ } else {
+ count += Math.clz32(nibble);
+ break;
+ }
+ }
+ return count;
+ }
+ function minePow(unsigned, difficulty) {
+ let count = 0;
+ const event = unsigned;
+ const tag = ["nonce", count.toString(), difficulty.toString()];
+ event.tags.push(tag);
+ while (true) {
+ const now2 = Math.floor(new Date().getTime() / 1e3);
+ if (now2 !== event.created_at) {
+ count = 0;
+ event.created_at = now2;
+ }
+ tag[1] = (++count).toString();
+ event.id = fastEventHash(event);
+ if (getPow(event.id) >= difficulty) {
+ break;
+ }
+ }
+ return event;
+ }
+ function fastEventHash(evt) {
+ return bytesToHex2(
+ sha2562(utf8Encoder.encode(JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content])))
+ );
+ }
+
+ // nip17.ts
+ var nip17_exports = {};
+ __export(nip17_exports, {
+ unwrapEvent: () => unwrapEvent2,
+ unwrapManyEvents: () => unwrapManyEvents2,
+ wrapEvent: () => wrapEvent2,
+ wrapManyEvents: () => wrapManyEvents2
+ });
+
+ // nip59.ts
+ var nip59_exports = {};
+ __export(nip59_exports, {
+ createRumor: () => createRumor,
+ createSeal: () => createSeal,
+ createWrap: () => createWrap,
+ unwrapEvent: () => unwrapEvent,
+ unwrapManyEvents: () => unwrapManyEvents,
+ wrapEvent: () => wrapEvent,
+ wrapManyEvents: () => wrapManyEvents
+ });
+
+ // nip44.ts
+ var nip44_exports = {};
+ __export(nip44_exports, {
+ decrypt: () => decrypt3,
+ encrypt: () => encrypt3,
+ getConversationKey: () => getConversationKey,
+ v2: () => v2
+ });
+
+ // node_modules/@noble/ciphers/esm/_poly1305.js
+ var u8to16 = (a, i2) => a[i2++] & 255 | (a[i2++] & 255) << 8;
+ var Poly1305 = class {
+ constructor(key) {
+ this.blockLen = 16;
+ this.outputLen = 16;
+ this.buffer = new Uint8Array(16);
+ this.r = new Uint16Array(10);
+ this.h = new Uint16Array(10);
+ this.pad = new Uint16Array(8);
+ this.pos = 0;
+ this.finished = false;
+ key = toBytes3(key);
+ bytes3(key, 32);
+ const t0 = u8to16(key, 0);
+ const t1 = u8to16(key, 2);
+ const t2 = u8to16(key, 4);
+ const t3 = u8to16(key, 6);
+ const t4 = u8to16(key, 8);
+ const t5 = u8to16(key, 10);
+ const t6 = u8to16(key, 12);
+ const t7 = u8to16(key, 14);
+ this.r[0] = t0 & 8191;
+ this.r[1] = (t0 >>> 13 | t1 << 3) & 8191;
+ this.r[2] = (t1 >>> 10 | t2 << 6) & 7939;
+ this.r[3] = (t2 >>> 7 | t3 << 9) & 8191;
+ this.r[4] = (t3 >>> 4 | t4 << 12) & 255;
+ this.r[5] = t4 >>> 1 & 8190;
+ this.r[6] = (t4 >>> 14 | t5 << 2) & 8191;
+ this.r[7] = (t5 >>> 11 | t6 << 5) & 8065;
+ this.r[8] = (t6 >>> 8 | t7 << 8) & 8191;
+ this.r[9] = t7 >>> 5 & 127;
+ for (let i2 = 0; i2 < 8; i2++)
+ this.pad[i2] = u8to16(key, 16 + 2 * i2);
+ }
+ process(data, offset, isLast = false) {
+ const hibit = isLast ? 0 : 1 << 11;
+ const { h, r } = this;
+ const r0 = r[0];
+ const r1 = r[1];
+ const r2 = r[2];
+ const r3 = r[3];
+ const r4 = r[4];
+ const r5 = r[5];
+ const r6 = r[6];
+ const r7 = r[7];
+ const r8 = r[8];
+ const r9 = r[9];
+ const t0 = u8to16(data, offset + 0);
+ const t1 = u8to16(data, offset + 2);
+ const t2 = u8to16(data, offset + 4);
+ const t3 = u8to16(data, offset + 6);
+ const t4 = u8to16(data, offset + 8);
+ const t5 = u8to16(data, offset + 10);
+ const t6 = u8to16(data, offset + 12);
+ const t7 = u8to16(data, offset + 14);
+ let h0 = h[0] + (t0 & 8191);
+ let h1 = h[1] + ((t0 >>> 13 | t1 << 3) & 8191);
+ let h2 = h[2] + ((t1 >>> 10 | t2 << 6) & 8191);
+ let h3 = h[3] + ((t2 >>> 7 | t3 << 9) & 8191);
+ let h4 = h[4] + ((t3 >>> 4 | t4 << 12) & 8191);
+ let h5 = h[5] + (t4 >>> 1 & 8191);
+ let h6 = h[6] + ((t4 >>> 14 | t5 << 2) & 8191);
+ let h7 = h[7] + ((t5 >>> 11 | t6 << 5) & 8191);
+ let h8 = h[8] + ((t6 >>> 8 | t7 << 8) & 8191);
+ let h9 = h[9] + (t7 >>> 5 | hibit);
+ let c = 0;
+ let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
+ c = d0 >>> 13;
+ d0 &= 8191;
+ d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
+ c += d0 >>> 13;
+ d0 &= 8191;
+ let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
+ c = d1 >>> 13;
+ d1 &= 8191;
+ d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
+ c += d1 >>> 13;
+ d1 &= 8191;
+ let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
+ c = d2 >>> 13;
+ d2 &= 8191;
+ d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
+ c += d2 >>> 13;
+ d2 &= 8191;
+ let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
+ c = d3 >>> 13;
+ d3 &= 8191;
+ d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
+ c += d3 >>> 13;
+ d3 &= 8191;
+ let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
+ c = d4 >>> 13;
+ d4 &= 8191;
+ d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
+ c += d4 >>> 13;
+ d4 &= 8191;
+ let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
+ c = d5 >>> 13;
+ d5 &= 8191;
+ d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
+ c += d5 >>> 13;
+ d5 &= 8191;
+ let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
+ c = d6 >>> 13;
+ d6 &= 8191;
+ d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
+ c += d6 >>> 13;
+ d6 &= 8191;
+ let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
+ c = d7 >>> 13;
+ d7 &= 8191;
+ d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
+ c += d7 >>> 13;
+ d7 &= 8191;
+ let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
+ c = d8 >>> 13;
+ d8 &= 8191;
+ d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
+ c += d8 >>> 13;
+ d8 &= 8191;
+ let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
+ c = d9 >>> 13;
+ d9 &= 8191;
+ d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
+ c += d9 >>> 13;
+ d9 &= 8191;
+ c = (c << 2) + c | 0;
+ c = c + d0 | 0;
+ d0 = c & 8191;
+ c = c >>> 13;
+ d1 += c;
+ h[0] = d0;
+ h[1] = d1;
+ h[2] = d2;
+ h[3] = d3;
+ h[4] = d4;
+ h[5] = d5;
+ h[6] = d6;
+ h[7] = d7;
+ h[8] = d8;
+ h[9] = d9;
+ }
+ finalize() {
+ const { h, pad: pad2 } = this;
+ const g = new Uint16Array(10);
+ let c = h[1] >>> 13;
+ h[1] &= 8191;
+ for (let i2 = 2; i2 < 10; i2++) {
+ h[i2] += c;
+ c = h[i2] >>> 13;
+ h[i2] &= 8191;
+ }
+ h[0] += c * 5;
+ c = h[0] >>> 13;
+ h[0] &= 8191;
+ h[1] += c;
+ c = h[1] >>> 13;
+ h[1] &= 8191;
+ h[2] += c;
+ g[0] = h[0] + 5;
+ c = g[0] >>> 13;
+ g[0] &= 8191;
+ for (let i2 = 1; i2 < 10; i2++) {
+ g[i2] = h[i2] + c;
+ c = g[i2] >>> 13;
+ g[i2] &= 8191;
+ }
+ g[9] -= 1 << 13;
+ let mask = (c ^ 1) - 1;
+ for (let i2 = 0; i2 < 10; i2++)
+ g[i2] &= mask;
+ mask = ~mask;
+ for (let i2 = 0; i2 < 10; i2++)
+ h[i2] = h[i2] & mask | g[i2];
+ h[0] = (h[0] | h[1] << 13) & 65535;
+ h[1] = (h[1] >>> 3 | h[2] << 10) & 65535;
+ h[2] = (h[2] >>> 6 | h[3] << 7) & 65535;
+ h[3] = (h[3] >>> 9 | h[4] << 4) & 65535;
+ h[4] = (h[4] >>> 12 | h[5] << 1 | h[6] << 14) & 65535;
+ h[5] = (h[6] >>> 2 | h[7] << 11) & 65535;
+ h[6] = (h[7] >>> 5 | h[8] << 8) & 65535;
+ h[7] = (h[8] >>> 8 | h[9] << 5) & 65535;
+ let f = h[0] + pad2[0];
+ h[0] = f & 65535;
+ for (let i2 = 1; i2 < 8; i2++) {
+ f = (h[i2] + pad2[i2] | 0) + (f >>> 16) | 0;
+ h[i2] = f & 65535;
+ }
+ }
+ update(data) {
+ exists3(this);
+ const { buffer, blockLen } = this;
+ data = toBytes3(data);
+ const len = data.length;
+ for (let pos = 0; pos < len; ) {
+ const take = Math.min(blockLen - this.pos, len - pos);
+ if (take === blockLen) {
+ for (; blockLen <= len - pos; pos += blockLen)
+ this.process(data, pos);
+ continue;
+ }
+ buffer.set(data.subarray(pos, pos + take), this.pos);
+ this.pos += take;
+ pos += take;
+ if (this.pos === blockLen) {
+ this.process(buffer, 0, false);
+ this.pos = 0;
+ }
+ }
+ return this;
+ }
+ destroy() {
+ this.h.fill(0);
+ this.r.fill(0);
+ this.buffer.fill(0);
+ this.pad.fill(0);
+ }
+ digestInto(out) {
+ exists3(this);
+ output3(out, this);
+ this.finished = true;
+ const { buffer, h } = this;
+ let { pos } = this;
+ if (pos) {
+ buffer[pos++] = 1;
+ for (; pos < 16; pos++)
+ buffer[pos] = 0;
+ this.process(buffer, 0, true);
+ }
+ this.finalize();
+ let opos = 0;
+ for (let i2 = 0; i2 < 8; i2++) {
+ out[opos++] = h[i2] >>> 0;
+ out[opos++] = h[i2] >>> 8;
+ }
+ return out;
+ }
+ digest() {
+ const { buffer, outputLen } = this;
+ this.digestInto(buffer);
+ const res = buffer.slice(0, outputLen);
+ this.destroy();
+ return res;
+ }
+ };
+ function wrapConstructorWithKey2(hashCons) {
+ const hashC = (msg, key) => hashCons(key).update(toBytes3(msg)).digest();
+ const tmp = hashCons(new Uint8Array(32));
+ hashC.outputLen = tmp.outputLen;
+ hashC.blockLen = tmp.blockLen;
+ hashC.create = (key) => hashCons(key);
+ return hashC;
+ }
+ var poly1305 = wrapConstructorWithKey2((key) => new Poly1305(key));
+
+ // node_modules/@noble/ciphers/esm/_arx.js
+ var sigma16 = utf8ToBytes4("expand 16-byte k");
+ var sigma32 = utf8ToBytes4("expand 32-byte k");
+ var sigma16_32 = u32(sigma16);
+ var sigma32_32 = u32(sigma32);
+ function rotl(a, b) {
+ return a << b | a >>> 32 - b;
+ }
+ function isAligned32(b) {
+ return b.byteOffset % 4 === 0;
+ }
+ var BLOCK_LEN = 64;
+ var BLOCK_LEN32 = 16;
+ var MAX_COUNTER = 2 ** 32 - 1;
+ var U32_EMPTY = new Uint32Array();
+ function runCipher(core, sigma, key, nonce, data, output4, counter, rounds) {
+ const len = data.length;
+ const block = new Uint8Array(BLOCK_LEN);
+ const b32 = u32(block);
+ const isAligned = isAligned32(data) && isAligned32(output4);
+ const d32 = isAligned ? u32(data) : U32_EMPTY;
+ const o32 = isAligned ? u32(output4) : U32_EMPTY;
+ for (let pos = 0; pos < len; counter++) {
+ core(sigma, key, nonce, b32, counter, rounds);
+ if (counter >= MAX_COUNTER)
+ throw new Error("arx: counter overflow");
+ const take = Math.min(BLOCK_LEN, len - pos);
+ if (isAligned && take === BLOCK_LEN) {
+ const pos32 = pos / 4;
+ if (pos % 4 !== 0)
+ throw new Error("arx: invalid block position");
+ for (let j = 0, posj; j < BLOCK_LEN32; j++) {
+ posj = pos32 + j;
+ o32[posj] = d32[posj] ^ b32[j];
+ }
+ pos += BLOCK_LEN;
+ continue;
+ }
+ for (let j = 0, posj; j < take; j++) {
+ posj = pos + j;
+ output4[posj] = data[posj] ^ block[j];
+ }
+ pos += take;
+ }
+ }
+ function createCipher(core, opts) {
+ const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts);
+ if (typeof core !== "function")
+ throw new Error("core must be a function");
+ number3(counterLength);
+ number3(rounds);
+ bool2(counterRight);
+ bool2(allowShortKeys);
+ return (key, nonce, data, output4, counter = 0) => {
+ bytes3(key);
+ bytes3(nonce);
+ bytes3(data);
+ const len = data.length;
+ if (!output4)
+ output4 = new Uint8Array(len);
+ bytes3(output4);
+ number3(counter);
+ if (counter < 0 || counter >= MAX_COUNTER)
+ throw new Error("arx: counter overflow");
+ if (output4.length < len)
+ throw new Error(`arx: output (${output4.length}) is shorter than data (${len})`);
+ const toClean = [];
+ let l = key.length, k, sigma;
+ if (l === 32) {
+ k = key.slice();
+ toClean.push(k);
+ sigma = sigma32_32;
+ } else if (l === 16 && allowShortKeys) {
+ k = new Uint8Array(32);
+ k.set(key);
+ k.set(key, 16);
+ sigma = sigma16_32;
+ toClean.push(k);
+ } else {
+ throw new Error(`arx: invalid 32-byte key, got length=${l}`);
+ }
+ if (!isAligned32(nonce)) {
+ nonce = nonce.slice();
+ toClean.push(nonce);
+ }
+ const k32 = u32(k);
+ if (extendNonceFn) {
+ if (nonce.length !== 24)
+ throw new Error(`arx: extended nonce must be 24 bytes`);
+ extendNonceFn(sigma, k32, u32(nonce.subarray(0, 16)), k32);
+ nonce = nonce.subarray(16);
+ }
+ const nonceNcLen = 16 - counterLength;
+ if (nonceNcLen !== nonce.length)
+ throw new Error(`arx: nonce must be ${nonceNcLen} or 16 bytes`);
+ if (nonceNcLen !== 12) {
+ const nc = new Uint8Array(12);
+ nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
+ nonce = nc;
+ toClean.push(nonce);
+ }
+ const n32 = u32(nonce);
+ runCipher(core, sigma, k32, n32, data, output4, counter, rounds);
+ while (toClean.length > 0)
+ toClean.pop().fill(0);
+ return output4;
+ };
+ }
+
+ // node_modules/@noble/ciphers/esm/chacha.js
+ function chachaCore(s, k, n, out, cnt, rounds = 20) {
+ let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2];
+ let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
+ for (let r = 0; r < rounds; r += 2) {
+ x00 = x00 + x04 | 0;
+ x12 = rotl(x12 ^ x00, 16);
+ x08 = x08 + x12 | 0;
+ x04 = rotl(x04 ^ x08, 12);
+ x00 = x00 + x04 | 0;
+ x12 = rotl(x12 ^ x00, 8);
+ x08 = x08 + x12 | 0;
+ x04 = rotl(x04 ^ x08, 7);
+ x01 = x01 + x05 | 0;
+ x13 = rotl(x13 ^ x01, 16);
+ x09 = x09 + x13 | 0;
+ x05 = rotl(x05 ^ x09, 12);
+ x01 = x01 + x05 | 0;
+ x13 = rotl(x13 ^ x01, 8);
+ x09 = x09 + x13 | 0;
+ x05 = rotl(x05 ^ x09, 7);
+ x02 = x02 + x06 | 0;
+ x14 = rotl(x14 ^ x02, 16);
+ x10 = x10 + x14 | 0;
+ x06 = rotl(x06 ^ x10, 12);
+ x02 = x02 + x06 | 0;
+ x14 = rotl(x14 ^ x02, 8);
+ x10 = x10 + x14 | 0;
+ x06 = rotl(x06 ^ x10, 7);
+ x03 = x03 + x07 | 0;
+ x15 = rotl(x15 ^ x03, 16);
+ x11 = x11 + x15 | 0;
+ x07 = rotl(x07 ^ x11, 12);
+ x03 = x03 + x07 | 0;
+ x15 = rotl(x15 ^ x03, 8);
+ x11 = x11 + x15 | 0;
+ x07 = rotl(x07 ^ x11, 7);
+ x00 = x00 + x05 | 0;
+ x15 = rotl(x15 ^ x00, 16);
+ x10 = x10 + x15 | 0;
+ x05 = rotl(x05 ^ x10, 12);
+ x00 = x00 + x05 | 0;
+ x15 = rotl(x15 ^ x00, 8);
+ x10 = x10 + x15 | 0;
+ x05 = rotl(x05 ^ x10, 7);
+ x01 = x01 + x06 | 0;
+ x12 = rotl(x12 ^ x01, 16);
+ x11 = x11 + x12 | 0;
+ x06 = rotl(x06 ^ x11, 12);
+ x01 = x01 + x06 | 0;
+ x12 = rotl(x12 ^ x01, 8);
+ x11 = x11 + x12 | 0;
+ x06 = rotl(x06 ^ x11, 7);
+ x02 = x02 + x07 | 0;
+ x13 = rotl(x13 ^ x02, 16);
+ x08 = x08 + x13 | 0;
+ x07 = rotl(x07 ^ x08, 12);
+ x02 = x02 + x07 | 0;
+ x13 = rotl(x13 ^ x02, 8);
+ x08 = x08 + x13 | 0;
+ x07 = rotl(x07 ^ x08, 7);
+ x03 = x03 + x04 | 0;
+ x14 = rotl(x14 ^ x03, 16);
+ x09 = x09 + x14 | 0;
+ x04 = rotl(x04 ^ x09, 12);
+ x03 = x03 + x04 | 0;
+ x14 = rotl(x14 ^ x03, 8);
+ x09 = x09 + x14 | 0;
+ x04 = rotl(x04 ^ x09, 7);
+ }
+ let oi = 0;
+ out[oi++] = y00 + x00 | 0;
+ out[oi++] = y01 + x01 | 0;
+ out[oi++] = y02 + x02 | 0;
+ out[oi++] = y03 + x03 | 0;
+ out[oi++] = y04 + x04 | 0;
+ out[oi++] = y05 + x05 | 0;
+ out[oi++] = y06 + x06 | 0;
+ out[oi++] = y07 + x07 | 0;
+ out[oi++] = y08 + x08 | 0;
+ out[oi++] = y09 + x09 | 0;
+ out[oi++] = y10 + x10 | 0;
+ out[oi++] = y11 + x11 | 0;
+ out[oi++] = y12 + x12 | 0;
+ out[oi++] = y13 + x13 | 0;
+ out[oi++] = y14 + x14 | 0;
+ out[oi++] = y15 + x15 | 0;
+ }
+ function hchacha(s, k, i2, o32) {
+ let x00 = s[0], x01 = s[1], x02 = s[2], x03 = s[3], x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3], x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7], x12 = i2[0], x13 = i2[1], x14 = i2[2], x15 = i2[3];
+ for (let r = 0; r < 20; r += 2) {
+ x00 = x00 + x04 | 0;
+ x12 = rotl(x12 ^ x00, 16);
+ x08 = x08 + x12 | 0;
+ x04 = rotl(x04 ^ x08, 12);
+ x00 = x00 + x04 | 0;
+ x12 = rotl(x12 ^ x00, 8);
+ x08 = x08 + x12 | 0;
+ x04 = rotl(x04 ^ x08, 7);
+ x01 = x01 + x05 | 0;
+ x13 = rotl(x13 ^ x01, 16);
+ x09 = x09 + x13 | 0;
+ x05 = rotl(x05 ^ x09, 12);
+ x01 = x01 + x05 | 0;
+ x13 = rotl(x13 ^ x01, 8);
+ x09 = x09 + x13 | 0;
+ x05 = rotl(x05 ^ x09, 7);
+ x02 = x02 + x06 | 0;
+ x14 = rotl(x14 ^ x02, 16);
+ x10 = x10 + x14 | 0;
+ x06 = rotl(x06 ^ x10, 12);
+ x02 = x02 + x06 | 0;
+ x14 = rotl(x14 ^ x02, 8);
+ x10 = x10 + x14 | 0;
+ x06 = rotl(x06 ^ x10, 7);
+ x03 = x03 + x07 | 0;
+ x15 = rotl(x15 ^ x03, 16);
+ x11 = x11 + x15 | 0;
+ x07 = rotl(x07 ^ x11, 12);
+ x03 = x03 + x07 | 0;
+ x15 = rotl(x15 ^ x03, 8);
+ x11 = x11 + x15 | 0;
+ x07 = rotl(x07 ^ x11, 7);
+ x00 = x00 + x05 | 0;
+ x15 = rotl(x15 ^ x00, 16);
+ x10 = x10 + x15 | 0;
+ x05 = rotl(x05 ^ x10, 12);
+ x00 = x00 + x05 | 0;
+ x15 = rotl(x15 ^ x00, 8);
+ x10 = x10 + x15 | 0;
+ x05 = rotl(x05 ^ x10, 7);
+ x01 = x01 + x06 | 0;
+ x12 = rotl(x12 ^ x01, 16);
+ x11 = x11 + x12 | 0;
+ x06 = rotl(x06 ^ x11, 12);
+ x01 = x01 + x06 | 0;
+ x12 = rotl(x12 ^ x01, 8);
+ x11 = x11 + x12 | 0;
+ x06 = rotl(x06 ^ x11, 7);
+ x02 = x02 + x07 | 0;
+ x13 = rotl(x13 ^ x02, 16);
+ x08 = x08 + x13 | 0;
+ x07 = rotl(x07 ^ x08, 12);
+ x02 = x02 + x07 | 0;
+ x13 = rotl(x13 ^ x02, 8);
+ x08 = x08 + x13 | 0;
+ x07 = rotl(x07 ^ x08, 7);
+ x03 = x03 + x04 | 0;
+ x14 = rotl(x14 ^ x03, 16);
+ x09 = x09 + x14 | 0;
+ x04 = rotl(x04 ^ x09, 12);
+ x03 = x03 + x04 | 0;
+ x14 = rotl(x14 ^ x03, 8);
+ x09 = x09 + x14 | 0;
+ x04 = rotl(x04 ^ x09, 7);
+ }
+ let oi = 0;
+ o32[oi++] = x00;
+ o32[oi++] = x01;
+ o32[oi++] = x02;
+ o32[oi++] = x03;
+ o32[oi++] = x12;
+ o32[oi++] = x13;
+ o32[oi++] = x14;
+ o32[oi++] = x15;
+ }
+ var chacha20 = /* @__PURE__ */ createCipher(chachaCore, {
+ counterRight: false,
+ counterLength: 4,
+ allowShortKeys: false
+ });
+ var xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
+ counterRight: false,
+ counterLength: 8,
+ extendNonceFn: hchacha,
+ allowShortKeys: false
+ });
+ var ZEROS162 = /* @__PURE__ */ new Uint8Array(16);
+ var updatePadded = (h, msg) => {
+ h.update(msg);
+ const left = msg.length % 16;
+ if (left)
+ h.update(ZEROS162.subarray(left));
+ };
+ var ZEROS322 = /* @__PURE__ */ new Uint8Array(32);
+ function computeTag2(fn, key, nonce, data, AAD) {
+ const authKey = fn(key, nonce, ZEROS322);
+ const h = poly1305.create(authKey);
+ if (AAD)
+ updatePadded(h, AAD);
+ updatePadded(h, data);
+ const num = new Uint8Array(16);
+ const view = createView3(num);
+ setBigUint643(view, 0, BigInt(AAD ? AAD.length : 0), true);
+ setBigUint643(view, 8, BigInt(data.length), true);
+ h.update(num);
+ const res = h.digest();
+ authKey.fill(0);
+ return res;
+ }
+ var _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
+ const tagLength = 16;
+ bytes3(key, 32);
+ bytes3(nonce);
+ return {
+ encrypt: (plaintext, output4) => {
+ const plength = plaintext.length;
+ const clength = plength + tagLength;
+ if (output4) {
+ bytes3(output4, clength);
+ } else {
+ output4 = new Uint8Array(clength);
+ }
+ xorStream(key, nonce, plaintext, output4, 1);
+ const tag = computeTag2(xorStream, key, nonce, output4.subarray(0, -tagLength), AAD);
+ output4.set(tag, plength);
+ return output4;
+ },
+ decrypt: (ciphertext, output4) => {
+ const clength = ciphertext.length;
+ const plength = clength - tagLength;
+ if (clength < tagLength)
+ throw new Error(`encrypted data must be at least ${tagLength} bytes`);
+ if (output4) {
+ bytes3(output4, plength);
+ } else {
+ output4 = new Uint8Array(plength);
+ }
+ const data = ciphertext.subarray(0, -tagLength);
+ const passedTag = ciphertext.subarray(-tagLength);
+ const tag = computeTag2(xorStream, key, nonce, data, AAD);
+ if (!equalBytes2(passedTag, tag))
+ throw new Error("invalid tag");
+ xorStream(key, nonce, data, output4, 1);
+ return output4;
+ }
+ };
+ };
+ var chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16 }, _poly1305_aead(chacha20));
+ var xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, _poly1305_aead(xchacha20));
+
+ // node_modules/@noble/hashes/esm/hmac.js
+ var HMAC2 = class extends Hash2 {
+ constructor(hash3, _key) {
+ super();
+ this.finished = false;
+ this.destroyed = false;
+ assert_default.hash(hash3);
+ const key = toBytes2(_key);
+ this.iHash = hash3.create();
+ if (typeof this.iHash.update !== "function")
+ throw new Error("Expected instance of class which extends utils.Hash");
+ this.blockLen = this.iHash.blockLen;
+ this.outputLen = this.iHash.outputLen;
+ const blockLen = this.blockLen;
+ const pad2 = new Uint8Array(blockLen);
+ pad2.set(key.length > blockLen ? hash3.create().update(key).digest() : key);
+ for (let i2 = 0; i2 < pad2.length; i2++)
+ pad2[i2] ^= 54;
+ this.iHash.update(pad2);
+ this.oHash = hash3.create();
+ for (let i2 = 0; i2 < pad2.length; i2++)
+ pad2[i2] ^= 54 ^ 92;
+ this.oHash.update(pad2);
+ pad2.fill(0);
+ }
+ update(buf) {
+ assert_default.exists(this);
+ this.iHash.update(buf);
+ return this;
+ }
+ digestInto(out) {
+ assert_default.exists(this);
+ assert_default.bytes(out, this.outputLen);
+ this.finished = true;
+ this.iHash.digestInto(out);
+ this.oHash.update(out);
+ this.oHash.digestInto(out);
+ this.destroy();
+ }
+ digest() {
+ const out = new Uint8Array(this.oHash.outputLen);
+ this.digestInto(out);
+ return out;
+ }
+ _cloneInto(to) {
+ to || (to = Object.create(Object.getPrototypeOf(this), {}));
+ const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
+ to = to;
+ to.finished = finished;
+ to.destroyed = destroyed;
+ to.blockLen = blockLen;
+ to.outputLen = outputLen;
+ to.oHash = oHash._cloneInto(to.oHash);
+ to.iHash = iHash._cloneInto(to.iHash);
+ return to;
+ }
+ destroy() {
+ this.destroyed = true;
+ this.oHash.destroy();
+ this.iHash.destroy();
+ }
+ };
+ var hmac2 = (hash3, key, message) => new HMAC2(hash3, key).update(message).digest();
+ hmac2.create = (hash3, key) => new HMAC2(hash3, key);
+
+ // node_modules/@noble/hashes/esm/hkdf.js
+ function extract(hash3, ikm, salt) {
+ assert_default.hash(hash3);
+ if (salt === void 0)
+ salt = new Uint8Array(hash3.outputLen);
+ return hmac2(hash3, toBytes2(salt), toBytes2(ikm));
+ }
+ var HKDF_COUNTER = new Uint8Array([0]);
+ var EMPTY_BUFFER = new Uint8Array();
+ function expand(hash3, prk, info, length = 32) {
+ assert_default.hash(hash3);
+ assert_default.number(length);
+ if (length > 255 * hash3.outputLen)
+ throw new Error("Length should be <= 255*HashLen");
+ const blocks = Math.ceil(length / hash3.outputLen);
+ if (info === void 0)
+ info = EMPTY_BUFFER;
+ const okm = new Uint8Array(blocks * hash3.outputLen);
+ const HMAC3 = hmac2.create(hash3, prk);
+ const HMACTmp = HMAC3._cloneInto();
+ const T = new Uint8Array(HMAC3.outputLen);
+ for (let counter = 0; counter < blocks; counter++) {
+ HKDF_COUNTER[0] = counter + 1;
+ HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T).update(info).update(HKDF_COUNTER).digestInto(T);
+ okm.set(T, hash3.outputLen * counter);
+ HMAC3._cloneInto(HMACTmp);
+ }
+ HMAC3.destroy();
+ HMACTmp.destroy();
+ T.fill(0);
+ HKDF_COUNTER.fill(0);
+ return okm.slice(0, length);
+ }
+
+ // nip44.ts
+ var minPlaintextSize = 1;
+ var maxPlaintextSize = 65535;
+ function getConversationKey(privkeyA, pubkeyB) {
+ const sharedX = secp256k1.getSharedSecret(privkeyA, "02" + pubkeyB).subarray(1, 33);
+ return extract(sha2562, sharedX, "nip44-v2");
+ }
+ function getMessageKeys(conversationKey, nonce) {
+ const keys = expand(sha2562, conversationKey, nonce, 76);
+ return {
+ chacha_key: keys.subarray(0, 32),
+ chacha_nonce: keys.subarray(32, 44),
+ hmac_key: keys.subarray(44, 76)
+ };
+ }
+ function calcPaddedLen(len) {
+ if (!Number.isSafeInteger(len) || len < 1)
+ throw new Error("expected positive integer");
+ if (len <= 32)
+ return 32;
+ const nextPower = 1 << Math.floor(Math.log2(len - 1)) + 1;
+ const chunk = nextPower <= 256 ? 32 : nextPower / 8;
+ return chunk * (Math.floor((len - 1) / chunk) + 1);
+ }
+ function writeU16BE(num) {
+ if (!Number.isSafeInteger(num) || num < minPlaintextSize || num > maxPlaintextSize)
+ throw new Error("invalid plaintext size: must be between 1 and 65535 bytes");
+ const arr = new Uint8Array(2);
+ new DataView(arr.buffer).setUint16(0, num, false);
+ return arr;
+ }
+ function pad(plaintext) {
+ const unpadded = utf8Encoder.encode(plaintext);
+ const unpaddedLen = unpadded.length;
+ const prefix = writeU16BE(unpaddedLen);
+ const suffix = new Uint8Array(calcPaddedLen(unpaddedLen) - unpaddedLen);
+ return concatBytes3(prefix, unpadded, suffix);
+ }
+ function unpad(padded) {
+ const unpaddedLen = new DataView(padded.buffer).getUint16(0);
+ const unpadded = padded.subarray(2, 2 + unpaddedLen);
+ if (unpaddedLen < minPlaintextSize || unpaddedLen > maxPlaintextSize || unpadded.length !== unpaddedLen || padded.length !== 2 + calcPaddedLen(unpaddedLen))
+ throw new Error("invalid padding");
+ return utf8Decoder.decode(unpadded);
+ }
+ function hmacAad(key, message, aad) {
+ if (aad.length !== 32)
+ throw new Error("AAD associated data must be 32 bytes");
+ const combined = concatBytes3(aad, message);
+ return hmac2(sha2562, key, combined);
+ }
+ function decodePayload(payload) {
+ if (typeof payload !== "string")
+ throw new Error("payload must be a valid string");
+ const plen = payload.length;
+ if (plen < 132 || plen > 87472)
+ throw new Error("invalid payload length: " + plen);
+ if (payload[0] === "#")
+ throw new Error("unknown encryption version");
+ let data;
+ try {
+ data = base64.decode(payload);
+ } catch (error) {
+ throw new Error("invalid base64: " + error.message);
+ }
+ const dlen = data.length;
+ if (dlen < 99 || dlen > 65603)
+ throw new Error("invalid data length: " + dlen);
+ const vers = data[0];
+ if (vers !== 2)
+ throw new Error("unknown encryption version " + vers);
+ return {
+ nonce: data.subarray(1, 33),
+ ciphertext: data.subarray(33, -32),
+ mac: data.subarray(-32)
+ };
+ }
+ function encrypt3(plaintext, conversationKey, nonce = randomBytes2(32)) {
+ const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce);
+ const padded = pad(plaintext);
+ const ciphertext = chacha20(chacha_key, chacha_nonce, padded);
+ const mac = hmacAad(hmac_key, ciphertext, nonce);
+ return base64.encode(concatBytes3(new Uint8Array([2]), nonce, ciphertext, mac));
+ }
+ function decrypt3(payload, conversationKey) {
+ const { nonce, ciphertext, mac } = decodePayload(payload);
+ const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce);
+ const calculatedMac = hmacAad(hmac_key, ciphertext, nonce);
+ if (!equalBytes2(calculatedMac, mac))
+ throw new Error("invalid MAC");
+ const padded = chacha20(chacha_key, chacha_nonce, ciphertext);
+ return unpad(padded);
+ }
+ var v2 = {
+ utils: {
+ getConversationKey,
+ calcPaddedLen
+ },
+ encrypt: encrypt3,
+ decrypt: decrypt3
+ };
+
+ // nip59.ts
+ var TWO_DAYS = 2 * 24 * 60 * 60;
+ var now = () => Math.round(Date.now() / 1e3);
+ var randomNow = () => Math.round(now() - Math.random() * TWO_DAYS);
+ var nip44ConversationKey = (privateKey, publicKey) => getConversationKey(privateKey, publicKey);
+ var nip44Encrypt = (data, privateKey, publicKey) => encrypt3(JSON.stringify(data), nip44ConversationKey(privateKey, publicKey));
+ var nip44Decrypt = (data, privateKey) => JSON.parse(decrypt3(data.content, nip44ConversationKey(privateKey, data.pubkey)));
+ function createRumor(event, privateKey) {
+ const rumor = {
+ created_at: now(),
+ content: "",
+ tags: [],
+ ...event,
+ pubkey: getPublicKey(privateKey)
+ };
+ rumor.id = getEventHash(rumor);
+ return rumor;
+ }
+ function createSeal(rumor, privateKey, recipientPublicKey) {
+ return finalizeEvent(
+ {
+ kind: Seal,
+ content: nip44Encrypt(rumor, privateKey, recipientPublicKey),
+ created_at: randomNow(),
+ tags: []
+ },
+ privateKey
+ );
+ }
+ function createWrap(seal, recipientPublicKey) {
+ const randomKey = generateSecretKey();
+ return finalizeEvent(
+ {
+ kind: GiftWrap,
+ content: nip44Encrypt(seal, randomKey, recipientPublicKey),
+ created_at: randomNow(),
+ tags: [["p", recipientPublicKey]]
+ },
+ randomKey
+ );
+ }
+ function wrapEvent(event, senderPrivateKey, recipientPublicKey) {
+ const rumor = createRumor(event, senderPrivateKey);
+ const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey);
+ return createWrap(seal, recipientPublicKey);
+ }
+ function wrapManyEvents(event, senderPrivateKey, recipientsPublicKeys) {
+ if (!recipientsPublicKeys || recipientsPublicKeys.length === 0) {
+ throw new Error("At least one recipient is required.");
+ }
+ const senderPublicKey = getPublicKey(senderPrivateKey);
+ const wrappeds = [wrapEvent(event, senderPrivateKey, senderPublicKey)];
+ recipientsPublicKeys.forEach((recipientPublicKey) => {
+ wrappeds.push(wrapEvent(event, senderPrivateKey, recipientPublicKey));
+ });
+ return wrappeds;
+ }
+ function unwrapEvent(wrap, recipientPrivateKey) {
+ const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey);
+ return nip44Decrypt(unwrappedSeal, recipientPrivateKey);
+ }
+ function unwrapManyEvents(wrappedEvents, recipientPrivateKey) {
+ let unwrappedEvents = [];
+ wrappedEvents.forEach((e) => {
+ unwrappedEvents.push(unwrapEvent(e, recipientPrivateKey));
+ });
+ unwrappedEvents.sort((a, b) => a.created_at - b.created_at);
+ return unwrappedEvents;
+ }
+
+ // nip17.ts
+ function createEvent(recipients, message, conversationTitle, replyTo) {
+ const baseEvent = {
+ created_at: Math.ceil(Date.now() / 1e3),
+ kind: PrivateDirectMessage,
+ tags: [],
+ content: message
+ };
+ const recipientsArray = Array.isArray(recipients) ? recipients : [recipients];
+ recipientsArray.forEach(({ publicKey, relayUrl }) => {
+ baseEvent.tags.push(relayUrl ? ["p", publicKey, relayUrl] : ["p", publicKey]);
+ });
+ if (replyTo) {
+ baseEvent.tags.push(["e", replyTo.eventId, replyTo.relayUrl || "", "reply"]);
+ }
+ if (conversationTitle) {
+ baseEvent.tags.push(["subject", conversationTitle]);
+ }
+ return baseEvent;
+ }
+ function wrapEvent2(senderPrivateKey, recipient, message, conversationTitle, replyTo) {
+ const event = createEvent(recipient, message, conversationTitle, replyTo);
+ return wrapEvent(event, senderPrivateKey, recipient.publicKey);
+ }
+ function wrapManyEvents2(senderPrivateKey, recipients, message, conversationTitle, replyTo) {
+ if (!recipients || recipients.length === 0) {
+ throw new Error("At least one recipient is required.");
+ }
+ const senderPublicKey = getPublicKey(senderPrivateKey);
+ return [{ publicKey: senderPublicKey }, ...recipients].map(
+ (recipient) => wrapEvent2(senderPrivateKey, recipient, message, conversationTitle, replyTo)
+ );
+ }
+ var unwrapEvent2 = unwrapEvent;
+ var unwrapManyEvents2 = unwrapManyEvents;
+
+ // nip18.ts
+ var nip18_exports = {};
+ __export(nip18_exports, {
+ finishRepostEvent: () => finishRepostEvent,
+ getRepostedEvent: () => getRepostedEvent,
+ getRepostedEventPointer: () => getRepostedEventPointer
+ });
+ function finishRepostEvent(t, reposted, relayUrl, privateKey) {
+ let kind;
+ const tags = [...t.tags ?? [], ["e", reposted.id, relayUrl], ["p", reposted.pubkey]];
+ if (reposted.kind === ShortTextNote) {
+ kind = Repost;
+ } else {
+ kind = GenericRepost;
+ tags.push(["k", String(reposted.kind)]);
+ }
+ return finalizeEvent(
+ {
+ kind,
+ tags,
+ content: t.content === "" || reposted.tags?.find((tag) => tag[0] === "-") ? "" : JSON.stringify(reposted),
+ created_at: t.created_at
+ },
+ privateKey
+ );
+ }
+ function getRepostedEventPointer(event) {
+ if (![Repost, GenericRepost].includes(event.kind)) {
+ return void 0;
+ }
+ let lastETag;
+ let lastPTag;
+ for (let i2 = event.tags.length - 1; i2 >= 0 && (lastETag === void 0 || lastPTag === void 0); i2--) {
+ const tag = event.tags[i2];
+ if (tag.length >= 2) {
+ if (tag[0] === "e" && lastETag === void 0) {
+ lastETag = tag;
+ } else if (tag[0] === "p" && lastPTag === void 0) {
+ lastPTag = tag;
+ }
+ }
+ }
+ if (lastETag === void 0) {
+ return void 0;
+ }
+ return {
+ id: lastETag[1],
+ relays: [lastETag[2], lastPTag?.[2]].filter((x) => typeof x === "string"),
+ author: lastPTag?.[1]
+ };
+ }
+ function getRepostedEvent(event, { skipVerification } = {}) {
+ const pointer = getRepostedEventPointer(event);
+ if (pointer === void 0 || event.content === "") {
+ return void 0;
+ }
+ let repostedEvent;
+ try {
+ repostedEvent = JSON.parse(event.content);
+ } catch (error) {
+ return void 0;
+ }
+ if (repostedEvent.id !== pointer.id) {
+ return void 0;
+ }
+ if (!skipVerification && !verifyEvent(repostedEvent)) {
+ return void 0;
+ }
+ return repostedEvent;
+ }
+
+ // nip21.ts
+ var nip21_exports = {};
+ __export(nip21_exports, {
+ NOSTR_URI_REGEX: () => NOSTR_URI_REGEX,
+ parse: () => parse2,
+ test: () => test
+ });
+ var NOSTR_URI_REGEX = new RegExp(`nostr:(${BECH32_REGEX.source})`);
+ function test(value) {
+ return typeof value === "string" && new RegExp(`^${NOSTR_URI_REGEX.source}$`).test(value);
+ }
+ function parse2(uri) {
+ const match = uri.match(new RegExp(`^${NOSTR_URI_REGEX.source}$`));
+ if (!match)
+ throw new Error(`Invalid Nostr URI: ${uri}`);
+ return {
+ uri: match[0],
+ value: match[1],
+ decoded: decode(match[1])
+ };
+ }
+
+ // nip25.ts
+ var nip25_exports = {};
+ __export(nip25_exports, {
+ finishReactionEvent: () => finishReactionEvent,
+ getReactedEventPointer: () => getReactedEventPointer
+ });
+ function finishReactionEvent(t, reacted, privateKey) {
+ const inheritedTags = reacted.tags.filter((tag) => tag.length >= 2 && (tag[0] === "e" || tag[0] === "p"));
+ return finalizeEvent(
+ {
+ ...t,
+ kind: Reaction,
+ tags: [...t.tags ?? [], ...inheritedTags, ["e", reacted.id], ["p", reacted.pubkey]],
+ content: t.content ?? "+"
+ },
+ privateKey
+ );
+ }
+ function getReactedEventPointer(event) {
+ if (event.kind !== Reaction) {
+ return void 0;
+ }
+ let lastETag;
+ let lastPTag;
+ for (let i2 = event.tags.length - 1; i2 >= 0 && (lastETag === void 0 || lastPTag === void 0); i2--) {
+ const tag = event.tags[i2];
+ if (tag.length >= 2) {
+ if (tag[0] === "e" && lastETag === void 0) {
+ lastETag = tag;
+ } else if (tag[0] === "p" && lastPTag === void 0) {
+ lastPTag = tag;
+ }
+ }
+ }
+ if (lastETag === void 0 || lastPTag === void 0) {
+ return void 0;
+ }
+ return {
+ id: lastETag[1],
+ relays: [lastETag[2], lastPTag[2]].filter((x) => x !== void 0),
+ author: lastPTag[1]
+ };
+ }
+
+ // nip27.ts
+ var nip27_exports = {};
+ __export(nip27_exports, {
+ parse: () => parse3
+ });
+ var noCharacter = /\W/m;
+ var noURLCharacter = /\W |\W$|$|,| /m;
+ function* parse3(content) {
+ const max = content.length;
+ let prevIndex = 0;
+ let index = 0;
+ while (index < max) {
+ let u = content.indexOf(":", index);
+ if (u === -1) {
+ break;
+ }
+ if (content.substring(u - 5, u) === "nostr") {
+ const m = content.substring(u + 60).match(noCharacter);
+ const end = m ? u + 60 + m.index : max;
+ try {
+ let pointer;
+ let { data, type } = decode(content.substring(u + 1, end));
+ switch (type) {
+ case "npub":
+ pointer = { pubkey: data };
+ break;
+ case "nsec":
+ case "note":
+ index = end + 1;
+ continue;
+ default:
+ pointer = data;
+ }
+ if (prevIndex !== u - 5) {
+ yield { type: "text", text: content.substring(prevIndex, u - 5) };
+ }
+ yield { type: "reference", pointer };
+ index = end;
+ prevIndex = index;
+ continue;
+ } catch (_err) {
+ index = u + 1;
+ continue;
+ }
+ } else if (content.substring(u - 5, u) === "https" || content.substring(u - 4, u) === "http") {
+ const m = content.substring(u + 4).match(noURLCharacter);
+ const end = m ? u + 4 + m.index : max;
+ const prefixLen = content[u - 1] === "s" ? 5 : 4;
+ try {
+ let url = new URL(content.substring(u - prefixLen, end));
+ if (url.hostname.indexOf(".") === -1) {
+ throw new Error("invalid url");
+ }
+ if (prevIndex !== u - prefixLen) {
+ yield { type: "text", text: content.substring(prevIndex, u - prefixLen) };
+ }
+ if (url.pathname.endsWith(".png") || url.pathname.endsWith(".jpg") || url.pathname.endsWith(".jpeg") || url.pathname.endsWith(".gif") || url.pathname.endsWith(".webp")) {
+ yield { type: "image", url: url.toString() };
+ index = end;
+ prevIndex = index;
+ continue;
+ }
+ if (url.pathname.endsWith(".mp4") || url.pathname.endsWith(".avi") || url.pathname.endsWith(".webm") || url.pathname.endsWith(".mkv")) {
+ yield { type: "video", url: url.toString() };
+ index = end;
+ prevIndex = index;
+ continue;
+ }
+ if (url.pathname.endsWith(".mp3") || url.pathname.endsWith(".aac") || url.pathname.endsWith(".ogg") || url.pathname.endsWith(".opus")) {
+ yield { type: "audio", url: url.toString() };
+ index = end;
+ prevIndex = index;
+ continue;
+ }
+ yield { type: "url", url: url.toString() };
+ index = end;
+ prevIndex = index;
+ continue;
+ } catch (_err) {
+ index = end + 1;
+ continue;
+ }
+ } else if (content.substring(u - 3, u) === "wss" || content.substring(u - 2, u) === "ws") {
+ const m = content.substring(u + 4).match(noURLCharacter);
+ const end = m ? u + 4 + m.index : max;
+ const prefixLen = content[u - 1] === "s" ? 3 : 2;
+ try {
+ let url = new URL(content.substring(u - prefixLen, end));
+ if (url.hostname.indexOf(".") === -1) {
+ throw new Error("invalid ws url");
+ }
+ if (prevIndex !== u - prefixLen) {
+ yield { type: "text", text: content.substring(prevIndex, u - prefixLen) };
+ }
+ yield { type: "relay", url: url.toString() };
+ index = end;
+ prevIndex = index;
+ continue;
+ } catch (_err) {
+ index = end + 1;
+ continue;
+ }
+ } else {
+ index = u + 1;
+ continue;
+ }
+ }
+ if (prevIndex !== max) {
+ yield { type: "text", text: content.substring(prevIndex) };
+ }
+ }
+
+ // nip28.ts
+ var nip28_exports = {};
+ __export(nip28_exports, {
+ channelCreateEvent: () => channelCreateEvent,
+ channelHideMessageEvent: () => channelHideMessageEvent,
+ channelMessageEvent: () => channelMessageEvent,
+ channelMetadataEvent: () => channelMetadataEvent,
+ channelMuteUserEvent: () => channelMuteUserEvent
+ });
+ var channelCreateEvent = (t, privateKey) => {
+ let content;
+ if (typeof t.content === "object") {
+ content = JSON.stringify(t.content);
+ } else if (typeof t.content === "string") {
+ content = t.content;
+ } else {
+ return void 0;
+ }
+ return finalizeEvent(
+ {
+ kind: ChannelCreation,
+ tags: [...t.tags ?? []],
+ content,
+ created_at: t.created_at
+ },
+ privateKey
+ );
+ };
+ var channelMetadataEvent = (t, privateKey) => {
+ let content;
+ if (typeof t.content === "object") {
+ content = JSON.stringify(t.content);
+ } else if (typeof t.content === "string") {
+ content = t.content;
+ } else {
+ return void 0;
+ }
+ return finalizeEvent(
+ {
+ kind: ChannelMetadata,
+ tags: [["e", t.channel_create_event_id], ...t.tags ?? []],
+ content,
+ created_at: t.created_at
+ },
+ privateKey
+ );
+ };
+ var channelMessageEvent = (t, privateKey) => {
+ const tags = [["e", t.channel_create_event_id, t.relay_url, "root"]];
+ if (t.reply_to_channel_message_event_id) {
+ tags.push(["e", t.reply_to_channel_message_event_id, t.relay_url, "reply"]);
+ }
+ return finalizeEvent(
+ {
+ kind: ChannelMessage,
+ tags: [...tags, ...t.tags ?? []],
+ content: t.content,
+ created_at: t.created_at
+ },
+ privateKey
+ );
+ };
+ var channelHideMessageEvent = (t, privateKey) => {
+ let content;
+ if (typeof t.content === "object") {
+ content = JSON.stringify(t.content);
+ } else if (typeof t.content === "string") {
+ content = t.content;
+ } else {
+ return void 0;
+ }
+ return finalizeEvent(
+ {
+ kind: ChannelHideMessage,
+ tags: [["e", t.channel_message_event_id], ...t.tags ?? []],
+ content,
+ created_at: t.created_at
+ },
+ privateKey
+ );
+ };
+ var channelMuteUserEvent = (t, privateKey) => {
+ let content;
+ if (typeof t.content === "object") {
+ content = JSON.stringify(t.content);
+ } else if (typeof t.content === "string") {
+ content = t.content;
+ } else {
+ return void 0;
+ }
+ return finalizeEvent(
+ {
+ kind: ChannelMuteUser,
+ tags: [["p", t.pubkey_to_mute], ...t.tags ?? []],
+ content,
+ created_at: t.created_at
+ },
+ privateKey
+ );
+ };
+
+ // nip30.ts
+ var nip30_exports = {};
+ __export(nip30_exports, {
+ EMOJI_SHORTCODE_REGEX: () => EMOJI_SHORTCODE_REGEX,
+ matchAll: () => matchAll,
+ regex: () => regex,
+ replaceAll: () => replaceAll
+ });
+ var EMOJI_SHORTCODE_REGEX = /:(\w+):/;
+ var regex = () => new RegExp(`\\B${EMOJI_SHORTCODE_REGEX.source}\\B`, "g");
+ function* matchAll(content) {
+ const matches = content.matchAll(regex());
+ for (const match of matches) {
+ try {
+ const [shortcode, name] = match;
+ yield {
+ shortcode,
+ name,
+ start: match.index,
+ end: match.index + shortcode.length
+ };
+ } catch (_e) {
+ }
+ }
+ }
+ function replaceAll(content, replacer) {
+ return content.replaceAll(regex(), (shortcode, name) => {
+ return replacer({
+ shortcode,
+ name
+ });
+ });
+ }
+
+ // nip39.ts
+ var nip39_exports = {};
+ __export(nip39_exports, {
+ useFetchImplementation: () => useFetchImplementation3,
+ validateGithub: () => validateGithub
+ });
+ var _fetch3;
+ try {
+ _fetch3 = fetch;
+ } catch {
+ }
+ function useFetchImplementation3(fetchImplementation) {
+ _fetch3 = fetchImplementation;
+ }
+ async function validateGithub(pubkey, username, proof) {
+ try {
+ let res = await (await _fetch3(`https://gist.github.com/${username}/${proof}/raw`)).text();
+ return res === `Verifying that I control the following Nostr public key: ${pubkey}`;
+ } catch (_) {
+ return false;
+ }
+ }
+
+ // nip47.ts
+ var nip47_exports = {};
+ __export(nip47_exports, {
+ makeNwcRequestEvent: () => makeNwcRequestEvent,
+ parseConnectionString: () => parseConnectionString
+ });
+ function parseConnectionString(connectionString) {
+ const { host, pathname, searchParams } = new URL(connectionString);
+ const pubkey = pathname || host;
+ const relay = searchParams.get("relay");
+ const secret = searchParams.get("secret");
+ if (!pubkey || !relay || !secret) {
+ throw new Error("invalid connection string");
+ }
+ return { pubkey, relay, secret };
+ }
+ async function makeNwcRequestEvent(pubkey, secretKey, invoice) {
+ const content = {
+ method: "pay_invoice",
+ params: {
+ invoice
+ }
+ };
+ const encryptedContent = encrypt2(secretKey, pubkey, JSON.stringify(content));
+ const eventTemplate = {
+ kind: NWCWalletRequest,
+ created_at: Math.round(Date.now() / 1e3),
+ content: encryptedContent,
+ tags: [["p", pubkey]]
+ };
+ return finalizeEvent(eventTemplate, secretKey);
+ }
+
+ // nip54.ts
+ var nip54_exports = {};
+ __export(nip54_exports, {
+ normalizeIdentifier: () => normalizeIdentifier
+ });
+ function normalizeIdentifier(name) {
+ name = name.trim().toLowerCase();
+ name = name.normalize("NFKC");
+ return Array.from(name).map((char) => {
+ if (/\p{Letter}/u.test(char) || /\p{Number}/u.test(char)) {
+ return char;
+ }
+ return "-";
+ }).join("");
+ }
+
+ // nip57.ts
+ var nip57_exports = {};
+ __export(nip57_exports, {
+ getSatoshisAmountFromBolt11: () => getSatoshisAmountFromBolt11,
+ getZapEndpoint: () => getZapEndpoint,
+ makeZapReceipt: () => makeZapReceipt,
+ makeZapRequest: () => makeZapRequest,
+ useFetchImplementation: () => useFetchImplementation4,
+ validateZapRequest: () => validateZapRequest
+ });
+ var _fetch4;
+ try {
+ _fetch4 = fetch;
+ } catch {
+ }
+ function useFetchImplementation4(fetchImplementation) {
+ _fetch4 = fetchImplementation;
+ }
+ async function getZapEndpoint(metadata) {
+ try {
+ let lnurl = "";
+ let { lud06, lud16 } = JSON.parse(metadata.content);
+ if (lud06) {
+ let { words } = bech32.decode(lud06, 1e3);
+ let data = bech32.fromWords(words);
+ lnurl = utf8Decoder.decode(data);
+ } else if (lud16) {
+ let [name, domain] = lud16.split("@");
+ lnurl = new URL(`/.well-known/lnurlp/${name}`, `https://${domain}`).toString();
+ } else {
+ return null;
+ }
+ let res = await _fetch4(lnurl);
+ let body = await res.json();
+ if (body.allowsNostr && body.nostrPubkey) {
+ return body.callback;
+ }
+ } catch (err) {
+ }
+ return null;
+ }
+ function makeZapRequest(params) {
+ let zr = {
+ kind: 9734,
+ created_at: Math.round(Date.now() / 1e3),
+ content: params.comment || "",
+ tags: [
+ ["p", "pubkey" in params ? params.pubkey : params.event.pubkey],
+ ["amount", params.amount.toString()],
+ ["relays", ...params.relays]
+ ]
+ };
+ if ("event" in params) {
+ zr.tags.push(["e", params.event.id]);
+ if (isReplaceableKind(params.event.kind)) {
+ const a = ["a", `${params.event.kind}:${params.event.pubkey}:`];
+ zr.tags.push(a);
+ } else if (isAddressableKind(params.event.kind)) {
+ let d = params.event.tags.find(([t, v]) => t === "d" && v);
+ if (!d)
+ throw new Error("d tag not found or is empty");
+ const a = ["a", `${params.event.kind}:${params.event.pubkey}:${d[1]}`];
+ zr.tags.push(a);
+ }
+ zr.tags.push(["k", params.event.kind.toString()]);
+ }
+ return zr;
+ }
+ function validateZapRequest(zapRequestString) {
+ let zapRequest;
+ try {
+ zapRequest = JSON.parse(zapRequestString);
+ } catch (err) {
+ return "Invalid zap request JSON.";
+ }
+ if (!validateEvent(zapRequest))
+ return "Zap request is not a valid Nostr event.";
+ if (!verifyEvent(zapRequest))
+ return "Invalid signature on zap request.";
+ let p = zapRequest.tags.find(([t, v]) => t === "p" && v);
+ if (!p)
+ return "Zap request doesn't have a 'p' tag.";
+ if (!p[1].match(/^[a-f0-9]{64}$/))
+ return "Zap request 'p' tag is not valid hex.";
+ let e = zapRequest.tags.find(([t, v]) => t === "e" && v);
+ if (e && !e[1].match(/^[a-f0-9]{64}$/))
+ return "Zap request 'e' tag is not valid hex.";
+ let relays = zapRequest.tags.find(([t, v]) => t === "relays" && v);
+ if (!relays)
+ return "Zap request doesn't have a 'relays' tag.";
+ return null;
+ }
+ function makeZapReceipt({
+ zapRequest,
+ preimage,
+ bolt11,
+ paidAt
+ }) {
+ let zr = JSON.parse(zapRequest);
+ let tagsFromZapRequest = zr.tags.filter(([t]) => t === "e" || t === "p" || t === "a");
+ let zap = {
+ kind: 9735,
+ created_at: Math.round(paidAt.getTime() / 1e3),
+ content: "",
+ tags: [...tagsFromZapRequest, ["P", zr.pubkey], ["bolt11", bolt11], ["description", zapRequest]]
+ };
+ if (preimage) {
+ zap.tags.push(["preimage", preimage]);
+ }
+ return zap;
+ }
+ function getSatoshisAmountFromBolt11(bolt11) {
+ if (bolt11.length < 50) {
+ return 0;
+ }
+ bolt11 = bolt11.substring(0, 50);
+ const idx = bolt11.lastIndexOf("1");
+ if (idx === -1) {
+ return 0;
+ }
+ const hrp = bolt11.substring(0, idx);
+ if (!hrp.startsWith("lnbc")) {
+ return 0;
+ }
+ const amount = hrp.substring(4);
+ if (amount.length < 1) {
+ return 0;
+ }
+ const char = amount[amount.length - 1];
+ const digit = char.charCodeAt(0) - "0".charCodeAt(0);
+ const isDigit = digit >= 0 && digit <= 9;
+ let cutPoint = amount.length - 1;
+ if (isDigit) {
+ cutPoint++;
+ }
+ if (cutPoint < 1) {
+ return 0;
+ }
+ const num = parseInt(amount.substring(0, cutPoint));
+ switch (char) {
+ case "m":
+ return num * 1e5;
+ case "u":
+ return num * 100;
+ case "n":
+ return num / 10;
+ case "p":
+ return num / 1e4;
+ default:
+ return num * 1e8;
+ }
+ }
+
+ // nip98.ts
+ var nip98_exports = {};
+ __export(nip98_exports, {
+ getToken: () => getToken,
+ hashPayload: () => hashPayload,
+ unpackEventFromToken: () => unpackEventFromToken,
+ validateEvent: () => validateEvent2,
+ validateEventKind: () => validateEventKind,
+ validateEventMethodTag: () => validateEventMethodTag,
+ validateEventPayloadTag: () => validateEventPayloadTag,
+ validateEventTimestamp: () => validateEventTimestamp,
+ validateEventUrlTag: () => validateEventUrlTag,
+ validateToken: () => validateToken
+ });
+ var _authorizationScheme = "Nostr ";
+ async function getToken(loginUrl, httpMethod, sign, includeAuthorizationScheme = false, payload) {
+ const event = {
+ kind: HTTPAuth,
+ tags: [
+ ["u", loginUrl],
+ ["method", httpMethod]
+ ],
+ created_at: Math.round(new Date().getTime() / 1e3),
+ content: ""
+ };
+ if (payload) {
+ event.tags.push(["payload", hashPayload(payload)]);
+ }
+ const signedEvent = await sign(event);
+ const authorizationScheme = includeAuthorizationScheme ? _authorizationScheme : "";
+ return authorizationScheme + base64.encode(utf8Encoder.encode(JSON.stringify(signedEvent)));
+ }
+ async function validateToken(token, url, method) {
+ const event = await unpackEventFromToken(token).catch((error) => {
+ throw error;
+ });
+ const valid = await validateEvent2(event, url, method).catch((error) => {
+ throw error;
+ });
+ return valid;
+ }
+ async function unpackEventFromToken(token) {
+ if (!token) {
+ throw new Error("Missing token");
+ }
+ token = token.replace(_authorizationScheme, "");
+ const eventB64 = utf8Decoder.decode(base64.decode(token));
+ if (!eventB64 || eventB64.length === 0 || !eventB64.startsWith("{")) {
+ throw new Error("Invalid token");
+ }
+ const event = JSON.parse(eventB64);
+ return event;
+ }
+ function validateEventTimestamp(event) {
+ if (!event.created_at) {
+ return false;
+ }
+ return Math.round(new Date().getTime() / 1e3) - event.created_at < 60;
+ }
+ function validateEventKind(event) {
+ return event.kind === HTTPAuth;
+ }
+ function validateEventUrlTag(event, url) {
+ const urlTag = event.tags.find((t) => t[0] === "u");
+ if (!urlTag) {
+ return false;
+ }
+ return urlTag.length > 0 && urlTag[1] === url;
+ }
+ function validateEventMethodTag(event, method) {
+ const methodTag = event.tags.find((t) => t[0] === "method");
+ if (!methodTag) {
+ return false;
+ }
+ return methodTag.length > 0 && methodTag[1].toLowerCase() === method.toLowerCase();
+ }
+ function hashPayload(payload) {
+ const hash3 = sha2562(utf8Encoder.encode(JSON.stringify(payload)));
+ return bytesToHex2(hash3);
+ }
+ function validateEventPayloadTag(event, payload) {
+ const payloadTag = event.tags.find((t) => t[0] === "payload");
+ if (!payloadTag) {
+ return false;
+ }
+ const payloadHash = hashPayload(payload);
+ return payloadTag.length > 0 && payloadTag[1] === payloadHash;
+ }
+ async function validateEvent2(event, url, method, body) {
+ if (!verifyEvent(event)) {
+ throw new Error("Invalid nostr event, signature invalid");
+ }
+ if (!validateEventKind(event)) {
+ throw new Error("Invalid nostr event, kind invalid");
+ }
+ if (!validateEventTimestamp(event)) {
+ throw new Error("Invalid nostr event, created_at timestamp invalid");
+ }
+ if (!validateEventUrlTag(event, url)) {
+ throw new Error("Invalid nostr event, url tag invalid");
+ }
+ if (!validateEventMethodTag(event, method)) {
+ throw new Error("Invalid nostr event, method tag invalid");
+ }
+ if (Boolean(body) && typeof body === "object" && Object.keys(body).length > 0) {
+ if (!validateEventPayloadTag(event, body)) {
+ throw new Error("Invalid nostr event, payload tag does not match request body hash");
+ }
+ }
+ return true;
+ }
+ return __toCommonJS(nostr_tools_exports);
+})();
diff --git a/lite/ui/modal.js b/lite/ui/modal.js
new file mode 100644
index 0000000..71cf7f5
--- /dev/null
+++ b/lite/ui/modal.js
@@ -0,0 +1,1009 @@
+/**
+ * NOSTR_LOGIN_LITE Modal UI
+ * Minimal vanilla JS modal to replace Stencil/Tailwind component library
+ */
+
+class Modal {
+ constructor(options) {
+ this.options = options;
+ this.container = null;
+ this.isVisible = false;
+ this.currentScreen = null;
+
+ // Initialize modal container and styles
+ this._initModal();
+ }
+
+ _initModal() {
+ // Create modal container
+ this.container = document.createElement('div');
+ this.container.id = 'nl-modal';
+ this.container.style.cssText = `
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.75);
+ display: none;
+ z-index: 10000;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ `;
+
+ // Create modal content
+ const modalContent = document.createElement('div');
+ modalContent.style.cssText = `
+ position: relative;
+ background: white;
+ width: 90%;
+ max-width: 400px;
+ margin: 50px auto;
+ border-radius: 12px;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+ max-height: 600px;
+ overflow: hidden;
+ `;
+
+ // Header
+ const modalHeader = document.createElement('div');
+ modalHeader.style.cssText = `
+ padding: 20px 24px 0 24px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ `;
+
+ const modalTitle = document.createElement('h2');
+ modalTitle.textContent = 'Nostr Login';
+ modalTitle.style.cssText = `
+ margin: 0;
+ font-size: 24px;
+ font-weight: 600;
+ color: #1f2937;
+ `;
+
+ const closeButton = document.createElement('button');
+ closeButton.innerHTML = '×';
+ closeButton.onclick = () => this.close();
+ closeButton.style.cssText = `
+ background: none;
+ border: none;
+ font-size: 28px;
+ color: #6b7280;
+ cursor: pointer;
+ padding: 0;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ `;
+ closeButton.onmouseover = () => closeButton.style.background = '#f3f4f6';
+ closeButton.onmouseout = () => closeButton.style.background = 'none';
+
+ modalHeader.appendChild(modalTitle);
+ modalHeader.appendChild(closeButton);
+
+ // Body
+ this.modalBody = document.createElement('div');
+ this.modalBody.style.cssText = `
+ padding: 24px;
+ overflow-y: auto;
+ max-height: 500px;
+ `;
+
+ modalContent.appendChild(modalHeader);
+ modalContent.appendChild(this.modalBody);
+ this.container.appendChild(modalContent);
+
+ // Add to body
+ document.body.appendChild(this.container);
+
+ // Click outside to close
+ this.container.onclick = (e) => {
+ if (e.target === this.container) {
+ this.close();
+ }
+ };
+
+ // Update theme
+ this.updateTheme();
+ }
+
+ updateTheme() {
+ const isDark = this.options?.darkMode;
+ const modalContent = this.container.querySelector(':nth-child(1)');
+ const title = this.container.querySelector('h2');
+
+ if (isDark) {
+ modalContent.style.background = '#1f2937';
+ title.style.color = 'white';
+ } else {
+ modalContent.style.background = 'white';
+ title.style.color = '#1f2937';
+ }
+ }
+
+ open(opts = {}) {
+ this.currentScreen = opts.startScreen;
+ this.isVisible = true;
+ this.container.style.display = 'block';
+
+ // Render login options
+ this._renderLoginOptions();
+ }
+
+ close() {
+ this.isVisible = false;
+ this.container.style.display = 'none';
+ this.modalBody.innerHTML = '';
+ }
+
+ _renderLoginOptions() {
+ this.modalBody.innerHTML = '';
+
+ const options = [];
+
+ // Extension option
+ if (this.options?.methods?.extension !== false) {
+ options.push({
+ type: 'extension',
+ title: 'Browser Extension',
+ description: 'Use your browser extension',
+ icon: '🔌'
+ });
+ }
+
+ // Local key option
+ if (this.options?.methods?.local !== false) {
+ options.push({
+ type: 'local',
+ title: 'Local Key',
+ description: 'Create or import your own key',
+ icon: '🔑'
+ });
+ }
+
+ // Nostr Connect option
+ if (this.options?.methods?.connect !== false) {
+ options.push({
+ type: 'connect',
+ title: 'Nostr Connect',
+ description: 'Connect with external signer',
+ icon: '🌐'
+ });
+ }
+
+ // Read-only option
+ if (this.options?.methods?.readonly !== false) {
+ options.push({
+ type: 'readonly',
+ title: 'Read Only',
+ description: 'Browse without signing',
+ icon: '👁️'
+ });
+ }
+
+ // OTP/DM option
+ if (this.options?.methods?.otp !== false && this.options?.otp) {
+ options.push({
+ type: 'otp',
+ title: 'DM/OTP',
+ description: 'Receive OTP via DM',
+ icon: '📱'
+ });
+ }
+
+ // Render each option
+ options.forEach(option => {
+ const button = document.createElement('button');
+ button.onclick = () => this._handleOptionClick(option.type);
+ button.style.cssText = `
+ display: flex;
+ align-items: center;
+ width: 100%;
+ padding: 16px;
+ margin-bottom: 12px;
+ background: ${this.options?.darkMode ? '#374151' : 'white'};
+ border: 1px solid ${this.options?.darkMode ? '#4b5563' : '#d1d5db'};
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s;
+ `;
+ button.onmouseover = () => {
+ button.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
+ };
+ button.onmouseout = () => {
+ button.style.boxShadow = 'none';
+ };
+
+ const iconDiv = document.createElement('div');
+ iconDiv.textContent = option.icon;
+ iconDiv.style.cssText = `
+ font-size: 24px;
+ margin-right: 16px;
+ width: 24px;
+ text-align: center;
+ `;
+
+ const contentDiv = document.createElement('div');
+ contentDiv.style.cssText = 'flex: 1; text-align: left;';
+
+ const titleDiv = document.createElement('div');
+ titleDiv.textContent = option.title;
+ titleDiv.style.cssText = `
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: ${this.options?.darkMode ? 'white' : '#1f2937'};
+ `;
+
+ const descDiv = document.createElement('div');
+ descDiv.textContent = option.description;
+ descDiv.style.cssText = `
+ font-size: 14px;
+ color: ${this.options?.darkMode ? '#9ca3af' : '#6b7280'};
+ `;
+
+ contentDiv.appendChild(titleDiv);
+ contentDiv.appendChild(descDiv);
+
+ button.appendChild(iconDiv);
+ button.appendChild(contentDiv);
+ this.modalBody.appendChild(button);
+ });
+ }
+
+ _handleOptionClick(type) {
+ console.log('Selected login type:', type);
+
+ // Handle different login types
+ switch (type) {
+ case 'extension':
+ this._handleExtension();
+ break;
+ case 'local':
+ this._showLocalKeyScreen();
+ break;
+ case 'connect':
+ this._showConnectScreen();
+ break;
+ case 'readonly':
+ this._handleReadonly();
+ break;
+ case 'otp':
+ this._showOtpScreen();
+ break;
+ }
+ }
+
+ _handleExtension() {
+ // Detect all available real extensions
+ const availableExtensions = this._detectAllExtensions();
+
+ if (availableExtensions.length === 0) {
+ console.log('Modal: No real extensions found');
+ this._showExtensionRequired();
+ } else if (availableExtensions.length === 1) {
+ console.log('Modal detected single extension:', availableExtensions[0].name);
+ this._tryExtensionLogin(availableExtensions[0].extension);
+ } else {
+ console.log('Modal detected multiple extensions:', availableExtensions.map(e => e.name));
+ this._showExtensionChoice(availableExtensions);
+ }
+ }
+
+ _detectAllExtensions() {
+ const extensions = [];
+
+ // Check navigator.nostr (NIP-07 standard location)
+ if (window.navigator?.nostr && typeof window.navigator.nostr.getPublicKey === 'function') {
+ extensions.push({
+ name: 'navigator.nostr',
+ displayName: 'Standard Extension (navigator.nostr)',
+ icon: '🌐',
+ extension: window.navigator.nostr
+ });
+ }
+
+ // Check webln.nostr (Alby WebLN)
+ if (window.webln?.nostr && typeof window.webln.nostr.getPublicKey === 'function') {
+ extensions.push({
+ name: 'webln.nostr',
+ displayName: 'Alby WebLN Extension',
+ icon: '⚡',
+ extension: window.webln.nostr
+ });
+ }
+
+ // Check alby.nostr (Alby direct)
+ if (window.alby?.nostr && typeof window.alby.nostr.getPublicKey === 'function') {
+ extensions.push({
+ name: 'alby.nostr',
+ displayName: 'Alby Extension (Direct)',
+ icon: '🐝',
+ extension: window.alby.nostr
+ });
+ }
+
+ // Check nos2x
+ if (window.nos2x && typeof window.nos2x.getPublicKey === 'function') {
+ extensions.push({
+ name: 'nos2x',
+ displayName: 'nos2x Extension',
+ icon: '🔌',
+ extension: window.nos2x
+ });
+ }
+
+ // Check window.nostr but make sure it's not our library
+ if (window.nostr && typeof window.nostr.getPublicKey === 'function') {
+ const isRealExtension = (
+ typeof window.nostr._hexToUint8Array !== 'function' && // Our library has this method
+ window.nostr.constructor.name !== 'Object' // Real extensions usually have proper constructors
+ );
+
+ if (isRealExtension) {
+ // Don't add if we already detected it via another path
+ const alreadyDetected = extensions.some(ext => ext.extension === window.nostr);
+ if (!alreadyDetected) {
+ extensions.push({
+ name: 'window.nostr',
+ displayName: 'Extension (window.nostr)',
+ icon: '🔑',
+ extension: window.nostr
+ });
+ }
+ }
+ }
+
+ return extensions;
+ }
+
+ _showExtensionChoice(extensions) {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Choose Browser Extension';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
+
+ const description = document.createElement('p');
+ description.textContent = `Found ${extensions.length} Nostr extensions. Choose which one to use:`;
+ description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(description);
+
+ // Create button for each extension
+ extensions.forEach((ext, index) => {
+ const button = document.createElement('button');
+ button.onclick = () => this._tryExtensionLogin(ext.extension);
+ button.style.cssText = `
+ display: flex;
+ align-items: center;
+ width: 100%;
+ padding: 16px;
+ margin-bottom: 12px;
+ background: ${this.options?.darkMode ? '#374151' : 'white'};
+ border: 1px solid ${this.options?.darkMode ? '#4b5563' : '#d1d5db'};
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s;
+ text-align: left;
+ `;
+
+ button.onmouseover = () => {
+ button.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
+ button.style.transform = 'translateY(-1px)';
+ };
+ button.onmouseout = () => {
+ button.style.boxShadow = 'none';
+ button.style.transform = 'none';
+ };
+
+ const iconDiv = document.createElement('div');
+ iconDiv.textContent = ext.icon;
+ iconDiv.style.cssText = `
+ font-size: 24px;
+ margin-right: 16px;
+ width: 24px;
+ text-align: center;
+ `;
+
+ const contentDiv = document.createElement('div');
+ contentDiv.style.cssText = 'flex: 1;';
+
+ const nameDiv = document.createElement('div');
+ nameDiv.textContent = ext.displayName;
+ nameDiv.style.cssText = `
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: ${this.options?.darkMode ? 'white' : '#1f2937'};
+ `;
+
+ const pathDiv = document.createElement('div');
+ pathDiv.textContent = ext.name;
+ pathDiv.style.cssText = `
+ font-size: 12px;
+ color: ${this.options?.darkMode ? '#9ca3af' : '#6b7280'};
+ font-family: monospace;
+ `;
+
+ contentDiv.appendChild(nameDiv);
+ contentDiv.appendChild(pathDiv);
+
+ button.appendChild(iconDiv);
+ button.appendChild(contentDiv);
+ this.modalBody.appendChild(button);
+ });
+
+ // Add back button
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back to Login Options';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 20px;';
+
+ this.modalBody.appendChild(backButton);
+ }
+
+ async _tryExtensionLogin(extensionObj) {
+ try {
+ // Show loading state
+ this.modalBody.innerHTML = '🔄 Connecting to extension...
';
+
+ // Get pubkey from extension
+ const pubkey = await extensionObj.getPublicKey();
+ console.log('Extension provided pubkey:', pubkey);
+
+ // Set extension method with the extension object
+ this._setAuthMethod('extension', { pubkey, extension: extensionObj });
+
+ } catch (error) {
+ console.error('Extension login failed:', error);
+ this._showError(`Extension login failed: ${error.message}`);
+ }
+ }
+
+ _showLocalKeyScreen() {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Local Key';
+ title.style.cssText = 'margin: 0 0 20px 0; font-size: 18px; font-weight: 600;';
+
+ const createButton = document.createElement('button');
+ createButton.textContent = 'Create New Key';
+ createButton.onclick = () => this._createLocalKey();
+ createButton.style.cssText = this._getButtonStyle();
+
+ const importButton = document.createElement('button');
+ importButton.textContent = 'Import Existing Key';
+ importButton.onclick = () => this._showImportKeyForm();
+ importButton.style.cssText = this._getButtonStyle() + 'margin-top: 12px;';
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = `
+ display: block;
+ margin-top: 20px;
+ padding: 12px;
+ background: #6b7280;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ `;
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(createButton);
+ this.modalBody.appendChild(importButton);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _createLocalKey() {
+ try {
+ const sk = window.NostrTools.generateSecretKey();
+ const pk = window.NostrTools.getPublicKey(sk);
+ const nsec = window.NostrTools.nip19.nsecEncode(sk);
+ const npub = window.NostrTools.nip19.npubEncode(pk);
+
+ this._showKeyDisplay(pk, nsec, 'created');
+ } catch (error) {
+ this._showError('Failed to create key: ' + error.message);
+ }
+ }
+
+ _showImportKeyForm() {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Import Local Key';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
+
+ const description = document.createElement('p');
+ description.textContent = 'Enter your secret key in either nsec or hex format:';
+ description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
+
+ const textarea = document.createElement('textarea');
+ textarea.placeholder = 'Enter your secret key:\n• nsec1... (bech32 format)\n• 64-character hex string';
+ textarea.style.cssText = `
+ width: 100%;
+ height: 100px;
+ padding: 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ margin-bottom: 12px;
+ resize: none;
+ font-family: monospace;
+ font-size: 14px;
+ box-sizing: border-box;
+ `;
+
+ // Add real-time format detection
+ const formatHint = document.createElement('div');
+ formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
+
+ textarea.oninput = () => {
+ const value = textarea.value.trim();
+ if (!value) {
+ formatHint.textContent = '';
+ return;
+ }
+
+ const format = this._detectKeyFormat(value);
+ if (format === 'nsec') {
+ formatHint.textContent = '✅ Valid nsec format detected';
+ formatHint.style.color = '#059669';
+ } else if (format === 'hex') {
+ formatHint.textContent = '✅ Valid hex format detected';
+ formatHint.style.color = '#059669';
+ } else {
+ formatHint.textContent = '❌ Invalid key format - must be nsec1... or 64-character hex';
+ formatHint.style.color = '#dc2626';
+ }
+ };
+
+ const importButton = document.createElement('button');
+ importButton.textContent = 'Import Key';
+ importButton.onclick = () => this._importLocalKey(textarea.value);
+ importButton.style.cssText = this._getButtonStyle();
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._showLocalKeyScreen();
+ backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(description);
+ this.modalBody.appendChild(textarea);
+ this.modalBody.appendChild(formatHint);
+ this.modalBody.appendChild(importButton);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _detectKeyFormat(keyValue) {
+ const trimmed = keyValue.trim();
+
+ // Check for nsec format
+ if (trimmed.startsWith('nsec1') && trimmed.length === 63) {
+ try {
+ window.NostrTools.nip19.decode(trimmed);
+ return 'nsec';
+ } catch {
+ return 'invalid';
+ }
+ }
+
+ // Check for hex format (64 characters, valid hex)
+ if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) {
+ return 'hex';
+ }
+
+ return 'invalid';
+ }
+
+ _importLocalKey(keyValue) {
+ try {
+ const trimmed = keyValue.trim();
+ if (!trimmed) {
+ throw new Error('Please enter a secret key');
+ }
+
+ const format = this._detectKeyFormat(trimmed);
+ let sk;
+
+ if (format === 'nsec') {
+ // Decode nsec format - this returns Uint8Array
+ const decoded = window.NostrTools.nip19.decode(trimmed);
+ if (decoded.type !== 'nsec') {
+ throw new Error('Invalid nsec format');
+ }
+ sk = decoded.data; // This is already Uint8Array
+ } else if (format === 'hex') {
+ // Convert hex string to Uint8Array
+ sk = this._hexToUint8Array(trimmed);
+ // Test that it's a valid secret key by trying to get public key
+ window.NostrTools.getPublicKey(sk);
+ } else {
+ throw new Error('Invalid key format. Please enter either nsec1... or 64-character hex string');
+ }
+
+ // Generate public key and encoded formats
+ const pk = window.NostrTools.getPublicKey(sk);
+ const nsec = window.NostrTools.nip19.nsecEncode(sk);
+ const npub = window.NostrTools.nip19.npubEncode(pk);
+
+ this._showKeyDisplay(pk, nsec, 'imported');
+ } catch (error) {
+ this._showError('Invalid key: ' + error.message);
+ }
+ }
+
+ _hexToUint8Array(hex) {
+ // Convert hex string to Uint8Array
+ if (hex.length % 2 !== 0) {
+ throw new Error('Invalid hex string length');
+ }
+ const bytes = new Uint8Array(hex.length / 2);
+ for (let i = 0; i < bytes.length; i++) {
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
+ }
+ return bytes;
+ }
+
+ _showKeyDisplay(pubkey, nsec, action) {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = `Key ${action} successfully!`;
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #059669;';
+
+ const warningDiv = document.createElement('div');
+ warningDiv.textContent = '⚠️ Save your secret key securely!';
+ warningDiv.style.cssText = 'background: #fef3c7; color: #92400e; padding: 12px; border-radius: 6px; margin-bottom: 16px; font-size: 14px;';
+
+ const nsecDiv = document.createElement('div');
+ nsecDiv.innerHTML = `Your Secret Key:
${nsec}`;
+ nsecDiv.style.cssText = 'margin-bottom: 16px; font-size: 14px;';
+
+ const npubDiv = document.createElement('div');
+ npubDiv.innerHTML = `Your Public Key:
${window.NostrTools.nip19.npubEncode(pubkey)}`;
+ npubDiv.style.cssText = 'margin-bottom: 16px; font-size: 14px;';
+
+ const continueButton = document.createElement('button');
+ continueButton.textContent = 'Continue';
+ continueButton.onclick = () => this._setAuthMethod('local', { secret: nsec, pubkey });
+ continueButton.style.cssText = this._getButtonStyle();
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(warningDiv);
+ this.modalBody.appendChild(nsecDiv);
+ this.modalBody.appendChild(npubDiv);
+ this.modalBody.appendChild(continueButton);
+ }
+
+ _setAuthMethod(method, options = {}) {
+ // Emit auth method selection
+ const event = new CustomEvent('nlMethodSelected', {
+ detail: { method, ...options }
+ });
+ window.dispatchEvent(event);
+
+ this.close();
+ }
+
+ _showError(message) {
+ this.modalBody.innerHTML = '';
+
+ const errorDiv = document.createElement('div');
+ errorDiv.style.cssText = 'background: #fee2e2; color: #dc2626; padding: 16px; border-radius: 6px; margin-bottom: 16px;';
+ errorDiv.innerHTML = `Error: ${message}`;
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary');
+
+ this.modalBody.appendChild(errorDiv);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _showExtensionRequired() {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Browser Extension Required';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
+
+ const message = document.createElement('p');
+ message.textContent = 'Please install a Nostr browser extension like Alby or getflattr and refresh the page.';
+ message.style.cssText = 'margin-bottom: 20px; color: #6b7280;';
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary');
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(message);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _showConnectScreen() {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Connect to NIP-46 Remote Signer';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
+
+ const description = document.createElement('p');
+ description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.';
+ description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
+
+ const formGroup = document.createElement('div');
+ formGroup.style.cssText = 'margin-bottom: 20px;';
+
+ const label = document.createElement('label');
+ label.textContent = 'Bunker Public Key:';
+ label.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;';
+
+ const pubkeyInput = document.createElement('input');
+ pubkeyInput.type = 'text';
+ pubkeyInput.placeholder = 'bunker://pubkey?relay=..., bunker:hex, hex, or npub...';
+ pubkeyInput.style.cssText = `
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ margin-bottom: 12px;
+ font-family: monospace;
+ box-sizing: border-box;
+ `;
+
+ const urlLabel = document.createElement('label');
+ urlLabel.textContent = 'Remote URL (optional):';
+ urlLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;';
+
+ const urlInput = document.createElement('input');
+ urlInput.type = 'url';
+ urlInput.placeholder = 'ws://localhost:8080 (default)';
+ urlInput.style.cssText = `
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ margin-bottom: 16px;
+ box-sizing: border-box;
+ `;
+
+ // Pre-fill with our bunker config if available
+ if (window.NIP46_BUNKER_CONFIG) {
+ pubkeyInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.pubkey;
+ urlInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.url;
+ }
+
+ const connectButton = document.createElement('button');
+ connectButton.textContent = 'Connect to Bunker';
+ connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value, urlInput.value);
+ connectButton.style.cssText = this._getButtonStyle();
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
+
+ formGroup.appendChild(label);
+ formGroup.appendChild(pubkeyInput);
+ formGroup.appendChild(urlLabel);
+ formGroup.appendChild(urlInput);
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(description);
+ this.modalBody.appendChild(formGroup);
+ this.modalBody.appendChild(connectButton);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _handleNip46Connect(bunkerPubkey, bunkerUrl) {
+ if (!bunkerPubkey || !bunkerPubkey.length) {
+ this._showError('Bunker pubkey is required');
+ return;
+ }
+
+ this._showNip46Connecting(bunkerPubkey, bunkerUrl);
+ this._performNip46Connect(bunkerPubkey, bunkerUrl);
+ }
+
+ _showNip46Connecting(bunkerPubkey, bunkerUrl) {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Connecting to Remote Signer...';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #059669;';
+
+ const description = document.createElement('p');
+ description.textContent = 'Establishing secure connection to your remote signer.';
+ description.style.cssText = 'margin-bottom: 20px; color: #6b7280;';
+
+ // Normalize bunker pubkey for display (= show original format if bunker: prefix)
+ const displayPubkey = bunkerPubkey.startsWith('bunker:') || bunkerPubkey.startsWith('npub') || bunkerPubkey.length === 64 ? bunkerPubkey : bunkerPubkey;
+
+ const bunkerInfo = document.createElement('div');
+ bunkerInfo.style.cssText = 'background: #f1f5f9; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;';
+ bunkerInfo.innerHTML = `
+ Connecting to bunker:
+ Pubkey: ${displayPubkey}
+ Relay: ${bunkerUrl || 'ws://localhost:8080'}
+ If this relay is offline, the bunker server may be unavailable.
+ `;
+
+ const connectingDiv = document.createElement('div');
+ connectingDiv.style.cssText = 'text-align: center; color: #6b7280;';
+ connectingDiv.innerHTML = `
+ ⏳
+ Please wait while we establish the connection...
+ This may take a few seconds
+ `;
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(description);
+ this.modalBody.appendChild(bunkerInfo);
+ this.modalBody.appendChild(connectingDiv);
+ }
+
+ async _performNip46Connect(bunkerPubkey, bunkerUrl) {
+ try {
+ console.log('Starting NIP-46 connection to bunker:', bunkerPubkey, bunkerUrl);
+
+ // Check if nostr-tools NIP-46 is available
+ if (!window.NostrTools?.nip46) {
+ throw new Error('nostr-tools NIP-46 module not available');
+ }
+
+ // Use nostr-tools to parse bunker input - this handles all formats correctly
+ console.log('Parsing bunker input with nostr-tools...');
+ const bunkerPointer = await window.NostrTools.nip46.parseBunkerInput(bunkerPubkey);
+
+ if (!bunkerPointer) {
+ throw new Error('Unable to parse bunker connection string or resolve NIP-05 identifier');
+ }
+
+ console.log('Parsed bunker pointer:', bunkerPointer);
+
+ // Create local client keypair for this session
+ const localSecretKey = window.NostrTools.generateSecretKey();
+ console.log('Generated local client keypair for NIP-46 session');
+
+ // Use nostr-tools BunkerSigner.fromBunker() for bunker:// connections
+ console.log('Creating nip46 BunkerSigner...');
+ const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, {
+ onauth: (url) => {
+ console.log('Received auth URL from bunker:', url);
+ // Open auth URL in popup or redirect
+ window.open(url, '_blank', 'width=600,height=800');
+ }
+ });
+
+ console.log('NIP-46 BunkerSigner created successfully');
+
+ // Attempt initial ping to verify connection
+ console.log('Testing bunker connection with ping...');
+ await signer.ping();
+ console.log('NIP-46 ping successful - bunker is reachable');
+
+ // Try to connect (this may trigger auth flow)
+ console.log('Attempting NIP-46 connect...');
+ await signer.connect();
+ console.log('NIP-46 connect successful');
+
+ // Get the user's public key from the bunker
+ console.log('Getting public key from bunker...');
+ const userPubkey = await signer.getPublicKey();
+ console.log('NIP-46 user public key:', userPubkey);
+
+ // Store the NIP-46 authentication info
+ const nip46Info = {
+ pubkey: userPubkey,
+ signer: {
+ method: 'nip46',
+ remotePubkey: bunkerPointer.pubkey,
+ bunkerSigner: signer,
+ secret: bunkerPointer.secret,
+ relays: bunkerPointer.relays
+ }
+ };
+
+ console.log('NOSTR_LOGIN_LITE NIP-46 connection established successfully!');
+
+ // Set as current auth method
+ this._setAuthMethod('nip46', nip46Info);
+ return;
+
+ } catch (error) {
+ console.error('NIP-46 connection failed:', error);
+ this._showNip46Error(error.message);
+ }
+ }
+
+ _showNip46Error(message) {
+ this.modalBody.innerHTML = '';
+
+ const title = document.createElement('h3');
+ title.textContent = 'Connection Failed';
+ title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #dc2626;';
+
+ const errorMsg = document.createElement('p');
+ errorMsg.textContent = `Unable to connect to remote signer: ${message}`;
+ errorMsg.style.cssText = 'margin-bottom: 20px; color: #6b7280;';
+
+ const retryButton = document.createElement('button');
+ retryButton.textContent = 'Try Again';
+ retryButton.onclick = () => this._showConnectScreen();
+ retryButton.style.cssText = this._getButtonStyle();
+
+ const backButton = document.createElement('button');
+ backButton.textContent = 'Back to Options';
+ backButton.onclick = () => this._renderLoginOptions();
+ backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
+
+ this.modalBody.appendChild(title);
+ this.modalBody.appendChild(errorMsg);
+ this.modalBody.appendChild(retryButton);
+ this.modalBody.appendChild(backButton);
+ }
+
+ _handleReadonly() {
+ // Set read-only mode
+ this._setAuthMethod('readonly');
+ }
+
+ _showOtpScreen() {
+ // Placeholder for OTP functionality
+ this._showError('OTP/DM not yet implemented - coming soon!');
+ }
+
+ _getButtonStyle(type = 'primary') {
+ const baseStyle = `
+ display: block;
+ width: 100%;
+ padding: 12px;
+ border: none;
+ border-radius: 8px;
+ font-size: 16px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+ `;
+
+ if (type === 'primary') {
+ return baseStyle + `
+ background: #3b82f6;
+ color: white;
+ `;
+ } else {
+ return baseStyle + `
+ background: #6b7280;
+ color: white;
+ `;
+ }
+ }
+
+ // Public API
+ static init(options) {
+ if (Modal.instance) return Modal.instance;
+ Modal.instance = new Modal(options);
+ return Modal.instance;
+ }
+
+ static getInstance() {
+ return Modal.instance;
+ }
+}
+
+// Initialize global instance
+let modalInstance = null;
+
+window.addEventListener('load', () => {
+ modalInstance = new Modal();
+});
\ No newline at end of file