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
31 changes: 31 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ module.exports = {
},
{
files: [
'app/components/hooks/useIsOriginalNativeTokenSymbol/**/*.{js,jsx,ts,tsx}',
'app/components/hooks/useTokenBalancesController/**/*.{js,jsx,ts,tsx}',
'app/components/hooks/useTokenBalance.tsx',
'app/components/hooks/useTokensData/**/*.{js,jsx,ts,tsx}',
'app/components/hooks/useSafeChains.ts',
'app/components/UI/Card/**/*.{js,jsx,ts,tsx}',
'app/components/Snaps/**/*.{js,jsx,ts,tsx}',
'app/components/UI/Predict/**/*.{js,jsx,ts,tsx}',
Expand All @@ -189,6 +194,32 @@ module.exports = {
'app/components/UI/Perps/**/*.{js,jsx,ts,tsx}',
'app/components/UI/Earn/**/*.{js,jsx,ts,tsx}',
'app/components/UI/Stake/**/*.{js,jsx,ts,tsx}',
// Assets team has a large number of folder ownership areas,
'app/components/UI/Assets/**/*.{js,jsx,ts,tsx}',
'app/components/UI/Tokens/**/*.{js,jsx,ts,tsx}',
'app/components/UI/AssetOverview/**/*.{js,jsx,ts,tsx}',
'app/components/UI/Collectibles/**/*.{js,jsx,ts,tsx}',
'app/components/UI/CollectibleContractElement/**/*.{js,jsx,ts,tsx}',
'app/components/UI/CollectibleContractInformation/**/*.{js,jsx,ts,tsx}',
'app/components/UI/CollectibleContractOverview/**/*.{js,jsx,ts,tsx}',
'app/components/UI/CollectibleContracts/**/*.{js,jsx,ts,tsx}',
'app/components/UI/CollectibleDetectionModal/**/*.{js,jsx,ts,tsx}',
'app/components/UI/CollectibleMedia/**/*.{js,jsx,ts,tsx}',
'app/components/UI/CollectibleModal/**/*.{js,jsx,ts,tsx}',
'app/components/UI/CollectibleOverview/**/*.{js,jsx,ts,tsx}',
'app/components/UI/ConfirmAddAsset/**/*.{js,jsx,ts,tsx}',
'app/components/UI/DeFiPositions/**/*.{js,jsx,ts,tsx}',
'app/components/UI/TokenDetails/**/*.{js,jsx,ts,tsx}',
'app/components/Views/AddAsset/**/*.{js,jsx,ts,tsx}',
'app/components/Views/Asset/**/*.{js,jsx,ts,tsx}',
'app/components/Views/AssetDetails/**/*.{js,jsx,ts,tsx}',
'app/components/Views/AssetHideConfirmation/**/*.{js,jsx,ts,tsx}',
'app/components/Views/AssetOptions/**/*.{js,jsx,ts,tsx}',
'app/components/Views/Collectible/**/*.{js,jsx,ts,tsx}',
'app/components/Views/CollectibleView/**/*.{js,jsx,ts,tsx}',
'app/components/Views/DetectedTokens/**/*.{js,jsx,ts,tsx}',
'app/components/Views/NFTAutoDetectionModal/**/*.{js,jsx,ts,tsx}',
'app/components/Views/NftDetails/**/*.{js,jsx,ts,tsx}',
],
rules: {
'@metamask/design-tokens/color-no-hex': 'error',
Expand Down
36 changes: 28 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ on:
type: boolean
default: false
source_branch:
description: 'Branch, tag, or SHA to build'
description: >-
Branch, tag, or SHA. If skip_version_bump is false: base branch for the version bump (empty uses github.ref_name).
If skip_version_bump is true: ref for prepare, node-modules setup, and build checkout (empty uses the default triggering ref).
required: false
type: string
default: ''
ref:
description: 'Git ref to checkout when skip_version_bump is true. Defaults to the triggering event ref.'
upload_to_sentry:
description: 'If true, enable Sentry CLI upload of JS source maps and native debug symbols during the build'
required: false
type: string
default: ''
type: boolean
default: false
workflow_dispatch:
inputs:
build_name:
Expand Down Expand Up @@ -50,6 +52,11 @@ on:
required: false
type: boolean
default: false
upload_to_sentry:
description: 'Upload JS source maps and native debug symbols to Sentry during the build (requires Sentry auth in the build environment)'
required: false
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -157,13 +164,13 @@ jobs:
submodules: recursive

- uses: actions/checkout@v4
if: ${{ inputs.skip_version_bump && inputs.ref != '' }}
if: ${{ inputs.skip_version_bump && inputs.source_branch != '' }}
with:
ref: ${{ inputs.ref }}
ref: ${{ inputs.source_branch }}
submodules: recursive

- uses: actions/checkout@v4
if: ${{ inputs.skip_version_bump && inputs.ref == '' }}
if: ${{ inputs.skip_version_bump && inputs.source_branch == '' }}
with:
submodules: recursive

Expand Down Expand Up @@ -313,6 +320,19 @@ jobs:
cat ios/.xcode.env.local
node --version

# Sentry: build.sh defaults SENTRY_DISABLE_AUTO_UPLOAD=true; set false only when opted in.
- name: Configure Sentry upload
env:
UPLOAD_TO_SENTRY: ${{ inputs.upload_to_sentry }}
run: |
if [ "$UPLOAD_TO_SENTRY" = "true" ]; then
echo "SENTRY_DISABLE_AUTO_UPLOAD=false" >> "$GITHUB_ENV"
echo "✅ Sentry auto-upload enabled for this build"
else
echo "SENTRY_DISABLE_AUTO_UPLOAD=true" >> "$GITHUB_ENV"
echo "ℹ️ Sentry auto-upload disabled"
fi

# Build with retry logic. Timeouts: 55min per attempt, 115min total for step, 120min job
- name: Build ${{ matrix.platform }}
timeout-minutes: 115
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/nightly-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
build_name: main-exp
platform: both
skip_version_bump: true
ref: ${{ needs.bump-version-exp.outputs.commit-hash }}
source_branch: ${{ needs.bump-version-exp.outputs.commit-hash }}
secrets: inherit

build-rc:
Expand All @@ -87,7 +87,7 @@ jobs:
build_name: main-rc
platform: both
skip_version_bump: true
ref: ${{ needs.bump-version-rc.outputs.commit-hash }}
source_branch: ${{ needs.bump-version-rc.outputs.commit-hash }}
secrets: inherit

upload-exp-testflight:
Expand Down
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ android {
applicationId "io.metamask"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionName "7.72.0"
versionCode 3607
versionName "7.71.0"
versionCode 4138
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders.MM_BRANCH_KEY_TEST = "$System.env.MM_BRANCH_KEY_TEST"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const mockCreateEventBuilder = jest.fn((event) => ({
build: jest.fn(() => event),
}));

jest.mock('../../../../components/hooks/useMetrics', () => ({
useMetrics: () => ({
jest.mock('../../../../components/hooks/useAnalytics/useAnalytics', () => ({
useAnalytics: () => ({
trackEvent: mockTrackEvent,
createEventBuilder: mockCreateEventBuilder,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { strings } from '../../../../../locales/i18n';
import { MetaMetricsEvents } from '../../../../core/Analytics';
import { IMetaMetricsEvent } from '../../../../core/Analytics/MetaMetrics.types';
import Routes from '../../../../constants/navigation/Routes';
import { useMetrics } from '../../../../components/hooks/useMetrics';
import { useAnalytics } from '../../../../components/hooks/useAnalytics/useAnalytics';
import { AddAccountBottomSheetSelectorsIDs } from '../../../../components/Views/AddAccountActions/AddAccountBottomSheet.testIds';

// Types
Expand All @@ -31,7 +31,7 @@ const MultichainAddWalletActions = ({
onBack,
}: MultichainAddWalletActionsProps) => {
const { navigate } = useNavigation();
const { trackEvent, createEventBuilder } = useMetrics();
const { trackEvent, createEventBuilder } = useAnalytics();

const createActionHandler = useCallback(
(config: Omit<ActionConfig, 'isVisible'>) => () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { createAccountConnectNavDetails } from '../../Views/AccountConnect';
import { useSelector } from 'react-redux';
import { MetaMetricsEvents } from '../../../core/Analytics';
import { render } from '@testing-library/react-native';
import { useMetrics } from '../../../components/hooks/useMetrics';
import { MetricsEventBuilder } from '../../../core/Analytics/MetricsEventBuilder';
import { useAnalytics } from '../../../components/hooks/useAnalytics/useAnalytics';
import { AnalyticsEventBuilder } from '../../../util/analytics/AnalyticsEventBuilder';
import { createMockUseAnalyticsHook } from '../../../util/test/analyticsMock';
import useOriginSource from '../../hooks/useOriginSource';
import {
Caip25EndowmentPermissionName,
Expand All @@ -21,7 +22,7 @@ import { selectPendingApprovals } from '../../../selectors/approvalController';
import { selectAccountsLength } from '../../../selectors/accountTrackerController';

jest.mock('../../Views/confirmations/hooks/useApprovalRequest');
jest.mock('../../../components/hooks/useMetrics');
jest.mock('../../../components/hooks/useAnalytics/useAnalytics');

jest.mock('../../Views/AccountConnect', () => ({
createAccountConnectNavDetails: jest.fn(),
Expand Down Expand Up @@ -116,23 +117,22 @@ const mockAccountsLength = (accountsLength: number) => {

const mockTrackEvent = jest.fn();

(useMetrics as jest.MockedFn<typeof useMetrics>).mockReturnValue({
trackEvent: mockTrackEvent,
createEventBuilder: MetricsEventBuilder.createEventBuilder,
enable: jest.fn(),
addTraitsToUser: jest.fn(),
createDataDeletionTask: jest.fn(),
checkDataDeleteStatus: jest.fn(),
getDeleteRegulationCreationDate: jest.fn(),
getDeleteRegulationId: jest.fn(),
isDataRecorded: jest.fn(),
isEnabled: jest.fn(),
getMetaMetricsId: jest.fn(),
});
jest.mocked(useAnalytics).mockReturnValue(
createMockUseAnalyticsHook({
trackEvent: mockTrackEvent,
createEventBuilder: AnalyticsEventBuilder.createEventBuilder,
}),
);

describe('PermissionApproval', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.mocked(useAnalytics).mockReturnValue(
createMockUseAnalyticsHook({
trackEvent: mockTrackEvent,
createEventBuilder: AnalyticsEventBuilder.createEventBuilder,
}),
);
(useOriginSource as jest.Mock).mockImplementation(() => 'IN_APP_BROWSER');
(
getAllScopesFromPermission as jest.MockedFn<
Expand Down Expand Up @@ -200,7 +200,7 @@ describe('PermissionApproval', () => {

render(<PermissionApproval navigation={navigationMock} />);

const expectedEvent = MetricsEventBuilder.createEventBuilder(
const expectedEvent = AnalyticsEventBuilder.createEventBuilder(
MetaMetricsEvents.CONNECT_REQUEST_STARTED,
)
.addProperties({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics';
import { createAccountConnectNavDetails } from '../../Views/AccountConnect';
import { useSelector } from 'react-redux';
import { selectAccountsLength } from '../../../selectors/accountTrackerController';
import { useMetrics } from '../../../components/hooks/useMetrics';
import { useAnalytics } from '../../../components/hooks/useAnalytics/useAnalytics';
import useOriginSource from '../../hooks/useOriginSource';
import {
Caip25EndowmentPermissionName,
Expand All @@ -22,7 +22,7 @@ export interface PermissionApprovalProps {
}

const PermissionApproval = (props: PermissionApprovalProps) => {
const { trackEvent, createEventBuilder } = useMetrics();
const { trackEvent, createEventBuilder } = useAnalytics();
const pendingApprovals = useSelector(selectPendingApprovals, isEqual);
const { approvalRequest } = useApprovalRequest();
const totalAccounts = useSelector(selectAccountsLength);
Expand Down
4 changes: 2 additions & 2 deletions app/components/UI/AccountRightButton/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jest.mock('@react-navigation/native', () => ({
useRoute: () => ({ params: {} }),
}));

jest.mock('../../../components/hooks/useMetrics', () => ({
useMetrics: () => ({
jest.mock('../../../components/hooks/useAnalytics/useAnalytics', () => ({
useAnalytics: () => ({
trackEvent: mockTrackEvent,
createEventBuilder: mockCreateEventBuilder,
}),
Expand Down
4 changes: 2 additions & 2 deletions app/components/UI/AccountRightButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import Routes from '../../../constants/navigation/Routes';
import { MetaMetricsEvents } from '../../../core/Analytics';
import { AccountOverviewSelectorsIDs } from './AccountOverview.testIds';
import { useMetrics } from '../../../components/hooks/useMetrics';
import { useAnalytics } from '../../../components/hooks/useAnalytics/useAnalytics';
import { useNetworkInfo } from '../../../selectors/selectedNetworkController';
import UrlParser from 'url-parse';
import { selectEvmChainId } from '../../../selectors/networkController';
Expand Down Expand Up @@ -67,7 +67,7 @@ const AccountRightButton = ({
// Placeholder ref for dismissing keyboard. Works when the focused input is within a Webview.
const placeholderInputRef = useRef<TextInput>(null);
const { navigate } = useNavigation();
const { trackEvent, createEventBuilder } = useMetrics();
const { trackEvent, createEventBuilder } = useAnalytics();
const [isKeyboardVisible, setIsKeyboardVisible] = useState<boolean>(false);

// TODO: Replace "any" with type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ jest.mock('@metamask/design-system-twrnc-preset', () => ({
},
}));

const mockTheme = {
colors: {
border: { muted: '#CCCCCC' },
primary: { default: '#0066CC' },
},
};
jest.mock('../../../../util/theme', () => ({
useTheme: () => mockTheme,
}));
jest.mock('../../../../util/theme', () => {
// Use the real mockTheme to avoid circular mock issues
const actual = jest.requireActual('../../../../util/theme');
return {
...actual,
useTheme: () => actual.mockTheme,
};
});

describe('ResourceRing', () => {
it('renders the ring icon', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ import { getMultichainTxFees } from '../../../../hooks/useMultichainTransactionD
import { useMultichainBlockExplorerTxUrl } from '../../hooks/useMultichainBlockExplorerTxUrl';
import { StatusResponse } from '@metamask/bridge-status-controller';
import { toDateFormat } from '../../../../../util/date';
import TagColored, {
TagColor,
} from '../../../../../component-library/components-temp/TagColored';
// import { renderShortAddress } from '../../../../../util/address';

const styles = StyleSheet.create({
Expand Down Expand Up @@ -99,6 +102,24 @@ interface BridgeTransactionDetailsProps {
};
}

const PaidByMetaMask = () => (
<TagColored
color={TagColor.Success}
labelProps={{
variant: TextVariant.BodySM,
style: {
textTransform: 'none',
textAlign: 'center',
bottom: 1,
fontWeight: 'normal',
},
testID: 'paid-by-metamask',
}}
>
{strings('transactions.paid_by_metamask')}
</TagColored>
);

const BridgeStatusToColorMap: Record<StatusTypes, TextColor> = {
[StatusTypes.PENDING]: TextColor.Warning,
[StatusTypes.COMPLETE]: TextColor.Success,
Expand Down Expand Up @@ -371,18 +392,24 @@ export const BridgeTransactionDetails = (
<Text variant={TextVariant.BodyMDMedium}>
{strings('bridge_transaction_details.total_gas_fee')}
</Text>
{/* TODO get solana gas fee from multiChainTx */}
{evmTotalGasFee && (
<Text>
{evmTotalGasFee}{' '}
{getNativeAssetForChainId(quote.srcChainId).symbol}
</Text>
)}
{multiChainTotalGasFee && (
<Text>
{multiChainTotalGasFee}{' '}
{getNativeAssetForChainId(quote.srcChainId).symbol}
</Text>
{evmTxMeta?.isGasFeeSponsored ? (
<PaidByMetaMask />
) : (
<>
{/* TODO get solana gas fee from multiChainTx */}
{evmTotalGasFee && (
<Text>
{evmTotalGasFee}{' '}
{getNativeAssetForChainId(quote.srcChainId).symbol}
</Text>
)}
{multiChainTotalGasFee && (
<Text>
{multiChainTotalGasFee}{' '}
{getNativeAssetForChainId(quote.srcChainId).symbol}
</Text>
)}
</>
)}
</Box>
</Box>
Expand Down
2 changes: 2 additions & 0 deletions app/components/UI/CollectibleContractOverview/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const initialState = {
favorite: false,
image: 'https://image.com/113',
isCurrentlyOwned: true,
// eslint-disable-next-line @metamask/design-tokens/color-no-hex -- false positive: '#113' is part of the NFT name, not a color literal
name: 'My Nft #113',
standard: 'ERC721',
tokenId: '113',
Expand Down Expand Up @@ -108,6 +109,7 @@ describe('CollectibleContractOverview', () => {
favorite: false,
image: 'https://image.com/113',
isCurrentlyOwned: true,
// eslint-disable-next-line @metamask/design-tokens/color-no-hex -- false positive: '#113' is part of the NFT name, not a color literal
name: 'My Nft #113',
standard: 'ERC721',
tokenId: '113',
Expand Down
Loading
Loading