Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f34c2de73 | ||
|
|
ca75df8bb4 | ||
|
|
c747f1f315 | ||
|
|
2a66b5eeec | ||
|
|
fa9688b17e |
1541
lite/build.js
1541
lite/build.js
File diff suppressed because it is too large
Load Diff
1991
lite/nostr-lite.js
1991
lite/nostr-lite.js
File diff suppressed because it is too large
Load Diff
5456
lite/nostr.bundle.js
5456
lite/nostr.bundle.js
File diff suppressed because it is too large
Load Diff
446
lite/ui/modal.js
446
lite/ui/modal.js
@@ -58,7 +58,7 @@ class Modal {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// Modal content: centered with margin
|
// Modal content: centered with margin, no fixed height
|
||||||
modalContent.style.cssText = `
|
modalContent.style.cssText = `
|
||||||
position: relative;
|
position: relative;
|
||||||
background: var(--nl-secondary-color);
|
background: var(--nl-secondary-color);
|
||||||
@@ -68,7 +68,6 @@ class Modal {
|
|||||||
margin: 50px auto;
|
margin: 50px auto;
|
||||||
border-radius: var(--nl-border-radius, 15px);
|
border-radius: var(--nl-border-radius, 15px);
|
||||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||||
max-height: 600px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -105,7 +104,7 @@ class Modal {
|
|||||||
closeButton.style.cssText = `
|
closeButton.style.cssText = `
|
||||||
background: var(--nl-secondary-color);
|
background: var(--nl-secondary-color);
|
||||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||||
border-radius: var(--nl-border-radius);
|
border-radius: 4px;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--nl-primary-color);
|
color: var(--nl-primary-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -133,8 +132,6 @@ class Modal {
|
|||||||
this.modalBody = document.createElement('div');
|
this.modalBody = document.createElement('div');
|
||||||
this.modalBody.style.cssText = `
|
this.modalBody.style.cssText = `
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 500px;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font-family: var(--nl-font-family, 'Courier New', monospace);
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
`;
|
`;
|
||||||
@@ -223,6 +220,16 @@ class Modal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seed Phrase option - only show if explicitly enabled
|
||||||
|
if (this.options?.methods?.seedphrase === true) {
|
||||||
|
options.push({
|
||||||
|
type: 'seedphrase',
|
||||||
|
title: 'Seed Phrase',
|
||||||
|
description: 'Import from mnemonic seed phrase',
|
||||||
|
icon: '🌱'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Nostr Connect option (check both 'connect' and 'remote' for compatibility)
|
// Nostr Connect option (check both 'connect' and 'remote' for compatibility)
|
||||||
if (this.options?.methods?.connect !== false && this.options?.methods?.remote !== false) {
|
if (this.options?.methods?.connect !== false && this.options?.methods?.remote !== false) {
|
||||||
options.push({
|
options.push({
|
||||||
@@ -280,6 +287,19 @@ class Modal {
|
|||||||
button.style.background = 'var(--nl-secondary-color)';
|
button.style.background = 'var(--nl-secondary-color)';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const iconDiv = document.createElement('div');
|
||||||
|
// Remove the icon entirely - no emojis or text-based icons
|
||||||
|
iconDiv.textContent = '';
|
||||||
|
iconDiv.style.cssText = `
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 16px;
|
||||||
|
width: 0px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--nl-primary-color);
|
||||||
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
|
`;
|
||||||
|
|
||||||
const contentDiv = document.createElement('div');
|
const contentDiv = document.createElement('div');
|
||||||
contentDiv.style.cssText = 'flex: 1; text-align: left;';
|
contentDiv.style.cssText = 'flex: 1; text-align: left;';
|
||||||
|
|
||||||
@@ -303,6 +323,7 @@ class Modal {
|
|||||||
contentDiv.appendChild(titleDiv);
|
contentDiv.appendChild(titleDiv);
|
||||||
contentDiv.appendChild(descDiv);
|
contentDiv.appendChild(descDiv);
|
||||||
|
|
||||||
|
button.appendChild(iconDiv);
|
||||||
button.appendChild(contentDiv);
|
button.appendChild(contentDiv);
|
||||||
this.modalBody.appendChild(button);
|
this.modalBody.appendChild(button);
|
||||||
});
|
});
|
||||||
@@ -319,6 +340,9 @@ class Modal {
|
|||||||
case 'local':
|
case 'local':
|
||||||
this._showLocalKeyScreen();
|
this._showLocalKeyScreen();
|
||||||
break;
|
break;
|
||||||
|
case 'seedphrase':
|
||||||
|
this._showSeedPhraseScreen();
|
||||||
|
break;
|
||||||
case 'connect':
|
case 'connect':
|
||||||
this._showConnectScreen();
|
this._showConnectScreen();
|
||||||
break;
|
break;
|
||||||
@@ -1165,10 +1189,6 @@ class Modal {
|
|||||||
_showConnectScreen() {
|
_showConnectScreen() {
|
||||||
this.modalBody.innerHTML = '';
|
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');
|
const description = document.createElement('p');
|
||||||
description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.';
|
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;';
|
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||||
@@ -1193,12 +1213,67 @@ class Modal {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Users will enter the complete bunker connection string with relay info
|
// Add real-time bunker key validation
|
||||||
|
const formatHint = document.createElement('div');
|
||||||
|
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
|
||||||
|
|
||||||
const connectButton = document.createElement('button');
|
const connectButton = document.createElement('button');
|
||||||
connectButton.textContent = 'Connect to Bunker';
|
connectButton.textContent = 'Connect to Bunker';
|
||||||
connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value);
|
connectButton.disabled = true;
|
||||||
connectButton.style.cssText = this._getButtonStyle();
|
connectButton.onclick = () => {
|
||||||
|
if (!connectButton.disabled) {
|
||||||
|
this._handleNip46Connect(pubkeyInput.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial disabled state
|
||||||
|
connectButton.style.cssText = `
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: var(--nl-border-width) solid var(--nl-muted-color);
|
||||||
|
border-radius: var(--nl-border-radius);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
|
background: var(--nl-secondary-color);
|
||||||
|
color: var(--nl-muted-color);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
pubkeyInput.oninput = () => {
|
||||||
|
const value = pubkeyInput.value.trim();
|
||||||
|
if (!value) {
|
||||||
|
formatHint.textContent = '';
|
||||||
|
// Disable button
|
||||||
|
connectButton.disabled = true;
|
||||||
|
connectButton.style.borderColor = 'var(--nl-muted-color)';
|
||||||
|
connectButton.style.color = 'var(--nl-muted-color)';
|
||||||
|
connectButton.style.cursor = 'not-allowed';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = this._validateBunkerKey(value);
|
||||||
|
if (isValid) {
|
||||||
|
formatHint.textContent = '✅ Valid bunker connection format detected';
|
||||||
|
formatHint.style.color = '#059669';
|
||||||
|
// Enable button
|
||||||
|
connectButton.disabled = false;
|
||||||
|
connectButton.style.borderColor = 'var(--nl-primary-color)';
|
||||||
|
connectButton.style.color = 'var(--nl-primary-color)';
|
||||||
|
connectButton.style.cursor = 'pointer';
|
||||||
|
} else {
|
||||||
|
formatHint.textContent = '❌ Invalid format - must be bunker://, npub, or 64-char hex';
|
||||||
|
formatHint.style.color = '#dc2626';
|
||||||
|
// Disable button
|
||||||
|
connectButton.disabled = true;
|
||||||
|
connectButton.style.borderColor = 'var(--nl-muted-color)';
|
||||||
|
connectButton.style.color = 'var(--nl-muted-color)';
|
||||||
|
connectButton.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const backButton = document.createElement('button');
|
const backButton = document.createElement('button');
|
||||||
backButton.textContent = 'Back';
|
backButton.textContent = 'Back';
|
||||||
@@ -1207,14 +1282,49 @@ class Modal {
|
|||||||
|
|
||||||
formGroup.appendChild(label);
|
formGroup.appendChild(label);
|
||||||
formGroup.appendChild(pubkeyInput);
|
formGroup.appendChild(pubkeyInput);
|
||||||
|
formGroup.appendChild(formatHint);
|
||||||
|
|
||||||
this.modalBody.appendChild(title);
|
|
||||||
this.modalBody.appendChild(description);
|
this.modalBody.appendChild(description);
|
||||||
this.modalBody.appendChild(formGroup);
|
this.modalBody.appendChild(formGroup);
|
||||||
this.modalBody.appendChild(connectButton);
|
this.modalBody.appendChild(connectButton);
|
||||||
this.modalBody.appendChild(backButton);
|
this.modalBody.appendChild(backButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_validateBunkerKey(bunkerKey) {
|
||||||
|
try {
|
||||||
|
const trimmed = bunkerKey.trim();
|
||||||
|
|
||||||
|
// Check for bunker:// format
|
||||||
|
if (trimmed.startsWith('bunker://')) {
|
||||||
|
// Should have format: bunker://pubkey or bunker://pubkey?param=value
|
||||||
|
const match = trimmed.match(/^bunker:\/\/([0-9a-fA-F]{64})(\?.*)?$/);
|
||||||
|
return !!match;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for npub format
|
||||||
|
if (trimmed.startsWith('npub1') && trimmed.length === 63) {
|
||||||
|
try {
|
||||||
|
if (window.NostrTools?.nip19) {
|
||||||
|
const decoded = window.NostrTools.nip19.decode(trimmed);
|
||||||
|
return decoded.type === 'npub';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for hex format (64 characters, valid hex)
|
||||||
|
if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Bunker key validation failed:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_handleNip46Connect(bunkerPubkey) {
|
_handleNip46Connect(bunkerPubkey) {
|
||||||
if (!bunkerPubkey || !bunkerPubkey.length) {
|
if (!bunkerPubkey || !bunkerPubkey.length) {
|
||||||
this._showError('Bunker pubkey is required');
|
this._showError('Bunker pubkey is required');
|
||||||
@@ -1284,9 +1394,9 @@ class Modal {
|
|||||||
const localSecretKey = window.NostrTools.generateSecretKey();
|
const localSecretKey = window.NostrTools.generateSecretKey();
|
||||||
console.log('Generated local client keypair for NIP-46 session');
|
console.log('Generated local client keypair for NIP-46 session');
|
||||||
|
|
||||||
// Use nostr-tools BunkerSigner constructor
|
// Use nostr-tools BunkerSigner factory method (not constructor - it's private)
|
||||||
console.log('Creating nip46 BunkerSigner...');
|
console.log('Creating nip46 BunkerSigner...');
|
||||||
const signer = new window.NostrTools.nip46.BunkerSigner(localSecretKey, bunkerPointer, {
|
const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, {
|
||||||
onauth: (url) => {
|
onauth: (url) => {
|
||||||
console.log('Received auth URL from bunker:', url);
|
console.log('Received auth URL from bunker:', url);
|
||||||
// Open auth URL in popup or redirect
|
// Open auth URL in popup or redirect
|
||||||
@@ -1363,6 +1473,312 @@ class Modal {
|
|||||||
this._setAuthMethod('readonly');
|
this._setAuthMethod('readonly');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_showSeedPhraseScreen() {
|
||||||
|
this.modalBody.innerHTML = '';
|
||||||
|
|
||||||
|
const description = document.createElement('p');
|
||||||
|
description.innerHTML = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts, or <span id="generate-new" style="text-decoration: underline; cursor: pointer; color: var(--nl-primary-color);">generate new</span>.';
|
||||||
|
description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
|
||||||
|
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
// Remove default placeholder text as requested
|
||||||
|
textarea.placeholder = '';
|
||||||
|
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 mnemonic validation
|
||||||
|
const formatHint = document.createElement('div');
|
||||||
|
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
|
||||||
|
|
||||||
|
const importButton = document.createElement('button');
|
||||||
|
importButton.textContent = 'Import Accounts';
|
||||||
|
importButton.disabled = true;
|
||||||
|
importButton.onclick = () => {
|
||||||
|
if (!importButton.disabled) {
|
||||||
|
this._importFromSeedPhrase(textarea.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial disabled state
|
||||||
|
importButton.style.cssText = `
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: var(--nl-border-width) solid var(--nl-muted-color);
|
||||||
|
border-radius: var(--nl-border-radius);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
|
background: var(--nl-secondary-color);
|
||||||
|
color: var(--nl-muted-color);
|
||||||
|
`;
|
||||||
|
|
||||||
|
textarea.oninput = () => {
|
||||||
|
const value = textarea.value.trim();
|
||||||
|
if (!value) {
|
||||||
|
formatHint.textContent = '';
|
||||||
|
// Disable button
|
||||||
|
importButton.disabled = true;
|
||||||
|
importButton.style.borderColor = 'var(--nl-muted-color)';
|
||||||
|
importButton.style.color = 'var(--nl-muted-color)';
|
||||||
|
importButton.style.cursor = 'not-allowed';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = this._validateMnemonic(value);
|
||||||
|
if (isValid) {
|
||||||
|
const wordCount = value.split(/\s+/).length;
|
||||||
|
formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`;
|
||||||
|
formatHint.style.color = '#059669';
|
||||||
|
// Enable button
|
||||||
|
importButton.disabled = false;
|
||||||
|
importButton.style.borderColor = 'var(--nl-primary-color)';
|
||||||
|
importButton.style.color = 'var(--nl-primary-color)';
|
||||||
|
importButton.style.cursor = 'pointer';
|
||||||
|
} else {
|
||||||
|
formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words';
|
||||||
|
formatHint.style.color = '#dc2626';
|
||||||
|
// Disable button
|
||||||
|
importButton.disabled = true;
|
||||||
|
importButton.style.borderColor = 'var(--nl-muted-color)';
|
||||||
|
importButton.style.color = 'var(--nl-muted-color)';
|
||||||
|
importButton.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const backButton = document.createElement('button');
|
||||||
|
backButton.textContent = 'Back';
|
||||||
|
backButton.onclick = () => this._renderLoginOptions();
|
||||||
|
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
|
||||||
|
|
||||||
|
this.modalBody.appendChild(description);
|
||||||
|
this.modalBody.appendChild(textarea);
|
||||||
|
this.modalBody.appendChild(formatHint);
|
||||||
|
this.modalBody.appendChild(importButton);
|
||||||
|
this.modalBody.appendChild(backButton);
|
||||||
|
|
||||||
|
// Add click handler for the "generate new" link
|
||||||
|
const generateLink = document.getElementById('generate-new');
|
||||||
|
if (generateLink) {
|
||||||
|
generateLink.addEventListener('mouseenter', () => {
|
||||||
|
generateLink.style.color = 'var(--nl-accent-color)';
|
||||||
|
});
|
||||||
|
generateLink.addEventListener('mouseleave', () => {
|
||||||
|
generateLink.style.color = 'var(--nl-primary-color)';
|
||||||
|
});
|
||||||
|
generateLink.addEventListener('click', () => {
|
||||||
|
this._generateNewSeedPhrase(textarea, formatHint);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_generateNewSeedPhrase(textarea, formatHint) {
|
||||||
|
try {
|
||||||
|
// Check if NIP-06 is available
|
||||||
|
if (!window.NostrTools?.nip06) {
|
||||||
|
throw new Error('NIP-06 not available in bundle');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random 12-word mnemonic using NostrTools
|
||||||
|
const mnemonic = window.NostrTools.nip06.generateSeedWords();
|
||||||
|
|
||||||
|
// Set the generated mnemonic in the textarea
|
||||||
|
textarea.value = mnemonic;
|
||||||
|
|
||||||
|
// Trigger the oninput event to properly validate and enable the button
|
||||||
|
if (textarea.oninput) {
|
||||||
|
textarea.oninput();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to generate seed phrase:', error);
|
||||||
|
formatHint.textContent = '❌ Failed to generate seed phrase - NIP-06 not available';
|
||||||
|
formatHint.style.color = '#dc2626';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateMnemonic(mnemonic) {
|
||||||
|
try {
|
||||||
|
// Check if NIP-06 is available
|
||||||
|
if (!window.NostrTools?.nip06) {
|
||||||
|
console.error('NIP-06 not available in bundle');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = mnemonic.trim().split(/\s+/);
|
||||||
|
|
||||||
|
// Must be 12 or 24 words
|
||||||
|
if (words.length !== 12 && words.length !== 24) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to validate using NostrTools nip06 - this will throw if invalid
|
||||||
|
window.NostrTools.nip06.privateKeyFromSeedWords(mnemonic, '', 0);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Mnemonic validation failed:', error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_importFromSeedPhrase(mnemonic) {
|
||||||
|
try {
|
||||||
|
const trimmed = mnemonic.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
throw new Error('Please enter a mnemonic seed phrase');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the mnemonic
|
||||||
|
if (!this._validateMnemonic(trimmed)) {
|
||||||
|
throw new Error('Invalid mnemonic. Please enter a valid 12 or 24-word BIP-39 seed phrase');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate accounts 0-5 using NIP-06
|
||||||
|
const accounts = [];
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
try {
|
||||||
|
const privateKey = window.NostrTools.nip06.privateKeyFromSeedWords(trimmed, '', i);
|
||||||
|
const publicKey = window.NostrTools.getPublicKey(privateKey);
|
||||||
|
const nsec = window.NostrTools.nip19.nsecEncode(privateKey);
|
||||||
|
const npub = window.NostrTools.nip19.npubEncode(publicKey);
|
||||||
|
|
||||||
|
accounts.push({
|
||||||
|
index: i,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
nsec,
|
||||||
|
npub
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to derive account ${i}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accounts.length === 0) {
|
||||||
|
throw new Error('Failed to derive any accounts from seed phrase');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Successfully derived ${accounts.length} accounts from seed phrase`);
|
||||||
|
this._showAccountSelection(accounts);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Seed phrase import failed:', error);
|
||||||
|
this._showError('Seed phrase import failed: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_showAccountSelection(accounts) {
|
||||||
|
this.modalBody.innerHTML = '';
|
||||||
|
|
||||||
|
const description = document.createElement('p');
|
||||||
|
description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`;
|
||||||
|
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||||
|
|
||||||
|
this.modalBody.appendChild(description);
|
||||||
|
|
||||||
|
// Create table for account selection
|
||||||
|
const table = document.createElement('table');
|
||||||
|
table.style.cssText = `
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Table header
|
||||||
|
const thead = document.createElement('thead');
|
||||||
|
thead.innerHTML = `
|
||||||
|
<tr style="background: #f3f4f6;">
|
||||||
|
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">#</th>
|
||||||
|
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Use</th>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
table.appendChild(thead);
|
||||||
|
|
||||||
|
// Table body
|
||||||
|
const tbody = document.createElement('tbody');
|
||||||
|
accounts.forEach(account => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.style.cssText = 'border: 1px solid #d1d5db;';
|
||||||
|
|
||||||
|
const indexCell = document.createElement('td');
|
||||||
|
indexCell.textContent = account.index;
|
||||||
|
indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;';
|
||||||
|
|
||||||
|
const actionCell = document.createElement('td');
|
||||||
|
actionCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db;';
|
||||||
|
|
||||||
|
// Show truncated npub in the button
|
||||||
|
const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`;
|
||||||
|
|
||||||
|
const selectButton = document.createElement('button');
|
||||||
|
selectButton.textContent = truncatedNpub;
|
||||||
|
selectButton.onclick = () => this._selectAccount(account);
|
||||||
|
selectButton.style.cssText = `
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
background: var(--nl-secondary-color);
|
||||||
|
color: var(--nl-primary-color);
|
||||||
|
border: 1px solid var(--nl-primary-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
selectButton.onmouseover = () => {
|
||||||
|
selectButton.style.borderColor = 'var(--nl-accent-color)';
|
||||||
|
};
|
||||||
|
selectButton.onmouseout = () => {
|
||||||
|
selectButton.style.borderColor = 'var(--nl-primary-color)';
|
||||||
|
};
|
||||||
|
|
||||||
|
actionCell.appendChild(selectButton);
|
||||||
|
|
||||||
|
row.appendChild(indexCell);
|
||||||
|
row.appendChild(actionCell);
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
table.appendChild(tbody);
|
||||||
|
|
||||||
|
this.modalBody.appendChild(table);
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
const backButton = document.createElement('button');
|
||||||
|
backButton.textContent = 'Back to Seed Phrase';
|
||||||
|
backButton.onclick = () => this._showSeedPhraseScreen();
|
||||||
|
backButton.style.cssText = this._getButtonStyle('secondary');
|
||||||
|
|
||||||
|
this.modalBody.appendChild(backButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectAccount(account) {
|
||||||
|
console.log('Selected account:', account.index, account.npub);
|
||||||
|
|
||||||
|
// Use the same auth method as local keys, but with seedphrase identifier
|
||||||
|
this._setAuthMethod('local', {
|
||||||
|
secret: account.nsec,
|
||||||
|
pubkey: account.publicKey,
|
||||||
|
source: 'seedphrase',
|
||||||
|
accountIndex: account.index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_showOtpScreen() {
|
_showOtpScreen() {
|
||||||
// Placeholder for OTP functionality
|
// Placeholder for OTP functionality
|
||||||
this._showError('OTP/DM not yet implemented - coming soon!');
|
this._showError('OTP/DM not yet implemented - coming soon!');
|
||||||
|
|||||||
1
nostr-tools
Submodule
1
nostr-tools
Submodule
Submodule nostr-tools added at 23aebbd341
Reference in New Issue
Block a user