Skip to content

Commit

Permalink
feat(suite-native): enable solana via message system
Browse files Browse the repository at this point in the history
  • Loading branch information
vytick committed Dec 9, 2024
1 parent d1ab1b7 commit 59d8d19
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 41 deletions.
1 change: 1 addition & 0 deletions suite-common/message-system/src/messageSystemTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const Feature = {
ethClaim: 'eth.staking.claim',
firmwareRevisionCheck: 'security.firmware.check',
firmwareHashCheck: 'security.firmware.hashCheck',
solanaMobile: 'mobile.solana',
} as const;

export type FeatureDomain = (typeof Feature)[keyof typeof Feature];
Expand Down
9 changes: 4 additions & 5 deletions suite-native/discovery/src/discoveryConfigSlice.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { A, F, pipe } from '@mobily/ts-belt';

import { MessageSystemRootState } from '@suite-common/message-system';
import { createWeakMapSelector, returnStableArrayIfEmpty } from '@suite-common/redux-utils';
import {
AccountsRootState,
DeviceRootState,
Expand All @@ -17,16 +19,15 @@ import {
} from '@suite-native/config';
import { type NetworkSymbol } from '@suite-common/wallet-config';
import {
createSelectIsFeatureFlagEnabled,
FeatureFlag,
FeatureFlagsRootState,
selectIsFeatureFlagEnabled,
selectIsSolanaEnabled,
} from '@suite-native/feature-flags';
import {
selectNetworkSymbolsOfAccountsWithTokensAllowed,
TokensRootState,
} from '@suite-native/tokens';
import { createWeakMapSelector, returnStableArrayIfEmpty } from '@suite-common/redux-utils';

type DiscoveryInfo = {
startTimestamp: number;
Expand Down Expand Up @@ -97,11 +98,9 @@ export const selectDiscoveryInfo = (state: DiscoveryConfigSliceRootState) =>
state.discoveryConfig.discoveryInfo;

const createMemoizedSelector = createWeakMapSelector.withTypes<
DeviceRootState & DiscoveryConfigSliceRootState & FeatureFlagsRootState
DeviceRootState & DiscoveryConfigSliceRootState & FeatureFlagsRootState & MessageSystemRootState
>();

const selectIsSolanaEnabled = createSelectIsFeatureFlagEnabled(FeatureFlag.IsSolanaEnabled);

export const selectFeatureFlagEnabledNetworkSymbols = createMemoizedSelector(
[selectIsSolanaEnabled, selectAreTestnetsEnabled],
(isSolanaEnabled, areTestnetsEnabled) => {
Expand Down
7 changes: 5 additions & 2 deletions suite-native/discovery/src/discoverySelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { TokenAddress, TokenSymbol } from '@suite-common/wallet-types';
import { isFirmwareVersionSupported } from '@suite-native/device';
import { FeatureFlagsRootState } from '@suite-native/feature-flags';
import { StaticSessionId } from '@trezor/connect';
import { MessageSystemRootState } from '@suite-common/message-system';

import {
DiscoveryConfigSliceRootState,
Expand Down Expand Up @@ -106,7 +107,8 @@ export const selectNetworksWithUnfinishedDiscovery = (
state: DeviceRootState &
AccountsRootState &
FeatureFlagsRootState &
DiscoveryConfigSliceRootState,
DiscoveryConfigSliceRootState &
MessageSystemRootState,
forcedAreTestnetsEnabled?: boolean,
availableCardanoDerivations?: AccountType[],
) => {
Expand All @@ -132,7 +134,8 @@ export const selectShouldRunDiscoveryForDevice = (
state: DeviceRootState &
AccountsRootState &
FeatureFlagsRootState &
DiscoveryConfigSliceRootState,
DiscoveryConfigSliceRootState &
MessageSystemRootState,
) => {
// no discovery for PortfolioTracker ever
const isPortfolioTrackerDevice = selectIsPortfolioTrackerDevice(state);
Expand Down
18 changes: 14 additions & 4 deletions suite-native/feature-flags/src/featureFlagsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export const FeatureFlag = {
IsDeviceConnectEnabled: 'isDeviceConnectEnabled',
IsRippleSendEnabled: 'isRippleSendEnabled',
IsCardanoSendEnabled: 'isCardanoSendEnabled',
IsSolanaSendEnabled: 'isSolanaSendEnabled',
IsRegtestEnabled: 'isRegtestEnabled',
IsSolanaEnabled: 'IsSolanaEnabled',
IsSolanaEnabledByRemote: 'IsSolanaEnabledByRemote', // should be updated only via message system
IsConnectPopupEnabled: 'IsConnectPopupEnabled',
} as const;
export type FeatureFlag = (typeof FeatureFlag)[keyof typeof FeatureFlag];
Expand All @@ -24,19 +24,19 @@ export const featureFlagsInitialState: FeatureFlagsState = {
[FeatureFlag.IsDeviceConnectEnabled]: isAndroid() || isDebugEnv(),
[FeatureFlag.IsRippleSendEnabled]: isAndroid() && isDevelopOrDebugEnv(),
[FeatureFlag.IsCardanoSendEnabled]: isAndroid() && isDevelopOrDebugEnv(),
[FeatureFlag.IsSolanaSendEnabled]: isAndroid() && isDevelopOrDebugEnv(),
[FeatureFlag.IsRegtestEnabled]: isDebugEnv() || isDetoxTestBuild(),
[FeatureFlag.IsSolanaEnabled]: false,
[FeatureFlag.IsSolanaEnabledByRemote]: false,
[FeatureFlag.IsConnectPopupEnabled]: isDevelopOrDebugEnv(),
};

export const featureFlagsPersistedKeys: Array<keyof FeatureFlagsState> = [
FeatureFlag.IsDeviceConnectEnabled,
FeatureFlag.IsRippleSendEnabled,
FeatureFlag.IsCardanoSendEnabled,
FeatureFlag.IsSolanaSendEnabled,
FeatureFlag.IsRegtestEnabled,
FeatureFlag.IsSolanaEnabled,
FeatureFlag.IsSolanaEnabledByRemote,
FeatureFlag.IsConnectPopupEnabled,
];

Expand All @@ -47,6 +47,12 @@ export const featureFlagsSlice = createSlice({
toggleFeatureFlag: (state, { payload }: PayloadAction<{ featureFlag: FeatureFlag }>) => {
state[payload.featureFlag] = !state[payload.featureFlag];
},
setFeatureFlag: (
state,
{ payload }: PayloadAction<{ featureFlag: FeatureFlag; value: boolean }>,
) => {
state[payload.featureFlag] = payload.value;
},
},
});

Expand All @@ -57,5 +63,9 @@ export const createSelectIsFeatureFlagEnabled =
export const selectIsFeatureFlagEnabled = (state: FeatureFlagsRootState, key: FeatureFlag) =>
state.featureFlags[key];

export const { toggleFeatureFlag } = featureFlagsSlice.actions;
export const selectIsSolanaEnabled = (state: FeatureFlagsRootState) =>
state.featureFlags[FeatureFlag.IsSolanaEnabledByRemote] ||
state.featureFlags[FeatureFlag.IsSolanaEnabled];

export const { toggleFeatureFlag, setFeatureFlag } = featureFlagsSlice.actions;
export const featureFlagsReducer = featureFlagsSlice.reducer;
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { A, G } from '@mobily/ts-belt';

import { Box, Button, PictogramTitleHeader, VStack } from '@suite-native/atoms';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { messageSystemActions, selectActiveFeatureMessages } from '@suite-common/message-system';
import { Variant } from '@suite-common/suite-types';
import { messageSystemActions } from '@suite-common/message-system';
import { Translation } from '@suite-native/intl';
import { useOpenLink } from '@suite-native/link';

Expand Down Expand Up @@ -35,14 +34,13 @@ const buttonsWrapperStyle = prepareNativeStyle(_ => ({
width: '100%',
}));

export const FeatureMessageScreen = () => {
export const KillswitchMessageScreen = () => {
const dispatch = useDispatch();
const openLink = useOpenLink();
const { applyStyle } = useNativeStyles();

const killswitch = A.head(useSelector(selectActiveKillswitchMessages));

const { applyStyle } = useNativeStyles();

if (!killswitch) return null;

const {
Expand Down
1 change: 1 addition & 0 deletions suite-native/message-system/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './messageSystemMiddleware';
export * from './components/MessageSystemBannerRenderer';
export * from './components/KillswitchMessageScreen';
export * from './selectors';
12 changes: 12 additions & 0 deletions suite-native/message-system/src/messageSystemMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
selectDeviceEnabledDiscoveryNetworkSymbols,
toggleEnabledDiscoveryNetworkSymbol,
} from '@suite-native/discovery';
import { FeatureFlag, setFeatureFlag } from '@suite-native/feature-flags';

import { selectIsSolanaFeatureEnabled } from './selectors';

const isAnyOfMessageSystemAffectingActions = isAnyOf(
messageSystemActions.fetchSuccessUpdate,
Expand Down Expand Up @@ -41,6 +44,15 @@ export const messageSystemMiddleware = createMiddleware((action, { next, dispatc
const categorizedValidMessages = categorizeMessages(validMessages);

dispatch(messageSystemActions.updateValidMessages(categorizedValidMessages));

const isSolanaRemoteFeatureEnabled = selectIsSolanaFeatureEnabled(getState());

dispatch(
setFeatureFlag({
featureFlag: FeatureFlag.IsSolanaEnabledByRemote,
value: isSolanaRemoteFeatureEnabled,
}),
);
}

return action;
Expand Down
21 changes: 17 additions & 4 deletions suite-native/message-system/src/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { createMemoizedSelector, selectActiveFeatureMessages } from '@suite-common/message-system';
import {
createMemoizedSelector,
Feature,
MessageSystemRootState,
selectActiveFeatureMessages,
selectIsFeatureEnabled,
} from '@suite-common/message-system';

export const selectActiveKillswitchMessages = createMemoizedSelector(
[selectActiveFeatureMessages],
messages =>
messages.filter(
m => m.feature?.filter(item => item.domain === 'killswitch' && item.flag) ?? false,
),
messages.filter(m => {
const killswitchFeatures = m.feature?.filter(
item => item.domain === 'killswitch' && item?.flag,
);

return (killswitchFeatures?.length ?? 0) > 0;
}),
);

export const selectIsSolanaFeatureEnabled = (state: MessageSystemRootState) =>
selectIsFeatureEnabled(state, Feature.solanaMobile);
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@suite-native/navigation';
import { Translation } from '@suite-native/intl';
import { FeatureFlag, FeatureFlagsRootState, useFeatureFlag } from '@suite-native/feature-flags';
import { MessageSystemRootState } from '@suite-common/message-system';

import { AccountDetailGraph } from './AccountDetailGraph';
import { AccountDetailCryptoValue } from './AccountDetailCryptoValue';
Expand Down Expand Up @@ -100,8 +101,9 @@ export const TransactionListHeader = memo(
const isTestnetAccount = useSelector((state: AccountsRootState) =>
selectIsTestnetAccount(state, accountKey),
);
const isNetworkSendFlowEnabled = useSelector((state: FeatureFlagsRootState) =>
selectIsNetworkSendFlowEnabled(state, account?.symbol),
const isNetworkSendFlowEnabled = useSelector(
(state: FeatureFlagsRootState & MessageSystemRootState) =>
selectIsNetworkSendFlowEnabled(state, account?.symbol),
);
const isPortfolioTrackerDevice = useSelector(selectIsPortfolioTrackerDevice);

Expand Down
16 changes: 9 additions & 7 deletions suite-native/module-accounts-management/src/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { D, pipe } from '@mobily/ts-belt';

import { MessageSystemRootState } from '@suite-common/message-system';
import { type NetworkSymbol, getNetworkType, networks } from '@suite-common/wallet-config';
import {
FeatureFlagsRootState,
selectIsFeatureFlagEnabled,
FeatureFlag,
selectIsSolanaEnabled,
} from '@suite-native/feature-flags';

const PRODUCTION_SEND_COINS_WHITELIST = pipe(
Expand All @@ -14,13 +16,13 @@ const PRODUCTION_SEND_COINS_WHITELIST = pipe(
);

export const selectIsNetworkSendFlowEnabled = (
state: FeatureFlagsRootState,
symbol?: NetworkSymbol,
state: FeatureFlagsRootState & MessageSystemRootState,
networkSymbol?: NetworkSymbol,
) => {
if (!symbol) return false;
const networkType = getNetworkType(symbol);
if (!networkSymbol) return false;
const networkType = getNetworkType(networkSymbol);

if (PRODUCTION_SEND_COINS_WHITELIST.includes(symbol)) return true;
if (PRODUCTION_SEND_COINS_WHITELIST.includes(networkSymbol)) return true;

const isRippleSendEnabled = selectIsFeatureFlagEnabled(state, FeatureFlag.IsRippleSendEnabled);

Expand All @@ -33,9 +35,9 @@ export const selectIsNetworkSendFlowEnabled = (

if (isCardanoSendEnabled && networkType === 'cardano') return true;

const isSolanaSendEnabled = selectIsFeatureFlagEnabled(state, FeatureFlag.IsSolanaSendEnabled);
const isSolanaEnabled = selectIsSolanaEnabled(state);

if (isSolanaSendEnabled && networkType === 'solana') return true;
if (isSolanaEnabled && networkType === 'solana') return true;

return false;
};
30 changes: 18 additions & 12 deletions suite-native/module-dev-utils/src/components/FeatureFlags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ const featureFlagsTitleMap = {
[FeatureFlagEnum.IsDeviceConnectEnabled]: 'Connect device',
[FeatureFlagEnum.IsRippleSendEnabled]: 'Ripple send',
[FeatureFlagEnum.IsCardanoSendEnabled]: 'Cardano send',
[FeatureFlagEnum.IsSolanaSendEnabled]: 'Solana send',
[FeatureFlagEnum.IsRegtestEnabled]: 'Regtest',
[FeatureFlagEnum.IsSolanaEnabled]: 'Solana',
[FeatureFlagEnum.IsSolanaEnabledByRemote]: 'Hidden in UI',
[FeatureFlagEnum.IsConnectPopupEnabled]: 'Connect Popup',
} as const satisfies Record<FeatureFlagEnum, string>;

Expand All @@ -22,15 +22,21 @@ const FeatureFlag = ({ featureFlag }: { featureFlag: FeatureFlagEnum }) => {
);
};

export const FeatureFlags = () => (
<Card>
<VStack spacing="sp8">
<Text variant="titleSmall">Feature Flags</Text>
<VStack>
{Object.values(FeatureFlagEnum).map(featureFlag => (
<FeatureFlag key={featureFlag} featureFlag={featureFlag} />
))}
export const FeatureFlags = () => {
const adjustableFeatureFlags = Object.values(FeatureFlagEnum).filter(
f => f !== FeatureFlagEnum.IsSolanaEnabledByRemote,
);

return (
<Card>
<VStack spacing="sp8">
<Text variant="titleSmall">Feature Flags</Text>
<VStack>
{adjustableFeatureFlags.map(featureFlag => (
<FeatureFlag key={featureFlag} featureFlag={featureFlag} />
))}
</VStack>
</VStack>
</VStack>
</Card>
);
</Card>
);
};

0 comments on commit 59d8d19

Please sign in to comment.