Skip to content

Commit 1e96df5

Browse files
committed
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.
1 parent 9443c24 commit 1e96df5

4 files changed

Lines changed: 76 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- added: Honor `af` affiliate parameter on `deep.edge.app` deep links, activating the promotion alongside any inner payload (e.g. private-key import).
1414
- added: Show swap KYC/terms modal for NExchange
1515
- added: Nym mixnet warning in Stake, Unstake, and Claim Rewards scenes
16+
- changed: Deprecate Botanix by switching it to keys-only mode on July 9, 2026.
1617

1718
## 4.48.2 (2026-06-03)
1819

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { afterEach, describe, expect, jest, test } from '@jest/globals'
2+
3+
import {
4+
isBotanixDeprecated,
5+
SPECIAL_CURRENCY_INFO
6+
} from '../../constants/WalletAndCurrencyConstants'
7+
8+
const DEPRECATION_MS = new Date('2026-07-09T00:00:00.000Z').getTime()
9+
10+
describe('isBotanixDeprecated', function () {
11+
afterEach(() => {
12+
jest.restoreAllMocks()
13+
})
14+
15+
test('returns false before the deprecation date', function () {
16+
jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS - 1)
17+
expect(isBotanixDeprecated()).toBe(false)
18+
})
19+
20+
test('returns true exactly on the deprecation date', function () {
21+
jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS)
22+
expect(isBotanixDeprecated()).toBe(true)
23+
})
24+
25+
test('returns true after the deprecation date', function () {
26+
jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS + 86400000)
27+
expect(isBotanixDeprecated()).toBe(true)
28+
})
29+
})
30+
31+
describe('botanix keysOnlyMode', function () {
32+
afterEach(() => {
33+
jest.restoreAllMocks()
34+
})
35+
36+
// The flag is a getter so it re-evaluates the date on each read, rather than
37+
// freezing the value at module load.
38+
test('re-evaluates the date on each read', function () {
39+
const nowSpy = jest.spyOn(Date, 'now')
40+
41+
nowSpy.mockReturnValue(DEPRECATION_MS - 1)
42+
expect(SPECIAL_CURRENCY_INFO.botanix.keysOnlyMode).toBe(false)
43+
44+
nowSpy.mockReturnValue(DEPRECATION_MS)
45+
expect(SPECIAL_CURRENCY_INFO.botanix.keysOnlyMode).toBe(true)
46+
})
47+
})

src/components/modals/WalletListModal.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,6 @@ interface Props {
8686
parentWalletId?: string
8787
}
8888

89-
const keysOnlyModeAssets: EdgeAsset[] = Object.keys(SPECIAL_CURRENCY_INFO)
90-
.filter(pluginId => isKeysOnlyPlugin(pluginId))
91-
.map(pluginId => ({
92-
pluginId,
93-
tokenId: null
94-
}))
95-
9689
export const WalletListModal: React.FC<Props> = props => {
9790
const {
9891
bridge,
@@ -140,10 +133,16 @@ export const WalletListModal: React.FC<Props> = props => {
140133

141134
// #region Init
142135

143-
// Prevent plugins that are "watch only" from being used unless it's explicitly allowed
136+
// Prevent plugins that are "watch only" from being used unless it's explicitly allowed.
137+
// Computed per render (not once at import) so date-gated keysOnlyMode plugins are
138+
// honored mid-session without an app restart.
144139
const walletListExcludeAssets = React.useMemo(() => {
145140
const result = excludeAssets
146-
return allowKeysOnlyMode ? result : keysOnlyModeAssets.concat(result ?? [])
141+
if (allowKeysOnlyMode) return result
142+
const keysOnlyModeAssets: EdgeAsset[] = Object.keys(SPECIAL_CURRENCY_INFO)
143+
.filter(pluginId => isKeysOnlyPlugin(pluginId))
144+
.map(pluginId => ({ pluginId, tokenId: null }))
145+
return keysOnlyModeAssets.concat(result ?? [])
147146
}, [allowKeysOnlyMode, excludeAssets])
148147

149148
// #endregion Init

src/constants/WalletAndCurrencyConstants.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,12 @@ export const SPECIAL_CURRENCY_INFO: Record<string, SpecialCurrencyInfo> = {
555555
showChainIcon: true,
556556
dummyPublicAddress: '0x0d73358506663d484945ba85d0cd435ad610b0a0',
557557
isImportKeySupported: true,
558+
// Getter, not a fixed boolean: this is read on each access (e.g. via
559+
// isKeysOnlyPlugin) so the date gate re-evaluates during a running session
560+
// and takes effect at the cutover without requiring an app restart.
561+
get keysOnlyMode(): boolean {
562+
return isBotanixDeprecated()
563+
},
558564
walletConnectV2ChainId: {
559565
namespace: 'eip155',
560566
reference: '3637'
@@ -1040,6 +1046,20 @@ function isZecBroken(): boolean {
10401046
return false
10411047
}
10421048

1049+
/**
1050+
* Botanix is being deprecated. On and after this date the asset becomes
1051+
* keys-only: existing wallets remain accessible (keys-only) but no new Botanix
1052+
* wallets can be created.
1053+
*
1054+
* The date is created inside the function (rather than referencing a
1055+
* module-level constant) because `SPECIAL_CURRENCY_INFO` evaluates this at
1056+
* module load, before a later `const` declaration would be initialized.
1057+
*/
1058+
export function isBotanixDeprecated(): boolean {
1059+
const deprecationDate = new Date('2026-07-09T00:00:00.000Z')
1060+
return Date.now() >= deprecationDate.getTime()
1061+
}
1062+
10431063
export const USD_FIAT = 'iso:USD'
10441064
/**
10451065
* Get the fiat symbol from an iso:[fiat] OR fiat currency code

0 commit comments

Comments
 (0)