Skip to content
Merged
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 @@ -16,6 +16,7 @@
- added: Reverse-resolve recipient addresses to ENS / Unstoppable Domains / ZNS names in the send flow, address modal, and transaction history.
- changed: Migrate Monero to the react-native-monero implementation, replacing edge-currency-monero
- changed: Migrate package manager from yarn to npm.
- changed: Deprecate Botanix by switching it to keys-only mode on July 9, 2026.
- fixed: Android build failure from the home screen long-press shortcuts feature, caused by an expo-quick-actions Kotlin compile error under Kotlin 2.3.

## 4.48.2 (2026-06-03)
Expand Down
49 changes: 49 additions & 0 deletions src/__tests__/constants/WalletAndCurrencyConstants.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { afterEach, describe, expect, jest, test } from '@jest/globals'

import {
isKeysOnlyModeDate,
SPECIAL_CURRENCY_INFO
} from '../../constants/WalletAndCurrencyConstants'

const DEPRECATION_MS = new Date('2026-07-09T00:00:00.000Z').getTime()

describe('isKeysOnlyModeDate', function () {
afterEach(() => {
jest.restoreAllMocks()
})

const date = new Date('2026-07-09T00:00:00.000Z')

test('returns false before the date', function () {
jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS - 1)
expect(isKeysOnlyModeDate(date)).toBe(false)
})

test('returns true exactly on the date', function () {
jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS)
expect(isKeysOnlyModeDate(date)).toBe(true)
})

test('returns true after the date', function () {
jest.spyOn(Date, 'now').mockReturnValue(DEPRECATION_MS + 86400000)
expect(isKeysOnlyModeDate(date)).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
25 changes: 25 additions & 0 deletions src/constants/WalletAndCurrencyConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,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 isKeysOnlyModeDate(new Date('2026-07-09T00:00:00.000Z'))
},
Comment thread
j0ntz marked this conversation as resolved.
walletConnectV2ChainId: {
namespace: 'eip155',
reference: '3637'
Expand Down Expand Up @@ -1094,6 +1100,25 @@ function isZecBroken(): boolean {
return false
}

/**
* Generic time-gate for deprecating an asset into keysOnlyMode (watch-only) on
* a specific date. Returns true once the current time is on or after `date`. On
* and after the date the asset becomes keys-only: existing wallets remain
* accessible (keys-only) but no new wallets can be created.
*
* Not specific to any single plugin. Call it from a `keysOnlyMode` getter so
* the gate re-evaluates on each read and takes effect at the cutover without an
* app restart:
* get keysOnlyMode(): boolean { return isKeysOnlyModeDate(new Date('YYYY-MM-DD')) }
*
* Declared as a hoisted function (not a `const`) because `SPECIAL_CURRENCY_INFO`
* references it during module initialization, before a later `const` would be
* assigned (temporal dead zone).
*/
export function isKeysOnlyModeDate(date: Date): boolean {
return Date.now() >= date.getTime()
}

export const USD_FIAT = 'iso:USD'
/**
* Get the fiat symbol from an iso:[fiat] OR fiat currency code
Expand Down
Loading