Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
47 changes: 47 additions & 0 deletions src/__tests__/constants/WalletAndCurrencyConstants.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
17 changes: 8 additions & 9 deletions src/components/modals/WalletListModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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> = props => {
const {
bridge,
Expand Down Expand Up @@ -140,10 +133,16 @@ export const WalletListModal: React.FC<Props> = 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 ?? [])
Comment thread
j0ntz marked this conversation as resolved.
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
Expand Down
20 changes: 20 additions & 0 deletions src/constants/WalletAndCurrencyConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,12 @@ export const SPECIAL_CURRENCY_INFO: Record<string, SpecialCurrencyInfo> = {
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()
},
Comment thread
j0ntz marked this conversation as resolved.
walletConnectV2ChainId: {
namespace: 'eip155',
reference: '3637'
Expand Down Expand Up @@ -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
Expand Down
Loading