From 1e96df51afdcbe43ce7583445256994e81bc3f95 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 18 Jun 2026 16:17:47 -0700 Subject: [PATCH] Deprecate Botanix to keys-only mode on July 9, 2026 Gate the botanix keysOnlyMode flag on a deprecation date so existing wallets stay accessible (keys-only) while new wallet creation is blocked on and after the date. Add a unit test for the date gate. --- CHANGELOG.md | 1 + .../WalletAndCurrencyConstants.test.ts | 47 +++++++++++++++++++ src/components/modals/WalletListModal.tsx | 17 ++++--- src/constants/WalletAndCurrencyConstants.ts | 20 ++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 src/__tests__/constants/WalletAndCurrencyConstants.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 687ecd99f94..7a6c8a7b2b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - added: Honor `af` affiliate parameter on `deep.edge.app` deep links, activating the promotion alongside any inner payload (e.g. private-key import). - added: Show swap KYC/terms modal for NExchange - added: Nym mixnet warning in Stake, Unstake, and Claim Rewards scenes +- changed: Deprecate Botanix by switching it to keys-only mode on July 9, 2026. ## 4.48.2 (2026-06-03) diff --git a/src/__tests__/constants/WalletAndCurrencyConstants.test.ts b/src/__tests__/constants/WalletAndCurrencyConstants.test.ts new file mode 100644 index 00000000000..f83f3634f72 --- /dev/null +++ b/src/__tests__/constants/WalletAndCurrencyConstants.test.ts @@ -0,0 +1,47 @@ +import { afterEach, describe, expect, jest, test } from '@jest/globals' + +import { + isBotanixDeprecated, + SPECIAL_CURRENCY_INFO +} from '../../constants/WalletAndCurrencyConstants' + +const DEPRECATION_MS = new Date('2026-07-09T00:00:00.000Z').getTime() + +describe('isBotanixDeprecated', function () { + afterEach(() => { + jest.restoreAllMocks() + }) + + test('returns false before the deprecation date', function () { + jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS - 1) + expect(isBotanixDeprecated()).toBe(false) + }) + + test('returns true exactly on the deprecation date', function () { + jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS) + expect(isBotanixDeprecated()).toBe(true) + }) + + test('returns true after the deprecation date', function () { + jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS + 86400000) + expect(isBotanixDeprecated()).toBe(true) + }) +}) + +describe('botanix keysOnlyMode', function () { + afterEach(() => { + jest.restoreAllMocks() + }) + + // The flag is a getter so it re-evaluates the date on each read, rather than + // freezing the value at module load. + test('re-evaluates the date on each read', function () { + const nowSpy = jest.spyOn(Date, 'now') + + nowSpy.mockReturnValue(DEPRECATION_MS - 1) + expect(SPECIAL_CURRENCY_INFO.botanix.keysOnlyMode).toBe(false) + + nowSpy.mockReturnValue(DEPRECATION_MS) + expect(SPECIAL_CURRENCY_INFO.botanix.keysOnlyMode).toBe(true) + }) +}) diff --git a/src/components/modals/WalletListModal.tsx b/src/components/modals/WalletListModal.tsx index 5e5dea7a5ea..86d655ce338 100644 --- a/src/components/modals/WalletListModal.tsx +++ b/src/components/modals/WalletListModal.tsx @@ -86,13 +86,6 @@ interface Props { parentWalletId?: string } -const keysOnlyModeAssets: EdgeAsset[] = Object.keys(SPECIAL_CURRENCY_INFO) - .filter(pluginId => isKeysOnlyPlugin(pluginId)) - .map(pluginId => ({ - pluginId, - tokenId: null - })) - export const WalletListModal: React.FC = props => { const { bridge, @@ -140,10 +133,16 @@ export const WalletListModal: React.FC = props => { // #region Init - // Prevent plugins that are "watch only" from being used unless it's explicitly allowed + // Prevent plugins that are "watch only" from being used unless it's explicitly allowed. + // Computed per render (not once at import) so date-gated keysOnlyMode plugins are + // honored mid-session without an app restart. const walletListExcludeAssets = React.useMemo(() => { const result = excludeAssets - return allowKeysOnlyMode ? result : keysOnlyModeAssets.concat(result ?? []) + if (allowKeysOnlyMode) return result + const keysOnlyModeAssets: EdgeAsset[] = Object.keys(SPECIAL_CURRENCY_INFO) + .filter(pluginId => isKeysOnlyPlugin(pluginId)) + .map(pluginId => ({ pluginId, tokenId: null })) + return keysOnlyModeAssets.concat(result ?? []) }, [allowKeysOnlyMode, excludeAssets]) // #endregion Init diff --git a/src/constants/WalletAndCurrencyConstants.ts b/src/constants/WalletAndCurrencyConstants.ts index c26b9899065..7dcff526555 100644 --- a/src/constants/WalletAndCurrencyConstants.ts +++ b/src/constants/WalletAndCurrencyConstants.ts @@ -555,6 +555,12 @@ export const SPECIAL_CURRENCY_INFO: Record = { showChainIcon: true, dummyPublicAddress: '0x0d73358506663d484945ba85d0cd435ad610b0a0', isImportKeySupported: true, + // Getter, not a fixed boolean: this is read on each access (e.g. via + // isKeysOnlyPlugin) so the date gate re-evaluates during a running session + // and takes effect at the cutover without requiring an app restart. + get keysOnlyMode(): boolean { + return isBotanixDeprecated() + }, walletConnectV2ChainId: { namespace: 'eip155', reference: '3637' @@ -1040,6 +1046,20 @@ function isZecBroken(): boolean { return false } +/** + * Botanix is being deprecated. On and after this date the asset becomes + * keys-only: existing wallets remain accessible (keys-only) but no new Botanix + * wallets can be created. + * + * The date is created inside the function (rather than referencing a + * module-level constant) because `SPECIAL_CURRENCY_INFO` evaluates this at + * module load, before a later `const` declaration would be initialized. + */ +export function isBotanixDeprecated(): boolean { + const deprecationDate = new Date('2026-07-09T00:00:00.000Z') + return Date.now() >= deprecationDate.getTime() +} + export const USD_FIAT = 'iso:USD' /** * Get the fiat symbol from an iso:[fiat] OR fiat currency code