hd-wallet-ui 2.0.6 → 2.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-wallet-ui",
3
- "version": "2.0.6",
3
+ "version": "2.0.10",
4
4
  "description": "HD Wallet modal UI — login, keys, identity, trust map, and security bond. Attach to any button in your app.",
5
5
  "type": "module",
6
6
  "main": "src/app.js",
@@ -40,7 +40,7 @@
40
40
  "buffer": "^6.0.3",
41
41
  "flatbuffers": "^25.9.23",
42
42
  "flatc-wasm": "^26.1.32",
43
- "hd-wallet-wasm": "^2.0.6",
43
+ "hd-wallet-wasm": "^2.0.10",
44
44
  "qrcode": "^1.5.3",
45
45
  "spacedatastandards.org": "^1.93.3",
46
46
  "vcard-cryptoperson": "^1.1.11"
package/src/app.js CHANGED
@@ -28,6 +28,8 @@ window.Buffer = Buffer;
28
28
 
29
29
  import { getModalHTML } from './template.js';
30
30
  import WalletStorage, { StorageMethod } from './wallet-storage.js';
31
+ import { safeCopyText } from './clipboard.js';
32
+ import { normalizeTabHash } from './hash.js';
31
33
 
32
34
  import {
33
35
  cryptoConfig,
@@ -74,14 +76,7 @@ const $ = (id) => {
74
76
  return null;
75
77
  };
76
78
 
77
- export function normalizeTabHash(rawHash) {
78
- return String(rawHash || '')
79
- .replace(/^\/+/g, '')
80
- .split(/[/?#]/)[0]
81
- .replace(/[^a-z0-9_-]/gi, '')
82
- .replace(/-tab$/i, '')
83
- .toLowerCase();
84
- }
79
+ export { normalizeTabHash };
85
80
 
86
81
  const $q = (sel) => _root.querySelector(sel) || (_root !== document ? document.querySelector(sel) : null);
87
82
  const $qa = (sel) => {
@@ -531,6 +526,50 @@ function deriveAccountPeerId() {
531
526
  }
532
527
  }
533
528
 
529
+ function getWalletIdentityPath(wallet = getCurrentWallet()) {
530
+ if (!wallet) return "m/44'/0'/0'";
531
+ return `m/44'/0'/${wallet.accountIndex}'`;
532
+ }
533
+
534
+ function getCurrentWalletIdentity(wallet = getCurrentWallet()) {
535
+ if (!state.hdRoot || !wallet) return { xpub: '', peerId: '', path: getWalletIdentityPath(wallet) };
536
+ const path = getWalletIdentityPath(wallet);
537
+ try {
538
+ const accountKey = deriveHDKey(path);
539
+ return {
540
+ xpub: accountKey?.toXpub?.() || '',
541
+ peerId: accountKey?.peerIdString?.() || '',
542
+ path,
543
+ };
544
+ } catch (e) {
545
+ console.warn('Failed to derive wallet identity keys:', e);
546
+ return { xpub: '', peerId: '', path };
547
+ }
548
+ }
549
+
550
+ function getCurrentWalletSigningAccounts(wallet = getCurrentWallet()) {
551
+ if (!wallet) return [];
552
+ return state.activeAccounts.filter(a => a.active && getAccountWalletId(a) === wallet.id && isSigningAccountForWallet(a, wallet));
553
+ }
554
+
555
+ function getCurrentWalletSignatureKey(wallet = getCurrentWallet()) {
556
+ if (!state.hdRoot || !wallet) return null;
557
+ const accountIndex = wallet.accountIndex;
558
+ try {
559
+ const path = buildSigningPath(501, accountIndex, 0);
560
+ const derived = deriveHDKey(path);
561
+ return {
562
+ privateKey: derived.privateKey(),
563
+ accountIndex,
564
+ index: 0,
565
+ path,
566
+ };
567
+ } catch (e) {
568
+ console.warn('Failed to derive selected wallet signature key:', e);
569
+ return null;
570
+ }
571
+ }
572
+
534
573
  function updatePathDisplay() {
535
574
  const coin = $('hd-coin')?.value;
536
575
  const account = $('hd-account')?.value || '0';
@@ -783,29 +822,41 @@ function updateCustomPathDefault() {
783
822
  }
784
823
 
785
824
  function renderWalletSelector() {
786
- const select = $('wallet-active-select');
787
- if (!select) return;
825
+ const selects = [$('account-wallet-select'), $('wallet-active-select')].filter(Boolean);
826
+ if (selects.length === 0) return;
788
827
  ensureWalletNamesNormalized();
789
828
 
790
829
  const currentWallet = getCurrentWallet();
791
830
  if (!currentWallet) {
792
- select.innerHTML = '';
831
+ selects.forEach((select) => { select.innerHTML = ''; });
793
832
  return;
794
833
  }
795
834
  state.activeWalletId = currentWallet.id;
796
835
 
797
- select.innerHTML = '';
798
836
  const displayCurrency = state.walletFiatCurrency || getSelectedCurrency();
799
837
  const activeWallets = getActiveWallets();
800
- activeWallets.forEach((wallet) => {
801
- const option = document.createElement('option');
802
- option.value = String(wallet.id);
803
- const walletValue = state.walletFiatTotals[wallet.id] ?? 0;
804
- option.textContent = `${wallet.name} (${formatCurrencyValue(walletValue, displayCurrency)})`;
805
- select.appendChild(option);
838
+ selects.forEach((select) => {
839
+ select.innerHTML = '';
840
+ activeWallets.forEach((wallet) => {
841
+ const option = document.createElement('option');
842
+ option.value = String(wallet.id);
843
+ const walletValue = state.walletFiatTotals[wallet.id] ?? 0;
844
+ option.textContent = `${wallet.name} (${formatCurrencyValue(walletValue, displayCurrency)})`;
845
+ select.appendChild(option);
846
+ });
847
+ select.value = String(state.activeWalletId);
806
848
  });
807
- select.value = String(state.activeWalletId);
808
849
  updateCustomPathWalletLabel();
850
+ updateIdentityWalletKeys();
851
+ }
852
+
853
+ function updateIdentityWalletKeys() {
854
+ const identity = getCurrentWalletIdentity();
855
+ const xpubEl = $('identity-wallet-xpub');
856
+ const peerIdEl = $('identity-wallet-peerid');
857
+
858
+ if (xpubEl) setTruncatedValue(xpubEl, identity.xpub || 'N/A');
859
+ if (peerIdEl) setTruncatedValue(peerIdEl, identity.peerId || 'N/A');
809
860
  }
810
861
 
811
862
  function sleep(ms) {
@@ -1226,6 +1277,7 @@ function hideWalletOverlays() {
1226
1277
  function showWalletMainView() {
1227
1278
  const main = $('wallet-main-view');
1228
1279
  hideWalletOverlays();
1280
+ closeAssetActionOverlay();
1229
1281
  if (main) main.style.display = 'block';
1230
1282
  }
1231
1283
 
@@ -1528,6 +1580,49 @@ function closeWalletActionMenus() {
1528
1580
  $('wallet-receive-menu')?.classList.remove('visible');
1529
1581
  }
1530
1582
 
1583
+ function closeAssetActionOverlay() {
1584
+ const overlay = $('wallet-asset-action-overlay');
1585
+ if (overlay) overlay.style.display = 'none';
1586
+ state.selectedWalletAssetIdx = null;
1587
+ }
1588
+
1589
+ function showAssetActionOverlay(acct, idx) {
1590
+ const overlay = $('wallet-asset-action-overlay');
1591
+ if (!overlay || !acct) return;
1592
+
1593
+ state.selectedWalletAssetIdx = idx;
1594
+ const icon = CHAIN_ICONS[acct.name] || { symbol: '?' };
1595
+ const fullName = CHAIN_FULL_NAMES[acct.name] || acct.name;
1596
+ const pathLabel = acct.path || `m/44'/${acct.coinType}'/${acct.account}'/0/${acct.index}`;
1597
+
1598
+ const titleEl = $('wallet-asset-action-title');
1599
+ const pathEl = $('wallet-asset-action-path');
1600
+ const addressEl = $('wallet-asset-action-address');
1601
+ if (titleEl) titleEl.textContent = `${icon.symbol} ${fullName}`;
1602
+ if (pathEl) pathEl.textContent = pathLabel;
1603
+ if (addressEl) {
1604
+ addressEl.textContent = acct.address || '';
1605
+ addressEl.title = acct.address || '';
1606
+ }
1607
+
1608
+ const sendBtn = $('wallet-asset-send');
1609
+ const receiveBtn = $('wallet-asset-receive');
1610
+ if (sendBtn) {
1611
+ sendBtn.onclick = () => {
1612
+ closeAssetActionOverlay();
1613
+ showSendView(idx);
1614
+ };
1615
+ }
1616
+ if (receiveBtn) {
1617
+ receiveBtn.onclick = () => {
1618
+ closeAssetActionOverlay();
1619
+ showReceiveModal(acct);
1620
+ };
1621
+ }
1622
+
1623
+ overlay.style.display = 'flex';
1624
+ }
1625
+
1531
1626
  function renderAccountsList() {
1532
1627
  const listEl = $('wallet-accounts-list');
1533
1628
  const emptyEl = $('wallet-accounts-empty');
@@ -1576,7 +1671,7 @@ function renderAccountsList() {
1576
1671
  '</div>';
1577
1672
 
1578
1673
  row.addEventListener('click', () => {
1579
- showReceiveModal(acct);
1674
+ showAssetActionOverlay(acct, idx);
1580
1675
  });
1581
1676
 
1582
1677
  listEl.appendChild(row);
@@ -1612,9 +1707,7 @@ async function handleAccountAction(action, idx) {
1612
1707
  showReceiveModal(acct);
1613
1708
  break;
1614
1709
  case 'copy':
1615
- try {
1616
- await navigator.clipboard.writeText(acct.address);
1617
- } catch {}
1710
+ await safeCopyText(acct.address);
1618
1711
  break;
1619
1712
  case 'toggle':
1620
1713
  toggleAccountActive(idx);
@@ -1669,7 +1762,7 @@ async function showReceiveModal(acct) {
1669
1762
  overlay.style.display = 'flex';
1670
1763
 
1671
1764
  overlay.querySelector('#wallet-receive-copy')?.addEventListener('click', () => {
1672
- navigator.clipboard.writeText(acct.address).catch(() => {});
1765
+ void safeCopyText(acct.address);
1673
1766
  }, { once: true });
1674
1767
 
1675
1768
  overlay.querySelector('#wallet-receive-close')?.addEventListener('click', () => {
@@ -2566,21 +2659,6 @@ function login(keys) {
2566
2659
  if (xpubEl) {
2567
2660
  setTruncatedValue(xpubEl, state.hdRoot.toXpub() || 'N/A');
2568
2661
  }
2569
- // Populate wallet tab xpub display
2570
- const walletTabXpubEl = $('wallet-tab-xpub');
2571
- if (walletTabXpubEl) {
2572
- setTruncatedValue(walletTabXpubEl, state.hdRoot.toXpub() || 'N/A');
2573
- }
2574
- // Populate wallet tab PeerID display
2575
- const walletTabPeerIdEl = $('wallet-tab-peerid');
2576
- const peerIdRow = $('ph-portfolio-peerid-row');
2577
- if (walletTabPeerIdEl && peerIdRow) {
2578
- const peerIdStr = deriveAccountPeerId();
2579
- if (peerIdStr) {
2580
- setTruncatedValue(walletTabPeerIdEl, peerIdStr);
2581
- peerIdRow.style.display = '';
2582
- }
2583
- }
2584
2662
  populateAccountAddressDropdown();
2585
2663
  if (xprvEl) {
2586
2664
  xprvEl.textContent = 'Hidden (click reveal)';
@@ -2824,47 +2902,9 @@ function downloadData(data, filename, mimeType) {
2824
2902
  // Wallet Address Population & Balance Fetching
2825
2903
  // =============================================================================
2826
2904
 
2827
- // Account header — show xpub only
2828
2905
  function populateAccountAddressDropdown() {
2829
- const addrEl = $('account-address-display');
2830
- if (!addrEl) return;
2831
-
2832
- const xpubStr = state.hdRoot ? state.hdRoot.toXpub() : '';
2833
- addrEl.textContent = `${xpubStr.slice(0,10)}...${xpubStr.slice(-10)}`;
2834
- addrEl.title = xpubStr;
2835
-
2836
- const copyBtn = $('account-address-copy');
2837
- if (copyBtn) {
2838
- copyBtn.onclick = () => {
2839
- if (xpubStr) {
2840
- navigator.clipboard.writeText(xpubStr).then(() => {
2841
- copyBtn.title = 'Copied!';
2842
- setTimeout(() => { copyBtn.title = 'Copy xpub'; }, 1500);
2843
- });
2844
- }
2845
- };
2846
- }
2847
-
2848
- // Populate PeerID row (derived from account-level secp256k1 key)
2849
- const peerIdStr = deriveAccountPeerId();
2850
- const peerIdRow = $('account-peerid-row');
2851
- const peerIdEl = $('account-peerid-display');
2852
- if (peerIdStr && peerIdRow && peerIdEl) {
2853
- peerIdRow.style.display = '';
2854
- peerIdEl.textContent = `${peerIdStr.slice(0,8)}...${peerIdStr.slice(-8)}`;
2855
- peerIdEl.title = peerIdStr;
2856
- }
2857
- const peerCopyBtn = $('account-peerid-copy');
2858
- if (peerCopyBtn) {
2859
- peerCopyBtn.onclick = () => {
2860
- if (peerIdStr) {
2861
- navigator.clipboard.writeText(peerIdStr).then(() => {
2862
- peerCopyBtn.title = 'Copied!';
2863
- setTimeout(() => { peerCopyBtn.title = 'Copy PeerID'; }, 1500);
2864
- });
2865
- }
2866
- };
2867
- }
2906
+ renderWalletSelector();
2907
+ updateIdentityWalletKeys();
2868
2908
  }
2869
2909
 
2870
2910
  function populateWalletAddresses() {
@@ -3323,10 +3363,11 @@ function generateVCard(info, { skipPhoto = false } = {}) {
3323
3363
  }
3324
3364
 
3325
3365
  if (info.includeKeys && state.wallet?.x25519) {
3366
+ const identity = getCurrentWalletIdentity();
3326
3367
  person.KEY = [
3327
- // Always include xPub
3328
- ...(state.hdRoot?.toXpub ? [{
3329
- XPUB: state.hdRoot.toXpub(),
3368
+ // Always include the selected wallet xPub.
3369
+ ...(identity.xpub ? [{
3370
+ XPUB: identity.xpub,
3330
3371
  LABEL: '',
3331
3372
  }] : []),
3332
3373
  // X25519 encryption key
@@ -3335,8 +3376,7 @@ function generateVCard(info, { skipPhoto = false } = {}) {
3335
3376
  LABEL: 'X25519',
3336
3377
  },
3337
3378
  // Active accounts from wallet scan
3338
- ...state.activeAccounts
3339
- .filter(a => a.active && isSigningAccount(a))
3379
+ ...getCurrentWalletSigningAccounts()
3340
3380
  .flatMap(a => {
3341
3381
  const entries = [];
3342
3382
  const pathLabel = a.path || `m/44'/${a.coinType}'/${a.account}'/0/${a.index}`;
@@ -3357,8 +3397,9 @@ function generateVCard(info, { skipPhoto = false } = {}) {
3357
3397
  return entries;
3358
3398
  }),
3359
3399
  ];
3360
- } else if (info.xpubOnly && state.hdRoot?.toXpub) {
3361
- person.KEY = [{ XPUB: state.hdRoot.toXpub(), LABEL: '' }];
3400
+ } else if (info.xpubOnly) {
3401
+ const identity = getCurrentWalletIdentity();
3402
+ if (identity.xpub) person.KEY = [{ XPUB: identity.xpub, LABEL: '' }];
3362
3403
  }
3363
3404
 
3364
3405
  const note = info.includeKeys
@@ -3410,15 +3451,16 @@ function getSignableBody(vcardText) {
3410
3451
  }
3411
3452
 
3412
3453
  function signVCard(vcardText) {
3413
- if (!state.wallet?.ed25519?.privateKey) return vcardText;
3454
+ const signatureKey = getCurrentWalletSignatureKey();
3455
+ if (!signatureKey?.privateKey) return vcardText;
3414
3456
 
3415
3457
  const body = getSignableBody(vcardText);
3416
3458
  const messageBytes = new TextEncoder().encode(body);
3417
- const signature = hdWallet().curves.ed25519.sign(messageBytes, state.wallet.ed25519.privateKey);
3459
+ const signature = hdWallet().curves.ed25519.sign(messageBytes, signatureKey.privateKey);
3418
3460
  const sigB64 = toBase64(signature);
3419
3461
 
3420
- // Encode signature + derivation path (coinType=501, account=0, index=0)
3421
- const sigValue = `${sigB64}:501:0:0`;
3462
+ // Encode signature + selected wallet derivation path.
3463
+ const sigValue = `${sigB64}:501:${signatureKey.accountIndex}:${signatureKey.index}`;
3422
3464
 
3423
3465
  // Find highest itemN and key index
3424
3466
  let maxItem = 0;
@@ -4661,10 +4703,12 @@ function setupMainAppHandlers() {
4661
4703
  if (targetEl) {
4662
4704
  try {
4663
4705
  let value = '';
4664
- if (targetId === 'wallet-xpub' || targetId === 'wallet-tab-xpub') {
4706
+ if (targetId === 'wallet-xpub') {
4665
4707
  value = state.hdRoot?.toXpub?.() || '';
4666
- } else if (targetId === 'wallet-tab-peerid') {
4667
- value = deriveAccountPeerId() || '';
4708
+ } else if (targetId === 'identity-wallet-xpub') {
4709
+ value = getCurrentWalletIdentity().xpub || '';
4710
+ } else if (targetId === 'identity-wallet-peerid') {
4711
+ value = getCurrentWalletIdentity().peerId || '';
4668
4712
  } else if (targetId === 'wallet-xprv') {
4669
4713
  if (targetEl.dataset.revealed !== 'true') {
4670
4714
  alert('Reveal the xprv first to copy it.');
@@ -4683,9 +4727,12 @@ function setupMainAppHandlers() {
4683
4727
  if (!value) {
4684
4728
  throw new Error('Nothing to copy');
4685
4729
  }
4686
- await navigator.clipboard.writeText(value);
4687
- btn.classList.add('copied');
4688
- setTimeout(() => btn.classList.remove('copied'), 1500);
4730
+ if (await safeCopyText(value)) {
4731
+ btn.classList.add('copied');
4732
+ setTimeout(() => btn.classList.remove('copied'), 1500);
4733
+ } else {
4734
+ throw new Error('Clipboard unavailable');
4735
+ }
4689
4736
  } catch (err) {
4690
4737
  console.error('Copy failed:', err);
4691
4738
  }
@@ -4754,74 +4801,42 @@ function setupMainAppHandlers() {
4754
4801
  });
4755
4802
 
4756
4803
  // Wallet tab controls
4757
- $('wallet-active-select')?.addEventListener('change', (e) => {
4804
+ const handleWalletSelectChange = (e) => {
4758
4805
  const walletId = Number.parseInt(e.target.value, 10);
4759
4806
  if (Number.isNaN(walletId)) return;
4760
4807
  state.activeWalletId = walletId;
4761
4808
  closeWalletActionMenus();
4809
+ closeAssetActionOverlay();
4762
4810
  renderWalletSelector();
4763
4811
  renderAccountsList();
4764
4812
  updateCustomPathDefault();
4813
+ };
4814
+ $('account-wallet-select')?.addEventListener('change', handleWalletSelectChange);
4815
+ $('wallet-active-select')?.addEventListener('change', handleWalletSelectChange);
4816
+ $('account-wallet-manage-btn')?.addEventListener('click', () => {
4817
+ closeWalletActionMenus();
4818
+ closeAssetActionOverlay();
4819
+ const walletTab = _root.querySelector?.('.modal-tab[data-modal-tab="wallet-tab-content"]');
4820
+ if (walletTab) walletTab.click();
4821
+ showWalletsView();
4765
4822
  });
4766
4823
  $('wallet-manage-btn')?.addEventListener('click', () => {
4767
4824
  closeWalletActionMenus();
4825
+ closeAssetActionOverlay();
4768
4826
  showWalletsView();
4769
4827
  });
4770
4828
  $('wallet-scan-btn')?.addEventListener('click', () => {
4771
4829
  scanActiveAccounts();
4772
4830
  });
4773
- const sendAction = $('wallet-send-action');
4774
- const receiveAction = $('wallet-receive-action');
4775
- $('wallet-send-btn')?.addEventListener('click', (e) => {
4776
- e.stopPropagation();
4777
- updateWalletActionMenus();
4778
- const sendMenu = $('wallet-send-menu');
4779
- const receiveMenu = $('wallet-receive-menu');
4780
- if (!sendMenu || !receiveMenu) return;
4781
- const nextVisible = !sendMenu.classList.contains('visible');
4782
- receiveMenu.classList.remove('visible');
4783
- sendMenu.classList.toggle('visible', nextVisible);
4784
- });
4785
- $('wallet-receive-btn-main')?.addEventListener('click', (e) => {
4786
- e.stopPropagation();
4787
- updateWalletActionMenus();
4788
- const sendMenu = $('wallet-send-menu');
4789
- const receiveMenu = $('wallet-receive-menu');
4790
- if (!sendMenu || !receiveMenu) return;
4791
- const nextVisible = !receiveMenu.classList.contains('visible');
4792
- sendMenu.classList.remove('visible');
4793
- receiveMenu.classList.toggle('visible', nextVisible);
4794
- });
4795
- $qa('#wallet-send-menu .ph-action-menu-item').forEach((btn) => {
4796
- btn.addEventListener('click', (e) => {
4797
- e.stopPropagation();
4798
- const chain = btn.dataset.chain;
4799
- const acct = getWalletAccountForChain(chain);
4800
- closeWalletActionMenus();
4801
- if (!acct) return;
4802
- showSendView(state.activeAccounts.indexOf(acct));
4803
- });
4804
- });
4805
- $qa('#wallet-receive-menu .ph-action-menu-item').forEach((btn) => {
4806
- btn.addEventListener('click', (e) => {
4807
- e.stopPropagation();
4808
- const chain = btn.dataset.chain;
4809
- const acct = getWalletAccountForChain(chain);
4810
- closeWalletActionMenus();
4811
- if (!acct) return;
4812
- showReceiveModal(acct);
4813
- });
4814
- });
4815
- _root.addEventListener('click', (e) => {
4816
- if (sendAction?.contains(e.target) || receiveAction?.contains(e.target)) return;
4817
- closeWalletActionMenus();
4818
- });
4831
+ $('wallet-asset-action-close')?.addEventListener('click', closeAssetActionOverlay);
4819
4832
  $('wallet-export-btn-main')?.addEventListener('click', () => {
4820
4833
  closeWalletActionMenus();
4834
+ closeAssetActionOverlay();
4821
4835
  showExportView();
4822
4836
  });
4823
4837
  $('wallet-advanced-btn-main')?.addEventListener('click', () => {
4824
4838
  closeWalletActionMenus();
4839
+ closeAssetActionOverlay();
4825
4840
  showAdvancedView();
4826
4841
  });
4827
4842
  $('wallet-wallets-back')?.addEventListener('click', () => {
@@ -5050,7 +5065,9 @@ function setupMainAppHandlers() {
5050
5065
  $('copy-vcard')?.addEventListener('click', async () => {
5051
5066
  const vcard = state._exportedVCard || '';
5052
5067
  try {
5053
- await navigator.clipboard.writeText(vcard);
5068
+ if (!await safeCopyText(vcard)) {
5069
+ throw new Error('Clipboard unavailable');
5070
+ }
5054
5071
  const btn = $('copy-vcard');
5055
5072
  if (btn) {
5056
5073
  btn.textContent = 'Copied!';
@@ -5697,7 +5714,7 @@ function setupTrustHandlers() {
5697
5714
  $('encrypt-copy-bundle')?.addEventListener('click', () => {
5698
5715
  const bundle = $('encrypt-bundle')?.value;
5699
5716
  if (bundle) {
5700
- navigator.clipboard.writeText(bundle).catch(() => {});
5717
+ void safeCopyText(bundle);
5701
5718
  }
5702
5719
  });
5703
5720
 
@@ -5853,7 +5870,8 @@ function setupHomepageHandlers() {
5853
5870
  btn.addEventListener('click', () => {
5854
5871
  const code = btn.closest('.code-block')?.querySelector('code');
5855
5872
  if (code) {
5856
- navigator.clipboard.writeText(code.textContent).then(() => {
5873
+ safeCopyText(code.textContent).then((copied) => {
5874
+ if (!copied) return;
5857
5875
  btn.title = 'Copied!';
5858
5876
  setTimeout(() => { btn.title = 'Copy code'; }, 2000);
5859
5877
  });
@@ -0,0 +1,44 @@
1
+ export async function safeCopyText(value) {
2
+ const text = String(value ?? '');
3
+ if (!text) return false;
4
+
5
+ try {
6
+ const writeText = globalThis.navigator?.clipboard?.writeText;
7
+ if (typeof writeText === 'function') {
8
+ await writeText.call(globalThis.navigator.clipboard, text);
9
+ return true;
10
+ }
11
+ } catch {
12
+ // Fall through to the legacy DOM copy path below.
13
+ }
14
+
15
+ return fallbackCopyText(text);
16
+ }
17
+
18
+ function fallbackCopyText(text) {
19
+ const doc = globalThis.document;
20
+ if (!doc?.body || typeof doc.createElement !== 'function' || typeof doc.execCommand !== 'function') {
21
+ return false;
22
+ }
23
+
24
+ const textarea = doc.createElement('textarea');
25
+ textarea.value = text;
26
+ textarea.readOnly = true;
27
+ textarea.setAttribute('aria-hidden', 'true');
28
+ textarea.style.position = 'fixed';
29
+ textarea.style.left = '-9999px';
30
+ textarea.style.top = '0';
31
+ textarea.style.opacity = '0';
32
+
33
+ doc.body.appendChild(textarea);
34
+ try {
35
+ textarea.focus();
36
+ textarea.select();
37
+ textarea.setSelectionRange(0, textarea.value.length);
38
+ return Boolean(doc.execCommand('copy'));
39
+ } catch {
40
+ return false;
41
+ } finally {
42
+ textarea.remove();
43
+ }
44
+ }
package/src/hash.js ADDED
@@ -0,0 +1,9 @@
1
+ export function normalizeTabHash(rawHash) {
2
+ return String(rawHash || '')
3
+ .replace(/^#+/g, '')
4
+ .replace(/^\/+/g, '')
5
+ .split(/[/?#]/)[0]
6
+ .replace(/[^a-z0-9_-]/gi, '')
7
+ .replace(/-tab$/i, '')
8
+ .toLowerCase();
9
+ }
package/src/template.js CHANGED
@@ -3,7 +3,7 @@ export function getModalHTML() {
3
3
  <!-- Keys Modal -->
4
4
  <div id="keys-modal" class="modal">
5
5
  <div class="modal-glass modal-wide">
6
- <div class="modal-header"><div class="account-header-info"><div class="account-header-top"><h3>Account</h3><h3 class="account-total-value" id="account-total-value"></h3></div><div class="account-address-row"><span class="account-address-label">xpub</span><code class="account-address-display" id="account-address-display"></code><button class="account-address-copy" id="account-address-copy" title="Copy xpub"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button></div><div class="account-address-row" id="account-peerid-row" style="display:none"><span class="account-address-label">PeerID</span><code class="account-address-display" id="account-peerid-display"></code><button class="account-address-copy" id="account-peerid-copy" title="Copy PeerID"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button></div></div><button class="modal-close">&times;</button></div>
6
+ <div class="modal-header"><div class="account-header-info"><div class="account-header-top"><h3>Account</h3><h3 class="account-total-value" id="account-total-value"></h3></div><div class="account-wallet-row"><select id="account-wallet-select" class="glass-input compact account-wallet-select" aria-label="Select wallet"></select><button id="account-wallet-manage-btn" class="glass-btn small">Manage</button></div></div><button class="modal-close">&times;</button></div>
7
7
  <div class="modal-tabs">
8
8
  <button class="modal-tab active" data-modal-tab="vcard-tab-content">Identity</button>
9
9
  <button class="modal-tab" data-modal-tab="trust-tab-content">Trust Map</button>
@@ -19,21 +19,6 @@ export function getModalHTML() {
19
19
  <div class="ph-portfolio">
20
20
  <div class="ph-portfolio-value" id="wallet-bond-value">$0.00</div>
21
21
  <div class="ph-portfolio-label">Total Balance</div>
22
- <div class="ph-portfolio-xpub">
23
- <code id="wallet-tab-xpub" class="ph-xpub-text truncate"></code>
24
- <button class="ph-xpub-copy copy-key-btn" data-copy="wallet-tab-xpub" title="Copy xPub"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
25
- </div>
26
- <div class="ph-portfolio-xpub" id="ph-portfolio-peerid-row" style="display:none">
27
- <code id="wallet-tab-peerid" class="ph-xpub-text truncate"></code>
28
- <button class="ph-xpub-copy copy-key-btn" data-copy="wallet-tab-peerid" title="Copy PeerID"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
29
- </div>
30
- </div>
31
-
32
- <div class="wallet-selector-row">
33
- <div class="wallet-selector-control">
34
- <select id="wallet-active-select" class="glass-input compact wallet-selector-input"></select>
35
- <button id="wallet-manage-btn" class="glass-btn small">Manage</button>
36
- </div>
37
22
  </div>
38
23
 
39
24
  <!-- Action Buttons Row -->
@@ -42,28 +27,6 @@ export function getModalHTML() {
42
27
  <div class="ph-action-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
43
28
  <span>Scan</span>
44
29
  </button>
45
- <div class="ph-action-wrap" id="wallet-send-action">
46
- <button class="ph-action-btn" id="wallet-send-btn">
47
- <div class="ph-action-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg></div>
48
- <span>Send</span>
49
- </button>
50
- <div class="ph-action-menu" id="wallet-send-menu">
51
- <button class="ph-action-menu-item" type="button" data-chain="BTC">Bitcoin (BTC)</button>
52
- <button class="ph-action-menu-item" type="button" data-chain="ETH">Ethereum (ETH)</button>
53
- <button class="ph-action-menu-item" type="button" data-chain="SOL">Solana (SOL)</button>
54
- </div>
55
- </div>
56
- <div class="ph-action-wrap" id="wallet-receive-action">
57
- <button class="ph-action-btn" id="wallet-receive-btn-main">
58
- <div class="ph-action-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></div>
59
- <span>Receive</span>
60
- </button>
61
- <div class="ph-action-menu" id="wallet-receive-menu">
62
- <button class="ph-action-menu-item" type="button" data-chain="BTC">Bitcoin (BTC)</button>
63
- <button class="ph-action-menu-item" type="button" data-chain="ETH">Ethereum (ETH)</button>
64
- <button class="ph-action-menu-item" type="button" data-chain="SOL">Solana (SOL)</button>
65
- </div>
66
- </div>
67
30
  <button class="ph-action-btn" id="wallet-export-btn-main">
68
31
  <div class="ph-action-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg></div>
69
32
  <span>Export</span>
@@ -89,6 +52,22 @@ export function getModalHTML() {
89
52
  <p class="ph-token-empty-sub">Log in and tap Scan to discover your accounts</p>
90
53
  </div>
91
54
  </div>
55
+ <div id="wallet-asset-action-overlay" class="wallet-asset-action-overlay" style="display:none;">
56
+ <div class="wallet-asset-action-card">
57
+ <div class="wallet-asset-action-header">
58
+ <div>
59
+ <h4 id="wallet-asset-action-title"></h4>
60
+ <div id="wallet-asset-action-path" class="wallet-asset-action-path"></div>
61
+ </div>
62
+ <button id="wallet-asset-action-close" class="modal-close" type="button">&times;</button>
63
+ </div>
64
+ <code id="wallet-asset-action-address" class="wallet-asset-action-address"></code>
65
+ <div class="wallet-asset-action-buttons">
66
+ <button id="wallet-asset-send" class="glass-btn primary">Send</button>
67
+ <button id="wallet-asset-receive" class="glass-btn">Receive</button>
68
+ </div>
69
+ </div>
70
+ </div>
92
71
  </div>
93
72
 
94
73
  <!-- Wallets View (replaces main view) -->
@@ -241,6 +220,19 @@ export function getModalHTML() {
241
220
  </div>
242
221
  </div>
243
222
 
223
+ <div class="identity-wallet-keys">
224
+ <div class="identity-wallet-key-row">
225
+ <span class="identity-wallet-key-label">XPUB</span>
226
+ <code id="identity-wallet-xpub" class="identity-wallet-key-value truncate"></code>
227
+ <button class="identity-wallet-copy copy-key-btn" data-copy="identity-wallet-xpub" title="Copy XPUB"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
228
+ </div>
229
+ <div class="identity-wallet-key-row">
230
+ <span class="identity-wallet-key-label">PeerID</span>
231
+ <code id="identity-wallet-peerid" class="identity-wallet-key-value truncate"></code>
232
+ <button class="identity-wallet-copy copy-key-btn" data-copy="identity-wallet-peerid" title="Copy PeerID"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
233
+ </div>
234
+ </div>
235
+
244
236
  <div class="vcard-actions-footer">
245
237
  <div class="vcard-split-btn-group">
246
238
  <button id="generate-vcard" class="glass-btn vcard-split-btn vcard-split-export">Export vCard</button>
package/styles/main.css CHANGED
@@ -653,42 +653,42 @@ body:has(.modal.active) .nav-bar {
653
653
  display: block;
654
654
  }
655
655
 
656
- /* Modal Tabs (Account modal - Keys / vCard) - Bootstrap-style nav tabs */
656
+ /* Modal Tabs */
657
657
  .modal-tabs {
658
658
  display: flex;
659
- gap: 0;
659
+ gap: 6px;
660
660
  margin: 0;
661
- padding: 0 24px;
661
+ padding: 8px 24px;
662
662
  background: transparent;
663
- border-bottom: 2px solid var(--white-10, rgba(255,255,255,0.1));
663
+ border-bottom: 1px solid var(--white-10, rgba(255,255,255,0.1));
664
664
  }
665
665
 
666
666
  .modal-tab {
667
- padding: 10px 16px;
667
+ padding: 8px 14px;
668
668
  background: transparent;
669
- border: none;
670
- border-bottom: 2px solid transparent;
671
- margin-bottom: -2px;
669
+ border: 1px solid transparent;
670
+ border-radius: 8px;
672
671
  color: var(--white-60);
673
672
  font-family: var(--font-sans);
674
673
  font-size: 13px;
675
674
  font-weight: 500;
676
675
  line-height: 1;
677
676
  cursor: pointer;
678
- transition: color 0.2s, border-color 0.2s;
677
+ transition: color 0.2s, border-color 0.2s, background 0.2s;
679
678
  text-align: center;
680
679
  white-space: nowrap;
681
680
  }
682
681
 
683
682
  .modal-tab:hover {
684
683
  color: var(--white, #fff);
685
- border-bottom-color: var(--white-30, rgba(255,255,255,0.3));
684
+ background: var(--white-05);
685
+ border-color: var(--white-10);
686
686
  }
687
687
 
688
688
  .modal-tab.active {
689
689
  color: var(--white, #fff);
690
- border-bottom-color: var(--accent, #00dc82);
691
- background: transparent;
690
+ border-color: var(--white-20);
691
+ background: var(--white-10);
692
692
  }
693
693
 
694
694
  .modal-tab-content {
@@ -1194,6 +1194,8 @@ body:has(.modal.active) .nav-bar {
1194
1194
  display: flex;
1195
1195
  flex-direction: column;
1196
1196
  gap: 2px;
1197
+ flex: 1 1 auto;
1198
+ min-width: 0;
1197
1199
  }
1198
1200
 
1199
1201
  .account-header-top {
@@ -1209,55 +1211,42 @@ body:has(.modal.active) .nav-bar {
1209
1211
  opacity: 0.9;
1210
1212
  }
1211
1213
 
1212
- .account-address-row {
1214
+ .account-wallet-row {
1213
1215
  display: flex;
1214
1216
  align-items: center;
1215
1217
  gap: 10px;
1216
1218
  margin-top: 4px;
1219
+ width: 100%;
1220
+ min-width: 0;
1217
1221
  }
1218
1222
 
1219
- .account-address-select {
1220
- min-width: 90px;
1221
- max-width: 130px;
1222
- font-size: 12px;
1223
- padding: 4px 28px 4px 10px;
1224
- height: 28px;
1225
- background-position: right 6px center;
1223
+ .account-wallet-select.glass-input.compact {
1224
+ flex: 1 1 auto;
1225
+ min-width: 180px;
1226
+ width: 100%;
1227
+ height: 32px;
1228
+ font-size: 13px;
1229
+ padding: 4px 34px 4px 10px;
1226
1230
  color: var(--white-90);
1227
- user-select: none;
1228
- -webkit-user-select: none;
1229
- }
1230
-
1231
- .account-address-display {
1232
- font-family: var(--font-mono, 'SF Mono', monospace);
1233
- font-size: 11px;
1234
- color: var(--white-50);
1235
- letter-spacing: 0.04em;
1236
- white-space: nowrap;
1237
- overflow: hidden;
1238
- text-overflow: ellipsis;
1239
- max-width: 280px;
1240
- flex: 1;
1241
- min-width: 0;
1231
+ appearance: none;
1232
+ -webkit-appearance: none;
1233
+ -moz-appearance: none;
1234
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 6l4.5 4 4.5-4' stroke='rgba(255,255,255,0.9)' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1235
+ background-repeat: no-repeat;
1236
+ background-position: right 11px center;
1242
1237
  }
1243
1238
 
1244
- .account-address-copy {
1245
- background: none;
1246
- border: none;
1247
- color: var(--white-30);
1248
- cursor: pointer;
1249
- padding: 2px;
1250
- flex-shrink: 0;
1251
- display: flex;
1252
- align-items: center;
1253
- }
1254
- .account-address-copy:hover {
1255
- color: var(--white-70);
1239
+ #account-wallet-manage-btn {
1240
+ flex: 0 0 auto;
1256
1241
  }
1257
1242
 
1258
1243
  @media (max-width: 600px) {
1259
- .account-address-display {
1260
- max-width: 140px;
1244
+ .account-wallet-row {
1245
+ gap: 8px;
1246
+ }
1247
+
1248
+ .account-wallet-select.glass-input.compact {
1249
+ min-width: 0;
1261
1250
  }
1262
1251
  }
1263
1252
 
@@ -1926,6 +1915,10 @@ body:has(.modal.active) .nav-bar {
1926
1915
  ============================================================================= */
1927
1916
 
1928
1917
  /* Portfolio Hero */
1918
+ #wallet-main-view {
1919
+ position: relative;
1920
+ }
1921
+
1929
1922
  .ph-portfolio {
1930
1923
  text-align: center;
1931
1924
  padding: 24px 0 20px;
@@ -1952,12 +1945,21 @@ body:has(.modal.active) .nav-bar {
1952
1945
  justify-content: center;
1953
1946
  gap: 6px;
1954
1947
  margin-top: 10px;
1948
+ width: 100%;
1949
+ max-width: 100%;
1955
1950
  }
1956
1951
 
1957
1952
  .ph-xpub-text {
1953
+ box-sizing: border-box;
1958
1954
  font-size: 11px;
1959
- color: var(--white-40);
1960
- max-width: 200px;
1955
+ color: var(--white-90);
1956
+ background: var(--white-05);
1957
+ border: 1px solid var(--glass-border);
1958
+ border-radius: 6px;
1959
+ padding: 2px 6px;
1960
+ flex: 1 1 auto;
1961
+ min-width: 0;
1962
+ width: 100%;
1961
1963
  overflow: hidden;
1962
1964
  text-overflow: ellipsis;
1963
1965
  white-space: nowrap;
@@ -2183,6 +2185,68 @@ body:has(.modal.active) .nav-bar {
2183
2185
  opacity: 0.45;
2184
2186
  }
2185
2187
 
2188
+ .wallet-asset-action-overlay {
2189
+ position: absolute;
2190
+ inset: 0;
2191
+ z-index: 40;
2192
+ display: none;
2193
+ align-items: flex-end;
2194
+ justify-content: center;
2195
+ padding: 16px;
2196
+ background: rgba(0, 0, 0, 0.42);
2197
+ }
2198
+
2199
+ .wallet-asset-action-card {
2200
+ width: min(100%, 420px);
2201
+ border: 1px solid var(--glass-border);
2202
+ border-radius: 12px;
2203
+ background: rgba(28, 28, 31, 0.98);
2204
+ box-shadow: 0 18px 44px rgba(0, 0, 0, 0.4);
2205
+ padding: 16px;
2206
+ }
2207
+
2208
+ .wallet-asset-action-header {
2209
+ display: flex;
2210
+ align-items: flex-start;
2211
+ justify-content: space-between;
2212
+ gap: 12px;
2213
+ margin-bottom: 12px;
2214
+ }
2215
+
2216
+ .wallet-asset-action-header h4 {
2217
+ margin: 0 0 4px;
2218
+ font-size: 16px;
2219
+ line-height: 1.2;
2220
+ }
2221
+
2222
+ .wallet-asset-action-path {
2223
+ font-size: 11px;
2224
+ color: var(--muted);
2225
+ font-family: var(--font-mono);
2226
+ }
2227
+
2228
+ .wallet-asset-action-address {
2229
+ display: block;
2230
+ box-sizing: border-box;
2231
+ width: 100%;
2232
+ padding: 8px 10px;
2233
+ border: 1px solid var(--glass-border);
2234
+ border-radius: 8px;
2235
+ background: var(--white-05);
2236
+ color: var(--white-90);
2237
+ font-size: 12px;
2238
+ overflow: hidden;
2239
+ text-overflow: ellipsis;
2240
+ white-space: nowrap;
2241
+ }
2242
+
2243
+ .wallet-asset-action-buttons {
2244
+ display: grid;
2245
+ grid-template-columns: 1fr 1fr;
2246
+ gap: 10px;
2247
+ margin-top: 14px;
2248
+ }
2249
+
2186
2250
  .ph-token-icon {
2187
2251
  width: 40px;
2188
2252
  height: 40px;
@@ -5203,6 +5267,63 @@ body:has(.modal.active) .nav-bar {
5203
5267
  display: none;
5204
5268
  }
5205
5269
 
5270
+ .identity-wallet-keys {
5271
+ display: flex;
5272
+ flex-direction: column;
5273
+ gap: 8px;
5274
+ margin: 14px 0 4px;
5275
+ }
5276
+
5277
+ .identity-wallet-key-row {
5278
+ display: grid;
5279
+ grid-template-columns: 62px minmax(0, 1fr) 28px;
5280
+ align-items: center;
5281
+ gap: 8px;
5282
+ min-width: 0;
5283
+ }
5284
+
5285
+ .identity-wallet-key-label {
5286
+ color: var(--muted);
5287
+ font-family: var(--font-mono);
5288
+ font-size: 11px;
5289
+ text-transform: uppercase;
5290
+ }
5291
+
5292
+ .identity-wallet-key-value {
5293
+ box-sizing: border-box;
5294
+ min-width: 0;
5295
+ width: 100%;
5296
+ padding: 5px 8px;
5297
+ border: 1px solid var(--glass-border);
5298
+ border-radius: 7px;
5299
+ background: var(--white-05);
5300
+ color: var(--white-90);
5301
+ font-family: var(--font-mono);
5302
+ font-size: 12px;
5303
+ line-height: 1.3;
5304
+ overflow: hidden;
5305
+ text-overflow: ellipsis;
5306
+ white-space: nowrap;
5307
+ }
5308
+
5309
+ .identity-wallet-copy {
5310
+ width: 28px;
5311
+ height: 28px;
5312
+ padding: 0;
5313
+ border: 1px solid var(--glass-border);
5314
+ border-radius: 7px;
5315
+ background: var(--white-05);
5316
+ color: var(--white-50);
5317
+ display: inline-flex;
5318
+ align-items: center;
5319
+ justify-content: center;
5320
+ }
5321
+
5322
+ .identity-wallet-copy:hover {
5323
+ color: var(--white);
5324
+ background: var(--white-10);
5325
+ }
5326
+
5206
5327
  /* Identity edit button (pencil icon in card) */
5207
5328
  .identity-edit-btn {
5208
5329
  position: absolute;
package/styles/widget.css CHANGED
@@ -656,42 +656,42 @@ body:has(#hd-wallet-ui-container .modal.active) #hd-wallet-ui-container .nav-bar
656
656
  display: block;
657
657
  }
658
658
 
659
- /* Modal Tabs (Account modal - Keys / vCard) - Bootstrap-style nav tabs */
659
+ /* Modal Tabs */
660
660
  #hd-wallet-ui-container .modal-tabs {
661
661
  display: flex;
662
- gap: 0;
662
+ gap: 6px;
663
663
  margin: 0;
664
- padding: 0 24px;
664
+ padding: 8px 24px;
665
665
  background: transparent;
666
- border-bottom: 2px solid var(--white-10, rgba(255,255,255,0.1));
666
+ border-bottom: 1px solid var(--white-10, rgba(255,255,255,0.1));
667
667
  }
668
668
 
669
669
  #hd-wallet-ui-container .modal-tab {
670
- padding: 10px 16px;
670
+ padding: 8px 14px;
671
671
  background: transparent;
672
- border: none;
673
- border-bottom: 2px solid transparent;
674
- margin-bottom: -2px;
672
+ border: 1px solid transparent;
673
+ border-radius: 8px;
675
674
  color: var(--white-60);
676
675
  font-family: var(--font-sans);
677
676
  font-size: 13px;
678
677
  font-weight: 500;
679
678
  line-height: 1;
680
679
  cursor: pointer;
681
- transition: color 0.2s, border-color 0.2s;
680
+ transition: color 0.2s, border-color 0.2s, background 0.2s;
682
681
  text-align: center;
683
682
  white-space: nowrap;
684
683
  }
685
684
 
686
685
  #hd-wallet-ui-container .modal-tab:hover {
687
686
  color: var(--white, #fff);
688
- border-bottom-color: var(--white-30, rgba(255,255,255,0.3));
687
+ background: var(--white-05);
688
+ border-color: var(--white-10);
689
689
  }
690
690
 
691
691
  #hd-wallet-ui-container .modal-tab.active {
692
692
  color: var(--white, #fff);
693
- border-bottom-color: var(--accent, #00dc82);
694
- background: transparent;
693
+ border-color: var(--white-20);
694
+ background: var(--white-10);
695
695
  }
696
696
 
697
697
  #hd-wallet-ui-container .modal-tab-content {
@@ -1196,6 +1196,8 @@ body:has(#hd-wallet-ui-container .modal.active) #hd-wallet-ui-container .nav-bar
1196
1196
  display: flex;
1197
1197
  flex-direction: column;
1198
1198
  gap: 2px;
1199
+ flex: 1 1 auto;
1200
+ min-width: 0;
1199
1201
  }
1200
1202
 
1201
1203
  #hd-wallet-ui-container .account-header-top {
@@ -1211,55 +1213,42 @@ body:has(#hd-wallet-ui-container .modal.active) #hd-wallet-ui-container .nav-bar
1211
1213
  opacity: 0.9;
1212
1214
  }
1213
1215
 
1214
- #hd-wallet-ui-container .account-address-row {
1216
+ #hd-wallet-ui-container .account-wallet-row {
1215
1217
  display: flex;
1216
1218
  align-items: center;
1217
1219
  gap: 10px;
1218
1220
  margin-top: 4px;
1221
+ width: 100%;
1222
+ min-width: 0;
1219
1223
  }
1220
1224
 
1221
- #hd-wallet-ui-container .account-address-select {
1222
- min-width: 90px;
1223
- max-width: 130px;
1224
- font-size: 12px;
1225
- padding: 4px 28px 4px 10px;
1226
- height: 28px;
1227
- background-position: right 6px center;
1225
+ #hd-wallet-ui-container .account-wallet-select.glass-input.compact {
1226
+ flex: 1 1 auto;
1227
+ min-width: 180px;
1228
+ width: 100%;
1229
+ height: 32px;
1230
+ font-size: 13px;
1231
+ padding: 4px 34px 4px 10px;
1228
1232
  color: var(--white-90);
1229
- user-select: none;
1230
- -webkit-user-select: none;
1231
- }
1232
-
1233
- #hd-wallet-ui-container .account-address-display {
1234
- font-family: var(--font-mono, 'SF Mono', monospace);
1235
- font-size: 11px;
1236
- color: var(--white-50);
1237
- letter-spacing: 0.04em;
1238
- white-space: nowrap;
1239
- overflow: hidden;
1240
- text-overflow: ellipsis;
1241
- max-width: 280px;
1242
- flex: 1;
1243
- min-width: 0;
1233
+ appearance: none;
1234
+ -webkit-appearance: none;
1235
+ -moz-appearance: none;
1236
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 6l4.5 4 4.5-4' stroke='rgba(255,255,255,0.9)' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1237
+ background-repeat: no-repeat;
1238
+ background-position: right 11px center;
1244
1239
  }
1245
1240
 
1246
- #hd-wallet-ui-container .account-address-copy {
1247
- background: none;
1248
- border: none;
1249
- color: var(--white-30);
1250
- cursor: pointer;
1251
- padding: 2px;
1252
- flex-shrink: 0;
1253
- display: flex;
1254
- align-items: center;
1255
- }
1256
- #hd-wallet-ui-container .account-address-copy:hover {
1257
- color: var(--white-70);
1241
+ #hd-wallet-ui-container #account-wallet-manage-btn {
1242
+ flex: 0 0 auto;
1258
1243
  }
1259
1244
 
1260
1245
  @media (max-width: 600px) {
1261
- #hd-wallet-ui-container .account-address-display {
1262
- max-width: 140px;
1246
+ #hd-wallet-ui-container .account-wallet-row {
1247
+ gap: 8px;
1248
+ }
1249
+
1250
+ #hd-wallet-ui-container .account-wallet-select.glass-input.compact {
1251
+ min-width: 0;
1263
1252
  }
1264
1253
  }
1265
1254
 
@@ -1924,6 +1913,10 @@ body:has(#hd-wallet-ui-container .modal.active) #hd-wallet-ui-container .nav-bar
1924
1913
  ============================================================================= */
1925
1914
 
1926
1915
  /* Portfolio Hero */
1916
+ #hd-wallet-ui-container #wallet-main-view {
1917
+ position: relative;
1918
+ }
1919
+
1927
1920
  #hd-wallet-ui-container .ph-portfolio {
1928
1921
  text-align: center;
1929
1922
  padding: 24px 0 20px;
@@ -1950,12 +1943,21 @@ body:has(#hd-wallet-ui-container .modal.active) #hd-wallet-ui-container .nav-bar
1950
1943
  justify-content: center;
1951
1944
  gap: 6px;
1952
1945
  margin-top: 10px;
1946
+ width: 100%;
1947
+ max-width: 100%;
1953
1948
  }
1954
1949
 
1955
1950
  #hd-wallet-ui-container .ph-xpub-text {
1951
+ box-sizing: border-box;
1956
1952
  font-size: 11px;
1957
- color: var(--white-40);
1958
- max-width: 200px;
1953
+ color: var(--white-90);
1954
+ background: var(--white-05);
1955
+ border: 1px solid var(--glass-border);
1956
+ border-radius: 6px;
1957
+ padding: 2px 6px;
1958
+ flex: 1 1 auto;
1959
+ min-width: 0;
1960
+ width: 100%;
1959
1961
  overflow: hidden;
1960
1962
  text-overflow: ellipsis;
1961
1963
  white-space: nowrap;
@@ -2181,6 +2183,68 @@ body:has(#hd-wallet-ui-container .modal.active) #hd-wallet-ui-container .nav-bar
2181
2183
  opacity: 0.45;
2182
2184
  }
2183
2185
 
2186
+ #hd-wallet-ui-container .wallet-asset-action-overlay {
2187
+ position: absolute;
2188
+ inset: 0;
2189
+ z-index: 40;
2190
+ display: none;
2191
+ align-items: flex-end;
2192
+ justify-content: center;
2193
+ padding: 16px;
2194
+ background: rgba(0, 0, 0, 0.42);
2195
+ }
2196
+
2197
+ #hd-wallet-ui-container .wallet-asset-action-card {
2198
+ width: min(100%, 420px);
2199
+ border: 1px solid var(--glass-border);
2200
+ border-radius: 12px;
2201
+ background: rgba(28, 28, 31, 0.98);
2202
+ box-shadow: 0 18px 44px rgba(0, 0, 0, 0.4);
2203
+ padding: 16px;
2204
+ }
2205
+
2206
+ #hd-wallet-ui-container .wallet-asset-action-header {
2207
+ display: flex;
2208
+ align-items: flex-start;
2209
+ justify-content: space-between;
2210
+ gap: 12px;
2211
+ margin-bottom: 12px;
2212
+ }
2213
+
2214
+ #hd-wallet-ui-container .wallet-asset-action-header h4 {
2215
+ margin: 0 0 4px;
2216
+ font-size: 16px;
2217
+ line-height: 1.2;
2218
+ }
2219
+
2220
+ #hd-wallet-ui-container .wallet-asset-action-path {
2221
+ font-size: 11px;
2222
+ color: var(--muted);
2223
+ font-family: var(--font-mono);
2224
+ }
2225
+
2226
+ #hd-wallet-ui-container .wallet-asset-action-address {
2227
+ display: block;
2228
+ box-sizing: border-box;
2229
+ width: 100%;
2230
+ padding: 8px 10px;
2231
+ border: 1px solid var(--glass-border);
2232
+ border-radius: 8px;
2233
+ background: var(--white-05);
2234
+ color: var(--white-90);
2235
+ font-size: 12px;
2236
+ overflow: hidden;
2237
+ text-overflow: ellipsis;
2238
+ white-space: nowrap;
2239
+ }
2240
+
2241
+ #hd-wallet-ui-container .wallet-asset-action-buttons {
2242
+ display: grid;
2243
+ grid-template-columns: 1fr 1fr;
2244
+ gap: 10px;
2245
+ margin-top: 14px;
2246
+ }
2247
+
2184
2248
  #hd-wallet-ui-container .ph-token-icon {
2185
2249
  width: 40px;
2186
2250
  height: 40px;
@@ -5195,6 +5259,63 @@ body:has(#hd-wallet-ui-container .modal.active) #hd-wallet-ui-container .nav-bar
5195
5259
  display: none;
5196
5260
  }
5197
5261
 
5262
+ #hd-wallet-ui-container .identity-wallet-keys {
5263
+ display: flex;
5264
+ flex-direction: column;
5265
+ gap: 8px;
5266
+ margin: 14px 0 4px;
5267
+ }
5268
+
5269
+ #hd-wallet-ui-container .identity-wallet-key-row {
5270
+ display: grid;
5271
+ grid-template-columns: 62px minmax(0, 1fr) 28px;
5272
+ align-items: center;
5273
+ gap: 8px;
5274
+ min-width: 0;
5275
+ }
5276
+
5277
+ #hd-wallet-ui-container .identity-wallet-key-label {
5278
+ color: var(--muted);
5279
+ font-family: var(--font-mono);
5280
+ font-size: 11px;
5281
+ text-transform: uppercase;
5282
+ }
5283
+
5284
+ #hd-wallet-ui-container .identity-wallet-key-value {
5285
+ box-sizing: border-box;
5286
+ min-width: 0;
5287
+ width: 100%;
5288
+ padding: 5px 8px;
5289
+ border: 1px solid var(--glass-border);
5290
+ border-radius: 7px;
5291
+ background: var(--white-05);
5292
+ color: var(--white-90);
5293
+ font-family: var(--font-mono);
5294
+ font-size: 12px;
5295
+ line-height: 1.3;
5296
+ overflow: hidden;
5297
+ text-overflow: ellipsis;
5298
+ white-space: nowrap;
5299
+ }
5300
+
5301
+ #hd-wallet-ui-container .identity-wallet-copy {
5302
+ width: 28px;
5303
+ height: 28px;
5304
+ padding: 0;
5305
+ border: 1px solid var(--glass-border);
5306
+ border-radius: 7px;
5307
+ background: var(--white-05);
5308
+ color: var(--white-50);
5309
+ display: inline-flex;
5310
+ align-items: center;
5311
+ justify-content: center;
5312
+ }
5313
+
5314
+ #hd-wallet-ui-container .identity-wallet-copy:hover {
5315
+ color: var(--white);
5316
+ background: var(--white-10);
5317
+ }
5318
+
5198
5319
  /* Identity edit button (pencil icon in card) */
5199
5320
  #hd-wallet-ui-container .identity-edit-btn {
5200
5321
  position: absolute;