From 4253eaddaea5bf3bfee234c06043f8c0227fe9db Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Mon, 1 Jul 2024 18:57:37 -0600 Subject: [PATCH 01/13] feat(ramps): add is-rampable logic to btc --- ui/components/app/asset-list/asset-list.js | 26 +++++++++++++--- .../app/wallet-overview/btc-overview.tsx | 8 ++--- .../app/wallet-overview/coin-buttons.tsx | 11 ++----- .../app/wallet-overview/coin-overview.tsx | 3 -- .../multichain/ramps-card/ramps-card.js | 2 +- ui/ducks/ramps/ramps.ts | 31 +++++++++++++++---- ui/ducks/ramps/types.ts | 2 +- ui/helpers/ramps/rampApi/rampAPI.ts | 15 ++++++++- ui/selectors/multichain.ts | 24 ++++++++++++++ 9 files changed, 91 insertions(+), 31 deletions(-) diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index 8e493037da44..9e08d18b7f04 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -19,6 +19,8 @@ import { getMultichainCurrencyImage, getMultichainIsMainnet, getMultichainSelectedAccountCachedBalance, + getMultichainIsBitcoin, + getMultichainSelectedAccountCachedBalanceIsZero, } from '../../../selectors/multichain'; import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'; import { MetaMetricsContext } from '../../../contexts/metametrics'; @@ -98,24 +100,31 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, ); - const { tokensWithBalances, totalFiatBalance, loading } = - useAccountTotalFiatBalance(selectedAccount, shouldHideZeroBalanceTokens); + const { tokensWithBalances, loading } = useAccountTotalFiatBalance( + selectedAccount, + shouldHideZeroBalanceTokens, + ); tokensWithBalances.forEach((token) => { // token.string is the balance displayed in the TokenList UI token.string = roundToDecimalPlacesRemovingExtraZeroes(token.string, 5); }); - const balanceIsZero = Number(totalFiatBalance) === 0; + + const balanceIsZero = useSelector( + getMultichainSelectedAccountCachedBalanceIsZero, + ); + ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) const isBuyableChain = useSelector(getIsNativeTokenBuyable); const shouldShowBuy = isBuyableChain && balanceIsZero; ///: END:ONLY_INCLUDE_IF const isEvm = useSelector(getMultichainIsEvm); - // NOTE: Since we can parametrize it now, we keep the original behavior // for EVM assets const shouldShowTokensLinks = showTokensLinks ?? isEvm; + const isBTC = useSelector(getMultichainIsBitcoin); + let isStakeable = isMainnet && isEvm; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) isStakeable = false; @@ -133,7 +142,14 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) shouldShowBuy ? ( - + ) : null ///: END:ONLY_INCLUDE_IF } diff --git a/ui/components/app/wallet-overview/btc-overview.tsx b/ui/components/app/wallet-overview/btc-overview.tsx index 3703252f205a..8ca498071045 100644 --- a/ui/components/app/wallet-overview/btc-overview.tsx +++ b/ui/components/app/wallet-overview/btc-overview.tsx @@ -1,10 +1,10 @@ import React from 'react'; - import { useSelector } from 'react-redux'; import { getMultichainProviderConfig, getMultichainSelectedAccountCachedBalance, } from '../../../selectors/multichain'; +import { getIsBitcoinBuyable } from '../../../ducks/ramps'; import { CoinOverview } from './coin-overview'; type BtcOverviewProps = { @@ -14,6 +14,7 @@ type BtcOverviewProps = { const BtcOverview = ({ className }: BtcOverviewProps) => { const { chainId } = useSelector(getMultichainProviderConfig); const balance = useSelector(getMultichainSelectedAccountCachedBalance); + const isBtcBuyable = useSelector(getIsBitcoinBuyable); return ( { isSwapsChain={false} ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) isBridgeChain={false} - isBuyableChain - // TODO: Remove this logic once `isNativeTokenBuyable` has been - // merged (see: https://github.com/MetaMask/metamask-extension/pull/24041) - isBuyableChainWithoutSigning + isBuyableChain={isBtcBuyable} ///: END:ONLY_INCLUDE_IF /> ); diff --git a/ui/components/app/wallet-overview/coin-buttons.tsx b/ui/components/app/wallet-overview/coin-buttons.tsx index b1774897edc4..6f4f274a54b8 100644 --- a/ui/components/app/wallet-overview/coin-buttons.tsx +++ b/ui/components/app/wallet-overview/coin-buttons.tsx @@ -75,9 +75,6 @@ const CoinButtons = ({ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) isBridgeChain, isBuyableChain, - // TODO: Remove this logic once `isNativeTokenBuyable` has been - // merged (see: https://github.com/MetaMask/metamask-extension/pull/24041) - isBuyableChainWithoutSigning = false, defaultSwapsToken, ///: END:ONLY_INCLUDE_IF classPrefix = 'coin', @@ -88,7 +85,6 @@ const CoinButtons = ({ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) isBridgeChain: boolean; isBuyableChain: boolean; - isBuyableChainWithoutSigning?: boolean; defaultSwapsToken?: SwapsEthToken; ///: END:ONLY_INCLUDE_IF classPrefix?: string; @@ -113,7 +109,7 @@ const CoinButtons = ({ { condition: !isBuyableChain, message: '' }, ///: END:ONLY_INCLUDE_IF { - condition: !(isSigningEnabled || isBuyableChainWithoutSigning), + condition: !isSigningEnabled, message: 'methodNotSupported', }, ], @@ -339,10 +335,7 @@ const CoinButtons = ({ Icon={ } - disabled={ - !isBuyableChain || - !(isSigningEnabled || isBuyableChainWithoutSigning) - } + disabled={!isBuyableChain || !isSigningEnabled} data-testid={`${classPrefix}-overview-buy`} label={t('buyAndSell')} onClick={handleBuyAndSellOnClick} diff --git a/ui/components/app/wallet-overview/coin-overview.tsx b/ui/components/app/wallet-overview/coin-overview.tsx index 6364b0231e82..53d98f17039d 100644 --- a/ui/components/app/wallet-overview/coin-overview.tsx +++ b/ui/components/app/wallet-overview/coin-overview.tsx @@ -39,7 +39,6 @@ export type CoinOverviewProps = { defaultSwapsToken?: SwapsEthToken; isBridgeChain: boolean; isBuyableChain: boolean; - isBuyableChainWithoutSigning: boolean; ///: END:ONLY_INCLUDE_IF isSwapsChain: boolean; isSigningEnabled: boolean; @@ -55,7 +54,6 @@ export const CoinOverview = ({ defaultSwapsToken, isBridgeChain, isBuyableChain, - isBuyableChainWithoutSigning, ///: END:ONLY_INCLUDE_IF isSwapsChain, isSigningEnabled, @@ -152,7 +150,6 @@ export const CoinOverview = ({ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) isBridgeChain, isBuyableChain, - isBuyableChainWithoutSigning, defaultSwapsToken, ///: END:ONLY_INCLUDE_IF classPrefix, diff --git a/ui/components/multichain/ramps-card/ramps-card.js b/ui/components/multichain/ramps-card/ramps-card.js index c03e9e956f9a..7a24cf3f45ab 100644 --- a/ui/components/multichain/ramps-card/ramps-card.js +++ b/ui/components/multichain/ramps-card/ramps-card.js @@ -117,7 +117,7 @@ export const RampsCard = ({ variant }) => { }} > - {t(title, [symbol])} + {t(title)} {t(body, [symbol])} diff --git a/ui/ducks/ramps/ramps.ts b/ui/ducks/ramps/ramps.ts index afff609cd4d8..c7e4c207f2b8 100644 --- a/ui/ducks/ramps/ramps.ts +++ b/ui/ducks/ramps/ramps.ts @@ -3,6 +3,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { getCurrentChainId, getUseExternalServices } from '../../selectors'; import RampAPI from '../../helpers/ramps/rampApi/rampAPI'; import { hexToDecimal } from '../../../shared/modules/conversion.utils'; +import { getMultichainIsBitcoin } from '../../selectors/multichain'; import { defaultBuyableChains } from './constants'; import { AggregatorNetwork } from './types'; @@ -59,16 +60,34 @@ const { reducer } = rampsSlice; export const getBuyableChains = (state: any) => state.ramps?.buyableChains ?? defaultBuyableChains; +export const getIsBitcoinBuyable = createSelector( + [getBuyableChains], + (buyableChains) => + buyableChains.some((network: AggregatorNetwork) => { + return ( + network.chainId === 'bip122:000000000019d6689c085ae165831e93' && + network.active + ); + }), +); + export const getIsNativeTokenBuyable = createSelector( - [getCurrentChainId, getBuyableChains], - (currentChainId, buyableChains) => { + [ + getCurrentChainId, + getBuyableChains, + getIsBitcoinBuyable, + getMultichainIsBitcoin, + ], + (currentChainId, buyableChains, isBtcBuyable, isBtc) => { try { return buyableChains .filter(Boolean) - .some( - (network: AggregatorNetwork) => - String(network.chainId) === hexToDecimal(currentChainId), - ); + .some((network: AggregatorNetwork) => { + if (isBtc) { + return isBtcBuyable; + } + return String(network.chainId) === hexToDecimal(currentChainId); + }); } catch (e) { return false; } diff --git a/ui/ducks/ramps/types.ts b/ui/ducks/ramps/types.ts index 6a1571715dfe..5cb36846a89e 100644 --- a/ui/ducks/ramps/types.ts +++ b/ui/ducks/ramps/types.ts @@ -1,6 +1,6 @@ export type AggregatorNetwork = { active: boolean; - chainId: number; + chainId: string; chainName: string; nativeTokenSupported: boolean; shortName: string; diff --git a/ui/helpers/ramps/rampApi/rampAPI.ts b/ui/helpers/ramps/rampApi/rampAPI.ts index a1da6da8ef0c..c6f914326ce8 100644 --- a/ui/helpers/ramps/rampApi/rampAPI.ts +++ b/ui/helpers/ramps/rampApi/rampAPI.ts @@ -17,7 +17,20 @@ const RampAPI = { url.searchParams.set('context', 'extension'); const response = await fetchWithTimeout(url.toString()); - const { networks } = await response.json(); + let { networks } = await response.json(); + + networks = [ + ...networks, + { + active: true, + chainId: 'bip122:000000000019d6689c085ae165831e93', + chainName: 'Bitcoin', + shortName: 'Bitcoin', + nativeTokenSupported: true, + isEvm: false, + }, + ]; + return networks; }, }; diff --git a/ui/selectors/multichain.ts b/ui/selectors/multichain.ts index eba3987a21db..cc31ef9741fe 100644 --- a/ui/selectors/multichain.ts +++ b/ui/selectors/multichain.ts @@ -28,6 +28,8 @@ import { getSelectedInternalAccount, getShouldShowFiat, } from '.'; +import { useSelector } from 'react-redux'; +import { createSelector } from '@reduxjs/toolkit'; export type RatesState = { metamask: RatesControllerState; @@ -127,6 +129,16 @@ export function getMultichainIsEvm( ); } +export function getMultichainIsBitcoin( + state: MultichainState, + account?: InternalAccount, +) { + const isEvm = getMultichainIsEvm(state, account); + const { symbol } = getMultichainDefaultToken(state, account); + + return !isEvm && symbol === 'BTC'; +} + /** * Retrieves the provider configuration for a multichain network. * @@ -265,6 +277,18 @@ export function getMultichainSelectedAccountCachedBalance( : getBtcCachedBalance(state); } +export const getMultichainSelectedAccountCachedBalanceIsZero = createSelector( + [getMultichainIsEvm, getMultichainSelectedAccountCachedBalance], + (isEVM, balance) => { + // TODO: there has to be a better way to do this... + if (isEVM) { + return balance === '0x00'; + } + + return balance === '0.00000000'; + }, +); + export function getMultichainConversionRate( state: MultichainState, account?: InternalAccount, From 79b1c68092645f78b4cbe30ac97e99cae9785231 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Mon, 1 Jul 2024 19:06:15 -0600 Subject: [PATCH 02/13] chore: removes hardcoded dev values --- .../multichain/ramps-card/ramps-card.js | 2 +- ui/helpers/ramps/rampApi/rampAPI.ts | 16 +--------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/ui/components/multichain/ramps-card/ramps-card.js b/ui/components/multichain/ramps-card/ramps-card.js index 7a24cf3f45ab..c03e9e956f9a 100644 --- a/ui/components/multichain/ramps-card/ramps-card.js +++ b/ui/components/multichain/ramps-card/ramps-card.js @@ -117,7 +117,7 @@ export const RampsCard = ({ variant }) => { }} > - {t(title)} + {t(title, [symbol])} {t(body, [symbol])} diff --git a/ui/helpers/ramps/rampApi/rampAPI.ts b/ui/helpers/ramps/rampApi/rampAPI.ts index c6f914326ce8..06355d01be6e 100644 --- a/ui/helpers/ramps/rampApi/rampAPI.ts +++ b/ui/helpers/ramps/rampApi/rampAPI.ts @@ -16,21 +16,7 @@ const RampAPI = { const url = new URL('/regions/networks', rampApiBaseUrl); url.searchParams.set('context', 'extension'); const response = await fetchWithTimeout(url.toString()); - - let { networks } = await response.json(); - - networks = [ - ...networks, - { - active: true, - chainId: 'bip122:000000000019d6689c085ae165831e93', - chainName: 'Bitcoin', - shortName: 'Bitcoin', - nativeTokenSupported: true, - isEvm: false, - }, - ]; - + const { networks } = await response.json(); return networks; }, }; From 91e7e7d694bf32016f38e7479f6e994b61006fdd Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Tue, 2 Jul 2024 06:31:24 -0600 Subject: [PATCH 03/13] refactor: use numeric util for zero evaluation --- ui/selectors/multichain.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/ui/selectors/multichain.ts b/ui/selectors/multichain.ts index cc31ef9741fe..53257b79da70 100644 --- a/ui/selectors/multichain.ts +++ b/ui/selectors/multichain.ts @@ -3,6 +3,8 @@ import { ProviderConfig } from '@metamask/network-controller'; import type { RatesControllerState } from '@metamask/assets-controllers'; import { CaipChainId, KnownCaipNamespace } from '@metamask/utils'; import { ChainId } from '@metamask/controller-utils'; +import { createSelector } from '@reduxjs/toolkit'; +import { Numeric } from '../../shared/modules/Numeric'; import { MultichainProviderConfig, MULTICHAIN_PROVIDER_CONFIGS, @@ -28,8 +30,6 @@ import { getSelectedInternalAccount, getShouldShowFiat, } from '.'; -import { useSelector } from 'react-redux'; -import { createSelector } from '@reduxjs/toolkit'; export type RatesState = { metamask: RatesControllerState; @@ -280,12 +280,9 @@ export function getMultichainSelectedAccountCachedBalance( export const getMultichainSelectedAccountCachedBalanceIsZero = createSelector( [getMultichainIsEvm, getMultichainSelectedAccountCachedBalance], (isEVM, balance) => { - // TODO: there has to be a better way to do this... - if (isEVM) { - return balance === '0x00'; - } - - return balance === '0.00000000'; + const base = isEVM ? 16 : 10; + const numericBalance = new Numeric(balance, base); + return numericBalance.isZero(); }, ); From f8d21159934cd06edf317d2ccada673493b5876e Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Tue, 2 Jul 2024 07:13:47 -0600 Subject: [PATCH 04/13] test: adds ramp test to btc-overview --- .../app/wallet-overview/btc-overview.test.tsx | 136 +++++++++++++----- .../app/wallet-overview/coin-buttons.tsx | 6 +- 2 files changed, 99 insertions(+), 43 deletions(-) diff --git a/ui/components/app/wallet-overview/btc-overview.test.tsx b/ui/components/app/wallet-overview/btc-overview.test.tsx index 233096f0918c..e50efce52457 100644 --- a/ui/components/app/wallet-overview/btc-overview.test.tsx +++ b/ui/components/app/wallet-overview/btc-overview.test.tsx @@ -9,6 +9,7 @@ import mockState from '../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../test/jest/rendering'; import { MultichainNetworks } from '../../../../shared/constants/multichain/networks'; import { RampsMetaMaskEntry } from '../../../hooks/ramps/useRamps/useRamps'; +import { defaultBuyableChains } from '../../../ducks/ramps/constants'; import BtcOverview from './btc-overview'; const PORTOFOLIO_URL = 'https://portfolio.test'; @@ -40,43 +41,63 @@ const mockNonEvmAccount = { type: BtcAccountType.P2wpkh, }; -function getStore(state?: Record) { - return configureMockStore([thunk])({ - metamask: { - ...mockState.metamask, - internalAccounts: { - accounts: { - [mockNonEvmAccount.id]: mockNonEvmAccount, - }, - selectedAccount: mockNonEvmAccount.id, - }, - // (Multichain) BalancesController - balances: { - [mockNonEvmAccount.id]: { - [MultichainNativeAssets.BITCOIN]: { - amount: mockNonEvmBalance, - unit: 'BTC', - }, - }, - }, - // (Multichain) RatesController - fiatCurrency: 'usd', - rates: { - [Cryptocurrency.Btc]: { - conversionRate: '1.000', - conversionDate: 0, - }, +const mockBtcChain = { + active: true, + chainId: 'bip122:000000000019d6689c085ae165831e93', + chainName: 'Bitcoin', + shortName: 'Bitcoin', + nativeTokenSupported: true, + isEvm: false, +}; +// default chains do not include BTC +const mockBuyableChainsWithoutBtc = defaultBuyableChains.filter( + (chain) => chain.chainId !== MultichainNetworks.BITCOIN, +); +const mockBuyableChainsWithBtc = [...mockBuyableChainsWithoutBtc, mockBtcChain]; + +const mockMetamaskStore = { + ...mockState.metamask, + internalAccounts: { + accounts: { + [mockNonEvmAccount.id]: mockNonEvmAccount, + }, + selectedAccount: mockNonEvmAccount.id, + }, + // (Multichain) BalancesController + balances: { + [mockNonEvmAccount.id]: { + [MultichainNativeAssets.BITCOIN]: { + amount: mockNonEvmBalance, + unit: 'BTC', }, - cryptocurrencies: [Cryptocurrency.Btc], - // Required, during onboarding, the extension will assume we're in an "EVM context", meaning - // most multichain selectors will not use non-EVM logic despite having a non-EVM - // selected account - completedOnboarding: true, - // Used when clicking on some buttons - metaMetricsId: mockMetaMetricsId, - // Override state if provided - ...state, }, + }, + // (Multichain) RatesController + fiatCurrency: 'usd', + rates: { + [Cryptocurrency.Btc]: { + conversionRate: '1.000', + conversionDate: 0, + }, + }, + cryptocurrencies: [Cryptocurrency.Btc], + // Required, during onboarding, the extension will assume we're in an "EVM context", meaning + // most multichain selectors will not use non-EVM logic despite having a non-EVM + // selected account + completedOnboarding: true, + // Used when clicking on some buttons + metaMetricsId: mockMetaMetricsId, + // Override state if provided +}; +const mockRampsStore = { + buyableChains: mockBuyableChainsWithoutBtc, +}; + +function getStore(state?: Record) { + return configureMockStore([thunk])({ + metamask: mockMetamaskStore, + ramps: mockRampsStore, + ...state, }); } @@ -103,8 +124,11 @@ describe('BtcOverview', () => { const { container } = renderWithProvider( , getStore({ - // The balances won't be available - balances: {}, + metamask: { + ...mockMetamaskStore, + // The balances won't be available + balances: {}, + }, }), ); @@ -134,8 +158,44 @@ describe('BtcOverview', () => { expect(buyButton).toBeInTheDocument(); }); - it('opens the Portfolio "Buy & Sell" URI when clicking on "Buy & Sell" button', async () => { + it('"Buy & Sell" button is disabled if BTC is not buyable', () => { const { queryByTestId } = renderWithProvider(, getStore()); + const buyButton = queryByTestId(BTC_OVERVIEW_BUY); + + expect(buyButton).toBeInTheDocument(); + expect(buyButton).toBeDisabled(); + }); + + it('"Buy & Sell" button is enabled if BTC is buyable', () => { + const storeWithBtcBuyable = getStore({ + ramps: { + buyableChains: mockBuyableChainsWithBtc, + }, + }); + + const { queryByTestId } = renderWithProvider( + , + storeWithBtcBuyable, + ); + + const buyButton = queryByTestId(BTC_OVERVIEW_BUY); + + expect(buyButton).toBeInTheDocument(); + expect(buyButton).not.toBeDisabled(); + }); + + it('opens the Portfolio "Buy & Sell" URI when clicking on "Buy & Sell" button', async () => { + const storeWithBtcBuyable = getStore({ + ramps: { + buyableChains: mockBuyableChainsWithBtc, + }, + }); + + const { queryByTestId } = renderWithProvider( + , + storeWithBtcBuyable, + ); + const openTabSpy = jest.spyOn(global.platform, 'openTab'); const buyButton = queryByTestId(BTC_OVERVIEW_BUY); diff --git a/ui/components/app/wallet-overview/coin-buttons.tsx b/ui/components/app/wallet-overview/coin-buttons.tsx index 6f4f274a54b8..5d710b83adfb 100644 --- a/ui/components/app/wallet-overview/coin-buttons.tsx +++ b/ui/components/app/wallet-overview/coin-buttons.tsx @@ -108,10 +108,6 @@ const CoinButtons = ({ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) { condition: !isBuyableChain, message: '' }, ///: END:ONLY_INCLUDE_IF - { - condition: !isSigningEnabled, - message: 'methodNotSupported', - }, ], sendButton: [ { condition: !isSigningEnabled, message: 'methodNotSupported' }, @@ -335,7 +331,7 @@ const CoinButtons = ({ Icon={ } - disabled={!isBuyableChain || !isSigningEnabled} + disabled={!isBuyableChain} data-testid={`${classPrefix}-overview-buy`} label={t('buyAndSell')} onClick={handleBuyAndSellOnClick} From 081d6a381f60c5ba7b610d8a01b1540becc6cd5e Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Tue, 2 Jul 2024 07:57:36 -0600 Subject: [PATCH 05/13] test: add test for multichain utils --- ui/ducks/ramps/ramps.ts | 11 ++++--- ui/selectors/multichain.test.ts | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/ui/ducks/ramps/ramps.ts b/ui/ducks/ramps/ramps.ts index c7e4c207f2b8..a169a7e8e552 100644 --- a/ui/ducks/ramps/ramps.ts +++ b/ui/ducks/ramps/ramps.ts @@ -6,6 +6,7 @@ import { hexToDecimal } from '../../../shared/modules/conversion.utils'; import { getMultichainIsBitcoin } from '../../selectors/multichain'; import { defaultBuyableChains } from './constants'; import { AggregatorNetwork } from './types'; +import { MultichainNetworks } from '../../../shared/constants/multichain/networks'; export const fetchBuyableChains = createAsyncThunk( 'ramps/fetchBuyableChains', @@ -63,12 +64,10 @@ export const getBuyableChains = (state: any) => export const getIsBitcoinBuyable = createSelector( [getBuyableChains], (buyableChains) => - buyableChains.some((network: AggregatorNetwork) => { - return ( - network.chainId === 'bip122:000000000019d6689c085ae165831e93' && - network.active - ); - }), + buyableChains.some( + (network: AggregatorNetwork) => + network.chainId === MultichainNetworks.BITCOIN && network.active, + ), ); export const getIsNativeTokenBuyable = createSelector( diff --git a/ui/selectors/multichain.test.ts b/ui/selectors/multichain.test.ts index fadbfe08a08a..6ffed9c18c04 100644 --- a/ui/selectors/multichain.test.ts +++ b/ui/selectors/multichain.test.ts @@ -28,6 +28,8 @@ import { getMultichainProviderConfig, getMultichainSelectedAccountCachedBalance, getMultichainShouldShowFiat, + getMultichainIsBitcoin, + getMultichainSelectedAccountCachedBalanceIsZero, } from './multichain'; import { getCurrentCurrency, @@ -368,4 +370,54 @@ describe('Multichain Selectors', () => { }, ); }); + + describe('getMultichainIsBitcoin', () => { + it('returns false if account is EVM', () => { + const state = getEvmState(); + expect(getMultichainIsBitcoin(state)).toBe(false); + }); + + it('returns true if account is BTC', () => { + const state = getNonEvmState(MOCK_ACCOUNT_BIP122_P2WPKH); + expect(getMultichainIsBitcoin(state)).toBe(true); + }); + }); + + describe('getMultichainSelectedAccountCachedBalanceIsZero', () => { + it('returns true if the selected EVM account has a zero balance', () => { + const state = getEvmState(); + state.metamask.accountsByChainId['0x1'][ + MOCK_ACCOUNT_EOA.address + ].balance = '0x00'; + expect(getMultichainSelectedAccountCachedBalanceIsZero(state)).toBe(true); + }); + + it('returns false if the selected EVM account has a non-zero balance', () => { + const state = getEvmState(); + state.metamask.accountsByChainId['0x1'][ + MOCK_ACCOUNT_EOA.address + ].balance = '3'; + expect(getMultichainSelectedAccountCachedBalanceIsZero(state)).toBe( + false, + ); + }); + + it('returns true if the selected non-EVM account has a zero balance', () => { + const state = getNonEvmState(MOCK_ACCOUNT_BIP122_P2WPKH); + state.metamask.balances[MOCK_ACCOUNT_BIP122_P2WPKH.id][ + MultichainNativeAssets.BITCOIN + ].amount = '0.00000000'; + expect(getMultichainSelectedAccountCachedBalanceIsZero(state)).toBe(true); + }); + + it('returns false if the selected non-EVM account has a non-zero balance', () => { + const state = getNonEvmState(MOCK_ACCOUNT_BIP122_P2WPKH); + state.metamask.balances[MOCK_ACCOUNT_BIP122_P2WPKH.id][ + MultichainNativeAssets.BITCOIN + ].amount = '1.00000000'; + expect(getMultichainSelectedAccountCachedBalanceIsZero(state)).toBe( + false, + ); + }); + }); }); From 947bbf0c05c57047c5fa708abaad20ffef50ff53 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Tue, 2 Jul 2024 09:15:03 -0600 Subject: [PATCH 06/13] feat: adds btc variant to asset list --- ui/components/app/asset-list/asset-list.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index 9e08d18b7f04..5a03bfc14040 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -144,10 +144,9 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { shouldShowBuy ? ( ) : null From a29ac79d55e316f5424942cf63c3eed2f49dc919 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 3 Jul 2024 07:45:29 -0600 Subject: [PATCH 07/13] test: fix broken ramps slice test --- ui/ducks/ramps/ramps.test.ts | 89 +++++++++++++++++++++++++++++++++++- ui/ducks/ramps/ramps.ts | 12 +++-- ui/ducks/ramps/types.ts | 2 +- 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/ui/ducks/ramps/ramps.test.ts b/ui/ducks/ramps/ramps.test.ts index c4ca4089815a..5f0430f32bd6 100644 --- a/ui/ducks/ramps/ramps.test.ts +++ b/ui/ducks/ramps/ramps.test.ts @@ -1,7 +1,12 @@ import { configureStore, Store } from '@reduxjs/toolkit'; +import { Cryptocurrency } from '@metamask/assets-controllers'; +import { BtcAccountType, BtcMethod } from '@metamask/keyring-api'; import RampAPI from '../../helpers/ramps/rampApi/rampAPI'; import { getCurrentChainId, getUseExternalServices } from '../../selectors'; import { CHAIN_IDS } from '../../../shared/constants/network'; +import mockEntireState from '../../../test/data/mock-state.json'; +import { MultichainNativeAssets } from '../../../shared/constants/multichain/assets'; +import { getMultichainIsBitcoin } from '../../selectors/multichain'; import rampsReducer, { fetchBuyableChains, getBuyableChains, @@ -9,15 +14,78 @@ import rampsReducer, { } from './ramps'; import { defaultBuyableChains } from './constants'; +const mockMetaMetricsId = 'deadbeef'; +const mockNonEvmBalance = '1'; +const mockNonEvmAccount = { + address: 'bc1qwl8399fz829uqvqly9tcatgrgtwp3udnhxfq4k', + id: '542490c8-d178-433b-9f31-f680b11f45a5', + metadata: { + name: 'Bitcoin Account', + keyring: { + type: 'Snap Keyring', + }, + snap: { + id: 'btc-snap-id', + name: 'btc-snap-name', + }, + }, + options: {}, + methods: [BtcMethod.SendMany], + type: BtcAccountType.P2wpkh, +}; + +const mockMetamaskStore = { + ...mockEntireState.metamask, + internalAccounts: { + accounts: { + [mockNonEvmAccount.id]: mockNonEvmAccount, + }, + selectedAccount: mockNonEvmAccount.id, + }, + // (Multichain) BalancesController + balances: { + [mockNonEvmAccount.id]: { + [MultichainNativeAssets.BITCOIN]: { + amount: mockNonEvmBalance, + unit: 'BTC', + }, + }, + }, + // (Multichain) RatesController + fiatCurrency: 'usd', + rates: { + [Cryptocurrency.Btc]: { + conversionRate: '1.000', + conversionDate: 0, + }, + }, + cryptocurrencies: [Cryptocurrency.Btc], + // Required, during onboarding, the extension will assume we're in an "EVM context", meaning + // most multichain selectors will not use non-EVM logic despite having a non-EVM + // selected account + completedOnboarding: true, + // Used when clicking on some buttons + metaMetricsId: mockMetaMetricsId, + // Override state if provided +}; + jest.mock('../../helpers/ramps/rampApi/rampAPI'); const mockedRampAPI = RampAPI as jest.Mocked; jest.mock('../../selectors', () => ({ + ...jest.requireActual('../../selectors'), getCurrentChainId: jest.fn(), getUseExternalServices: jest.fn(), getNames: jest.fn(), })); +jest.mock('../../selectors/multichain', () => ({ + ...jest.requireActual('../../selectors/multichain'), + getMultichainIsBitcoin: jest.fn(), +})); + +const placeholderStore = (state = {}) => state; + describe('rampsSlice', () => { let store: Store; @@ -25,7 +93,18 @@ describe('rampsSlice', () => { store = configureStore({ reducer: { ramps: rampsReducer, + metamask: (state) => mockMetamaskStore, + DNS: placeholderStore, + activeTab: placeholderStore, + appState: placeholderStore, + confirm: placeholderStore, + confirmAlerts: placeholderStore, + confirmTransaction: placeholderStore, + history: placeholderStore, + localeMessages: placeholderStore, + send: placeholderStore, }, + preloadedState: mockEntireState, }); mockedRampAPI.getNetworks.mockReset(); }); @@ -151,37 +230,43 @@ describe('rampsSlice', () => { describe('getIsNativeTokenBuyable', () => { const getCurrentChainIdMock = jest.mocked(getCurrentChainId); + const getMultichainIsBitcoinMock = jest.mocked(getMultichainIsBitcoin); afterEach(() => { jest.restoreAllMocks(); }); it('should return true when current chain is buyable', () => { + getMultichainIsBitcoinMock.mockReturnValue(false); getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.MAINNET); const state = store.getState(); expect(getIsNativeTokenBuyable(state)).toEqual(true); }); it('should return false when current chain is not buyable', () => { + getMultichainIsBitcoinMock.mockReturnValue(false); getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.GOERLI); const state = store.getState(); expect(getIsNativeTokenBuyable(state)).toEqual(false); }); it('should return false when current chain is not a valid hex string', () => { + getMultichainIsBitcoinMock.mockReturnValue(false); getCurrentChainIdMock.mockReturnValue('0x'); const state = store.getState(); expect(getIsNativeTokenBuyable(state)).toEqual(false); }); it('should return false when buyable chains is a corrupted array', () => { - const mockState = { + getMultichainIsBitcoinMock.mockReturnValue(false); + const mockCorruptedState = { + ...store.getState(), ramps: { buyableChains: [null, null, null], }, }; getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.MAINNET); - expect(getIsNativeTokenBuyable(mockState)).toEqual(false); + expect(getIsNativeTokenBuyable(mockCorruptedState)).toEqual(false); }); }); }); diff --git a/ui/ducks/ramps/ramps.ts b/ui/ducks/ramps/ramps.ts index a169a7e8e552..4dd3731ad2ed 100644 --- a/ui/ducks/ramps/ramps.ts +++ b/ui/ducks/ramps/ramps.ts @@ -4,9 +4,9 @@ import { getCurrentChainId, getUseExternalServices } from '../../selectors'; import RampAPI from '../../helpers/ramps/rampApi/rampAPI'; import { hexToDecimal } from '../../../shared/modules/conversion.utils'; import { getMultichainIsBitcoin } from '../../selectors/multichain'; +import { MultichainNetworks } from '../../../shared/constants/multichain/networks'; import { defaultBuyableChains } from './constants'; import { AggregatorNetwork } from './types'; -import { MultichainNetworks } from '../../../shared/constants/multichain/networks'; export const fetchBuyableChains = createAsyncThunk( 'ramps/fetchBuyableChains', @@ -64,10 +64,12 @@ export const getBuyableChains = (state: any) => export const getIsBitcoinBuyable = createSelector( [getBuyableChains], (buyableChains) => - buyableChains.some( - (network: AggregatorNetwork) => - network.chainId === MultichainNetworks.BITCOIN && network.active, - ), + buyableChains + .filter(Boolean) + .some( + (network: AggregatorNetwork) => + network.chainId === MultichainNetworks.BITCOIN && network.active, + ), ); export const getIsNativeTokenBuyable = createSelector( diff --git a/ui/ducks/ramps/types.ts b/ui/ducks/ramps/types.ts index 5cb36846a89e..9620e3c3cb77 100644 --- a/ui/ducks/ramps/types.ts +++ b/ui/ducks/ramps/types.ts @@ -1,6 +1,6 @@ export type AggregatorNetwork = { active: boolean; - chainId: string; + chainId: number | string; chainName: string; nativeTokenSupported: boolean; shortName: string; From 653de6989ec2f1deeee51dfa355f4fe289c4a641 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 3 Jul 2024 07:54:58 -0600 Subject: [PATCH 08/13] test: adds tests for ramps slice btc logic and selectors --- ui/ducks/ramps/ramps.test.ts | 76 +++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/ui/ducks/ramps/ramps.test.ts b/ui/ducks/ramps/ramps.test.ts index 5f0430f32bd6..458a3b94b872 100644 --- a/ui/ducks/ramps/ramps.test.ts +++ b/ui/ducks/ramps/ramps.test.ts @@ -7,9 +7,11 @@ import { CHAIN_IDS } from '../../../shared/constants/network'; import mockEntireState from '../../../test/data/mock-state.json'; import { MultichainNativeAssets } from '../../../shared/constants/multichain/assets'; import { getMultichainIsBitcoin } from '../../selectors/multichain'; +import { MultichainNetworks } from '../../../shared/constants/multichain/networks'; import rampsReducer, { fetchBuyableChains, getBuyableChains, + getIsBitcoinBuyable, getIsNativeTokenBuyable, } from './ramps'; import { defaultBuyableChains } from './constants'; @@ -66,7 +68,6 @@ const mockMetamaskStore = { completedOnboarding: true, // Used when clicking on some buttons metaMetricsId: mockMetaMetricsId, - // Override state if provided }; jest.mock('../../helpers/ramps/rampApi/rampAPI'); @@ -244,20 +245,47 @@ describe('rampsSlice', () => { }); it('should return false when current chain is not buyable', () => { - getMultichainIsBitcoinMock.mockReturnValue(false); getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.GOERLI); + getMultichainIsBitcoinMock.mockReturnValue(false); + const mockBuyableChains = [{ chainId: CHAIN_IDS.MAINNET, active: true }]; + store.dispatch({ + type: 'ramps/setBuyableChains', + payload: mockBuyableChains, + }); const state = store.getState(); - expect(getIsNativeTokenBuyable(state)).toEqual(false); + expect(getIsNativeTokenBuyable(state)).toBe(false); }); - it('should return false when current chain is not a valid hex string', () => { - getMultichainIsBitcoinMock.mockReturnValue(false); - getCurrentChainIdMock.mockReturnValue('0x'); + it('should return true when Bitcoin is buyable and current chain is Bitcoin', () => { + getCurrentChainIdMock.mockReturnValue(MultichainNetworks.BITCOIN); + getMultichainIsBitcoinMock.mockReturnValue(true); + const mockBuyableChains = [ + { chainId: MultichainNetworks.BITCOIN, active: true }, + ]; + store.dispatch({ + type: 'ramps/setBuyableChains', + payload: mockBuyableChains, + }); const state = store.getState(); - expect(getIsNativeTokenBuyable(state)).toEqual(false); + expect(getIsNativeTokenBuyable(state)).toBe(true); + }); + + it('should return false when Bitcoin is not buyable and current chain is Bitcoin', () => { + getCurrentChainIdMock.mockReturnValue(MultichainNetworks.BITCOIN); + getMultichainIsBitcoinMock.mockReturnValue(true); + const mockBuyableChains = [ + { chainId: MultichainNetworks.BITCOIN, active: false }, + ]; + store.dispatch({ + type: 'ramps/setBuyableChains', + payload: mockBuyableChains, + }); + const state = store.getState(); + expect(getIsNativeTokenBuyable(state)).toBe(false); }); it('should return false when buyable chains is a corrupted array', () => { + getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.MAINNET); getMultichainIsBitcoinMock.mockReturnValue(false); const mockCorruptedState = { ...store.getState(), @@ -265,8 +293,38 @@ describe('rampsSlice', () => { buyableChains: [null, null, null], }, }; - getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.MAINNET); - expect(getIsNativeTokenBuyable(mockCorruptedState)).toEqual(false); + expect(getIsNativeTokenBuyable(mockCorruptedState)).toBe(false); + }); + }); + + describe('getIsBitcoinBuyable', () => { + it('should return false when Bitcoin is not in buyableChains', () => { + const state = store.getState(); + expect(getIsBitcoinBuyable(state)).toBe(false); + }); + + it('should return true when Bitcoin is in buyableChains and active', () => { + const mockBuyableChains = [ + { chainId: MultichainNetworks.BITCOIN, active: true }, + ]; + store.dispatch({ + type: 'ramps/setBuyableChains', + payload: mockBuyableChains, + }); + const state = store.getState(); + expect(getIsBitcoinBuyable(state)).toBe(true); + }); + + it('should return false when Bitcoin is in buyableChains but not active', () => { + const mockBuyableChains = [ + { chainId: MultichainNetworks.BITCOIN, active: false }, + ]; + store.dispatch({ + type: 'ramps/setBuyableChains', + payload: mockBuyableChains, + }); + const state = store.getState(); + expect(getIsBitcoinBuyable(state)).toBe(false); }); }); }); From 496cd231827f902cef7db030bf6ffdaab29d9909 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 3 Jul 2024 07:59:13 -0600 Subject: [PATCH 09/13] test: removes unneeded code in ramp slice test --- ui/ducks/ramps/ramps.test.ts | 73 +----------------------------------- 1 file changed, 1 insertion(+), 72 deletions(-) diff --git a/ui/ducks/ramps/ramps.test.ts b/ui/ducks/ramps/ramps.test.ts index 458a3b94b872..c943a73fcf5c 100644 --- a/ui/ducks/ramps/ramps.test.ts +++ b/ui/ducks/ramps/ramps.test.ts @@ -1,11 +1,7 @@ import { configureStore, Store } from '@reduxjs/toolkit'; -import { Cryptocurrency } from '@metamask/assets-controllers'; -import { BtcAccountType, BtcMethod } from '@metamask/keyring-api'; import RampAPI from '../../helpers/ramps/rampApi/rampAPI'; import { getCurrentChainId, getUseExternalServices } from '../../selectors'; import { CHAIN_IDS } from '../../../shared/constants/network'; -import mockEntireState from '../../../test/data/mock-state.json'; -import { MultichainNativeAssets } from '../../../shared/constants/multichain/assets'; import { getMultichainIsBitcoin } from '../../selectors/multichain'; import { MultichainNetworks } from '../../../shared/constants/multichain/networks'; import rampsReducer, { @@ -16,60 +12,6 @@ import rampsReducer, { } from './ramps'; import { defaultBuyableChains } from './constants'; -const mockMetaMetricsId = 'deadbeef'; -const mockNonEvmBalance = '1'; -const mockNonEvmAccount = { - address: 'bc1qwl8399fz829uqvqly9tcatgrgtwp3udnhxfq4k', - id: '542490c8-d178-433b-9f31-f680b11f45a5', - metadata: { - name: 'Bitcoin Account', - keyring: { - type: 'Snap Keyring', - }, - snap: { - id: 'btc-snap-id', - name: 'btc-snap-name', - }, - }, - options: {}, - methods: [BtcMethod.SendMany], - type: BtcAccountType.P2wpkh, -}; - -const mockMetamaskStore = { - ...mockEntireState.metamask, - internalAccounts: { - accounts: { - [mockNonEvmAccount.id]: mockNonEvmAccount, - }, - selectedAccount: mockNonEvmAccount.id, - }, - // (Multichain) BalancesController - balances: { - [mockNonEvmAccount.id]: { - [MultichainNativeAssets.BITCOIN]: { - amount: mockNonEvmBalance, - unit: 'BTC', - }, - }, - }, - // (Multichain) RatesController - fiatCurrency: 'usd', - rates: { - [Cryptocurrency.Btc]: { - conversionRate: '1.000', - conversionDate: 0, - }, - }, - cryptocurrencies: [Cryptocurrency.Btc], - // Required, during onboarding, the extension will assume we're in an "EVM context", meaning - // most multichain selectors will not use non-EVM logic despite having a non-EVM - // selected account - completedOnboarding: true, - // Used when clicking on some buttons - metaMetricsId: mockMetaMetricsId, -}; - jest.mock('../../helpers/ramps/rampApi/rampAPI'); const mockedRampAPI = RampAPI as jest.Mocked; @@ -85,8 +27,6 @@ jest.mock('../../selectors/multichain', () => ({ getMultichainIsBitcoin: jest.fn(), })); -const placeholderStore = (state = {}) => state; - describe('rampsSlice', () => { let store: Store; @@ -94,18 +34,7 @@ describe('rampsSlice', () => { store = configureStore({ reducer: { ramps: rampsReducer, - metamask: (state) => mockMetamaskStore, - DNS: placeholderStore, - activeTab: placeholderStore, - appState: placeholderStore, - confirm: placeholderStore, - confirmAlerts: placeholderStore, - confirmTransaction: placeholderStore, - history: placeholderStore, - localeMessages: placeholderStore, - send: placeholderStore, }, - preloadedState: mockEntireState, }); mockedRampAPI.getNetworks.mockReset(); }); @@ -238,8 +167,8 @@ describe('rampsSlice', () => { }); it('should return true when current chain is buyable', () => { - getMultichainIsBitcoinMock.mockReturnValue(false); getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.MAINNET); + getMultichainIsBitcoinMock.mockReturnValue(false); const state = store.getState(); expect(getIsNativeTokenBuyable(state)).toEqual(true); }); From 91cff94dc082b2e6274322544a19c21596ca0916 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 3 Jul 2024 08:37:34 -0600 Subject: [PATCH 10/13] test: fixes eth-overview test for disabled buy button --- ui/components/app/wallet-overview/eth-overview.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/components/app/wallet-overview/eth-overview.test.js b/ui/components/app/wallet-overview/eth-overview.test.js index 0d079a32f104..2f913bcdb6fb 100644 --- a/ui/components/app/wallet-overview/eth-overview.test.js +++ b/ui/components/app/wallet-overview/eth-overview.test.js @@ -470,7 +470,6 @@ describe('EthOverview', () => { describe('Disabled buttons when an account cannot sign transactions', () => { const buttonTestCases = [ - { testId: ETH_OVERVIEW_BUY, buttonText: 'Buy & Sell' }, { testId: ETH_OVERVIEW_SEND, buttonText: 'Send' }, { testId: ETH_OVERVIEW_SWAP, buttonText: 'Swap' }, { testId: ETH_OVERVIEW_BRIDGE, buttonText: 'Bridge' }, From 75c8f5bae4d60d524182cd98df7838455194ad1c Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 3 Jul 2024 09:16:08 -0600 Subject: [PATCH 11/13] add conditional compilation comments for build configurations --- ui/components/app/wallet-overview/btc-overview.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/components/app/wallet-overview/btc-overview.tsx b/ui/components/app/wallet-overview/btc-overview.tsx index 8ca498071045..03fdb5407f10 100644 --- a/ui/components/app/wallet-overview/btc-overview.tsx +++ b/ui/components/app/wallet-overview/btc-overview.tsx @@ -14,7 +14,9 @@ type BtcOverviewProps = { const BtcOverview = ({ className }: BtcOverviewProps) => { const { chainId } = useSelector(getMultichainProviderConfig); const balance = useSelector(getMultichainSelectedAccountCachedBalance); + ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) const isBtcBuyable = useSelector(getIsBitcoinBuyable); + ///: END:ONLY_INCLUDE_IF return ( Date: Wed, 3 Jul 2024 11:32:35 -0600 Subject: [PATCH 12/13] chore: some small fixes from code review --- ui/components/app/asset-list/asset-list.js | 4 ++-- ui/components/app/wallet-overview/btc-overview.test.tsx | 2 +- ui/components/app/wallet-overview/btc-overview.tsx | 2 ++ ui/ducks/ramps/types.ts | 4 +++- ui/selectors/multichain.ts | 4 ++-- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index 5a03bfc14040..daf425a5d096 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -123,7 +123,7 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { // for EVM assets const shouldShowTokensLinks = showTokensLinks ?? isEvm; - const isBTC = useSelector(getMultichainIsBitcoin); + const isBtc = useSelector(getMultichainIsBitcoin); let isStakeable = isMainnet && isEvm; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -144,7 +144,7 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { shouldShowBuy ? ( { - const base = isEVM ? 16 : 10; + (isEvm, balance) => { + const base = isEvm ? 16 : 10; const numericBalance = new Numeric(balance, base); return numericBalance.isZero(); }, From 785472f2b589a3acdff51dd97bebc78b9cbdfdb3 Mon Sep 17 00:00:00 2001 From: georgeweiler Date: Wed, 3 Jul 2024 11:44:59 -0600 Subject: [PATCH 13/13] add conditional compilation comments for build configurations --- ui/components/app/asset-list/asset-list.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index daf425a5d096..31514a8c60cc 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -19,7 +19,9 @@ import { getMultichainCurrencyImage, getMultichainIsMainnet, getMultichainSelectedAccountCachedBalance, + ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) getMultichainIsBitcoin, + ///: END:ONLY_INCLUDE_IF getMultichainSelectedAccountCachedBalanceIsZero, } from '../../../selectors/multichain'; import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'; @@ -123,7 +125,9 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { // for EVM assets const shouldShowTokensLinks = showTokensLinks ?? isEvm; + ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) const isBtc = useSelector(getMultichainIsBitcoin); + ///: END:ONLY_INCLUDE_IF let isStakeable = isMainnet && isEvm; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi)