diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js
index 2500c634865..eaf3b7f68b5 100644
--- a/app/components/Nav/Main/RootRPCMethodsUI.js
+++ b/app/components/Nav/Main/RootRPCMethodsUI.js
@@ -31,7 +31,7 @@ import {
import { BN } from 'ethereumjs-util';
import Logger from '../../../util/Logger';
import Approve from '../../Views/ApproveView/Approve';
-import ApprovalFlowLoader from '../../UI/ApprovalFlowLoader';
+import ApprovalFlowLoader from '../../UI/Approval/ApprovalFlowLoader';
import WatchAssetRequest from '../../UI/WatchAssetRequest';
import AccountApproval from '../../UI/AccountApproval';
import TransactionTypes from '../../../core/TransactionTypes';
@@ -58,6 +58,8 @@ import {
selectProviderType,
} from '../../../selectors/networkController';
import { createAccountConnectNavDetails } from '../../Views/AccountConnect';
+import { ApprovalResult } from '../../UI/Approval/ApprovalResult';
+import { ApprovalResultType } from '../../UI/Approval/ApprovalResult/ApprovalResult';
const APPROVAL_TYPES_WITH_DISABLED_CLOSE_ON_APPROVE = [
ApprovalTypes.TRANSACTION,
@@ -91,6 +93,8 @@ const RootRPCMethodsUI = (props) => {
const [watchAsset, setWatchAsset] = useState(undefined);
+ const [approvalResultRequest, setApprovalResultRequest] = useState(undefined);
+
const [signMessageParams, setSignMessageParams] = useState(undefined);
const setTransactionObject = props.setTransactionObject;
@@ -403,6 +407,46 @@ const RootRPCMethodsUI = (props) => {
);
+ const onApprovalResultConfirm = () => {
+ setShowPendingApproval(false);
+ acceptPendingApproval(approvalResultRequest.id, approvalResultRequest.data);
+ setApprovalResultRequest(undefined);
+ };
+
+ const renderApprovalResultModal = () => {
+ if (
+ ![ApprovalTypes.RESULT_SUCCESS, ApprovalTypes.RESULT_ERROR].includes(
+ showPendingApproval?.type,
+ )
+ ) {
+ return null;
+ }
+ return (
+
+
+
+ );
+ };
+
const renderQRSigningModal = () => {
const { isSigningQRObject, QRState } = props;
@@ -789,6 +833,14 @@ const RootRPCMethodsUI = (props) => {
origin: request.origin,
});
break;
+ case ApprovalTypes.RESULT_SUCCESS:
+ case ApprovalTypes.RESULT_ERROR:
+ setApprovalResultRequest({ data: requestData, id: request.id });
+ showPendingApprovalModal({
+ type: request.type,
+ origin: request.origin,
+ });
+ break;
default:
break;
}
@@ -850,6 +902,7 @@ const RootRPCMethodsUI = (props) => {
{renderQRSigningModal()}
{renderAccountsApprovalModal()}
{renderApprovalFlowModal()}
+ {renderApprovalResultModal()}
);
};
diff --git a/app/components/UI/ApprovalFlowLoader/__snapshots__/index.test.tsx.snap b/app/components/UI/Approval/ApprovalFlowLoader/__snapshots__/index.test.tsx.snap
similarity index 100%
rename from app/components/UI/ApprovalFlowLoader/__snapshots__/index.test.tsx.snap
rename to app/components/UI/Approval/ApprovalFlowLoader/__snapshots__/index.test.tsx.snap
diff --git a/app/components/UI/ApprovalFlowLoader/index.js b/app/components/UI/Approval/ApprovalFlowLoader/index.js
similarity index 86%
rename from app/components/UI/ApprovalFlowLoader/index.js
rename to app/components/UI/Approval/ApprovalFlowLoader/index.js
index acacd5a0ebc..637e05eb536 100644
--- a/app/components/UI/ApprovalFlowLoader/index.js
+++ b/app/components/UI/Approval/ApprovalFlowLoader/index.js
@@ -1,10 +1,10 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
-import Device from '../../../util/device';
-import { useTheme } from '../../../util/theme';
-import Text from '../../Base/Text';
-import Spinner from '../AnimatedSpinner';
+import Device from '../../../../util/device';
+import { useTheme } from '../../../../util/theme';
+import Text from '../../../Base/Text';
+import Spinner from '../../AnimatedSpinner';
const createStyles = (colors) =>
StyleSheet.create({
diff --git a/app/components/UI/ApprovalFlowLoader/index.test.tsx b/app/components/UI/Approval/ApprovalFlowLoader/index.test.tsx
similarity index 86%
rename from app/components/UI/ApprovalFlowLoader/index.test.tsx
rename to app/components/UI/Approval/ApprovalFlowLoader/index.test.tsx
index dcc3f445e0f..2752a7d4836 100644
--- a/app/components/UI/ApprovalFlowLoader/index.test.tsx
+++ b/app/components/UI/Approval/ApprovalFlowLoader/index.test.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
-import ApprovalFlowLoader from './';
+import ApprovalFlowLoader from '.';
describe('ApprovalFlowLoader', () => {
it('should render correctly', () => {
diff --git a/app/components/UI/Approval/ApprovalResult/ApprovalResult.styles.ts b/app/components/UI/Approval/ApprovalResult/ApprovalResult.styles.ts
new file mode 100644
index 00000000000..1f2c1c087ec
--- /dev/null
+++ b/app/components/UI/Approval/ApprovalResult/ApprovalResult.styles.ts
@@ -0,0 +1,60 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../util/theme/models';
+import Device from '../../../../util/device';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ root: {
+ backgroundColor: colors.background.default,
+ paddingTop: 24,
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ minHeight: 200,
+ paddingBottom: Device.isIphoneX() ? 20 : 0,
+ },
+ accountCardWrapper: {
+ paddingHorizontal: 24,
+ },
+ actionContainer: {
+ flex: 0,
+ paddingVertical: 16,
+ justifyContent: 'center',
+ },
+ description: {
+ textAlign: 'center',
+ paddingBottom: 16,
+ },
+ snapCell: {
+ marginVertical: 16,
+ },
+ snapPermissionContainer: {
+ maxHeight: 300,
+ borderWidth: 1,
+ borderRadius: 8,
+ borderColor: colors.border.muted,
+ },
+ iconContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ iconWrapper: {
+ width: 48,
+ height: 48,
+ borderRadius: 24,
+ backgroundColor: colors.success.muted,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/UI/Approval/ApprovalResult/ApprovalResult.test.tsx b/app/components/UI/Approval/ApprovalResult/ApprovalResult.test.tsx
new file mode 100644
index 00000000000..77b43028b46
--- /dev/null
+++ b/app/components/UI/Approval/ApprovalResult/ApprovalResult.test.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import ApprovalResult, { ApprovalResultType } from './ApprovalResult';
+
+describe('ApprovalResult', () => {
+ const mockProps = {
+ requestData: {
+ message: 'Success message',
+ },
+ onConfirm: jest.fn(),
+ requestType: ApprovalResultType.Success,
+ };
+
+ it('renders approval result with success type', () => {
+ const wrapper = render();
+
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('renders approval result with error type', () => {
+ const errorMockProps = {
+ ...mockProps,
+ requestData: {
+ error: 'Error message',
+ },
+ requestType: ApprovalResultType.Failure,
+ };
+
+ const wrapper = render();
+
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/Approval/ApprovalResult/ApprovalResult.tsx b/app/components/UI/Approval/ApprovalResult/ApprovalResult.tsx
new file mode 100644
index 00000000000..d2b94f49065
--- /dev/null
+++ b/app/components/UI/Approval/ApprovalResult/ApprovalResult.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { View } from 'react-native';
+import stylesheet from './ApprovalResult.styles';
+import { strings } from '../../../../../locales/i18n';
+import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader';
+import Text, {
+ TextVariant,
+} from '../../../../component-library/components/Texts/Text';
+import Icon, {
+ IconColor,
+ IconName,
+ IconSize,
+} from '../../../../component-library/components/Icons/Icon';
+import {
+ ButtonSize,
+ ButtonVariants,
+} from '../../../../component-library/components/Buttons/Button';
+import BottomSheetFooter, {
+ ButtonsAlignment,
+} from '../../../../component-library/components/BottomSheets/BottomSheetFooter';
+import { ButtonProps } from '../../../../component-library/components/Buttons/Button/Button.types';
+import { useStyles } from '../../../hooks/useStyles';
+
+export enum ApprovalResultType {
+ Success = 'success',
+ Failure = 'failure',
+}
+
+export interface ApprovalResultData {
+ message?: string;
+ error?: string;
+ header?: unknown;
+}
+
+export interface ApprovalResultProps {
+ requestData: ApprovalResultData;
+ onConfirm: () => void;
+ requestType: ApprovalResultType;
+}
+const isApprovalResultTypeSuccess = (type: string) =>
+ ApprovalResultType.Success === type;
+
+const processMessage = (
+ requestData: ApprovalResultData,
+ requestType: ApprovalResultType,
+) => {
+ if (isApprovalResultTypeSuccess(requestType)) {
+ return (
+ requestData?.message ??
+ strings('approval_result.resultPageSuccessDefaultMessage')
+ );
+ }
+ return (
+ requestData?.error ??
+ strings('approval_result.resultPageErrorDefaultMessage')
+ );
+};
+
+const ApprovalResult = ({
+ requestData,
+ onConfirm,
+ requestType,
+}: ApprovalResultProps) => {
+ const { styles } = useStyles(stylesheet, {});
+
+ const okButtonProps: ButtonProps = {
+ variant: ButtonVariants.Primary,
+ label: strings('approval_result.ok'),
+ size: ButtonSize.Lg,
+ onPress: onConfirm,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {processMessage(requestData, requestType)}
+
+
+
+
+
+
+ );
+};
+
+export default ApprovalResult;
diff --git a/app/components/UI/Approval/ApprovalResult/__snapshots__/ApprovalResult.test.tsx.snap b/app/components/UI/Approval/ApprovalResult/__snapshots__/ApprovalResult.test.tsx.snap
new file mode 100644
index 00000000000..c927307a618
--- /dev/null
+++ b/app/components/UI/Approval/ApprovalResult/__snapshots__/ApprovalResult.test.tsx.snap
@@ -0,0 +1,349 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ApprovalResult renders approval result with error type 1`] = `
+
+
+
+
+
+
+
+
+
+
+ Error
+
+
+
+
+ Error message
+
+
+
+
+
+ OK
+
+
+
+
+
+
+`;
+
+exports[`ApprovalResult renders approval result with success type 1`] = `
+
+
+
+
+
+
+
+
+
+
+ Success
+
+
+
+
+ Success message
+
+
+
+
+
+ OK
+
+
+
+
+
+
+`;
diff --git a/app/components/UI/Approval/ApprovalResult/index.ts b/app/components/UI/Approval/ApprovalResult/index.ts
new file mode 100644
index 00000000000..b7f534d9a4a
--- /dev/null
+++ b/app/components/UI/Approval/ApprovalResult/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import ApprovalResult from './ApprovalResult';
+
+export { ApprovalResult };
diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts
index ee5f185c1ed..b25cdc7acc1 100644
--- a/app/core/RPCMethods/RPCMethodMiddleware.ts
+++ b/app/core/RPCMethods/RPCMethodMiddleware.ts
@@ -43,6 +43,8 @@ export enum ApprovalTypes {
ETH_SIGN_TYPED_DATA = 'eth_signTypedData',
WATCH_ASSET = 'wallet_watchAsset',
TRANSACTION = 'transaction',
+ RESULT_ERROR = 'result_error',
+ RESULT_SUCCESS = 'result_success',
}
interface RPCMethodsMiddleParameters {
diff --git a/locales/languages/en.json b/locales/languages/en.json
index 83b21410a38..f261d7b42cc 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -430,6 +430,13 @@
"cta_no_thanks": "No thanks",
"cta_i_agree": "I agree"
},
+ "approval_result": {
+ "ok": "OK",
+ "success": "Success",
+ "error": "Error",
+ "resultPageSuccessDefaultMessage": "The operation completed successfully.",
+ "resultPageErrorDefaultMessage": "The operation failed."
+ },
"token": {
"token_symbol": "Token Symbol",
"token_address": "Token Address",
diff --git a/package.json b/package.json
index 0946bbb931e..1ee004d50d7 100644
--- a/package.json
+++ b/package.json
@@ -118,7 +118,7 @@
"react-native-level-fs/**/semver": "^4.3.2",
"@metamask/contract-metadata": "^2.1.0",
"@metamask/controller-utils": "~3.0.0",
- "@metamask/approval-controller": "3.3.0",
+ "@metamask/approval-controller": "3.4.0",
"@exodus/react-native-payments/validator": "^13.7.0",
"react-devtools-core": "4.22.1",
"**/got": "^11.8.5",
@@ -153,7 +153,7 @@
"@keystonehq/metamask-airgapped-keyring": "^0.3.0",
"@keystonehq/ur-decoder": "^0.6.1",
"@metamask/address-book-controller": "^2.0.0",
- "@metamask/approval-controller": "^3.3.0",
+ "@metamask/approval-controller": "^3.4.0",
"@metamask/assets-controllers": "5.0.0",
"@metamask/base-controller": "^2.0.0",
"@metamask/composable-controller": "^2.0.0",
diff --git a/yarn.lock b/yarn.lock
index cede7eea116..da4119a4a26 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5107,10 +5107,10 @@
"@metamask/base-controller" "^2.0.0"
"@metamask/controller-utils" "^3.0.0"
-"@metamask/approval-controller@3.3.0", "@metamask/approval-controller@^2.0.0", "@metamask/approval-controller@^2.1.1", "@metamask/approval-controller@^3.3.0":
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-3.3.0.tgz#8e98de054ea92af82e50d90d300dc5222c444f3b"
- integrity sha512-UlnNWm/mewteLbVNv+e4yepPkwov1cxHGtJWa5OKN+jE8ZoxX1MGUFBr6G4JczyOmv6JSR03mx0jP7T04ZN0nw==
+"@metamask/approval-controller@3.4.0", "@metamask/approval-controller@^2.0.0", "@metamask/approval-controller@^2.1.1", "@metamask/approval-controller@^3.4.0":
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-3.4.0.tgz#282900361d42f785578728b45014ff8cb5e557ea"
+ integrity sha512-DjqrhiX9+W/Fh6Crr7FPJ87Y/uhPWzBvfXGtekv1LHZNmEtUxkrA7aelddUM0fpTdURIGT4aNGBoQudFidc+Lw==
dependencies:
"@metamask/base-controller" "^3.0.0"
"@metamask/utils" "^5.0.2"