aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api.js4
-rw-r--r--src/app.js215
-rw-r--r--src/features/ark.js32
-rw-r--r--src/features/nostrlogin.js2
-rw-r--r--src/features/nwc.js2
-rw-r--r--src/features/sync.js2
-rw-r--r--src/i18n.js2
-rw-r--r--src/style.css3
8 files changed, 133 insertions, 129 deletions
diff --git a/src/api.js b/src/api.js
index 5824120..42e953a 100644
--- a/src/api.js
+++ b/src/api.js
@@ -248,6 +248,10 @@ export function arkPresets(net = getNetwork()) {
return ARK_PRESETS_BY_NET[net] || ARK_PRESETS_BY_NET.mainnet;
}
export function getArkProviderId(net = getNetwork()) {
+ // Mainnet always rides coinos — the provider picker was removed from
+ // Settings, so a stale stored choice must not silently win. Dev networks
+ // still honor a stored id (regtest/mutinynet testing needs it).
+ if (net === 'mainnet') return 'coinos';
try {
const id = localStorage.getItem(nk(ARK_PROVIDER_KEY, net));
if (id && arkPresets(net).some((p) => p.id === id)) return id;
diff --git a/src/app.js b/src/app.js
index 63220fc..fd60248 100644
--- a/src/app.js
+++ b/src/app.js
@@ -333,7 +333,7 @@ wallet.subscribe(scheduleRender);
// buttons (and Android/system back) move between screens we've actually viewed.
// We snapshot only the navigation-relevant `ui` fields, so incidental re-renders
// (typing, polling, balance updates) don't create history entries.
-const NAV_FIELDS = ['screen', 'tab', 'txDetail', 'bump', 'giftMode', 'claimStep', 'msgView', 'msgCommunity', 'msgPeer'];
+const NAV_FIELDS = ['screen', 'tab', 'txDetail', 'bump', 'giftMode', 'claimStep', 'msgView', 'msgCommunity', 'msgPeer', 'settingsPage'];
function navSnapshot() {
const s = {};
for (const f of NAV_FIELDS) s[f] = ui[f] ?? null;
@@ -1425,30 +1425,33 @@ function addressScanView() {
function settingsTab() {
if (ui.addrScan && !wallet.offline) return addressScanView();
+ if (ui.settingsPage === 'advanced') return advancedSettingsView();
+ const a = activeAccount();
+ const nostrCards = featureAll('nostrSettingsCards');
return h(
'div',
{ class: 'col', style: 'gap:16px' },
- // Quick link to the active wallet's own settings (name, seed, pubkey, auto-logout).
- activeAccount()
- ? h('div', { class: 'card col' },
- h('h3', {}, activeAccount().label),
- h('p', { class: 'small muted', style: 'margin:0' }, t('walletSettingsDesc')),
- h('button', { class: 'btn-primary btn-block', onClick: () => openAccountSettings(activeId) }, '⚙ ' + t('walletSettings'))
- )
- : null,
- // Recovery phrase + public key live on each wallet's own settings page now
- // (Accounts → ⚙). Watch-only wallets can still add their seed here.
- wallet.watchOnly
- ? (ui.loadSeed
- ? loadSeedCard()
- : h('div', { class: 'card col' },
- h('h3', {}, t('watchOnly')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('watchOnlyNote')),
- activeAccount() && activeAccount().xpub
- ? h('button', { class: 'btn-primary btn-block', style: 'margin-top:4px', onClick: () => startLoadSeed() }, t('loadSeedBtn'))
- : null
- ))
+ // The active wallet's own settings, inlined (auto-logout lives in Advanced).
+ ...(a ? [recoveryCard(a), walletNameCard(a), pubkeyCard(a)] : []),
+ ...featureAll('settingsCards'),
+ networkCard(),
+ explorerCard(),
+ // Everything nostr — identity, sync, wallet connect — under one heading.
+ nostrCards.length || (a && nostrNpubCard(a))
+ ? h('h3', { class: 'settings-section' }, t('nostrSection'))
: null,
+ a ? nostrNpubCard(a) : null,
+ ...nostrCards,
+ h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.settingsPage = 'advanced'; render(); } }, t('advancedSettings'))
+ );
+}
+
+// Rarely used, easy to fat-finger: offline transfer, address rescan, auto-logout.
+function advancedSettingsView() {
+ const a = activeAccount();
+ return h(
+ 'div',
+ { class: 'col', style: 'gap:16px' },
wallet.watchOnly
? null
: h(
@@ -1466,10 +1469,8 @@ function settingsTab() {
? null
: h('button', { onClick: () => { ui.addrScan = true; ui.addrScanPage = 0; render(); } }, t('rescanAddresses'))
),
- networkCard(),
- ...featureAll('settingsCards'),
- explorerCard(),
- null
+ a ? autolockCard(a) : null,
+ h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.settingsPage = null; render(); } }, t('back'))
);
}
@@ -1738,91 +1739,110 @@ function openAccountSettings(id) {
render();
}
-// Per-wallet settings: name, auto-logout, recovery phrase, and public key — for
-// any account (not just the active one; full accounts keep their seed in memory).
-function accountSettingsScreen() {
- const a = accounts.find((x) => x.id === ui.settingsId) || activeAccount();
- if (!a) { ui.screen = 'accounts'; return accountsScreen(); }
+// Per-wallet settings cards, shared by the per-account screen (Accounts → ⚙)
+// and the main Settings tab (which inlines them for the active wallet).
+
+function recoveryCard(a) {
const isWatch = a.type === 'watch' || (!a.mnemonic && !a.xprv);
const shown = ui.revealShown;
const words = a.mnemonic ? a.mnemonic.split(' ') : [];
- let zpub = '';
- try { if (a.xpub) zpub = xpubToZpub(a.xpub); } catch {}
+ if (isWatch) {
+ return ui.loadSeed && ui.loadSeed.accId === a.id
+ ? loadSeedCard()
+ : h('div', { class: 'card col' },
+ h('h3', {}, t('recoveryPhrase')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('watchOnlyNote')),
+ a.xpub
+ ? h('button', { class: 'btn-primary btn-block', style: 'margin-top:4px', onClick: () => startLoadSeed({ accId: a.id }) }, t('loadSeedBtn'))
+ : null
+ );
+ }
+ if (!a.mnemonic)
+ return h('div', { class: 'card col' }, h('h3', {}, t('importedKey')), h('p', { class: 'small muted', style: 'margin:0' }, t('importedKeyNote')));
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('recoveryPhrase')),
+ h('div', { class: 'warn-box' }, t('recoveryWarn')),
+ h('div', { class: 'words' }, words.map((w, i) =>
+ h('div', { class: 'w' + (shown ? '' : ' masked') }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, shown ? w : '••••••')))),
+ shown && a.passphrase
+ ? h('div', { class: 'col gap6' }, h('span', { class: 'lab' }, t('bip39Passphrase')), h('div', { class: 'addr-box' }, a.passphrase))
+ : null,
+ shown
+ ? h('div', { class: 'row gap6 wrap' },
+ copyBtn(a.mnemonic, t('copyPhrase')),
+ a.passphrase ? copyBtn(a.passphrase, t('copyPassphrase')) : null,
+ h('button', { class: 'btn-sm grow', onClick: () => { ui.revealShown = false; render(); } }, t('hide')))
+ : h('button', { class: 'btn-primary btn-block', onClick: () => { ui.revealShown = true; render(); } }, t('revealRecovery'))
+ );
+}
+
+function walletNameCard(a) {
const saveName = () => {
const v = (ui.editLabel || '').trim();
if (v && v !== a.label) { a.label = v; persistAccounts(); if (a.persisted) writeVault(); }
ui.editLabel = null;
render();
};
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('walletName')),
+ h('div', { class: 'row gap6' },
+ h('input', { type: 'text', style: 'flex:1', value: ui.editLabel != null ? ui.editLabel : a.label,
+ onInput: (e) => { ui.editLabel = e.target.value; },
+ onKeyDown: (e) => { if (e.key === 'Enter') saveName(); } }),
+ h('button', { class: 'btn-sm', onClick: saveName }, t('save'))
+ )
+ );
+}
+
+function pubkeyCard(a) {
+ let zpub = '';
+ try { if (a.xpub) zpub = xpubToZpub(a.xpub); } catch {}
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('publicKey')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('publicKeyDesc')),
+ ui.pubkeyShown
+ ? h('div', { class: 'col gap6' },
+ h('div', { class: 'addr-box break', style: 'font-size:12px' }, zpub),
+ h('div', { class: 'row gap6 wrap' },
+ copyBtn(zpub, t('copyKey')),
+ h('button', { class: 'btn-sm grow', onClick: () => { ui.pubkeyShown = false; render(); } }, t('hide'))))
+ : h('button', { class: 'btn-block', onClick: () => { ui.pubkeyShown = true; render(); } }, t('showPublicKey'))
+ );
+}
+
+// Nostr address — share it so others can send you locked gifts.
+function nostrNpubCard(a) {
+ if (a.id !== activeId || !wallet.nostrNpub || !wallet.nostrNpub()) return null;
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('nostrKeyTitle')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('nostrKeyDesc')),
+ h('div', { class: 'addr-box break', style: 'font-size:12px' }, wallet.nostrNpub()),
+ copyBtn(wallet.nostrNpub(), t('copyKey')));
+}
+
+function autolockCard(a) {
+ return h('div', { class: 'card col' },
+ h('h3', {}, t('autolockTitle')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('autolockDesc')),
+ h('select', { onChange: (e) => { a.autoLock = Number(e.target.value) || 0; persistAccounts(); if (a.persisted) writeVault(); render(); } },
+ AUTOLOCK_OPTIONS.map((o) => h('option', { value: String(o.ms), selected: o.ms === accAutoLock(a) }, t(o.label))))
+ );
+}
+
+// Per-wallet settings screen — reached from Accounts → ⚙ for any account
+// (not just the active one; full accounts keep their seed in memory).
+function accountSettingsScreen() {
+ const a = accounts.find((x) => x.id === ui.settingsId) || activeAccount();
+ if (!a) { ui.screen = 'accounts'; return accountsScreen(); }
return h(
'div',
{ class: 'col', style: 'gap:16px' },
brandHeader(false),
- // Recovery phrase (top)
- isWatch
- ? (ui.loadSeed && ui.loadSeed.accId === a.id
- ? loadSeedCard()
- : h('div', { class: 'card col' },
- h('h3', {}, t('recoveryPhrase')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('watchOnlyNote')),
- a.xpub
- ? h('button', { class: 'btn-primary btn-block', style: 'margin-top:4px', onClick: () => startLoadSeed({ accId: a.id }) }, t('loadSeedBtn'))
- : null
- ))
- : !a.mnemonic
- ? h('div', { class: 'card col' }, h('h3', {}, t('importedKey')), h('p', { class: 'small muted', style: 'margin:0' }, t('importedKeyNote')))
- : h('div', { class: 'card col' },
- h('h3', {}, t('recoveryPhrase')),
- h('div', { class: 'warn-box' }, t('recoveryWarn')),
- h('div', { class: 'words' }, words.map((w, i) =>
- h('div', { class: 'w' + (shown ? '' : ' masked') }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, shown ? w : '••••••')))),
- shown && a.passphrase
- ? h('div', { class: 'col gap6' }, h('span', { class: 'lab' }, t('bip39Passphrase')), h('div', { class: 'addr-box' }, a.passphrase))
- : null,
- shown
- ? h('div', { class: 'row gap6 wrap' },
- copyBtn(a.mnemonic, t('copyPhrase')),
- a.passphrase ? copyBtn(a.passphrase, t('copyPassphrase')) : null,
- h('button', { class: 'btn-sm grow', onClick: () => { ui.revealShown = false; render(); } }, t('hide')))
- : h('button', { class: 'btn-primary btn-block', onClick: () => { ui.revealShown = true; render(); } }, t('revealRecovery'))
- ),
- // Name
- h('div', { class: 'card col' },
- h('h3', {}, t('walletName')),
- h('div', { class: 'row gap6' },
- h('input', { type: 'text', style: 'flex:1', value: ui.editLabel != null ? ui.editLabel : a.label,
- onInput: (e) => { ui.editLabel = e.target.value; },
- onKeyDown: (e) => { if (e.key === 'Enter') saveName(); } }),
- h('button', { class: 'btn-sm', onClick: saveName }, t('save'))
- )
- ),
- // Public key (zpub)
- h('div', { class: 'card col' },
- h('h3', {}, t('publicKey')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('publicKeyDesc')),
- ui.pubkeyShown
- ? h('div', { class: 'col gap6' },
- h('div', { class: 'addr-box break', style: 'font-size:12px' }, zpub),
- h('div', { class: 'row gap6 wrap' },
- copyBtn(zpub, t('copyKey')),
- h('button', { class: 'btn-sm grow', onClick: () => { ui.pubkeyShown = false; render(); } }, t('hide'))))
- : h('button', { class: 'btn-block', onClick: () => { ui.pubkeyShown = true; render(); } }, t('showPublicKey'))
- ),
- // Nostr address — share it so others can send you locked gifts.
- (a.id === activeId && wallet.nostrNpub && wallet.nostrNpub())
- ? h('div', { class: 'card col' },
- h('h3', {}, t('nostrKeyTitle')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('nostrKeyDesc')),
- h('div', { class: 'addr-box break', style: 'font-size:12px' }, wallet.nostrNpub()),
- copyBtn(wallet.nostrNpub(), t('copyKey')))
- : null,
- // Auto-logout (bottom)
- h('div', { class: 'card col' },
- h('h3', {}, t('autolockTitle')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('autolockDesc')),
- h('select', { onChange: (e) => { a.autoLock = Number(e.target.value) || 0; persistAccounts(); if (a.persisted) writeVault(); render(); } },
- AUTOLOCK_OPTIONS.map((o) => h('option', { value: String(o.ms), selected: o.ms === accAutoLock(a) }, t(o.label))))
- ),
+ recoveryCard(a),
+ walletNameCard(a),
+ pubkeyCard(a),
+ nostrNpubCard(a),
+ autolockCard(a),
h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.editLabel = null; ui.revealShown = false; ui.pubkeyShown = false; ui.loadSeed = null; goBack(() => { ui.screen = 'accounts'; }); } }, t('back'))
);
}
@@ -1967,6 +1987,7 @@ function tabsBar() {
ui.arkMoveDetail = null;
ui.giftDetail = null;
ui.addrScan = false; // and back to the main Settings, not the address list
+ ui.settingsPage = null;
ui.bump = null;
ui.giftMode = false;
render();
diff --git a/src/features/ark.js b/src/features/ark.js
index baee9a4..c9ea4e9 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -14,8 +14,7 @@ import { decodeVtxo, getVtxoStatus, VTXO_STATE_SPENT, concatBytes } from '../ark
import { signedExitTxs, buildBumpChild, buildExitClaim, submitPackage } from '../ark/exit.js';
import { utxoId } from '../wallet.js';
import {
- getNetwork, setNetwork, arkPresets, getArkProviderId, setArkProviderId,
- getArkCustom, setArkCustom, getArkConfig,
+ getNetwork, setNetwork, getArkProviderId, getArkConfig,
} from '../api.js';
import { t } from '../i18n.js';
import { qrSvg } from '../qr.js';
@@ -833,37 +832,12 @@ export function arkFeature(ctx) {
function arkCard() {
const net = getNetwork();
+ // No provider picker: coinos is the provider (per-network defaults cover
+ // dev networks). The card is status only.
const id = getArkProviderId(net);
- const custom = getArkCustom(net);
- const presets = arkPresets(net);
- const applyProvider = (v) => {
- setArkProviderId(v, net);
- ui.receiveType = null;
- initArk();
- render();
- };
const head = [
h('h3', { class: 'row gap6', style: 'align-items:center' }, t('arkTitle')),
h('p', { class: 'small muted', style: 'margin:0' }, t('arkDesc')),
- h('select', { onChange: (e) => applyProvider(e.target.value) },
- presets.map((p) => h('option', { value: p.id, selected: p.id === id }, p.label))),
- id === 'custom'
- ? h('div', { class: 'col', style: 'gap:8px' },
- h('label', { class: 'field' },
- h('span', { class: 'lab' }, t('arkServerUrl')),
- h('input', {
- type: 'text', class: 'mono-input', placeholder: 'https://ark.example.com',
- autocapitalize: 'none', autocomplete: 'off', spellcheck: 'false', value: custom.ark,
- onChange: (e) => { setArkCustom({ ark: e.target.value.trim(), esplora: custom.esplora }, net); initArk(); render(); },
- })),
- h('label', { class: 'field' },
- h('span', { class: 'lab' }, t('arkEsploraUrl')),
- h('input', {
- type: 'text', class: 'mono-input', placeholder: 'https://mempool.example.com/api',
- autocapitalize: 'none', autocomplete: 'off', spellcheck: 'false', value: custom.esplora,
- onChange: (e) => { setArkCustom({ ark: custom.ark, esplora: e.target.value.trim() }, net); initArk(); render(); },
- })))
- : null,
ui.arkError ? h('div', { class: 'notice err' }, ui.arkError) : null,
];
if (id === 'off' || wallet.watchOnly) {
diff --git a/src/features/nostrlogin.js b/src/features/nostrlogin.js
index 20cd6f2..9b4b992 100644
--- a/src/features/nostrlogin.js
+++ b/src/features/nostrlogin.js
@@ -209,6 +209,6 @@ export function nostrLoginFeature(ctx) {
} catch { return null; }
},
unlockExtra() { return unlockExtra(); },
- settingsCards() { return [settingsCard()]; },
+ nostrSettingsCards() { return [settingsCard()]; },
};
}
diff --git a/src/features/nwc.js b/src/features/nwc.js
index ffdabda..2d9d633 100644
--- a/src/features/nwc.js
+++ b/src/features/nwc.js
@@ -766,6 +766,6 @@ export function nwcFeature(ctx) {
nwcOfferString() { return hook('arkReady') && !wallet.watchOnly ? offerString() : null; },
init() { listen(); startWatchdog(); refreshRegistration(); reconcileBg(); },
stop() { stop(); if (watchdog) { clearInterval(watchdog); watchdog = null; } },
- settingsCards() { return [nwcCard()]; },
+ nostrSettingsCards() { return [nwcCard()]; },
};
}
diff --git a/src/features/sync.js b/src/features/sync.js
index 94dfc68..96616c9 100644
--- a/src/features/sync.js
+++ b/src/features/sync.js
@@ -252,7 +252,7 @@ export function syncFeature(ctx) {
return {
id: 'sync',
- settingsCards() {
+ nostrSettingsCards() {
return wallet.watchOnly || !wallet.mnemonic ? [] : [syncCard()];
},
};
diff --git a/src/i18n.js b/src/i18n.js
index 474a37b..32e7685 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -198,6 +198,8 @@ const STR = {
msgInviteSent: 'Invite sent',
msgLeave: 'Leave community',
msgLeaveConfirm: 'Really leave? Rejoining needs a new invite',
+ nostrSection: 'Nostr',
+ advancedSettings: 'Advanced settings',
copyAddress: 'Copy address',
paymentReceived: 'Payment received!',
tapToProceed: 'Tap anywhere to proceed',
diff --git a/src/style.css b/src/style.css
index cde36e6..c5fde7c 100644
--- a/src/style.css
+++ b/src/style.css
@@ -379,3 +379,6 @@ hr.divider { margin: 16px 0; }
.chat-row.dm.mine .chat-body { text-align: right; }
.chat-bubble.me { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); text-align: left; }
.chat-row.dm .chat-time { font-size: 10px; color: var(--faint); margin-top: 2px; }
+
+/* Settings: section heading between card groups. */
+.settings-section { margin: 8px 0 -6px 4px; font-size: 15px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }