polish: signing dialog, unpriced balances, Hive button contrast - #382
Conversation
The profile links (PeakD / Hive.blog / Ecency) and both Retry buttons used a bare Chakra `variant="outline"`. The app mounts ChakraProvider without a color-mode provider, so that variant resolves against the gray palette's light-mode pair — near-black text inside a bright border — which inverts against the dark glass card and renders the labels almost unreadable. Every other outline button in the app already passes explicit colors (SweepDialog, AddChainDialog, StakingPanel, ...); this panel was the only one relying on the default. Add a shared `outlineBtn` style using the v3 tokens the panel already speaks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three fixes to the signing approval dialog, all visible on any blind-sign request with a large payload: - Glow: the card ran an infinite 2s pulse peaking at a 24px/0.7-alpha gold shadow plus a 48px halo, which washed out the card edge and made the hex payload hard to scan. Reduce to a faint 10px/0.18 breathe over 4s and drop the border from 2px solid gold to 1px at 45% alpha. - Pinned actions: the whole card was `maxH=90vh overflowY=auto`, so a large calldata blob pushed Approve/Reject below the fold — the user had to scroll past dozens of lines of hex to reach either button. Split into a pinned header, a `flex=1 minH=0 overflowY=auto` scroll region, and a pinned action row. - Decoded calldata: add an Etherscan-style Hex/Decoded toggle for payloads we can't clear-sign. Without an ABI we can't name parameters, but the 4-byte selector plus indexed 32-byte words — annotated where a word looks like an address or fits a safe integer — is what makes a blind-signing payload auditable. Non-word-aligned trailing bytes are flagged, since that indicates malformed calldata. New strings are added to en only; other locales already run behind and i18next falls back to English via fallbackLng. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The drilled chain view gated its empty state on USD value alone:
const hasAggregate = (agg?.balanceUsd ?? 0) > 0
A chain whose price feed is missing or failing reports balanceUsd 0 while
the user still holds funds, so the pane rendered "No <chain> balance yet"
over a real balance. Reproduced with Arbitrum holding 0.000891 ETH: the
sidebar showed the amount, the detail pane claimed the chain was empty.
For a wallet, "your funds are gone" is the worst possible false message.
The `fundedAddrs` filter directly above had the same bug, so the
"held on N other accounts" hint was likewise blind to unpriced accounts.
Both now also check the token amount, via a shared `hasNonZeroBalance()`
in lib/formatting so the next balance gate has something correct to reach
for. USD stays presentation-only.
Note: the All-Chains total is unchanged and still excludes unpriced chains
— that figure is USD and the arithmetic is right. That Arbitrum native ETH
resolves no price at all is a separate upstream pricing bug.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every EVM broadcast path handed the device's serialized bytes straight to eth_sendRawTransaction without checking that the signature recovers to the account we asked to sign. That is exactly the failure the browser extension already guards against, and the vault had no equivalent seatbelt: a WalletConnect dApp signing through the vault would silently broadcast a doomed transaction. The failure is not a compromised device. KeepKey firmware 7.x.0 through 7.14.0 hashes the EIP-1559 empty access-list byte (0xC0) too early, so any tx whose calldata exceeds the 1024-byte single-chunk transport limit gets a non-canonical pre-image. The signature is mathematically valid, so RPC nodes ACCEPT the broadcast — and then the tx is dropped from the mempool forever, because the address it recovers to has no balance and the wrong nonce. Fixed in firmware 7.14.1, but the stable channel still ships an affected build, so the check has to live in the vault. Add verifyEvmSigner() and make `expectedFrom` a required parameter of broadcastEvmTx(). Required rather than optional on purpose: a silently mis-signed tx is unrecoverable once it leaves, so a future broadcast path must not be able to forget the check. All five call sites are updated (three in swap.ts, the custom-chain handler in index.ts, WalletConnect). WalletConnect verifies even when it is not the broadcaster — the dApp may broadcast the bytes we hand back, so the check belongs before they leave. Failing loudly turns a silently stuck transaction — and, on a swap, a spent approval with no swap — into an actionable error naming the firmware fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Added a fourth commit ( Verify the device signer before broadcastingEvery EVM broadcast path handed the device's serialized bytes straight to This isn't about a compromised device. Firmware 7.x.0–7.14.0 hashes the EIP-1559 empty access-list byte ( Design note: WalletConnect verifies even when it isn't the broadcaster, since the dApp may broadcast the bytes we hand back. TestsNew Full suite: 325 pass / 2 fail. Those 2 failures are pre-existing — I confirmed by stashing this branch's changes and re-running |
BitHighlander
left a comment
There was a problem hiding this comment.
Reviewed the full 11-file diff. I found two fail-open paths in the EVM signer guard and pushed commit 33495f65 to the PR branch:
- signer-verification failures are now typed and rethrown before the swap code can fall back to Pioneer, so rejected bytes cannot route around the guard
- custom EVM broadcast now verifies against the request's
fromAddress, not mutable current account selection between build/sign/broadcast
Validation after the correction:
bun test __tests__/evm-signer-verify.test.ts— 4 passed- full renderer/backend/Electrobun
bun run buildpassed git diff --checkpassed
I found no further code-review blockers in the Hive styling, unpriced-balance handling, signing-dialog/calldata inspector, WalletConnect verification, or EVM paths. The remaining gate is canonical GitHub Actions; develop PR builds are being enabled in #381, after which this branch should be synchronized/re-run before merge.
BitHighlander
left a comment
There was a problem hiding this comment.
Approved after full review and correction of the EVM signer-verification paths.
The branch was synchronized with current develop after #381 merged. Combined head 33a33e00 passes canonical GitHub Actions on Linux and macOS, including module builds, the Ironwood Zcash sidecar, the full Vault app build, architecture verification, packaging, and artifact upload.
The earlier fail-open findings remain fixed:
- signer-verification errors cannot fall through to Pioneer broadcast
- custom EVM broadcast verifies against the signed request's
fromAddress, not mutable account selection
No unresolved review threads or remaining merge blockers.
Three independent UI/correctness fixes found while running the latest
developlocally. Each is its own commit and can be split if you'd rather review separately.1. Hive panel outline buttons unreadable
The PeakD / Hive.blog / Ecency profile links and both Retry buttons used a bare Chakra
variant="outline". The app mountsChakraProviderwith no color-mode provider, so that variant resolves against the gray palette's light-mode pair — near-black text inside a bright border — which inverts against the dark glass card.Every other outline button in the app already passes explicit colors (
SweepDialog,AddChainDialog,StakingPanel, …); this panel was the only one relying on the default, which is why it was the only place the inversion was visible.2. Signing dialog polish
maxH="90vh" overflowY="auto", so a large calldata blob pushed Approve/Reject below the fold. Now: pinned header, scrolling content region, pinned action row.New strings added to
enonly — other locales already run behind andfallbackLng: "en"covers them.3. Unpriced balances rendered as "no balance"
The drilled chain view gated its empty state on USD alone. A chain with a missing/failing price feed reports
balanceUsd: 0while the user still holds funds, so the pane rendered "No Arbitrum balance yet" over a real 0.000891 ETH balance — the sidebar showed the amount, the detail pane claimed the chain was empty. ThefundedAddrsfilter above it had the same bug.Both now also check the token amount, via a shared
hasNonZeroBalance()inlib/formatting.Out of scope: the All-Chains total still excludes unpriced chains — that figure is USD and the arithmetic is correct. That Arbitrum native ETH resolves no price at all is a separate upstream pricing bug worth its own issue.
Testing
tsc --noEmitclean (aside from the pre-existingTS2688 minimatchimplicit-type-library error ondevelop),vite buildpasses.🤖 Generated with Claude Code