From 435837106f6a231f7e04410e2aa74462a1999737 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 18 Feb 2025 17:10:16 -0800 Subject: [PATCH 01/18] chore: disable tx submission based on non-evm balance --- ui/hooks/bridge/useLatestBalance.ts | 40 ++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/ui/hooks/bridge/useLatestBalance.ts b/ui/hooks/bridge/useLatestBalance.ts index 3d60e106832e..e06c0b862af1 100644 --- a/ui/hooks/bridge/useLatestBalance.ts +++ b/ui/hooks/bridge/useLatestBalance.ts @@ -1,11 +1,16 @@ -import { useSelector } from 'react-redux'; import { type Hex, type CaipChainId, isCaipChainId } from '@metamask/utils'; +import type { BigNumber } from 'bignumber.js'; import { Numeric } from '../../../shared/modules/Numeric'; -import { getCurrentChainId } from '../../../shared/modules/selectors/networks'; import { getSelectedInternalAccount } from '../../selectors'; import { calcLatestSrcBalance } from '../../../shared/modules/bridge-utils/balance'; import { useAsyncResult } from '../useAsyncResult'; import { calcTokenAmount } from '../../../shared/lib/transactions-controller-utils'; +import { useMultichainSelector } from '../useMultichainSelector'; +import { + getMultichainBalances, + getMultichainCurrentChainId, +} from '../../selectors/multichain'; +import { calcTokenValue } from '../../../shared/lib/swaps-utils'; /** * Custom hook to fetch and format the latest balance of a given token or native asset. @@ -19,14 +24,22 @@ const useLatestBalance = ( address: string; decimals: number; symbol: string; + string?: string; } | null, chainId?: Hex | CaipChainId, ) => { - const { address: selectedAddress } = useSelector(getSelectedInternalAccount); - const currentChainId = useSelector(getCurrentChainId); + const { address: selectedAddress, id } = useMultichainSelector( + getSelectedInternalAccount, + ); + const currentChainId = useMultichainSelector(getMultichainCurrentChainId); + + const nonEvmBalancesByAccountId = useMultichainSelector( + getMultichainBalances, + ); + const nonEvmBalances = nonEvmBalancesByAccountId[id]; const { value: latestBalance } = useAsyncResult< - Numeric | undefined + Numeric | BigNumber | undefined >(async () => { if ( token?.address && @@ -42,8 +55,23 @@ const useLatestBalance = ( chainId, ); } + + if (isCaipChainId(chainId) && token?.decimals && token?.string) { + return calcTokenValue( + nonEvmBalances?.[`${chainId}/${token.address}`]?.amount ?? token.string, + token.decimals, + ); + } + return undefined; - }, [currentChainId, token?.address, selectedAddress]); + }, [ + chainId, + currentChainId, + token, + selectedAddress, + global.ethereumProvider, + nonEvmBalances, + ]); if (token && !token.decimals) { throw new Error( From 54f91e3d24b34a3aafd2d59501be1b80087d7238 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 18 Feb 2025 17:14:39 -0800 Subject: [PATCH 02/18] chore: add SOL native token to isNativeAddress --- ui/pages/bridge/prepare/bridge-input-group.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/bridge/prepare/bridge-input-group.tsx b/ui/pages/bridge/prepare/bridge-input-group.tsx index 3fc34b0872ef..8cb9a830d611 100644 --- a/ui/pages/bridge/prepare/bridge-input-group.tsx +++ b/ui/pages/bridge/prepare/bridge-input-group.tsx @@ -145,7 +145,7 @@ export const BridgeInputGroup = ({ inputRef={inputRef} type={TextFieldType.Text} className="amount-input" - placeholder={'0'} + placeholder="0" onKeyPress={(e?: React.KeyboardEvent) => { if (e) { // Only allow numbers and at most one decimal point From 9fd0dd970ba5dd2a63c663949043cca09b60a915 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Wed, 19 Feb 2025 11:56:36 -0800 Subject: [PATCH 03/18] chore: use multichain account in bridge-status controller --- .../controllers/bridge-status/bridge-status-controller.ts | 4 +++- app/scripts/controllers/bridge-status/types.ts | 4 ++-- app/scripts/metamask-controller.js | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.ts index 4251b7dd3328..c869418c9eff 100644 --- a/app/scripts/controllers/bridge-status/bridge-status-controller.ts +++ b/app/scripts/controllers/bridge-status/bridge-status-controller.ts @@ -211,7 +211,9 @@ export default class BridgeStatusController extends StaticIntervalPollingControl }; #getSelectedAccount() { - return this.messagingSystem.call('AccountsController:getSelectedAccount'); + return this.messagingSystem.call( + 'AccountsController:getSelectedMultichainAccount', + ); } #fetchBridgeTxStatus = async ({ diff --git a/app/scripts/controllers/bridge-status/types.ts b/app/scripts/controllers/bridge-status/types.ts index 12c4952163d0..0778f46e07e6 100644 --- a/app/scripts/controllers/bridge-status/types.ts +++ b/app/scripts/controllers/bridge-status/types.ts @@ -8,7 +8,7 @@ import { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction, } from '@metamask/network-controller'; -import { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller'; +import { AccountsControllerGetSelectedMultichainAccountAction } from '@metamask/accounts-controller'; import { TransactionControllerGetStateAction } from '@metamask/transaction-controller'; import { BridgeHistoryItem, @@ -63,7 +63,7 @@ type AllowedActions = | NetworkControllerFindNetworkClientIdByChainIdAction | NetworkControllerGetStateAction | NetworkControllerGetNetworkClientByIdAction - | AccountsControllerGetSelectedAccountAction + | AccountsControllerGetSelectedMultichainAccountAction | TransactionControllerGetStateAction; /** diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b2944138b0ef..e70da94ea9bb 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1823,7 +1823,7 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.getRestricted({ name: BRIDGE_STATUS_CONTROLLER_NAME, allowedActions: [ - 'AccountsController:getSelectedAccount', + 'AccountsController:getSelectedMultichainAccount', 'NetworkController:getNetworkClientById', 'NetworkController:findNetworkClientIdByChainId', 'NetworkController:getState', From 64a156a103ca968fde64873e5d7c0022c86af763 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 18 Feb 2025 16:22:04 -0800 Subject: [PATCH 04/18] feat: submit solana tx using snap wallet --- .../accounts/useMultichainWalletSnapClient.ts | 8 ++ ui/pages/bridge/hooks/useHandleTx.ts | 96 ++++++++++++++++++- .../hooks/useSubmitBridgeTransaction.ts | 19 ++-- 3 files changed, 113 insertions(+), 10 deletions(-) diff --git a/ui/hooks/accounts/useMultichainWalletSnapClient.ts b/ui/hooks/accounts/useMultichainWalletSnapClient.ts index c5727c283450..c4fb2d9a4505 100644 --- a/ui/hooks/accounts/useMultichainWalletSnapClient.ts +++ b/ui/hooks/accounts/useMultichainWalletSnapClient.ts @@ -41,6 +41,14 @@ export class MultichainWalletSnapSender implements Sender { }; } +export function useMultichainWalletSnapSender(snapId: SnapId) { + const client = useMemo(() => { + return new MultichainWalletSnapSender(snapId); + }, [snapId]); + + return client; +} + export class MultichainWalletSnapClient { readonly #client: KeyringClient; diff --git a/ui/pages/bridge/hooks/useHandleTx.ts b/ui/pages/bridge/hooks/useHandleTx.ts index 59b6c7ee9c89..32bc187abf28 100644 --- a/ui/pages/bridge/hooks/useHandleTx.ts +++ b/ui/pages/bridge/hooks/useHandleTx.ts @@ -1,8 +1,14 @@ import { TransactionMeta, + TransactionParams, + TransactionStatus, TransactionType, } from '@metamask/transaction-controller'; import { useDispatch, useSelector } from 'react-redux'; +import { KeyringRpcMethod } from '@metamask/keyring-api'; +import { useEffect, useMemo } from 'react'; +import { Hex } from '@metamask/utils'; +import { useHistory } from 'react-router-dom'; import { forceUpdateMetamaskState, addTransaction, @@ -14,10 +20,26 @@ import { getTxGasEstimates, } from '../../../ducks/bridge/utils'; import { getGasFeeEstimates } from '../../../ducks/metamask/metamask'; -import { checkNetworkAndAccountSupports1559 } from '../../../selectors'; +import { useMultichainSelector } from '../../../hooks/useMultichainSelector'; import type { ChainId } from '../../../../shared/types/bridge'; import { decimalToPrefixedHex } from '../../../../shared/modules/conversion.utils'; import { getIsSmartTransaction } from '../../../../shared/modules/selectors'; +import { + getMultichainCurrentChainId, + getMultichainIsSolana, +} from '../../../selectors/multichain'; +import { SOLANA_WALLET_SNAP_ID } from '../../../../shared/lib/accounts/solana-wallet-snap'; +import { useMultichainWalletSnapSender } from '../../../hooks/accounts/useMultichainWalletSnapClient'; +import { + checkNetworkAndAccountSupports1559, + getMemoizedUnapprovedTemplatedConfirmations, + getMemoizedUnapprovedConfirmations, + getSelectedInternalAccount, +} from '../../../selectors'; +import { + CONFIRM_TRANSACTION_ROUTE, + CONFIRMATION_V_NEXT_ROUTE, +} from '../../../helpers/constants/routes'; export default function useHandleTx() { const dispatch = useDispatch(); @@ -27,7 +49,7 @@ export default function useHandleTx() { const networkGasFeeEstimates = useSelector(getGasFeeEstimates); const shouldUseSmartTransaction = useSelector(getIsSmartTransaction); - const handleTx = async ({ + const handleEvmTx = async ({ txType, txParams, fieldsToAddToTxMeta, @@ -88,5 +110,75 @@ export default function useHandleTx() { return txMeta; }; + const selectedAccount = useSelector(getSelectedInternalAccount); + const currentChainId = useSelector(getMultichainCurrentChainId); + + const snapSender = useMultichainWalletSnapSender(SOLANA_WALLET_SNAP_ID); + const history = useHistory(); + + const unapprovedTemplatedConfirmations = useSelector( + getMemoizedUnapprovedTemplatedConfirmations, + ); + const unapprovedConfirmations = useSelector( + getMemoizedUnapprovedConfirmations, + ); + // Snaps are allowed to redirect to their own pending confirmations (templated or not) + const templatedSnapApproval = unapprovedTemplatedConfirmations.find( + (approval) => approval.origin === SOLANA_WALLET_SNAP_ID, + ); + const snapApproval = unapprovedConfirmations.find( + (approval) => approval.origin === SOLANA_WALLET_SNAP_ID, + ); + + useEffect(() => { + if (templatedSnapApproval) { + history.push(`${CONFIRMATION_V_NEXT_ROUTE}/${templatedSnapApproval.id}`); + } else if (snapApproval) { + history.push(`${CONFIRM_TRANSACTION_ROUTE}/${snapApproval.id}`); + } + }, [unapprovedTemplatedConfirmations, unapprovedConfirmations, history]); + + const handleSolanaTx = async ({ + txType, + txParams, + fieldsToAddToTxMeta, + }: { + txType: TransactionType.bridge; + txParams: string; + fieldsToAddToTxMeta: Omit, 'status'>; + }): Promise => { + (await snapSender.send({ + id: crypto.randomUUID(), + jsonrpc: '2.0', + method: KeyringRpcMethod.SubmitRequest, + params: { + request: { + params: { base64EncodedTransactionMessage: txParams }, + method: 'sendAndConfirmTransaction', + }, + id: crypto.randomUUID(), + account: selectedAccount.id, + scope: currentChainId, + }, + })) as string; + + return { + ...fieldsToAddToTxMeta, + id: crypto.randomUUID(), + chainId: currentChainId as Hex, + networkClientId: selectedAccount.id, + time: Date.now(), + txParams: { data: txParams } as TransactionParams, + type: txType, + status: TransactionStatus.submitted, + }; + }; + + const isSolana = useMultichainSelector(getMultichainIsSolana); + const handleTx = useMemo( + () => (isSolana ? handleSolanaTx : handleEvmTx), + [isSolana], + ); + return { handleTx }; } diff --git a/ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts b/ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts index 1a1982078fbd..a32506ad60a0 100644 --- a/ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts +++ b/ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts @@ -31,6 +31,8 @@ import { MetricsBackgroundState, StatusTypes, } from '../../../../shared/types/bridge-status'; +import { useMultichainSelector } from '../../../hooks/useMultichainSelector'; +import { getMultichainIsEvm } from '../../../selectors/multichain'; import useAddToken from './useAddToken'; import useHandleApprovalTx, { APPROVAL_TX_ERROR, @@ -83,6 +85,7 @@ export default function useSubmitBridgeTransaction() { const { slippage } = useSelector(getQuoteRequest); const selectedAddress = useSelector(getSelectedAddress); const trackCrossChainSwapsEvent = useCrossChainSwapsEventTracker(); + const isEvm = useMultichainSelector(getMultichainIsEvm); const submitBridgeTransaction = async ( quoteResponse: QuoteResponse & QuoteMetadata, @@ -210,15 +213,15 @@ export default function useSubmitBridgeTransaction() { startTime: bridgeTxMeta.time, }), ); - - // Add tokens if not the native gas token - if (quoteResponse.quote.srcAsset.address !== zeroAddress()) { - addSourceToken(quoteResponse); - } - if (quoteResponse.quote.destAsset.address !== zeroAddress()) { - await addDestToken(quoteResponse); + if (isEvm) { + // Add tokens if not the native gas token + if (quoteResponse.quote.srcAsset.address !== zeroAddress()) { + addSourceToken(quoteResponse); + } + if (quoteResponse.quote.destAsset.address !== zeroAddress()) { + await addDestToken(quoteResponse); + } } - // Route user to activity tab on Home page await dispatch(setDefaultHomeActiveTabName('activity')); history.push({ From 6f9708e55186ef4a7378a22d06c5f667aa7391f6 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Thu, 20 Feb 2025 18:49:19 -0800 Subject: [PATCH 05/18] chore: add comments and fix lint issues --- ui/hooks/bridge/useLatestBalance.ts | 33 +++++++++---------- ui/pages/bridge/hooks/useHandleTx.ts | 24 +++++++------- .../hooks/useSubmitBridgeTransaction.ts | 2 ++ 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/ui/hooks/bridge/useLatestBalance.ts b/ui/hooks/bridge/useLatestBalance.ts index e06c0b862af1..d1bedac2b741 100644 --- a/ui/hooks/bridge/useLatestBalance.ts +++ b/ui/hooks/bridge/useLatestBalance.ts @@ -1,16 +1,14 @@ import { type Hex, type CaipChainId, isCaipChainId } from '@metamask/utils'; -import type { BigNumber } from 'bignumber.js'; -import { Numeric } from '../../../shared/modules/Numeric'; +import { BigNumber } from 'bignumber.js'; import { getSelectedInternalAccount } from '../../selectors'; import { calcLatestSrcBalance } from '../../../shared/modules/bridge-utils/balance'; import { useAsyncResult } from '../useAsyncResult'; -import { calcTokenAmount } from '../../../shared/lib/transactions-controller-utils'; import { useMultichainSelector } from '../useMultichainSelector'; import { getMultichainBalances, getMultichainCurrentChainId, } from '../../selectors/multichain'; -import { calcTokenValue } from '../../../shared/lib/swaps-utils'; +import { MultichainNetworks } from '../../../shared/constants/multichain/networks'; /** * Custom hook to fetch and format the latest balance of a given token or native asset. @@ -38,8 +36,8 @@ const useLatestBalance = ( ); const nonEvmBalances = nonEvmBalancesByAccountId[id]; - const { value: latestBalance } = useAsyncResult< - Numeric | BigNumber | undefined + const { value: balanceAmount } = useAsyncResult< + string | undefined >(async () => { if ( token?.address && @@ -48,19 +46,23 @@ const useLatestBalance = ( chainId && currentChainId === chainId ) { - return await calcLatestSrcBalance( + const balanceValue = await calcLatestSrcBalance( global.ethereumProvider, selectedAddress, token.address, chainId, ); + return balanceValue?.shiftedBy(token.decimals).toString(); } - if (isCaipChainId(chainId) && token?.decimals && token?.string) { - return calcTokenValue( - nonEvmBalances?.[`${chainId}/${token.address}`]?.amount ?? token.string, - token.decimals, - ); + // No need to fetch the balance for non-EVM tokens, use the balance provided by the + // multichain balances controller + if ( + isCaipChainId(chainId) && + chainId === MultichainNetworks.SOLANA && + token?.decimals + ) { + return nonEvmBalances?.[token.address]?.amount ?? token?.string; } return undefined; @@ -79,13 +81,8 @@ const useLatestBalance = ( ); } - const tokenDecimals = token?.decimals ? Number(token.decimals) : 1; - return { - balanceAmount: - token && latestBalance - ? calcTokenAmount(latestBalance.toString(), tokenDecimals) - : undefined, + balanceAmount: balanceAmount ? new BigNumber(balanceAmount) : undefined, }; }; diff --git a/ui/pages/bridge/hooks/useHandleTx.ts b/ui/pages/bridge/hooks/useHandleTx.ts index 32bc187abf28..9258dd4d0436 100644 --- a/ui/pages/bridge/hooks/useHandleTx.ts +++ b/ui/pages/bridge/hooks/useHandleTx.ts @@ -1,6 +1,6 @@ import { - TransactionMeta, - TransactionParams, + type TransactionMeta, + type TransactionParams, TransactionStatus, TransactionType, } from '@metamask/transaction-controller'; @@ -112,31 +112,30 @@ export default function useHandleTx() { const selectedAccount = useSelector(getSelectedInternalAccount); const currentChainId = useSelector(getMultichainCurrentChainId); - const snapSender = useMultichainWalletSnapSender(SOLANA_WALLET_SNAP_ID); const history = useHistory(); + // Find unapproved confirmations which the snap has initiated const unapprovedTemplatedConfirmations = useSelector( getMemoizedUnapprovedTemplatedConfirmations, ); const unapprovedConfirmations = useSelector( getMemoizedUnapprovedConfirmations, ); - // Snaps are allowed to redirect to their own pending confirmations (templated or not) - const templatedSnapApproval = unapprovedTemplatedConfirmations.find( - (approval) => approval.origin === SOLANA_WALLET_SNAP_ID, - ); - const snapApproval = unapprovedConfirmations.find( - (approval) => approval.origin === SOLANA_WALLET_SNAP_ID, - ); - + // Redirect to the confirmation page if an unapproved confirmation exists useEffect(() => { + const templatedSnapApproval = unapprovedTemplatedConfirmations.find( + (approval) => approval.origin === SOLANA_WALLET_SNAP_ID, + ); + const snapApproval = unapprovedConfirmations.find( + (approval) => approval.origin === SOLANA_WALLET_SNAP_ID, + ); if (templatedSnapApproval) { history.push(`${CONFIRMATION_V_NEXT_ROUTE}/${templatedSnapApproval.id}`); } else if (snapApproval) { history.push(`${CONFIRM_TRANSACTION_ROUTE}/${snapApproval.id}`); } - }, [unapprovedTemplatedConfirmations, unapprovedConfirmations, history]); + }, [history, unapprovedTemplatedConfirmations, unapprovedConfirmations]); const handleSolanaTx = async ({ txType, @@ -147,6 +146,7 @@ export default function useHandleTx() { txParams: string; fieldsToAddToTxMeta: Omit, 'status'>; }): Promise => { + // Submit a signing request to the snap (await snapSender.send({ id: crypto.randomUUID(), jsonrpc: '2.0', diff --git a/ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts b/ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts index a32506ad60a0..aa02cebbb2a5 100644 --- a/ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts +++ b/ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts @@ -213,6 +213,8 @@ export default function useSubmitBridgeTransaction() { startTime: bridgeTxMeta.time, }), ); + // Only add tokens if the source chain is an EVM chain bc non-evm tokens + // are detected by the multichain asset controllers if (isEvm) { // Add tokens if not the native gas token if (quoteResponse.quote.srcAsset.address !== zeroAddress()) { From 93a4149c344d107e7614f1114819423277cf7748 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Thu, 20 Feb 2025 18:49:45 -0800 Subject: [PATCH 06/18] chore: add devnet solana rpc to privacy-snapshot --- privacy-snapshot.json | 1 + 1 file changed, 1 insertion(+) diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 81c2e6e2d665..0b506fb9d370 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -68,6 +68,7 @@ "signature-insights.api.cx.metamask.io", "snaps.metamask.io", "solana-mainnet.infura.io", + "solana-devnet.infura.io", "solana.rpc.grove.city", "sourcify.dev", "start.metamask.io", From 555da54cd8014966556b5d02343df11da3810b06 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Thu, 20 Feb 2025 18:50:26 -0800 Subject: [PATCH 07/18] refactor: get multichain account address --- .../bridge-status/bridge-status-controller.test.ts | 2 +- .../bridge-status/bridge-status-controller.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts index aab698551191..38e2f05184f7 100644 --- a/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts +++ b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts @@ -20,7 +20,7 @@ const getMessengerMock = ({ } = {}) => ({ call: jest.fn((method: string) => { - if (method === 'AccountsController:getSelectedAccount') { + if (method === 'AccountsController:getSelectedMultichainAccount') { return { address: account }; } else if (method === 'NetworkController:findNetworkClientIdByChainId') { return 'networkClientId'; diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.ts index c869418c9eff..cc364e8de318 100644 --- a/app/scripts/controllers/bridge-status/bridge-status-controller.ts +++ b/app/scripts/controllers/bridge-status/bridge-status-controller.ts @@ -157,7 +157,7 @@ export default class BridgeStatusController extends StaticIntervalPollingControl targetContractAddress, } = startPollingForBridgeTxStatusArgs; const { bridgeStatusState } = this.state; - const { address: account } = this.#getSelectedAccount(); + const accountAddress = this.#getMultichainSelectedAccountAddress(); // Write all non-status fields to state so we can reference the quote in Activity list without the Bridge API // We know it's in progress but not the exact status yet @@ -176,7 +176,7 @@ export default class BridgeStatusController extends StaticIntervalPollingControl }, initialDestAssetBalance, targetContractAddress, - account, + account: accountAddress, status: { // We always have a PENDING status when we start polling for a tx, don't need the Bridge API for that // Also we know the bare minimum fields for status at this point in time @@ -210,9 +210,13 @@ export default class BridgeStatusController extends StaticIntervalPollingControl await this.#fetchBridgeTxStatus(pollingInput); }; - #getSelectedAccount() { - return this.messagingSystem.call( - 'AccountsController:getSelectedMultichainAccount', + // Returns an empty string if no account is selected, but this will never happen since + // the multichain selected account defaults to the EVM account + #getMultichainSelectedAccountAddress() { + return ( + this.messagingSystem.call( + 'AccountsController:getSelectedMultichainAccount', + )?.address ?? '' ); } From be6df4d55aaf343a319b0c2c95a85676ebb3ffe0 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Wed, 19 Feb 2025 11:42:59 -0800 Subject: [PATCH 08/18] chore: upgrade solana wallet snap to 1.7.0 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3cb57b8a5e17..64d14bfd45b2 100644 --- a/package.json +++ b/package.json @@ -321,7 +321,7 @@ "@metamask/snaps-rpc-methods": "^11.12.0", "@metamask/snaps-sdk": "^6.18.0", "@metamask/snaps-utils": "^9.0.0", - "@metamask/solana-wallet-snap": "^1.2.0", + "@metamask/solana-wallet-snap": "^1.7.0", "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A45.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-45.0.0-010fef9da6.patch", "@metamask/user-operation-controller": "^24.0.1", "@metamask/utils": "^11.1.0", diff --git a/yarn.lock b/yarn.lock index 4ad6ebf8426e..c9e6e42ee56e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6421,10 +6421,10 @@ __metadata: languageName: node linkType: hard -"@metamask/solana-wallet-snap@npm:^1.2.0": - version: 1.2.0 - resolution: "@metamask/solana-wallet-snap@npm:1.2.0" - checksum: 10/d2d38ca5701a3b9401c3a14c517c2cdb4f19b2a0e4f9e1a235b8602e52b656bed580315ec6b5ea1c4dd6ec51afaa1de0e88ee606cfc9068c18e9482adcde3eaf +"@metamask/solana-wallet-snap@npm:^1.7.0": + version: 1.7.0 + resolution: "@metamask/solana-wallet-snap@npm:1.7.0" + checksum: 10/bf5a88a10388e5637c2379d811517c7ee7b77ae041dc90e3237d39d88b2daffbb8d0535538e030e16df6525d8a6b109565ab01415726bef9fba1b718cc732c4e languageName: node linkType: hard @@ -27004,7 +27004,7 @@ __metadata: "@metamask/snaps-rpc-methods": "npm:^11.12.0" "@metamask/snaps-sdk": "npm:^6.18.0" "@metamask/snaps-utils": "npm:^9.0.0" - "@metamask/solana-wallet-snap": "npm:^1.2.0" + "@metamask/solana-wallet-snap": "npm:^1.7.0" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:9.0.0" "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A45.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-45.0.0-010fef9da6.patch" From be76ec80616acf48e4e667c0cc785c32d8b43333 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Mon, 24 Feb 2025 18:01:15 -0600 Subject: [PATCH 09/18] feat: update solana-snap to 1.8.0, change from sendAndConfirm to signAndSend --- package.json | 2 +- ui/pages/bridge/hooks/useHandleTx.ts | 8 ++++++-- yarn.lock | 10 +++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 64d14bfd45b2..5e6b690ecc5b 100644 --- a/package.json +++ b/package.json @@ -321,7 +321,7 @@ "@metamask/snaps-rpc-methods": "^11.12.0", "@metamask/snaps-sdk": "^6.18.0", "@metamask/snaps-utils": "^9.0.0", - "@metamask/solana-wallet-snap": "^1.7.0", + "@metamask/solana-wallet-snap": "^1.8.0", "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A45.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-45.0.0-010fef9da6.patch", "@metamask/user-operation-controller": "^24.0.1", "@metamask/utils": "^11.1.0", diff --git a/ui/pages/bridge/hooks/useHandleTx.ts b/ui/pages/bridge/hooks/useHandleTx.ts index 9258dd4d0436..155ea040039e 100644 --- a/ui/pages/bridge/hooks/useHandleTx.ts +++ b/ui/pages/bridge/hooks/useHandleTx.ts @@ -153,8 +153,12 @@ export default function useHandleTx() { method: KeyringRpcMethod.SubmitRequest, params: { request: { - params: { base64EncodedTransactionMessage: txParams }, - method: 'sendAndConfirmTransaction', + params: { + account: { address: selectedAccount.address }, + transaction: txParams, + scope: currentChainId, + }, + method: 'signAndSendTransaction', }, id: crypto.randomUUID(), account: selectedAccount.id, diff --git a/yarn.lock b/yarn.lock index c9e6e42ee56e..daee5bc0536f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6421,10 +6421,10 @@ __metadata: languageName: node linkType: hard -"@metamask/solana-wallet-snap@npm:^1.7.0": - version: 1.7.0 - resolution: "@metamask/solana-wallet-snap@npm:1.7.0" - checksum: 10/bf5a88a10388e5637c2379d811517c7ee7b77ae041dc90e3237d39d88b2daffbb8d0535538e030e16df6525d8a6b109565ab01415726bef9fba1b718cc732c4e +"@metamask/solana-wallet-snap@npm:^1.8.0": + version: 1.8.0 + resolution: "@metamask/solana-wallet-snap@npm:1.8.0" + checksum: 10/ff4d9bcb7ada1cb6fa0e5f3532e0819f1e907d3399b98b3414e0ee677e01ff9cc3b9a7873b713c8a079f3ae354a26f4b3d77ecc19e5a4fab3efec8078fea6aae languageName: node linkType: hard @@ -27004,7 +27004,7 @@ __metadata: "@metamask/snaps-rpc-methods": "npm:^11.12.0" "@metamask/snaps-sdk": "npm:^6.18.0" "@metamask/snaps-utils": "npm:^9.0.0" - "@metamask/solana-wallet-snap": "npm:^1.7.0" + "@metamask/solana-wallet-snap": "npm:^1.8.0" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:9.0.0" "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A45.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-45.0.0-010fef9da6.patch" From a4ca36fd108daf581c6133ac7e4d73bb7f96cf68 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 18 Feb 2025 17:10:16 -0800 Subject: [PATCH 10/18] chore: show cached balance in useLatestbalance when chainId is not evm --- ui/hooks/bridge/useIsTxSubmittable.ts | 21 ++++++++------- ui/hooks/bridge/useLatestBalance.ts | 26 ++++++++++++------- ui/hooks/bridge/useQuoteFetchEvents.ts | 4 +-- ui/pages/bridge/prepare/bridge-cta-button.tsx | 4 +-- .../bridge/prepare/bridge-input-group.tsx | 2 +- .../bridge/prepare/prepare-bridge-page.tsx | 7 ++--- 6 files changed, 35 insertions(+), 29 deletions(-) diff --git a/ui/hooks/bridge/useIsTxSubmittable.ts b/ui/hooks/bridge/useIsTxSubmittable.ts index 8302001c5bf8..32f3d98ed92e 100644 --- a/ui/hooks/bridge/useIsTxSubmittable.ts +++ b/ui/hooks/bridge/useIsTxSubmittable.ts @@ -3,43 +3,46 @@ import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP } from '../../../shared/constants/swaps import { getBridgeQuotes, getFromAmount, - getFromChain, getFromToken, getToChain, getValidationErrors, getToToken, } from '../../ducks/bridge/selectors'; +import { getMultichainCurrentChainId } from '../../selectors/multichain'; +import { useMultichainSelector } from '../useMultichainSelector'; +import { useIsMultichainSwap } from '../../pages/bridge/hooks/useIsMultichainSwap'; import useLatestBalance from './useLatestBalance'; export const useIsTxSubmittable = () => { const fromToken = useSelector(getFromToken); const toToken = useSelector(getToToken); - const fromChain = useSelector(getFromChain); + const fromChainId = useMultichainSelector(getMultichainCurrentChainId); const toChain = useSelector(getToChain); const fromAmount = useSelector(getFromAmount); const { activeQuote } = useSelector(getBridgeQuotes); + const isSwap = useIsMultichainSwap(); const { isInsufficientBalance, isInsufficientGasBalance, isInsufficientGasForQuote, } = useSelector(getValidationErrors); - const { balanceAmount } = useLatestBalance(fromToken, fromChain?.chainId); - const { balanceAmount: nativeAssetBalance } = useLatestBalance( - fromChain?.chainId + const balanceAmount = useLatestBalance(fromToken, fromChainId); + const nativeAssetBalance = useLatestBalance( + fromChainId ? SWAPS_CHAINID_DEFAULT_TOKEN_MAP[ - fromChain.chainId as keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP + fromChainId as keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP ] : null, - fromChain?.chainId, + fromChainId, ); return Boolean( fromToken && toToken && - fromChain && - toChain && + fromChainId && + (isSwap || toChain) && fromAmount && activeQuote && !isInsufficientBalance(balanceAmount) && diff --git a/ui/hooks/bridge/useLatestBalance.ts b/ui/hooks/bridge/useLatestBalance.ts index d1bedac2b741..55d0fb40130f 100644 --- a/ui/hooks/bridge/useLatestBalance.ts +++ b/ui/hooks/bridge/useLatestBalance.ts @@ -1,8 +1,10 @@ import { type Hex, type CaipChainId, isCaipChainId } from '@metamask/utils'; -import { BigNumber } from 'bignumber.js'; +import { useMemo } from 'react'; import { getSelectedInternalAccount } from '../../selectors'; import { calcLatestSrcBalance } from '../../../shared/modules/bridge-utils/balance'; import { useAsyncResult } from '../useAsyncResult'; +import { Numeric } from '../../../shared/modules/Numeric'; +import { calcTokenAmount } from '../../../shared/lib/transactions-controller-utils'; import { useMultichainSelector } from '../useMultichainSelector'; import { getMultichainBalances, @@ -36,9 +38,7 @@ const useLatestBalance = ( ); const nonEvmBalances = nonEvmBalancesByAccountId[id]; - const { value: balanceAmount } = useAsyncResult< - string | undefined - >(async () => { + const value = useAsyncResult(async () => { if ( token?.address && // TODO check whether chainId is EVM when MultichainNetworkController is integrated @@ -46,13 +46,12 @@ const useLatestBalance = ( chainId && currentChainId === chainId ) { - const balanceValue = await calcLatestSrcBalance( + return await calcLatestSrcBalance( global.ethereumProvider, selectedAddress, token.address, chainId, ); - return balanceValue?.shiftedBy(token.decimals).toString(); } // No need to fetch the balance for non-EVM tokens, use the balance provided by the @@ -62,7 +61,10 @@ const useLatestBalance = ( chainId === MultichainNetworks.SOLANA && token?.decimals ) { - return nonEvmBalances?.[token.address]?.amount ?? token?.string; + return Numeric.from( + nonEvmBalances?.[token.address]?.amount ?? token?.string, + 10, + ).shiftedBy(-1 * token.decimals); } return undefined; @@ -81,9 +83,13 @@ const useLatestBalance = ( ); } - return { - balanceAmount: balanceAmount ? new BigNumber(balanceAmount) : undefined, - }; + return useMemo( + () => + value?.value + ? calcTokenAmount(value.value.toString(), token?.decimals) + : undefined, + [value.value, token?.decimals], + ); }; export default useLatestBalance; diff --git a/ui/hooks/bridge/useQuoteFetchEvents.ts b/ui/hooks/bridge/useQuoteFetchEvents.ts index a272430c3f2a..d890d5b30e60 100644 --- a/ui/hooks/bridge/useQuoteFetchEvents.ts +++ b/ui/hooks/bridge/useQuoteFetchEvents.ts @@ -44,8 +44,8 @@ export const useQuoteFetchEvents = () => { const fromToken = useSelector(getFromToken); const fromChain = useSelector(getFromChain); - const { balanceAmount } = useLatestBalance(fromToken, fromChain?.chainId); - const { balanceAmount: nativeAssetBalance } = useLatestBalance( + const balanceAmount = useLatestBalance(fromToken, fromChain?.chainId); + const nativeAssetBalance = useLatestBalance( fromChain?.chainId ? SWAPS_CHAINID_DEFAULT_TOKEN_MAP[ fromChain.chainId as keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP diff --git a/ui/pages/bridge/prepare/bridge-cta-button.tsx b/ui/pages/bridge/prepare/bridge-cta-button.tsx index 7b8fdf397e5a..303c1f544ea1 100644 --- a/ui/pages/bridge/prepare/bridge-cta-button.tsx +++ b/ui/pages/bridge/prepare/bridge-cta-button.tsx @@ -73,8 +73,8 @@ export const BridgeCTAButton = ({ const wasTxDeclined = useSelector(getWasTxDeclined); - const { balanceAmount } = useLatestBalance(fromToken, fromChain?.chainId); - const { balanceAmount: nativeAssetBalance } = useLatestBalance( + const balanceAmount = useLatestBalance(fromToken, fromChain?.chainId); + const nativeAssetBalance = useLatestBalance( fromChain?.chainId ? SWAPS_CHAINID_DEFAULT_TOKEN_MAP[ fromChain.chainId as keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP diff --git a/ui/pages/bridge/prepare/bridge-input-group.tsx b/ui/pages/bridge/prepare/bridge-input-group.tsx index 8cb9a830d611..caba4d15da18 100644 --- a/ui/pages/bridge/prepare/bridge-input-group.tsx +++ b/ui/pages/bridge/prepare/bridge-input-group.tsx @@ -90,7 +90,7 @@ export const BridgeInputGroup = ({ const currentChainId = useMultichainSelector(getMultichainCurrentChainId); const selectedChainId = networkProps?.network?.chainId ?? currentChainId; - const { balanceAmount } = useLatestBalance(token, selectedChainId); + const balanceAmount = useLatestBalance(token, selectedChainId); const [, handleCopy] = useCopyToClipboard(MINUTE) as [ boolean, diff --git a/ui/pages/bridge/prepare/prepare-bridge-page.tsx b/ui/pages/bridge/prepare/prepare-bridge-page.tsx index 60c355717e71..f6c3b8ddbcee 100644 --- a/ui/pages/bridge/prepare/prepare-bridge-page.tsx +++ b/ui/pages/bridge/prepare/prepare-bridge-page.tsx @@ -173,17 +173,14 @@ const PrepareBridgePage = () => { const { quotesRefreshCount } = useSelector(getBridgeQuotes); const { openBuyCryptoInPdapp } = useRamps(); - const { balanceAmount: nativeAssetBalance } = useLatestBalance( + const nativeAssetBalance = useLatestBalance( SWAPS_CHAINID_DEFAULT_TOKEN_MAP[ fromChain?.chainId as keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP ], fromChain?.chainId, ); - const { balanceAmount: srcTokenBalance } = useLatestBalance( - fromToken, - fromChain?.chainId, - ); + const srcTokenBalance = useLatestBalance(fromToken, fromChain?.chainId); const { filteredTokenListGenerator: toTokenListGenerator, From a7df7a5ed8cbaac93dbd478d3b7b55ec8f63a5df Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 25 Feb 2025 12:49:46 -0800 Subject: [PATCH 11/18] chore: undo privacy snapshot update --- privacy-snapshot.json | 1 - 1 file changed, 1 deletion(-) diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 0b506fb9d370..81c2e6e2d665 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -68,7 +68,6 @@ "signature-insights.api.cx.metamask.io", "snaps.metamask.io", "solana-mainnet.infura.io", - "solana-devnet.infura.io", "solana.rpc.grove.city", "sourcify.dev", "start.metamask.io", From bf3ed6af3b8de759ac124b65b4a60a48dfb33929 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 25 Feb 2025 13:18:35 -0800 Subject: [PATCH 12/18] fix: type errors --- ui/pages/bridge/hooks/useHandleTx.ts | 34 +++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/ui/pages/bridge/hooks/useHandleTx.ts b/ui/pages/bridge/hooks/useHandleTx.ts index 155ea040039e..90f5fdcaa10c 100644 --- a/ui/pages/bridge/hooks/useHandleTx.ts +++ b/ui/pages/bridge/hooks/useHandleTx.ts @@ -6,7 +6,7 @@ import { } from '@metamask/transaction-controller'; import { useDispatch, useSelector } from 'react-redux'; import { KeyringRpcMethod } from '@metamask/keyring-api'; -import { useEffect, useMemo } from 'react'; +import { useEffect } from 'react'; import { Hex } from '@metamask/utils'; import { useHistory } from 'react-router-dom'; import { @@ -179,10 +179,32 @@ export default function useHandleTx() { }; const isSolana = useMultichainSelector(getMultichainIsSolana); - const handleTx = useMemo( - () => (isSolana ? handleSolanaTx : handleEvmTx), - [isSolana], - ); - return { handleTx }; + return { + handleTx: async ({ + txType, + txParams, + fieldsToAddToTxMeta, + }: { + txType: TransactionType.bridgeApproval | TransactionType.bridge; + txParams: { + chainId: ChainId; + to: string; + from: string; + value: string; + data: string; + gasLimit: number | null; + }; + fieldsToAddToTxMeta: Omit, 'status'>; // We don't add status, so omit it to fix the type error + }) => { + if ( + isSolana && + txType === TransactionType.bridge && + typeof txParams === 'string' + ) { + return handleSolanaTx({ txType, txParams, fieldsToAddToTxMeta }); + } + return handleEvmTx({ txType, txParams, fieldsToAddToTxMeta }); + }, + }; } From 92ed651433ef6c3edd296f732d9937690ef26a77 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 25 Feb 2025 13:19:27 -0800 Subject: [PATCH 13/18] fix: undo privacy snapshot deletion --- privacy-snapshot.json | 1 + 1 file changed, 1 insertion(+) diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 81c2e6e2d665..0b506fb9d370 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -68,6 +68,7 @@ "signature-insights.api.cx.metamask.io", "snaps.metamask.io", "solana-mainnet.infura.io", + "solana-devnet.infura.io", "solana.rpc.grove.city", "sourcify.dev", "start.metamask.io", From 3c72d029a58abe49f474dcfc5e01bd88040f4143 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 25 Feb 2025 13:36:10 -0800 Subject: [PATCH 14/18] fix: unit tests --- .../bridge-status/bridge-status-controller.test.ts | 4 ++-- ui/hooks/bridge/useLatestBalance.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts index 38e2f05184f7..2b12f9d0794f 100644 --- a/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts +++ b/app/scripts/controllers/bridge-status/bridge-status-controller.test.ts @@ -216,7 +216,7 @@ describe('BridgeStatusController', () => { let getSelectedAccountCalledTimes = 0; const messengerMock = { call: jest.fn((method: string) => { - if (method === 'AccountsController:getSelectedAccount') { + if (method === 'AccountsController:getSelectedMultichainAccount') { let account; if (getSelectedAccountCalledTimes === 0) { account = '0xaccount1'; @@ -399,7 +399,7 @@ describe('BridgeStatusController', () => { jest.useFakeTimers(); const messengerMock = { call: jest.fn((method: string) => { - if (method === 'AccountsController:getSelectedAccount') { + if (method === 'AccountsController:getSelectedMultichainAccount') { return { address: '0xaccount1' }; } else if ( method === 'NetworkController:findNetworkClientIdByChainId' diff --git a/ui/hooks/bridge/useLatestBalance.test.ts b/ui/hooks/bridge/useLatestBalance.test.ts index f255a3a10b9b..7b0b6b7befc1 100644 --- a/ui/hooks/bridge/useLatestBalance.test.ts +++ b/ui/hooks/bridge/useLatestBalance.test.ts @@ -57,7 +57,7 @@ describe('useLatestBalance', () => { ); await waitForNextUpdate(); - expect(result.current.balanceAmount).toStrictEqual(new BigNumber('1')); + expect(result.current).toStrictEqual(new BigNumber('1')); expect(mockGetBalance).toHaveBeenCalledTimes(1); expect(mockGetBalance).toHaveBeenCalledWith( @@ -76,7 +76,7 @@ describe('useLatestBalance', () => { ); await waitForNextUpdate(); - expect(result.current.balanceAmount).toStrictEqual(new BigNumber('15.39')); + expect(result.current).toStrictEqual(new BigNumber('15.39')); expect(mockFetchTokenBalance).toHaveBeenCalledTimes(1); expect(mockFetchTokenBalance).toHaveBeenCalledWith( From 75be6240a54fe1d81060d32165e66f903850be9b Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Wed, 26 Feb 2025 12:28:47 -0600 Subject: [PATCH 15/18] fix: fix balance amount --- ui/pages/bridge/prepare/bridge-input-group.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/bridge/prepare/bridge-input-group.tsx b/ui/pages/bridge/prepare/bridge-input-group.tsx index 99f77135ee78..17c3ddff0921 100644 --- a/ui/pages/bridge/prepare/bridge-input-group.tsx +++ b/ui/pages/bridge/prepare/bridge-input-group.tsx @@ -87,7 +87,7 @@ export const BridgeInputGroup = ({ const locale = useSelector(getIntlLocale); const selectedChainId = networkProps?.network?.chainId; - const { balanceAmount } = useLatestBalance(token, selectedChainId); + const balanceAmount = useLatestBalance(token, selectedChainId); const [, handleCopy] = useCopyToClipboard(MINUTE) as [ boolean, From 2b294adb864c6de4baea68ac362699c5b5edebcb Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Wed, 26 Feb 2025 12:37:34 -0600 Subject: [PATCH 16/18] feat: update solana-snap from 1.8 to 1.9 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 23a896feedef..682164e3db2a 100644 --- a/package.json +++ b/package.json @@ -321,7 +321,7 @@ "@metamask/snaps-rpc-methods": "^11.12.0", "@metamask/snaps-sdk": "^6.18.0", "@metamask/snaps-utils": "^9.0.0", - "@metamask/solana-wallet-snap": "^1.8.0", + "@metamask/solana-wallet-snap": "^1.9.0", "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A45.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-45.0.0-010fef9da6.patch", "@metamask/user-operation-controller": "^24.0.1", "@metamask/utils": "^11.1.0", diff --git a/yarn.lock b/yarn.lock index 2660457b33da..9529af2720b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6421,10 +6421,10 @@ __metadata: languageName: node linkType: hard -"@metamask/solana-wallet-snap@npm:^1.8.0": - version: 1.8.0 - resolution: "@metamask/solana-wallet-snap@npm:1.8.0" - checksum: 10/ff4d9bcb7ada1cb6fa0e5f3532e0819f1e907d3399b98b3414e0ee677e01ff9cc3b9a7873b713c8a079f3ae354a26f4b3d77ecc19e5a4fab3efec8078fea6aae +"@metamask/solana-wallet-snap@npm:^1.9.0": + version: 1.9.0 + resolution: "@metamask/solana-wallet-snap@npm:1.9.0" + checksum: 10/f4163e9c931b2b7458afcb10e5c36970c2010b96901274c4b4da07ecf3ec3c9899358e3496f73c235b41eb96cb58fd58ddce7f8b056866625a71b004df6855db languageName: node linkType: hard @@ -27004,7 +27004,7 @@ __metadata: "@metamask/snaps-rpc-methods": "npm:^11.12.0" "@metamask/snaps-sdk": "npm:^6.18.0" "@metamask/snaps-utils": "npm:^9.0.0" - "@metamask/solana-wallet-snap": "npm:^1.8.0" + "@metamask/solana-wallet-snap": "npm:^1.9.0" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:9.0.0" "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A45.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-45.0.0-010fef9da6.patch" From 9a51bbd8a041fac75911902dfa15cfe8ba434248 Mon Sep 17 00:00:00 2001 From: ghgoodreau Date: Wed, 26 Feb 2025 13:45:40 -0600 Subject: [PATCH 17/18] fix: increase timeouts in send flow e2e tests --- test/e2e/flask/solana/send-flow.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/e2e/flask/solana/send-flow.spec.ts b/test/e2e/flask/solana/send-flow.spec.ts index ed0b326cdc71..87ed637332c2 100644 --- a/test/e2e/flask/solana/send-flow.spec.ts +++ b/test/e2e/flask/solana/send-flow.spec.ts @@ -11,7 +11,7 @@ const commonSolanaAddress = 'GYP1hGem9HBkYKEWNUQUxEwfmu4hhjuujRgGnj5LrHna'; describe.skip('Send flow', function (this: Suite) { // skipped due tohttps://github.com/MetaMask/snaps/issues/3019 it('with some field validation', async function () { - this.timeout(120000); + this.timeout(300000); await withSolanaAccountSnap( { title: this.test?.fullTitle(), @@ -72,7 +72,7 @@ describe.skip('Send flow', function (this: Suite) { describe.skip('Send full flow of USD', function (this: Suite) { it('with a positive balance account', async function () { // skipped due tohttps://consensyssoftware.atlassian.net/browse/SOL-100 - this.timeout(120000); + this.timeout(300000); await withSolanaAccountSnap( { title: this.test?.fullTitle(), @@ -222,7 +222,7 @@ describe.skip('Send full flow of USD', function (this: Suite) { }); describe('Send full flow of SOL', function (this: Suite) { it('with a positive balance account', async function () { - this.timeout(120000); + this.timeout(300000); await withSolanaAccountSnap( { title: this.test?.fullTitle(), @@ -345,7 +345,7 @@ describe('Send full flow of SOL', function (this: Suite) { }); describe('Send flow', function (this: Suite) { it('and Transaction fails', async function () { - this.timeout(120000); // there is a bug open for this big timeout https://consensyssoftware.atlassian.net/browse/SOL-90 + this.timeout(300000); await withSolanaAccountSnap( { title: this.test?.fullTitle(), From ad39507f729f638687724e922fb92688f1a6b97b Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 26 Feb 2025 13:51:17 -0700 Subject: [PATCH 18/18] feat: added getMinimumBalanceForRentExemption to solana e2e tests --- test/e2e/flask/solana/common-solana.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/e2e/flask/solana/common-solana.ts b/test/e2e/flask/solana/common-solana.ts index 5d29a7bc18fb..a5e618b7ea5a 100644 --- a/test/e2e/flask/solana/common-solana.ts +++ b/test/e2e/flask/solana/common-solana.ts @@ -436,6 +436,26 @@ export async function mockGetFeeForMessage(mockServer: Mockttp) { }); } +export async function mockGetMinimumBalanceForRentExemption( + mockServer: Mockttp, +) { + const response = { + statusCode: 200, + json: { + result: 500, + id: 1337, + }, + }; + return await mockServer + .forPost(SOLANA_URL_REGEX) + .withJsonBodyIncluding({ + method: 'getMinimumBalanceForRentExemption', + }) + .thenCallback(() => { + return response; + }); +} + export async function withSolanaAccountSnap( { title, @@ -480,6 +500,7 @@ export async function withSolanaAccountSnap( await mockMultiCoinPrice(mockServer), await mockGetLatestBlockhash(mockServer), await mockGetFeeForMessage(mockServer), + await mockGetMinimumBalanceForRentExemption(mockServer), ]); } if (mockSendTransaction) {