From d435f9f45a3fe4cf8e261ab7544fcd47211291ae Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Thu, 6 Mar 2025 15:56:53 -0600 Subject: [PATCH 1/8] fix: WIP solana network in bridge fix --- .../asset-picker-modal/asset-picker-modal.tsx | 81 +++++++++------- .../solana-account-creation-prompt.tsx | 95 +++++++++++++++++++ ui/ducks/bridge/selectors.ts | 49 +++++++++- 3 files changed, 189 insertions(+), 36 deletions(-) create mode 100644 ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx index 63ca110458ca..870c05ed2e25 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx @@ -59,6 +59,7 @@ import { getMultichainSelectedAccountCachedBalance, getMultichainIsEvm, } from '../../../../selectors/multichain'; +import { needsSolanaAccountForDestination } from '../../../../ducks/bridge/selectors'; import { MultichainNetworks } from '../../../../../shared/constants/multichain/networks'; import { getAssetsMetadata } from '../../../../selectors/assets'; import { Numeric } from '../../../../../shared/modules/Numeric'; @@ -73,6 +74,7 @@ import { AssetPickerModalNftTab } from './asset-picker-modal-nft-tab'; import AssetList from './AssetList'; import { Search } from './asset-picker-modal-search'; import { AssetPickerModalNetwork } from './asset-picker-modal-network'; +import { SolanaAccountCreationPrompt } from './solana-account-creation-prompt'; type AssetPickerModalProps = { header: JSX.Element | string | null; @@ -141,6 +143,9 @@ export function AssetPickerModal({ const [searchQuery, setSearchQuery] = useState(''); + // Check if we need to show the Solana account creation UI + const needsSolanaAccount = useSelector(needsSolanaAccountForDestination); + const swapsBlockedTokens = useSelector(getSwapsBlockedTokens); const memoizedSwapsBlockedTokens = useMemo(() => { return new Set(swapsBlockedTokens); @@ -535,43 +540,53 @@ export function AssetPickerModal({ )} - - - setSearchQuery(value)} - autoFocus={autoFocus} - /> - - - ( + {/* Show Solana account creation prompt if the destination is Solana but no Solana account exists */} + {needsSolanaAccount ? ( + { + // Refresh the component after account creation + onClose(); + }} + /> + ) : ( + + setSearchQuery(value)} + autoFocus={autoFocus} /> - )} - /> - + + + ( + setSearchQuery(value)} + /> + )} + /> + + )} diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx new file mode 100644 index 000000000000..b13e3c437ae6 --- /dev/null +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { + Box, + Text, + Button, + ButtonSize, + ButtonVariant, +} from '../../../component-library'; +import { + Display, + TextAlign, + TextColor, + TextVariant, + AlignItems, + JustifyContent, + FlexDirection, +} from '../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { MultichainNetworks } from '../../../../../shared/constants/multichain/networks'; +import { getMetaMaskKeyrings } from '../../../../selectors'; +import { + WalletClientType, + useMultichainWalletSnapClient, +} from '../../../../hooks/accounts/useMultichainWalletSnapClient'; + +export const SolanaAccountCreationPrompt = ({ + onSuccess, +}: { + onSuccess: () => void; +}) => { + const t = useI18nContext(); + const solanaWalletSnapClient = useMultichainWalletSnapClient( + WalletClientType.Solana, + ); + const [primaryKeyring] = useSelector(getMetaMaskKeyrings); + const [isCreating, setIsCreating] = React.useState(false); + + const handleCreateAccount = async () => { + try { + setIsCreating(true); + await solanaWalletSnapClient.createAccount( + MultichainNetworks.SOLANA, + primaryKeyring.metadata.id, + ); + onSuccess(); + } catch (error) { + console.error('Error creating Solana account:', error); + } finally { + setIsCreating(false); + } + }; + + return ( + + + {/* {t('createSolanaAccountTitle')} */} + create solana account + + + + create 2{/* {t('createSolanaAccountToUseDestinationDescription')} */} + + + + + ); +}; diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 048b8dc30a51..2385ff02118c 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -4,6 +4,8 @@ import type { ///: END:ONLY_INCLUDE_IF NetworkState, } from '@metamask/network-controller'; +import { SolAccountType } from '@metamask/keyring-api'; +import { AccountsControllerState } from '@metamask/accounts-controller'; import { orderBy, uniqBy } from 'lodash'; import { createSelector } from 'reselect'; import type { GasFeeEstimates } from '@metamask/gas-fee-controller'; @@ -79,7 +81,8 @@ import type { BridgeState } from './bridge'; export type BridgeAppState = { metamask: BridgeControllerState & - NetworkState & { + NetworkState & + AccountsControllerState & { useExternalServices: boolean; currencyRates: { [currency: string]: { @@ -91,6 +94,18 @@ export type BridgeAppState = { bridge: BridgeState; }; +// checks if the user has any solana accounts created +export const hasSolanaAccounts = (state: BridgeAppState) => { + // Access accounts from the state + const accounts = state.metamask.internalAccounts?.accounts || {}; + + // Check if any account is a Solana account + return Object.values(accounts).some((account) => { + const { DataAccount } = SolAccountType; + return Boolean(account && account.type === DataAccount); + }); +}; + // only includes networks user has added export const getAllBridgeableNetworks = createDeepEqualSelector( getNetworkConfigurationsByChainId, @@ -124,8 +139,18 @@ export const getAllBridgeableNetworks = createDeepEqualSelector( export const getFromChains = createDeepEqualSelector( getAllBridgeableNetworks, (state: BridgeAppState) => state.metamask.bridgeState?.bridgeFeatureFlags, - (allBridgeableNetworks, bridgeFeatureFlags) => { - return allBridgeableNetworks.filter( + (state: BridgeAppState) => hasSolanaAccounts(state), + (allBridgeableNetworks, bridgeFeatureFlags, hasSolanaAccount) => { + // First filter out Solana from source chains if no Solana account exists + const filteredNetworks = hasSolanaAccount + ? allBridgeableNetworks + : allBridgeableNetworks.filter( + // @ts-expect-error: gotta fix type here. + ({ chainId }) => chainId !== MultichainNetworks.SOLANA, + ); + + // Then apply the standard filter for active source chains + return filteredNetworks.filter( ({ chainId }) => bridgeFeatureFlags[BridgeFeatureFlagsKey.EXTENSION_CONFIG].chains[ formatChainIdToCaip(chainId) @@ -680,6 +705,24 @@ export const isBridgeSolanaEnabled = createDeepEqualSelector( }, ); +/** + * Checks if the destination chain is Solana and the user has no Solana accounts + */ +export const needsSolanaAccountForDestination = createDeepEqualSelector( + getToChain, + (state: BridgeAppState) => hasSolanaAccounts(state), + (toChain, hasSolanaAccount) => { + if (!toChain) { + return false; + } + + const isSolanaDestination = + formatChainIdToCaip(toChain.chainId) === MultichainNetworks.SOLANA; + + return isSolanaDestination && !hasSolanaAccount; + }, +); + export const getIsToOrFromSolana = createSelector( getFromChain, getToChain, From 5d1d068c66a49cd080c11a21c16d9f72af301c11 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Thu, 6 Mar 2025 16:46:10 -0600 Subject: [PATCH 2/8] chore: add localization, update styling --- app/_locales/en/messages.json | 6 ++++++ app/_locales/en_GB/messages.json | 7 +++++++ .../solana-account-creation-prompt.tsx | 11 +++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index ac39224ee8c1..fbc776ae5d3e 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -758,6 +758,12 @@ "bridgeConfirmTwoTransactions": { "message": "You'll need to confirm 2 transactions on your hardware wallet:" }, + "bridgeCreateSolanaAccountDescription": { + "message": "To swap to the Solana network, you'll need to create a Solana account first." + }, + "bridgeCreateSolanaAccountTitle": { + "message": "You haven't enabled Solana yet." + }, "bridgeEnterAmount": { "message": "Select amount" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index ac39224ee8c1..408019429043 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -758,6 +758,13 @@ "bridgeConfirmTwoTransactions": { "message": "You'll need to confirm 2 transactions on your hardware wallet:" }, + "bridgeCreateSolanaAccountDescription": { + "message": "To swap to the Solana network, you'll need to create a Solana account first." + }, + "bridgeCreateSolanaAccountTitle": { + "message": "You haven't enabled Solana yet." + }, + "bridgeEnterAmount": { "message": "Select amount" }, diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx index b13e3c437ae6..81ba1244c1d8 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx @@ -64,26 +64,25 @@ export const SolanaAccountCreationPrompt = ({ style={{ minHeight: '300px' }} > - {/* {t('createSolanaAccountTitle')} */} - create solana account + {t('bridgeCreateSolanaAccountTitle')} - create 2{/* {t('createSolanaAccountToUseDestinationDescription')} */} + {t('bridgeCreateSolanaAccountDescription')} From 32be0f60208b97ef0d4159e5108b1906ed531710 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Thu, 6 Mar 2025 18:22:28 -0600 Subject: [PATCH 4/8] chore: remove unused import --- .../asset-picker-modal/solana-account-creation-prompt.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx index e7eccb6d90b4..d9c4be32aabb 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/solana-account-creation-prompt.tsx @@ -15,7 +15,6 @@ import { AlignItems, JustifyContent, FlexDirection, - BlockSize, } from '../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { MultichainNetworks } from '../../../../../shared/constants/multichain/networks'; From d3784be6b6d65c977c73b49ba719274858a99d71 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Fri, 7 Mar 2025 11:54:29 -0600 Subject: [PATCH 5/8] chore: copy update --- app/_locales/en/messages.json | 4 ++-- app/_locales/en_GB/messages.json | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index fbc776ae5d3e..d78d7aeb21cb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -759,10 +759,10 @@ "message": "You'll need to confirm 2 transactions on your hardware wallet:" }, "bridgeCreateSolanaAccountDescription": { - "message": "To swap to the Solana network, you'll need to create a Solana account first." + "message": "To swap to the Solana network, you need an account and receiving address." }, "bridgeCreateSolanaAccountTitle": { - "message": "You haven't enabled Solana yet." + "message": "You'll need a Solana account first." }, "bridgeEnterAmount": { "message": "Select amount" diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 408019429043..d78d7aeb21cb 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -759,12 +759,11 @@ "message": "You'll need to confirm 2 transactions on your hardware wallet:" }, "bridgeCreateSolanaAccountDescription": { - "message": "To swap to the Solana network, you'll need to create a Solana account first." + "message": "To swap to the Solana network, you need an account and receiving address." }, "bridgeCreateSolanaAccountTitle": { - "message": "You haven't enabled Solana yet." + "message": "You'll need a Solana account first." }, - "bridgeEnterAmount": { "message": "Select amount" }, From d4fbfba9a927c304fb10aeef40a9b4cde24580e0 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Sat, 8 Mar 2025 16:47:05 -0600 Subject: [PATCH 6/8] fix: revert getFromChains selector changes to see if fixes CI --- ui/ducks/bridge/selectors.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 2385ff02118c..63bd490bc0ee 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -139,18 +139,8 @@ export const getAllBridgeableNetworks = createDeepEqualSelector( export const getFromChains = createDeepEqualSelector( getAllBridgeableNetworks, (state: BridgeAppState) => state.metamask.bridgeState?.bridgeFeatureFlags, - (state: BridgeAppState) => hasSolanaAccounts(state), - (allBridgeableNetworks, bridgeFeatureFlags, hasSolanaAccount) => { - // First filter out Solana from source chains if no Solana account exists - const filteredNetworks = hasSolanaAccount - ? allBridgeableNetworks - : allBridgeableNetworks.filter( - // @ts-expect-error: gotta fix type here. - ({ chainId }) => chainId !== MultichainNetworks.SOLANA, - ); - - // Then apply the standard filter for active source chains - return filteredNetworks.filter( + (allBridgeableNetworks, bridgeFeatureFlags) => { + return allBridgeableNetworks.filter( ({ chainId }) => bridgeFeatureFlags[BridgeFeatureFlagsKey.EXTENSION_CONFIG].chains[ formatChainIdToCaip(chainId) From 189a52009cacfc53e917637a47b1cd2f8dd06284 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Sat, 8 Mar 2025 17:05:37 -0600 Subject: [PATCH 7/8] fix: undo revert --- ui/ducks/bridge/selectors.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 63bd490bc0ee..2385ff02118c 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -139,8 +139,18 @@ export const getAllBridgeableNetworks = createDeepEqualSelector( export const getFromChains = createDeepEqualSelector( getAllBridgeableNetworks, (state: BridgeAppState) => state.metamask.bridgeState?.bridgeFeatureFlags, - (allBridgeableNetworks, bridgeFeatureFlags) => { - return allBridgeableNetworks.filter( + (state: BridgeAppState) => hasSolanaAccounts(state), + (allBridgeableNetworks, bridgeFeatureFlags, hasSolanaAccount) => { + // First filter out Solana from source chains if no Solana account exists + const filteredNetworks = hasSolanaAccount + ? allBridgeableNetworks + : allBridgeableNetworks.filter( + // @ts-expect-error: gotta fix type here. + ({ chainId }) => chainId !== MultichainNetworks.SOLANA, + ); + + // Then apply the standard filter for active source chains + return filteredNetworks.filter( ({ chainId }) => bridgeFeatureFlags[BridgeFeatureFlagsKey.EXTENSION_CONFIG].chains[ formatChainIdToCaip(chainId) From 1e09d77176cc1e87c49e102d72bb62ab52688d02 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Sat, 8 Mar 2025 17:11:05 -0600 Subject: [PATCH 8/8] feat: attempt to fix tests --- ui/ducks/bridge/selectors.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 2385ff02118c..b8bd38bc2ea8 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -175,7 +175,7 @@ export const getToChains = createDeepEqualSelector( (allBridgeableNetworks, bridgeFeatureFlags) => uniqBy([...allBridgeableNetworks, ...FEATURED_RPCS], 'chainId').filter( ({ chainId }) => - bridgeFeatureFlags[BridgeFeatureFlagsKey.EXTENSION_CONFIG].chains[ + bridgeFeatureFlags?.[BridgeFeatureFlagsKey.EXTENSION_CONFIG]?.chains?.[ formatChainIdToCaip(chainId) ]?.isActiveDest, ), @@ -196,12 +196,14 @@ export const getTopAssetsFromFeatureFlags = ( export const getToChain = createSelector( getToChains, - (state: BridgeAppState) => state.bridge.toChainId, + (state: BridgeAppState) => state.bridge?.toChainId, (toChains, toChainId) => - toChains.find( - ({ chainId }) => - chainId === toChainId || formatChainIdToCaip(chainId) === toChainId, - ), + toChainId + ? toChains.find( + ({ chainId }) => + chainId === toChainId || formatChainIdToCaip(chainId) === toChainId, + ) + : undefined, ); export const getFromToken = createSelector(