Skip to content

fix(side-panel): receive tab loads offline + auto token discovery#50

Merged
BitHighlander merged 8 commits intodevelopfrom
ui-revamp
Apr 28, 2026
Merged

fix(side-panel): receive tab loads offline + auto token discovery#50
BitHighlander merged 8 commits intodevelopfrom
ui-revamp

Conversation

@BitHighlander
Copy link
Copy Markdown
Collaborator

Summary

Two related fixes for the side-panel UI revamp branch.

Receive tab — loads without a device round-trip

  • Derive UTXO receive addresses locally from the cached xpub (BIP32 + script-type encoding) instead of calling sdk.address.utxoGetAddress. Page renders in view-only mode and survives device hot-swaps. Adds @scure/bip32, @scure/base, @noble/hashes; new chrome-extension/src/background/utxoDerive.ts covers BTC (legacy / segwit / native segwit), LTC, DOGE, DASH, BCH cashaddr.
  • Fix the account-switch dropdown — it looked up pubkeys by .address || .master, both empty on UTXO, so clicks were silent no-ops. Now keys by pubkey.note and shows real derived addresses instead of truncated xpubs.
  • Labels read Account 0 · Native Segwit etc. so the three BTC entries that all share account index 0 are distinguishable.
  • GET_UTXO_ADDRESS match order tightened so note wins over scriptType (multiple p2wpkh accounts can be disambiguated).

Asset detail layout

  • Center Receive and AssetDetail views vertically (full-height wrappers + biased flex spacers).
  • Hide the Tokens tab on UTXO chains entirely instead of showing an empty "No tokens for UTXO chains" panel.

Token auto-discovery race

  • Cold-start used three parallel prefetch().then(fetch(true)) chains (Solana / Tron / TON). Each fetch's snapshot missed pubkeys still being added by the other two prefetches, and the staleness guard at commit time discarded all but the slowest. Result: SPL / TRC-20 tokens silently never landed in cachedBalances, and users had to press "Discover Tokens" to see them. Replaced with a single Promise.allSettled([sol, tron, ton]).then(fetch(true)) so the one committed fetch sees the full pubkey set.
  • Wire the Discover/Refresh button to REFRESH_ALL_BALANCES so it actually round-trips Pioneer instead of re-reading the cache (it was a placebo before).

Test plan

  • Reload extension; with no device attached, open Receive on BTC — address renders without a spinner.
  • With device attached, switch the Receive account dropdown; the QR/address updates and the global pubkey context follows.
  • Confirm BTC dropdown labels distinguish Account 0 · Legacy / Segwit / Native Segwit.
  • Open Bitcoin asset detail — Tokens tab is gone, only Activity is shown; layout is centered.
  • Cold-start the extension and open Solana asset detail — SPL tokens appear without pressing any button.
  • Press the Refresh / Discover Tokens button — observe a Pioneer /portfolio request fires (network tab) instead of just re-reading cache.

🤖 Generated with Claude Code

BitHighlander and others added 8 commits April 24, 2026 18:13
…n handoff)

Adopts the Claude Design handoff's visual language for the side panel —
darker base (#0b0d10), Inter + JetBrains Mono typography, gold-gradient
primary buttons, and tokenized surfaces/lines. Shield badge on the left
doubles as both "go home" button and device-status indicator (gold
pulsing when transient, green when paired, red when errored), replacing
the separate status icon.

Bug fixes along the way:
- "Add blockchain" picker now hides the dashboard block and is resettable
  from the home button (state lifted out of Balances into SidePanel).
- Asset-detail drawer no longer renders a redundant "Ethereum" title bar;
  a minimal floating back chevron replaces it.
- Header row heights unified at 32px so the Network/Account pills and
  shield all sit on the same baseline.
- TronLink badge next to "Tron" on the asset page (passive, no link —
  TronLink is an unaffiliated wallet; we just signal protocol use).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Receive tab
- Derive UTXO receive addresses locally from xpubs (BIP32 +
  script-type encoding) instead of round-tripping the device. Page
  now renders in view-only mode and survives hot-swaps. Adds
  @scure/bip32, @scure/base, @noble/hashes; new utxoDerive.ts covers
  BTC (legacy/segwit/native segwit), LTC, DOGE, DASH, BCH cashaddr.
- Fix the account dropdown — it looked up pubkeys by .address|.master
  (both empty on UTXO) so clicks were silent no-ops. Keys by
  pubkey.note now and shows derived addresses, not truncated xpubs.
- Labels read "Account 0 · Native Segwit" etc. so the three BTC
  entries that all share account index 0 are distinguishable.
- Tighten GET_UTXO_ADDRESS match order so note wins over scriptType;
  multiple p2wpkh accounts can be disambiguated.
- Center Receive + AssetDetail vertically; hide the Tokens tab on
  UTXO chains instead of rendering an empty state.

Token auto-discovery
- Replace the per-chain prefetch().then(fetch(true)) chains with a
  single Promise.allSettled([sol, tron, ton]).then(fetch(true)).
  The old shape raced three parallel fetches that the snapshot
  staleness guard then discarded — SPL/TRC-20 tokens silently never
  landed in cachedBalances, and users had to press "Discover Tokens"
  to see them.
- Wire the Discover/Refresh button to REFRESH_ALL_BALANCES so it
  forces a Pioneer round-trip instead of re-reading the cache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- script_type, not scriptType: raw pubkey objects use snake_case (matches
  chainConfig.ts and the SDK request shape in wallet.ts). Reading
  .scriptType was always undefined, so segwit / native-segwit accounts
  silently fell through to the p2pkh branch and rendered legacy "1..."
  addresses for "Account 0 · Native Segwit" entries.
- Drop the chrome.storage.session derived-address cache. Local BIP32 +
  hash160 + encoding is microseconds; the cache only added a stale
  surface — it was keyed by (networkId, scriptType, note) without the
  xpub or device id, so a hot-swap in the same session could return the
  previous device's address for the same note.
- Pick the UTXO Receive default address from pubkeyContext.note (the
  header's selected account) instead of ctxAsset.pubkeys[0]. After
  SET_ASSET_CONTEXT, the asset's pubkeys field carries every pubkey on
  the network, and idx===0 was the first configured chainConfig path
  (legacy BTC), not the user's selection. New effect resolves
  addressByNote[pubkeyContext.note] when both are present, falls back
  to pubkeys[0].note only if no context is set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The high-finding from the second review pass: header-selected UTXO
accounts weren't reliably carried into Receive. Header fired
SET_ASSET_CONTEXT with the selected pubkey, but the background
overwrote asset.pubkeys with all network pubkeys, and
GET_PUBKEY_CONTEXT only restored by accountIndex — UTXO accounts share
an accountIndex (BTC's Legacy / Segwit / Native Segwit all live at
account 0), so it always fell back to scoped[0] and Receive opened on
the first configured chainConfig path.

- NetworkAccountHeader.setAssetContext now puts script_type on the
  asset alongside accountIndex (snake_case to match raw pubkey shape).
- GET_PUBKEY_CONTEXT prefers script_type for the scoped lookup, then
  falls through to accountIndex (multi-account EVM) and finally
  scoped[0]. Receive's existing pubkeyContext.note → addressByNote
  resolver picks up the right entry without further changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous fix carried script_type through SET_ASSET_CONTEXT, but
multiple BTC paths share each script_type (chainConfig has account 0
+ account 1 both at p2wpkh, plus several legacy accounts). The header
also keyed rows on script_type alone, so account-1 Native Segwit
collapsed onto account-0 in the dropdown, and even if surfaced
separately GET_PUBKEY_CONTEXT would have matched the first p2wpkh
(account 0).

- AccountItem gains an optional `note` field. headerUtils' BTC and
  UTXO builders key rows on `pk.note` (unique per chainConfig path)
  and append "· Account N" to the label only when the script_type
  repeats. Only the first p2wpkh row is flagged isDefault now.
- NetworkAccountHeader.setAssetContext sends `note` alongside
  `script_type` and `accountIndex`.
- GET_PUBKEY_CONTEXT match priority is now note → script_type →
  accountIndex → scoped[0]. Note is the only identifier that's
  unique across every chainConfig path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Header dropdown was correct, but two entry points still desynced:

- Global Receive / dashboard / asset list: SidePanel.handleGlobalReceive
  picks a default balance row that has no note or script_type, so the
  background's GET_PUBKEY_CONTEXT fell through to scoped[0] (legacy
  BTC). The header could read "Native SegWit" while Receive surfaced
  a "1..." address. Background SET_ASSET_CONTEXT now defaults a UTXO
  asset that arrived without note/script_type to the network's first
  p2wpkh pubkey (else scoped[0]) before storing.
- Header auto-default: the useEffect that picks the default account on
  mount/network-change set local selectedAccountKey only — it never
  fired SET_ASSET_CONTEXT. Stored context could be stale or absent
  while the dropdown showed Native SegWit. Effect now calls
  setAssetContext for the chosen account when a network is selected.
  Reordered the effect to sit after setAssetContext's useCallback so
  it isn't referenced in the temporal dead zone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Header restore by note (was: accountIndex only). UTXO accounts
   share an accountIndex, so a stored "BTC Native Segwit account 0"
   round-trip would resolve to whatever chainConfig path landed first
   at accountIndex 0 — usually legacy. fetchPubkeys now seeds
   desiredNote / desiredScriptType / desiredAccountIndex from the
   stored asset context.

2. ASSET_CONTEXT_UPDATED resync. Listener feeds the same desired*
   state, so within-network context changes now retarget the dropdown
   instead of leaving selectedAccountKey pinned to a stale row. The
   auto-select effect compares before firing setAssetContext, so the
   broadcast back from our own SET_ASSET_CONTEXT can't loop.

3. Per-chain UTXO fallback in background SET_ASSET_CONTEXT. Previously
   defaulted every bip122:* asset to first p2wpkh, which silently
   shifted LTC from p2pkh-default (header's items.length===0) to
   native segwit. Now BTC keeps p2wpkh-first, other UTXO uses
   scoped[0] — both match the header builders.

4. Auto-default no longer opens AssetDetail. setAssetContext is now a
   pure data setter (sends SET_ASSET_CONTEXT, returns the asset). The
   click handlers (handleNetworkSelect / handleAccountSelect) call
   onSelectNetwork explicitly, so the auto-default effect can sync
   stored context without an unprompted drawer-open on cold start.

5. AssetDetail derives UTXO addresses. asset.pubkeys[0].address is
   empty on UTXO and Pioneer's /portfolio stuffs the xpub into
   b.address (background/index.ts:439), so the address bar / copy /
   explorer link were either blank or showing an xpub. New UTXO
   branch in the address-fetch effect calls GET_UTXO_ADDRESS with
   the asset's note + script_type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ASSET_CONTEXT_UPDATED listener fires for every SET_ASSET_CONTEXT
the background receives — including the header auto-default sync we
just added (cold-start restore + ASSET_CONTEXT_UPDATED resync) and
dApp-driven chain switches. With onAssetDetailOpen() unconditional
in the listener, the drawer would still pop up unprompted even
after we made setAssetContext a pure data setter.

Drop the auto-open. setSelectedAsset still runs, so an already-open
drawer reflects the new context. Explicit opens already go through
handleAssetSelect (asset list click, header click), which sets
selectedAsset and calls onAssetDetailOpen together.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@BitHighlander BitHighlander merged commit ee840ea into develop Apr 28, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant