From ccd5883ca55782e7ca031bf774b5f57cda48aa4a Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 15:26:44 -0700 Subject: [PATCH 01/17] Add Maestro build tag to Help scene version number Appends a -m suffix to the version string shown in the Help modal when the app is a Maestro test build, so QA/devs can tell at a glance that a Maestro build is installed. --- CHANGELOG.md | 1 + src/components/modals/HelpModal.tsx | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 685ea4ac0e3..6e1058282ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - added: Verbose logging for exchange rate queries: the request body, resolved/rate-less counts, and errors are captured when the Verbose Logging setting is enabled. - added: Exchange-rate cache snapshot in the support log output, plus a `rates-cache-replay` script that re-runs those queries against the rates server and reports the result for each pair. - changed: Sign MoonPay buy/sell widget URLs and bind them to the customer's IP via the info server, for MoonPay's on-ramp IP-matching security upgrade. +- added: "-m" tag on the version number in the Help scene for Maestro test builds - fixed: NYM max swaps from EVM wallets now report the correct limit error instead of an unsupported-route error (edge-exchange-plugins 2.52.1). - fixed: Exchange rate queries no longer request each chain's own asset twice, which had been inflating every rate query with duplicate pairs. diff --git a/src/components/modals/HelpModal.tsx b/src/components/modals/HelpModal.tsx index c46b405b897..5a2719c2936 100644 --- a/src/components/modals/HelpModal.tsx +++ b/src/components/modals/HelpModal.tsx @@ -11,6 +11,7 @@ import { lstrings } from '../../locales/strings' import { config } from '../../theme/appConfig' import { useSelector } from '../../types/reactRedux' import type { NavigationBase } from '../../types/routerTypes' +import { isMaestro } from '../../util/maestro' import { openBrowserUri } from '../../util/WebUtils' import { ChatBubblesIcon } from '../icons/ThemedIcons' import { Airship } from '../services/AirshipInstance' @@ -60,7 +61,9 @@ export const HelpModal: React.FC = (props: Props) => { }, []) const styles = getStyles(theme) - const versionText = `${lstrings.help_version} ${versionNumber}` + const versionText = `${lstrings.help_version} ${versionNumber}${ + isMaestro() ? '-m' : '' + }` const buildText = `${lstrings.help_build} ${buildNumber}` const helpModalTitle = sprintf( lstrings.help_modal_title_thanks, From f199c4f95c90791893c4f72f3c5331fa7cffff1c Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 15:28:25 -0700 Subject: [PATCH 02/17] Clarify XRP minimum balance warning copy Replace the XRP reserve warning body text so it no longer implies a user must deposit 1 XRP in addition to their balance. The reserve is met once the address balance itself reaches 1 XRP. --- CHANGELOG.md | 1 + src/locales/en_US.ts | 2 +- src/locales/strings/enUS.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e1058282ca..5c054de58a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - added: "-m" tag on the version number in the Help scene for Maestro test builds - fixed: NYM max swaps from EVM wallets now report the correct limit error instead of an unsupported-route error (edge-exchange-plugins 2.52.1). - fixed: Exchange rate queries no longer request each chain's own asset twice, which had been inflating every rate query with duplicate pairs. +- fixed: XRP minimum balance warning copy to clarify the reserve is met once the address balance reaches 1 XRP, not on top of it. ## 4.50.0 (2026-07-21) diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index a6006046045..343fb2e5990 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -229,7 +229,7 @@ const strings = { fragment_error_report_id_copied: 'Error report ID copied', request_minimum_notification_title: 'Minimum Balance Required', request_xrp_minimum_notification_body_1xrp: - 'Ripple (XRP) wallets require a 1 XRP minimum balance. You must deposit at least 1 XRP to this address before this wallet will show a balance or transactions. 1 XRP will be unspendable for the lifetime of this wallet address.', + 'A minimum balance of 1 XRP is required for an address on the XRP Ledger to be active. The 1 XRP is an unspendable reserve on the network. The reserve is met as soon as the address balance reaches 1 XRP or more. Any amount above this reserve is available for transactions.', request_xrp_minimum_notification_alert_body_1xrp: 'This wallet will always require a 1 XRP minimum', request_xlm_minimum_notification_body: diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index 39c1368be78..3bdc227deef 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -141,7 +141,7 @@ "fragment_event_id": "Event ID", "fragment_error_report_id_copied": "Error report ID copied", "request_minimum_notification_title": "Minimum Balance Required", - "request_xrp_minimum_notification_body_1xrp": "Ripple (XRP) wallets require a 1 XRP minimum balance. You must deposit at least 1 XRP to this address before this wallet will show a balance or transactions. 1 XRP will be unspendable for the lifetime of this wallet address.", + "request_xrp_minimum_notification_body_1xrp": "A minimum balance of 1 XRP is required for an address on the XRP Ledger to be active. The 1 XRP is an unspendable reserve on the network. The reserve is met as soon as the address balance reaches 1 XRP or more. Any amount above this reserve is available for transactions.", "request_xrp_minimum_notification_alert_body_1xrp": "This wallet will always require a 1 XRP minimum", "request_xlm_minimum_notification_body": "Stellar (XLM) wallets require a 1 XLM minimum balance. You must deposit at least 1 XLM to this address before this wallet will show a balance or transactions. 1 XLM will be unspendable for the lifetime of this wallet address.", "request_xlm_minimum_notification_alert_body": "This wallet will always require a 1 XLM minimum", From 516cb5f11a65e567fee9feb11f30dcd09f8d5a31 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 15:39:59 -0700 Subject: [PATCH 03/17] Round fiat balances to cents in Wallets list The Wallets list row balance used the auto-expanding fiat precision meant for unit prices, showing up to 6 decimals for sub-$1 balances. Cap it at 2 decimals to match the wallet detail scene's rounding. --- CHANGELOG.md | 1 + src/components/themed/WalletListCurrencyRow.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c054de58a3..a9fb09299cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - fixed: NYM max swaps from EVM wallets now report the correct limit error instead of an unsupported-route error (edge-exchange-plugins 2.52.1). - fixed: Exchange rate queries no longer request each chain's own asset twice, which had been inflating every rate query with duplicate pairs. - fixed: XRP minimum balance warning copy to clarify the reserve is met once the address balance reaches 1 XRP, not on top of it. +- fixed: Round fiat balances to cents in the Wallets list, matching the wallet detail scene ## 4.50.0 (2026-07-21) diff --git a/src/components/themed/WalletListCurrencyRow.tsx b/src/components/themed/WalletListCurrencyRow.tsx index e7f1c9abeb3..ca4ec42d57a 100644 --- a/src/components/themed/WalletListCurrencyRow.tsx +++ b/src/components/themed/WalletListCurrencyRow.tsx @@ -211,6 +211,7 @@ const WalletListCurrencyRowComponent = ( tokenId={tokenId} currencyConfig={wallet.currencyConfig} hideBalance={hideBalance} + autoPrecision={false} style={styles.secondaryText} /> From 02150841c12a9278ff3cec3d053c89900f44fe6c Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 15:38:14 -0700 Subject: [PATCH 04/17] Fix lint warnings in WalletListCurrencyRow --- src/components/themed/WalletListCurrencyRow.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/themed/WalletListCurrencyRow.tsx b/src/components/themed/WalletListCurrencyRow.tsx index ca4ec42d57a..1c365e221ff 100644 --- a/src/components/themed/WalletListCurrencyRow.tsx +++ b/src/components/themed/WalletListCurrencyRow.tsx @@ -38,9 +38,7 @@ interface Props { ) => Promise | void } -const WalletListCurrencyRowComponent = ( - props: Props -): React.ReactElement | null => { +const WalletListCurrencyRowComponent: React.FC = props => { const { customAsset, token, From c6bab8b433d6c88b5ab26e05043a6fb9db317ba1 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Fri, 24 Jul 2026 12:12:28 -0700 Subject: [PATCH 05/17] Stop notification cards from shrinking their text Add explicit React.FC return types and use the shared themed CloseIcon instead of importing react-native-vector-icons directly. --- CHANGELOG.md | 1 + eslint.config.mjs | 2 +- .../notification/NotificationCenterCard.tsx | 27 +++++++++++-------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9fb09299cf..9af566b6913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - fixed: Exchange rate queries no longer request each chain's own asset twice, which had been inflating every rate query with duplicate pairs. - fixed: XRP minimum balance warning copy to clarify the reserve is met once the address balance reaches 1 XRP, not on top of it. - fixed: Round fiat balances to cents in the Wallets list, matching the wallet detail scene +- fixed: Notification center cards no longer shrink their text to fit. Long titles and messages now truncate with an ellipsis so every card renders at the same size. ## 4.50.0 (2026-07-21) diff --git a/eslint.config.mjs b/eslint.config.mjs index 988cc271d09..5dde2d78010 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -245,7 +245,7 @@ export default [ 'src/components/navigation/ParamHeaderTitle.tsx', 'src/components/navigation/SideMenuButton.tsx', 'src/components/navigation/TransactionDetailsTitle.tsx', - 'src/components/notification/NotificationCenterCard.tsx', + 'src/components/progress-indicators/AccountSyncBar.tsx', 'src/components/progress-indicators/FullScreenLoader.tsx', diff --git a/src/components/notification/NotificationCenterCard.tsx b/src/components/notification/NotificationCenterCard.tsx index 84dabb7861e..ca1018f34c6 100644 --- a/src/components/notification/NotificationCenterCard.tsx +++ b/src/components/notification/NotificationCenterCard.tsx @@ -2,12 +2,12 @@ import * as React from 'react' import { View } from 'react-native' import FastImage from 'react-native-fast-image' import { cacheStyles } from 'react-native-patina' -import AntDesignIcon from 'react-native-vector-icons/AntDesign' import { useHandler } from '../../hooks/useHandler' import { toLocaleDate, toLocaleTime } from '../../locales/intl' import { getThemedIconUri } from '../../util/CdnUris' import { EdgeTouchableOpacity } from '../common/EdgeTouchableOpacity' +import { CloseIcon } from '../icons/ThemedIcons' import { type Theme, useTheme } from '../services/ThemeContext' import { EdgeText } from '../themed/EdgeText' @@ -23,7 +23,7 @@ interface Props { onClose?: () => void | Promise } -export const NotificationCenterRow = (props: Props) => { +export const NotificationCenterRow: React.FC = props => { const theme = useTheme() const styles = getStyles(theme) @@ -71,7 +71,7 @@ interface NotificationCenterCardProps { onClose?: () => void | Promise } -const NotificationCenterCard = (props: NotificationCenterCardProps) => { +const NotificationCenterCard: React.FC = props => { const theme = useTheme() const styles = getStyles(theme) @@ -98,15 +98,24 @@ const NotificationCenterCard = (props: NotificationCenterCardProps) => { - + {/* Font scaling is disabled so every card renders at the same text + size. Long titles and messages wrap up to their line limit and + then truncate with an ellipsis instead of shrinking. */} + {title} - {toLocaleTime(date)} + + {toLocaleTime(date)} + {message} @@ -114,11 +123,7 @@ const NotificationCenterCard = (props: NotificationCenterCardProps) => { {onClose == null ? null : ( - + )} From 09b8ddd874077832b23d1455004f9795e6fe2376 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 16:26:18 -0700 Subject: [PATCH 06/17] test: add missing testIDs for maestro selectors Give the alert drop-down's close button a testID so UI tests can dismiss it instead of tapping coordinates. Committing the file graduates it off the lint suppression list, so AlertDropdown also picks up its explicit React.FC type. --- eslint.config.mjs | 1 - src/components/navigation/AlertDropdown.tsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 5dde2d78010..e9c9aa43d0c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -231,7 +231,6 @@ export default [ 'src/components/modals/WalletListSortModal.tsx', 'src/components/modals/WcSmartContractModal.tsx', - 'src/components/navigation/AlertDropdown.tsx', 'src/components/navigation/BackButton.tsx', 'src/components/navigation/CurrencySettingsTitle.tsx', 'src/components/navigation/EdgeHeader.tsx', diff --git a/src/components/navigation/AlertDropdown.tsx b/src/components/navigation/AlertDropdown.tsx index b6418f54227..1cf0078b9fd 100644 --- a/src/components/navigation/AlertDropdown.tsx +++ b/src/components/navigation/AlertDropdown.tsx @@ -36,7 +36,7 @@ interface Props { onPress?: () => void | Promise } -export function AlertDropdown(props: Props) { +export const AlertDropdown: React.FC = props => { const { bridge, error, @@ -95,7 +95,7 @@ export function AlertDropdown(props: Props) { {message} - + Date: Thu, 23 Jul 2026 16:49:42 -0700 Subject: [PATCH 07/17] Style entire signin line as tertiary link on getting-started scene Previously only "Sign in" was colored as a tappable link; "Already have an account?" was dimmed even though the whole line is already one tap target. --- CHANGELOG.md | 1 + src/components/scenes/GettingStartedScene.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af566b6913..e2984d4034c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - fixed: XRP minimum balance warning copy to clarify the reserve is met once the address balance reaches 1 XRP, not on top of it. - fixed: Round fiat balances to cents in the Wallets list, matching the wallet detail scene - fixed: Notification center cards no longer shrink their text to fit. Long titles and messages now truncate with an ellipsis so every card renders at the same size. +- changed: Style the entire "Already have an account? Sign in" line in the getting-started USP carousel with the tertiary link color, not just "Sign in". ## 4.50.0 (2026-07-21) diff --git a/src/components/scenes/GettingStartedScene.tsx b/src/components/scenes/GettingStartedScene.tsx index 3083e8f27f3..642fda3df1b 100644 --- a/src/components/scenes/GettingStartedScene.tsx +++ b/src/components/scenes/GettingStartedScene.tsx @@ -533,7 +533,7 @@ const getStyles = cacheStyles((theme: Theme) => ({ alignItems: 'center' }, tertiaryText: { - color: theme.textInputTextColorDisabled + color: theme.iconTappable }, tappableText: { color: theme.iconTappable From cff0fd84637467c177433615bb6c95fcc71590c2 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 16:59:11 -0700 Subject: [PATCH 08/17] Sort Privacy Settings asset list alphabetically The Nym Mix Net Assets list order followed plugin registration order, which differs between iOS and Android, producing inconsistent and non-alphabetical ordering on both platforms. --- CHANGELOG.md | 1 + src/components/scenes/PrivacySettingsScene.tsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2984d4034c..773d1ad3160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - fixed: Round fiat balances to cents in the Wallets list, matching the wallet detail scene - fixed: Notification center cards no longer shrink their text to fit. Long titles and messages now truncate with an ellipsis so every card renders at the same size. - changed: Style the entire "Already have an account? Sign in" line in the getting-started USP carousel with the tertiary link color, not just "Sign in". +- fixed: Sort the Privacy Settings Nym Mix Net asset list alphabetically by display name ## 4.50.0 (2026-07-21) diff --git a/src/components/scenes/PrivacySettingsScene.tsx b/src/components/scenes/PrivacySettingsScene.tsx index 49576dcf008..b435bba57bf 100644 --- a/src/components/scenes/PrivacySettingsScene.tsx +++ b/src/components/scenes/PrivacySettingsScene.tsx @@ -34,15 +34,21 @@ export const PrivacySettingsScene: React.FC = props => { const account = useSelector(state => state.core.account) const { currencyConfig } = account - // Get list of pluginIds that support network privacy + // Get list of pluginIds that support network privacy, sorted + // alphabetically by display name const supportedPluginIds = React.useMemo(() => { - return Object.keys(currencyConfig).filter(pluginId => { + const pluginIds = Object.keys(currencyConfig).filter(pluginId => { const config = currencyConfig[pluginId] const defaultSetting = asMaybePrivateNetworkingSetting( config.currencyInfo.defaultSettings ) return defaultSetting != null }) + return pluginIds.sort((a, b) => + currencyConfig[a].currencyInfo.displayName.localeCompare( + currencyConfig[b].currencyInfo.displayName + ) + ) }, [currencyConfig]) return ( From 99d595dafc67d8fa6333fc148ced1433b3bde1d0 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 17:02:29 -0700 Subject: [PATCH 09/17] Fix Next button overlapping wallet list in Choose Wallets to Add The Next button on CreateWalletSelectCryptoScene (shared by both the onboarding and logged-in "+" add-wallet flows) was double-absolutely positioned: SceneWrapper already renders dockProps.children inside its own absolutely-positioned, keyboard-aware dock, but the button itself was also given position:absolute via SceneButtons' absolute prop. That took the button out of the dock's layout flow, so the dock's measured height collapsed and the scene under-reserved scroll clearance for it, letting the button cover the last row(s) when scrolled to the end. The absolute prop was only forced on outside of Maestro, so automated UI tests exercised a different, correctly-docked layout and never caught the regression. Switch to KavButtons, the same primitive every other scene uses inside dockProps.children, and drop the now-unneeded manual paddingBottom compensation since the dock's real height is now measured correctly. --- CHANGELOG.md | 1 + ...reateWalletSelectCryptoScene.test.tsx.snap | 2 +- .../scenes/CreateWalletSelectCryptoScene.tsx | 20 ++++++++++++++----- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 773d1ad3160..de7115b5dc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - fixed: Notification center cards no longer shrink their text to fit. Long titles and messages now truncate with an ellipsis so every card renders at the same size. - changed: Style the entire "Already have an account? Sign in" line in the getting-started USP carousel with the tertiary link color, not just "Sign in". - fixed: Sort the Privacy Settings Nym Mix Net asset list alphabetically by display name +- fixed: Next button overlapping the wallet list on the Choose Wallets to Add scene ## 4.50.0 (2026-07-21) diff --git a/src/__tests__/scenes/__snapshots__/CreateWalletSelectCryptoScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/CreateWalletSelectCryptoScene.test.tsx.snap index 1e33a1d08e0..c1c21141995 100644 --- a/src/__tests__/scenes/__snapshots__/CreateWalletSelectCryptoScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/CreateWalletSelectCryptoScene.test.tsx.snap @@ -816,7 +816,7 @@ exports[`CreateWalletSelectCrypto should render with loading props 1`] = ` contentContainerStyle={ { "marginHorizontal": 11, - "paddingBottom": 112, + "paddingBottom": 0, "paddingLeft": 0, "paddingRight": 0, "paddingTop": 0, diff --git a/src/components/scenes/CreateWalletSelectCryptoScene.tsx b/src/components/scenes/CreateWalletSelectCryptoScene.tsx index 1f9b1918acb..81a09e127cf 100644 --- a/src/components/scenes/CreateWalletSelectCryptoScene.tsx +++ b/src/components/scenes/CreateWalletSelectCryptoScene.tsx @@ -22,10 +22,9 @@ import { import { useDispatch, useSelector } from '../../types/reactRedux' import type { EdgeAppSceneProps, NavigationBase } from '../../types/routerTypes' import type { EdgeAsset } from '../../types/types' -import { isMaestro } from '../../util/maestro' import { logEvent } from '../../util/tracking' import { EdgeButton } from '../buttons/EdgeButton' -import { SceneButtons } from '../buttons/SceneButtons' +import { KavButtons } from '../buttons/KavButtons' import { EdgeAnim } from '../common/EdgeAnim' import { SceneWrapper } from '../common/SceneWrapper' import { SearchIconAnimated } from '../icons/ThemedIcons' @@ -137,6 +136,7 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => { } return out }) + const [isNextPending, setIsNextPending] = React.useState(false) const findMainnetItem = (pluginId: string): MainWalletCreateItem => { const newItem = createWalletList.find(item => item.pluginId === pluginId) @@ -166,7 +166,17 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => { showError(lstrings.create_wallet_no_assets_selected) return } + setIsNextPending(true) + try { + await handleNextPressBody() + } catch (error: unknown) { + showError(error) + } finally { + setIsNextPending(false) + } + }) + const handleNextPressBody = useHandler(async () => { if (newAccountFlow != null) dispatch( logEvent('Signup_Wallets_Selected_Next', { @@ -423,13 +433,14 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => { exit={{ type: 'fadeOut', duration: 300 }} accessible={false} > - ) @@ -465,7 +476,6 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => { contentContainerStyle={{ ...insetStyle, paddingTop: 0, - paddingBottom: insetStyle.paddingBottom + theme.rem(5), marginHorizontal: theme.rem(0.5) }} data={filteredCreateWalletList} From 97e09caa21fa63d1fd43e924bc9505327ae95a13 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Fri, 24 Jul 2026 01:09:44 -0700 Subject: [PATCH 10/17] Use solo-layout Next button on Choose Wallets to Add scene Switches from KavButtons (fullWidth) to EdgeButton with layout="solo", matching the button's original non-full-width appearance and letting EdgeButton's built-in usePendingPress hook manage the pending/error state instead of a hand-rolled scene state. --- .../scenes/CreateWalletSelectCryptoScene.tsx | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/components/scenes/CreateWalletSelectCryptoScene.tsx b/src/components/scenes/CreateWalletSelectCryptoScene.tsx index 81a09e127cf..e2ae42d404a 100644 --- a/src/components/scenes/CreateWalletSelectCryptoScene.tsx +++ b/src/components/scenes/CreateWalletSelectCryptoScene.tsx @@ -24,7 +24,6 @@ import type { EdgeAppSceneProps, NavigationBase } from '../../types/routerTypes' import type { EdgeAsset } from '../../types/types' import { logEvent } from '../../util/tracking' import { EdgeButton } from '../buttons/EdgeButton' -import { KavButtons } from '../buttons/KavButtons' import { EdgeAnim } from '../common/EdgeAnim' import { SceneWrapper } from '../common/SceneWrapper' import { SearchIconAnimated } from '../icons/ThemedIcons' @@ -136,8 +135,6 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => { } return out }) - const [isNextPending, setIsNextPending] = React.useState(false) - const findMainnetItem = (pluginId: string): MainWalletCreateItem => { const newItem = createWalletList.find(item => item.pluginId === pluginId) return newItem as MainWalletCreateItem @@ -166,17 +163,7 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => { showError(lstrings.create_wallet_no_assets_selected) return } - setIsNextPending(true) - try { - await handleNextPressBody() - } catch (error: unknown) { - showError(error) - } finally { - setIsNextPending(false) - } - }) - const handleNextPressBody = useHandler(async () => { if (newAccountFlow != null) dispatch( logEvent('Signup_Wallets_Selected_Next', { @@ -433,14 +420,12 @@ const CreateWalletSelectCryptoComponent: React.FC = (props: Props) => { exit={{ type: 'fadeOut', duration: 300 }} accessible={false} > - ) From 8fbea6c3b78c02f0349f4393c18f65c6c03e563a Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 16:50:16 -0700 Subject: [PATCH 11/17] Update custom icon font with new designs Regenerated the fontello 'custom' font from the designer SVGs attached to the task. The buy, sell, sort and control-panel-scan-qr glyphs are replaced with the updated artwork, and two new glyphs (list, chart) are added. The font is rebuilt through the fontello session API, which reproduces the existing custom.ttf byte for byte from the checked-in config, so the only outline changes are the six intended glyphs. --- CHANGELOG.md | 2 ++ android/app/src/main/assets/fonts/custom.ttf | Bin 21564 -> 22908 bytes src/assets/vector/config.json | 34 +++++++++++++------ src/components/themed/SideMenu.tsx | 2 +- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de7115b5dc4..6c073cee99c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ - changed: Style the entire "Already have an account? Sign in" line in the getting-started USP carousel with the tertiary link color, not just "Sign in". - fixed: Sort the Privacy Settings Nym Mix Net asset list alphabetically by display name - fixed: Next button overlapping the wallet list on the Choose Wallets to Add scene +- changed: Refresh the buy, sell, sort, scan-QR and FIO names icons to the updated design. +- changed: Use a custom chart icon for the side menu Markets row, so it matches the rest of the menu. ## 4.50.0 (2026-07-21) diff --git a/android/app/src/main/assets/fonts/custom.ttf b/android/app/src/main/assets/fonts/custom.ttf index 605e9dd4b390060d212328d001454d957e643c70..313e372e5dbfd64e31fed6181947fc783a7d4d83 100644 GIT binary patch delta 3801 zcmai1eQ;FO6+h>`@Avk-w{PF>+plDIlZ9Q%N4~RZ0+JAdLO=*$4N^)7L6Rw?1f*0Q z%ce-Qoe@78`3G$4Fluc_8HPY)s!X+FMICjVaqPFFt<$a!9hq7gogqul+kH`+QhhVO zch0%*+;i`{_q^Y^@0rK(@=3hEE`Rv7p}hcj0)TsDVBg*pl&rRkWN15N-fie0=EU{%!R`>PG-wJU!11QG@&={zbAE zkv(f@c<-%8KDcs)>@8$JymQyUivDzybrN71*|2W7|JD(lG7peV0U`CB{^6UqYHzJ2 znm&A{>g=t02|(U;FDK+p9OT|z^{NSY4b^{^mfkz z5BE~L4`g7#7m|f*3o8l_UA*MZr2vJl!t!aW)Ze`!d3VEV=z*T`YPJTm6C+GVa0f4V0F87H6dDR1KquV;7^HgulXMU6k{3LHMY@MP1jz6J zHt8N9NV*3Ik?sK;(mg~Bn{swl*-lrL?K?Ol${phTW?5QnlG6?TK|Y!{N{SFIex%#Bt_K2DDk{Tr zOjS`HR$_OkF?mErwJeH1?lXcxqt6J1F0+WMP8CRJZy<%9+HS-+&Tp`zMszB}M-BWp zje(>TbwfWN(D6|UuNS|m2V{zWJB=g9=DP9}4L}1a3aZ~%W4JqHFgG?cLQ|WA21g^% zEjaO}+{))U+OGrLAGkJ{4~t+G)Pq~Y53Yp(0M zW>x#Dg{U6c`wf{F=dNFAg|Rkh zUcTQ922F0n3|<~~-_8t$UK5RePS|8;c0?mA6xVAaCsU?v$-J-2X2@f!PvwMaBPlEG z`CFn&@lCHEcUQ=`d|zpab{ip>Q1$gJFVeRG+~e&0eb^CihcF(JnAq(9koz+BD3JxF5R?f7C5EGbvV>pB9t^(79`oj=tCNYa zQ(k+QJw9Lie3>OaXN75&D6_)!+RpxrZm!NYU&S7*s>BVpUE&7C2`0Dg=q|6FshL9+}%MD7@6%!Qxb82{Z2G7)7g=pB{u1+8HMNBk> z-o-qX&MSwxv)pW|TXnDkHp6bX6Ar`A;S9VCe}NCNt0OHV!t-ZNa-9A2FIj-wjas1a zDifV|-eO_#S`;K{)yflzo(&z_bX3u{)jh!30hl64=E@Kg49bw68b9SI44NLns~=hX&hAT z(HR4oqwYun)7d|BC~5R+L*>6}teZNP-F@)-?xy;R=!Fm7`|BTn|HkVtzVPd_Pn|mc z#G}W4a^${y4j#Vy!5{3uZQrh)+XvS5bZ@@?TU|@qTI*LdT{kydT@|gXsBLUYh>>}A zQ*%>pAvT0dbxEZ9B#GHfKAUOA2C7%#P*AKvs#Y1dYGpIImPV{jm+DoETfH19=umGb zL{&|o2Q^5+oSa+U=&6AcdhwkQxt2;_c6N}0P8Y#a=jo8{L`o$xpZ;H`nJtnK3^_C6 zd^VU^exFS9%wA5Y#tama%<{jWh_K3Bk$bWC@NNa1rjOhlV zsGRc$Sn9iMduyBDLd$>Y*2F+AA$lAA{#`LxNjMMJZ7g8y)JUz8DnGywF8l{_9d^nXP zDRg$yO#`iu{M1?Q(~7z|RG-%$J!;1>ZnK?MG|vlhr})y*qrq6YB_enY z(=Me3cFM2NnImCGGEzkZ(JDU0c!{%ZIT4Ix#6*M@&mx+V-&87HpLhg0WOq1KVZRYb z6emlHU0FO%ifPyh`WJ#Lk8}0h#B<4bvSRShH%9u*MIVF772LDN-+wspi)lIj$>J8A zGhWv|f4rleAK%ozFV7O}LC)Ytxf6UF|FZBe@e%1B>5MGPN0breg1W`mrf<_P8uyxp z`LchtzhD*Y$ATL|CqfsTShynE6FU|^RvD=*B#tF}X&5BTfs5`x1zil5!$CMi+It3Z zd+7?m+uq+CKr@VA-|;r)#^*0Nj=70HEonoVp0ji{!>^5RTe?CJ28R0gOgyple*b>} D+AX58 delta 2476 zcmbVOZEO_B8GhfH-I=|;z5Te|JAa?;JNw+!$N}HkCNY?6WrIK(G zDEWl52#!+GBt(ux6}co*RU9QWigG1F6+hZYP$})d7AaDtq{^RcKhmNqNm~maedkuG zXw>#cSNF^_@4WBM?7Z{L%-wtoue^$9*Os4p;_;mT_$B~9zwh|5HmE%#r-|$Xb2Eqb z_Wa{+`Pz4E=`u{j=0y zd{?^$u*o93WA@OoN6)cax5!>g_R@n#_HCW|*u428YWNj3j31hMbPoUK%m9>{$!3`z|Vl! zYHNs1n%y5ojO?V-Y|xeTIVioG=~%6jL7xs;@8+0TkO2 zXm|*n2%wmfub>to^df*_`W}TcsEQu}6bBJNaVi2RPDcR6brC>ueFRY45CIf7Mxbm! z*c1U2(?ThL;uad83IIZwi2#aQBY@)J2+&465&_zYH%EXh@n{6d5pRtE9mGo!fD~}! zvj~u<@ic&2-{K*Aw{pLJhLssL0l&o*+$u(&3Ex!HqXw9q$%fh*Ek_8Ti%bBZ6mI(SjN|-^*y}u5_4^XpGQ+x z^U`F`ju;jm5SA+*)8!+2(qwWs9y2VbeKi+z0wID57NaU|lxf=c#0*yg>LF z+=O-MAl)il4U>8lt2^{BXa+FWk952eG zJZs?Vkw)B17^Tz z_l!4{#&7Gf{I4z(o<0Bn_PM*%V_8CU9OiOc9V?%viIu-ay%dicBOWsjLR>TByAvc%-PH6Mh{w$- zFyrReTAC3KOy76!`1UQEHw>;@+nH27S#iWUsgh4$Y5jaba{ z_AHrXkwNu;^I?!-y#qt6*wuqw`7DW5ctM7?Z>_P1Wu?u$psp4~d%hS&G5*BrRXRgB z8&u`$RVmqES<2fQZ-z13Z!qF^Ow*jW(cs&<7IR`&V4ALBICZXVl1s0}IGPr-YcVl) zPUiz$uNBol82twQs((}_jYAG5E&q$}Ro}OKm+Lv5yMBG(7}RS}{UBkO4h`U#G4NL((xivcpNiA)EY&_(JUsF#78dt!egg{?f6LuS { handleMarketsPress() }, - iconNameFontAwesome: 'chart-line', + iconName: 'chart', title: lstrings.title_markets }, // Only show gift card menu option if Phaze API key is configured From a411c28c5a184581ceaecbed08b6a65d6399d4e8 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 19:30:36 -0700 Subject: [PATCH 12/17] Wrap fiat value in parens on Stake amount tile Also remove the space between the fiat symbol and the amount so it matches the network fee tile's formatting (useFiatText's default), and add the explicit React.FC return type. --- CHANGELOG.md | 1 + eslint.config.mjs | 2 +- .../__snapshots__/SendScene2.ui.test.tsx.snap | 28 +++++++++---------- src/components/tiles/EditableAmountTile.tsx | 8 +++--- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c073cee99c..1cfcd99d23c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - fixed: Next button overlapping the wallet list on the Choose Wallets to Add scene - changed: Refresh the buy, sell, sort, scan-QR and FIO names icons to the updated design. - changed: Use a custom chart icon for the side menu Markets row, so it matches the rest of the menu. +- fixed: Wrap the fiat value in parentheses on the Stake/Unstake/Claim amount row, and remove the space between the fiat symbol and amount to match the network fee tile. ## 4.50.0 (2026-07-21) diff --git a/eslint.config.mjs b/eslint.config.mjs index e9c9aa43d0c..c0606e4ad34 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -400,7 +400,7 @@ export default [ 'src/components/tiles/AprCard.tsx', 'src/components/tiles/CountdownTile.tsx', 'src/components/tiles/CryptoFiatAmountTile.tsx', - 'src/components/tiles/EditableAmountTile.tsx', + 'src/components/tiles/ErrorTile.tsx', 'src/components/tiles/FiatAmountTile.tsx', 'src/components/tiles/InterestRateChangeTile.tsx', diff --git a/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap index 889f7f1a68f..d50f1d07082 100644 --- a/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap @@ -925,7 +925,7 @@ exports[`SendScene2 1 spendTarget 1`] = ` ] } > - € 0.23 + (€0.23) - € 0.23 + (€0.23) - Amount: 0.00001234 BTC (€ 0.23) + Amount: 0.00001234 BTC (€0.23) - € 2.26 + (€2.26) - Amount: 0.00001234 BTC (€ 0.23) + Amount: 0.00001234 BTC (€0.23) - € 2.26 + (€2.26) - Amount: 0.00001234 BTC (€ 0.23) + Amount: 0.00001234 BTC (€0.23) - Amount: 0.00001234 BTC (€ 0.23) + Amount: 0.00001234 BTC (€0.23) - Amount: 0.00001234 BTC (€ 0.23) + Amount: 0.00001234 BTC (€0.23) - € 2.26 + (€2.26) - Amount: 0.00001234 BTC (€ 0.23) + Amount: 0.00001234 BTC (€0.23) @@ -13157,7 +13157,7 @@ exports[`SendScene2 2 spendTargets lock tiles 2`] = ` ] } > - € 2.26 + (€2.26) @@ -14446,7 +14446,7 @@ exports[`SendScene2 2 spendTargets lock tiles 3`] = ` ] } > - Amount: 0.00001234 BTC (€ 0.23) + Amount: 0.00001234 BTC (€0.23) @@ -14786,7 +14786,7 @@ exports[`SendScene2 2 spendTargets lock tiles 3`] = ` ] } > - € 2.26 + (€2.26) diff --git a/src/components/tiles/EditableAmountTile.tsx b/src/components/tiles/EditableAmountTile.tsx index fc45bba2bfe..8f02c132061 100644 --- a/src/components/tiles/EditableAmountTile.tsx +++ b/src/components/tiles/EditableAmountTile.tsx @@ -29,7 +29,7 @@ interface Props { onPress: () => void } -export const EditableAmountTile = (props: Props) => { +export const EditableAmountTile: React.FC = props => { let cryptoAmountSyntax let cryptoAmountStyle let fiatAmountSyntax @@ -74,8 +74,8 @@ export const EditableAmountTile = (props: Props) => { exchangeAmount ) cryptoAmountSyntax = `${displayAmount ?? '0'} ${displayDenomination.name}` - if (fiatAmount) { - fiatAmountSyntax = `${fiatSymbol} ${ + if (fiatAmount !== '') { + fiatAmountSyntax = `${fiatSymbol}${ toFixed(round(fiatAmount, -2), 2, 2) ?? '0' }` } @@ -119,7 +119,7 @@ export const EditableAmountTile = (props: Props) => { {cryptoAmountSyntax} {fiatAmountSyntax == null ? null : ( - {fiatAmountSyntax} + {`(${fiatAmountSyntax})`} )} From 574ba5572d18b72efa89b31bab8a2319161e4693 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 19:25:43 -0700 Subject: [PATCH 13/17] Fix staked locked balance text cutoff Truncate the locked crypto amount with the shared getCryptoText helper so it uses the same exchange-rate-adjusted precision as the rest of the app, and drop the nested maxWidth clamps that limited the status text to roughly half the card width. --- CHANGELOG.md | 1 + src/components/themed/TransactionListTop.tsx | 27 +++++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cfcd99d23c..da9069fa172 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - changed: Refresh the buy, sell, sort, scan-QR and FIO names icons to the updated design. - changed: Use a custom chart icon for the side menu Markets row, so it matches the rest of the menu. - fixed: Wrap the fiat value in parentheses on the Stake/Unstake/Claim amount row, and remove the space between the fiat symbol and amount to match the network fee tile. +- fixed: Staked "locked" balance in the wallet view no longer gets cut off. The crypto amount is truncated to an exchange-rate-appropriate number of decimals, and the text is no longer clamped to a fraction of the card width. ## 4.50.0 (2026-07-21) diff --git a/src/components/themed/TransactionListTop.tsx b/src/components/themed/TransactionListTop.tsx index d8332b3b2cf..96b08f93a0f 100644 --- a/src/components/themed/TransactionListTop.tsx +++ b/src/components/themed/TransactionListTop.tsx @@ -41,6 +41,7 @@ import type { WalletsTabSceneProps } from '../../types/routerTypes' import { CryptoAmount } from '../../util/CryptoAmount' +import { getCryptoText } from '../../util/cryptoTextUtils' import { isKeysOnlyPlugin } from '../../util/CurrencyInfoHelpers' import { triggerHaptic } from '../../util/haptic' import { @@ -55,6 +56,7 @@ import { getUkCompliantString } from '../../util/ukComplianceUtils' import { convertNativeToDenomination, DECIMAL_PRECISION, + getDenomFromIsoCode, removeIsoPrefix, zeroString } from '../../util/utils' @@ -587,12 +589,16 @@ export const TransactionListTop: React.FC = props => { const nativeLocked = add(fioStatus.locked, lockedNativeAmount) if (nativeLocked === '0') return null - const stakingCryptoAmount = convertNativeToDenomination( - displayDenomination.multiplier - )(nativeLocked) - const stakingCryptoAmountFormat = formatNumber( - add(stakingCryptoAmount, '0') - ) + // Truncate to an exchange-rate-appropriate number of decimals, so the + // whole "locked" message stays readable on narrow screens: + const stakingCryptoText = getCryptoText({ + currencyCode: displayDenomination.name, + displayDenomination, + exchangeDenomination, + exchangeRate, + fiatDenomination: getDenomFromIsoCode(defaultIsoFiat), + nativeAmount: nativeLocked + }) const stakingExchangeAmount = convertNativeToDenomination( exchangeDenomination.multiplier @@ -608,7 +614,7 @@ export const TransactionListTop: React.FC = props => { {sprintf( lstrings.staking_status, - stakingCryptoAmountFormat + ' ' + displayDenomination.name, + stakingCryptoText, fiatSymbol + stakingFiatBalanceFormat + ' ' + defaultFiat )} @@ -909,15 +915,12 @@ const getStyles = cacheStyles((theme: Theme) => ({ // Staking Box stakingBoxContainer: { height: theme.rem(1.25), - minWidth: theme.rem(18), - maxWidth: '70%', alignItems: 'center', - flexDirection: 'row', - justifyContent: 'space-between' + flexDirection: 'row' }, stakingStatusText: { + flexShrink: 1, color: theme.secondaryText, - maxWidth: '70%', fontSize: theme.rem(1) } })) From 65951b9b3c0ab33fd9f8528dbada17f0bb825026 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Fri, 24 Jul 2026 12:19:32 -0700 Subject: [PATCH 14/17] Migrate raw keys and master private key warnings to UI4 card Replace the ad-hoc warning-color Paragraph on the Reveal Raw Keys and Reveal Master Private Key password confirmation modals with the AlertCardUi4 warning card. Also fold in the follow-up lint fixes: explicit return types, safe catch callback typing, and replacing the styled() inner view with a plain View + StyleSheet. --- CHANGELOG.md | 1 + eslint.config.mjs | 1 - .../TextInputModal.test.tsx.snap | 22 ++++++------ src/components/modals/TextInputModal.tsx | 36 ++++++++++--------- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da9069fa172..bed3b72890a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - changed: Use a custom chart icon for the side menu Markets row, so it matches the rest of the menu. - fixed: Wrap the fiat value in parentheses on the Stake/Unstake/Claim amount row, and remove the space between the fiat symbol and amount to match the network fee tile. - fixed: Staked "locked" balance in the wallet view no longer gets cut off. The crypto amount is truncated to an exchange-rate-appropriate number of decimals, and the text is no longer clamped to a fraction of the card width. +- changed: Use the UI4 warning card for the Reveal Raw Keys and Reveal Master Private Key password confirmation warnings. ## 4.50.0 (2026-07-21) diff --git a/eslint.config.mjs b/eslint.config.mjs index c0606e4ad34..d4f822fbd21 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -225,7 +225,6 @@ export default [ 'src/components/modals/ScanModal.tsx', 'src/components/modals/StateProvinceListModal.tsx', - 'src/components/modals/TextInputModal.tsx', 'src/components/modals/TransferModal.tsx', 'src/components/modals/WalletListSortModal.tsx', diff --git a/src/__tests__/modals/__snapshots__/TextInputModal.test.tsx.snap b/src/__tests__/modals/__snapshots__/TextInputModal.test.tsx.snap index 57fdb3f2886..c67d53b4e75 100644 --- a/src/__tests__/modals/__snapshots__/TextInputModal.test.tsx.snap +++ b/src/__tests__/modals/__snapshots__/TextInputModal.test.tsx.snap @@ -286,12 +286,13 @@ exports[`TextInputModal should render with a blank input field 1`] = ` = props => { const { autoCapitalize, autoFocus = true, @@ -82,12 +81,12 @@ export function TextInputModal(props: Props) { const [errorMessage, setErrorMessage] = React.useState() const [text, setText] = React.useState(initialValue) - const handleChangeText = (text: string) => { + const handleChangeText = (text: string): void => { setText(text) setErrorMessage(undefined) } - const handleSubmit = () => { + const handleSubmit = (): void => { if (onSubmit == null) { bridge.resolve(text) return @@ -100,7 +99,7 @@ export function TextInputModal(props: Props) { bridge.resolve(text) } }, - error => { + (error: unknown) => { showError(error) } ) @@ -115,19 +114,20 @@ export function TextInputModal(props: Props) { bridge.resolve(undefined) }} > - + {typeof message === 'string' ? ( {message} ) : ( <>{message} )} {warningMessage != null ? ( - ) : null} - + ) } -const StyledInnerView = styled(View)<{ fullHeight: boolean }>(() => props => ({ - flexShrink: 1, - flexGrow: props.fullHeight ? 1 : undefined -})) +const styles = StyleSheet.create({ + innerView: { + flexShrink: 1 + }, + fullHeight: { + flexGrow: 1 + } +}) From 6cd17b99be3f23d06b0faffbc8c1b2b811993651 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 19:32:41 -0700 Subject: [PATCH 15/17] Fix lint warnings in tronStakePlugin --- eslint.config.mjs | 2 +- src/plugins/stake-plugins/currency/tronStakePlugin.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index d4f822fbd21..2b4d8469310 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -461,7 +461,7 @@ export default [ 'src/plugins/gui/util/fetchRevolut.ts', 'src/plugins/gui/util/initializeProviders.ts', - 'src/plugins/stake-plugins/currency/tronStakePlugin.ts', + 'src/plugins/stake-plugins/generic/pluginInfo/optimismTarotPool.ts', 'src/plugins/stake-plugins/generic/policyAdapters/CardanoKilnAdaptor.ts', 'src/plugins/stake-plugins/generic/policyAdapters/EthereumKilnAdaptor.ts', diff --git a/src/plugins/stake-plugins/currency/tronStakePlugin.ts b/src/plugins/stake-plugins/currency/tronStakePlugin.ts index aa0e0e303b2..7b8bcc6bf43 100644 --- a/src/plugins/stake-plugins/currency/tronStakePlugin.ts +++ b/src/plugins/stake-plugins/currency/tronStakePlugin.ts @@ -152,7 +152,7 @@ export const makeTronStakePlugin = async ( const out: StakePolicy[] = [] for (const policy of policies) { - if (policy.deprecated && filter?.wallet != null) { + if (policy.deprecated === true && filter?.wallet != null) { const deprecatedPolicyBalance = filter.wallet.stakingStatus.stakedAmounts.find( stakedAmount => @@ -319,7 +319,7 @@ export const makeTronStakePlugin = async ( } ) - const approve = async () => { + const approve = async (): Promise => { const signedTx = await wallet.signTx(edgeTransaction) const broadcastedTx = await wallet.broadcastTx(signedTx) await wallet.saveTx(broadcastedTx) @@ -435,7 +435,8 @@ const fetchChangeQuoteV1 = async ( } ], otherParams: { - type: policy.deprecated ? 'remove' : isStake ? 'addV2' : 'removeV2', + type: + policy.deprecated === true ? 'remove' : isStake ? 'addV2' : 'removeV2', params: { nativeAmount, resource } } } @@ -458,7 +459,7 @@ const fetchChangeQuoteV1 = async ( } ] - const approve = async () => { + const approve = async (): Promise => { const signedTx = await wallet.signTx(edgeTransaction) const broadcastedTx = await wallet.broadcastTx(signedTx) await wallet.saveTx(broadcastedTx) @@ -498,7 +499,7 @@ const fetchStakePositionV1 = async ( locktime } ], - canStake: !policy.deprecated && gt(balanceTrx, '0'), + canStake: policy.deprecated !== true && gt(balanceTrx, '0'), canUnstake: locktime != null ? new Date() >= new Date(locktime) : true, canUnstakeAndClaim: false, canClaim: false From 8d10d0880b064c0cf38a1b2e632492375f0c38d5 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 23 Jul 2026 19:38:31 -0700 Subject: [PATCH 16/17] Use reclaim wording for Tron resource staking claims Freezing TRX buys bandwidth and energy rather than yield, and the claim action only returns the user's own unfrozen TRX to their spendable balance. Let a stake policy replace the default reward wording so the Tron policies read as reclaiming instead of earning. --- CHANGELOG.md | 1 + .../scenes/Staking/StakeModifyScene.tsx | 7 ++++-- .../scenes/Staking/StakeOverviewScene.tsx | 20 +++++++++------- src/locales/en_US.ts | 4 ++++ src/locales/strings/enUS.json | 4 ++++ .../stake-plugins/currency/tronStakePlugin.ts | 15 ++++++++++++ src/plugins/stake-plugins/types.ts | 24 +++++++++++++++++++ 7 files changed, 64 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed3b72890a..a1258e6f338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - fixed: Wrap the fiat value in parentheses on the Stake/Unstake/Claim amount row, and remove the space between the fiat symbol and amount to match the network fee tile. - fixed: Staked "locked" balance in the wallet view no longer gets cut off. The crypto amount is truncated to an exchange-rate-appropriate number of decimals, and the text is no longer clamped to a fraction of the card width. - changed: Use the UI4 warning card for the Reveal Raw Keys and Reveal Master Private Key password confirmation warnings. +- changed: Tron resource staking now describes its claim action as reclaiming your own TRX, instead of claiming a reward. ## 4.50.0 (2026-07-21) diff --git a/src/components/scenes/Staking/StakeModifyScene.tsx b/src/components/scenes/Staking/StakeModifyScene.tsx index 092c7760b95..afe90a850bb 100644 --- a/src/components/scenes/Staking/StakeModifyScene.tsx +++ b/src/components/scenes/Staking/StakeModifyScene.tsx @@ -79,6 +79,7 @@ const StakeModifySceneComponent: React.FC = props => { const { modification, title, stakePlugin, stakePolicy } = route.params const dispatch = useDispatch() const { + claimLanguage, stakePolicyId, stakeWarning, unstakeWarning, @@ -284,7 +285,8 @@ const StakeModifySceneComponent: React.FC = props => { const message = { stake: lstrings.stake_change_stake_success, unstake: lstrings.stake_change_unstake_success, - claim: lstrings.stake_change_claim_success, + claim: + claimLanguage?.successMessage ?? lstrings.stake_change_claim_success, unstakeExact: '' } @@ -479,7 +481,8 @@ const StakeModifySceneComponent: React.FC = props => { allocationType === 'stake' ? sprintf(lstrings.stake_amount_s_stake, quoteCurrencyCode) : isClaim - ? sprintf(lstrings.stake_amount_claim, quoteCurrencyCode) + ? claimLanguage?.amountLabel ?? + sprintf(lstrings.stake_amount_claim, quoteCurrencyCode) : sprintf(lstrings.stake_amount_s_unstake, quoteCurrencyCode) const nativeAmount = zeroString(quoteAllocation?.nativeAmount) diff --git a/src/components/scenes/Staking/StakeOverviewScene.tsx b/src/components/scenes/Staking/StakeOverviewScene.tsx index e9a290d74f6..fd8777dc602 100644 --- a/src/components/scenes/Staking/StakeOverviewScene.tsx +++ b/src/components/scenes/Staking/StakeOverviewScene.tsx @@ -109,6 +109,10 @@ const StakeOverviewSceneComponent: React.FC = props => { ? { stakeAssetUris: [], rewardAssetUris: [] } : getPolicyIconUris(account.currencyConfig, stakePolicy) + // A policy may replace the default reward wording of the claim action + const claimLabel = + stakePolicy?.claimLanguage?.actionLabel ?? lstrings.stake_claim_rewards + // Hooks const [stakeAllocations, setStakeAllocations] = React.useState< @@ -177,7 +181,7 @@ const StakeOverviewSceneComponent: React.FC = props => { if (stakePolicy == null) return const sceneTitleMap = { stake: getPolicyTitleName(stakePolicy, countryCode), - claim: lstrings.stake_claim_rewards, + claim: claimLabel, unstake: lstrings.stake_unstake, unstakeAndClaim: lstrings.stake_unstake_claim, unstakeExact: '' // Only for internal use @@ -211,16 +215,14 @@ const StakeOverviewSceneComponent: React.FC = props => { }): React.ReactElement => { const { allocationType, currencyCode, nativeAmount, pluginId, tokenId } = item + const claimablePolicyLabel = stakePolicy?.claimLanguage?.positionLabel const titleBase = allocationType === 'staked' - ? lstrings.stake_s_staked + ? sprintf(lstrings.stake_s_staked, currencyCode) : allocationType === 'earned' - ? lstrings.stake_s_earned - : lstrings.stake_s_unstaked - const title = `${sprintf( - titleBase, - currencyCode - )}${getAllocationLocktimeMessage(item)}` + ? claimablePolicyLabel ?? sprintf(lstrings.stake_s_earned, currencyCode) + : sprintf(lstrings.stake_s_unstaked, currencyCode) + const title = `${titleBase}${getAllocationLocktimeMessage(item)}` const denomination = displayDenomMap[currencyCode] // This is not the wallet we are staking from, but the asset being staked. @@ -312,7 +314,7 @@ const StakeOverviewSceneComponent: React.FC = props => { stakePolicy.hideClaimAction === true ? undefined : { - label: lstrings.stake_claim_rewards, + label: claimLabel, disabled: !canClaim, onPress: handleModifyPress('claim') } diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 343fb2e5990..7e53f2d7a0e 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -1733,6 +1733,10 @@ const strings = { stake_change_stake_success: 'Funds successfully staked', stake_change_unstake_success: 'Funds successfully unstaked', stake_change_claim_success: 'Claim transactions sent successfully', + stake_reclaim_1s: 'Reclaim %1$s', + stake_amount_reclaim_1s: 'Amount of %1$s to Reclaim', + stake_reclaimable_1s: '%1$s Pending Reclaim', + stake_change_reclaim_success: 'Reclaim transaction sent successfully', stake_disabled_slider: 'Enter Amount', stake_warning_multiple_transactions: 'Staking requires multiple transactions to confirm and may take 20 seconds or more to complete', diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index 3bdc227deef..64c7ff05560 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -1360,6 +1360,10 @@ "stake_change_stake_success": "Funds successfully staked", "stake_change_unstake_success": "Funds successfully unstaked", "stake_change_claim_success": "Claim transactions sent successfully", + "stake_reclaim_1s": "Reclaim %1$s", + "stake_amount_reclaim_1s": "Amount of %1$s to Reclaim", + "stake_reclaimable_1s": "%1$s Pending Reclaim", + "stake_change_reclaim_success": "Reclaim transaction sent successfully", "stake_disabled_slider": "Enter Amount", "stake_warning_multiple_transactions": "Staking requires multiple transactions to confirm and may take 20 seconds or more to complete", "stake_warning_stake": "Staking funds will block you from claiming rewards for 16 hours and withdrawing your staked funds for 36 hours.\n\nEvery interaction (stake, unstake, claim rewards) will reset both timers.", diff --git a/src/plugins/stake-plugins/currency/tronStakePlugin.ts b/src/plugins/stake-plugins/currency/tronStakePlugin.ts index 7b8bcc6bf43..7f34767450f 100644 --- a/src/plugins/stake-plugins/currency/tronStakePlugin.ts +++ b/src/plugins/stake-plugins/currency/tronStakePlugin.ts @@ -1,6 +1,7 @@ import { add, gt, lt } from 'biggystring' import { asDate, asMaybe, asObject, asString } from 'cleaners' import type { EdgeSpendInfo, EdgeTransaction } from 'edge-core-js' +import { sprintf } from 'sprintf-js' import { lstrings } from '../../../locales/strings' import { @@ -10,6 +11,7 @@ import { type PositionAllocation, type QuoteAllocation, StakeBelowLimitError, + type StakeClaimLanguage, type StakePlugin, type StakePolicy, type StakePolicyFilter, @@ -19,6 +21,7 @@ import { } from '../types' const MIN_TRX_STAKE = '1000000' // 1 TRX +const TRX_CURRENCY_CODE = 'TRX' const WITHDRAW_PREFIX = 'WITHDRAWEXPIREUNFREEZE_' const stakeProviderInfo: StakeProviderInfo = { @@ -27,8 +30,20 @@ const stakeProviderInfo: StakeProviderInfo = { stakeProviderId: 'tronResources' } +// Freezing TRX buys bandwidth and energy, so there is no yield and no reward to +// claim. The claim action runs WithdrawExpireUnfreeze, which moves the user's +// own unfrozen TRX back to their spendable balance once Tron's post-unfreeze +// delay has passed. +const claimLanguage: StakeClaimLanguage = { + actionLabel: sprintf(lstrings.stake_reclaim_1s, TRX_CURRENCY_CODE), + amountLabel: sprintf(lstrings.stake_amount_reclaim_1s, TRX_CURRENCY_CODE), + positionLabel: sprintf(lstrings.stake_reclaimable_1s, TRX_CURRENCY_CODE), + successMessage: lstrings.stake_change_reclaim_success +} + const policyDefault = { apy: 0, + claimLanguage, stakeProviderInfo, stakeWarning: null, unstakeWarning: null, diff --git a/src/plugins/stake-plugins/types.ts b/src/plugins/stake-plugins/types.ts index c744f7ffb11..662c80fac4f 100644 --- a/src/plugins/stake-plugins/types.ts +++ b/src/plugins/stake-plugins/types.ts @@ -76,6 +76,24 @@ export interface StakeProviderInfo { stakeProviderId: string } +/** + * Display strings for the claim action, already formatted for display. A policy + * provides these to replace the default reward wording. + */ +export interface StakeClaimLanguage { + // Claim button label and claim scene title + actionLabel: string + + // Claim amount tile title + amountLabel: string + + // Title of the claimable position row + positionLabel: string + + // Shown once the claim transaction is sent + successMessage: string +} + export interface StakePolicy { // Internal policy id, unique across all stake policies offered by Edge stakePolicyId: string @@ -96,6 +114,12 @@ export interface StakePolicy { hideUnstakeAction?: boolean hideUnstakeAndClaimAction?: boolean + // Replaces the default reward wording of the claim action. Policies such as + // Tron resource freezing pay no yield and hold no third-party reward: their + // claim only returns the user's own previously staked funds to the spendable + // balance, so calling it a reward misrepresents what the user receives. + claimLanguage?: StakeClaimLanguage + // Staking policy properties isLiquidStaking?: boolean From 28fb216ef9112ec52f98bb1c7955c440338566b8 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Fri, 24 Jul 2026 01:06:31 -0700 Subject: [PATCH 17/17] Improve unstake scene error handling Replace the popup error alert and generic "unknown error occurred" on the StakeModify scene with the real error surfaced in the existing on-scene error field. When the failure is insufficient funds during an unstake, show a clear message that the wallet needs a native-asset balance to cover the network fee. --- CHANGELOG.md | 13 ++++--- src/__tests__/stakeErrorUtils.test.ts | 38 +++++++++++++++++++ .../scenes/Staking/StakeModifyScene.tsx | 23 ++++++++--- src/locales/en_US.ts | 2 + src/locales/strings/enUS.json | 1 + src/util/stakeErrorUtils.ts | 12 ++++++ 6 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 src/__tests__/stakeErrorUtils.test.ts create mode 100644 src/util/stakeErrorUtils.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a1258e6f338..28179efd375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,22 +4,23 @@ - added: Verbose logging for exchange rate queries: the request body, resolved/rate-less counts, and errors are captured when the Verbose Logging setting is enabled. - added: Exchange-rate cache snapshot in the support log output, plus a `rates-cache-replay` script that re-runs those queries against the rates server and reports the result for each pair. -- changed: Sign MoonPay buy/sell widget URLs and bind them to the customer's IP via the info server, for MoonPay's on-ramp IP-matching security upgrade. - added: "-m" tag on the version number in the Help scene for Maestro test builds +- changed: Sign MoonPay buy/sell widget URLs and bind them to the customer's IP via the info server, for MoonPay's on-ramp IP-matching security upgrade. +- changed: Style the entire "Already have an account? Sign in" line in the getting-started USP carousel with the tertiary link color, not just "Sign in". +- changed: Refresh the buy, sell, sort, scan-QR and FIO names icons to the updated design. +- changed: Use a custom chart icon for the side menu Markets row, so it matches the rest of the menu. +- changed: Use the UI4 warning card for the Reveal Raw Keys and Reveal Master Private Key password confirmation warnings. +- changed: Tron resource staking now describes its claim action as reclaiming your own TRX, instead of claiming a reward. - fixed: NYM max swaps from EVM wallets now report the correct limit error instead of an unsupported-route error (edge-exchange-plugins 2.52.1). - fixed: Exchange rate queries no longer request each chain's own asset twice, which had been inflating every rate query with duplicate pairs. - fixed: XRP minimum balance warning copy to clarify the reserve is met once the address balance reaches 1 XRP, not on top of it. - fixed: Round fiat balances to cents in the Wallets list, matching the wallet detail scene - fixed: Notification center cards no longer shrink their text to fit. Long titles and messages now truncate with an ellipsis so every card renders at the same size. -- changed: Style the entire "Already have an account? Sign in" line in the getting-started USP carousel with the tertiary link color, not just "Sign in". - fixed: Sort the Privacy Settings Nym Mix Net asset list alphabetically by display name - fixed: Next button overlapping the wallet list on the Choose Wallets to Add scene -- changed: Refresh the buy, sell, sort, scan-QR and FIO names icons to the updated design. -- changed: Use a custom chart icon for the side menu Markets row, so it matches the rest of the menu. - fixed: Wrap the fiat value in parentheses on the Stake/Unstake/Claim amount row, and remove the space between the fiat symbol and amount to match the network fee tile. - fixed: Staked "locked" balance in the wallet view no longer gets cut off. The crypto amount is truncated to an exchange-rate-appropriate number of decimals, and the text is no longer clamped to a fraction of the card width. -- changed: Use the UI4 warning card for the Reveal Raw Keys and Reveal Master Private Key password confirmation warnings. -- changed: Tron resource staking now describes its claim action as reclaiming your own TRX, instead of claiming a reward. +- fixed: Improve the unstake error experience by replacing the popup alert and generic "unknown error occurred" with the real error in the scene's error field, and showing a clear message when the wallet lacks the balance to cover the unstaking network fee. ## 4.50.0 (2026-07-21) diff --git a/src/__tests__/stakeErrorUtils.test.ts b/src/__tests__/stakeErrorUtils.test.ts new file mode 100644 index 00000000000..ff520ae1c61 --- /dev/null +++ b/src/__tests__/stakeErrorUtils.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, test } from '@jest/globals' +import { DustSpendError, InsufficientFundsError } from 'edge-core-js' + +import { lstrings } from '../locales/strings' +import { getDisplayErrorMessage } from '../util/stakeErrorUtils' + +describe('getDisplayErrorMessage', () => { + test('returns the message of an Error that carries one', () => { + expect(getDisplayErrorMessage(new Error('Insufficient funds'))).toBe( + 'Insufficient funds' + ) + }) + + test('fishes out the message from edge-core-js error subclasses', () => { + expect( + getDisplayErrorMessage(new InsufficientFundsError({ tokenId: null })) + ).not.toBe('') + expect(getDisplayErrorMessage(new DustSpendError())).not.toBe('') + }) + + test('falls back to the generic string for an Error with no message', () => { + expect(getDisplayErrorMessage(new Error(''))).toBe( + lstrings.unknown_error_occurred_fragment + ) + }) + + test('falls back to the generic string for non-Error values', () => { + expect(getDisplayErrorMessage('some string')).toBe( + lstrings.unknown_error_occurred_fragment + ) + expect(getDisplayErrorMessage(undefined)).toBe( + lstrings.unknown_error_occurred_fragment + ) + expect(getDisplayErrorMessage(null)).toBe( + lstrings.unknown_error_occurred_fragment + ) + }) +}) diff --git a/src/components/scenes/Staking/StakeModifyScene.tsx b/src/components/scenes/Staking/StakeModifyScene.tsx index afe90a850bb..99f7f1775f1 100644 --- a/src/components/scenes/Staking/StakeModifyScene.tsx +++ b/src/components/scenes/Staking/StakeModifyScene.tsx @@ -31,6 +31,7 @@ import { useDispatch, useSelector } from '../../../types/reactRedux' import type { EdgeAppSceneProps } from '../../../types/routerTypes' import { getCurrencyIconUris } from '../../../util/CdnUris' import { getWalletName } from '../../../util/CurrencyWalletHelpers' +import { getDisplayErrorMessage } from '../../../util/stakeErrorUtils' import { enableStakeTokens, getPolicyIconUris, @@ -228,15 +229,26 @@ const StakeModifySceneComponent: React.FC = props => { ) setErrorMessage(errMessage) } else if (err instanceof InsufficientFundsError) { - setErrorMessage(lstrings.exchange_insufficient_funds_title) + // The unstake network fee is paid in the wallet's native asset, so + // tell the user which balance they need instead of a bare + // "Insufficient Funds". + setErrorMessage( + changeQuoteRequest.action === 'unstake' + ? sprintf( + lstrings.stake_error_insufficient_funds_unstake_s, + wallet.currencyInfo.currencyCode + ) + : lstrings.exchange_insufficient_funds_title + ) } else if ( err instanceof HumanFriendlyError || err instanceof DustSpendError ) { setErrorMessage(err.message) } else { - showError(err) - setErrorMessage(lstrings.unknown_error_occurred_fragment) + // Show the real error in the on-scene error field rather than a + // scary popup alert plus a generic "unknown error occurred". + setErrorMessage(getDisplayErrorMessage(err)) } }) .finally(() => { @@ -331,8 +343,9 @@ const StakeModifySceneComponent: React.FC = props => { }, 10000) }) .catch((err: unknown) => { - showError(err) - setErrorMessage(lstrings.unknown_error_occurred_fragment) + // Surface the real error in the on-scene error field instead of a + // scary popup alert plus a generic "unknown error occurred". + setErrorMessage(getDisplayErrorMessage(err)) }) .finally(() => { setSliderLocked(false) diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 7e53f2d7a0e..1c1c8b67e5e 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -1749,6 +1749,8 @@ const strings = { stake_modal_modify_stake_title: 'Stake from %s', stake_modal_modify_unstake_title: 'Unstake from %s', stake_error_insufficient_s: 'Insufficient %s', + stake_error_insufficient_funds_unstake_s: + 'This wallet needs a %s balance to cover the network fee for unstaking.', stake_error_stake_below_minimum: 'Stake amount below minimum', stake_error_unstake_below_minimum: 'Unstake amount below minimum', state_error_pool_full_s: diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index 64c7ff05560..685fd7577ea 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -1372,6 +1372,7 @@ "stake_modal_modify_stake_title": "Stake from %s", "stake_modal_modify_unstake_title": "Unstake from %s", "stake_error_insufficient_s": "Insufficient %s", + "stake_error_insufficient_funds_unstake_s": "This wallet needs a %s balance to cover the network fee for unstaking.", "stake_error_stake_below_minimum": "Stake amount below minimum", "stake_error_unstake_below_minimum": "Unstake amount below minimum", "state_error_pool_full_s": "The %1$s asset pool is currency full. Please try again later.", diff --git a/src/util/stakeErrorUtils.ts b/src/util/stakeErrorUtils.ts new file mode 100644 index 00000000000..3a4fdc122bf --- /dev/null +++ b/src/util/stakeErrorUtils.ts @@ -0,0 +1,12 @@ +import { lstrings } from '../locales/strings' + +/** + * Extract a user-presentable message from an unknown error thrown while + * fetching or approving a stake change quote. Falls back to a generic string + * when the error carries no message, so the on-scene error field can show the + * real reason instead of a scary popup alert plus "unknown error occurred". + */ +export const getDisplayErrorMessage = (err: unknown): string => + err instanceof Error && err.message !== '' + ? err.message + : lstrings.unknown_error_occurred_fragment