Release v1.2.9 - #59
Merged
Merged
Conversation
Settings drawer now has full update flow — Download, Restart & Install, and a "Download from GitHub" fallback when auto-update fails (Windows zig-zstd decompression issue). Previously the settings drawer only showed "Version X available" text with no actionable buttons. Users had to find the top UpdateBanner (which scrolls off-screen on default Windows window size) to actually download and install updates. Changes: - DeviceSettingsDrawer: add onDownloadUpdate, onApplyUpdate, updateVersion props - Settings update section: Download Update button (available phase), Restart & Install button (ready phase), Download from GitHub link (error phase fallback), View Release link - App.tsx: wire update.downloadUpdate/applyUpdate/info.version to both drawer instances
* fix: Windows auto-update — download setup exe directly, bypass zig-zstd Electrobun's zig-zstd decompressor crashes on Windows with "undefined is not an object" because updateInfo is uninitialized. Rather than patching Electrobun, bypass it entirely on Windows: 1. downloadUpdate detects Windows, fetches setup exe from GitHub release 2. Streams with progress → rpc update-status messages → UI progress bar 3. Saves to temp dir 4. applyUpdate launches the installer via cmd /c start (detached) 5. App exits, installer replaces old version, relaunches Also adds Download/Install/Download-from-GitHub buttons to Settings drawer so users don't need the top banner (scrolls off-screen on default Windows window size). * fix: guard against downloading current version as update
Bootloader flash auto-reboots — show "Device is rebooting..." with spinner. Firmware flash requires manual action — keep "Please disconnect and reconnect." Previously both showed identical disconnect/reconnect text (QA-1.2.0 #1). Also adds wizard-messaging.test.ts (17 tests) covering reboot message differentiation, elapsed-time escalation, and seed phrase warning visibility. Adds engine-state-machine.test.ts and wizard-messaging.test.ts to make test-unit.
…ompt When user enters wrong PIN, the overlay now shows an error banner with shake animation instead of silently dismissing and reappearing. Backend emits pin-error event when promptPin fails and device is still locked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix timing race: reset pinFailed before setting true via queueMicrotask so useEffect fires even when PinEntry remounts after unmount gap - Clear pinFailed on cancel and wipe (prevents stale error on re-open) - Simplify pin-error schema to Record<string, never> (attemptsRemaining was declared but never populated) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Timing-safe API key validation — iterate all keys with constant-time comparison to prevent timing-based key guessing (auth.ts) 2. Sanitize audit log — redact signatures, serializedTx, typedData, and other sensitive fields from signing route logs (rest-api.ts) 3. localStorage try/catch in i18n init — prevents crash in private browsing or when storage is blocked (i18n/index.ts) 4. ChainId range validation — reject NaN, negative, or >uint32 values before passing to device for signing (rest-api.ts) 5. Makefile build target now depends on build-zcash-cli to prevent incomplete builds missing the zcash binary
Sanitize: recurse into nested objects (depth-limited to 8) so nested
signing payloads like signDoc.msgs, txMetadata.signedPayload are
redacted. Added data, signedPayload, msgs, memo, tx, txBytes, signDoc,
authInfoBytes, bodyBytes to sensitive key set. Switched to Set for O(1)
lookup.
ChainId: reject non-integer strings ("1abc" → 400, "1.5" → 400,
"" → 400) with regex validation before parseInt. Reject fractional
numbers (1.5 → 400) with Number.isInteger check. Prevents silent
coercion to wrong chain.
fix: differentiate bootloader vs firmware reboot messaging
windowsDownloadAndInstall() read version from Updater.updateInfo() which is null for pre-release updates found via GitHub API. The function threw "No update version available" and the UI fell back to opening the GitHub releases webpage. Fix: cache the update version in pendingUpdateVersion when found by either the pre-release GitHub check or the Electrobun stable check. windowsDownloadAndInstall() reads this first. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: Incorrect PIN error UI in PIN overlay
fix: Windows auto-update fails for pre-release channel
After bootloader flash, the OLD bootloader does NOT auto-reboot — device screen shows "FIRMWARE UPDATE COMPLETE — Please disconnect and reconnect." The app was showing a blue "Device is rebooting... will restart automatically" box, causing users to wait indefinitely. Changed bootloader step's reboot UI to yellow "Please disconnect and reconnect your KeepKey" with time-based escalation, matching the actual device behavior. Firmware step remains separate — NEW bootloader DOES auto-reboot via board_reset(). Updated wizard-messaging tests to properly distinguish bootloader (manual-disconnect) from firmware (auto-reboot).
After bootloader flash, OLD bootloader does NOT auto-reboot — device says "Please disconnect and reconnect." After firmware flash, NEW bootloader calls board_reset() — device auto-reboots. rebootPhase was a single 'rebooting' state, so both steps rendered the same UI. Now split into 'bootloader-rebooting' (yellow disconnect box) and 'firmware-rebooting' (blue spinner + "restarting automatically"). Derived `isRebooting` boolean replaces all guard checks. Render blocks use the specific phase to show the correct UI for each step. Evidence: firmware source — bootloader/main.c:update_fw() calls board_reset(RESET_PARAM_NONE), blupdater/main.c:success() calls board_reset(RESET_PARAM_REQUEST_UPDATE). PR #37 logs confirm firmware auto-reboots in ~7s.
…messaging fix: bootloader step shows rebooting when device needs manual unplug
Zig 0.15 no longer implicitly coerces [:0]const u16 (sentinel-terminated slice from utf8ToUtf16LeStringLiteral) to [*]const u16. Use .ptr to explicitly get the many-pointer from the slice. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Electrobun's Updater.checkForUpdate() fails on Windows (no update.json published in releases) and returns stale versions on macOS (update.json is generated before the release is published, so it's always one version behind). Replace both the RPC checkForUpdate handler and the startup background check with GitHub releases API, which works cross-platform and always returns the correct latest version. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: Zig 0.15.2 DrawTextW compat for wrapper launcher
…launch) Electrobun's Updater.downloadUpdate() uses releases/latest/download which ignores pre-releases and has stale update.json. macosDownloadAndInstall() downloads the tar.zst directly from the specific release tag, decompresses, swaps the .app bundle, and relaunches — same pattern as Windows bypass.
1. Asset name now uses process.arch to select arm64 vs x64 tar.zst (was hardcoded to arm64, breaking Intel Macs) 2. Use zig-zstd from Contents/MacOS/ instead of system zstd (stock macOS doesn't ship zstd; falls back to system zstd if bundled binary isn't found)
fix: use GitHub API for update checks instead of Electrobun native
zig-zstd uses different CLI flags than zstd, stock macOS has no zstd. Replace macosDownloadAndInstall() with macosOpenReleasePage() — opens the GitHub release tag page so user can download the DMG directly.
…Mac) - Remove windowsDownloadAndInstall(), windowsLaunchInstaller(), macosOpenReleasePage() — replace with single openReleasePage() - Both Win + Mac downloadUpdate/applyUpdate now open the releases page - UpdateBanner: merge available+ready phases into one "Download from GitHub" button - DeviceSettingsDrawer: simplify to single button for all update states - PowerShell BOM fix: use .NET WriteAllText to avoid UTF-8 BOM in version.json
The collapsed available+ready button broke Linux which still uses Electrobun's Updater.downloadUpdate()/applyUpdate(). Restore separate phases: available → Download, ready → Restart & Install, error → fallback. On Win/Mac the backend redirects both to openReleasePage() so the phase never reaches 'ready', but Linux reaches it via Electrobun.
fix: replace in-app auto-update with GitHub releases redirect (Win + Mac)
Root cause (proven by ALPHA-1 through ALPHA-6 on Windows):
device-protocol submodule has lib/ in .gitignore. On fresh checkout,
lib/ is empty. collect-externals copies empty lib/ into the bundle.
Bun crashes on require('device-protocol/lib/messages_pb') before any
Electrobun code runs — silent blank window, zero logs, zero diagnostics.
Fixes:
1. collect-externals.ts: verify messages_pb.js exists after copy,
attempt auto-build if missing, fail hard if still missing
2. build-windows-production.ps1: init device-protocol submodule,
build lib/ if absent; fix em-dashes that break PowerShell 5
3. vite.config.ts: fixed filenames (no content hashes) — prevents
WebView2 cache mismatch on over-install upgrade
4. db.ts: strip BOM from version.json (PowerShell 5 writes BOM,
breaks Electrobun's JSON.parse, SQLite never initialized on Windows)
5. index.ts: deferred init (window first, backend second), file
logger before all imports, perf markers
6. auth.ts: reloadPairings() after DB init (deferred init regression)
7. installer.iss: remove [InstallDelete] (was nuking files on upgrade),
add SetupLogging=yes
Version bump: 1.2.1 -> 1.2.6
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single doc capturing all learnings from the release-cycle investigation: root cause, secondary issues, fixes applied, diagnostic patterns, and Windows build checklist. Replaces 13 investigation docs from release-cycle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…uild 1. index.ts: logger comment now states it only catches runtime errors, not import-time crashes. Build-time messages_pb.js check is the real guard. No false claims about pre-import diagnostics. 2. collect-externals.ts: removed auto-build fallback (device-protocol's build:postprocess uses BSD sed -i '' which fails on Windows). Now just fail-fast with clear instructions to build on macOS first. 3. build-windows-production.ps1: same — check-only, no auto-build. Clear error message pointing to macOS build step. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l sources 1. VAULT_INSTALL_STAMP now depends on PROTO_BUILD_STAMP (not just PROTO_INSTALL_STAMP), so `make vault` / `make install` cannot proceed without a built proto-tx-builder/dist/index.js. 2. PROTO_BUILD_INPUTS now uses find to track all .ts/.js files under modules/proto-tx-builder/src/, not just tsconfig.json and index.ts. This prevents stale stamps after branch switches or edits to proto/, amino/, or any other subdirectory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
perf: pre-bundle backend — first launch 56s to 2.1s
Zcash NU5+ requires ZIP-317 conventional fees: 5000 zats per logical action, minimum 2 actions (10,000 zats / 0.0001 ZEC). The UTXO builder used coinselect with sat/byte fee rates (1-5 sat/byte) which produced fees of ~400-1000 zats — 10-22x below the ZIP-317 floor. Nodes reject these with "tx unpaid action limit exceeded: 1 action(s) exceeds limit of 0". Added ZIP-317 fee enforcement after coin selection (same pattern as DOGE minimum fee). Absorbs the deficit from the change output, or reduces the spend output if no change exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: ZEC broadcast rejected — enforce ZIP-317 minimum fee
verifySeed() was setting setupInProgress=true, which caused the PIN_REQUEST handler to classify the unlock PIN as 'new-first' (Create a PIN) instead of 'current' (Enter PIN). Introduced a separate verifyInProgress flag so dry-run verification doesn't affect PIN type classification while still guarding sendCharacter* and sendPin from racing with the transport. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…release block start - Scan with both External and Internal (change) IVKs so spent-note change outputs are properly detected and reflected in balance - Support targeted rescan from a specific block height by resetting the scan cursor (preserves existing notes via INSERT OR IGNORE) - Start first scan from KeepKey release block (3282941) instead of Orchard activation (1687104) — skips ~1.6M irrelevant blocks - Return synced_to height in balance response for UI sync indicator - Clickable block heights in ZcashPrivacyTab to set as rescan target Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the DEX node rejects a swap because outbound fees exceed the output value, show a user-friendly message instead of the raw Go error stack. Covers "not enough asset to pay for fees" and "pool not available". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…release block constant - Define KEEPKEY_RELEASE_BLOCK constant in frontend (was referenced but missing) - Add needsScan state — detects unscanned wallets and shows initial scan prompt - Balance fetch now reads synced_to from sidecar to detect scan state - Sidecar startup ready signal includes synced_to + keepkey_release_block - "Scan from block 3,282,941" button guides first-time users
Review fixes for PR #56: 1. ZIP-317 fee (utxo.ts): The fallback path that reduces the recipient output to cover the fee deficit is now gated to isMax or memo-present (swap) flows only. Exact-amount user sends fail with a clear message instead of silently sending less than requested. 2. Sidecar scan state (zcash-sidecar.ts, index.ts, ZcashPrivacyTab.tsx): The Rust sidecar ready signal now includes synced_to — capture it in the TS sidecar module and expose via zcashShieldedStatus RPC. The frontend reads it on mount so needsScan is set immediately, not only after a separate balance RPC. Also updates cachedSyncedTo after scans. 3. i18n: Replace hardcoded English strings in needsScan UI block with t() calls (needsScanPrompt, scanFromBlock keys in asset namespace). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add synced_to and keepkey_release_block to zcashShieldedStatus response type. Add synced_to, notes_total, notes_unspent, keepkey_release_block to zcashShieldedBalance response type. Aligns shared types with the actual RPC handler responses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: Zcash broadcast fees, scanner improvements, swap UX
fix: verify seed shows Enter PIN instead of Create PIN
…ivacy tab - Add #3,282,941 reference height next to "Scan for Payments" label — click to populate the scan input with KeepKey release block - TX history heights: single-click copies to clipboard, double-click sets as rescan height Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The hardcoded 10,000 ZAT fee only covered ≤2 actions. Transactions spending >2 notes were rejected by consensus with "Unpaid actions is higher than the limit". Now computes fee = 5000 × max(2, actions) per ZIP-317, matching the existing shield and transparent paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: dynamic ZIP-317 fee for Orchard privacy sends
…uption syncState() had no guard for setupInProgress/verifyInProgress. A USB attach event during cipher recovery would call getFeatures() on the transport — which is locked by the recovery session. The device received GetFeatures instead of the expected CharacterAck, corrupting the decoded word buffer and causing firmware to reject valid words with "Word not found in BIP39 wordlist". Two guards added: - syncState(): return early if recovery/verify in progress - USB attach handler: ignore spurious re-attach during cipher session
fix: recovery cipher transport race — syncState guard
Wires `make audit` (dep manifest + SBOM) into build-signed between build-stable and prune-bundle. Also adds audit step to CI workflow so Linux builds include the SBOM artifact.
rm -rf target dir before cp -r to prevent nested lib/lib/ when target already exists as empty directory. Also fail hard if 0 JS files are copied instead of silently continuing.
build/_ext_modules is consumed by Electrobun packaging, so audit may not find it in CI. Real audit runs locally via make build-signed.
Audit needs build/_ext_modules which is consumed during Electrobun packaging. CI can't run it post-build. Signed release artifacts are built locally via make build-signed which runs audit correctly.
collect-externals outputs to _build/_ext_modules, not build/.
Stale .make/zcash-cli.stamp from unsigned builds skips Developer ID signing, causing Apple notarization to reject the binary. Now build-signed always clears the stamp first.
Electrobun creates tar.zst BEFORE patch-bundle.ts runs, so the archive always has unpatched bun/index.js. The prune step extracts from tar.zst, so it must apply the patch after extracting.
# Conflicts: # projects/keepkey-vault/electrobun.config.ts # projects/keepkey-vault/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
make build-signed-intelcross-compiles via Rosettamake build-signed, CI device-protocol fixBuild & Infra Changes
bundle-backend.ts— new script pre-bundles all JS deps into single file (externals: node-hid, usb, google-protobuf, proto-tx-builder)collect-externals.ts— slimmed to only 4 external packages (was 10+)audit-deps.ts— fixed path lookup (_build/notbuild/), added tomake build-signedpipelineMakefile— Intel Mac targets, proto-tx-builder build stamp, zcash-cli stamp force-clear inbuild-signedcp -rnesting fix, fail-hard on 0 JS files, version guardTest plan
Files changed
60 files, +1671/-303 lines