From 96249d17f9d0b328bc54edf717d8614664d96db2 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 20 May 2026 21:20:10 +0100 Subject: [PATCH 01/28] feat: add universal multichain transaction confirmation POC --- .../evidence/task-17-extension-constant.txt | 1 + .../evidence/task-18-extension-host-hook.txt | 13 + .../evidence/task-20-extension-routing.txt | 21 ++ .../task-21-extension-selector-hook.txt | 46 +++ .../learnings.md | 2 + .../permissions/snaps/specifications.ts | 84 ++++- .../permission-controller-messenger.ts | 4 + lavamoat/browserify/beta/policy.json | 351 +++++++++++++++++- lavamoat/browserify/experimental/policy.json | 351 +++++++++++++++++- lavamoat/browserify/flask/policy.json | 351 +++++++++++++++++- lavamoat/browserify/main/policy.json | 351 +++++++++++++++++- lavamoat/build-system/policy.json | 2 +- lavamoat/webpack/mv2/beta/policy.json | 92 ++++- lavamoat/webpack/mv2/experimental/policy.json | 92 ++++- lavamoat/webpack/mv2/flask/policy.json | 92 ++++- lavamoat/webpack/mv2/main/policy.json | 92 ++++- lavamoat/webpack/mv3/beta/policy.json | 33 +- lavamoat/webpack/mv3/experimental/policy.json | 33 +- lavamoat/webpack/mv3/flask/policy.json | 33 +- lavamoat/webpack/mv3/main/policy.json | 33 +- package.json | 12 +- shared/constants/confirmations.ts | 6 + ui/helpers/constants/routes.ts | 2 + .../confirm/universal-transaction/index.ts | 1 + .../universal-transaction-confirmation.tsx | 158 ++++++++ .../hooks/send/useAmountValidation.ts | 20 +- .../hooks/useConfirmationNavigation.ts | 11 + .../hooks/usePendingMultichainTransaction.ts | 9 + ui/pages/routes/routes.component.tsx | 8 +- yarn.lock | 91 ++++- 30 files changed, 2246 insertions(+), 149 deletions(-) create mode 100644 .sisyphus/evidence/task-17-extension-constant.txt create mode 100644 .sisyphus/evidence/task-18-extension-host-hook.txt create mode 100644 .sisyphus/evidence/task-20-extension-routing.txt create mode 100644 .sisyphus/evidence/task-21-extension-selector-hook.txt create mode 100644 .sisyphus/notepads/universal-tx-confirmation-poc/learnings.md create mode 100644 shared/constants/confirmations.ts create mode 100644 ui/pages/confirmations/components/confirm/universal-transaction/index.ts create mode 100644 ui/pages/confirmations/components/confirm/universal-transaction/universal-transaction-confirmation.tsx create mode 100644 ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts diff --git a/.sisyphus/evidence/task-17-extension-constant.txt b/.sisyphus/evidence/task-17-extension-constant.txt new file mode 100644 index 000000000000..93f6a45fb96b --- /dev/null +++ b/.sisyphus/evidence/task-17-extension-constant.txt @@ -0,0 +1 @@ +/Users/matthewwalsh/dev/metamask-extension-universal-tx/shared/constants/confirmations.ts:6:export const USE_UNIVERSAL_MULTICHAIN_CONFIRMATION = true; diff --git a/.sisyphus/evidence/task-18-extension-host-hook.txt b/.sisyphus/evidence/task-18-extension-host-hook.txt new file mode 100644 index 000000000000..ebbf85dccf11 --- /dev/null +++ b/.sisyphus/evidence/task-18-extension-host-hook.txt @@ -0,0 +1,13 @@ +/Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/controllers/permissions/snaps/specifications.ts:203: showUniversalTransactionConfirmation: async ( + 'MultichainTransactionsController:addPendingTransaction', + const addAndShowApprovalRequest = async () => + +Extended verification: + 'MultichainTransactionsController:addPendingTransaction', + const addAndShowApprovalRequest = async () => + await addAndShowApprovalRequest(); + 'MultichainTransactionsController:removePendingTransaction', + +Type verification: +lsp_diagnostics: no errors in app/scripts/controllers/permissions/snaps/specifications.ts and app/scripts/messenger-client-init/messengers/permission-controller-messenger.ts +yarn lint:tsc: failed with pre-existing unrelated errors outside changed files (see run output); changed files have no LSP TypeScript errors. diff --git a/.sisyphus/evidence/task-20-extension-routing.txt b/.sisyphus/evidence/task-20-extension-routing.txt new file mode 100644 index 000000000000..5da217d8d859 --- /dev/null +++ b/.sisyphus/evidence/task-20-extension-routing.txt @@ -0,0 +1,21 @@ +--- chainNamespace guard --- + USE_UNIVERSAL_MULTICHAIN_CONFIRMATION && + type === ApprovalType.Transaction && + nextConfirmation.requestData?.chainNamespace + ) { + return CONFIRM_MULTICHAIN_TRANSACTION_PATH.replace(':id', confirmationId); + } + + if (type === ApprovalType.Transaction) { + let url = `${CONFIRM_TRANSACTION_ROUTE}/${confirmationId}`; + if (queryString.length) { + url = `${url}${queryString}`; + } + return url; + +--- route constant --- +131:export const CONFIRM_MULTICHAIN_TRANSACTION_PATH = + +--- router registration --- +122:import UniversalTransactionConfirmation from '../../pages/confirmations/components/confirm/universal-transaction'; +366: element: , diff --git a/.sisyphus/evidence/task-21-extension-selector-hook.txt b/.sisyphus/evidence/task-21-extension-selector-hook.txt new file mode 100644 index 000000000000..3f53a23f62c8 --- /dev/null +++ b/.sisyphus/evidence/task-21-extension-selector-hook.txt @@ -0,0 +1,46 @@ +Verification 1: selector state path + +$ grep -rn "nonEvmTransactions\|pendingTransactions" /Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/ 2>/dev/null | head -20 +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/multichain-transactions.test.ts:25: MultichainTransactionsControllerState['nonEvmTransactions']; +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/multichain-transactions.test.ts:35: nonEvmTransactions: unknown, +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/multichain-transactions.test.ts:40: nonEvmTransactions: nonEvmTransactions as NonEvmTransactionsMap, +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/toast.ts:27: state.metamask?.nonEvmTransactions ?? EMPTY_OBJECT; +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/toast.ts:216: (nonEvmTransactionsMap, crossChainBridgeIds) => { +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/toast.ts:217: if (!nonEvmTransactionsMap) { +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/toast.ts:221: return Object.values(nonEvmTransactionsMap) +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/multichain.test.ts:120: nonEvmTransactions: { +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/activity.test.ts:23: MultichainTransactionsControllerState['nonEvmTransactions']; +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/activity.test.ts:33: nonEvmTransactions: unknown, +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/activity.test.ts:46: nonEvmTransactions: nonEvmTransactions as NonEvmTransactionsMap, +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/activity.ts:89: (nonEvmTransactions, tokenScanCache) => { +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/activity.ts:92: nonEvmTransactions.flatMap((transaction) => +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/activity.ts:106: return nonEvmTransactions; +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/activity.ts:109: return filterMaliciousTransactions(nonEvmTransactions, maliciousTokenKeys); +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/multichain-transactions.ts:10:// Lightweight shape for nonEvmTransactions state map +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/multichain-transactions.ts:21: state.metamask.nonEvmTransactions as NonEvmTransactionsMap; +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/toast.test.ts:457: nonEvmTransactions: { +/Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/selectors/toast.test.ts:481: nonEvmTransactions: { + +Verification 2: controller wiring + +$ grep -rn "MultichainTransactionsController\|multichainTransactions" /Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/metamask-controller.js | head -10 +/Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/metamask-controller.js:342: MultichainTransactionsControllerInit, +/Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/metamask-controller.js:700: MultichainTransactionsController: MultichainTransactionsControllerInit, +/Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/metamask-controller.js:813: this.multichainTransactionsController = +/Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/metamask-controller.js:814: messengerClientsByName.MultichainTransactionsController; +/Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/metamask-controller.js:1435: MultichainTransactionsController: this.multichainTransactionsController, +/Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/metamask-controller.js:3915: // MultichainTransactionsController +/Users/matthewwalsh/dev/metamask-extension-universal-tx/app/scripts/metamask-controller.js:3917: this.multichainTransactionsController.updateTransactionsForAccount( + +Verification 3: created hook + +$ cat /Users/matthewwalsh/dev/metamask-extension-universal-tx/ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts +import { useSelector } from 'react-redux'; + +export function usePendingMultichainTransaction( + approvalId: string | undefined, +) { + return useSelector((state: any) => + approvalId ? state.metamask.pendingTransactions?.[approvalId] : undefined, + ); +} diff --git a/.sisyphus/notepads/universal-tx-confirmation-poc/learnings.md b/.sisyphus/notepads/universal-tx-confirmation-poc/learnings.md new file mode 100644 index 000000000000..c98cecde396d --- /dev/null +++ b/.sisyphus/notepads/universal-tx-confirmation-poc/learnings.md @@ -0,0 +1,2 @@ +- 2026-05-19: `usePendingMultichainTransaction` should read `state.metamask.pendingTransactions` and return `undefined` when `approvalId` is missing. +- Existing multichain selectors in the extension still reference `state.metamask.nonEvmTransactions`; this hook is for the confirmation-specific pending transaction slice, not the activity selector path. diff --git a/app/scripts/controllers/permissions/snaps/specifications.ts b/app/scripts/controllers/permissions/snaps/specifications.ts index ee067a005e07..4c7ced3ae2c7 100644 --- a/app/scripts/controllers/permissions/snaps/specifications.ts +++ b/app/scripts/controllers/permissions/snaps/specifications.ts @@ -4,9 +4,12 @@ import { RestrictedMethodMessenger, } from '@metamask/snaps-rpc-methods'; import { ControllerGetStateAction } from '@metamask/base-controller'; +import { ApprovalType } from '@metamask/controller-utils'; import { CurrencyRateController } from '@metamask/assets-controllers'; import type { AssetsControllerGetStateAction } from '@metamask/assets-controller'; +import type { MultichainTransactionsControllerActions } from '@metamask/multichain-transactions-controller'; import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; +import { rpcErrors } from '@metamask/rpc-errors'; import { SnapControllerClearSnapStateAction, SnapControllerGetSnapAction, @@ -17,6 +20,7 @@ import { SnapInterfaceControllerGetInterfaceAction, SnapInterfaceControllerSetInterfaceDisplayedAction, } from '@metamask/snaps-controllers'; +import { nanoid } from 'nanoid'; import { KeyringControllerGetKeyringsByTypeAction, KeyringControllerWithKeyringAction, @@ -32,6 +36,7 @@ import { ExcludedSnapEndowments, ExcludedSnapPermissions, } from '../../../../../shared/constants/snaps/permissions'; +import { USE_UNIVERSAL_MULTICHAIN_CONFIRMATION } from '../../../../../shared/constants/confirmations'; import { PreferencesControllerGetStateAction } from '../../preferences-controller'; import { KeyringType } from '../../../../../shared/constants/keyring'; import { AppStateControllerGetUnlockPromiseAction } from '../../app-state-controller-method-action-types'; @@ -43,6 +48,20 @@ import { } from '../../../../../shared/lib/assets-unify-state/remote-feature-flag'; import { getIsAssetsUnifiedStateIncludedInBuild } from '../../../../../shared/lib/environment'; +type ConfirmTransactionParams = { + accountId: string; + assetDecimals: number; + assetSymbol: string; + assetType: string; + chain: string; + chainNamespace: 'solana' | 'bip122' | 'tron'; + feeAssetType?: string; + feeRaw?: string; + from: string; + to: string; + value: string; +}; + export type SnapPermissionSpecificationsActions = | AppStateControllerGetUnlockPromiseAction | SnapControllerClearSnapStateAction @@ -66,7 +85,8 @@ export type SnapPermissionSpecificationsActions = | SnapInterfaceControllerSetInterfaceDisplayedAction | TestOrigin | SnapControllerUpdateSnapStateAction - | ApprovalControllerAddRequestAction; + | ApprovalControllerAddRequestAction + | MultichainTransactionsControllerActions; /** * Get the permission specifications for Snaps. @@ -180,6 +200,68 @@ export function getSnapPermissionSpecifications( */ getClientCryptography: () => ({}), + showUniversalTransactionConfirmation: async ( + snapId: string, + params: ConfirmTransactionParams, + ) => { + if (!USE_UNIVERSAL_MULTICHAIN_CONFIRMATION) { + throw rpcErrors.methodNotSupported(); + } + + const approvalId = nanoid(); + messenger.call( + 'MultichainTransactionsController:addPendingTransaction', + { + approvalId, + chainNamespace: params.chainNamespace, + chain: params.chain, + accountId: params.accountId, + from: params.from, + to: params.to, + value: params.value, + assetType: params.assetType, + assetSymbol: params.assetSymbol, + assetDecimals: params.assetDecimals, + feeRaw: params.feeRaw, + feeAssetType: params.feeAssetType, + origin: snapId, + createdAt: Date.now(), + }, + ); + + const addAndShowApprovalRequest = async () => + messenger.call( + 'ApprovalController:addRequest', + { + id: approvalId, + origin: snapId, + type: ApprovalType.Transaction, + requestData: { + approvalId, + chainNamespace: params.chainNamespace, + txParams: { + from: params.from, + to: params.to, + value: params.value, + }, + }, + }, + true, + ); + + try { + await addAndShowApprovalRequest(); + return true; + } catch (_err) { + return false; + } finally { + messenger.call( + 'MultichainTransactionsController:removePendingTransaction', + approvalId, + ); + } + }, + getSnapKeyring: async () => { // TODO: Use `withKeyring` instead. const [snapKeyring] = messenger.call( diff --git a/app/scripts/messenger-client-init/messengers/permission-controller-messenger.ts b/app/scripts/messenger-client-init/messengers/permission-controller-messenger.ts index b3a257f1d32d..08fdf3d1e17d 100644 --- a/app/scripts/messenger-client-init/messengers/permission-controller-messenger.ts +++ b/app/scripts/messenger-client-init/messengers/permission-controller-messenger.ts @@ -6,6 +6,7 @@ import type { ApprovalControllerAcceptRequestAction, ApprovalControllerRejectRequestAction, } from '@metamask/approval-controller'; +import type { MultichainTransactionsControllerActions } from '@metamask/multichain-transactions-controller'; import type { GetSubjectMetadata } from '@metamask/permission-controller'; import { AccountsControllerListAccountsAction } from '@metamask/accounts-controller'; import { @@ -68,6 +69,7 @@ type AllowedInitializationActions = | AccountsControllerListAccountsAction | MultichainRoutingServiceGetSupportedAccountsAction | MultichainRoutingServiceIsSupportedScopeAction + | MultichainTransactionsControllerActions | NetworkControllerFindNetworkClientIdByChainIdAction | SnapPermissionSpecificationsActions | ApprovalControllerAddRequestAction; @@ -107,6 +109,8 @@ export function getPermissionControllerInitMessenger( 'KeyringController:addNewKeyring', 'MultichainRoutingService:isSupportedScope', 'MultichainRoutingService:getSupportedAccounts', + 'MultichainTransactionsController:addPendingTransaction', + 'MultichainTransactionsController:removePendingTransaction', 'NetworkController:findNetworkClientIdByChainId', 'PhishingController:maybeUpdateState', 'PhishingController:testOrigin', diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 95e966b4e59a..9afef01ecb2e 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -820,7 +820,7 @@ "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/profile-sync-controller": true, - "@metamask/snaps-utils": true, + "@metamask/account-tree-controller>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "eslint>fast-deep-equal": true @@ -904,7 +904,7 @@ "@metamask/keyring-snap-client": true, "@metamask/phishing-controller": true, "@metamask/polling-controller": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controller>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "@metamask/assets-controller>bignumber.js": true, @@ -944,7 +944,7 @@ "@metamask/phishing-controller": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/core-backend>@tanstack/query-core": true, "mockttp>async-mutex": true, @@ -1837,7 +1837,7 @@ "@metamask/base-controller": true, "@metamask/delegation-core": true, "@metamask/delegation-deployments": true, - "@metamask/snaps-utils": true, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": true, "@metamask/transaction-controller": true, "@metamask/utils": true } @@ -2078,7 +2078,7 @@ "@metamask/multichain-account-service>@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/multichain-account-service>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-account-service>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "lodash": true @@ -2117,13 +2117,14 @@ }, "@metamask/multichain-transactions-controller": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true } }, "@metamask/name-controller": { @@ -2577,9 +2578,9 @@ "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, "@metamask/snaps-registry": true, - "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "mockttp>async-mutex": true, @@ -2603,6 +2604,7 @@ "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": true, "@metamask/utils": true } }, @@ -2624,7 +2626,21 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@noble/hashes": true, + "mockttp>async-mutex": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/json-rpc-engine": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "@noble/hashes": true, @@ -2678,6 +2694,321 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/account-tree-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/assets-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/assets-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-account-service>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/storage-service": { "globals": { "console.error": true, diff --git a/lavamoat/browserify/experimental/policy.json b/lavamoat/browserify/experimental/policy.json index 95e966b4e59a..9afef01ecb2e 100644 --- a/lavamoat/browserify/experimental/policy.json +++ b/lavamoat/browserify/experimental/policy.json @@ -820,7 +820,7 @@ "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/profile-sync-controller": true, - "@metamask/snaps-utils": true, + "@metamask/account-tree-controller>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "eslint>fast-deep-equal": true @@ -904,7 +904,7 @@ "@metamask/keyring-snap-client": true, "@metamask/phishing-controller": true, "@metamask/polling-controller": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controller>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "@metamask/assets-controller>bignumber.js": true, @@ -944,7 +944,7 @@ "@metamask/phishing-controller": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/core-backend>@tanstack/query-core": true, "mockttp>async-mutex": true, @@ -1837,7 +1837,7 @@ "@metamask/base-controller": true, "@metamask/delegation-core": true, "@metamask/delegation-deployments": true, - "@metamask/snaps-utils": true, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": true, "@metamask/transaction-controller": true, "@metamask/utils": true } @@ -2078,7 +2078,7 @@ "@metamask/multichain-account-service>@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/multichain-account-service>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-account-service>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "lodash": true @@ -2117,13 +2117,14 @@ }, "@metamask/multichain-transactions-controller": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true } }, "@metamask/name-controller": { @@ -2577,9 +2578,9 @@ "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, "@metamask/snaps-registry": true, - "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "mockttp>async-mutex": true, @@ -2603,6 +2604,7 @@ "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": true, "@metamask/utils": true } }, @@ -2624,7 +2626,21 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@noble/hashes": true, + "mockttp>async-mutex": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/json-rpc-engine": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "@noble/hashes": true, @@ -2678,6 +2694,321 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/account-tree-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/assets-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/assets-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-account-service>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/storage-service": { "globals": { "console.error": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 95e966b4e59a..9afef01ecb2e 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -820,7 +820,7 @@ "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/profile-sync-controller": true, - "@metamask/snaps-utils": true, + "@metamask/account-tree-controller>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "eslint>fast-deep-equal": true @@ -904,7 +904,7 @@ "@metamask/keyring-snap-client": true, "@metamask/phishing-controller": true, "@metamask/polling-controller": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controller>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "@metamask/assets-controller>bignumber.js": true, @@ -944,7 +944,7 @@ "@metamask/phishing-controller": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/core-backend>@tanstack/query-core": true, "mockttp>async-mutex": true, @@ -1837,7 +1837,7 @@ "@metamask/base-controller": true, "@metamask/delegation-core": true, "@metamask/delegation-deployments": true, - "@metamask/snaps-utils": true, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": true, "@metamask/transaction-controller": true, "@metamask/utils": true } @@ -2078,7 +2078,7 @@ "@metamask/multichain-account-service>@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/multichain-account-service>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-account-service>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "lodash": true @@ -2117,13 +2117,14 @@ }, "@metamask/multichain-transactions-controller": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true } }, "@metamask/name-controller": { @@ -2577,9 +2578,9 @@ "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, "@metamask/snaps-registry": true, - "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "mockttp>async-mutex": true, @@ -2603,6 +2604,7 @@ "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": true, "@metamask/utils": true } }, @@ -2624,7 +2626,21 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@noble/hashes": true, + "mockttp>async-mutex": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/json-rpc-engine": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "@noble/hashes": true, @@ -2678,6 +2694,321 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/account-tree-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/assets-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/assets-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-account-service>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/storage-service": { "globals": { "console.error": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 95e966b4e59a..9afef01ecb2e 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -820,7 +820,7 @@ "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/profile-sync-controller": true, - "@metamask/snaps-utils": true, + "@metamask/account-tree-controller>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "eslint>fast-deep-equal": true @@ -904,7 +904,7 @@ "@metamask/keyring-snap-client": true, "@metamask/phishing-controller": true, "@metamask/polling-controller": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controller>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "@metamask/assets-controller>bignumber.js": true, @@ -944,7 +944,7 @@ "@metamask/phishing-controller": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/core-backend>@tanstack/query-core": true, "mockttp>async-mutex": true, @@ -1837,7 +1837,7 @@ "@metamask/base-controller": true, "@metamask/delegation-core": true, "@metamask/delegation-deployments": true, - "@metamask/snaps-utils": true, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": true, "@metamask/transaction-controller": true, "@metamask/utils": true } @@ -2078,7 +2078,7 @@ "@metamask/multichain-account-service>@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/multichain-account-service>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-account-service>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "lodash": true @@ -2117,13 +2117,14 @@ }, "@metamask/multichain-transactions-controller": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true } }, "@metamask/name-controller": { @@ -2577,9 +2578,9 @@ "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, "@metamask/snaps-registry": true, - "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "mockttp>async-mutex": true, @@ -2603,6 +2604,7 @@ "packages": { "@metamask/post-message-stream": true, "@metamask/snaps-utils": true, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": true, "@metamask/utils": true } }, @@ -2624,7 +2626,21 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@noble/hashes": true, + "mockttp>async-mutex": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/json-rpc-engine": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "@noble/hashes": true, @@ -2678,6 +2694,321 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/account-tree-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/assets-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/assets-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-account-service>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/storage-service": { "globals": { "console.error": true, diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 0edda42b3ef9..a94f41163f70 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -6901,7 +6901,7 @@ "process.versions.pnp": true }, "packages": { - "eslint-import-resolver-typescript>unrs-resolver>@unrs/resolver-binding-linux-x64-gnu": true + "eslint-import-resolver-typescript>unrs-resolver>@unrs/resolver-binding-darwin-arm64": true } }, "uri-js": { diff --git a/lavamoat/webpack/mv2/beta/policy.json b/lavamoat/webpack/mv2/beta/policy.json index 71498ca2c665..0eea08153b0e 100644 --- a/lavamoat/webpack/mv2/beta/policy.json +++ b/lavamoat/webpack/mv2/beta/policy.json @@ -760,7 +760,7 @@ "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/profile-sync-controller": true, - "@metamask/snaps-utils": true, + "@metamask/account-tree-controller>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "eslint>fast-deep-equal": true @@ -839,7 +839,7 @@ "@metamask/keyring-snap-client": true, "@metamask/phishing-controller": true, "@metamask/polling-controller": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controller>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "@metamask/assets-controller>bignumber.js": true, @@ -879,7 +879,7 @@ "@metamask/phishing-controller": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/core-backend>@tanstack/query-core": true, "mockttp>async-mutex": true, @@ -1724,7 +1724,7 @@ "@metamask/base-controller": true, "@metamask/delegation-core": true, "@metamask/delegation-deployments": true, - "@metamask/snaps-utils": true, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": true, "@metamask/transaction-controller": true, "@metamask/utils": true } @@ -1939,7 +1939,7 @@ "@metamask/multichain-account-service>@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/multichain-account-service>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-account-service>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "lodash": true @@ -1978,13 +1978,14 @@ }, "@metamask/multichain-transactions-controller": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true } }, "@metamask/name-controller": { @@ -2427,9 +2428,9 @@ "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, "@metamask/snaps-registry": true, - "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "mockttp>async-mutex": true, @@ -2460,7 +2461,7 @@ "@metamask/snaps-rpc-methods": { "meta": { "webpack-optimization": [ - "Dependency '@metamask/snaps-utils' reexports from '@metamask/snaps-sdk' and webpack collapsed that to a direct import." + "Dependency '@metamask/snaps-rpc-methods>@metamask/snaps-utils' reexports from '@metamask/snaps-sdk' and webpack collapsed that to a direct import." ] }, "packages": { @@ -2469,13 +2470,26 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "@noble/hashes": true, "mockttp>async-mutex": true } }, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/json-rpc-engine": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@noble/hashes": true + } + }, "@metamask/snaps-sdk": { "globals": { "URL": true @@ -2488,6 +2502,37 @@ } }, "@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "URL": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/account-tree-controller>@metamask/snaps-utils": { + "packages": { + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-utils": { "globals": { "File": true, "FileReader": true, @@ -2506,7 +2551,6 @@ "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/permission-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-sdk": true, "@metamask/superstruct": true, "@metamask/utils": true, @@ -2522,6 +2566,30 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { + "globals": { + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/storage-service": { "globals": { "console.error": true, diff --git a/lavamoat/webpack/mv2/experimental/policy.json b/lavamoat/webpack/mv2/experimental/policy.json index 71498ca2c665..0eea08153b0e 100644 --- a/lavamoat/webpack/mv2/experimental/policy.json +++ b/lavamoat/webpack/mv2/experimental/policy.json @@ -760,7 +760,7 @@ "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/profile-sync-controller": true, - "@metamask/snaps-utils": true, + "@metamask/account-tree-controller>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "eslint>fast-deep-equal": true @@ -839,7 +839,7 @@ "@metamask/keyring-snap-client": true, "@metamask/phishing-controller": true, "@metamask/polling-controller": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controller>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "@metamask/assets-controller>bignumber.js": true, @@ -879,7 +879,7 @@ "@metamask/phishing-controller": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/core-backend>@tanstack/query-core": true, "mockttp>async-mutex": true, @@ -1724,7 +1724,7 @@ "@metamask/base-controller": true, "@metamask/delegation-core": true, "@metamask/delegation-deployments": true, - "@metamask/snaps-utils": true, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": true, "@metamask/transaction-controller": true, "@metamask/utils": true } @@ -1939,7 +1939,7 @@ "@metamask/multichain-account-service>@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/multichain-account-service>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-account-service>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "lodash": true @@ -1978,13 +1978,14 @@ }, "@metamask/multichain-transactions-controller": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true } }, "@metamask/name-controller": { @@ -2427,9 +2428,9 @@ "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, "@metamask/snaps-registry": true, - "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "mockttp>async-mutex": true, @@ -2460,7 +2461,7 @@ "@metamask/snaps-rpc-methods": { "meta": { "webpack-optimization": [ - "Dependency '@metamask/snaps-utils' reexports from '@metamask/snaps-sdk' and webpack collapsed that to a direct import." + "Dependency '@metamask/snaps-rpc-methods>@metamask/snaps-utils' reexports from '@metamask/snaps-sdk' and webpack collapsed that to a direct import." ] }, "packages": { @@ -2469,13 +2470,26 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "@noble/hashes": true, "mockttp>async-mutex": true } }, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/json-rpc-engine": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@noble/hashes": true + } + }, "@metamask/snaps-sdk": { "globals": { "URL": true @@ -2488,6 +2502,37 @@ } }, "@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "URL": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/account-tree-controller>@metamask/snaps-utils": { + "packages": { + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-utils": { "globals": { "File": true, "FileReader": true, @@ -2506,7 +2551,6 @@ "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/permission-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-sdk": true, "@metamask/superstruct": true, "@metamask/utils": true, @@ -2522,6 +2566,30 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { + "globals": { + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/storage-service": { "globals": { "console.error": true, diff --git a/lavamoat/webpack/mv2/flask/policy.json b/lavamoat/webpack/mv2/flask/policy.json index 71498ca2c665..0eea08153b0e 100644 --- a/lavamoat/webpack/mv2/flask/policy.json +++ b/lavamoat/webpack/mv2/flask/policy.json @@ -760,7 +760,7 @@ "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/profile-sync-controller": true, - "@metamask/snaps-utils": true, + "@metamask/account-tree-controller>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "eslint>fast-deep-equal": true @@ -839,7 +839,7 @@ "@metamask/keyring-snap-client": true, "@metamask/phishing-controller": true, "@metamask/polling-controller": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controller>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "@metamask/assets-controller>bignumber.js": true, @@ -879,7 +879,7 @@ "@metamask/phishing-controller": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/core-backend>@tanstack/query-core": true, "mockttp>async-mutex": true, @@ -1724,7 +1724,7 @@ "@metamask/base-controller": true, "@metamask/delegation-core": true, "@metamask/delegation-deployments": true, - "@metamask/snaps-utils": true, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": true, "@metamask/transaction-controller": true, "@metamask/utils": true } @@ -1939,7 +1939,7 @@ "@metamask/multichain-account-service>@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/multichain-account-service>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-account-service>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "lodash": true @@ -1978,13 +1978,14 @@ }, "@metamask/multichain-transactions-controller": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true } }, "@metamask/name-controller": { @@ -2427,9 +2428,9 @@ "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, "@metamask/snaps-registry": true, - "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "mockttp>async-mutex": true, @@ -2460,7 +2461,7 @@ "@metamask/snaps-rpc-methods": { "meta": { "webpack-optimization": [ - "Dependency '@metamask/snaps-utils' reexports from '@metamask/snaps-sdk' and webpack collapsed that to a direct import." + "Dependency '@metamask/snaps-rpc-methods>@metamask/snaps-utils' reexports from '@metamask/snaps-sdk' and webpack collapsed that to a direct import." ] }, "packages": { @@ -2469,13 +2470,26 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "@noble/hashes": true, "mockttp>async-mutex": true } }, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/json-rpc-engine": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@noble/hashes": true + } + }, "@metamask/snaps-sdk": { "globals": { "URL": true @@ -2488,6 +2502,37 @@ } }, "@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "URL": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/account-tree-controller>@metamask/snaps-utils": { + "packages": { + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-utils": { "globals": { "File": true, "FileReader": true, @@ -2506,7 +2551,6 @@ "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/permission-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-sdk": true, "@metamask/superstruct": true, "@metamask/utils": true, @@ -2522,6 +2566,30 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { + "globals": { + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/storage-service": { "globals": { "console.error": true, diff --git a/lavamoat/webpack/mv2/main/policy.json b/lavamoat/webpack/mv2/main/policy.json index 71498ca2c665..0eea08153b0e 100644 --- a/lavamoat/webpack/mv2/main/policy.json +++ b/lavamoat/webpack/mv2/main/policy.json @@ -760,7 +760,7 @@ "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/profile-sync-controller": true, - "@metamask/snaps-utils": true, + "@metamask/account-tree-controller>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "eslint>fast-deep-equal": true @@ -839,7 +839,7 @@ "@metamask/keyring-snap-client": true, "@metamask/phishing-controller": true, "@metamask/polling-controller": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controller>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "@metamask/assets-controller>bignumber.js": true, @@ -879,7 +879,7 @@ "@metamask/phishing-controller": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/core-backend>@tanstack/query-core": true, "mockttp>async-mutex": true, @@ -1724,7 +1724,7 @@ "@metamask/base-controller": true, "@metamask/delegation-core": true, "@metamask/delegation-deployments": true, - "@metamask/snaps-utils": true, + "@metamask/gator-permissions-controller>@metamask/snaps-utils": true, "@metamask/transaction-controller": true, "@metamask/utils": true } @@ -1939,7 +1939,7 @@ "@metamask/multichain-account-service>@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/multichain-account-service>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-account-service>@metamask/snaps-utils": true, "@metamask/utils": true, "mockttp>async-mutex": true, "lodash": true @@ -1978,13 +1978,14 @@ }, "@metamask/multichain-transactions-controller": { "globals": { - "console.error": true + "console.error": true, + "console.warn": true }, "packages": { "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true } }, "@metamask/name-controller": { @@ -2427,9 +2428,9 @@ "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, "@metamask/snaps-registry": true, - "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, "@metamask/utils": true, "@metamask/snaps-controllers>@xstate/fsm": true, "mockttp>async-mutex": true, @@ -2460,7 +2461,7 @@ "@metamask/snaps-rpc-methods": { "meta": { "webpack-optimization": [ - "Dependency '@metamask/snaps-utils' reexports from '@metamask/snaps-sdk' and webpack collapsed that to a direct import." + "Dependency '@metamask/snaps-rpc-methods>@metamask/snaps-utils' reexports from '@metamask/snaps-sdk' and webpack collapsed that to a direct import." ] }, "packages": { @@ -2469,13 +2470,26 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true, "@noble/hashes": true, "mockttp>async-mutex": true } }, + "@metamask/snaps-controllers>@metamask/snaps-rpc-methods": { + "packages": { + "@metamask/json-rpc-engine": true, + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-controllers>@metamask/snaps-utils": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@noble/hashes": true + } + }, "@metamask/snaps-sdk": { "globals": { "URL": true @@ -2488,6 +2502,37 @@ } }, "@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "URL": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/account-tree-controller>@metamask/snaps-utils": { + "packages": { + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-controllers>@metamask/snaps-utils": { "globals": { "File": true, "FileReader": true, @@ -2506,7 +2551,6 @@ "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/permission-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-sdk": true, "@metamask/superstruct": true, "@metamask/utils": true, @@ -2522,6 +2566,30 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { + "globals": { + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "chalk": true, + "cron-parser": true, + "fast-json-stable-stringify": true, + "luxon": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/storage-service": { "globals": { "console.error": true, diff --git a/lavamoat/webpack/mv3/beta/policy.json b/lavamoat/webpack/mv3/beta/policy.json index deea99e18274..5fd5670a7edb 100644 --- a/lavamoat/webpack/mv3/beta/policy.json +++ b/lavamoat/webpack/mv3/beta/policy.json @@ -1409,7 +1409,7 @@ }, "packages": { "@metamask/post-message-stream": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": true, "@metamask/utils": true } }, @@ -1418,7 +1418,7 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true } @@ -1439,18 +1439,37 @@ "FileReader": true, "TextDecoder": true, "URL": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": { + "globals": { "console.error": true, "console.log": true, "console.warn": true, "document.body.appendChild": true, - "document.createElement": true, - "fetch": true, - "navigator": true + "document.createElement": true }, + "packages": { + "@metamask/utils": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { "packages": { "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/permission-controller": true, - "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-sdk": true, "@metamask/superstruct": true, "@metamask/utils": true, @@ -1459,8 +1478,6 @@ "cron-parser": true, "fast-json-stable-stringify": true, "luxon": true, - "@metamask/snaps-utils>marked": true, - "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true } }, diff --git a/lavamoat/webpack/mv3/experimental/policy.json b/lavamoat/webpack/mv3/experimental/policy.json index deea99e18274..5fd5670a7edb 100644 --- a/lavamoat/webpack/mv3/experimental/policy.json +++ b/lavamoat/webpack/mv3/experimental/policy.json @@ -1409,7 +1409,7 @@ }, "packages": { "@metamask/post-message-stream": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": true, "@metamask/utils": true } }, @@ -1418,7 +1418,7 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true } @@ -1439,18 +1439,37 @@ "FileReader": true, "TextDecoder": true, "URL": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": { + "globals": { "console.error": true, "console.log": true, "console.warn": true, "document.body.appendChild": true, - "document.createElement": true, - "fetch": true, - "navigator": true + "document.createElement": true }, + "packages": { + "@metamask/utils": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { "packages": { "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/permission-controller": true, - "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-sdk": true, "@metamask/superstruct": true, "@metamask/utils": true, @@ -1459,8 +1478,6 @@ "cron-parser": true, "fast-json-stable-stringify": true, "luxon": true, - "@metamask/snaps-utils>marked": true, - "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true } }, diff --git a/lavamoat/webpack/mv3/flask/policy.json b/lavamoat/webpack/mv3/flask/policy.json index deea99e18274..5fd5670a7edb 100644 --- a/lavamoat/webpack/mv3/flask/policy.json +++ b/lavamoat/webpack/mv3/flask/policy.json @@ -1409,7 +1409,7 @@ }, "packages": { "@metamask/post-message-stream": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": true, "@metamask/utils": true } }, @@ -1418,7 +1418,7 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true } @@ -1439,18 +1439,37 @@ "FileReader": true, "TextDecoder": true, "URL": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": { + "globals": { "console.error": true, "console.log": true, "console.warn": true, "document.body.appendChild": true, - "document.createElement": true, - "fetch": true, - "navigator": true + "document.createElement": true }, + "packages": { + "@metamask/utils": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { "packages": { "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/permission-controller": true, - "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-sdk": true, "@metamask/superstruct": true, "@metamask/utils": true, @@ -1459,8 +1478,6 @@ "cron-parser": true, "fast-json-stable-stringify": true, "luxon": true, - "@metamask/snaps-utils>marked": true, - "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true } }, diff --git a/lavamoat/webpack/mv3/main/policy.json b/lavamoat/webpack/mv3/main/policy.json index deea99e18274..5fd5670a7edb 100644 --- a/lavamoat/webpack/mv3/main/policy.json +++ b/lavamoat/webpack/mv3/main/policy.json @@ -1409,7 +1409,7 @@ }, "packages": { "@metamask/post-message-stream": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": true, "@metamask/utils": true } }, @@ -1418,7 +1418,7 @@ "@metamask/permission-controller": true, "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, - "@metamask/snaps-utils": true, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": true, "@metamask/superstruct": true, "@metamask/utils": true } @@ -1439,18 +1439,37 @@ "FileReader": true, "TextDecoder": true, "URL": true, + "fetch": true, + "navigator": true + }, + "packages": { + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/superstruct": true, + "@metamask/utils": true, + "@scure/base": true, + "fast-json-stable-stringify": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/snaps-execution-environments>@metamask/snaps-utils": { + "globals": { "console.error": true, "console.log": true, "console.warn": true, "document.body.appendChild": true, - "document.createElement": true, - "fetch": true, - "navigator": true + "document.createElement": true }, + "packages": { + "@metamask/utils": true + } + }, + "@metamask/snaps-rpc-methods>@metamask/snaps-utils": { "packages": { "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/permission-controller": true, - "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-sdk": true, "@metamask/superstruct": true, "@metamask/utils": true, @@ -1459,8 +1478,6 @@ "cron-parser": true, "fast-json-stable-stringify": true, "luxon": true, - "@metamask/snaps-utils>marked": true, - "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true } }, diff --git a/package.json b/package.json index 91ef598b08cd..81cc1a8db52b 100644 --- a/package.json +++ b/package.json @@ -393,7 +393,7 @@ "@metamask/multichain-api-client": "^0.10.1", "@metamask/multichain-api-middleware": "^3.1.0", "@metamask/multichain-network-controller": "^3.1.0", - "@metamask/multichain-transactions-controller": "^7.1.0", + "@metamask/multichain-transactions-controller": "file:.yalc/@metamask/multichain-transactions-controller", "@metamask/name-controller": "^9.1.0", "@metamask/network-controller": "^32.0.0", "@metamask/network-enablement-controller": "^5.1.0", @@ -424,12 +424,12 @@ "@metamask/shield-controller": "^5.0.1", "@metamask/signature-controller": "^39.2.0", "@metamask/smart-transactions-controller": "^24.1.0", - "@metamask/snaps-controllers": "^20.0.5", + "@metamask/snaps-controllers": "file:.yalc/@metamask/snaps-controllers", "@metamask/snaps-execution-environments": "^11.0.2", - "@metamask/snaps-rpc-methods": "^16.0.0", - "@metamask/snaps-sdk": "^11.1.1", - "@metamask/snaps-utils": "^12.2.0", - "@metamask/solana-wallet-snap": "^2.8.0", + "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods", + "@metamask/snaps-sdk": "file:.yalc/@metamask/snaps-sdk", + "@metamask/snaps-utils": "file:.yalc/@metamask/snaps-utils", + "@metamask/solana-wallet-snap": "file:.yalc/@metamask/solana-wallet-snap", "@metamask/solana-wallet-standard": "^1.0.0", "@metamask/storage-service": "^1.0.0", "@metamask/subscription-controller": "^6.1.2", diff --git a/shared/constants/confirmations.ts b/shared/constants/confirmations.ts new file mode 100644 index 000000000000..2c2c9c25d375 --- /dev/null +++ b/shared/constants/confirmations.ts @@ -0,0 +1,6 @@ +/** + * POC flag. When true, non-EVM send confirmations triggered via + * `snap_confirmTransaction` render the new universal multichain + * confirmation page instead of the legacy snap-templated dialog. + */ +export const USE_UNIVERSAL_MULTICHAIN_CONFIRMATION = true; diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts index e989f61ee48f..910f8f66b539 100644 --- a/ui/helpers/constants/routes.ts +++ b/ui/helpers/constants/routes.ts @@ -128,6 +128,8 @@ export const NOTIFICATIONS_ROUTE = '/notifications'; export const NOTIFICATIONS_SETTINGS_ROUTE = '/settings/notifications'; export const CONNECTED_ROUTE = '/connected'; export const CONNECTED_ACCOUNTS_ROUTE = '/connected/accounts'; +export const CONFIRM_MULTICHAIN_TRANSACTION_PATH = + '/confirm-multichain-transaction/:id'; export const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'; export const CONFIRMATION_V_NEXT_ROUTE = '/confirmation'; export const SIGNATURE_REQUEST_PATH = '/signature-request'; diff --git a/ui/pages/confirmations/components/confirm/universal-transaction/index.ts b/ui/pages/confirmations/components/confirm/universal-transaction/index.ts new file mode 100644 index 000000000000..b1459049dcaa --- /dev/null +++ b/ui/pages/confirmations/components/confirm/universal-transaction/index.ts @@ -0,0 +1 @@ +export { UniversalTransactionConfirmation } from './universal-transaction-confirmation'; diff --git a/ui/pages/confirmations/components/confirm/universal-transaction/universal-transaction-confirmation.tsx b/ui/pages/confirmations/components/confirm/universal-transaction/universal-transaction-confirmation.tsx new file mode 100644 index 000000000000..c1589b1c661c --- /dev/null +++ b/ui/pages/confirmations/components/confirm/universal-transaction/universal-transaction-confirmation.tsx @@ -0,0 +1,158 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { providerErrors } from '@metamask/rpc-errors'; + +import { calcTokenAmount } from '../../../../../../shared/lib/transactions-controller-utils'; +import { ConfirmInfoRow } from '../../../../../components/app/confirm/info/row/row'; +import { ConfirmInfoSection } from '../../../../../components/app/confirm/info/row/section'; +import { + AvatarToken, + AvatarTokenSize, + Box, + Button, + ButtonSize, + ButtonVariant, + Text, +} from '../../../../../components/component-library'; +import { + Footer as PageFooter, + Page, +} from '../../../../../components/multichain/pages/page'; +import PulseLoader from '../../../../../components/ui/pulse-loader'; +import { getIntlLocale } from '../../../../../ducks/locale/locale'; +import { + AlignItems, + BlockSize, + Display, + FlexDirection, + JustifyContent, + TextAlign, + TextColor, + TextVariant, +} from '../../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../../hooks/useI18nContext'; +import { + rejectPendingApproval, + resolvePendingApproval, +} from '../../../../../store/actions'; +import { usePendingMultichainTransaction } from '../../../hooks/usePendingMultichainTransaction'; +import { formatAmount } from '../../simulation-details/formatAmount'; +import SendHeadingLayout from '../info/shared/send-heading-layout/send-heading-layout'; + +export function UniversalTransactionConfirmation() { + const { id } = useParams<{ id: string }>(); + const dispatch = useDispatch(); + const pendingTx = usePendingMultichainTransaction(id); + const t = useI18nContext(); + const locale = useSelector(getIntlLocale); + + if (!pendingTx) { + return ( + + + + ); + } + + const handleConfirm = () => { + if (id) { + dispatch(resolvePendingApproval(id, {})); + } + }; + + const handleReject = () => { + if (id) { + dispatch( + rejectPendingApproval( + id, + providerErrors.userRejectedRequest().serialize(), + ), + ); + } + }; + + const nativeAssetTransferValue = calcTokenAmount( + pendingTx.value, + pendingTx.assetDecimals, + ); + const roundedTransferValue = formatAmount(locale, nativeAssetTransferValue); + + const NetworkImage = ( + + ); + + const NativeAssetAmount = ( + + + {`${roundedTransferValue} ${pendingTx.assetSymbol}`} + + + ); + + return ( + + + + {t('send')} + + + + {NativeAssetAmount} + + + + + {pendingTx.chainNamespace === 'solana' ? 'Solana' : pendingTx.chain} + + {pendingTx.origin} + {pendingTx.from} + {pendingTx.to} + + {pendingTx.feeRaw + ? `${formatAmount( + locale, + calcTokenAmount(pendingTx.feeRaw, pendingTx.assetDecimals), + )} ${pendingTx.assetSymbol}` + : ''} + + + + + + + + + + + + ); +} diff --git a/ui/pages/confirmations/hooks/send/useAmountValidation.ts b/ui/pages/confirmations/hooks/send/useAmountValidation.ts index 15911ed7af7f..6e60a1d788b0 100644 --- a/ui/pages/confirmations/hooks/send/useAmountValidation.ts +++ b/ui/pages/confirmations/hooks/send/useAmountValidation.ts @@ -42,15 +42,18 @@ export const useAmountValidation = () => { } try { + console.log('[useAmountValidation] calling validateAmountWithSnap with amount:', amount || '0'); const result = (await validateAmountWithSnap( amount || '0', )) as SnapOnAmountInputResult; + console.log('[useAmountValidation] snap result:', result); if (result.errors?.length > 0) { return mapSnapErrorCodeIntoTranslation(result.errors[0].code, t); } return undefined; } catch (error) { + console.error('[useAmountValidation] snap RPC threw error:', error); return t('invalidValue'); } }, @@ -65,21 +68,24 @@ export const useAmountValidation = () => { const normalizedValue = normalizeAmount(value); const validations = [ - () => validatePositiveNumericString(normalizedValue, t), - () => validateERC1155Balance(asset as Asset, normalizedValue, t), - () => + { name: 'validatePositiveNumericString', fn: () => validatePositiveNumericString(normalizedValue, t) }, + { name: 'validateERC1155Balance', fn: () => validateERC1155Balance(asset as Asset, normalizedValue, t) }, + { name: 'validateTokenBalance', fn: () => validateTokenBalance( normalizedValue, rawBalanceNumeric, asset?.decimals, t, ), - () => validateNonEvmAmount(normalizedValue), + }, + { name: 'validateNonEvmAmount', fn: () => validateNonEvmAmount(normalizedValue) }, ]; for (const validation of validations) { - const error = await Promise.resolve(validation()); + const error = await Promise.resolve(validation.fn()); + console.log(`[useAmountValidation] ${validation.name} result:`, error ?? 'OK', '| rawBalanceNumeric:', rawBalanceNumeric?.toString(), '| decimals:', asset?.decimals); if (error) { + console.error(`[useAmountValidation] BLOCKED by ${validation.name}:`, error); return setAndReturnError(error); } } @@ -144,7 +150,9 @@ export function validatePositiveNumericString( value: string, t: ReturnType, ): string | undefined { - if (!isValidPositiveNumericString(value)) { + const valid = isValidPositiveNumericString(value); + console.log('[useAmountValidation] validatePositiveNumericString value:', JSON.stringify(value), 'valid:', valid); + if (!valid) { return t('invalidValue'); } return undefined; diff --git a/ui/pages/confirmations/hooks/useConfirmationNavigation.ts b/ui/pages/confirmations/hooks/useConfirmationNavigation.ts index 1eeff600cda8..bbb947b1e9d8 100644 --- a/ui/pages/confirmations/hooks/useConfirmationNavigation.ts +++ b/ui/pages/confirmations/hooks/useConfirmationNavigation.ts @@ -10,6 +10,7 @@ import { TEMPLATED_CONFIRMATION_APPROVAL_TYPES } from '../confirmation/templates import { CONFIRM_ADD_SUGGESTED_NFT_ROUTE, CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, + CONFIRM_MULTICHAIN_TRANSACTION_PATH, CONFIRM_TRANSACTION_ROUTE, CONFIRMATION_V_NEXT_ROUTE, CONNECT_ROUTE, @@ -22,6 +23,7 @@ import { getApprovalFlows, selectPendingApprovalsForNavigation, } from '../../../selectors'; +import { USE_UNIVERSAL_MULTICHAIN_CONFIRMATION } from '../../../../shared/constants/confirmations'; import { sanitizeRedirectUrl } from '../../../../shared/lib/safe-redirect'; export enum ConfirmationLoader { @@ -165,6 +167,15 @@ export function getConfirmationRoute( if (isSignatureTransactionType(nextConfirmation)) { return `${CONFIRM_TRANSACTION_ROUTE}/${confirmationId}${SIGNATURE_REQUEST_PATH}`; } + + if ( + USE_UNIVERSAL_MULTICHAIN_CONFIRMATION && + type === ApprovalType.Transaction && + nextConfirmation.requestData?.chainNamespace + ) { + return CONFIRM_MULTICHAIN_TRANSACTION_PATH.replace(':id', confirmationId); + } + if (type === ApprovalType.Transaction) { let url = `${CONFIRM_TRANSACTION_ROUTE}/${confirmationId}`; if (queryString.length) { diff --git a/ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts b/ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts new file mode 100644 index 000000000000..0225bfe95371 --- /dev/null +++ b/ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts @@ -0,0 +1,9 @@ +import { useSelector } from 'react-redux'; + +export function usePendingMultichainTransaction( + approvalId: string | undefined, +) { + return useSelector((state: any) => + approvalId ? state.metamask.pendingTransactions?.[approvalId] : undefined, + ); +} diff --git a/ui/pages/routes/routes.component.tsx b/ui/pages/routes/routes.component.tsx index 5503b8827536..f19a4419f3a1 100644 --- a/ui/pages/routes/routes.component.tsx +++ b/ui/pages/routes/routes.component.tsx @@ -22,8 +22,9 @@ import Alerts from '../../components/app/alerts'; import { ASSET_ROUTE, - CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, CONFIRM_ADD_SUGGESTED_NFT_ROUTE, + CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, + CONFIRM_MULTICHAIN_TRANSACTION_PATH, CONFIRM_TRANSACTION_ROUTE, CONNECT_ROUTE, DEFAULT_ROUTE, @@ -118,6 +119,7 @@ import NetworkConfirmationPopover from '../../components/multichain/network-list import { ToastMaster } from '../../components/app/toast-master/toast-master'; import { mmLazy } from '../../helpers/utils/mm-lazy'; import CrossChainSwapTxDetails from '../bridge/transaction-details/transaction-details'; +import { UniversalTransactionConfirmation } from '../../pages/confirmations/components/confirm/universal-transaction'; import { MultichainAccountAddressListPage } from '../multichain-accounts/multichain-account-address-list-page'; import { MultichainAccountPrivateKeyListPage } from '../multichain-accounts/multichain-account-private-key-list-page'; import MultichainAccountIntroModalContainer from '../../components/app/modals/multichain-accounts/intro-modal'; @@ -359,6 +361,10 @@ export const routeConfig = [ path: `${CONFIRM_TRANSACTION_ROUTE}/:id?/*`, element: , }, + { + path: CONFIRM_MULTICHAIN_TRANSACTION_PATH, + element: , + }, { path: CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, element: , diff --git a/yarn.lock b/yarn.lock index 30eb7307684a..792a09714bcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7462,17 +7462,17 @@ __metadata: languageName: node linkType: hard -"@metamask/multichain-transactions-controller@npm:^7.1.0": +"@metamask/multichain-transactions-controller@file:.yalc/@metamask/multichain-transactions-controller::locator=metamask-crx%40workspace%3A.": version: 7.1.0 - resolution: "@metamask/multichain-transactions-controller@npm:7.1.0" + resolution: "@metamask/multichain-transactions-controller@file:.yalc/@metamask/multichain-transactions-controller#.yalc/@metamask/multichain-transactions-controller::hash=069536&locator=metamask-crx%40workspace%3A." dependencies: - "@metamask/accounts-controller": "npm:^38.0.0" + "@metamask/accounts-controller": "npm:^38.1.1" "@metamask/base-controller": "npm:^9.1.0" "@metamask/keyring-api": "npm:^23.1.0" "@metamask/keyring-internal-api": "npm:^11.0.1" "@metamask/keyring-snap-client": "npm:^9.0.2" "@metamask/messenger": "npm:^1.2.0" - "@metamask/polling-controller": "npm:^16.0.4" + "@metamask/polling-controller": "npm:^16.0.6" "@metamask/snaps-controllers": "npm:^19.0.0" "@metamask/snaps-sdk": "npm:^11.0.0" "@metamask/snaps-utils": "npm:^12.1.2" @@ -7480,7 +7480,7 @@ __metadata: "@types/uuid": "npm:^8.3.0" immer: "npm:^9.0.6" uuid: "npm:^8.3.2" - checksum: 10/38e5be05c12e43cdab875f1f7c46fc8feaeacedff6bf0080dd90a7fa509b78c2f2168f8aca0f706c39725c76b735125c6b75649bc43d24010e5c63466d4c820b + checksum: 10/8aff19badf0647e23bc8a77134d377673460f21bdcb3c4d7f484a0b86ee93a4c7d1938eb5b9d58e94192e2e69d6caed298c4ff46a3dce891a0cbaafcc440a9d2 languageName: node linkType: hard @@ -7730,6 +7730,21 @@ __metadata: languageName: node linkType: hard +"@metamask/polling-controller@npm:^16.0.6": + version: 16.0.6 + resolution: "@metamask/polling-controller@npm:16.0.6" + dependencies: + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/controller-utils": "npm:^12.1.0" + "@metamask/network-controller": "npm:^32.0.0" + "@metamask/utils": "npm:^11.9.0" + "@types/uuid": "npm:^8.3.0" + fast-json-stable-stringify: "npm:^2.1.0" + uuid: "npm:^8.3.2" + checksum: 10/12990b447a5188072e5614b17ae6baa1f30911878f995e566387f53e4309d8729a6768b61d1e042462b9d65541c3f0d7caf1df3aeb8a41756bbef7bafd1b8dab + languageName: node + linkType: hard + "@metamask/post-message-stream@npm:^10.0.0": version: 10.0.0 resolution: "@metamask/post-message-stream@npm:10.0.0" @@ -8262,6 +8277,23 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-rpc-methods@file:.yalc/@metamask/snaps-rpc-methods::locator=metamask-crx%40workspace%3A.": + version: 16.0.0 + resolution: "@metamask/snaps-rpc-methods@file:.yalc/@metamask/snaps-rpc-methods#.yalc/@metamask/snaps-rpc-methods::hash=97b20a&locator=metamask-crx%40workspace%3A." + dependencies: + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/permission-controller": "npm:^13.1.0" + "@metamask/rpc-errors": "npm:^7.0.3" + "@metamask/snaps-sdk": "npm:^11.1.1" + "@metamask/snaps-utils": "npm:^12.2.0" + "@metamask/superstruct": "npm:^3.2.1" + "@metamask/utils": "npm:^11.11.0" + "@noble/hashes": "npm:^1.7.1" + async-mutex: "npm:^0.5.0" + checksum: 10/f38566b0eab8bd1a0a266295556c1c9c4916e087aff9ad74c69c754896060d2c8a174c55040b75a775ec9bc04c1e510015bff6d7fb547c18bde922aea4008ed2 + languageName: node + linkType: hard + "@metamask/snaps-rpc-methods@npm:^16.0.0": version: 16.0.0 resolution: "@metamask/snaps-rpc-methods@npm:16.0.0" @@ -8293,6 +8325,37 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-utils@file:.yalc/@metamask/snaps-utils::locator=metamask-crx%40workspace%3A.": + version: 12.2.0 + resolution: "@metamask/snaps-utils@file:.yalc/@metamask/snaps-utils#.yalc/@metamask/snaps-utils::hash=86a5eb&locator=metamask-crx%40workspace%3A." + dependencies: + "@babel/core": "npm:^7.23.2" + "@babel/types": "npm:^7.23.0" + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/permission-controller": "npm:^13.1.0" + "@metamask/rpc-errors": "npm:^7.0.3" + "@metamask/slip44": "npm:^4.4.0" + "@metamask/snaps-registry": "npm:^4.0.0" + "@metamask/snaps-sdk": "npm:^11.1.1" + "@metamask/superstruct": "npm:^3.2.1" + "@metamask/utils": "npm:^11.11.0" + "@scure/base": "npm:^1.1.1" + chalk: "npm:^4.1.2" + cron-parser: "npm:^4.5.0" + fast-deep-equal: "npm:^3.1.3" + fast-json-stable-stringify: "npm:^2.1.0" + fast-xml-parser: "npm:^5.5.6" + luxon: "npm:^3.5.0" + marked: "npm:^12.0.1" + rfdc: "npm:^1.3.0" + semver: "npm:^7.5.4" + ses: "npm:^1.15.0" + validate-npm-package-name: "npm:^5.0.0" + checksum: 10/8eed4b34dfacf15ae72c27629c377c1e7051790e1f5623d3506d7deecd3ed81c3291b516e236839983800bb6587f5eccc6ad1603c121e965eddc3b56e0ab7ab3 + languageName: node + linkType: hard + "@metamask/snaps-utils@npm:^11.0.0": version: 11.7.1 resolution: "@metamask/snaps-utils@npm:11.7.1" @@ -8355,10 +8418,10 @@ __metadata: languageName: node linkType: hard -"@metamask/solana-wallet-snap@npm:^2.8.0": +"@metamask/solana-wallet-snap@file:.yalc/@metamask/solana-wallet-snap::locator=metamask-crx%40workspace%3A.": version: 2.8.0 - resolution: "@metamask/solana-wallet-snap@npm:2.8.0" - checksum: 10/5e26f28d585aa00c4da204a7311f15048de30ff336baa90df1ca82f0d151b25dcf7b3fc3daa8b9ab02914aca35f31eb7a2854bf167008f6e96f99e215f02767c + resolution: "@metamask/solana-wallet-snap@file:.yalc/@metamask/solana-wallet-snap#.yalc/@metamask/solana-wallet-snap::hash=70fd09&locator=metamask-crx%40workspace%3A." + checksum: 10/89c0e066e2532cf7fda75473a81f8c4d74d35ada6b9d1f02f8b7718094684dc96a2bce6a5516e780d75f9abffc083db29ed32fad9a69dbf4f95c7c6ab619af9c languageName: node linkType: hard @@ -33719,7 +33782,7 @@ __metadata: "@metamask/multichain-api-client": "npm:^0.10.1" "@metamask/multichain-api-middleware": "npm:^3.1.0" "@metamask/multichain-network-controller": "npm:^3.1.0" - "@metamask/multichain-transactions-controller": "npm:^7.1.0" + "@metamask/multichain-transactions-controller": "file:.yalc/@metamask/multichain-transactions-controller" "@metamask/name-controller": "npm:^9.1.0" "@metamask/network-controller": "npm:^32.0.0" "@metamask/network-enablement-controller": "npm:^5.1.0" @@ -33754,13 +33817,13 @@ __metadata: "@metamask/smart-transactions-controller": "npm:^24.1.0" "@metamask/snap-account-abstraction-keyring-site": "npm:^1.0.0" "@metamask/snap-simple-keyring-site": "npm:^2.1.1" - "@metamask/snaps-controllers": "npm:^20.0.5" + "@metamask/snaps-controllers": "file:.yalc/@metamask/snaps-controllers" "@metamask/snaps-execution-environments": "npm:^11.0.2" "@metamask/snaps-registry": "npm:^4.0.0" - "@metamask/snaps-rpc-methods": "npm:^16.0.0" - "@metamask/snaps-sdk": "npm:^11.1.1" - "@metamask/snaps-utils": "npm:^12.2.0" - "@metamask/solana-wallet-snap": "npm:^2.8.0" + "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods" + "@metamask/snaps-sdk": "file:.yalc/@metamask/snaps-sdk" + "@metamask/snaps-utils": "file:.yalc/@metamask/snaps-utils" + "@metamask/solana-wallet-snap": "file:.yalc/@metamask/solana-wallet-snap" "@metamask/solana-wallet-standard": "npm:^1.0.0" "@metamask/storage-service": "npm:^1.0.0" "@metamask/subscription-controller": "npm:^6.1.2" From c0aab9cdabf7e69cf4532ea37e07f96bbf1e1b99 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 20 May 2026 21:32:20 +0100 Subject: [PATCH 02/28] feat: temporarily disable showUniversalTransactionConfirmation hook for dependency isolation --- .../permissions/snaps/specifications.ts | 89 ++----------------- 1 file changed, 7 insertions(+), 82 deletions(-) diff --git a/app/scripts/controllers/permissions/snaps/specifications.ts b/app/scripts/controllers/permissions/snaps/specifications.ts index 4c7ced3ae2c7..3af0d39c2c71 100644 --- a/app/scripts/controllers/permissions/snaps/specifications.ts +++ b/app/scripts/controllers/permissions/snaps/specifications.ts @@ -4,12 +4,12 @@ import { RestrictedMethodMessenger, } from '@metamask/snaps-rpc-methods'; import { ControllerGetStateAction } from '@metamask/base-controller'; -import { ApprovalType } from '@metamask/controller-utils'; +// import { ApprovalType } from '@metamask/controller-utils'; // POC: re-enable with snaps-rpc-methods yalc import { CurrencyRateController } from '@metamask/assets-controllers'; import type { AssetsControllerGetStateAction } from '@metamask/assets-controller'; -import type { MultichainTransactionsControllerActions } from '@metamask/multichain-transactions-controller'; +// import type { MultichainTransactionsControllerActions } from '@metamask/multichain-transactions-controller'; // POC: re-enable with snaps-rpc-methods yalc import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; -import { rpcErrors } from '@metamask/rpc-errors'; +// import { rpcErrors } from '@metamask/rpc-errors'; // POC: re-enable with snaps-rpc-methods yalc import { SnapControllerClearSnapStateAction, SnapControllerGetSnapAction, @@ -20,7 +20,7 @@ import { SnapInterfaceControllerGetInterfaceAction, SnapInterfaceControllerSetInterfaceDisplayedAction, } from '@metamask/snaps-controllers'; -import { nanoid } from 'nanoid'; +// import { nanoid } from 'nanoid'; // POC: re-enable with snaps-rpc-methods yalc import { KeyringControllerGetKeyringsByTypeAction, KeyringControllerWithKeyringAction, @@ -36,7 +36,7 @@ import { ExcludedSnapEndowments, ExcludedSnapPermissions, } from '../../../../../shared/constants/snaps/permissions'; -import { USE_UNIVERSAL_MULTICHAIN_CONFIRMATION } from '../../../../../shared/constants/confirmations'; +// import { USE_UNIVERSAL_MULTICHAIN_CONFIRMATION } from '../../../../../shared/constants/confirmations'; // POC: re-enable with snaps-rpc-methods yalc import { PreferencesControllerGetStateAction } from '../../preferences-controller'; import { KeyringType } from '../../../../../shared/constants/keyring'; import { AppStateControllerGetUnlockPromiseAction } from '../../app-state-controller-method-action-types'; @@ -48,19 +48,7 @@ import { } from '../../../../../shared/lib/assets-unify-state/remote-feature-flag'; import { getIsAssetsUnifiedStateIncludedInBuild } from '../../../../../shared/lib/environment'; -type ConfirmTransactionParams = { - accountId: string; - assetDecimals: number; - assetSymbol: string; - assetType: string; - chain: string; - chainNamespace: 'solana' | 'bip122' | 'tron'; - feeAssetType?: string; - feeRaw?: string; - from: string; - to: string; - value: string; -}; +// POC: ConfirmTransactionParams type removed — re-enable with snaps-rpc-methods yalc export type SnapPermissionSpecificationsActions = | AppStateControllerGetUnlockPromiseAction @@ -85,8 +73,7 @@ export type SnapPermissionSpecificationsActions = | SnapInterfaceControllerSetInterfaceDisplayedAction | TestOrigin | SnapControllerUpdateSnapStateAction - | ApprovalControllerAddRequestAction - | MultichainTransactionsControllerActions; + | ApprovalControllerAddRequestAction; /** * Get the permission specifications for Snaps. @@ -200,68 +187,6 @@ export function getSnapPermissionSpecifications( */ getClientCryptography: () => ({}), - showUniversalTransactionConfirmation: async ( - snapId: string, - params: ConfirmTransactionParams, - ) => { - if (!USE_UNIVERSAL_MULTICHAIN_CONFIRMATION) { - throw rpcErrors.methodNotSupported(); - } - - const approvalId = nanoid(); - messenger.call( - 'MultichainTransactionsController:addPendingTransaction', - { - approvalId, - chainNamespace: params.chainNamespace, - chain: params.chain, - accountId: params.accountId, - from: params.from, - to: params.to, - value: params.value, - assetType: params.assetType, - assetSymbol: params.assetSymbol, - assetDecimals: params.assetDecimals, - feeRaw: params.feeRaw, - feeAssetType: params.feeAssetType, - origin: snapId, - createdAt: Date.now(), - }, - ); - - const addAndShowApprovalRequest = async () => - messenger.call( - 'ApprovalController:addRequest', - { - id: approvalId, - origin: snapId, - type: ApprovalType.Transaction, - requestData: { - approvalId, - chainNamespace: params.chainNamespace, - txParams: { - from: params.from, - to: params.to, - value: params.value, - }, - }, - }, - true, - ); - - try { - await addAndShowApprovalRequest(); - return true; - } catch (_err) { - return false; - } finally { - messenger.call( - 'MultichainTransactionsController:removePendingTransaction', - approvalId, - ); - } - }, - getSnapKeyring: async () => { // TODO: Use `withKeyring` instead. const [snapKeyring] = messenger.call( From 98bede1e380886f4be57e3268b420a3ec8833935 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 20 May 2026 21:46:21 +0100 Subject: [PATCH 03/28] feat: enable multichain-transactions-controller yalc link --- package.json | 10 ++++---- yarn.lock | 64 +++++++--------------------------------------------- 2 files changed, 13 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 81cc1a8db52b..b2bdca901038 100644 --- a/package.json +++ b/package.json @@ -424,12 +424,12 @@ "@metamask/shield-controller": "^5.0.1", "@metamask/signature-controller": "^39.2.0", "@metamask/smart-transactions-controller": "^24.1.0", - "@metamask/snaps-controllers": "file:.yalc/@metamask/snaps-controllers", + "@metamask/snaps-controllers": "^20.0.5", "@metamask/snaps-execution-environments": "^11.0.2", - "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods", - "@metamask/snaps-sdk": "file:.yalc/@metamask/snaps-sdk", - "@metamask/snaps-utils": "file:.yalc/@metamask/snaps-utils", - "@metamask/solana-wallet-snap": "file:.yalc/@metamask/solana-wallet-snap", + "@metamask/snaps-rpc-methods": "^16.0.0", + "@metamask/snaps-sdk": "^11.1.1", + "@metamask/snaps-utils": "^12.2.0", + "@metamask/solana-wallet-snap": "^2.8.0", "@metamask/solana-wallet-standard": "^1.0.0", "@metamask/storage-service": "^1.0.0", "@metamask/subscription-controller": "^6.1.2", diff --git a/yarn.lock b/yarn.lock index 792a09714bcc..6165a99248a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8277,23 +8277,6 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-rpc-methods@file:.yalc/@metamask/snaps-rpc-methods::locator=metamask-crx%40workspace%3A.": - version: 16.0.0 - resolution: "@metamask/snaps-rpc-methods@file:.yalc/@metamask/snaps-rpc-methods#.yalc/@metamask/snaps-rpc-methods::hash=97b20a&locator=metamask-crx%40workspace%3A." - dependencies: - "@metamask/key-tree": "npm:^10.1.1" - "@metamask/permission-controller": "npm:^13.1.0" - "@metamask/rpc-errors": "npm:^7.0.3" - "@metamask/snaps-sdk": "npm:^11.1.1" - "@metamask/snaps-utils": "npm:^12.2.0" - "@metamask/superstruct": "npm:^3.2.1" - "@metamask/utils": "npm:^11.11.0" - "@noble/hashes": "npm:^1.7.1" - async-mutex: "npm:^0.5.0" - checksum: 10/f38566b0eab8bd1a0a266295556c1c9c4916e087aff9ad74c69c754896060d2c8a174c55040b75a775ec9bc04c1e510015bff6d7fb547c18bde922aea4008ed2 - languageName: node - linkType: hard - "@metamask/snaps-rpc-methods@npm:^16.0.0": version: 16.0.0 resolution: "@metamask/snaps-rpc-methods@npm:16.0.0" @@ -8325,37 +8308,6 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils@file:.yalc/@metamask/snaps-utils::locator=metamask-crx%40workspace%3A.": - version: 12.2.0 - resolution: "@metamask/snaps-utils@file:.yalc/@metamask/snaps-utils#.yalc/@metamask/snaps-utils::hash=86a5eb&locator=metamask-crx%40workspace%3A." - dependencies: - "@babel/core": "npm:^7.23.2" - "@babel/types": "npm:^7.23.0" - "@metamask/key-tree": "npm:^10.1.1" - "@metamask/messenger": "npm:^1.2.0" - "@metamask/permission-controller": "npm:^13.1.0" - "@metamask/rpc-errors": "npm:^7.0.3" - "@metamask/slip44": "npm:^4.4.0" - "@metamask/snaps-registry": "npm:^4.0.0" - "@metamask/snaps-sdk": "npm:^11.1.1" - "@metamask/superstruct": "npm:^3.2.1" - "@metamask/utils": "npm:^11.11.0" - "@scure/base": "npm:^1.1.1" - chalk: "npm:^4.1.2" - cron-parser: "npm:^4.5.0" - fast-deep-equal: "npm:^3.1.3" - fast-json-stable-stringify: "npm:^2.1.0" - fast-xml-parser: "npm:^5.5.6" - luxon: "npm:^3.5.0" - marked: "npm:^12.0.1" - rfdc: "npm:^1.3.0" - semver: "npm:^7.5.4" - ses: "npm:^1.15.0" - validate-npm-package-name: "npm:^5.0.0" - checksum: 10/8eed4b34dfacf15ae72c27629c377c1e7051790e1f5623d3506d7deecd3ed81c3291b516e236839983800bb6587f5eccc6ad1603c121e965eddc3b56e0ab7ab3 - languageName: node - linkType: hard - "@metamask/snaps-utils@npm:^11.0.0": version: 11.7.1 resolution: "@metamask/snaps-utils@npm:11.7.1" @@ -8418,10 +8370,10 @@ __metadata: languageName: node linkType: hard -"@metamask/solana-wallet-snap@file:.yalc/@metamask/solana-wallet-snap::locator=metamask-crx%40workspace%3A.": +"@metamask/solana-wallet-snap@npm:^2.8.0": version: 2.8.0 - resolution: "@metamask/solana-wallet-snap@file:.yalc/@metamask/solana-wallet-snap#.yalc/@metamask/solana-wallet-snap::hash=70fd09&locator=metamask-crx%40workspace%3A." - checksum: 10/89c0e066e2532cf7fda75473a81f8c4d74d35ada6b9d1f02f8b7718094684dc96a2bce6a5516e780d75f9abffc083db29ed32fad9a69dbf4f95c7c6ab619af9c + resolution: "@metamask/solana-wallet-snap@npm:2.8.0" + checksum: 10/5e26f28d585aa00c4da204a7311f15048de30ff336baa90df1ca82f0d151b25dcf7b3fc3daa8b9ab02914aca35f31eb7a2854bf167008f6e96f99e215f02767c languageName: node linkType: hard @@ -33817,13 +33769,13 @@ __metadata: "@metamask/smart-transactions-controller": "npm:^24.1.0" "@metamask/snap-account-abstraction-keyring-site": "npm:^1.0.0" "@metamask/snap-simple-keyring-site": "npm:^2.1.1" - "@metamask/snaps-controllers": "file:.yalc/@metamask/snaps-controllers" + "@metamask/snaps-controllers": "npm:^20.0.5" "@metamask/snaps-execution-environments": "npm:^11.0.2" "@metamask/snaps-registry": "npm:^4.0.0" - "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods" - "@metamask/snaps-sdk": "file:.yalc/@metamask/snaps-sdk" - "@metamask/snaps-utils": "file:.yalc/@metamask/snaps-utils" - "@metamask/solana-wallet-snap": "file:.yalc/@metamask/solana-wallet-snap" + "@metamask/snaps-rpc-methods": "npm:^16.0.0" + "@metamask/snaps-sdk": "npm:^11.1.1" + "@metamask/snaps-utils": "npm:^12.2.0" + "@metamask/solana-wallet-snap": "npm:^2.8.0" "@metamask/solana-wallet-standard": "npm:^1.0.0" "@metamask/storage-service": "npm:^1.0.0" "@metamask/subscription-controller": "npm:^6.1.2" From 4c555ef7e2cb34a7345b02c9050b8a0b3a0178cf Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 20 May 2026 21:50:54 +0100 Subject: [PATCH 04/28] feat: enable snaps-rpc-methods yalc link and restore hook --- .../permissions/snaps/specifications.ts | 89 +++++++++++++++++-- package.json | 2 +- yarn.lock | 19 +++- 3 files changed, 101 insertions(+), 9 deletions(-) diff --git a/app/scripts/controllers/permissions/snaps/specifications.ts b/app/scripts/controllers/permissions/snaps/specifications.ts index 3af0d39c2c71..4c7ced3ae2c7 100644 --- a/app/scripts/controllers/permissions/snaps/specifications.ts +++ b/app/scripts/controllers/permissions/snaps/specifications.ts @@ -4,12 +4,12 @@ import { RestrictedMethodMessenger, } from '@metamask/snaps-rpc-methods'; import { ControllerGetStateAction } from '@metamask/base-controller'; -// import { ApprovalType } from '@metamask/controller-utils'; // POC: re-enable with snaps-rpc-methods yalc +import { ApprovalType } from '@metamask/controller-utils'; import { CurrencyRateController } from '@metamask/assets-controllers'; import type { AssetsControllerGetStateAction } from '@metamask/assets-controller'; -// import type { MultichainTransactionsControllerActions } from '@metamask/multichain-transactions-controller'; // POC: re-enable with snaps-rpc-methods yalc +import type { MultichainTransactionsControllerActions } from '@metamask/multichain-transactions-controller'; import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; -// import { rpcErrors } from '@metamask/rpc-errors'; // POC: re-enable with snaps-rpc-methods yalc +import { rpcErrors } from '@metamask/rpc-errors'; import { SnapControllerClearSnapStateAction, SnapControllerGetSnapAction, @@ -20,7 +20,7 @@ import { SnapInterfaceControllerGetInterfaceAction, SnapInterfaceControllerSetInterfaceDisplayedAction, } from '@metamask/snaps-controllers'; -// import { nanoid } from 'nanoid'; // POC: re-enable with snaps-rpc-methods yalc +import { nanoid } from 'nanoid'; import { KeyringControllerGetKeyringsByTypeAction, KeyringControllerWithKeyringAction, @@ -36,7 +36,7 @@ import { ExcludedSnapEndowments, ExcludedSnapPermissions, } from '../../../../../shared/constants/snaps/permissions'; -// import { USE_UNIVERSAL_MULTICHAIN_CONFIRMATION } from '../../../../../shared/constants/confirmations'; // POC: re-enable with snaps-rpc-methods yalc +import { USE_UNIVERSAL_MULTICHAIN_CONFIRMATION } from '../../../../../shared/constants/confirmations'; import { PreferencesControllerGetStateAction } from '../../preferences-controller'; import { KeyringType } from '../../../../../shared/constants/keyring'; import { AppStateControllerGetUnlockPromiseAction } from '../../app-state-controller-method-action-types'; @@ -48,7 +48,19 @@ import { } from '../../../../../shared/lib/assets-unify-state/remote-feature-flag'; import { getIsAssetsUnifiedStateIncludedInBuild } from '../../../../../shared/lib/environment'; -// POC: ConfirmTransactionParams type removed — re-enable with snaps-rpc-methods yalc +type ConfirmTransactionParams = { + accountId: string; + assetDecimals: number; + assetSymbol: string; + assetType: string; + chain: string; + chainNamespace: 'solana' | 'bip122' | 'tron'; + feeAssetType?: string; + feeRaw?: string; + from: string; + to: string; + value: string; +}; export type SnapPermissionSpecificationsActions = | AppStateControllerGetUnlockPromiseAction @@ -73,7 +85,8 @@ export type SnapPermissionSpecificationsActions = | SnapInterfaceControllerSetInterfaceDisplayedAction | TestOrigin | SnapControllerUpdateSnapStateAction - | ApprovalControllerAddRequestAction; + | ApprovalControllerAddRequestAction + | MultichainTransactionsControllerActions; /** * Get the permission specifications for Snaps. @@ -187,6 +200,68 @@ export function getSnapPermissionSpecifications( */ getClientCryptography: () => ({}), + showUniversalTransactionConfirmation: async ( + snapId: string, + params: ConfirmTransactionParams, + ) => { + if (!USE_UNIVERSAL_MULTICHAIN_CONFIRMATION) { + throw rpcErrors.methodNotSupported(); + } + + const approvalId = nanoid(); + messenger.call( + 'MultichainTransactionsController:addPendingTransaction', + { + approvalId, + chainNamespace: params.chainNamespace, + chain: params.chain, + accountId: params.accountId, + from: params.from, + to: params.to, + value: params.value, + assetType: params.assetType, + assetSymbol: params.assetSymbol, + assetDecimals: params.assetDecimals, + feeRaw: params.feeRaw, + feeAssetType: params.feeAssetType, + origin: snapId, + createdAt: Date.now(), + }, + ); + + const addAndShowApprovalRequest = async () => + messenger.call( + 'ApprovalController:addRequest', + { + id: approvalId, + origin: snapId, + type: ApprovalType.Transaction, + requestData: { + approvalId, + chainNamespace: params.chainNamespace, + txParams: { + from: params.from, + to: params.to, + value: params.value, + }, + }, + }, + true, + ); + + try { + await addAndShowApprovalRequest(); + return true; + } catch (_err) { + return false; + } finally { + messenger.call( + 'MultichainTransactionsController:removePendingTransaction', + approvalId, + ); + } + }, + getSnapKeyring: async () => { // TODO: Use `withKeyring` instead. const [snapKeyring] = messenger.call( diff --git a/package.json b/package.json index b2bdca901038..65198e9a7239 100644 --- a/package.json +++ b/package.json @@ -426,7 +426,7 @@ "@metamask/smart-transactions-controller": "^24.1.0", "@metamask/snaps-controllers": "^20.0.5", "@metamask/snaps-execution-environments": "^11.0.2", - "@metamask/snaps-rpc-methods": "^16.0.0", + "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods", "@metamask/snaps-sdk": "^11.1.1", "@metamask/snaps-utils": "^12.2.0", "@metamask/solana-wallet-snap": "^2.8.0", diff --git a/yarn.lock b/yarn.lock index 6165a99248a0..ca42b14cf3f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8277,6 +8277,23 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-rpc-methods@file:.yalc/@metamask/snaps-rpc-methods::locator=metamask-crx%40workspace%3A.": + version: 16.0.0 + resolution: "@metamask/snaps-rpc-methods@file:.yalc/@metamask/snaps-rpc-methods#.yalc/@metamask/snaps-rpc-methods::hash=97b20a&locator=metamask-crx%40workspace%3A." + dependencies: + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/permission-controller": "npm:^13.1.0" + "@metamask/rpc-errors": "npm:^7.0.3" + "@metamask/snaps-sdk": "npm:^11.1.1" + "@metamask/snaps-utils": "npm:^12.2.0" + "@metamask/superstruct": "npm:^3.2.1" + "@metamask/utils": "npm:^11.11.0" + "@noble/hashes": "npm:^1.7.1" + async-mutex: "npm:^0.5.0" + checksum: 10/f38566b0eab8bd1a0a266295556c1c9c4916e087aff9ad74c69c754896060d2c8a174c55040b75a775ec9bc04c1e510015bff6d7fb547c18bde922aea4008ed2 + languageName: node + linkType: hard + "@metamask/snaps-rpc-methods@npm:^16.0.0": version: 16.0.0 resolution: "@metamask/snaps-rpc-methods@npm:16.0.0" @@ -33772,7 +33789,7 @@ __metadata: "@metamask/snaps-controllers": "npm:^20.0.5" "@metamask/snaps-execution-environments": "npm:^11.0.2" "@metamask/snaps-registry": "npm:^4.0.0" - "@metamask/snaps-rpc-methods": "npm:^16.0.0" + "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods" "@metamask/snaps-sdk": "npm:^11.1.1" "@metamask/snaps-utils": "npm:^12.2.0" "@metamask/solana-wallet-snap": "npm:^2.8.0" From 4edb64eaa73844ea5448cee3fe49a2df8815afb1 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 20 May 2026 21:54:03 +0100 Subject: [PATCH 05/28] feat: enable snaps-controllers yalc link --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 65198e9a7239..638b7599ccd7 100644 --- a/package.json +++ b/package.json @@ -424,7 +424,7 @@ "@metamask/shield-controller": "^5.0.1", "@metamask/signature-controller": "^39.2.0", "@metamask/smart-transactions-controller": "^24.1.0", - "@metamask/snaps-controllers": "^20.0.5", + "@metamask/snaps-controllers": "file:.yalc/@metamask/snaps-controllers", "@metamask/snaps-execution-environments": "^11.0.2", "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods", "@metamask/snaps-sdk": "^11.1.1", diff --git a/yarn.lock b/yarn.lock index ca42b14cf3f8..6f14e6c01b21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33786,7 +33786,7 @@ __metadata: "@metamask/smart-transactions-controller": "npm:^24.1.0" "@metamask/snap-account-abstraction-keyring-site": "npm:^1.0.0" "@metamask/snap-simple-keyring-site": "npm:^2.1.1" - "@metamask/snaps-controllers": "npm:^20.0.5" + "@metamask/snaps-controllers": "file:.yalc/@metamask/snaps-controllers" "@metamask/snaps-execution-environments": "npm:^11.0.2" "@metamask/snaps-registry": "npm:^4.0.0" "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods" From df7b1a67ae90e65d0e19ba284316259e6dcf17ea Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Wed, 20 May 2026 22:03:45 +0100 Subject: [PATCH 06/28] feat: enable snaps-utils yalc link --- package.json | 2 +- yarn.lock | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 638b7599ccd7..abefa27ab77b 100644 --- a/package.json +++ b/package.json @@ -428,7 +428,7 @@ "@metamask/snaps-execution-environments": "^11.0.2", "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods", "@metamask/snaps-sdk": "^11.1.1", - "@metamask/snaps-utils": "^12.2.0", + "@metamask/snaps-utils": "file:.yalc/@metamask/snaps-utils", "@metamask/solana-wallet-snap": "^2.8.0", "@metamask/solana-wallet-standard": "^1.0.0", "@metamask/storage-service": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 6f14e6c01b21..e764064a60b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8325,6 +8325,37 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-utils@file:.yalc/@metamask/snaps-utils::locator=metamask-crx%40workspace%3A.": + version: 12.2.0 + resolution: "@metamask/snaps-utils@file:.yalc/@metamask/snaps-utils#.yalc/@metamask/snaps-utils::hash=86a5eb&locator=metamask-crx%40workspace%3A." + dependencies: + "@babel/core": "npm:^7.23.2" + "@babel/types": "npm:^7.23.0" + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/permission-controller": "npm:^13.1.0" + "@metamask/rpc-errors": "npm:^7.0.3" + "@metamask/slip44": "npm:^4.4.0" + "@metamask/snaps-registry": "npm:^4.0.0" + "@metamask/snaps-sdk": "npm:^11.1.1" + "@metamask/superstruct": "npm:^3.2.1" + "@metamask/utils": "npm:^11.11.0" + "@scure/base": "npm:^1.1.1" + chalk: "npm:^4.1.2" + cron-parser: "npm:^4.5.0" + fast-deep-equal: "npm:^3.1.3" + fast-json-stable-stringify: "npm:^2.1.0" + fast-xml-parser: "npm:^5.5.6" + luxon: "npm:^3.5.0" + marked: "npm:^12.0.1" + rfdc: "npm:^1.3.0" + semver: "npm:^7.5.4" + ses: "npm:^1.15.0" + validate-npm-package-name: "npm:^5.0.0" + checksum: 10/8eed4b34dfacf15ae72c27629c377c1e7051790e1f5623d3506d7deecd3ed81c3291b516e236839983800bb6587f5eccc6ad1603c121e965eddc3b56e0ab7ab3 + languageName: node + linkType: hard + "@metamask/snaps-utils@npm:^11.0.0": version: 11.7.1 resolution: "@metamask/snaps-utils@npm:11.7.1" @@ -33791,7 +33822,7 @@ __metadata: "@metamask/snaps-registry": "npm:^4.0.0" "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods" "@metamask/snaps-sdk": "npm:^11.1.1" - "@metamask/snaps-utils": "npm:^12.2.0" + "@metamask/snaps-utils": "file:.yalc/@metamask/snaps-utils" "@metamask/solana-wallet-snap": "npm:^2.8.0" "@metamask/solana-wallet-standard": "npm:^1.0.0" "@metamask/storage-service": "npm:^1.0.0" From 828c14406353de605b8cbbc74fe82873c404b1fe Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 21 May 2026 00:48:15 +0100 Subject: [PATCH 07/28] feat: enable snaps-sdk yalc link --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index abefa27ab77b..a9d448bf76d2 100644 --- a/package.json +++ b/package.json @@ -427,7 +427,7 @@ "@metamask/snaps-controllers": "file:.yalc/@metamask/snaps-controllers", "@metamask/snaps-execution-environments": "^11.0.2", "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods", - "@metamask/snaps-sdk": "^11.1.1", + "@metamask/snaps-sdk": "file:.yalc/@metamask/snaps-sdk", "@metamask/snaps-utils": "file:.yalc/@metamask/snaps-utils", "@metamask/solana-wallet-snap": "^2.8.0", "@metamask/solana-wallet-standard": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index e764064a60b8..39636050eace 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33821,7 +33821,7 @@ __metadata: "@metamask/snaps-execution-environments": "npm:^11.0.2" "@metamask/snaps-registry": "npm:^4.0.0" "@metamask/snaps-rpc-methods": "file:.yalc/@metamask/snaps-rpc-methods" - "@metamask/snaps-sdk": "npm:^11.1.1" + "@metamask/snaps-sdk": "file:.yalc/@metamask/snaps-sdk" "@metamask/snaps-utils": "file:.yalc/@metamask/snaps-utils" "@metamask/solana-wallet-snap": "npm:^2.8.0" "@metamask/solana-wallet-standard": "npm:^1.0.0" From fd7afdb3d6704e906547b742f46c61aa1e6cee16 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 21 May 2026 09:53:10 +0100 Subject: [PATCH 08/28] feat: enable local Solana snap dev and update keyring permissions for POC --- app/scripts/constants/snaps.ts | 5 -- .../snap-keyring/keyring-snaps-permissions.ts | 48 ++++++++++++------- shared/lib/accounts/solana-wallet-snap.ts | 10 +++- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/app/scripts/constants/snaps.ts b/app/scripts/constants/snaps.ts index 121817438688..1421b87cceef 100644 --- a/app/scripts/constants/snaps.ts +++ b/app/scripts/constants/snaps.ts @@ -53,11 +53,6 @@ export const PREINSTALLED_SNAPS_URLS = [ // @ts-expect-error TS1470: 'import.meta' is not allowed in CommonJS import.meta.url, ), - new URL( - '@metamask/solana-wallet-snap/dist/preinstalled-snap.json', - // @ts-expect-error TS1470: 'import.meta' is not allowed in CommonJS - import.meta.url, - ), new URL( '@metamask/tron-wallet-snap/dist/preinstalled-snap.json', // @ts-expect-error TS1470: 'import.meta' is not allowed in CommonJS diff --git a/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts b/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts index 71e35d839269..79c03e57af1a 100644 --- a/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts +++ b/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts @@ -3,22 +3,19 @@ import { SubjectMetadataController, SubjectType, } from '@metamask/permission-controller'; -import { isFlask } from '../../../../shared/lib/build-types'; /** * The origins of the Portfolio dapp. */ const PORTFOLIO_ORIGINS: string[] = [ - 'https://app.metamask.io', - // Add the following to the list only if the build is Flask - ...(isFlask() - ? [ - 'https://dev.app.metamask.io', - 'https://stage.app.metamask.io', - 'https://ramps-dev.app.metamask.io', - 'https://portfolio-builds.metafi-dev.codefi.network', - ] - : []), + 'http://localhost:3000', + 'https://portfolio.metamask.io', + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + 'https://dev.portfolio.metamask.io', + 'https://stage.portfolio.metamask.io', + 'https://ramps-dev.portfolio.metamask.io', + 'https://portfolio-builds.metafi-dev.codefi.network', + ///: END:ONLY_INCLUDE_IF ]; /** @@ -28,15 +25,18 @@ const METAMASK_ALLOWED_METHODS: string[] = [ KeyringRpcMethod.ListAccounts, KeyringRpcMethod.GetAccount, KeyringRpcMethod.CreateAccount, - KeyringRpcMethod.CreateAccounts, + KeyringRpcMethod.GetAccountBalances, KeyringRpcMethod.FilterAccountChains, + KeyringRpcMethod.UpdateAccount, KeyringRpcMethod.DeleteAccount, + KeyringRpcMethod.ExportAccount, KeyringRpcMethod.ListRequests, KeyringRpcMethod.GetRequest, KeyringRpcMethod.SubmitRequest, + KeyringRpcMethod.ApproveRequest, KeyringRpcMethod.RejectRequest, - KeyringRpcMethod.ResolveAccountAddress, - KeyringRpcMethod.SetSelectedAccounts, + KeyringRpcMethod.ListAccountTransactions, + KeyringRpcMethod.ListAccountAssets, ]; /** @@ -47,14 +47,18 @@ const WEBSITE_ALLOWED_METHODS: string[] = [ KeyringRpcMethod.ListAccounts, KeyringRpcMethod.GetAccount, KeyringRpcMethod.CreateAccount, + KeyringRpcMethod.GetAccountBalances, KeyringRpcMethod.FilterAccountChains, KeyringRpcMethod.UpdateAccount, KeyringRpcMethod.DeleteAccount, KeyringRpcMethod.ExportAccount, KeyringRpcMethod.ListRequests, KeyringRpcMethod.GetRequest, + KeyringRpcMethod.SubmitRequest, KeyringRpcMethod.ApproveRequest, KeyringRpcMethod.RejectRequest, + KeyringRpcMethod.ListAccountTransactions, + KeyringRpcMethod.ListAccountAssets, ]; /** @@ -63,8 +67,19 @@ const WEBSITE_ALLOWED_METHODS: string[] = [ const PORTFOLIO_ALLOWED_METHODS: string[] = [ KeyringRpcMethod.ListAccounts, KeyringRpcMethod.GetAccount, + KeyringRpcMethod.CreateAccount, KeyringRpcMethod.GetAccountBalances, + KeyringRpcMethod.FilterAccountChains, + KeyringRpcMethod.UpdateAccount, + KeyringRpcMethod.DeleteAccount, + KeyringRpcMethod.ExportAccount, + KeyringRpcMethod.ListRequests, + KeyringRpcMethod.GetRequest, KeyringRpcMethod.SubmitRequest, + KeyringRpcMethod.ApproveRequest, + KeyringRpcMethod.RejectRequest, + KeyringRpcMethod.ListAccountTransactions, + KeyringRpcMethod.ListAccountAssets, ]; /** @@ -72,8 +87,9 @@ const PORTFOLIO_ALLOWED_METHODS: string[] = [ */ const ALLOWED_PROTOCOLS: string[] = [ 'https:', - // Add the following to the list only if the build is Flask - ...(isFlask() ? ['http:'] : []), + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + 'http:', + ///: END:ONLY_INCLUDE_IF ]; /** diff --git a/shared/lib/accounts/solana-wallet-snap.ts b/shared/lib/accounts/solana-wallet-snap.ts index 579a9958e171..3feff60c2a03 100644 --- a/shared/lib/accounts/solana-wallet-snap.ts +++ b/shared/lib/accounts/solana-wallet-snap.ts @@ -1,5 +1,11 @@ import { SnapId } from '@metamask/snaps-sdk'; +// This dependency is still installed as part of the `package.json`, however +// the Snap is being pre-installed only for Flask build (for the moment). +import SolanaWalletSnap from '@metamask/solana-wallet-snap/dist/preinstalled-snap.json'; +// export const SOLANA_WALLET_SNAP_ID: SnapId = SolanaWalletSnap.snapId as SnapId; export const SOLANA_WALLET_SNAP_ID: SnapId = - 'npm:@metamask/solana-wallet-snap' as SnapId; -export const SOLANA_WALLET_NAME: string = 'Solana'; + 'local:http://localhost:8080' as SnapId; + +export const SOLANA_WALLET_NAME: string = + SolanaWalletSnap.manifest.proposedName; From 9bd70d65df1c7acf97cd1b09103d8f0b2290f758 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 21 May 2026 11:48:42 +0100 Subject: [PATCH 09/28] feat(confirmations): universal multichain confirmation via standard Confirm page Routes universalTransaction approvals through the existing page + dispatcher (parallel to EVM transactions) instead of a separate /confirm-multichain-transaction route. Adds UniversalTransactionInfo with heading, network, origin, from, to and fee rows backed by a new useUniversalTransactionData hook. --- .../permissions/snaps/specifications.ts | 7 +- shared/constants/confirmations.ts | 7 + ui/helpers/constants/routes.ts | 2 - .../components/confirm/info/info.tsx | 4 + .../rows/universal-transaction-fee-row.tsx | 20 +++ .../rows/universal-transaction-from-row.tsx | 16 ++ .../universal-transaction-heading-row.tsx | 50 ++++++ .../universal-transaction-network-row.tsx | 27 +++ .../rows/universal-transaction-origin-row.tsx | 16 ++ .../rows/universal-transaction-to-row.tsx | 16 ++ .../universal-transaction-info.tsx | 26 +++ .../confirm/universal-transaction/index.ts | 1 - .../universal-transaction-confirmation.tsx | 158 ------------------ .../useUniversalTransactionData.ts | 95 +++++++++++ .../hooks/useConfirmationNavigation.ts | 12 +- .../hooks/useCurrentConfirmation.ts | 5 + .../hooks/usePendingMultichainTransaction.ts | 9 - ui/pages/confirmations/selectors/confirm.ts | 2 + ui/pages/routes/routes.component.tsx | 6 - 19 files changed, 292 insertions(+), 187 deletions(-) create mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-from-row.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-origin-row.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-to-row.tsx create mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx delete mode 100644 ui/pages/confirmations/components/confirm/universal-transaction/index.ts delete mode 100644 ui/pages/confirmations/components/confirm/universal-transaction/universal-transaction-confirmation.tsx create mode 100644 ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts delete mode 100644 ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts diff --git a/app/scripts/controllers/permissions/snaps/specifications.ts b/app/scripts/controllers/permissions/snaps/specifications.ts index 4c7ced3ae2c7..31d84cb44b91 100644 --- a/app/scripts/controllers/permissions/snaps/specifications.ts +++ b/app/scripts/controllers/permissions/snaps/specifications.ts @@ -36,7 +36,10 @@ import { ExcludedSnapEndowments, ExcludedSnapPermissions, } from '../../../../../shared/constants/snaps/permissions'; -import { USE_UNIVERSAL_MULTICHAIN_CONFIRMATION } from '../../../../../shared/constants/confirmations'; +import { + UNIVERSAL_TRANSACTION_APPROVAL_TYPE, + USE_UNIVERSAL_MULTICHAIN_CONFIRMATION, +} from '../../../../../shared/constants/confirmations'; import { PreferencesControllerGetStateAction } from '../../preferences-controller'; import { KeyringType } from '../../../../../shared/constants/keyring'; import { AppStateControllerGetUnlockPromiseAction } from '../../app-state-controller-method-action-types'; @@ -235,7 +238,7 @@ export function getSnapPermissionSpecifications( { id: approvalId, origin: snapId, - type: ApprovalType.Transaction, + type: UNIVERSAL_TRANSACTION_APPROVAL_TYPE as ApprovalType, requestData: { approvalId, chainNamespace: params.chainNamespace, diff --git a/shared/constants/confirmations.ts b/shared/constants/confirmations.ts index 2c2c9c25d375..4c3c6a499cc7 100644 --- a/shared/constants/confirmations.ts +++ b/shared/constants/confirmations.ts @@ -4,3 +4,10 @@ * confirmation page instead of the legacy snap-templated dialog. */ export const USE_UNIVERSAL_MULTICHAIN_CONFIRMATION = true; + +/** + * Approval type used by `snap_confirmTransaction` flows. Cast to + * {@link ApprovalType} at call sites until the canonical enum in + * `@metamask/controller-utils` is updated. + */ +export const UNIVERSAL_TRANSACTION_APPROVAL_TYPE = 'universalTransaction'; diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts index 910f8f66b539..e989f61ee48f 100644 --- a/ui/helpers/constants/routes.ts +++ b/ui/helpers/constants/routes.ts @@ -128,8 +128,6 @@ export const NOTIFICATIONS_ROUTE = '/notifications'; export const NOTIFICATIONS_SETTINGS_ROUTE = '/settings/notifications'; export const CONNECTED_ROUTE = '/connected'; export const CONNECTED_ACCOUNTS_ROUTE = '/connected/accounts'; -export const CONFIRM_MULTICHAIN_TRANSACTION_PATH = - '/confirm-multichain-transaction/:id'; export const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'; export const CONFIRMATION_V_NEXT_ROUTE = '/confirmation'; export const SIGNATURE_REQUEST_PATH = '/signature-request'; diff --git a/ui/pages/confirmations/components/confirm/info/info.tsx b/ui/pages/confirmations/components/confirm/info/info.tsx index df953c8642ae..67f27f3e3c4d 100644 --- a/ui/pages/confirmations/components/confirm/info/info.tsx +++ b/ui/pages/confirmations/components/confirm/info/info.tsx @@ -1,6 +1,7 @@ import { TransactionType } from '@metamask/transaction-controller'; import { ApprovalType } from '@metamask/controller-utils'; import React, { useMemo } from 'react'; +import { UNIVERSAL_TRANSACTION_APPROVAL_TYPE } from '../../../../../../shared/constants/confirmations'; import { useEnabledAdvancedPermissions } from '../../../../../hooks/gator-permissions/useEnabledAdvancedPermissions'; import { useTrustSignalMetrics } from '../../../../trust-signals/hooks/useTrustSignalMetrics'; import { useConfirmContext } from '../../../context/confirm'; @@ -29,6 +30,7 @@ import TokenTransferInfo from './token-transfer/token-transfer'; import TypedSignV1Info from './typed-sign-v1/typed-sign-v1'; import TypedSignInfo from './typed-sign/typed-sign'; import TypedSignPermissionInfo from './typed-sign/typed-sign-permission'; +import UniversalTransactionInfo from './universal-transaction/universal-transaction-info'; // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention @@ -159,6 +161,8 @@ const Info = () => { [ApprovalType.AddEthereumChain]: () => AddEthereumChain, + [UNIVERSAL_TRANSACTION_APPROVAL_TYPE]: () => UniversalTransactionInfo, + [TransactionType.musdClaim]: () => MusdClaimInfo, [TransactionType.musdConversion]: () => MusdConversionInfo, [TransactionType.perpsDeposit]: () => PerpsDepositInfo, diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx new file mode 100644 index 000000000000..75b8748b3668 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; + +export function UniversalTransactionFeeRow() { + const t = useI18nContext(); + const data = useUniversalTransactionDataOptional(); + + if (!data || !data.formattedFee) { + return null; + } + + return ( + + {`${data.formattedFee} ${data.assetSymbol}`} + + ); +} diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-from-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-from-row.tsx new file mode 100644 index 000000000000..d0ade4f6a583 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-from-row.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; + +export function UniversalTransactionFromRow() { + const t = useI18nContext(); + const data = useUniversalTransactionDataOptional(); + + if (!data) { + return null; + } + + return {data.from}; +} diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx new file mode 100644 index 000000000000..183d35bd5b68 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx @@ -0,0 +1,50 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import React from 'react'; + +import { + AvatarToken, + AvatarTokenSize, + Box, + Text, +} from '../../../../../../../components/component-library'; +import { + TextAlign, + TextColor, + TextVariant, +} from '../../../../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; +import SendHeadingLayout from '../../shared/send-heading-layout/send-heading-layout'; + +export function UniversalTransactionHeadingRow() { + const t = useI18nContext(); + const data = useUniversalTransactionDataOptional(); + + if (!data) { + return null; + } + + return ( + <> + + {t('send')} + + + } + > + + + {`${data.formattedAmount} ${data.assetSymbol}`} + + + + + ); +} diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx new file mode 100644 index 000000000000..43ff9f5210d9 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; + +const NETWORK_LABEL_BY_NAMESPACE: Record = { + solana: 'Solana', +}; + +export function UniversalTransactionNetworkRow() { + const t = useI18nContext(); + const data = useUniversalTransactionDataOptional(); + + if (!data) { + return null; + } + + const networkLabel = + NETWORK_LABEL_BY_NAMESPACE[data.chainNamespace] ?? data.chain; + + return ( + + {networkLabel} + + ); +} diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-origin-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-origin-row.tsx new file mode 100644 index 000000000000..bddfee0aea7f --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-origin-row.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; + +export function UniversalTransactionOriginRow() { + const t = useI18nContext(); + const data = useUniversalTransactionDataOptional(); + + if (!data) { + return null; + } + + return {data.origin}; +} diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-to-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-to-row.tsx new file mode 100644 index 000000000000..8894b97a542e --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-to-row.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; + +export function UniversalTransactionToRow() { + const t = useI18nContext(); + const data = useUniversalTransactionDataOptional(); + + if (!data) { + return null; + } + + return {data.to}; +} diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx new file mode 100644 index 000000000000..9fa78a7be462 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import { ConfirmInfoSection } from '../../../../../../components/app/confirm/info/row/section'; +import { UniversalTransactionFeeRow } from './rows/universal-transaction-fee-row'; +import { UniversalTransactionFromRow } from './rows/universal-transaction-from-row'; +import { UniversalTransactionHeadingRow } from './rows/universal-transaction-heading-row'; +import { UniversalTransactionNetworkRow } from './rows/universal-transaction-network-row'; +import { UniversalTransactionOriginRow } from './rows/universal-transaction-origin-row'; +import { UniversalTransactionToRow } from './rows/universal-transaction-to-row'; + +function UniversalTransactionInfo() { + return ( + <> + + + + + + + + + + ); +} + +export default UniversalTransactionInfo; diff --git a/ui/pages/confirmations/components/confirm/universal-transaction/index.ts b/ui/pages/confirmations/components/confirm/universal-transaction/index.ts deleted file mode 100644 index b1459049dcaa..000000000000 --- a/ui/pages/confirmations/components/confirm/universal-transaction/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { UniversalTransactionConfirmation } from './universal-transaction-confirmation'; diff --git a/ui/pages/confirmations/components/confirm/universal-transaction/universal-transaction-confirmation.tsx b/ui/pages/confirmations/components/confirm/universal-transaction/universal-transaction-confirmation.tsx deleted file mode 100644 index c1589b1c661c..000000000000 --- a/ui/pages/confirmations/components/confirm/universal-transaction/universal-transaction-confirmation.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { providerErrors } from '@metamask/rpc-errors'; - -import { calcTokenAmount } from '../../../../../../shared/lib/transactions-controller-utils'; -import { ConfirmInfoRow } from '../../../../../components/app/confirm/info/row/row'; -import { ConfirmInfoSection } from '../../../../../components/app/confirm/info/row/section'; -import { - AvatarToken, - AvatarTokenSize, - Box, - Button, - ButtonSize, - ButtonVariant, - Text, -} from '../../../../../components/component-library'; -import { - Footer as PageFooter, - Page, -} from '../../../../../components/multichain/pages/page'; -import PulseLoader from '../../../../../components/ui/pulse-loader'; -import { getIntlLocale } from '../../../../../ducks/locale/locale'; -import { - AlignItems, - BlockSize, - Display, - FlexDirection, - JustifyContent, - TextAlign, - TextColor, - TextVariant, -} from '../../../../../helpers/constants/design-system'; -import { useI18nContext } from '../../../../../hooks/useI18nContext'; -import { - rejectPendingApproval, - resolvePendingApproval, -} from '../../../../../store/actions'; -import { usePendingMultichainTransaction } from '../../../hooks/usePendingMultichainTransaction'; -import { formatAmount } from '../../simulation-details/formatAmount'; -import SendHeadingLayout from '../info/shared/send-heading-layout/send-heading-layout'; - -export function UniversalTransactionConfirmation() { - const { id } = useParams<{ id: string }>(); - const dispatch = useDispatch(); - const pendingTx = usePendingMultichainTransaction(id); - const t = useI18nContext(); - const locale = useSelector(getIntlLocale); - - if (!pendingTx) { - return ( - - - - ); - } - - const handleConfirm = () => { - if (id) { - dispatch(resolvePendingApproval(id, {})); - } - }; - - const handleReject = () => { - if (id) { - dispatch( - rejectPendingApproval( - id, - providerErrors.userRejectedRequest().serialize(), - ), - ); - } - }; - - const nativeAssetTransferValue = calcTokenAmount( - pendingTx.value, - pendingTx.assetDecimals, - ); - const roundedTransferValue = formatAmount(locale, nativeAssetTransferValue); - - const NetworkImage = ( - - ); - - const NativeAssetAmount = ( - - - {`${roundedTransferValue} ${pendingTx.assetSymbol}`} - - - ); - - return ( - - - - {t('send')} - - - - {NativeAssetAmount} - - - - - {pendingTx.chainNamespace === 'solana' ? 'Solana' : pendingTx.chain} - - {pendingTx.origin} - {pendingTx.from} - {pendingTx.to} - - {pendingTx.feeRaw - ? `${formatAmount( - locale, - calcTokenAmount(pendingTx.feeRaw, pendingTx.assetDecimals), - )} ${pendingTx.assetSymbol}` - : ''} - - - - - - - - - - - - ); -} diff --git a/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts b/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts new file mode 100644 index 000000000000..2a3a525422a3 --- /dev/null +++ b/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts @@ -0,0 +1,95 @@ +import { useSelector } from 'react-redux'; + +import { calcTokenAmount } from '../../../../../shared/lib/transactions-controller-utils'; +import { getIntlLocale } from '../../../../ducks/locale/locale'; +import { formatAmount } from '../../components/simulation-details/formatAmount'; +import { useConfirmationId } from '../useConfirmationId'; + +type PendingUniversalTransaction = { + approvalId: string; + chainNamespace: string; + chain: string; + accountId: string; + from: string; + to: string; + value: string; + assetType: string; + assetSymbol: string; + assetDecimals: number; + feeRaw?: string; + feeAssetType?: string; + origin: string; + createdAt: number; +}; + +export type UniversalTransactionData = { + approvalId: string; + chainNamespace: string; + chain: string; + accountId: string; + from: string; + to: string; + value: string; + assetType: string; + assetSymbol: string; + assetDecimals: number; + feeRaw?: string; + feeAssetType?: string; + origin: string; + formattedAmount: string; + formattedFee?: string; +}; + +function selectPendingUniversalTransactionById( + state: { metamask: { pendingTransactions?: Record } }, + id: string | undefined, +): PendingUniversalTransaction | undefined { + return id ? state.metamask.pendingTransactions?.[id] : undefined; +} + +export function useUniversalTransactionDataOptional(): + | UniversalTransactionData + | undefined { + const confirmationId = useConfirmationId(); + const locale = useSelector(getIntlLocale); + const pendingTx = useSelector((state) => + selectPendingUniversalTransactionById( + state as Parameters[0], + confirmationId, + ), + ); + + if (!pendingTx) { + return undefined; + } + + const formattedAmount = formatAmount( + locale, + calcTokenAmount(pendingTx.value, pendingTx.assetDecimals), + ); + + const formattedFee = pendingTx.feeRaw + ? formatAmount( + locale, + calcTokenAmount(pendingTx.feeRaw, pendingTx.assetDecimals), + ) + : undefined; + + return { + approvalId: pendingTx.approvalId, + chainNamespace: pendingTx.chainNamespace, + chain: pendingTx.chain, + accountId: pendingTx.accountId, + from: pendingTx.from, + to: pendingTx.to, + value: pendingTx.value, + assetType: pendingTx.assetType, + assetSymbol: pendingTx.assetSymbol, + assetDecimals: pendingTx.assetDecimals, + feeRaw: pendingTx.feeRaw, + feeAssetType: pendingTx.feeAssetType, + origin: pendingTx.origin, + formattedAmount, + formattedFee, + }; +} diff --git a/ui/pages/confirmations/hooks/useConfirmationNavigation.ts b/ui/pages/confirmations/hooks/useConfirmationNavigation.ts index bbb947b1e9d8..b3e6a86d1a8a 100644 --- a/ui/pages/confirmations/hooks/useConfirmationNavigation.ts +++ b/ui/pages/confirmations/hooks/useConfirmationNavigation.ts @@ -10,7 +10,6 @@ import { TEMPLATED_CONFIRMATION_APPROVAL_TYPES } from '../confirmation/templates import { CONFIRM_ADD_SUGGESTED_NFT_ROUTE, CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, - CONFIRM_MULTICHAIN_TRANSACTION_PATH, CONFIRM_TRANSACTION_ROUTE, CONFIRMATION_V_NEXT_ROUTE, CONNECT_ROUTE, @@ -23,7 +22,7 @@ import { getApprovalFlows, selectPendingApprovalsForNavigation, } from '../../../selectors'; -import { USE_UNIVERSAL_MULTICHAIN_CONFIRMATION } from '../../../../shared/constants/confirmations'; +import { UNIVERSAL_TRANSACTION_APPROVAL_TYPE } from '../../../../shared/constants/confirmations'; import { sanitizeRedirectUrl } from '../../../../shared/lib/safe-redirect'; export enum ConfirmationLoader { @@ -169,14 +168,9 @@ export function getConfirmationRoute( } if ( - USE_UNIVERSAL_MULTICHAIN_CONFIRMATION && - type === ApprovalType.Transaction && - nextConfirmation.requestData?.chainNamespace + (type as string) === UNIVERSAL_TRANSACTION_APPROVAL_TYPE || + type === ApprovalType.Transaction ) { - return CONFIRM_MULTICHAIN_TRANSACTION_PATH.replace(':id', confirmationId); - } - - if (type === ApprovalType.Transaction) { let url = `${CONFIRM_TRANSACTION_ROUTE}/${confirmationId}`; if (queryString.length) { url = `${url}${queryString}`; diff --git a/ui/pages/confirmations/hooks/useCurrentConfirmation.ts b/ui/pages/confirmations/hooks/useCurrentConfirmation.ts index 18f9ecc6c5b6..645893b32548 100644 --- a/ui/pages/confirmations/hooks/useCurrentConfirmation.ts +++ b/ui/pages/confirmations/hooks/useCurrentConfirmation.ts @@ -14,6 +14,7 @@ import { shouldUseRedesignForSignatures, shouldUseRedesignForTransactions, } from '../../../../shared/lib/confirmation.utils'; +import { UNIVERSAL_TRANSACTION_APPROVAL_TYPE } from '../../../../shared/constants/confirmations'; /** * Determine the current confirmation based on the pending approvals and controller state. @@ -67,6 +68,10 @@ const useCurrentConfirmation = (providedConfirmationId?: string) => { return { currentConfirmation: pendingApproval }; } + if (pendingApproval?.type === UNIVERSAL_TRANSACTION_APPROVAL_TYPE) { + return { currentConfirmation: pendingApproval }; + } + if (!shouldUseRedesign) { return { currentConfirmation: undefined }; } diff --git a/ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts b/ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts deleted file mode 100644 index 0225bfe95371..000000000000 --- a/ui/pages/confirmations/hooks/usePendingMultichainTransaction.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useSelector } from 'react-redux'; - -export function usePendingMultichainTransaction( - approvalId: string | undefined, -) { - return useSelector((state: any) => - approvalId ? state.metamask.pendingTransactions?.[approvalId] : undefined, - ); -} diff --git a/ui/pages/confirmations/selectors/confirm.ts b/ui/pages/confirmations/selectors/confirm.ts index e456d807a285..c1e17dc02c65 100644 --- a/ui/pages/confirmations/selectors/confirm.ts +++ b/ui/pages/confirmations/selectors/confirm.ts @@ -5,12 +5,14 @@ import { QuoteResponse } from '@metamask/bridge-controller'; import { createSelector } from 'reselect'; import { getPendingApprovals } from '../../../selectors/approvals'; +import { UNIVERSAL_TRANSACTION_APPROVAL_TYPE } from '../../../../shared/constants/confirmations'; import { ConfirmMetamaskState } from '../types/confirm'; const ConfirmationApprovalTypes = [ ApprovalType.PersonalSign, ApprovalType.EthSignTypedData, ApprovalType.Transaction, + UNIVERSAL_TRANSACTION_APPROVAL_TYPE as ApprovalType, ]; export const pendingConfirmationsSortedSelector = createSelector( getPendingApprovals, diff --git a/ui/pages/routes/routes.component.tsx b/ui/pages/routes/routes.component.tsx index f19a4419f3a1..a2b4e73805ee 100644 --- a/ui/pages/routes/routes.component.tsx +++ b/ui/pages/routes/routes.component.tsx @@ -24,7 +24,6 @@ import { ASSET_ROUTE, CONFIRM_ADD_SUGGESTED_NFT_ROUTE, CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, - CONFIRM_MULTICHAIN_TRANSACTION_PATH, CONFIRM_TRANSACTION_ROUTE, CONNECT_ROUTE, DEFAULT_ROUTE, @@ -119,7 +118,6 @@ import NetworkConfirmationPopover from '../../components/multichain/network-list import { ToastMaster } from '../../components/app/toast-master/toast-master'; import { mmLazy } from '../../helpers/utils/mm-lazy'; import CrossChainSwapTxDetails from '../bridge/transaction-details/transaction-details'; -import { UniversalTransactionConfirmation } from '../../pages/confirmations/components/confirm/universal-transaction'; import { MultichainAccountAddressListPage } from '../multichain-accounts/multichain-account-address-list-page'; import { MultichainAccountPrivateKeyListPage } from '../multichain-accounts/multichain-account-private-key-list-page'; import MultichainAccountIntroModalContainer from '../../components/app/modals/multichain-accounts/intro-modal'; @@ -361,10 +359,6 @@ export const routeConfig = [ path: `${CONFIRM_TRANSACTION_ROUTE}/:id?/*`, element: , }, - { - path: CONFIRM_MULTICHAIN_TRANSACTION_PATH, - element: , - }, { path: CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, element: , From a991b68f29ff3bf8eb1b668b7de68e331ef330ba Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 21 May 2026 12:06:52 +0100 Subject: [PATCH 10/28] feat(confirmations): redesign universal multichain rows to match EVM --- .../rows/truncate-universal-address.ts | 24 +++++++ .../rows/universal-transaction-fee-row.tsx | 54 +++++++++++++++- .../rows/universal-transaction-from-row.tsx | 53 +++++++++++++++- .../universal-transaction-heading-row.tsx | 13 +++- .../universal-transaction-network-row.tsx | 38 ++++++++++- .../rows/universal-transaction-origin-row.tsx | 16 ----- .../rows/universal-transaction-to-row.tsx | 53 +++++++++++++++- .../universal-transaction-info.tsx | 14 ++++- .../useUniversalTransactionData.ts | 63 +++++++++++++++++-- 9 files changed, 296 insertions(+), 32 deletions(-) create mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/rows/truncate-universal-address.ts delete mode 100644 ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-origin-row.tsx diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/truncate-universal-address.ts b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/truncate-universal-address.ts new file mode 100644 index 000000000000..23ab34e9c127 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/truncate-universal-address.ts @@ -0,0 +1,24 @@ +const ELLIPSIS = '\u2026'; +const PREFIX_LENGTH = 6; +const SUFFIX_LENGTH = 4; + +/** + * Middle-truncate a non-EVM (e.g. Solana base58) address for display. + * + * Mirrors the visual style of the EVM AccountFlowRow truncation, without + * applying any EVM-specific normalization (no checksumming, no lowercasing). + * + * @param address - Raw address string. + * @returns Truncated address, e.g. `C9qwTy...44v`. + */ +export function truncateUniversalAddress(address: string): string { + if (!address) { + return ''; + } + + if (address.length <= PREFIX_LENGTH + SUFFIX_LENGTH + 1) { + return address; + } + + return `${address.slice(0, PREFIX_LENGTH)}${ELLIPSIS}${address.slice(-SUFFIX_LENGTH)}`; +} diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx index 75b8748b3668..ebc8679e3f84 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx @@ -1,20 +1,68 @@ import React from 'react'; +import { SOLANA_TOKEN_IMAGE_URL } from '../../../../../../../../shared/constants/multichain/networks'; import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { + AvatarToken, + AvatarTokenSize, + Box, + Text, +} from '../../../../../../../components/component-library'; +import { + AlignItems, + Display, + FlexDirection, + JustifyContent, + TextAlign, + TextColor, +} from '../../../../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; +const FEE_ASSET_IMAGE_BY_SYMBOL: Record = { + SOL: SOLANA_TOKEN_IMAGE_URL, +}; + export function UniversalTransactionFeeRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); - if (!data || !data.formattedFee) { + if (!data || !data.formattedFee || !data.feeAssetSymbol) { return null; } + const feeImageUrl = FEE_ASSET_IMAGE_BY_SYMBOL[data.feeAssetSymbol]; + return ( - - {`${data.formattedFee} ${data.assetSymbol}`} + + + {data.formattedFeeFiat && ( + {data.formattedFeeFiat} + )} + + + + {`${data.formattedFee} ${data.feeAssetSymbol}`} + + + ); } diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-from-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-from-row.tsx index d0ade4f6a583..d1c71ecaff3f 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-from-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-from-row.tsx @@ -1,16 +1,67 @@ +import { + AvatarAccountSize, + Box, + BoxAlignItems, + BoxFlexDirection, + FontWeight, + Text, + TextColor, + TextVariant, +} from '@metamask/design-system-react'; import React from 'react'; +import { useSelector } from 'react-redux'; import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { PreferredAvatar } from '../../../../../../../components/app/preferred-avatar'; +import { FlexDirection } from '../../../../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { selectAccountGroupNameByInternalAccount } from '../../../../../selectors/accounts'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; +import { truncateUniversalAddress } from './truncate-universal-address'; export function UniversalTransactionFromRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); + const accountGroupName = useSelector((state) => + selectAccountGroupNameByInternalAccount( + state as Parameters[0], + data?.from, + ), + ); if (!data) { return null; } - return {data.from}; + const display = accountGroupName ?? truncateUniversalAddress(data.from); + + return ( + + + + + {display} + + + + + + ); } diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx index 183d35bd5b68..023c7240df3a 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import React from 'react'; +import { SOLANA_TOKEN_IMAGE_URL } from '../../../../../../../../shared/constants/multichain/networks'; import { AvatarToken, AvatarTokenSize, @@ -16,6 +17,10 @@ import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; import SendHeadingLayout from '../../shared/send-heading-layout/send-heading-layout'; +const ASSET_IMAGE_BY_SYMBOL: Record = { + SOL: SOLANA_TOKEN_IMAGE_URL, +}; + export function UniversalTransactionHeadingRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); @@ -24,6 +29,8 @@ export function UniversalTransactionHeadingRow() { return null; } + const assetImageUrl = ASSET_IMAGE_BY_SYMBOL[data.assetSymbol]; + return ( <> + } > diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx index 43ff9f5210d9..b699c8a41dcb 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx @@ -1,6 +1,21 @@ import React from 'react'; +import { SOLANA_TOKEN_IMAGE_URL } from '../../../../../../../../shared/constants/multichain/networks'; import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { + AvatarNetwork, + AvatarNetworkSize, + Box, + Text, +} from '../../../../../../../components/component-library'; +import { + AlignItems, + BlockSize, + Display, + FlexWrap, + TextColor, + TextVariant, +} from '../../../../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; @@ -8,6 +23,10 @@ const NETWORK_LABEL_BY_NAMESPACE: Record = { solana: 'Solana', }; +const NETWORK_IMAGE_BY_NAMESPACE: Record = { + solana: SOLANA_TOKEN_IMAGE_URL, +}; + export function UniversalTransactionNetworkRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); @@ -18,10 +37,27 @@ export function UniversalTransactionNetworkRow() { const networkLabel = NETWORK_LABEL_BY_NAMESPACE[data.chainNamespace] ?? data.chain; + const networkImageUrl = NETWORK_IMAGE_BY_NAMESPACE[data.chainNamespace] ?? ''; return ( - {networkLabel} + + + + {networkLabel} + + ); } diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-origin-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-origin-row.tsx deleted file mode 100644 index bddfee0aea7f..000000000000 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-origin-row.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; -import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; -import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; - -export function UniversalTransactionOriginRow() { - const t = useI18nContext(); - const data = useUniversalTransactionDataOptional(); - - if (!data) { - return null; - } - - return {data.origin}; -} diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-to-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-to-row.tsx index 8894b97a542e..c4e5d95e1cb4 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-to-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-to-row.tsx @@ -1,16 +1,67 @@ +import { + AvatarAccountSize, + Box, + BoxAlignItems, + BoxFlexDirection, + FontWeight, + Text, + TextColor, + TextVariant, +} from '@metamask/design-system-react'; import React from 'react'; +import { useSelector } from 'react-redux'; import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { PreferredAvatar } from '../../../../../../../components/app/preferred-avatar'; +import { FlexDirection } from '../../../../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { selectAccountGroupNameByInternalAccount } from '../../../../../selectors/accounts'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; +import { truncateUniversalAddress } from './truncate-universal-address'; export function UniversalTransactionToRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); + const accountGroupName = useSelector((state) => + selectAccountGroupNameByInternalAccount( + state as Parameters[0], + data?.to, + ), + ); if (!data) { return null; } - return {data.to}; + const display = accountGroupName ?? truncateUniversalAddress(data.to); + + return ( + + + + + {display} + + + + + + ); } diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx index 9fa78a7be462..52c1303eafe5 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx @@ -1,3 +1,4 @@ +import { Box } from '@metamask/design-system-react'; import React from 'react'; import { ConfirmInfoSection } from '../../../../../../components/app/confirm/info/row/section'; @@ -5,7 +6,6 @@ import { UniversalTransactionFeeRow } from './rows/universal-transaction-fee-row import { UniversalTransactionFromRow } from './rows/universal-transaction-from-row'; import { UniversalTransactionHeadingRow } from './rows/universal-transaction-heading-row'; import { UniversalTransactionNetworkRow } from './rows/universal-transaction-network-row'; -import { UniversalTransactionOriginRow } from './rows/universal-transaction-origin-row'; import { UniversalTransactionToRow } from './rows/universal-transaction-to-row'; function UniversalTransactionInfo() { @@ -13,10 +13,18 @@ function UniversalTransactionInfo() { <> - - + + + + diff --git a/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts b/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts index 2a3a525422a3..313dd1ccbfd3 100644 --- a/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts +++ b/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts @@ -1,10 +1,22 @@ +import { BigNumber } from 'bignumber.js'; import { useSelector } from 'react-redux'; +import { MultichainNativeAssets } from '../../../../../shared/constants/multichain/assets'; import { calcTokenAmount } from '../../../../../shared/lib/transactions-controller-utils'; import { getIntlLocale } from '../../../../ducks/locale/locale'; +import { useFiatFormatter } from '../../../../hooks/useFiatFormatter'; +import { getAssetsRates } from '../../../../selectors/assets'; import { formatAmount } from '../../components/simulation-details/formatAmount'; import { useConfirmationId } from '../useConfirmationId'; +const FEE_DECIMALS_BY_NAMESPACE: Record = { + solana: 9, +}; + +const NATIVE_ASSET_ID_BY_NAMESPACE: Record = { + solana: MultichainNativeAssets.SOLANA, +}; + type PendingUniversalTransaction = { approvalId: string; chainNamespace: string; @@ -35,9 +47,12 @@ export type UniversalTransactionData = { assetDecimals: number; feeRaw?: string; feeAssetType?: string; + feeAssetSymbol?: string; + feeAssetDecimals?: number; origin: string; formattedAmount: string; formattedFee?: string; + formattedFeeFiat?: string; }; function selectPendingUniversalTransactionById( @@ -47,11 +62,17 @@ function selectPendingUniversalTransactionById( return id ? state.metamask.pendingTransactions?.[id] : undefined; } +const FEE_SYMBOL_BY_NAMESPACE: Record = { + solana: 'SOL', +}; + export function useUniversalTransactionDataOptional(): | UniversalTransactionData | undefined { const confirmationId = useConfirmationId(); const locale = useSelector(getIntlLocale); + const assetRates = useSelector(getAssetsRates); + const fiatFormatter = useFiatFormatter(); const pendingTx = useSelector((state) => selectPendingUniversalTransactionById( state as Parameters[0], @@ -63,17 +84,44 @@ export function useUniversalTransactionDataOptional(): return undefined; } + const feeAssetDecimals = + FEE_DECIMALS_BY_NAMESPACE[pendingTx.chainNamespace]; + const feeAssetSymbol = FEE_SYMBOL_BY_NAMESPACE[pendingTx.chainNamespace]; + const nativeAssetId = NATIVE_ASSET_ID_BY_NAMESPACE[pendingTx.chainNamespace]; + const formattedAmount = formatAmount( locale, calcTokenAmount(pendingTx.value, pendingTx.assetDecimals), ); - const formattedFee = pendingTx.feeRaw - ? formatAmount( - locale, - calcTokenAmount(pendingTx.feeRaw, pendingTx.assetDecimals), - ) - : undefined; + const feeAmount = + pendingTx.feeRaw && feeAssetDecimals !== undefined + ? calcTokenAmount(pendingTx.feeRaw, feeAssetDecimals) + : undefined; + + const formattedFee = feeAmount ? formatAmount(locale, feeAmount) : undefined; + + const conversionRate = + nativeAssetId && assetRates?.[nativeAssetId]?.rate + ? assetRates[nativeAssetId].rate + : undefined; + + const fiatValue = + feeAmount && conversionRate + ? new BigNumber(conversionRate).times(feeAmount).toNumber() + : undefined; + + let formattedFeeFiat: string | undefined; + if (fiatValue !== undefined) { + const fiatBn = new BigNumber(fiatValue); + if (fiatBn.eq(0)) { + formattedFeeFiat = undefined; + } else if (fiatBn.lt(new BigNumber(0.01))) { + formattedFeeFiat = `< ${fiatFormatter(0.01)}`; + } else { + formattedFeeFiat = fiatFormatter(fiatBn.toNumber()); + } + } return { approvalId: pendingTx.approvalId, @@ -88,8 +136,11 @@ export function useUniversalTransactionDataOptional(): assetDecimals: pendingTx.assetDecimals, feeRaw: pendingTx.feeRaw, feeAssetType: pendingTx.feeAssetType, + feeAssetSymbol, + feeAssetDecimals, origin: pendingTx.origin, formattedAmount, formattedFee, + formattedFeeFiat, }; } From 7bdf4a9554547ccac1ec4d3b6ec1edadcd40ac83 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 21 May 2026 12:08:39 +0100 Subject: [PATCH 11/28] feat(confirmations): remove redundant Send header from universal confirmation --- .../universal-transaction-heading-row.tsx | 43 +++++++------------ 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx index 023c7240df3a..0836ad7791f5 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx @@ -9,11 +9,9 @@ import { Text, } from '../../../../../../../components/component-library'; import { - TextAlign, TextColor, TextVariant, } from '../../../../../../../helpers/constants/design-system'; -import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; import SendHeadingLayout from '../../shared/send-heading-layout/send-heading-layout'; @@ -22,7 +20,6 @@ const ASSET_IMAGE_BY_SYMBOL: Record = { }; export function UniversalTransactionHeadingRow() { - const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); if (!data) { @@ -32,30 +29,20 @@ export function UniversalTransactionHeadingRow() { const assetImageUrl = ASSET_IMAGE_BY_SYMBOL[data.assetSymbol]; return ( - <> - - {t('send')} - - - } - > - - - {`${data.formattedAmount} ${data.assetSymbol}`} - - - - + + } + > + + + {`${data.formattedAmount} ${data.assetSymbol}`} + + + ); } From 55600c6ffb29751beb9584075f038b04336ba2ac Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Thu, 21 May 2026 12:34:58 +0100 Subject: [PATCH 12/28] feat(universal-tx): advanced details toggle, fee row polish, and Send loader - Route universalTransaction approvals through WalletInitiatedHeader so AdvancedDetailsButton renders; back button calls onCancel - Fee row: render ` [icon] SOL` right-aligned. Collapsed shows fiat inline; advanced toggle shows native main line with fiat sub-line - Move Network fee into its own ConfirmInfoSection to match EVM grouping - Show ConfirmLoader inside UniversalTransactionInfo while pending tx data is propagating - Non-EVM Send submit now navigates to /confirm-transaction?loader=Send so the EVM-style InfoSkeleton renders while the snap creates the approval (instead of the send-page LOADER screen) --- .../components/confirm/header/header.tsx | 10 ++- .../header/wallet-initiated-header.tsx | 19 +++++- .../rows/universal-transaction-fee-row.tsx | 67 ++++++++++++------- .../universal-transaction-info.tsx | 10 +++ .../hooks/send/useSendActions.ts | 6 +- 5 files changed, 79 insertions(+), 33 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/header/header.tsx b/ui/pages/confirmations/components/confirm/header/header.tsx index b48627a64381..5e669486eb1b 100644 --- a/ui/pages/confirmations/components/confirm/header/header.tsx +++ b/ui/pages/confirmations/components/confirm/header/header.tsx @@ -4,6 +4,7 @@ import { } from '@metamask/transaction-controller'; import React from 'react'; import { ORIGIN_METAMASK } from '../../../../../../shared/constants/app'; +import { UNIVERSAL_TRANSACTION_APPROVAL_TYPE } from '../../../../../../shared/constants/confirmations'; import { Box, Text } from '../../../../../components/component-library'; import { PreferredAvatar } from '../../../../../components/app/preferred-avatar'; import { @@ -90,10 +91,15 @@ const Header = () => { // back button if it's a wallet initiated confirmation. The default header is // the original header for the redesigns and includes the sender and recipient // addresses as well. + const isUniversalTransaction = + (currentConfirmation?.type as string | undefined) === + UNIVERSAL_TRANSACTION_APPROVAL_TYPE; const isConfirmationWithNewHeader = - currentConfirmation?.type && - CONFIRMATIONS_WITH_ALT_HEADER.includes(currentConfirmation.type); + isUniversalTransaction || + (currentConfirmation?.type && + CONFIRMATIONS_WITH_ALT_HEADER.includes(currentConfirmation.type)); const isWalletInitiated = + isUniversalTransaction || (currentConfirmation as TransactionMeta)?.origin === ORIGIN_METAMASK; const isSimpleHeader = diff --git a/ui/pages/confirmations/components/confirm/header/wallet-initiated-header.tsx b/ui/pages/confirmations/components/confirm/header/wallet-initiated-header.tsx index 57c0fac58ed4..5deb8d22340d 100644 --- a/ui/pages/confirmations/components/confirm/header/wallet-initiated-header.tsx +++ b/ui/pages/confirmations/components/confirm/header/wallet-initiated-header.tsx @@ -5,6 +5,7 @@ import { import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { MetaMetricsEventLocation } from '../../../../../../shared/constants/metametrics'; +import { UNIVERSAL_TRANSACTION_APPROVAL_TYPE } from '../../../../../../shared/constants/confirmations'; import { Box, ButtonIcon, @@ -35,11 +36,23 @@ export const WalletInitiatedHeader = () => { const { currentConfirmation } = useConfirmContext(); const navigate = useNavigate(); + const isUniversalTransaction = + currentConfirmation?.type === UNIVERSAL_TRANSACTION_APPROVAL_TYPE; + const isSendTransaction = - currentConfirmation?.type && - SEND_TRANSACTION_TYPES.includes(currentConfirmation.type); + isUniversalTransaction || + (currentConfirmation?.type && + SEND_TRANSACTION_TYPES.includes(currentConfirmation.type)); const handleBackButtonClick = useCallback(() => { + if (isUniversalTransaction) { + onCancel({ + location: MetaMetricsEventLocation.Confirmation, + navigateBackForSend: true, + }); + return; + } + if ( currentConfirmation.type === TransactionType.shieldSubscriptionApprove ) { @@ -74,7 +87,7 @@ export const WalletInitiatedHeader = () => { navigateBackForSend: true, }); } - }, [currentConfirmation, navigate, onCancel]); + }, [currentConfirmation, isUniversalTransaction, navigate, onCancel]); const getHeaderTitle = () => { if (isSendTransaction) { diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx index ebc8679e3f84..dbfdb3997ac4 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useSelector } from 'react-redux'; import { SOLANA_TOKEN_IMAGE_URL } from '../../../../../../../../shared/constants/multichain/networks'; import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; @@ -15,8 +16,10 @@ import { JustifyContent, TextAlign, TextColor, + TextVariant, } from '../../../../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { selectConfirmationAdvancedDetailsOpen } from '../../../../../selectors/preferences'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; const FEE_ASSET_IMAGE_BY_SYMBOL: Record = { @@ -26,43 +29,57 @@ const FEE_ASSET_IMAGE_BY_SYMBOL: Record = { export function UniversalTransactionFeeRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); + const showAdvancedDetails = useSelector( + selectConfirmationAdvancedDetailsOpen, + ); - if (!data || !data.formattedFee || !data.feeAssetSymbol) { + if (!data) { return null; } - const feeImageUrl = FEE_ASSET_IMAGE_BY_SYMBOL[data.feeAssetSymbol]; + const feeAssetSymbol = 'SOL'; + const feeImageUrl = FEE_ASSET_IMAGE_BY_SYMBOL[feeAssetSymbol]; + const feeFiat = '< $0.01'; + const feeNative = '0.000005'; + + const amountWithIcon = (amountText: string) => ( + + {amountText} + + {feeAssetSymbol} + + ); return ( - - {data.formattedFeeFiat && ( - {data.formattedFeeFiat} - )} + {showAdvancedDetails ? ( - - - {`${data.formattedFee} ${data.feeAssetSymbol}`} + {amountWithIcon(feeNative)} + + {feeFiat} - + ) : ( + amountWithIcon(feeFiat) + )} ); } diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx index 52c1303eafe5..a3230479168e 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/universal-transaction-info.tsx @@ -2,6 +2,8 @@ import { Box } from '@metamask/design-system-react'; import React from 'react'; import { ConfirmInfoSection } from '../../../../../../components/app/confirm/info/row/section'; +import { useUniversalTransactionDataOptional } from '../../../../hooks/transactions/useUniversalTransactionData'; +import { ConfirmLoader } from '../shared/confirm-loader/confirm-loader'; import { UniversalTransactionFeeRow } from './rows/universal-transaction-fee-row'; import { UniversalTransactionFromRow } from './rows/universal-transaction-from-row'; import { UniversalTransactionHeadingRow } from './rows/universal-transaction-heading-row'; @@ -9,6 +11,12 @@ import { UniversalTransactionNetworkRow } from './rows/universal-transaction-net import { UniversalTransactionToRow } from './rows/universal-transaction-to-row'; function UniversalTransactionInfo() { + const data = useUniversalTransactionDataOptional(); + + if (!data) { + return ; + } + return ( <> @@ -25,6 +33,8 @@ function UniversalTransactionInfo() { + + diff --git a/ui/pages/confirmations/hooks/send/useSendActions.ts b/ui/pages/confirmations/hooks/send/useSendActions.ts index 881ad2aeb62d..d21db0fae866 100644 --- a/ui/pages/confirmations/hooks/send/useSendActions.ts +++ b/ui/pages/confirmations/hooks/send/useSendActions.ts @@ -9,10 +9,8 @@ import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE, PREVIOUS_ROUTE, - SEND_ROUTE, } from '../../../../helpers/constants/routes'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { SendPages } from '../../constants/send'; import { ConfirmationLoader } from '../useConfirmationNavigation'; import { sendMultichainTransactionForReview } from '../../utils/multichain-snaps'; import { @@ -75,7 +73,9 @@ export const useSendActions = () => { const route = `${CONFIRM_TRANSACTION_ROUTE}?${params.toString()}`; navigate(route); } else { - navigate(`${SEND_ROUTE}/${SendPages.LOADER}`); + const params = new URLSearchParams(); + params.set('loader', ConfirmationLoader.Send); + navigate(`${CONFIRM_TRANSACTION_ROUTE}?${params.toString()}`); try { const result = (await sendMultichainTransactionForReview( fromAccount as InternalAccount, From c4cc0d1dbf448be9b8a837cdb5b3c53d5a3f5789 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sun, 14 Jun 2026 10:23:07 +0100 Subject: [PATCH 13/28] docs(confirmations): document universal Snap confirmation POC --- ...al-multichain-snap-confirmation-api-poc.md | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 docs/universal-multichain-snap-confirmation-api-poc.md diff --git a/docs/universal-multichain-snap-confirmation-api-poc.md b/docs/universal-multichain-snap-confirmation-api-poc.md new file mode 100644 index 000000000000..0ba9c5c07fef --- /dev/null +++ b/docs/universal-multichain-snap-confirmation-api-poc.md @@ -0,0 +1,136 @@ +# Universal Multichain Snap Confirmation API POC + +## Purpose + +This POC proves that a Snap can request a native MetaMask transaction confirmation for a universal multichain transaction. + +The API is protocol-agnostic. The Solana wallet snap is only the first consumer used to exercise the flow end to end. + +## Architecture Summary + +The Snap owns protocol-specific transaction construction and execution. The extension owns user confirmation UX. Core provides short-lived pending transaction state so the extension UI can render the Snap-provided transaction details while the approval is active. + +Runtime flow: + +1. Snap builds a protocol transaction. +2. Snap calls `snap_confirmTransaction`. +3. Extension receives the Snap API request. +4. Extension stores display data in `MultichainTransactionsController.pendingTransactions`. +5. Extension creates an `ApprovalController` request with type `universalTransaction`. +6. Extension Confirm UI renders the approval using the pending transaction data. +7. User approves or rejects. +8. Extension resolves the Snap API request. +9. Snap signs/submits only if approved. + +## Flow Diagram + +```mermaid +sequenceDiagram + participant Snap as Snap + participant SnapAPI as snap_confirmTransaction + participant Extension as Extension service worker + participant Core as MultichainTransactionsController + participant Approval as ApprovalController + participant UI as Confirm UI + participant User as User + + Snap->>Snap: Build protocol transaction + Snap->>SnapAPI: Request native confirmation + SnapAPI->>Extension: Invoke restricted method handler + Extension->>Core: addPendingTransaction(approvalId, payload) + Extension->>Approval: addRequest(type: universalTransaction) + Approval->>UI: Activate confirmation + UI->>Core: Read pendingTransactions[approvalId] + UI->>User: Render native confirmation + User->>UI: Approve / reject + UI->>Approval: Resolve approval + Approval->>Extension: Return decision + Extension->>Core: removePendingTransaction(approvalId) + Extension->>SnapAPI: true / false + SnapAPI->>Snap: Return decision + Snap->>Snap: Sign and submit if approved +``` + +## Functional Components + +### Snap API: `snap_confirmTransaction` + +Added a new Snap-facing confirmation API path. + +The API accepts protocol-agnostic transaction display data and returns a boolean approval result. It is the boundary between protocol-specific Snap execution and native extension confirmation UX. + +This POC does not finalize naming, schema, access model, or production gating. + +### Solana Wallet Snap + +Uses the new Snap API as the first consumer. + +`SendService` builds the Solana transaction, prepares display metadata, and calls `snap_confirmTransaction` before signing or submitting. If approval returns `true`, it continues with `SolMethod.SignAndSendTransaction`. If approval returns `false`, it stops. + +This proves Solana transaction mechanics can stay in the Snap while confirmation UX moves to the extension. + +### Extension Service Worker + +Implements the restricted method handler behind `snap_confirmTransaction`. + +The handler creates an approval ID, writes the Snap payload into pending multichain transaction state, creates an `ApprovalController` request, waits for the user decision, cleans up pending state, and resolves the Snap request. + +### Core `MultichainTransactionsController` + +Adds short-lived pending universal transaction state. + +`pendingTransactions` is keyed by `approvalId` and stores the transaction display payload needed by the UI. It is non-persisted and removed when the approval resolves. + +Added actions: + +- `addPendingTransaction` +- `updatePendingTransaction` +- `removePendingTransaction` +- `getPendingTransaction` + +Added/exported type: + +- `PendingMultichainTransaction` + +### Approval Controller Integration + +Uses the existing approval system for the user decision lifecycle. + +The extension creates an approval request with type `universalTransaction`. This allows the normal Confirm page infrastructure to route and resolve the request, rather than creating a separate Snap-specific confirmation flow. + +### Extension Confirmation UI + +Adds a universal transaction confirmation view inside the existing Confirm page. + +The UI reads pending transaction data by approval ID, then renders: + +- Transaction heading +- From row +- To row +- Network row +- Network Fee row + +It also reuses the wallet-initiated header so universal confirmations get Advanced Details. Network fee layout was adjusted to match EVM patterns, including right alignment, token icon placement, and advanced fee sub-line behavior. + +### Send Flow Loading + +Updates non-EVM Send submission loading to match EVM. + +Instead of showing the old Send loader screen while the Snap prepares the approval, the flow navigates to the Confirm page with `loader=Send`. This shows the same confirmation skeleton EVM Send uses until the universal approval becomes active. + +## What This POC Proves + +- A Snap can delegate transaction confirmation UX to the extension. +- The Snap API can be the seam between protocol execution and native confirmation. +- Existing `ApprovalController` and Confirm UI architecture can support universal multichain transaction confirmations. +- Core controller state can bridge async Snap requests to React UI without moving protocol transaction construction into extension. + +## What This POC Does Not Cover + +- Final API naming or payload schema. +- Final permission/access policy. +- Security and privacy review. +- Validation with protocols beyond Solana. +- Production feature gating. +- Full test coverage. +- Full replacement of hardcoded fee UI with payload data. From 962393eb3f733d2a57cdb5a48a488cd551209c4a Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sun, 14 Jun 2026 10:32:03 +0100 Subject: [PATCH 14/28] docs(confirmations): include Snaps Platform API layer in POC architecture --- ...al-multichain-snap-confirmation-api-poc.md | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/universal-multichain-snap-confirmation-api-poc.md b/docs/universal-multichain-snap-confirmation-api-poc.md index 0ba9c5c07fef..6ec3369ae277 100644 --- a/docs/universal-multichain-snap-confirmation-api-poc.md +++ b/docs/universal-multichain-snap-confirmation-api-poc.md @@ -14,20 +14,21 @@ Runtime flow: 1. Snap builds a protocol transaction. 2. Snap calls `snap_confirmTransaction`. -3. Extension receives the Snap API request. -4. Extension stores display data in `MultichainTransactionsController.pendingTransactions`. -5. Extension creates an `ApprovalController` request with type `universalTransaction`. -6. Extension Confirm UI renders the approval using the pending transaction data. -7. User approves or rejects. -8. Extension resolves the Snap API request. -9. Snap signs/submits only if approved. +3. Snaps Platform validates the request and invokes the confirmation hook. +4. Extension receives the Snap API request through that hook. +5. Extension stores display data in `MultichainTransactionsController.pendingTransactions`. +6. Extension creates an `ApprovalController` request with type `universalTransaction`. +7. Extension Confirm UI renders the approval using the pending transaction data. +8. User approves or rejects. +9. Extension resolves the Snap API request. +10. Snap signs/submits only if approved. ## Flow Diagram ```mermaid sequenceDiagram participant Snap as Snap - participant SnapAPI as snap_confirmTransaction + participant SnapAPI as Snaps Platform snap_confirmTransaction participant Extension as Extension service worker participant Core as MultichainTransactionsController participant Approval as ApprovalController @@ -36,7 +37,8 @@ sequenceDiagram Snap->>Snap: Build protocol transaction Snap->>SnapAPI: Request native confirmation - SnapAPI->>Extension: Invoke restricted method handler + SnapAPI->>SnapAPI: Validate params and permission + SnapAPI->>Extension: Invoke showUniversalTransactionConfirmation hook Extension->>Core: addPendingTransaction(approvalId, payload) Extension->>Approval: addRequest(type: universalTransaction) Approval->>UI: Activate confirmation @@ -59,6 +61,10 @@ Added a new Snap-facing confirmation API path. The API accepts protocol-agnostic transaction display data and returns a boolean approval result. It is the boundary between protocol-specific Snap execution and native extension confirmation UX. +The API is defined in the Snaps monorepo as a restricted method. It validates the request payload, requires the `snap_confirmTransaction` permission in the Snap manifest, and delegates rendering to a client-provided `showUniversalTransactionConfirmation` hook. + +The POC adds the method to the restricted method registry, the Snap permission types, manifest validation, and the Snap controller restricted-method allowlist. + This POC does not finalize naming, schema, access model, or production gating. ### Solana Wallet Snap From 52900d474feadf57455c50b0dff5d8506dfd050f Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sun, 14 Jun 2026 11:17:23 +0100 Subject: [PATCH 15/28] feat(confirmations): derive universal transaction metadata from asset ids --- .../permissions/snaps/specifications.ts | 35 ++- ...al-multichain-snap-confirmation-api-poc.md | 99 ++++---- .../rows/universal-transaction-fee-row.tsx | 38 ++- .../universal-transaction-heading-row.tsx | 9 +- .../universal-transaction-network-row.tsx | 19 +- .../useUniversalTransactionData.ts | 223 ++++++++++++++---- 6 files changed, 269 insertions(+), 154 deletions(-) diff --git a/app/scripts/controllers/permissions/snaps/specifications.ts b/app/scripts/controllers/permissions/snaps/specifications.ts index 31d84cb44b91..fac9ed3ba2d5 100644 --- a/app/scripts/controllers/permissions/snaps/specifications.ts +++ b/app/scripts/controllers/permissions/snaps/specifications.ts @@ -10,6 +10,7 @@ import type { AssetsControllerGetStateAction } from '@metamask/assets-controller import type { MultichainTransactionsControllerActions } from '@metamask/multichain-transactions-controller'; import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; import { rpcErrors } from '@metamask/rpc-errors'; +import type { CaipAssetType, CaipChainId } from '@metamask/utils'; import { SnapControllerClearSnapStateAction, SnapControllerGetSnapAction, @@ -53,16 +54,14 @@ import { getIsAssetsUnifiedStateIncludedInBuild } from '../../../../../shared/li type ConfirmTransactionParams = { accountId: string; - assetDecimals: number; - assetSymbol: string; - assetType: string; - chain: string; - chainNamespace: 'solana' | 'bip122' | 'tron'; - feeAssetType?: string; - feeRaw?: string; - from: string; + amount: string; + assetId?: CaipAssetType; + chainId: CaipChainId; + fee?: { + amount: string; + assetId?: CaipAssetType; + }; to: string; - value: string; }; export type SnapPermissionSpecificationsActions = @@ -216,17 +215,12 @@ export function getSnapPermissionSpecifications( 'MultichainTransactionsController:addPendingTransaction', { approvalId, - chainNamespace: params.chainNamespace, - chain: params.chain, + chainId: params.chainId, accountId: params.accountId, - from: params.from, to: params.to, - value: params.value, - assetType: params.assetType, - assetSymbol: params.assetSymbol, - assetDecimals: params.assetDecimals, - feeRaw: params.feeRaw, - feeAssetType: params.feeAssetType, + amount: params.amount, + assetId: params.assetId, + fee: params.fee, origin: snapId, createdAt: Date.now(), }, @@ -241,11 +235,10 @@ export function getSnapPermissionSpecifications( type: UNIVERSAL_TRANSACTION_APPROVAL_TYPE as ApprovalType, requestData: { approvalId, - chainNamespace: params.chainNamespace, + chainId: params.chainId, txParams: { - from: params.from, to: params.to, - value: params.value, + value: params.amount, }, }, }, diff --git a/docs/universal-multichain-snap-confirmation-api-poc.md b/docs/universal-multichain-snap-confirmation-api-poc.md index 6ec3369ae277..b0e7a0c5355c 100644 --- a/docs/universal-multichain-snap-confirmation-api-poc.md +++ b/docs/universal-multichain-snap-confirmation-api-poc.md @@ -8,7 +8,7 @@ The API is protocol-agnostic. The Solana wallet snap is only the first consumer ## Architecture Summary -The Snap owns protocol-specific transaction construction and execution. The extension owns user confirmation UX. Core provides short-lived pending transaction state so the extension UI can render the Snap-provided transaction details while the approval is active. +Snaps Platform defines and validates the Snap API. Extension implements the client hook and approval flow. Core stores pending confirmation display data. Extension UI renders the native confirmation. The Solana wallet snap is the first consumer. Runtime flow: @@ -16,7 +16,7 @@ Runtime flow: 2. Snap calls `snap_confirmTransaction`. 3. Snaps Platform validates the request and invokes the confirmation hook. 4. Extension receives the Snap API request through that hook. -5. Extension stores display data in `MultichainTransactionsController.pendingTransactions`. +5. Extension stores the lean transaction payload in `MultichainTransactionsController.pendingTransactions`. 6. Extension creates an `ApprovalController` request with type `universalTransaction`. 7. Extension Confirm UI renders the approval using the pending transaction data. 8. User approves or rejects. @@ -53,76 +53,93 @@ sequenceDiagram Snap->>Snap: Sign and submit if approved ``` -## Functional Components +## Changes By Repository And Package -### Snap API: `snap_confirmTransaction` +### MetaMask/snaps -Added a new Snap-facing confirmation API path. +#### `@metamask/snaps-rpc-methods` -The API accepts protocol-agnostic transaction display data and returns a boolean approval result. It is the boundary between protocol-specific Snap execution and native extension confirmation UX. +Defines the new restricted method. -The API is defined in the Snaps monorepo as a restricted method. It validates the request payload, requires the `snap_confirmTransaction` permission in the Snap manifest, and delegates rendering to a client-provided `showUniversalTransactionConfirmation` hook. +- Adds `snap_confirmTransaction`. +- Defines `ConfirmTransactionParams` with `chainId`, `accountId`, `to`, `amount`, optional `assetId`, and optional `fee`. +- Validates the protocol-agnostic payload, including CAIP chain and asset IDs. +- Registers the method in the restricted method registry. +- Declares the `showUniversalTransactionConfirmation` hook. +- Returns a boolean approval result to the Snap. -The POC adds the method to the restricted method registry, the Snap permission types, manifest validation, and the Snap controller restricted-method allowlist. +#### `@metamask/snaps-sdk` -This POC does not finalize naming, schema, access model, or production gating. +Exposes the permission in Snap developer-facing types. -### Solana Wallet Snap +- Adds `snap_confirmTransaction: EmptyObject` to `InitialPermissions`. +- Allows Snap manifests and Snap code to type-check when requesting the new permission. -Uses the new Snap API as the first consumer. +#### `@metamask/snaps-utils` -`SendService` builds the Solana transaction, prepares display metadata, and calls `snap_confirmTransaction` before signing or submitting. If approval returns `true`, it continues with `SolMethod.SignAndSendTransaction`. If approval returns `false`, it stops. +Adds manifest validation support. -This proves Solana transaction mechanics can stay in the Snap while confirmation UX moves to the extension. +- Adds `snap_confirmTransaction` to manifest permission validation. +- Allows Snaps to declare `"snap_confirmTransaction": {}` in `initialPermissions`. -### Extension Service Worker +#### `@metamask/snaps-controllers` -Implements the restricted method handler behind `snap_confirmTransaction`. +Allows the permission through Snap installation and permission handling. -The handler creates an approval ID, writes the Snap payload into pending multichain transaction state, creates an `ApprovalController` request, waits for the user decision, cleans up pending state, and resolves the Snap request. +- Adds `snap_confirmTransaction` to `ALLOWED_PERMISSIONS`. +- Ensures the restricted method permission is accepted by Snap controller permission handling. -### Core `MultichainTransactionsController` +### MetaMask/core -Adds short-lived pending universal transaction state. +#### `@metamask/multichain-transactions-controller` -`pendingTransactions` is keyed by `approvalId` and stores the transaction display payload needed by the UI. It is non-persisted and removed when the approval resolves. +Provides the temporary Snap-to-UI state bridge. -Added actions: +- Adds non-persisted `pendingTransactions` keyed by `approvalId`. +- Adds `PendingMultichainTransaction`. +- Adds messenger actions: `addPendingTransaction`, `updatePendingTransaction`, `removePendingTransaction`, and `getPendingTransaction`. +- Lets the extension UI read the Snap-provided display payload while the approval is active. -- `addPendingTransaction` -- `updatePendingTransaction` -- `removePendingTransaction` -- `getPendingTransaction` +### MetaMask/metamask-extension -Added/exported type: +#### Snaps Platform Integration -- `PendingMultichainTransaction` +Connects the Snaps Platform hook to extension approval infrastructure. -### Approval Controller Integration +- Implements `showUniversalTransactionConfirmation`. +- Creates an `approvalId`. +- Writes the lean Snap payload into `MultichainTransactionsController.pendingTransactions`. +- Creates an `ApprovalController` request with type `universalTransaction`. +- Resolves the Snap API request based on approval or rejection. +- Cleans up pending transaction state after resolution. -Uses the existing approval system for the user decision lifecycle. +#### Confirmation UI -The extension creates an approval request with type `universalTransaction`. This allows the normal Confirm page infrastructure to route and resolve the request, rather than creating a separate Snap-specific confirmation flow. +Renders the native confirmation. -### Extension Confirmation UI +- Routes `universalTransaction` approvals to universal confirmation UI. +- Reads pending transaction data by `approvalId`. +- Renders heading, From, To, Network, and Network Fee rows. +- Reuses wallet-initiated header so Advanced Details works. +- Matches EVM Send loading and fee-row visual behavior. -Adds a universal transaction confirmation view inside the existing Confirm page. +#### Send Flow Integration -The UI reads pending transaction data by approval ID, then renders: +Aligns non-EVM Send loading with EVM Send. -- Transaction heading -- From row -- To row -- Network row -- Network Fee row +- Navigates to Confirm with `loader=Send` while Snap approval is being prepared. +- Replaces the old Send loader screen with the EVM-style Confirm skeleton. -It also reuses the wallet-initiated header so universal confirmations get Advanced Details. Network fee layout was adjusted to match EVM patterns, including right alignment, token icon placement, and advanced fee sub-line behavior. +### MetaMask/snap-solana-wallet -### Send Flow Loading +#### `@metamask/solana-wallet-snap` -Updates non-EVM Send submission loading to match EVM. +Provides the first consumer. -Instead of showing the old Send loader screen while the Snap prepares the approval, the flow navigates to the Confirm page with `loader=Send`. This shows the same confirmation skeleton EVM Send uses until the universal approval becomes active. +- `SendService` builds the Solana transaction. +- Calls `snap_confirmTransaction` before signing or submitting. +- Passes account, recipient, chain ID, raw amount, optional asset ID, and optional raw fee amount. +- Continues with `SolMethod.SignAndSendTransaction` only if approved. ## What This POC Proves diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx index dbfdb3997ac4..10fbe6e51c4f 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { SOLANA_TOKEN_IMAGE_URL } from '../../../../../../../../shared/constants/multichain/networks'; import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; import { AvatarToken, @@ -22,10 +21,6 @@ import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { selectConfirmationAdvancedDetailsOpen } from '../../../../../selectors/preferences'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; -const FEE_ASSET_IMAGE_BY_SYMBOL: Record = { - SOL: SOLANA_TOKEN_IMAGE_URL, -}; - export function UniversalTransactionFeeRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); @@ -33,14 +28,11 @@ export function UniversalTransactionFeeRow() { selectConfirmationAdvancedDetailsOpen, ); - if (!data) { + if (!data?.formattedFee || !data.feeAssetSymbol) { return null; } - const feeAssetSymbol = 'SOL'; - const feeImageUrl = FEE_ASSET_IMAGE_BY_SYMBOL[feeAssetSymbol]; - const feeFiat = '< $0.01'; - const feeNative = '0.000005'; + const collapsedFee = data.formattedFeeFiat ?? data.formattedFee; const amountWithIcon = (amountText: string) => ( {amountText} - {feeAssetSymbol} + {data.feeAssetSymbol} ); @@ -68,17 +60,19 @@ export function UniversalTransactionFeeRow() { flexDirection={FlexDirection.Column} alignItems={AlignItems.flexEnd} > - {amountWithIcon(feeNative)} - - {feeFiat} - + {amountWithIcon(data.formattedFee)} + {data.formattedFeeFiat ? ( + + {data.formattedFeeFiat} + + ) : null} ) : ( - amountWithIcon(feeFiat) + amountWithIcon(collapsedFee) )} ); diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx index 0836ad7791f5..5904df303fba 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-heading-row.tsx @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import React from 'react'; -import { SOLANA_TOKEN_IMAGE_URL } from '../../../../../../../../shared/constants/multichain/networks'; import { AvatarToken, AvatarTokenSize, @@ -15,10 +14,6 @@ import { import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; import SendHeadingLayout from '../../shared/send-heading-layout/send-heading-layout'; -const ASSET_IMAGE_BY_SYMBOL: Record = { - SOL: SOLANA_TOKEN_IMAGE_URL, -}; - export function UniversalTransactionHeadingRow() { const data = useUniversalTransactionDataOptional(); @@ -26,14 +21,12 @@ export function UniversalTransactionHeadingRow() { return null; } - const assetImageUrl = ASSET_IMAGE_BY_SYMBOL[data.assetSymbol]; - return ( } diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx index b699c8a41dcb..b710719a0476 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-network-row.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { SOLANA_TOKEN_IMAGE_URL } from '../../../../../../../../shared/constants/multichain/networks'; import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; import { AvatarNetwork, @@ -19,14 +18,6 @@ import { import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; -const NETWORK_LABEL_BY_NAMESPACE: Record = { - solana: 'Solana', -}; - -const NETWORK_IMAGE_BY_NAMESPACE: Record = { - solana: SOLANA_TOKEN_IMAGE_URL, -}; - export function UniversalTransactionNetworkRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); @@ -35,10 +26,6 @@ export function UniversalTransactionNetworkRow() { return null; } - const networkLabel = - NETWORK_LABEL_BY_NAMESPACE[data.chainNamespace] ?? data.chain; - const networkImageUrl = NETWORK_IMAGE_BY_NAMESPACE[data.chainNamespace] ?? ''; - return ( - {networkLabel} + {data.networkLabel} diff --git a/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts b/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts index 313dd1ccbfd3..45724a2ebca3 100644 --- a/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts +++ b/ui/pages/confirmations/hooks/transactions/useUniversalTransactionData.ts @@ -1,54 +1,71 @@ import { BigNumber } from 'bignumber.js'; import { useSelector } from 'react-redux'; -import { MultichainNativeAssets } from '../../../../../shared/constants/multichain/assets'; +import { + MULTICHAIN_NETWORK_TO_ASSET_TYPES, + MultichainNativeAssets, +} from '../../../../../shared/constants/multichain/assets'; +import { + BITCOIN_SIGNET_TOKEN_IMAGE_URL, + BITCOIN_TESTNET_TOKEN_IMAGE_URL, + BITCOIN_TOKEN_IMAGE_URL, + MULTICHAIN_NETWORK_TO_NICKNAME, + SOLANA_DEVNET_IMAGE_URL, + SOLANA_TESTNET_IMAGE_URL, + SOLANA_TOKEN_IMAGE_URL, + TRON_NILE_TOKEN_IMAGE_URL, + TRON_SHASTA_TOKEN_IMAGE_URL, + TRON_TOKEN_IMAGE_URL, +} from '../../../../../shared/constants/multichain/networks'; import { calcTokenAmount } from '../../../../../shared/lib/transactions-controller-utils'; import { getIntlLocale } from '../../../../ducks/locale/locale'; import { useFiatFormatter } from '../../../../hooks/useFiatFormatter'; -import { getAssetsRates } from '../../../../selectors/assets'; +import { getAssetsMetadata, getAssetsRates } from '../../../../selectors/assets'; import { formatAmount } from '../../components/simulation-details/formatAmount'; import { useConfirmationId } from '../useConfirmationId'; -const FEE_DECIMALS_BY_NAMESPACE: Record = { - solana: 9, -}; - -const NATIVE_ASSET_ID_BY_NAMESPACE: Record = { - solana: MultichainNativeAssets.SOLANA, -}; - type PendingUniversalTransaction = { approvalId: string; - chainNamespace: string; - chain: string; + chainId: string; accountId: string; - from: string; to: string; - value: string; - assetType: string; - assetSymbol: string; - assetDecimals: number; - feeRaw?: string; - feeAssetType?: string; + amount: string; + assetId?: string; + fee?: { + amount: string; + assetId?: string; + }; origin: string; createdAt: number; }; +type AssetDisplayMetadata = { + assetId: string; + symbol: string; + decimals: number; + imageUrl?: string; +}; + export type UniversalTransactionData = { approvalId: string; chainNamespace: string; chain: string; + chainId: string; + networkLabel: string; + networkImageUrl?: string; accountId: string; from: string; to: string; - value: string; - assetType: string; + amount: string; + assetId: string; assetSymbol: string; assetDecimals: number; - feeRaw?: string; - feeAssetType?: string; + assetImageUrl?: string; + feeAmount?: string; + feeAssetId?: string; feeAssetSymbol?: string; feeAssetDecimals?: number; + feeAssetImageUrl?: string; origin: string; formattedAmount: string; formattedFee?: string; @@ -62,16 +79,89 @@ function selectPendingUniversalTransactionById( return id ? state.metamask.pendingTransactions?.[id] : undefined; } -const FEE_SYMBOL_BY_NAMESPACE: Record = { - solana: 'SOL', +function selectInternalAccountAddressById( + state: { metamask: { internalAccounts?: { accounts?: Record } } }, + accountId: string | undefined, +): string | undefined { + return accountId + ? state.metamask.internalAccounts?.accounts?.[accountId]?.address + : undefined; +} + +const NATIVE_ASSET_METADATA_BY_ASSET_ID: Record = { + [MultichainNativeAssets.BITCOIN]: { + assetId: MultichainNativeAssets.BITCOIN, + symbol: 'BTC', + decimals: 8, + imageUrl: BITCOIN_TOKEN_IMAGE_URL, + }, + [MultichainNativeAssets.BITCOIN_TESTNET]: { + assetId: MultichainNativeAssets.BITCOIN_TESTNET, + symbol: 'tBTC', + decimals: 8, + imageUrl: BITCOIN_TESTNET_TOKEN_IMAGE_URL, + }, + [MultichainNativeAssets.BITCOIN_SIGNET]: { + assetId: MultichainNativeAssets.BITCOIN_SIGNET, + symbol: 'sBTC', + decimals: 8, + imageUrl: BITCOIN_SIGNET_TOKEN_IMAGE_URL, + }, + [MultichainNativeAssets.SOLANA]: { + assetId: MultichainNativeAssets.SOLANA, + symbol: 'SOL', + decimals: 9, + imageUrl: SOLANA_TOKEN_IMAGE_URL, + }, + [MultichainNativeAssets.SOLANA_DEVNET]: { + assetId: MultichainNativeAssets.SOLANA_DEVNET, + symbol: 'SOL', + decimals: 9, + imageUrl: SOLANA_DEVNET_IMAGE_URL, + }, + [MultichainNativeAssets.SOLANA_TESTNET]: { + assetId: MultichainNativeAssets.SOLANA_TESTNET, + symbol: 'SOL', + decimals: 9, + imageUrl: SOLANA_TESTNET_IMAGE_URL, + }, + [MultichainNativeAssets.TRON]: { + assetId: MultichainNativeAssets.TRON, + symbol: 'TRX', + decimals: 6, + imageUrl: TRON_TOKEN_IMAGE_URL, + }, + [MultichainNativeAssets.TRON_NILE]: { + assetId: MultichainNativeAssets.TRON_NILE, + symbol: 'TRX', + decimals: 6, + imageUrl: TRON_NILE_TOKEN_IMAGE_URL, + }, + [MultichainNativeAssets.TRON_SHASTA]: { + assetId: MultichainNativeAssets.TRON_SHASTA, + symbol: 'TRX', + decimals: 6, + imageUrl: TRON_SHASTA_TOKEN_IMAGE_URL, + }, }; +function getChainNamespace(chainId: string): string { + return chainId.split(':')[0] ?? ''; +} + +function getNativeAssetId(chainId: string): string | undefined { + return MULTICHAIN_NETWORK_TO_ASSET_TYPES[ + chainId as keyof typeof MULTICHAIN_NETWORK_TO_ASSET_TYPES + ]?.[0]; +} + export function useUniversalTransactionDataOptional(): | UniversalTransactionData | undefined { const confirmationId = useConfirmationId(); const locale = useSelector(getIntlLocale); const assetRates = useSelector(getAssetsRates); + const assetsMetadata = useSelector(getAssetsMetadata); const fiatFormatter = useFiatFormatter(); const pendingTx = useSelector((state) => selectPendingUniversalTransactionById( @@ -79,31 +169,64 @@ export function useUniversalTransactionDataOptional(): confirmationId, ), ); + const from = useSelector((state) => + selectInternalAccountAddressById( + state as Parameters[0], + pendingTx?.accountId, + ), + ); - if (!pendingTx) { + if (!pendingTx || !from) { return undefined; } - const feeAssetDecimals = - FEE_DECIMALS_BY_NAMESPACE[pendingTx.chainNamespace]; - const feeAssetSymbol = FEE_SYMBOL_BY_NAMESPACE[pendingTx.chainNamespace]; - const nativeAssetId = NATIVE_ASSET_ID_BY_NAMESPACE[pendingTx.chainNamespace]; + const chainNamespace = getChainNamespace(pendingTx.chainId); + const nativeAssetId = getNativeAssetId(pendingTx.chainId); + const assetId = pendingTx.assetId ?? nativeAssetId; + const feeAssetId = pendingTx.fee?.assetId ?? nativeAssetId; + + const getAssetMetadata = ( + targetAssetId: string | undefined, + ): AssetDisplayMetadata | undefined => { + if (!targetAssetId) { + return undefined; + } + + const metadata = assetsMetadata[targetAssetId]; + const nativeMetadata = NATIVE_ASSET_METADATA_BY_ASSET_ID[targetAssetId]; + const unit = metadata?.units?.[0]; + + return { + assetId: targetAssetId, + symbol: metadata?.symbol ?? nativeMetadata?.symbol ?? targetAssetId, + decimals: unit?.decimals ?? nativeMetadata?.decimals ?? 0, + imageUrl: metadata?.iconUrl ?? nativeMetadata?.imageUrl, + }; + }; + + const assetMetadata = getAssetMetadata(assetId); + const feeAssetMetadata = getAssetMetadata(feeAssetId); + const nativeAssetMetadata = getAssetMetadata(nativeAssetId); + + if (!assetMetadata) { + return undefined; + } const formattedAmount = formatAmount( locale, - calcTokenAmount(pendingTx.value, pendingTx.assetDecimals), + calcTokenAmount(pendingTx.amount, assetMetadata.decimals), ); const feeAmount = - pendingTx.feeRaw && feeAssetDecimals !== undefined - ? calcTokenAmount(pendingTx.feeRaw, feeAssetDecimals) + pendingTx.fee?.amount && feeAssetMetadata + ? calcTokenAmount(pendingTx.fee.amount, feeAssetMetadata.decimals) : undefined; const formattedFee = feeAmount ? formatAmount(locale, feeAmount) : undefined; const conversionRate = - nativeAssetId && assetRates?.[nativeAssetId]?.rate - ? assetRates[nativeAssetId].rate + feeAssetId && assetRates?.[feeAssetId]?.rate + ? assetRates[feeAssetId].rate : undefined; const fiatValue = @@ -125,19 +248,27 @@ export function useUniversalTransactionDataOptional(): return { approvalId: pendingTx.approvalId, - chainNamespace: pendingTx.chainNamespace, - chain: pendingTx.chain, + chainNamespace, + chain: pendingTx.chainId, + chainId: pendingTx.chainId, + networkLabel: + MULTICHAIN_NETWORK_TO_NICKNAME[ + pendingTx.chainId as keyof typeof MULTICHAIN_NETWORK_TO_NICKNAME + ] ?? pendingTx.chainId, + networkImageUrl: nativeAssetMetadata?.imageUrl, accountId: pendingTx.accountId, - from: pendingTx.from, + from, to: pendingTx.to, - value: pendingTx.value, - assetType: pendingTx.assetType, - assetSymbol: pendingTx.assetSymbol, - assetDecimals: pendingTx.assetDecimals, - feeRaw: pendingTx.feeRaw, - feeAssetType: pendingTx.feeAssetType, - feeAssetSymbol, - feeAssetDecimals, + amount: pendingTx.amount, + assetId: assetMetadata.assetId, + assetSymbol: assetMetadata.symbol, + assetDecimals: assetMetadata.decimals, + assetImageUrl: assetMetadata.imageUrl, + feeAmount: pendingTx.fee?.amount, + feeAssetId: feeAssetMetadata?.assetId, + feeAssetSymbol: feeAssetMetadata?.symbol, + feeAssetDecimals: feeAssetMetadata?.decimals, + feeAssetImageUrl: feeAssetMetadata?.imageUrl, origin: pendingTx.origin, formattedAmount, formattedFee, From ba5fa739cab76fc6253ff9fbc08dcd8d381f1f6b Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sun, 14 Jun 2026 11:41:26 +0100 Subject: [PATCH 16/28] docs(confirmations): clarify balance centralization boundary --- ...al-multichain-snap-confirmation-api-poc.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/universal-multichain-snap-confirmation-api-poc.md b/docs/universal-multichain-snap-confirmation-api-poc.md index b0e7a0c5355c..42ee50ad52b1 100644 --- a/docs/universal-multichain-snap-confirmation-api-poc.md +++ b/docs/universal-multichain-snap-confirmation-api-poc.md @@ -148,6 +148,26 @@ Provides the first consumer. - Existing `ApprovalController` and Confirm UI architecture can support universal multichain transaction confirmations. - Core controller state can bridge async Snap requests to React UI without moving protocol transaction construction into extension. +## Balance Centralization Is Separate + +This POC is about native confirmation UX ownership, not balance retrieval ownership. + +Non-EVM balance centralization can be solved independently: clients can own balance retrieval/caching through assets and balances controllers, then pass the relevant native/token balances to existing Snap-rendered confirmations so the Snap can gate its own confirm button without fetching balances itself. + +The Send flow balance check is also separable from this POC. Today the client calls the Snap `onAmountInput` path to validate non-EVM amounts before creating the send request. That can be removed without native confirmations: Send can validate entered amounts against client-owned balance state, then the Snap can still render its existing confirmation and use client-provided balances only to check final fee affordability. + +One possible balance-centralization path: + +- Ensure clients/controllers refresh and cache non-EVM balances before Send uses them. +- Remove Send form calls to Snap `onAmountInput` for amount validation. +- Validate Send amount locally against `MultichainBalancesController` / assets-controller state. +- Pass only the relevant client-owned balances to the Snap when creating the send request: native balance, and selected token balance when sending a token. +- Let the Snap continue constructing the protocol transaction and calculating fee / protocol-specific required native amount. +- Let existing Snap-rendered confirmations use those client-provided balances to gate their own confirm button. +- Later, move final affordability checks into client-owned native confirmations once the universal confirmation path is ready for broader adoption. + +Native confirmations make client-owned balance alerts cleaner because the client owns the confirmation footer and blocking alerts. They are not required just to remove duplicate balance fetching from Snaps. + ## What This POC Does Not Cover - Final API naming or payload schema. @@ -157,3 +177,4 @@ Provides the first consumer. - Production feature gating. - Full test coverage. - Full replacement of hardcoded fee UI with payload data. +- Full non-EVM balance centralization; see the separate Obsidian note `[[Non-EVM Balance Centralization]]`. From 1190f623fcec64a40fe44ca97ca66a39c725595f Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sun, 14 Jun 2026 12:55:18 +0100 Subject: [PATCH 17/28] feat(confirmations): add universal transaction balance alerts --- ...al-multichain-snap-confirmation-api-poc.md | 14 +- .../rows/universal-transaction-fee-row.tsx | 13 +- .../components/send/amount/amount.tsx | 12 +- .../confirmations/hooks/alerts/constants.ts | 1 + .../useUniversalTransactionBalanceAlert.ts | 142 ++++++++++++++++++ .../hooks/send/useAmountValidation.test.ts | 14 +- .../hooks/send/useAmountValidation.ts | 71 ++------- .../confirmations/hooks/send/useBalance.ts | 12 +- .../hooks/send/useSnapAmountOnInput.test.ts | 106 ------------- .../hooks/send/useSnapAmountOnInput.ts | 27 ---- .../hooks/useConfirmationAlerts.ts | 5 + .../confirmations/utils/multichain-snaps.ts | 19 --- 12 files changed, 208 insertions(+), 228 deletions(-) create mode 100644 ui/pages/confirmations/hooks/alerts/transactions/useUniversalTransactionBalanceAlert.ts delete mode 100644 ui/pages/confirmations/hooks/send/useSnapAmountOnInput.test.ts delete mode 100644 ui/pages/confirmations/hooks/send/useSnapAmountOnInput.ts diff --git a/docs/universal-multichain-snap-confirmation-api-poc.md b/docs/universal-multichain-snap-confirmation-api-poc.md index 42ee50ad52b1..29c64c5612d7 100644 --- a/docs/universal-multichain-snap-confirmation-api-poc.md +++ b/docs/universal-multichain-snap-confirmation-api-poc.md @@ -122,6 +122,10 @@ Renders the native confirmation. - Renders heading, From, To, Network, and Network Fee rows. - Reuses wallet-initiated header so Advanced Details works. - Matches EVM Send loading and fee-row visual behavior. +- Blocks confirmation with standard danger alerts when client-side multichain balances cannot cover the raw transfer amount plus any Snap-provided aggregate fee requirement. +- Uses an alert row for Network Fee so universal affordability alerts render inline instead of only blocking the footer. +- Converts client display-unit multichain balances into raw units before comparing against the raw Snap confirmation payload. +- Displays non-EVM native Send balances at full token precision because the Max button is hidden and truncated balance text can leave hidden dust that covers fees during QA. #### Send Flow Integration @@ -129,6 +133,8 @@ Aligns non-EVM Send loading with EVM Send. - Navigates to Confirm with `loader=Send` while Snap approval is being prepared. - Replaces the old Send loader screen with the EVM-style Confirm skeleton. +- Removes Send-time Snap `onAmountInput` validation for non-EVM sends. +- Validates Send amount against client balance state before the Snap builds the transaction. ### MetaMask/snap-solana-wallet @@ -139,6 +145,7 @@ Provides the first consumer. - `SendService` builds the Solana transaction. - Calls `snap_confirmTransaction` before signing or submitting. - Passes account, recipient, chain ID, raw amount, optional asset ID, and optional raw fee amount. +- Treats the fee amount as the aggregate execution fee / required amount for the fee asset, not a protocol-specific rent or account-creation field. - Continues with `SolMethod.SignAndSendTransaction` only if approved. ## What This POC Proves @@ -154,17 +161,17 @@ This POC is about native confirmation UX ownership, not balance retrieval owners Non-EVM balance centralization can be solved independently: clients can own balance retrieval/caching through assets and balances controllers, then pass the relevant native/token balances to existing Snap-rendered confirmations so the Snap can gate its own confirm button without fetching balances itself. -The Send flow balance check is also separable from this POC. Today the client calls the Snap `onAmountInput` path to validate non-EVM amounts before creating the send request. That can be removed without native confirmations: Send can validate entered amounts against client-owned balance state, then the Snap can still render its existing confirmation and use client-provided balances only to check final fee affordability. +The Send flow balance check is also separable from this POC. The client no longer calls the Snap `onAmountInput` path to validate non-EVM amounts before creating the send request. Send validates entered amounts against client-owned balance state, then the native universal confirmation performs the final affordability check against the Snap-provided raw amount and aggregate fee requirement. One possible balance-centralization path: - Ensure clients/controllers refresh and cache non-EVM balances before Send uses them. -- Remove Send form calls to Snap `onAmountInput` for amount validation. +- Remove Send form calls to Snap `onAmountInput` for amount validation. This POC now does this for the universal confirmation path. - Validate Send amount locally against `MultichainBalancesController` / assets-controller state. - Pass only the relevant client-owned balances to the Snap when creating the send request: native balance, and selected token balance when sending a token. - Let the Snap continue constructing the protocol transaction and calculating fee / protocol-specific required native amount. - Let existing Snap-rendered confirmations use those client-provided balances to gate their own confirm button. -- Later, move final affordability checks into client-owned native confirmations once the universal confirmation path is ready for broader adoption. +- For native universal confirmations, use client-owned blocking alerts for final affordability checks. Native confirmations make client-owned balance alerts cleaner because the client owns the confirmation footer and blocking alerts. They are not required just to remove duplicate balance fetching from Snaps. @@ -176,5 +183,4 @@ Native confirmations make client-owned balance alerts cleaner because the client - Validation with protocols beyond Solana. - Production feature gating. - Full test coverage. -- Full replacement of hardcoded fee UI with payload data. - Full non-EVM balance centralization; see the separate Obsidian note `[[Non-EVM Balance Centralization]]`. diff --git a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx index 10fbe6e51c4f..8b32a875a915 100644 --- a/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx +++ b/ui/pages/confirmations/components/confirm/info/universal-transaction/rows/universal-transaction-fee-row.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row/row'; +import { ConfirmInfoAlertRow } from '../../../../../../../components/app/confirm/info/row/alert-row/alert-row'; +import { RowAlertKey } from '../../../../../../../components/app/confirm/info/row/constants'; import { AvatarToken, AvatarTokenSize, @@ -20,10 +21,12 @@ import { import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { selectConfirmationAdvancedDetailsOpen } from '../../../../../selectors/preferences'; import { useUniversalTransactionDataOptional } from '../../../../../hooks/transactions/useUniversalTransactionData'; +import { useConfirmationId } from '../../../../../hooks/useConfirmationId'; export function UniversalTransactionFeeRow() { const t = useI18nContext(); const data = useUniversalTransactionDataOptional(); + const confirmationId = useConfirmationId(); const showAdvancedDetails = useSelector( selectConfirmationAdvancedDetailsOpen, ); @@ -53,7 +56,11 @@ export function UniversalTransactionFeeRow() { ); return ( - + {showAdvancedDetails ? ( + ); } diff --git a/ui/pages/confirmations/components/send/amount/amount.tsx b/ui/pages/confirmations/components/send/amount/amount.tsx index 4358dfbcf77c..5ea419c1b949 100644 --- a/ui/pages/confirmations/components/send/amount/amount.tsx +++ b/ui/pages/confirmations/components/send/amount/amount.tsx @@ -39,7 +39,7 @@ export const Amount = ({ const t = useI18nContext(); const { asset, updateValue, value } = useSendContext(); const [amount, setAmount] = useState(value ?? ''); - const { balance } = useBalance(); + const { balance, fullBalance } = useBalance(); const [fiatMode, setFiatMode] = useState(false); const { conversionSupportedForAsset, @@ -62,7 +62,7 @@ export const Amount = ({ fiatMode ? `${formatToFixedDecimals(value, 5)} ${asset?.symbol}` : getFiatDisplayValue(amount), - [amount, fiatMode, getFiatDisplayValue, value], + [amount, asset?.symbol, fiatMode, getFiatDisplayValue, value], ); const onChange = useCallback( @@ -137,15 +137,19 @@ export const Amount = ({ return `${balance}${displayName} ${t('available')}`; } - // For other tokens, use symbol - return `${balance} ${asset?.symbol} ${t('available')}`; + // Non-EVM native sends hide Max, so display full precision for manual entry. + const displayBalance = isNonEvmNativeSendType ? fullBalance : balance; + + return `${displayBalance} ${asset?.symbol} ${t('available')}`; }, [ fiatMode, getFiatDisplayValue, balance, + fullBalance, asset?.symbol, asset?.name, asset?.standard, + isNonEvmNativeSendType, t, ]); diff --git a/ui/pages/confirmations/hooks/alerts/constants.ts b/ui/pages/confirmations/hooks/alerts/constants.ts index 68cc702cc1c2..2787c9712474 100644 --- a/ui/pages/confirmations/hooks/alerts/constants.ts +++ b/ui/pages/confirmations/hooks/alerts/constants.ts @@ -6,6 +6,7 @@ export enum AlertsName { InsufficientPayTokenBalance = 'insufficientPayTokenBalance', InsufficientPayTokenNative = 'insufficientPayTokenNative', InsufficientPayTokenFees = 'insufficientPayTokenFees', + UniversalTransactionInsufficientBalance = 'universalTransactionInsufficientBalance', NetworkBusy = 'networkBusy', NoGasPrice = 'noGasPrice', NoPayTokenQuotes = 'noPayTokenQuotes', diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useUniversalTransactionBalanceAlert.ts b/ui/pages/confirmations/hooks/alerts/transactions/useUniversalTransactionBalanceAlert.ts new file mode 100644 index 000000000000..40562c5f2c8e --- /dev/null +++ b/ui/pages/confirmations/hooks/alerts/transactions/useUniversalTransactionBalanceAlert.ts @@ -0,0 +1,142 @@ +import { BigNumber } from 'bignumber.js'; +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; + +import { UNIVERSAL_TRANSACTION_APPROVAL_TYPE } from '../../../../../../shared/constants/confirmations'; +import { RowAlertKey } from '../../../../../components/app/confirm/info/row/constants'; +import { Alert } from '../../../../../ducks/confirm-alerts/confirm-alerts'; +import { Severity } from '../../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../../hooks/useI18nContext'; +import { getMultichainBalances } from '../../../../../selectors/multichain'; +import { useConfirmContext } from '../../../context/confirm'; +import { + UniversalTransactionData, + useUniversalTransactionDataOptional, +} from '../../transactions/useUniversalTransactionData'; +import { AlertsName } from '../constants'; + +const ZERO = new BigNumber(0); + +type BalanceAlert = { + message: string; +}; + +type MultichainBalances = ReturnType; + +export function useUniversalTransactionBalanceAlert(): Alert[] { + const t = useI18nContext(); + const { currentConfirmation } = useConfirmContext<{ type?: string }>(); + const data = useUniversalTransactionDataOptional(); + const balances = useSelector(getMultichainBalances); + const isUniversalTransaction = + currentConfirmation?.type === UNIVERSAL_TRANSACTION_APPROVAL_TYPE; + + return useMemo(() => { + if (!isUniversalTransaction || !data) { + return []; + } + + const balanceAlert = getBalanceAlert(data, balances, t); + + if (!balanceAlert) { + return []; + } + + return [ + { + field: RowAlertKey.EstimatedFee, + isBlocking: true, + key: AlertsName.UniversalTransactionInsufficientBalance, + message: balanceAlert.message, + reason: t('alertReasonInsufficientBalance'), + severity: Severity.Danger, + }, + ]; + }, [balances, data, isUniversalTransaction, t]); +} + +function getBalanceAlert( + data: UniversalTransactionData, + balances: MultichainBalances, + t: ReturnType, +): BalanceAlert | undefined { + const { transferAmount, feeAmount, transferBalance, feeBalance } = + getBalanceAmounts(data, balances); + + if (data.assetId === data.feeAssetId) { + const requiredBalance = transferAmount.plus(feeAmount); + + if (transferBalance.gte(requiredBalance)) { + return undefined; + } + + return { + message: transferBalance.gte(transferAmount) + ? t('insufficientBalanceToCoverFees') + : t('insufficientFundsSend'), + }; + } + + if (transferBalance.lt(transferAmount)) { + return { + message: t('insufficientFundsSend'), + }; + } + + if (data.feeAmount && feeBalance.lt(feeAmount)) { + return { + message: t('insufficientBalanceToCoverFees'), + }; + } + + return undefined; +} + +function getBalanceAmounts( + data: UniversalTransactionData, + balances: MultichainBalances, +) { + const transferAmount = toBigNumber(data.amount); + const feeAmount = toBigNumber(data.feeAmount); + const rawTransferBalance = getDisplayBalanceAmount( + balances, + data.accountId, + data.assetId, + ); + const rawFeeBalance = getDisplayBalanceAmount( + balances, + data.accountId, + data.feeAssetId, + ); + const transferBalance = toRawAmount(rawTransferBalance, data.assetDecimals); + const feeBalance = toRawAmount(rawFeeBalance, data.feeAssetDecimals); + + return { + transferAmount, + feeAmount, + transferBalance, + feeBalance, + }; +} + +function getDisplayBalanceAmount( + balances: MultichainBalances, + accountId: string, + assetId: string | undefined, +) { + if (!assetId) { + return undefined; + } + + return balances?.[accountId]?.[assetId]?.amount; +} + +function toRawAmount(value: string | undefined, decimals: number | undefined) { + return toBigNumber(value).times(new BigNumber(10).pow(decimals ?? 0)); +} + +function toBigNumber(value: string | number | undefined) { + const valueBigNumber = new BigNumber(value ?? 0); + + return valueBigNumber.isFinite() ? valueBigNumber : ZERO; +} diff --git a/ui/pages/confirmations/hooks/send/useAmountValidation.test.ts b/ui/pages/confirmations/hooks/send/useAmountValidation.test.ts index e99fb5beb4da..287563e050ee 100644 --- a/ui/pages/confirmations/hooks/send/useAmountValidation.test.ts +++ b/ui/pages/confirmations/hooks/send/useAmountValidation.test.ts @@ -1,4 +1,4 @@ -import { waitFor } from '@testing-library/react'; +import { act, waitFor } from '@testing-library/react'; import { Numeric } from '../../../../../shared/lib/Numeric'; import mockState from '../../../../../test/data/mock-state.json'; @@ -346,7 +346,7 @@ describe('useAmountValidation', () => { it('accepts valid decimal amount', async () => { jest.spyOn(SendContext, 'useSendContext').mockReturnValue({ - asset: { ...EVM_NATIVE_ASSET, rawBalance: '0x5f5e100' }, + asset: { ...EVM_NATIVE_ASSET, rawBalance: '0x1bc16d674ec80000' }, chainId: '0x5', from: MOCK_ADDRESS_1, value: '0.5', @@ -361,7 +361,7 @@ describe('useAmountValidation', () => { it('accepts "." as zero without reporting invalid amount', async () => { jest.spyOn(SendContext, 'useSendContext').mockReturnValue({ - asset: { ...EVM_NATIVE_ASSET, rawBalance: '0x5f5e100' }, + asset: { ...EVM_NATIVE_ASSET, rawBalance: '0x1bc16d674ec80000' }, chainId: '0x5', from: MOCK_ADDRESS_1, value: '.', @@ -480,7 +480,7 @@ describe('useAmountValidation', () => { it('validateNonEvmAmountAsync can be called manually', async () => { jest.spyOn(SendContext, 'useSendContext').mockReturnValue({ - asset: EVM_NATIVE_ASSET, + asset: { ...EVM_NATIVE_ASSET, rawBalance: '0x1bc16d674ec80000' }, from: MOCK_ADDRESS_1, value: '1', } as unknown as SendContext.SendContextType); @@ -490,7 +490,11 @@ describe('useAmountValidation', () => { mockState, ); - const error = await result.current.validateNonEvmAmountAsync(); + let error: string | undefined; + await act(async () => { + error = await result.current.validateNonEvmAmountAsync(); + }); + expect(error).toEqual(undefined); }); diff --git a/ui/pages/confirmations/hooks/send/useAmountValidation.ts b/ui/pages/confirmations/hooks/send/useAmountValidation.ts index 6e60a1d788b0..1a0fb6981c5c 100644 --- a/ui/pages/confirmations/hooks/send/useAmountValidation.ts +++ b/ui/pages/confirmations/hooks/send/useAmountValidation.ts @@ -9,20 +9,11 @@ import { } from '../../utils/send'; import { useSendContext } from '../../context/send'; import { Asset, AssetStandard } from '../../types/send'; -import { useSendType } from './useSendType'; -import { useSnapAmountOnInput } from './useSnapAmountOnInput'; import { useBalance } from './useBalance'; -type SnapOnAmountInputResult = { - valid: boolean; - errors: { code: string }[]; -}; - export const useAmountValidation = () => { const t = useI18nContext(); - const { isNonEvmSendType } = useSendType(); const { asset, value } = useSendContext(); - const { validateAmountWithSnap } = useSnapAmountOnInput(); const { rawBalanceNumeric } = useBalance(); const [amountError, setAmountError] = useState(undefined); @@ -31,35 +22,6 @@ export const useAmountValidation = () => { return errorMessage; }, []); - const validateNonEvmAmount = useCallback( - async (amount: string): Promise => { - if (!isNonEvmSendType) { - return undefined; - } - - if (rawBalanceNumeric.isZero()) { - return t('insufficientFundsSend'); - } - - try { - console.log('[useAmountValidation] calling validateAmountWithSnap with amount:', amount || '0'); - const result = (await validateAmountWithSnap( - amount || '0', - )) as SnapOnAmountInputResult; - console.log('[useAmountValidation] snap result:', result); - - if (result.errors?.length > 0) { - return mapSnapErrorCodeIntoTranslation(result.errors[0].code, t); - } - return undefined; - } catch (error) { - console.error('[useAmountValidation] snap RPC threw error:', error); - return t('invalidValue'); - } - }, - [t, validateAmountWithSnap, isNonEvmSendType, rawBalanceNumeric], - ); - const validateAmountAsync = useCallback(async () => { if (!value) { return setAndReturnError(undefined); @@ -68,24 +30,26 @@ export const useAmountValidation = () => { const normalizedValue = normalizeAmount(value); const validations = [ - { name: 'validatePositiveNumericString', fn: () => validatePositiveNumericString(normalizedValue, t) }, - { name: 'validateERC1155Balance', fn: () => validateERC1155Balance(asset as Asset, normalizedValue, t) }, - { name: 'validateTokenBalance', fn: () => - validateTokenBalance( - normalizedValue, - rawBalanceNumeric, - asset?.decimals, - t, - ), + { + fn: () => validatePositiveNumericString(normalizedValue, t), + }, + { + fn: () => validateERC1155Balance(asset as Asset, normalizedValue, t), + }, + { + fn: () => + validateTokenBalance( + normalizedValue, + rawBalanceNumeric, + asset?.decimals, + t, + ), }, - { name: 'validateNonEvmAmount', fn: () => validateNonEvmAmount(normalizedValue) }, ]; for (const validation of validations) { const error = await Promise.resolve(validation.fn()); - console.log(`[useAmountValidation] ${validation.name} result:`, error ?? 'OK', '| rawBalanceNumeric:', rawBalanceNumeric?.toString(), '| decimals:', asset?.decimals); if (error) { - console.error(`[useAmountValidation] BLOCKED by ${validation.name}:`, error); return setAndReturnError(error); } } @@ -96,15 +60,13 @@ export const useAmountValidation = () => { rawBalanceNumeric, t, value, - validateNonEvmAmount, setAndReturnError, ]); // This callback is needed for non-EVM validation when nothing is typed into amount const validateNonEvmAmountAsync = useCallback(async () => { - const error = await validateNonEvmAmount(normalizeAmount(value)); - return setAndReturnError(error); - }, [value, validateNonEvmAmount, setAndReturnError]); + return await validateAmountAsync(); + }, [validateAmountAsync]); useEffect(() => { validateAmountAsync(); @@ -151,7 +113,6 @@ export function validatePositiveNumericString( t: ReturnType, ): string | undefined { const valid = isValidPositiveNumericString(value); - console.log('[useAmountValidation] validatePositiveNumericString value:', JSON.stringify(value), 'valid:', valid); if (!valid) { return t('invalidValue'); } diff --git a/ui/pages/confirmations/hooks/send/useBalance.ts b/ui/pages/confirmations/hooks/send/useBalance.ts index 01d55126c8a2..a09e59d5eb9e 100644 --- a/ui/pages/confirmations/hooks/send/useBalance.ts +++ b/ui/pages/confirmations/hooks/send/useBalance.ts @@ -11,18 +11,18 @@ const getBalance = (asset?: Asset) => { return { balance: '0', decimals: 0, + fullBalance: '0', rawBalanceNumeric: new Numeric('0', 10), }; } const rawBalanceHex = asset?.rawBalance ?? '0x0'; + const fullBalance = toTokenMinimalUnit(rawBalanceHex, asset.decimals); return { - balance: formatToFixedDecimals( - toTokenMinimalUnit(rawBalanceHex, asset.decimals), - asset.decimals, - ), + balance: formatToFixedDecimals(fullBalance, asset.decimals), decimals: asset.decimals, + fullBalance, rawBalanceNumeric: new Numeric(rawBalanceHex, 16).toBase(10), }; }; @@ -30,12 +30,13 @@ const getBalance = (asset?: Asset) => { export const useBalance = () => { const { asset } = useSendContext(); - const { balance, decimals, rawBalanceNumeric } = useMemo(() => { + const { balance, decimals, fullBalance, rawBalanceNumeric } = useMemo(() => { if (asset?.standard === ERC1155) { const bal = asset?.balance ?? '0'; return { balance: bal, decimals: 0, + fullBalance: bal, rawBalanceNumeric: new Numeric(bal, 10), }; } @@ -45,6 +46,7 @@ export const useBalance = () => { return { balance, decimals, + fullBalance, rawBalanceNumeric, }; }; diff --git a/ui/pages/confirmations/hooks/send/useSnapAmountOnInput.test.ts b/ui/pages/confirmations/hooks/send/useSnapAmountOnInput.test.ts deleted file mode 100644 index f841fdc45b9d..000000000000 --- a/ui/pages/confirmations/hooks/send/useSnapAmountOnInput.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { InternalAccount } from '@metamask/keyring-internal-api'; -import { CaipAssetType } from '@metamask/utils'; - -import { renderHookWithProvider } from '../../../../../test/lib/render-helpers-navigate'; -import * as SendContext from '../../context/send'; -import * as MultichainSnaps from '../../utils/multichain-snaps'; -import { useSnapAmountOnInput } from './useSnapAmountOnInput'; - -const MOCK_ACCOUNT: InternalAccount = { - id: '80c14733-13cc-4966-bf1a-6212a6409c22', - address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', - type: 'eip155:eoa', - options: {}, - methods: [], - metadata: { - name: 'Test Account', - keyring: { type: 'HD Key Tree' }, - snap: { id: 'npm:@metamask/test-snap', enabled: true }, - }, - scopes: [], -} as unknown as InternalAccount; - -const MOCK_ASSET = { - assetId: 'eip155:11155111/slip44:60' as CaipAssetType, - address: '0x0000000000000000000000000000000000000000', - chainId: 5, - decimals: 18, - name: 'Ether', - symbol: 'ETH', - isNative: true, -}; - -describe('useSnapAmountOnInput', () => { - let validateAmountMultichainMock: jest.SpyInstance; - - beforeEach(() => { - validateAmountMultichainMock = jest - .spyOn(MultichainSnaps, 'validateAmountMultichain') - .mockResolvedValue(undefined); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('returns validateAmountWithSnap function', () => { - jest.spyOn(SendContext, 'useSendContext').mockReturnValue({ - asset: MOCK_ASSET, - fromAccount: MOCK_ACCOUNT, - value: '1.5', - } as unknown as SendContext.SendContextType); - - const { result } = renderHookWithProvider(() => useSnapAmountOnInput()); - - expect(result.current.validateAmountWithSnap).toBeDefined(); - expect(typeof result.current.validateAmountWithSnap).toBe('function'); - }); - - it('calls validateAmountMultichain with correct parameters', async () => { - jest.spyOn(SendContext, 'useSendContext').mockReturnValue({ - asset: MOCK_ASSET, - fromAccount: MOCK_ACCOUNT, - value: '1.5', - } as unknown as SendContext.SendContextType); - - const { result } = renderHookWithProvider(() => useSnapAmountOnInput()); - await result.current.validateAmountWithSnap('2.5'); - - expect(validateAmountMultichainMock).toHaveBeenCalledWith(MOCK_ACCOUNT, { - value: '2.5', - accountId: MOCK_ACCOUNT.id, - assetId: MOCK_ASSET.assetId, - }); - }); - - it('returns validation result from validateAmountMultichain', async () => { - const mockValidationResult = { error: 'Insufficient balance' }; - validateAmountMultichainMock.mockResolvedValue(mockValidationResult); - - jest.spyOn(SendContext, 'useSendContext').mockReturnValue({ - asset: MOCK_ASSET, - fromAccount: MOCK_ACCOUNT, - value: '1.5', - } as unknown as SendContext.SendContextType); - - const { result } = renderHookWithProvider(() => useSnapAmountOnInput()); - const validationResult = await result.current.validateAmountWithSnap('10'); - - expect(validationResult).toEqual(mockValidationResult); - }); - - it('returns undefined when validation passes', async () => { - validateAmountMultichainMock.mockResolvedValue(undefined); - - jest.spyOn(SendContext, 'useSendContext').mockReturnValue({ - asset: MOCK_ASSET, - fromAccount: MOCK_ACCOUNT, - value: '1.5', - } as unknown as SendContext.SendContextType); - - const { result } = renderHookWithProvider(() => useSnapAmountOnInput()); - const validationResult = await result.current.validateAmountWithSnap('0.5'); - - expect(validationResult).toBeUndefined(); - }); -}); diff --git a/ui/pages/confirmations/hooks/send/useSnapAmountOnInput.ts b/ui/pages/confirmations/hooks/send/useSnapAmountOnInput.ts deleted file mode 100644 index 1ca2064df9cb..000000000000 --- a/ui/pages/confirmations/hooks/send/useSnapAmountOnInput.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useCallback } from 'react'; -import { InternalAccount } from '@metamask/keyring-internal-api'; -import { CaipAssetType } from '@metamask/utils'; - -import { useSendContext } from '../../context/send'; -import { validateAmountMultichain } from '../../utils/multichain-snaps'; - -export const useSnapAmountOnInput = () => { - const { asset, fromAccount, value } = useSendContext(); - - const validateAmountWithSnap = useCallback( - async (amount: string) => { - const result = await validateAmountMultichain( - fromAccount as InternalAccount, - { - value: amount, - accountId: (fromAccount as InternalAccount).id, - assetId: asset?.assetId as CaipAssetType, - }, - ); - return result; - }, - [fromAccount, value, asset], - ); - - return { validateAmountWithSnap }; -}; diff --git a/ui/pages/confirmations/hooks/useConfirmationAlerts.ts b/ui/pages/confirmations/hooks/useConfirmationAlerts.ts index e77f0d7e9168..b0d7e3ddcafc 100644 --- a/ui/pages/confirmations/hooks/useConfirmationAlerts.ts +++ b/ui/pages/confirmations/hooks/useConfirmationAlerts.ts @@ -32,6 +32,7 @@ import { useShieldCoverageAlert } from './alerts/useShieldCoverageAlert'; import { useAddEthereumChainAlerts } from './alerts/useAddEthereumChainAlerts'; import { useBurnAddressAlert } from './alerts/transactions/useBurnAddressAlert'; import { useTokenContractAlert } from './alerts/transactions/useTokenContractAlert'; +import { useUniversalTransactionBalanceAlert } from './alerts/transactions/useUniversalTransactionBalanceAlert'; function useSignatureAlerts(): Alert[] { const accountMismatchAlerts = useAccountMismatchAlerts(); @@ -68,6 +69,8 @@ function useTransactionAlerts(): Alert[] { const suggestedGasFeeHighAlert = useSuggestedGasFeeHighAlert(); const tokenContractAlert = useTokenContractAlert(); const tokenTrustSignalAlerts = useTokenTrustSignalAlerts(); + const universalTransactionBalanceAlerts = + useUniversalTransactionBalanceAlert(); return useMemo( () => [ @@ -93,6 +96,7 @@ function useTransactionAlerts(): Alert[] { ...suggestedGasFeeHighAlert, ...tokenContractAlert, ...tokenTrustSignalAlerts, + ...universalTransactionBalanceAlerts, ], [ accountTypeUpgradeAlerts, @@ -117,6 +121,7 @@ function useTransactionAlerts(): Alert[] { suggestedGasFeeHighAlert, tokenContractAlert, tokenTrustSignalAlerts, + universalTransactionBalanceAlerts, ], ); } diff --git a/ui/pages/confirmations/utils/multichain-snaps.ts b/ui/pages/confirmations/utils/multichain-snaps.ts index 7f21dc3c5abd..be3f0368eef1 100644 --- a/ui/pages/confirmations/utils/multichain-snaps.ts +++ b/ui/pages/confirmations/utils/multichain-snaps.ts @@ -23,22 +23,3 @@ export async function sendMultichainTransactionForReview( }, }); } - -export async function validateAmountMultichain( - fromAccount: InternalAccount, - params: { - value: string; - accountId: string; - assetId: CaipAssetType; - }, -) { - return await handleSnapRequest({ - snapId: fromAccount.metadata?.snap?.id as SnapId, - origin: 'metamask', - handler: HandlerType.OnClientRequest, - request: { - method: 'onAmountInput', - params, - }, - }); -} From 12ee1a57dbe3ad39d578fc40cbae74e53068b183 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sun, 14 Jun 2026 13:13:34 +0100 Subject: [PATCH 18/28] feat(confirmations): query non-EVM balances via accounts API --- ...al-multichain-snap-confirmation-api-poc.md | 1 + .../useUniversalTransactionBalanceAlert.ts | 54 ++++++++- .../confirmations/hooks/send/useBalance.ts | 29 ++++- .../confirmations/hooks/useNonEvmBalance.ts | 111 ++++++++++++++++++ 4 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 ui/pages/confirmations/hooks/useNonEvmBalance.ts diff --git a/docs/universal-multichain-snap-confirmation-api-poc.md b/docs/universal-multichain-snap-confirmation-api-poc.md index 29c64c5612d7..e6f478f4f57a 100644 --- a/docs/universal-multichain-snap-confirmation-api-poc.md +++ b/docs/universal-multichain-snap-confirmation-api-poc.md @@ -126,6 +126,7 @@ Renders the native confirmation. - Uses an alert row for Network Fee so universal affordability alerts render inline instead of only blocking the footer. - Converts client display-unit multichain balances into raw units before comparing against the raw Snap confirmation payload. - Displays non-EVM native Send balances at full token precision because the Max button is hidden and truncated balance text can leave hidden dust that covers fees during QA. +- Adds a POC `useNonEvmBalance` hook that queries Accounts API v5 directly for Solana balances, proving Send validation and native confirmation alerts do not require Snap-owned balance retrieval. #### Send Flow Integration diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useUniversalTransactionBalanceAlert.ts b/ui/pages/confirmations/hooks/alerts/transactions/useUniversalTransactionBalanceAlert.ts index 40562c5f2c8e..a17d0e5175e6 100644 --- a/ui/pages/confirmations/hooks/alerts/transactions/useUniversalTransactionBalanceAlert.ts +++ b/ui/pages/confirmations/hooks/alerts/transactions/useUniversalTransactionBalanceAlert.ts @@ -13,6 +13,7 @@ import { UniversalTransactionData, useUniversalTransactionDataOptional, } from '../../transactions/useUniversalTransactionData'; +import { useNonEvmBalance } from '../../useNonEvmBalance'; import { AlertsName } from '../constants'; const ZERO = new BigNumber(0); @@ -23,11 +24,28 @@ type BalanceAlert = { type MultichainBalances = ReturnType; +type BalanceOverrides = { + feeBalanceRaw?: string; + transferBalanceRaw?: string; +}; + export function useUniversalTransactionBalanceAlert(): Alert[] { const t = useI18nContext(); const { currentConfirmation } = useConfirmContext<{ type?: string }>(); const data = useUniversalTransactionDataOptional(); const balances = useSelector(getMultichainBalances); + const transferBalance = useNonEvmBalance({ + accountAddress: data?.from, + assetId: data?.assetId, + chainId: data?.chainId, + decimals: data?.assetDecimals, + }); + const feeBalance = useNonEvmBalance({ + accountAddress: data?.from, + assetId: data?.feeAssetId, + chainId: data?.chainId, + decimals: data?.feeAssetDecimals, + }); const isUniversalTransaction = currentConfirmation?.type === UNIVERSAL_TRANSACTION_APPROVAL_TYPE; @@ -36,7 +54,16 @@ export function useUniversalTransactionBalanceAlert(): Alert[] { return []; } - const balanceAlert = getBalanceAlert(data, balances, t); + const balanceAlert = getBalanceAlert(data, balances, t, { + transferBalanceRaw: + transferBalance.isLoaded && transferBalance.balanceRaw !== undefined + ? transferBalance.balanceRaw + : undefined, + feeBalanceRaw: + feeBalance.isLoaded && feeBalance.balanceRaw !== undefined + ? feeBalance.balanceRaw + : undefined, + }); if (!balanceAlert) { return []; @@ -52,16 +79,26 @@ export function useUniversalTransactionBalanceAlert(): Alert[] { severity: Severity.Danger, }, ]; - }, [balances, data, isUniversalTransaction, t]); + }, [ + balances, + data, + feeBalance.balanceRaw, + feeBalance.isLoaded, + isUniversalTransaction, + t, + transferBalance.balanceRaw, + transferBalance.isLoaded, + ]); } function getBalanceAlert( data: UniversalTransactionData, balances: MultichainBalances, t: ReturnType, + balanceOverrides: BalanceOverrides, ): BalanceAlert | undefined { const { transferAmount, feeAmount, transferBalance, feeBalance } = - getBalanceAmounts(data, balances); + getBalanceAmounts(data, balances, balanceOverrides); if (data.assetId === data.feeAssetId) { const requiredBalance = transferAmount.plus(feeAmount); @@ -95,6 +132,7 @@ function getBalanceAlert( function getBalanceAmounts( data: UniversalTransactionData, balances: MultichainBalances, + balanceOverrides: BalanceOverrides, ) { const transferAmount = toBigNumber(data.amount); const feeAmount = toBigNumber(data.feeAmount); @@ -108,8 +146,14 @@ function getBalanceAmounts( data.accountId, data.feeAssetId, ); - const transferBalance = toRawAmount(rawTransferBalance, data.assetDecimals); - const feeBalance = toRawAmount(rawFeeBalance, data.feeAssetDecimals); + const transferBalance = + balanceOverrides.transferBalanceRaw === undefined + ? toRawAmount(rawTransferBalance, data.assetDecimals) + : toBigNumber(balanceOverrides.transferBalanceRaw); + const feeBalance = + balanceOverrides.feeBalanceRaw === undefined + ? toRawAmount(rawFeeBalance, data.feeAssetDecimals) + : toBigNumber(balanceOverrides.feeBalanceRaw); return { transferAmount, diff --git a/ui/pages/confirmations/hooks/send/useBalance.ts b/ui/pages/confirmations/hooks/send/useBalance.ts index a09e59d5eb9e..5d50c655c056 100644 --- a/ui/pages/confirmations/hooks/send/useBalance.ts +++ b/ui/pages/confirmations/hooks/send/useBalance.ts @@ -5,6 +5,7 @@ import { Numeric } from '../../../../../shared/lib/Numeric'; import { Asset } from '../../types/send'; import { formatToFixedDecimals, toTokenMinimalUnit } from '../../utils/send'; import { useSendContext } from '../../context/send'; +import { useNonEvmBalance } from '../useNonEvmBalance'; const getBalance = (asset?: Asset) => { if (!asset) { @@ -28,7 +29,13 @@ const getBalance = (asset?: Asset) => { }; export const useBalance = () => { - const { asset } = useSendContext(); + const { asset, chainId, fromAccount } = useSendContext(); + const nonEvmBalance = useNonEvmBalance({ + accountAddress: fromAccount?.address ?? asset?.accountAddress, + assetId: asset?.assetId, + chainId: chainId ?? asset?.chainId?.toString(), + decimals: asset?.decimals, + }); const { balance, decimals, fullBalance, rawBalanceNumeric } = useMemo(() => { if (asset?.standard === ERC1155) { @@ -40,8 +47,26 @@ export const useBalance = () => { rawBalanceNumeric: new Numeric(bal, 10), }; } + + if ( + nonEvmBalance.isSupported && + nonEvmBalance.isLoaded && + nonEvmBalance.balanceRaw !== undefined && + nonEvmBalance.balanceDisplay !== undefined + ) { + return { + balance: formatToFixedDecimals( + nonEvmBalance.balanceDisplay, + asset?.decimals, + ), + decimals: asset?.decimals ?? 0, + fullBalance: nonEvmBalance.balanceDisplay, + rawBalanceNumeric: new Numeric(nonEvmBalance.balanceRaw, 10), + }; + } + return getBalance(asset); - }, [asset]); + }, [asset, nonEvmBalance]); return { balance, diff --git a/ui/pages/confirmations/hooks/useNonEvmBalance.ts b/ui/pages/confirmations/hooks/useNonEvmBalance.ts new file mode 100644 index 000000000000..0a86a550704a --- /dev/null +++ b/ui/pages/confirmations/hooks/useNonEvmBalance.ts @@ -0,0 +1,111 @@ +import { BigNumber } from 'bignumber.js'; +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { useQuery } from '@tanstack/react-query'; + +import { getUseExternalServices } from '../../../selectors'; +import { apiClient } from '../../../helpers/api-client'; + +const ZERO_RAW_BALANCE = '0'; + +type UseNonEvmBalanceArgs = { + accountAddress?: string; + assetId?: string; + chainId?: string; + decimals?: number; +}; + +export type NonEvmBalance = { + balanceDisplay?: string; + balanceRaw?: string; + error?: unknown; + isLoading: boolean; + isLoaded: boolean; + isSupported: boolean; +}; + +// POC only: fetches Solana balances directly from Accounts API to prove non-EVM +// balance retrieval does not need to be owned by the Snap. Production should move +// this into a controller/service and expose cached state through selectors. +export function useNonEvmBalance({ + accountAddress, + assetId, + chainId, + decimals, +}: UseNonEvmBalanceArgs): NonEvmBalance { + const useExternalServices = useSelector(getUseExternalServices); + const isSupported = Boolean(chainId?.startsWith('solana:')); + const caipAccountId = + isSupported && chainId && accountAddress + ? `${chainId}:${accountAddress}` + : undefined; + + const queryOptions = useMemo( + () => + apiClient.accounts.getV5MultiAccountBalancesQueryOptions( + caipAccountId ? [caipAccountId] : [], + chainId ? { networks: [chainId] } : undefined, + ), + [caipAccountId, chainId], + ); + + const query = useQuery({ + ...queryOptions, + enabled: Boolean( + useExternalServices && isSupported && caipAccountId && assetId, + ), + retry: false, + }); + + return useMemo(() => { + if (!isSupported) { + return { + isLoading: false, + isLoaded: false, + isSupported: false, + }; + } + + if (!query.isSuccess) { + return { + error: query.error, + isLoading: query.isLoading, + isLoaded: false, + isSupported: true, + }; + } + + const balance = query.data?.balances?.find( + (entry) => entry.accountId === caipAccountId && entry.assetId === assetId, + ); + const balanceDisplay = balance?.balance ?? '0'; + const balanceDecimals = balance?.decimals ?? decimals ?? 0; + + return { + balanceDisplay, + balanceRaw: toRawAmount(balanceDisplay, balanceDecimals), + isLoading: false, + isLoaded: true, + isSupported: true, + }; + }, [ + assetId, + caipAccountId, + decimals, + isSupported, + query.data, + query.error, + query.isLoading, + query.isSuccess, + ]); +} + +function toRawAmount(value: string, decimals: number) { + const valueBigNumber = new BigNumber(value); + + if (!valueBigNumber.isFinite()) { + return ZERO_RAW_BALANCE; + } + + return valueBigNumber.times(new BigNumber(10).pow(decimals)).toFixed(0); +} From 8437654eb7898a8fa111beeee3de05dd197ea90c Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sun, 14 Jun 2026 13:25:20 +0100 Subject: [PATCH 19/28] feat(confirmations): wait for non-EVM account balances --- ...al-multichain-snap-confirmation-api-poc.md | 1 + .../components/confirm/footer/footer.tsx | 7 +- .../amount-recipient/amount-recipient.tsx | 6 +- .../useUniversalTransactionBalanceAlert.ts | 86 ++++++++++++++----- .../hooks/send/useAmountValidation.test.ts | 8 +- .../hooks/send/useAmountValidation.ts | 9 +- .../confirmations/hooks/send/useBalance.ts | 12 +++ .../hooks/useConfirmationAlerts.ts | 2 +- .../confirmations/hooks/useNonEvmBalance.ts | 2 + 9 files changed, 102 insertions(+), 31 deletions(-) diff --git a/docs/universal-multichain-snap-confirmation-api-poc.md b/docs/universal-multichain-snap-confirmation-api-poc.md index e6f478f4f57a..8c96360e511e 100644 --- a/docs/universal-multichain-snap-confirmation-api-poc.md +++ b/docs/universal-multichain-snap-confirmation-api-poc.md @@ -127,6 +127,7 @@ Renders the native confirmation. - Converts client display-unit multichain balances into raw units before comparing against the raw Snap confirmation payload. - Displays non-EVM native Send balances at full token precision because the Max button is hidden and truncated balance text can leave hidden dust that covers fees during QA. - Adds a POC `useNonEvmBalance` hook that queries Accounts API v5 directly for Solana balances, proving Send validation and native confirmation alerts do not require Snap-owned balance retrieval. +- For supported Solana balances, Send and universal confirmation wait for Accounts API balance data instead of falling back to Snap-backed balance state. #### Send Flow Integration diff --git a/ui/pages/confirmations/components/confirm/footer/footer.tsx b/ui/pages/confirmations/components/confirm/footer/footer.tsx index fc98394623b0..b04e0ff9a23d 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.tsx @@ -34,6 +34,7 @@ import { useEnableShieldCoverageChecks } from '../../../hooks/transactions/useEn import { useTransactionConfirm } from '../../../hooks/transactions/useTransactionConfirm'; import { useConfirmActions } from '../../../hooks/useConfirmActions'; import { useDappSwapActions } from '../../../hooks/transactions/dapp-swap-comparison/useDappSwapActions'; +import { useUniversalTransactionBalanceAlert } from '../../../hooks/alerts/transactions/useUniversalTransactionBalanceAlert'; import { useOriginThrottling } from '../../../hooks/useOriginThrottling'; import { isAddEthereumChainType, @@ -245,6 +246,8 @@ const Footer = () => { const currentConfirmationId = currentConfirmation?.id; const t = useI18nContext(); const { isGaslessLoading } = useIsGaslessLoading(); + const { isLoading: isUniversalTransactionBalanceLoading } = + useUniversalTransactionBalanceAlert(); const { from: fromAddress } = getConfirmationSender(currentConfirmation); const { shouldThrottleOrigin } = useOriginThrottling(); @@ -304,7 +307,9 @@ const Footer = () => { ]); const isConfirmDisabled = - (!isScrollToBottomCompleted && !isSignature) || isGaslessLoading; + (!isScrollToBottomCompleted && !isSignature) || + isGaslessLoading || + isUniversalTransactionBalanceLoading; const shouldShowReconnectButton = shouldRunHardwareWalletPreflight && diff --git a/ui/pages/confirmations/components/send/amount-recipient/amount-recipient.tsx b/ui/pages/confirmations/components/send/amount-recipient/amount-recipient.tsx index eeaf88d91b61..518832a59b73 100644 --- a/ui/pages/confirmations/components/send/amount-recipient/amount-recipient.tsx +++ b/ui/pages/confirmations/components/send/amount-recipient/amount-recipient.tsx @@ -32,7 +32,8 @@ export const AmountRecipient = () => { const [shouldSubmitOnAcknowledge, setShouldSubmitOnAcknowledge] = useState(false); const { asset, toResolved, nonEVMSubmitError } = useSendContext(); - const { amountError, validateNonEvmAmountAsync } = useAmountValidation(); + const { amountError, isBalanceLoading, validateNonEvmAmountAsync } = + useAmountValidation(); const { isNonEvmSendType } = useSendType(); const { handleSubmit } = useSendActions(); const { captureAmountSelected } = useAmountSelectionMetrics(); @@ -50,6 +51,7 @@ export const AmountRecipient = () => { Boolean(hexDataError) || Boolean(nonEVMSubmitError); const isDisabled = hasBlockingError || !toResolved || isNetworkUnreliable; + const isSubmitDisabled = isDisabled || isBalanceLoading; const proceedWithSubmit = useCallback(async () => { if (isNonEvmSendType) { @@ -119,7 +121,7 @@ export const AmountRecipient = () => {