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
12 changes: 12 additions & 0 deletions .github/workflows/performance-test-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ on:
type: string
default: test
description: 'Sentry DSN target: test or real'
build_variant:
required: false
type: string
default: rc
description: 'Build variant for app artifacts (rc or exp)'
secrets:
BROWSERSTACK_USERNAME:
required: true
Expand Down Expand Up @@ -163,6 +168,12 @@ jobs:
SENTRY_REAL_DSN="${{ secrets.MM_SENTRY_DSN }}"
SELECTED_SENTRY_DSN=""
SENTRY_ENVIRONMENT="github-actions-performance-e2e"
BUILD_VARIANT="${{ inputs.build_variant }}"

if [[ "$BUILD_VARIANT" != "rc" && "$BUILD_VARIANT" != "exp" ]]; then
echo "❌ Invalid build_variant '$BUILD_VARIANT'. Expected 'rc' or 'exp'."
exit 1
fi

# Validate that we have a BrowserStack URL
if [ -z "${{ inputs.browserstack_app_url }}" ]; then
Expand Down Expand Up @@ -210,6 +221,7 @@ jobs:
echo "E2E_PERFORMANCE_SENTRY_DSN=$SELECTED_SENTRY_DSN"
echo "E2E_PERFORMANCE_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT"
echo "E2E_PERFORMANCE_SENTRY_RELEASE=${{ github.sha }}"
echo "E2E_PERFORMANCE_BUILD_VARIANT=$BUILD_VARIANT"
echo "DISABLE_VIDEO_DOWNLOAD=true"
} >> "$GITHUB_ENV"

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/run-performance-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ jobs:
platform: android
build_type: onboarding
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.android_matrix }}
browserstack_app_url: ${{ needs.trigger-android-dual-versions.outputs.without-srp-browserstack-url || inputs.browserstack_app_url_android_onboarding }}
app_version: ${{ needs.trigger-android-dual-versions.outputs.without-srp-version || 'Manual-Input' }}
Expand All @@ -257,6 +258,7 @@ jobs:
platform: ios
build_type: onboarding
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.ios_matrix }}
browserstack_app_url: ${{ needs.trigger-ios-dual-versions.outputs.without-srp-browserstack-url || inputs.browserstack_app_url_ios_onboarding }}
app_version: ${{ needs.trigger-ios-dual-versions.outputs.without-srp-version || 'Manual-Input' }}
Expand Down Expand Up @@ -295,6 +297,7 @@ jobs:
platform: android
build_type: imported-wallet
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.android_matrix }}
browserstack_app_url: ${{ needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url || inputs.browserstack_app_url_android_imported_wallet }}
app_version: ${{ needs.trigger-android-dual-versions.outputs.with-srp-version || 'Manual-Input' }}
Expand All @@ -318,6 +321,7 @@ jobs:
platform: ios
build_type: imported-wallet
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.ios_matrix }}
browserstack_app_url: ${{ needs.trigger-ios-dual-versions.outputs.with-srp-browserstack-url || inputs.browserstack_app_url_ios_imported_wallet }}
app_version: ${{ needs.trigger-ios-dual-versions.outputs.with-srp-version || 'Manual-Input' }}
Expand Down Expand Up @@ -377,6 +381,7 @@ jobs:
platform: android
build_type: mm-connect
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.android_matrix }}
browserstack_app_url: ${{ needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url || inputs.browserstack_app_url_android_imported_wallet }}
app_version: ${{ needs.trigger-android-dual-versions.outputs.with-srp-version || 'Manual-Input' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ export const BridgeTokenSelector: React.FC = () => {
>
<ButtonIcon
iconName={IconName.Info}
size={ButtonIconSize.Md}
size={ButtonIconSize.Sm}
onPress={() => handleInfoButtonPress(item)}
iconProps={{ color: IconColor.IconAlternative }}
/>
Expand Down
8 changes: 4 additions & 4 deletions app/components/UI/Bridge/components/TokenSelectorItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ const FiatBalanceView = ({

return (
<Text
variant={TextVariant.BodyMD}
variant={TextVariant.BodySM}
color={TextColor.Alternative}
numberOfLines={1}
>
Expand Down Expand Up @@ -272,7 +272,7 @@ export const TokenSelectorItem: React.FC<TokenSelectorItemProps> = ({
>
<Box style={styles.tokenMainInfo} gap={4}>
<Text
variant={TextVariant.BodyMDMedium}
variant={TextVariant.BodyMD}
numberOfLines={1}
ellipsizeMode="tail"
>
Expand All @@ -297,7 +297,7 @@ export const TokenSelectorItem: React.FC<TokenSelectorItemProps> = ({
<View style={styles.skeleton} />
) : (
<Text
variant={TextVariant.BodyMDMedium}
variant={TextVariant.BodyMD}
color={TextColor.Default}
numberOfLines={1}
style={styles.rightValue}
Expand All @@ -314,7 +314,7 @@ export const TokenSelectorItem: React.FC<TokenSelectorItemProps> = ({
justifyContent={JustifyContent.spaceBetween}
>
<Text
variant={TextVariant.BodyMD}
variant={TextVariant.BodySM}
color={TextColor.Alternative}
numberOfLines={1}
ellipsizeMode="tail"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { renderHookWithProvider } from '../../../../util/test/renderWithProvider
import { useDefaultPayWithTokenWhenNoPerpsBalance } from './useDefaultPayWithTokenWhenNoPerpsBalance';
import type { PerpsToken } from '@metamask/perps-controller';

jest.mock('react-native-device-info', () => ({
getVersion: jest.fn().mockReturnValue('7.70.0'),
}));

jest.mock('./usePerpsPaymentTokens', () => ({
usePerpsPaymentTokens: jest.fn(() => []),
}));
Expand All @@ -18,13 +22,15 @@ function getState(
allowlistAssets?: string[];
isTestnet?: boolean;
activeProvider?: 'hyperliquid' | 'myx' | 'aggregated';
defaultPayTokenWhenNoBalanceEnabled?: boolean;
} = {},
) {
const {
perpsAccount = { availableBalance: '0' },
allowlistAssets = [],
isTestnet = false,
activeProvider,
defaultPayTokenWhenNoBalanceEnabled = true,
} = overrides;
return {
engine: {
Expand All @@ -37,6 +43,10 @@ function getState(
RemoteFeatureFlagController: {
remoteFeatureFlags: {
perpsPayWithAnyTokenAllowlistAssets: allowlistAssets,
perpsDefaultPayTokenWhenNoBalanceEnabled:
defaultPayTokenWhenNoBalanceEnabled
? { enabled: true, minimumVersion: '0.0.0' }
: { enabled: false, minimumVersion: '0.0.0' },
},
},
},
Expand Down Expand Up @@ -80,6 +90,27 @@ describe('useDefaultPayWithTokenWhenNoPerpsBalance', () => {
expect(result.current).toBeNull();
});

it('returns null when feature flag is disabled', () => {
mockUsePerpsPaymentTokens.mockReturnValue([
{
address: '0xusdc',
chainId: '0xa4b1',
symbol: 'USDC',
balanceFiat: 'US$500',
decimals: 6,
},
] as PerpsToken[]);

const { result } = runHook(
getState({
allowlistAssets: ['0xa4b1.0xusdc'],
defaultPayTokenWhenNoBalanceEnabled: false,
}),
);

expect(result.current).toBeNull();
});

it('returns null when allowlist is empty', () => {
const { result } = runHook(getState({ allowlistAssets: [] }));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
PERPS_MIN_BALANCE_THRESHOLD,
PROVIDER_CONFIG,
} from '../constants/perpsConfig';
import { selectPerpsPayWithAnyTokenAllowlistAssets } from '../selectors/featureFlags';
import {
selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag,
selectPerpsPayWithAnyTokenAllowlistAssets,
} from '../selectors/featureFlags';
import {
selectPerpsAccountState,
selectPerpsProvider,
Expand All @@ -21,6 +24,9 @@ import { usePerpsPaymentTokens } from './usePerpsPaymentTokens';
* Otherwise returns null (caller should default to "Perps balance").
*/
export function useDefaultPayWithTokenWhenNoPerpsBalance(): PerpsSelectedPaymentToken | null {
const featureEnabled = useSelector(
selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag,
);
const perpsAccount = useSelector(selectPerpsAccountState);
const allowlistAssets = useSelector(
selectPerpsPayWithAnyTokenAllowlistAssets,
Expand All @@ -30,6 +36,9 @@ export function useDefaultPayWithTokenWhenNoPerpsBalance(): PerpsSelectedPayment
const paymentTokens = usePerpsPaymentTokens();

return useMemo(() => {
if (!featureEnabled) {
return null;
}
const availableBalance = Number.parseFloat(
perpsAccount?.availableBalance?.toString() ?? '0',
);
Expand Down Expand Up @@ -82,6 +91,7 @@ export function useDefaultPayWithTokenWhenNoPerpsBalance(): PerpsSelectedPayment
description: top.symbol,
};
}, [
featureEnabled,
perpsAccount?.availableBalance,
allowlistAssets,
activeProvider,
Expand Down
1 change: 1 addition & 0 deletions app/components/UI/Perps/mocks/remoteFeatureFlagMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export const mockedPerpsFeatureFlagsEnabledState: Record<
perpsOrderBookEnabled: mockEnabledPerpsLDFlag,
perpsFeedbackEnabled: mockEnabledPerpsLDFlag,
perpsMyxProviderEnabled: mockEnabledPerpsLDFlag,
perpsDefaultPayTokenWhenNoBalanceEnabled: mockEnabledPerpsLDFlag,
};
94 changes: 94 additions & 0 deletions app/components/UI/Perps/selectors/featureFlags/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
selectPerpsOrderBookEnabledFlag,
selectPerpsButtonColorTestVariant,
selectHip3ConfigVersion,
selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag,
selectPerpsFeedbackEnabledFlag,
selectPerpsTradeWithAnyTokenEnabledFlag,
selectPerpsPayWithAnyTokenAllowlistAssets,
Expand Down Expand Up @@ -894,6 +895,99 @@ describe('Perps Feature Flag Selectors', () => {
});
});

describe('selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag', () => {
const createEmptyFlagsState = () => ({
engine: {
backgroundState: {
RemoteFeatureFlagController: {
remoteFeatureFlags: {},
cacheTimestamp: 0,
},
},
},
});

it('returns true when remote flag is not set (default)', () => {
const result = selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag(
createEmptyFlagsState(),
);
expect(result).toBe(true);
});

it('uses remote flag when valid and enabled', () => {
mockHasMinimumRequiredVersion.mockReturnValue(true);

const stateWithEnabledRemoteFlag = {
engine: {
backgroundState: {
RemoteFeatureFlagController: {
remoteFeatureFlags: {
perpsDefaultPayTokenWhenNoBalanceEnabled: {
enabled: true,
minimumVersion: '1.0.0',
},
},
cacheTimestamp: 0,
},
},
},
};

const result = selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag(
stateWithEnabledRemoteFlag,
);
expect(result).toBe(true);
});

it('uses remote flag when valid but disabled', () => {
mockHasMinimumRequiredVersion.mockReturnValue(true);

const stateWithDisabledRemoteFlag = {
engine: {
backgroundState: {
RemoteFeatureFlagController: {
remoteFeatureFlags: {
perpsDefaultPayTokenWhenNoBalanceEnabled: {
enabled: false,
minimumVersion: '1.0.0',
},
},
cacheTimestamp: 0,
},
},
},
};

const result = selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag(
stateWithDisabledRemoteFlag,
);
expect(result).toBe(false);
});

it('returns true when remote flag is invalid (default fallback)', () => {
const stateWithInvalidRemoteFlag = {
engine: {
backgroundState: {
RemoteFeatureFlagController: {
remoteFeatureFlags: {
perpsDefaultPayTokenWhenNoBalanceEnabled: {
enabled: 'invalid',
minimumVersion: 123,
},
},
cacheTimestamp: 0,
},
},
},
};

const result = selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag(
stateWithInvalidRemoteFlag,
);
expect(result).toBe(true);
});
});

describe('selectPerpsRewardsReferralCodeEnabledFlag', () => {
it('returns false when flag is not set', () => {
const result = selectPerpsRewardsReferralCodeEnabledFlag(
Expand Down
17 changes: 17 additions & 0 deletions app/components/UI/Perps/selectors/featureFlags/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,20 @@ export const selectPerpsMYXProviderEnabledFlag = createSelector(
selectRemoteFeatureFlags,
(remoteFeatureFlags) => resolvePerpsMyxProviderEnabled(remoteFeatureFlags),
);

/**
* Selector for default pay token when no perps balance feature flag.
* When enabled: preselect allowlist token with highest balance in Pay row when user has no perps balance,
* and show "Add funds" CTA on market details when no token can be preselected.
* When disabled: no default token preselection and no Add funds CTA (legacy behavior).
* Controlled only by remote flag; when remote is missing or invalid, defaults to true.
*
* @returns boolean - true if feature is enabled, false otherwise
*/
export const selectPerpsDefaultPayTokenWhenNoBalanceEnabledFlag =
createSelector(selectRemoteFeatureFlags, (remoteFeatureFlags) => {
const remoteFlag =
remoteFeatureFlags?.perpsDefaultPayTokenWhenNoBalanceEnabled as unknown as VersionGatedFeatureFlag;

return validatedVersionGatedFeatureFlag(remoteFlag) ?? true;
});
Loading
Loading