Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multi chain asset list #12431

Merged
merged 110 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
3a51189
feat: cherry picking from multichain list experimental branch
vinnyhoward Nov 25, 2024
9857f4c
Merge branch 'main' of github.com:MetaMask/metamask-mobile into feat-…
vinnyhoward Nov 26, 2024
9354e71
feat: add multichain tokens autodetection feature
salimtb Nov 24, 2024
b55ee59
fix: fix hide token
salimtb Nov 25, 2024
a3fb53a
fix: fix linter
salimtb Nov 25, 2024
25e4d9f
fix: add unit test
salimtb Nov 26, 2024
bb938c9
fix: fix hide token case
salimtb Nov 26, 2024
55b163e
Merge branch 'main' of github.com:MetaMask/metamask-mobile into feat-…
vinnyhoward Nov 26, 2024
bd8fcc8
fix: add unit tests
salimtb Nov 26, 2024
c22734c
fix: clean up
salimtb Nov 26, 2024
febb235
fix: fixed tests for feature flag off and all except one for feature …
vinnyhoward Nov 27, 2024
b1205f5
fix: linting issues
vinnyhoward Nov 27, 2024
0bbc069
fix: updating tests to increase coverage
vinnyhoward Nov 27, 2024
60a2121
merged token detection branch and updated tests
vinnyhoward Nov 27, 2024
5b8756b
Merge branch 'main' into salim/multichain-detect-tokens-feat
salimtb Nov 28, 2024
b6ea346
Merge branch 'salim/multichain-detect-tokens-feat' into feat-multicha…
salimtb Nov 28, 2024
e7e4217
fix: fix linter
salimtb Nov 28, 2024
7fca372
fix: fix hide token
salimtb Nov 28, 2024
576d910
fix: increase test coverage
salimtb Nov 28, 2024
5f81279
fix: increase test coverage
salimtb Nov 28, 2024
f0db4e5
fix: add test for staking chain hook
salimtb Nov 28, 2024
32ce6a0
fix: increase test coverage
salimtb Nov 28, 2024
83dfe6c
fix: increase test coverage
salimtb Nov 28, 2024
cc24739
fix: fix snapshot
salimtb Nov 28, 2024
5f57e94
fix: increase coverage
salimtb Nov 28, 2024
16e1d24
fix: increase test coverage
salimtb Nov 28, 2024
c15d95e
fix: increase test coverage
salimtb Nov 28, 2024
3d5cf83
fix: fix block explorer
salimtb Nov 29, 2024
767f1c2
fix: fix bugs
salimtb Nov 29, 2024
71d004a
fix: clean up
salimtb Nov 29, 2024
9b8fc02
fix: clean up feature flag
salimtb Nov 29, 2024
59e1fc2
fix: fix unit test
salimtb Nov 29, 2024
98b5fb5
fix: fix PR comments
salimtb Dec 1, 2024
02cc393
Merge branch 'main' into salim/multichain-detect-tokens-feat
salimtb Dec 1, 2024
ea8ee34
fix: fix selected network
salimtb Dec 1, 2024
8183136
Merge branch 'main' into salim/multichain-detect-tokens-feat
salimtb Dec 2, 2024
593a2cc
Merge branch 'salim/multichain-detect-tokens-feat' into feat-multicha…
salimtb Dec 2, 2024
32db30a
Merge branch 'salim/multichain-detect-tokens-feat' into feat-multicha…
vinnyhoward Dec 2, 2024
38c0b87
fix: resolve memory leaks and mock function for feature flag causing …
vinnyhoward Dec 3, 2024
6e0ad0a
Merge branch 'main' into salim/multichain-detect-tokens-feat
salimtb Dec 3, 2024
1fa9ed2
fix: fix snapshot
salimtb Dec 3, 2024
b8776b9
fix: fix snapshot
salimtb Dec 3, 2024
6f26ee6
fix: use allTokens
salimtb Dec 3, 2024
4932fb2
Merge branch 'salim/multichain-detect-tokens-feat' into feat-multicha…
salimtb Dec 3, 2024
74b8e1d
fix: fix after conflicts
salimtb Dec 3, 2024
156924f
feat: aggregated balance cross chains for portfolio view
sahar-fehri Dec 3, 2024
58c95e6
Merge branch 'main' of github.com:MetaMask/metamask-mobile into feat-…
vinnyhoward Dec 3, 2024
a7641c8
fix: lint
vinnyhoward Dec 3, 2024
7f825dd
fix: increase test coverage and fix asset details page not showing ba…
vinnyhoward Dec 3, 2024
b102439
fix: e2e with feature flag off is fixed and added more test coverage
vinnyhoward Dec 4, 2024
47d4570
fix: fixed tests
vinnyhoward Dec 4, 2024
9d1d81c
Merge branch 'feat/cross-chain-aggregated-balance-feature' into feat-…
sahar-fehri Dec 4, 2024
ec13b66
feat: fix lint
sahar-fehri Dec 4, 2024
a616c41
fix: fix native token balance display when zero
sahar-fehri Dec 4, 2024
c36f9f8
fix: filter system files
salimtb Dec 4, 2024
b0513fc
fix: use swap vertical icon
salimtb Dec 4, 2024
9345091
fix: fix swaps button display
sahar-fehri Dec 4, 2024
72cccc5
fix: feedback
sahar-fehri Dec 4, 2024
e2b34d8
fix: added more test coverage, cleaned up todos, reverted unnecessary…
vinnyhoward Dec 4, 2024
8f8fc79
Merge branch 'feat/cross-chain-aggregated-balance-feature' of github.…
vinnyhoward Dec 4, 2024
1ace57e
test: increase test coverage for multichain file and fixed incorrect …
vinnyhoward Dec 5, 2024
4035ed4
fix: fix error when showConversion is not enabled for testnet
sahar-fehri Dec 5, 2024
16abd59
fix: fix send flow testnet
salimtb Dec 5, 2024
613a431
fix(12550): adding popular network no longer switches network filter …
vinnyhoward Dec 5, 2024
95e9f50
Merge branch 'main' of github.com:MetaMask/metamask-mobile into feat-…
vinnyhoward Dec 5, 2024
f9dcdfe
fix: removed dup import
vinnyhoward Dec 5, 2024
f6563b4
fix: add missing symbol in main balance
vinnyhoward Dec 5, 2024
0613e3b
fix: optimized multichain list so that it re-renders less
vinnyhoward Dec 6, 2024
08149c9
Merge branch 'main' into feat-multichain-list
vinnyhoward Dec 6, 2024
9fc34a6
test: update multichain test
vinnyhoward Dec 6, 2024
b7092e9
fix: lint
vinnyhoward Dec 6, 2024
a112133
fix: fix balance
sahar-fehri Dec 6, 2024
53213e9
Merge branch 'main' into feat-multichain-list
salimtb Dec 6, 2024
5a24e5f
fix: fix showing fiat balance on testnet when conversion rate is off
sahar-fehri Dec 6, 2024
8dc0e77
fix: fix merge conflicts
sahar-fehri Dec 6, 2024
b78f25c
fix: refactored multichain list to be faster and simpler
vinnyhoward Dec 7, 2024
f4b88cd
Merge branch 'feat-multichain-list' of github.com:MetaMask/metamask-m…
vinnyhoward Dec 7, 2024
5c948c8
fix: fix balance
salimtb Dec 7, 2024
f3c49ba
fix: fix linter
salimtb Dec 7, 2024
926a344
fix: fix linter
salimtb Dec 7, 2024
cd0d012
fix: fix native/fiat toggle
salimtb Dec 7, 2024
d182921
fix: clean up
salimtb Dec 8, 2024
de30cd5
fix: clean up and reduce review scope
salimtb Dec 8, 2024
381cb77
fix: fix balance + detection
salimtb Dec 8, 2024
53b55b2
fix: fix unit tests
salimtb Dec 8, 2024
181f057
Merge branch 'main' into feat-multichain-list
vinnyhoward Dec 9, 2024
bfaf402
fix: added fallbacks if there is missing markets, token balances
vinnyhoward Dec 9, 2024
c17e91d
fix: remove conf file from diff
salimtb Dec 9, 2024
16bf9d5
fix: fix conversions for testnets
sahar-fehri Dec 9, 2024
c8a3cb5
Merge branch 'main' into feat-multichain-list
salimtb Dec 9, 2024
f7b40d1
fix: fix after conflicts
salimtb Dec 9, 2024
5c6e35f
fix: fix after conflicts
salimtb Dec 9, 2024
4467c9a
fix: native balance address
vinnyhoward Dec 9, 2024
c69c5fa
fix: token balances render again for non-native tokens in asset overv…
vinnyhoward Dec 9, 2024
3a92dff
fix: bnb and polygon returning network names and not token names
vinnyhoward Dec 9, 2024
e332409
fix: fixed issue where POL and BNB wasn't showing balance in asset ov…
vinnyhoward Dec 9, 2024
146257a
fix: fixed balance issues relating to non-ethereum native tokens
vinnyhoward Dec 9, 2024
8822f78
fix: update snap
vinnyhoward Dec 9, 2024
a59f2e4
Merge branch 'main' into feat-multichain-list
sahar-fehri Dec 9, 2024
22ad818
fix: tests no longer skip due to feature flags but run conditionally
vinnyhoward Dec 10, 2024
7921acd
fix: update snapshot
vinnyhoward Dec 10, 2024
136bd9f
fix: reduce code owners
salimtb Dec 10, 2024
a04930a
fix: fix getDefaultNetworkByChainId any
sahar-fehri Dec 10, 2024
c161315
fix: add TS doc
salimtb Dec 10, 2024
3cc327d
fix: address refresh mutex
salimtb Dec 10, 2024
b6a631b
fix: clean up
salimtb Dec 10, 2024
53f5c05
fix: rm new locales
sahar-fehri Dec 10, 2024
4d480f7
fix: fix
sahar-fehri Dec 10, 2024
d1d239e
Merge branch 'main' into feat-multichain-list
sahar-fehri Dec 10, 2024
fdae34b
Merge branch 'main' into feat-multichain-list
salimtb Dec 10, 2024
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import React, { useMemo } from 'react';
import {
TextColor,
TextVariant,
} from '../../../../component-library/components/Texts/Text';
import SensitiveText from '../../../../component-library/components/Texts/SensitiveText';
import { View } from 'react-native';
import { renderFiat } from '../../../../util/number';
import { useSelector } from 'react-redux';
import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
import styleSheet from './AggregatedPercentage.styles';
import { useStyles } from '../../../hooks';
import {
FORMATTED_VALUE_PRICE_TEST_ID,
FORMATTED_PERCENTAGE_TEST_ID,
} from './AggregatedPercentage.constants';
import { toChecksumAddress, zeroAddress } from 'ethereumjs-util';
import { selectTokenMarketData } from '../../../../selectors/tokenRatesController';
import {
MarketDataMapping,
TokensWithBalances,
} from '../../../../components/hooks/useGetFormattedTokensPerChain';

export interface AggregatedPercentageProps {
ethFiat: number;
tokenFiat: number;
tokenFiat1dAgo: number;
ethFiat1dAgo: number;
}

export const getCalculatedTokenAmount1dAgo = (
tokenFiatBalance: number,
tokenPricePercentChange1dAgo: number,
) =>
tokenPricePercentChange1dAgo !== undefined && tokenFiatBalance
? tokenFiatBalance / (1 + tokenPricePercentChange1dAgo / 100)
: tokenFiatBalance ?? 0;

const isValidAmount = (amount: number | null | undefined): boolean =>
amount !== null && amount !== undefined && !Number.isNaN(amount);

const AggregatedPercentageCrossChains = ({
privacyMode = false,
totalFiatCrossChains,
tokenFiatBalancesCrossChains,
}: {
privacyMode?: boolean;
totalFiatCrossChains: number;
tokenFiatBalancesCrossChains: {
chainId: string;
nativeFiatValue: number;
tokenFiatBalances: number[];
tokensWithBalances: TokensWithBalances[];
}[];
}) => {
const crossChainMarketData: MarketDataMapping = useSelector(
selectTokenMarketData,
);

const totalFiat1dAgoCrossChains = useMemo(() => {
const getPerChainTotalFiat1dAgo = (
chainId: string,
tokenFiatBalances: number[],
tokensWithBalances: TokensWithBalances[],
) => {
const totalPerChain1dAgoERC20 = tokensWithBalances.reduce(
(total1dAgo: number, item: { address: string }, idx: number) => {
const found =
crossChainMarketData?.[chainId]?.[toChecksumAddress(item.address)];

const tokenFiat1dAgo = getCalculatedTokenAmount1dAgo(
tokenFiatBalances[idx],
found?.pricePercentChange1d,
);
return total1dAgo + Number(tokenFiat1dAgo);
},
0,
);

return totalPerChain1dAgoERC20;
};
return tokenFiatBalancesCrossChains.reduce(
(
total1dAgoCrossChains: number,
item: {
chainId: string;
nativeFiatValue: number;
tokenFiatBalances: number[];
tokensWithBalances: TokensWithBalances[];
},
) => {
const perChainERC20Total = getPerChainTotalFiat1dAgo(
item.chainId,
item.tokenFiatBalances,
item.tokensWithBalances,
);

const nativePricePercentChange1d =
crossChainMarketData?.[item.chainId]?.[zeroAddress()]
?.pricePercentChange1d;

const nativeFiat1dAgo = getCalculatedTokenAmount1dAgo(
item.nativeFiatValue,
nativePricePercentChange1d,
);
return (
total1dAgoCrossChains + perChainERC20Total + Number(nativeFiat1dAgo)
);
},
0,
);
}, [tokenFiatBalancesCrossChains, crossChainMarketData]);

const totalCrossChainBalance: number = Number(totalFiatCrossChains);
const crossChainTotalBalance1dAgo = totalFiat1dAgoCrossChains;

const amountChangeCrossChains =
totalCrossChainBalance - crossChainTotalBalance1dAgo;

const percentageChangeCrossChains =
(amountChangeCrossChains / crossChainTotalBalance1dAgo) * 100 || 0;

const validFormattedPercentChange = `(${
percentageChangeCrossChains >= 0 ? '+' : ''
}${percentageChangeCrossChains.toFixed(2)}%)`;

const formattedPercentChangeCrossChains = isValidAmount(
percentageChangeCrossChains,
)
? validFormattedPercentChange
: '';
const currentCurrency = useSelector(selectCurrentCurrency);
const DECIMALS_TO_SHOW = 2;

const validFormattedAmountChange = `${
amountChangeCrossChains >= 0 ? '+' : ''
}${renderFiat(amountChangeCrossChains, currentCurrency, DECIMALS_TO_SHOW)} `;
const formattedAmountChangeCrossChains = isValidAmount(
amountChangeCrossChains,
)
? validFormattedAmountChange
: '';

let percentageTextColor;

if (!privacyMode) {
if (percentageChangeCrossChains === 0) {
percentageTextColor = TextColor.Default;
} else if (percentageChangeCrossChains > 0) {
percentageTextColor = TextColor.Success;
} else {
percentageTextColor = TextColor.Error;
}
} else {
percentageTextColor = TextColor.Alternative;
}
const { styles } = useStyles(styleSheet, {});

return (
<View style={styles.wrapper}>
<SensitiveText
isHidden={privacyMode}
length="10"
color={percentageTextColor}
variant={TextVariant.BodyMDMedium}
testID={FORMATTED_VALUE_PRICE_TEST_ID}
>
{formattedAmountChangeCrossChains}
</SensitiveText>
<SensitiveText
isHidden={privacyMode}
length="10"
color={percentageTextColor}
variant={TextVariant.BodyMDMedium}
testID={FORMATTED_PERCENTAGE_TEST_ID}
>
{formattedPercentChangeCrossChains}
</SensitiveText>
</View>
);
};

