From bb5673abe32253cdf6a0d12fc2e38fc2220ef378 Mon Sep 17 00:00:00 2001 From: Raul Oliveira <38788084+raul-oliveira@users.noreply.github.com> Date: Tue, 12 May 2026 16:37:51 -0300 Subject: [PATCH 1/9] chore: bumps to 0.35.0-rc.1 (#867) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b068079..1cbfa8a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hathor-wallet", - "version": "0.34.0", + "version": "0.35.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hathor-wallet", - "version": "0.34.0", + "version": "0.35.0-rc.1", "hasInstallScript": true, "dependencies": { "@hathor/hathor-rpc-handler": "4.4.0", diff --git a/package.json b/package.json index a491dc67..b3d906fd 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "productName": "Hathor Wallet", "description": "Light wallet for Hathor Network", "author": "Hathor Labs (https://hathor.network/)", - "version": "0.34.0", + "version": "0.35.0-rc.1", "engines": { "node": ">=22.0.0", "npm": ">=10.0.0" From 169f71fc5fba7c18c0dc9fec5366672dfa9e45dc Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 21 May 2026 19:34:45 -0300 Subject: [PATCH 2/9] fix: upgrade wallet lib to v3.1.1 (#868) --- package-lock.json | 47 +++++++++++++++++++++++++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1cbfa8a9..e82c408e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@hathor/hathor-rpc-handler": "4.4.0", - "@hathor/wallet-lib": "3.0.1", + "@hathor/wallet-lib": "3.1.1", "@ledgerhq/hw-transport-node-hid": "6.28.1", "@reduxjs/toolkit": "2.2.3", "@reown/walletkit": "1.1.2", @@ -3248,7 +3248,7 @@ "node": ">=22" } }, - "node_modules/@hathor/wallet-lib": { + "node_modules/@hathor/hathor-rpc-handler/node_modules/@hathor/wallet-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-3.0.1.tgz", "integrity": "sha512-PpC6JO1L/7uuP3DZkZtojzTlwhUtEJYVANGqw0LeIkotBjNyiUPd8OxCB0NdBf0Ofa/YmYRdV6zxXdjENwhh1Q==", @@ -3270,6 +3270,49 @@ "npm": ">=10.0.0" } }, + "node_modules/@hathor/hathor-rpc-handler/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@hathor/wallet-lib": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-3.1.1.tgz", + "integrity": "sha512-D1ZC7IxVd9DtycgqSnVNByGnQiTJLNoUlgk8sBlCKmVMO3AehNo3t6PVkyXEUA2DOjW/rACqPVbYBPiprbPR0g==", + "license": "MIT", + "dependencies": { + "axios": "1.7.7", + "bitcore-lib": "8.25.10", + "bitcore-mnemonic": "8.25.10", + "buffer": "6.0.3", + "crypto-js": "4.2.0", + "isomorphic-ws": "5.0.0", + "lodash": "4.17.21", + "queue-microtask": "1.2.3", + "ws": "8.17.1", + "zod": "3.23.8" + }, + "engines": { + "node": ">=22.0.0", + "npm": ">=10.0.0" + } + }, "node_modules/@hathor/wallet-lib/node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", diff --git a/package.json b/package.json index b3d906fd..fa22ddbf 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ }, "dependencies": { "@hathor/hathor-rpc-handler": "4.4.0", - "@hathor/wallet-lib": "3.0.1", + "@hathor/wallet-lib": "3.1.1", "@ledgerhq/hw-transport-node-hid": "6.28.1", "@reduxjs/toolkit": "2.2.3", "@reown/walletkit": "1.1.2", From 02f857bcfd02b3af9c0ffb68e7cff88c65d888f4 Mon Sep 17 00:00:00 2001 From: Raul Oliveira <38788084+raul-oliveira@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:42:53 -0300 Subject: [PATCH 3/9] fix: prevent Send screen crash on LavaMoat build via lodash setTimeout grant (#876) --- lavamoat/webpack/policy-override.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lavamoat/webpack/policy-override.json b/lavamoat/webpack/policy-override.json index 333a7689..9be9409e 100644 --- a/lavamoat/webpack/policy-override.json +++ b/lavamoat/webpack/policy-override.json @@ -1,5 +1,11 @@ { "resources": { + "cypress>lodash": { + "globals": { + "setTimeout": true, + "clearTimeout": true + } + }, "bootstrap": { "globals": { "document": true, From 5382cc6d2b74a3aa613fc6321672509e62c6cc23 Mon Sep 17 00:00:00 2001 From: Raul Oliveira <38788084+raul-oliveira@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:57:31 -0300 Subject: [PATCH 4/9] chore: bump to 0.35.0-rc.2 (#877) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e82c408e..e6f64bb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hathor-wallet", - "version": "0.35.0-rc.1", + "version": "0.35.0-rc.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hathor-wallet", - "version": "0.35.0-rc.1", + "version": "0.35.0-rc.2", "hasInstallScript": true, "dependencies": { "@hathor/hathor-rpc-handler": "4.4.0", diff --git a/package.json b/package.json index fa22ddbf..80be5e32 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "productName": "Hathor Wallet", "description": "Light wallet for Hathor Network", "author": "Hathor Labs (https://hathor.network/)", - "version": "0.35.0-rc.1", + "version": "0.35.0-rc.2", "engines": { "node": ">=22.0.0", "npm": ">=10.0.0" From a37ea64850b463f575788febea1b93a365dc16af Mon Sep 17 00:00:00 2001 From: Raul Oliveira <38788084+raul-oliveira@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:09:14 -0300 Subject: [PATCH 5/9] fix: restore lost UX in Send Tokens flow (PIN focus and Close returns Home) (#878) The fee-token send refactor replaced the ModalPin/ModalSendTx flow with ModalTransactionOverview and ModalSendSuccess, dropping two behaviors: - The PIN field was no longer auto-focused. The overview modal relied on the input's `autoFocus`, which fires while the modal is still display:none, so the browser ignores it. Focus the field on Bootstrap's `shown.bs.modal` event instead (the old ModalPin approach), which also restores focus across every flow that uses the shared overview modal. - The success modal's "Close" (and top-right "x") only reset the form and stayed on the Send screen. They now navigate to the wallet home (/wallet/), restoring the pre-refactor behavior; the now-dead resetForm helper is removed. --- src/components/ModalTransactionOverview.js | 16 +++++++++++++++- src/screens/SendTokens.js | 16 +--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/ModalTransactionOverview.js b/src/components/ModalTransactionOverview.js index 847734f2..4202b977 100644 --- a/src/components/ModalTransactionOverview.js +++ b/src/components/ModalTransactionOverview.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { t } from 'ttag'; import $ from 'jquery'; import PropTypes from 'prop-types'; @@ -40,12 +40,25 @@ function ModalTransactionOverview({ const [phase, setPhase] = useState('review'); const [errorMessage, setErrorMessage] = useState(''); const [preparedTx, setPreparedTx] = useState(null); + const pinInputRef = useRef(null); const tokenMetadata = useSelector((state) => state.tokenMetadata); useEffect(() => { manageDomLifecycle(`#${MODAL_ID}`); }, [manageDomLifecycle]); + // Focus the PIN field after the modal finishes fading in. `autoFocus` alone + // fails on first open: the input mounts while the modal is still `display:none`, + // so the browser drops the focus. `autoFocus` is still kept — it covers the + // error -> "Try again" -> review remount, where the modal is already visible. + useEffect(() => { + const onShown = () => pinInputRef.current?.focus(); + $(`#${MODAL_ID}`).on('shown.bs.modal.focuspin', onShown); + return () => { + $(`#${MODAL_ID}`).off('shown.bs.modal.focuspin', onShown); + }; + }, []); + const fee = typeof totalFee === 'bigint' ? totalFee : BigInt(totalFee || 0); const hasAnyFee = fee > 0n; @@ -291,6 +304,7 @@ function ModalTransactionOverview({
{ globalModalContext.hideModal(); - resetForm(); + navigate('/wallet/'); }, onViewDetails: () => { console.log('View tx details:', tx.hash, tx); @@ -596,20 +596,6 @@ function SendTokens() { }); }; - /** - * Reset form to initial state after successful send - */ - const resetForm = () => { - setTxTokens([...getSelectedToken()]); - setDataOutputs([]); - setTokenFees({}); - setTokenChangeOutputs({}); - setFeeError(''); - setErrorMessage(''); - references.current = [React.createRef()]; - dataOutputRefs.current = {}; - }; - /** * Group outputs by token for display in the transaction overview modal. * Returns an array of { token, outputs, total } where total only includes From 608c0a50cab50a22c0393f3d1632d82c46420501 Mon Sep 17 00:00:00 2001 From: Raul Oliveira <38788084+raul-oliveira@users.noreply.github.com> Date: Mon, 6 Jul 2026 16:32:53 -0300 Subject: [PATCH 6/9] chore: bump to 0.35.0-rc.3 (#880) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6f64bb5..e5875641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hathor-wallet", - "version": "0.35.0-rc.2", + "version": "0.35.0-rc.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hathor-wallet", - "version": "0.35.0-rc.2", + "version": "0.35.0-rc.3", "hasInstallScript": true, "dependencies": { "@hathor/hathor-rpc-handler": "4.4.0", diff --git a/package.json b/package.json index 80be5e32..b987e4a9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "productName": "Hathor Wallet", "description": "Light wallet for Hathor Network", "author": "Hathor Labs (https://hathor.network/)", - "version": "0.35.0-rc.2", + "version": "0.35.0-rc.3", "engines": { "node": ">=22.0.0", "npm": ">=10.0.0" From ecfddc8d3879e74bbdf0371b5f0997cca44928a9 Mon Sep 17 00:00:00 2001 From: Raul Oliveira <38788084+raul-oliveira@users.noreply.github.com> Date: Thu, 9 Jul 2026 12:06:44 -0300 Subject: [PATCH 7/9] fix: RC regressions (address mode, import banner, explorer link, send PIN) (#881) Fixes a batch of UX regressions introduced in the 0.35.0 release candidates: - Address mode kept across passphrase changes: adding/removing a passphrase no longer forces the wallet into multi-address mode. startWallet re-syncs the feature toggles from the live Unleash singleton before reading the single-address flag, so the clean_data reset in the passphrase flow no longer makes the flag read a stale default. - Import Tokens banner no longer advertises phantom tokens: unregistering a token with no history and zero balance now clears it from allTokens and tokensBalance, so fetchUnknownTokens stops reporting a token the import modal cannot resolve. - Easy Import Tokens "Open in Explorer" opens the system default browser via helpers.openExternalURL instead of a new Electron window. - Send Tokens confirmation validates the PIN with wallet.checkPin before sending, showing "Invalid PIN" on the review screen instead of a generic "Transaction failed" after a failed send attempt. --- src/components/ModalTokenImport.js | 6 +++++- src/components/ModalTransactionOverview.js | 23 ++++++++++++++++++++++ src/reducers/index.js | 17 ++++++++++------ src/sagas/featureToggle.js | 17 ++++++++++++---- src/sagas/wallet.js | 7 ++++++- 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/components/ModalTokenImport.js b/src/components/ModalTokenImport.js index eba489ff..9f52d828 100644 --- a/src/components/ModalTokenImport.js +++ b/src/components/ModalTokenImport.js @@ -14,6 +14,7 @@ import { GlobalModalContext, MODAL_TYPES } from './GlobalModal'; import { tokenRegisterRequested } from '../actions/index'; import { getGlobalWallet } from '../modules/wallet'; import walletUtils from '../utils/wallet'; +import helpers from '../utils/helpers'; import { colors } from '../constants'; /** @@ -337,7 +338,10 @@ export default function ModalTokenImport({ onClose, manageDomLifecycle }) { { + e.preventDefault(); + helpers.openExternalURL(explorerLink); + }} rel="noopener noreferrer" title={uid} > diff --git a/src/components/ModalTransactionOverview.js b/src/components/ModalTransactionOverview.js index 4202b977..ecce218a 100644 --- a/src/components/ModalTransactionOverview.js +++ b/src/components/ModalTransactionOverview.js @@ -15,6 +15,7 @@ import { useSelector } from 'react-redux'; import helpers from '../utils/helpers'; import { TOKEN_FEE_RFC_URL, colors } from '../constants'; import SendTxHandler from './SendTxHandler'; +import { getGlobalWallet } from '../modules/wallet'; const MODAL_ID = 'transactionOverviewModal'; @@ -39,6 +40,7 @@ function ModalTransactionOverview({ const [pin, setPin] = useState(''); const [phase, setPhase] = useState('review'); const [errorMessage, setErrorMessage] = useState(''); + const [pinError, setPinError] = useState(''); const [preparedTx, setPreparedTx] = useState(null); const pinInputRef = useRef(null); const tokenMetadata = useSelector((state) => state.tokenMetadata); @@ -108,10 +110,28 @@ function ModalTransactionOverview({ const handleCancel = () => { $(`#${MODAL_ID}`).modal('hide'); setPin(''); + setPinError(''); onCancel(); }; const handleConfirm = async () => { + // Validate the PIN before attempting the send so a wrong PIN surfaces as + // "Invalid PIN" on the review phase instead of a generic send failure. A + // missing wallet or a checkPin failure is a real error, not a wrong PIN, so + // route it to the error phase instead of leaving the rejection unhandled. + try { + const wallet = getGlobalWallet(); + if (!await wallet.checkPin(pin)) { + setPinError(t`Invalid PIN`); + return; + } + } catch (e) { + setErrorMessage(e.message || t`Error validating PIN.`); + setPhase('error'); + return; + } + + setPinError(''); setPhase('sending'); setErrorMessage(''); @@ -142,6 +162,7 @@ function ModalTransactionOverview({ const handleRetry = () => { setPreparedTx(null); setErrorMessage(''); + setPinError(''); setPin(''); setPhase('review'); }; @@ -149,6 +170,7 @@ function ModalTransactionOverview({ const handlePinChange = (e) => { const value = e.target.value.replace(/\D/g, '').slice(0, 6); setPin(value); + setPinError(''); }; // --- Render helpers for the review phase --- @@ -314,6 +336,7 @@ function ModalTransactionOverview({ autoFocus autoComplete="off" /> + {pinError &&

{pinError}

}
); diff --git a/src/reducers/index.js b/src/reducers/index.js index ac3d5de7..11055065 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -670,18 +670,23 @@ const removeTokenMetadata = (state, action) => { delete newMeta[uid]; } - // If the token has zero balance we should remove the balance data + // Drop a zero-balance unregistered token from allTokens and tokensBalance + // together (fetchUnknownTokens reads tokensBalance[uid].data for every allTokens + // entry): a leftover keeps the "Import Tokens" banner advertising a token the + // modal can't resolve. Tokens with a balance stay, still re-importable. const newBalance = Object.assign({}, state.tokensBalance); - if (uid in newBalance && (!!newBalance[uid].data)) { - const balance = newBalance[uid].data; - if ((balance.available + balance.locked) === 0n) { - delete newBalance[uid]; - } + const newAllTokens = Object.assign({}, state.allTokens); + const balance = newBalance[uid]?.data; + if (balance && (balance.available + balance.locked) === 0n) { + delete newBalance[uid]; + delete newAllTokens[uid]; } return { ...state, tokenMetadata: newMeta, + tokensBalance: newBalance, + allTokens: newAllTokens, }; }; diff --git a/src/sagas/featureToggle.js b/src/sagas/featureToggle.js index ed11c8c7..5c4ce27f 100644 --- a/src/sagas/featureToggle.js +++ b/src/sagas/featureToggle.js @@ -250,7 +250,14 @@ export function mapFeatureToggles(toggles) { }, {}); } -export function* handleToggleUpdate() { +/** + * Re-hydrate state.featureToggles from the Unleash singleton after clean_data + * wipes it, so a post-restart checkForFeatureFlag read isn't stale. + * + * Doesn't dispatch FEATURE_TOGGLE_UPDATED — it can reload the wallet, unsafe + * from inside startWallet. + */ +export function* syncFeatureTogglesFromClient() { const unleashClient = getUnleashClient(); const featureTogglesInitialized = yield select((state) => state.featureTogglesInitialized); @@ -258,10 +265,12 @@ export function* handleToggleUpdate() { return; } - const { toggles } = unleashClient; - const featureToggles = mapFeatureToggles(toggles); - + const featureToggles = mapFeatureToggles(unleashClient.toggles); yield put(setFeatureToggles(featureToggles)); +} + +export function* handleToggleUpdate() { + yield call(syncFeatureTogglesFromClient); yield put({ type: 'FEATURE_TOGGLE_UPDATED' }); } diff --git a/src/sagas/wallet.js b/src/sagas/wallet.js index 54c2f1e6..2ce668e3 100644 --- a/src/sagas/wallet.js +++ b/src/sagas/wallet.js @@ -75,7 +75,7 @@ import { dispatchLedgerTokenSignatureVerification, } from './helpers'; import { fetchTokenData, restoreTokensForNetwork } from './tokens'; -import { updateUnleashClientContext } from './featureToggle'; +import { updateUnleashClientContext, syncFeatureTogglesFromClient } from './featureToggle'; import walletUtils from '../utils/wallet'; import tokensUtils from '../utils/tokens'; import nanoUtils from '../utils/nanoContracts'; @@ -159,6 +159,11 @@ export function* startWallet(action) { yield put(loadingAddresses(true)); + // Refresh the mirror before the checkForFeatureFlag reads below: the passphrase + // flow reaches startWallet right after clean_data wiped it, which would make the + // single-address read fall back to multi. + yield call(syncFeatureTogglesFromClient); + if (hardware) { // We need to ensure that the hardware wallet storage is always generated here since we may be // starting the wallet with a second device and so we cannot trust the xpub saved on storage. From a5340d78ccd999ce60c886d0cdf490844298c41a Mon Sep 17 00:00:00 2001 From: Raul Oliveira <38788084+raul-oliveira@users.noreply.github.com> Date: Thu, 9 Jul 2026 12:26:03 -0300 Subject: [PATCH 8/9] chore: bump to 0.35.0-rc.4 (#882) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e5875641..ad302828 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hathor-wallet", - "version": "0.35.0-rc.3", + "version": "0.35.0-rc.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hathor-wallet", - "version": "0.35.0-rc.3", + "version": "0.35.0-rc.4", "hasInstallScript": true, "dependencies": { "@hathor/hathor-rpc-handler": "4.4.0", diff --git a/package.json b/package.json index b987e4a9..0b30eb42 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "productName": "Hathor Wallet", "description": "Light wallet for Hathor Network", "author": "Hathor Labs (https://hathor.network/)", - "version": "0.35.0-rc.3", + "version": "0.35.0-rc.4", "engines": { "node": ">=22.0.0", "npm": ">=10.0.0" From fbc08838fb1f6720d61be1c2d90527d04ffc9dee Mon Sep 17 00:00:00 2001 From: Raul Oliveira <38788084+raul-oliveira@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:29:53 -0300 Subject: [PATCH 9/9] chore: bump to 0.35.0 (#889) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ad302828..b002b8d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hathor-wallet", - "version": "0.35.0-rc.4", + "version": "0.35.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hathor-wallet", - "version": "0.35.0-rc.4", + "version": "0.35.0", "hasInstallScript": true, "dependencies": { "@hathor/hathor-rpc-handler": "4.4.0", diff --git a/package.json b/package.json index 0b30eb42..50b15754 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "productName": "Hathor Wallet", "description": "Light wallet for Hathor Network", "author": "Hathor Labs (https://hathor.network/)", - "version": "0.35.0-rc.4", + "version": "0.35.0", "engines": { "node": ">=22.0.0", "npm": ">=10.0.0"