From 4667be99f618a6c654e84790adb7df5f8771745c Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 27 Feb 2025 15:26:38 +0100 Subject: [PATCH 1/2] Add `AssetSelector` --- app/_locales/en/messages.json | 3 + .../snap-interface-controller-messenger.ts | 8 +- builds.yml | 2 +- .../safe-component-list.js | 2 + .../app/snaps/snap-ui-asset-selector/index.ts | 1 + .../snap-ui-asset-selector.tsx | 124 ++++++++++ .../useSnapAssetDisplay.tsx | 218 ++++++++++++++++++ .../components/asset-selector.ts | 19 ++ .../snap-ui-renderer/components/field.ts | 24 +- .../snap-ui-renderer/components/index.ts | 2 + .../app/snaps/snap-ui-renderer/utils.ts | 1 + .../snap-ui-selector/snap-ui-selector.tsx | 45 +++- ui/ducks/locale/locale.ts | 4 +- ui/selectors/accounts.ts | 11 + ui/selectors/multichain.ts | 7 + ui/selectors/selectors.js | 6 + 16 files changed, 464 insertions(+), 13 deletions(-) create mode 100644 ui/components/app/snaps/snap-ui-asset-selector/index.ts create mode 100644 ui/components/app/snaps/snap-ui-asset-selector/snap-ui-asset-selector.tsx create mode 100644 ui/components/app/snaps/snap-ui-asset-selector/useSnapAssetDisplay.tsx create mode 100644 ui/components/app/snaps/snap-ui-renderer/components/asset-selector.ts diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index ac39224ee8c1..5ab5a87812d1 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -5020,6 +5020,9 @@ "snapResultSuccessDescription": { "message": "$1 is ready to use" }, + "snapUIAssetSelectorTitle": { + "message": "Select an asset" + }, "snapUpdateAlertDescription": { "message": "Get the latest version of $1", "description": "Description used in Snap update alert banner when snap update is available. $1 is the Snap name." diff --git a/app/scripts/controller-init/messengers/snaps/snap-interface-controller-messenger.ts b/app/scripts/controller-init/messengers/snaps/snap-interface-controller-messenger.ts index ce5cc2c3619a..ee4660cfd6e2 100644 --- a/app/scripts/controller-init/messengers/snaps/snap-interface-controller-messenger.ts +++ b/app/scripts/controller-init/messengers/snaps/snap-interface-controller-messenger.ts @@ -6,13 +6,17 @@ import { } from '@metamask/approval-controller'; import { MaybeUpdateState, TestOrigin } from '@metamask/phishing-controller'; import { NotificationListUpdatedEvent } from '@metamask/notification-services-controller/notification-services'; +import { AccountsControllerGetAccountByAddressAction } from '@metamask/accounts-controller'; +import { MultichainAssetsControllerGetStateAction } from '@metamask/assets-controllers'; type Actions = | MaybeUpdateState | TestOrigin | HasApprovalRequest | AcceptRequest - | GetSnap; + | GetSnap + | AccountsControllerGetAccountByAddressAction + | MultichainAssetsControllerGetStateAction; type Events = NotificationListUpdatedEvent; @@ -39,6 +43,8 @@ export function getSnapInterfaceControllerMessenger( `ApprovalController:hasRequest`, `ApprovalController:acceptRequest`, `SnapController:get`, + 'AccountsController:getAccountByAddress', + 'MultichainAssetsController:getState', ], allowedEvents: ['NotificationServicesController:notificationsListUpdated'], }); diff --git a/builds.yml b/builds.yml index aab81be90cad..e5b319b281a9 100644 --- a/builds.yml +++ b/builds.yml @@ -81,7 +81,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - REJECT_INVALID_SNAPS_PLATFORM_VERSION: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/7.0.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: http://localhost:6363 - SUPPORT_LINK: https://support.metamask.io/ - SUPPORT_REQUEST_LINK: https://support.metamask.io/ - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID diff --git a/ui/components/app/metamask-template-renderer/safe-component-list.js b/ui/components/app/metamask-template-renderer/safe-component-list.js index d73b4fd85c14..24fac42b800e 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -49,6 +49,7 @@ import { SnapUIMarkdown } from '../snaps/snap-ui-markdown'; import { SnapUIRadioGroup } from '../snaps/snap-ui-radio-group'; import { SnapUISelector } from '../snaps/snap-ui-selector'; import { SnapUITooltip } from '../snaps/snap-ui-tooltip'; +import { SnapUIAssetSelector } from '../snaps/snap-ui-asset-selector'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import { SnapAccountErrorMessage } from '../../../pages/confirmations/components/snap-account-error-message'; import { SnapAccountSuccessMessage } from '../../../pages/confirmations/components/snap-account-success-message'; @@ -107,6 +108,7 @@ export const safeComponentList = { SnapUIRadioGroup, SnapUISelector, SnapUITooltip, + SnapUIAssetSelector, span: 'span', Spinner, Skeleton, diff --git a/ui/components/app/snaps/snap-ui-asset-selector/index.ts b/ui/components/app/snaps/snap-ui-asset-selector/index.ts new file mode 100644 index 000000000000..d2ee0d30ba04 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-asset-selector/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-asset-selector'; diff --git a/ui/components/app/snaps/snap-ui-asset-selector/snap-ui-asset-selector.tsx b/ui/components/app/snaps/snap-ui-asset-selector/snap-ui-asset-selector.tsx new file mode 100644 index 000000000000..3d9d84a59578 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-asset-selector/snap-ui-asset-selector.tsx @@ -0,0 +1,124 @@ +import React, { FunctionComponent } from 'react'; +import { CaipAccountId, CaipChainId } from '@metamask/utils'; + +import { SnapUISelector } from '../snap-ui-selector'; + +import { + Display, + FlexDirection, + TextAlign, + TextColor, + TextVariant, + AlignItems, + BackgroundColor, + BlockSize, +} from '../../../../helpers/constants/design-system'; +import { + Box, + Text, + AvatarToken, + BadgeWrapper, + AvatarNetwork, + AvatarNetworkSize, +} from '../../../component-library'; + +import { SnapUIAsset, useSnapAssetSelectorData } from './useSnapAssetDisplay'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +const SnapUIAssetSelectorOption: FunctionComponent = ({ + icon, + symbol, + name, + balance, + fiat, + networkName, + networkIcon, +}) => ( + + + + } + > + + + + + + {name} + + + {networkName} + + + + + {balance} {symbol} + + + {fiat} + + + +); + +type SnapUIAssetSelectorProps = { + name: string; + addresses: CaipAccountId[]; + chainIds?: CaipChainId[]; + disabled?: boolean; + form?: string; + label?: string; + error?: string; +}; + +export const SnapUIAssetSelector: FunctionComponent< + SnapUIAssetSelectorProps +> = ({ addresses, chainIds, disabled, ...props }) => { + const t = useI18nContext(); + const assets = useSnapAssetSelectorData({ addresses, chainIds }); + + const options = assets.map(({ address, name, symbol }) => ({ + key: 'asset', + value: { asset: address, name, symbol }, + disabled: false, + })); + + const optionComponents = assets.map((asset, index) => ( + + )); + + return ( + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-asset-selector/useSnapAssetDisplay.tsx b/ui/components/app/snaps/snap-ui-asset-selector/useSnapAssetDisplay.tsx new file mode 100644 index 000000000000..6ed143e4a883 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-asset-selector/useSnapAssetDisplay.tsx @@ -0,0 +1,218 @@ +import { + CaipAccountId, + CaipAssetType, + CaipChainId, + KnownCaipNamespace, + parseCaipAccountId, + parseCaipChainId, + toCaipAssetType, + toCaipChainId, +} from '@metamask/utils'; +import { useSelector } from 'react-redux'; +import { InternalAccount } from '@metamask/keyring-internal-api'; +import { + getInternalAccountByAddress, + getMemoizedCurrentCurrency, + getMemoizedInternalAccountByAddress, +} from '../../../../selectors'; +import { + getMultiChainAssets, + getTokenBalancesEvm, +} from '../../../../selectors/assets'; +import { TokenWithFiatAmount } from '../../assets/types'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; +import { getIntlLocale } from '../../../../ducks/locale/locale'; +import { formatWithThreshold } from '../../assets/util/formatWithThreshold'; +import { + getImageForChainId, + getMemoizedMultichainNetworkConfigurationsByChainId, + getMultichainNetworkConfigurationsByChainId, +} from '../../../../selectors/multichain'; +import { + AllowedBridgeChainIds, + NETWORK_TO_SHORT_NETWORK_NAME_MAP, +} from '../../../../../shared/constants/bridge'; +import { + networkTitleOverrides, + TranslateFunction, +} from '../../assets/util/networkTitleOverrides'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +/** + * An asset for the SnapUIAssetSelector. + */ +export type SnapUIAsset = { + icon: string; + symbol: string; + name: string; + balance: string; + fiat: string; + chainId: CaipChainId; + address: CaipAssetType; + networkName: string; + networkIcon?: string; +}; + +/** + * A multichain asset. Derived from the EVM asset type. + */ +type MultichainAsset = Omit & { + chainId: CaipChainId; + address: CaipAssetType; +}; + +/** + * The parameters for the hook. + * + * @param addresses - The addresses to get the assets for. + * This is a list of the same address but for different chains. + * @param chainIds - The chainIds to filter the assets by. + */ +type UseSnapAssetSelectorDataParams = { + addresses: CaipAccountId[]; + chainIds?: CaipChainId[]; +}; + +/** + * Gets the assets from state and format them for the SnapUIAssetSelector. + * + * @param params - The parameters for the hook. + * @param params.addresses - The addresses to get the assets for. + * This is a list of the same address but for different chains. + * @param params.chainIds - The chainIds to filter the assets by. + * @returns The formatted assets. + */ +export const useSnapAssetSelectorData = ({ + addresses, + chainIds, +}: UseSnapAssetSelectorDataParams) => { + const t = useI18nContext(); + const currentCurrency = useSelector(getMemoizedCurrentCurrency); + const locale = useSelector(getIntlLocale); + + const parsedAccounts = addresses.map(parseCaipAccountId); + + const account = useSelector((state) => + getMemoizedInternalAccountByAddress(state, parsedAccounts[0].address), + ); + const networks = useSelector( + getMemoizedMultichainNetworkConfigurationsByChainId, + ); + + const nonEvmAssets = useSelector((state) => + getMultiChainAssets(state, account), + ); + + const evmAssets = useSelector((state) => + getTokenBalancesEvm(state, account?.address), + ); + + /** + * Formats a fiat balance. + * + * @param balance - The balance to format. + * @returns The formatted balance. + */ + const formatFiatBalance = (balance: number | null = 0) => + formatWithThreshold(balance, 0.01, locale, { + style: 'currency', + currency: currentCurrency.toUpperCase(), + }); + + /** + * Formats an EVM asset for the SnapUIAssetSelector. + * + * @param asset - The asset to format. + * @returns The formatted asset. + */ + const formatEvmAsset = (asset: TokenWithFiatAmount) => { + const chainId = toCaipChainId('eip155', BigInt(asset.chainId).toString(10)); + + const networkName = + NETWORK_TO_SHORT_NETWORK_NAME_MAP[ + asset.chainId as AllowedBridgeChainIds + ] ?? networks[asset.chainId]?.name; + + // Get the native asset name or the token name + const assetName = asset.isNative + ? networkTitleOverrides(t as TranslateFunction, { title: asset.symbol }) + : // @ts-expect-error wrong asset type + asset.name; + + // Convert the EVM asset address to a CAIP asset type + const assetNamepace = asset.isNative ? 'slip44' : 'erc20'; + const assetReference = asset.isNative ? '60' : asset.address; + const address = toCaipAssetType( + 'eip155', + BigInt(asset.chainId).toString(10), + assetNamepace, + assetReference, + ); + + return { + icon: asset.image, + symbol: asset.symbol, + name: assetName, + networkName, + networkIcon: getImageForChainId(asset.chainId), + balance: asset.balance ?? '0', + fiat: formatFiatBalance(asset.tokenFiatAmount), + chainId, + address, + }; + }; + + /** + * Formats a non-EVM asset for the SnapUIAssetSelector. + * + * @param asset - The asset to format. + * @returns The formatted asset. + */ + const formatNonEvmAsset = (asset: MultichainAsset) => { + const networkName = + NETWORK_TO_SHORT_NETWORK_NAME_MAP[ + asset.chainId as AllowedBridgeChainIds + ] ?? networks[asset.chainId]?.name; + + return { + icon: asset.image, + symbol: asset.symbol, + name: asset.title, + balance: asset.primary, + networkName, + networkIcon: getImageForChainId(asset.chainId), + fiat: formatFiatBalance(asset.secondary), + chainId: asset.chainId, + address: asset.address, + }; + }; + + // Filter the chain IDs to only include the requested ones. + const requestedChainIds = parsedAccounts + .map((chainId) => chainId) + .filter(({ chainId }) => (chainIds ? chainIds?.includes(chainId) : true)); + + // Format the assets + const formattedNonEvmAssets = nonEvmAssets.map(formatNonEvmAsset); + const formattedEvmAssets = evmAssets.map(formatEvmAsset); + + const assets: SnapUIAsset[] = [ + ...formattedNonEvmAssets, + ...formattedEvmAssets, + ]; + + // Filter the assets by the requested chain IDs + const filteredAssets = assets.filter((asset) => + requestedChainIds.some(({ chainId, chain: { namespace, reference } }) => { + // Handles the "eip155:0" case + if (namespace === KnownCaipNamespace.Eip155 && reference === '0') { + const { namespace: assetNamepace } = parseCaipChainId(asset.chainId); + return assetNamepace === namespace; + } + + return chainId === asset.chainId; + }), + ); + + return filteredAssets; +}; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/asset-selector.ts b/ui/components/app/snaps/snap-ui-renderer/components/asset-selector.ts new file mode 100644 index 000000000000..bbbeace033ac --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/asset-selector.ts @@ -0,0 +1,19 @@ +import { AssetSelectorElement, CardElement } from '@metamask/snaps-sdk/jsx'; +import { mapToTemplate } from '../utils'; +import { UIComponentFactory } from './types'; + +export const assetSelector: UIComponentFactory = ({ + element, + form, +}) => { + return { + element: 'SnapUIAssetSelector', + props: { + name: element.props.name, + addresses: element.props.addresses, + chainIds: element.props.chainIds, + disabled: element.props.disabled, + form: form, + }, + }; +}; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/field.ts b/ui/components/app/snaps/snap-ui-renderer/components/field.ts index 30686ef8926b..36b7bee9e297 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/field.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/field.ts @@ -6,6 +6,7 @@ import { RadioGroupElement, CheckboxElement, SelectorElement, + AssetSelectorElement, } from '@metamask/snaps-sdk/jsx'; import { getJsxChildren } from '@metamask/snaps-utils'; import { getPrimaryChildElementIndex, mapToTemplate } from '../utils'; @@ -13,6 +14,7 @@ import { dropdown as dropdownFn } from './dropdown'; import { radioGroup as radioGroupFn } from './radioGroup'; import { checkbox as checkboxFn } from './checkbox'; import { selector as selectorFn } from './selector'; +import { assetSelector as assetSelectorFn } from './asset-selector'; import { UIComponentFactory, UIComponentParams } from './types'; import { constructInputProps } from './input'; @@ -23,11 +25,12 @@ export const field: UIComponentFactory = ({ }) => { // For fields we don't render the Input itself, we just adapt SnapUIInput. const children = getJsxChildren(element); + console.log(children); const primaryChildIndex = getPrimaryChildElementIndex( children as JSXElement[], ); const child = children[primaryChildIndex] as JSXElement; - + console.log(child); switch (child.type) { case 'FileInput': { return { @@ -181,6 +184,25 @@ export const field: UIComponentFactory = ({ }; } + case 'AssetSelector': { + const assetSelector = child as AssetSelectorElement; + const assetSelectorMapped = assetSelectorFn({ + ...params, + element: assetSelector, + } as UIComponentParams); + + return { + ...assetSelectorMapped, + element: 'SnapUIAssetSelector', + props: { + ...assetSelectorMapped.props, + label: element.props.label, + form, + error: element.props.error, + }, + }; + } + default: throw new Error(`Invalid Field child: ${child.type}`); } diff --git a/ui/components/app/snaps/snap-ui-renderer/components/index.ts b/ui/components/app/snaps/snap-ui-renderer/components/index.ts index 50cf2e8cbe44..8bd014f54849 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/index.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/index.ts @@ -29,8 +29,10 @@ import { section } from './section'; import { avatar } from './avatar'; import { banner } from './banner'; import { skeleton } from './skeleton'; +import { assetSelector } from './asset-selector'; export const COMPONENT_MAPPING = { + AssetSelector: assetSelector, Box: box, Heading: heading, Text: text, diff --git a/ui/components/app/snaps/snap-ui-renderer/utils.ts b/ui/components/app/snaps/snap-ui-renderer/utils.ts index e9e81c07d665..addd701e74f2 100644 --- a/ui/components/app/snaps/snap-ui-renderer/utils.ts +++ b/ui/components/app/snaps/snap-ui-renderer/utils.ts @@ -135,6 +135,7 @@ export const FIELD_ELEMENT_TYPES = [ 'RadioGroup', 'Checkbox', 'Selector', + 'AssetSelector', ]; /** diff --git a/ui/components/app/snaps/snap-ui-selector/snap-ui-selector.tsx b/ui/components/app/snaps/snap-ui-selector/snap-ui-selector.tsx index 3546d2980b2c..3f4e723ad329 100644 --- a/ui/components/app/snaps/snap-ui-selector/snap-ui-selector.tsx +++ b/ui/components/app/snaps/snap-ui-selector/snap-ui-selector.tsx @@ -4,6 +4,8 @@ import React, { MouseEvent as ReactMouseEvent, } from 'react'; import classnames from 'classnames'; +import { State } from '@metamask/snaps-sdk'; +import { isObject } from '@metamask/utils'; import { Box, ButtonBase, @@ -34,24 +36,27 @@ import { useSnapInterfaceContext } from '../../../../contexts/snaps'; export type SnapUISelectorProps = { name: string; title: string; - options: { value: string; disabled: boolean }[]; + options: { key?: string; value: State; disabled: boolean }[]; optionComponents: React.ReactNode[]; form?: string; label?: string; error?: string; disabled?: boolean; + onSelect?: (value: State) => void; }; type SelectorItemProps = { - value: string; + value: State; children: React.ReactNode; - onSelect: (value: string) => void; disabled?: boolean; + selected: boolean; + onSelect: (value: State) => void; }; const SelectorItem: React.FunctionComponent = ({ value, children, + selected, onSelect, disabled, }) => { @@ -62,7 +67,9 @@ const SelectorItem: React.FunctionComponent = ({ return ( = ({ height: 'inherit', minHeight: '48px', maxHeight: '64px', + position: 'relative', }} disabled={disabled} > + {selected && ( + + )} {children} ); @@ -97,10 +119,11 @@ export const SnapUISelector: React.FunctionComponent = ({ label, error, disabled, + onSelect, }) => { const { handleInputChange, getValue } = useSnapInterfaceContext(); - const initialValue = getValue(name, form) as string; + const initialValue = getValue(name, form); const [selectedOptionValue, setSelectedOption] = useState(initialValue); const [isModalOpen, setIsModalOpen] = useState(false); @@ -118,14 +141,18 @@ export const SnapUISelector: React.FunctionComponent = ({ const handleModalClose = () => setIsModalOpen(false); - const handleSelect = (value: string) => { + const handleSelect = (value: State) => { setSelectedOption(value); + onSelect?.(value); handleInputChange(name, value, form); handleModalClose(); }; - const selectedOptionIndex = options.findIndex( - (option) => option.value === selectedOptionValue, + const selectedOptionIndex = options.findIndex((option) => + option.key && isObject(option.value) + ? option.value[option.key as keyof typeof option.value] === + selectedOptionValue?.[option.key as keyof typeof selectedOptionValue] + : option.value === selectedOptionValue, ); const selectedOption = optionComponents[selectedOptionIndex]; @@ -200,6 +227,8 @@ export const SnapUISelector: React.FunctionComponent = ({ value={options[index].value} disabled={options[index]?.disabled} onSelect={handleSelect} + selected={index === selectedOptionIndex} + key={index} > {component} diff --git a/ui/ducks/locale/locale.ts b/ui/ducks/locale/locale.ts index 2ca0cbaac4dc..91fa90eee7d3 100644 --- a/ui/ducks/locale/locale.ts +++ b/ui/ducks/locale/locale.ts @@ -1,7 +1,7 @@ -import { createSelector } from 'reselect'; import { Action } from 'redux'; // Import types for actions import * as actionConstants from '../../store/actionConstants'; import { FALLBACK_LOCALE } from '../../../shared/modules/i18n'; +import { createDeepEqualSelector } from '../../../shared/modules/selectors/util'; /** * Type for the locale messages part of the state @@ -79,7 +79,7 @@ export const getCurrentLocale = (state: AppState): string | undefined => * * @returns The user's selected locale in BCP 47 format */ -export const getIntlLocale = createSelector( +export const getIntlLocale = createDeepEqualSelector( getCurrentLocale, (locale): string => Intl.getCanonicalLocales( diff --git a/ui/selectors/accounts.ts b/ui/selectors/accounts.ts index 5d4a99f456d6..c376702321a5 100644 --- a/ui/selectors/accounts.ts +++ b/ui/selectors/accounts.ts @@ -9,6 +9,8 @@ import { isBtcMainnetAddress, isBtcTestnetAddress, } from '../../shared/lib/multichain/accounts'; +import { createDeepEqualSelector } from '../../shared/modules/selectors/util'; +import { isEqualCaseInsensitive } from '../../shared/modules/string-utils'; export type AccountsState = { metamask: AccountsControllerState; @@ -30,6 +32,15 @@ export function getInternalAccounts(state: AccountsState) { return Object.values(state.metamask.internalAccounts.accounts); } +export const getMemoizedInternalAccountByAddress = createDeepEqualSelector( + [getInternalAccounts, (_state, address) => address], + (internalAccounts, address) => { + return internalAccounts.find((account) => + isEqualCaseInsensitive(account.address, address), + ); + }, +); + export function getSelectedInternalAccount(state: AccountsState) { const accountId = state.metamask.internalAccounts.selectedAccount; return state.metamask.internalAccounts.accounts[accountId]; diff --git a/ui/selectors/multichain.ts b/ui/selectors/multichain.ts index c30f55896290..d6c82bc88df2 100644 --- a/ui/selectors/multichain.ts +++ b/ui/selectors/multichain.ts @@ -45,6 +45,7 @@ import { } from '../../shared/modules/selectors/networks'; // eslint-disable-next-line import/no-restricted-paths import { getConversionRatesForNativeAsset } from '../../app/scripts/lib/util'; +import { createDeepEqualSelector } from '../../shared/modules/selectors/util'; import { AccountsState, getInternalAccounts, @@ -520,6 +521,12 @@ export const getMultichainNetworkConfigurationsByChainId = ( }; }; +export const getMemoizedMultichainNetworkConfigurationsByChainId = + createDeepEqualSelector( + [getMultichainNetworkConfigurationsByChainId], + (networkConfigurations) => networkConfigurations, + ); + export function getLastSelectedNonEvmAccount(state: MultichainState) { const nonEvmAccounts = getInternalAccounts(state); const sortedNonEvmAccounts = nonEvmAccounts diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index e39328bca086..4820fae3dfe1 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -91,6 +91,7 @@ import { isAddressLedger, getIsUnlocked, getCompletedOnboarding, + getCurrentCurrency, } from '../ducks/metamask/metamask'; import { getLedgerWebHidConnectedStatus, @@ -3091,3 +3092,8 @@ export function getKeyringSnapAccounts(state) { return keyringAccounts; } ///: END:ONLY_INCLUDE_IF + +export const getMemoizedCurrentCurrency = createDeepEqualSelector( + [getCurrentCurrency], + (currency) => currency, +); From 9a22941fdc1e4b0e68c6ea7391405df8d2a583e3 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Fri, 7 Mar 2025 15:21:37 +0100 Subject: [PATCH 2/2] remove console.log --- ui/components/app/snaps/snap-ui-renderer/components/field.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/components/app/snaps/snap-ui-renderer/components/field.ts b/ui/components/app/snaps/snap-ui-renderer/components/field.ts index 36b7bee9e297..81ec79bb0cc1 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/field.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/field.ts @@ -25,12 +25,12 @@ export const field: UIComponentFactory = ({ }) => { // For fields we don't render the Input itself, we just adapt SnapUIInput. const children = getJsxChildren(element); - console.log(children); + const primaryChildIndex = getPrimaryChildElementIndex( children as JSXElement[], ); const child = children[primaryChildIndex] as JSXElement; - console.log(child); + switch (child.type) { case 'FileInput': { return {