export default AggregatedPercentageCrossChains;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`AggregatedPercentageCrossChains should match snapshot 1`] = `
<View
style={
{
"alignItems": "center",
"flexDirection": "row",
}
}
>
<Text
accessibilityRole="text"
style={
{
"color": "#141618",
"fontFamily": "EuclidCircularB-Medium",
"fontSize": 14,
"fontWeight": "500",
"letterSpacing": 0,
"lineHeight": 22,
}
}
testID="formatted-value-price-test-id"
>
+0 USD
</Text>
<Text
accessibilityRole="text"
style={
{
"color": "#141618",
"fontFamily": "EuclidCircularB-Medium",
"fontSize": 14,
"fontWeight": "500",
"letterSpacing": 0,
"lineHeight": 22,
}
}
testID="formatted-percentage-test-id"
>
(+0.00%)
</Text>
</View>
`;
7 changes: 7 additions & 0 deletions app/components/UI/AccountApproval/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ const mockInitialState = {
},
},
},
TokensController: {
allTokens: {
'0x1': {
'0xc4966c0d659d99699bfd7eb54d8fafee40e4a756': [],
},
},
},
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ const mockInitialState: DeepPartial<RootState> = {
},
},
TokenBalancesController: {
tokenBalances: { },
tokenBalances: {
'0x326836cc6cd09B5aa59B81A7F72F25FcC0136b95': {
'0x5': {
'0x326836cc6cd09B5aa59B81A7F72F25FcC0136b95': '0x2b46',
},
},
},
},
AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE,
},
Expand Down
86 changes: 78 additions & 8 deletions app/components/UI/AssetOverview/AssetOverview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import {
MOCK_ADDRESS_2,
} from '../../../util/test/accountsControllerTestUtils';
import { createBuyNavigationDetails } from '../Ramp/routes/utils';
import { getDecimalChainId } from '../../../util/networks';
import {
getDecimalChainId,
isPortfolioViewEnabled,
} from '../../../util/networks';
import { TokenOverviewSelectorsIDs } from '../../../../e2e/selectors/TokenOverview.selectors';
// eslint-disable-next-line import/no-namespace
import * as networks from '../../../util/networks';

const MOCK_CHAIN_ID = '0x1';

Expand Down Expand Up @@ -43,6 +48,15 @@ const mockInitialState = {
},
} as const,
},
CurrencyRateController: {
conversionRate: {
ETH: {
conversionDate: 1732572535.47,
conversionRate: 3432.53,
usdConversionRate: 3432.53,
},
},
},
},
settings: {
primaryCurrency: 'ETH',
Expand All @@ -51,6 +65,15 @@ const mockInitialState = {

const mockNavigate = jest.fn();
const navigate = mockNavigate;
const mockNetworkConfiguration = {
rpcEndpoints: [
{
networkClientId: 'mockNetworkClientId',
},
],
defaultRpcEndpointIndex: 0,
};

jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native');
return {
Expand All @@ -72,9 +95,21 @@ jest.mock('../../hooks/useStyles', () => ({
}),
}));

jest.mock('../../../core/Engine', () => ({
context: {
NetworkController: {
getNetworkConfigurationByChainId: jest
.fn()
.mockReturnValue(mockNetworkConfiguration),
setActiveNetwork: jest.fn().mockResolvedValue(undefined),
},
},
}));

const asset = {
balance: '400',
balanceFiat: '1500',
chainId: MOCK_CHAIN_ID,
logo: 'https://upload.wikimedia.org/wikipedia/commons/0/05/Ethereum_logo_2014.svg',
symbol: 'ETH',
name: 'Ethereum',
Expand All @@ -87,6 +122,10 @@ const asset = {
};

describe('AssetOverview', () => {
beforeEach(() => {
jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(false);
});

it('should render correctly', async () => {
const container = renderWithProvider(
<AssetOverview asset={asset} displayBuyButton displaySwapsButton />,
Expand All @@ -95,6 +134,16 @@ describe('AssetOverview', () => {
expect(container).toMatchSnapshot();
});

it('should render correctly when portfolio view is enabled', async () => {
jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(true);

const container = renderWithProvider(
<AssetOverview asset={asset} displayBuyButton displaySwapsButton />,
{ state: mockInitialState },
);
expect(container).toMatchSnapshot();
});

it('should handle buy button press', async () => {
const { getByTestId } = renderWithProvider(
<AssetOverview asset={asset} displayBuyButton displaySwapsButton />,
Expand Down Expand Up @@ -133,13 +182,34 @@ describe('AssetOverview', () => {
const swapButton = getByTestId('token-swap-button');
fireEvent.press(swapButton);

expect(navigate).toHaveBeenCalledWith('Swaps', {
params: {
sourcePage: 'MainView',
sourceToken: asset.address,
},
screen: 'SwapsAmountView',
});
if (isPortfolioViewEnabled()) {
expect(navigate).toHaveBeenCalledTimes(3);
expect(navigate).toHaveBeenNthCalledWith(1, 'RampBuy', {
screen: 'GetStarted',
params: {
address: asset.address,
chainId: getDecimalChainId(MOCK_CHAIN_ID),
},
});
expect(navigate).toHaveBeenNthCalledWith(2, 'SendFlowView', {});
expect(navigate).toHaveBeenNthCalledWith(3, 'Swaps', {
screen: 'SwapsAmountView',
params: {
sourcePage: 'MainView',
address: asset.address,
chainId: MOCK_CHAIN_ID,
},
});
} else {
expect(navigate).toHaveBeenCalledWith('Swaps', {
screen: 'SwapsAmountView',
params: {
sourcePage: 'MainView',
sourceToken: asset.address,
chainId: '0x1',
},
});
}
});

it('should not render swap button if displaySwapsButton is false', async () => {
Expand Down
Loading
Loading