1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 11:22:43 +02:00

[MMI] Fix Connect MMI and Deep link Flows (#19881)

* Sending showCustodyConfirmLink as a prop and fixing other issues

* Upgraded MMI extension monrepo and trying to fix the issue

* prevents deeplink from closing

* Fixed styles of Custody view and changed the place of it

* Fixed CI issues

* fixing eslint issues

* Update LavaMoat policies

* fixing tests

* Fixed test

* updated snapshots

* reorder, otherwise it won't make sense

* adds necessary methods

* removes duplicated key value

* updated snapshot

---------

Co-authored-by: Antonio Regadas <antonio.regadas@consensys.net>
Co-authored-by: MetaMask Bot <metamaskbot@users.noreply.github.com>
Co-authored-by: António Regadas <apregadas@gmail.com>
This commit is contained in:
Albert Olivé 2023-07-13 10:42:08 +02:00 committed by GitHub
parent 4e89c6ca8c
commit b5ece42ca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 361 additions and 274 deletions

View File

@ -982,6 +982,12 @@
"custodianAccount": { "custodianAccount": {
"message": "Custodian account" "message": "Custodian account"
}, },
"custodianAccountAddedDesc": {
"message": "You can now use your custodian accounts in MetaMask Institutional."
},
"custodianAccountAddedTitle": {
"message": "Selected custodian accounts have been added."
},
"custodianReplaceRefreshTokenChangedFailed": { "custodianReplaceRefreshTokenChangedFailed": {
"message": "Please go to $1 and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again." "message": "Please go to $1 and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again."
}, },

View File

@ -815,11 +815,18 @@
}, },
"packages": { "packages": {
"@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>@ethereumjs/util": true,
"@metamask-institutional/custody-controller": true, "@metamask-institutional/extension>@metamask-institutional/custody-controller": true,
"@metamask-institutional/sdk": true, "@metamask-institutional/sdk": true,
"@metamask-institutional/sdk>@metamask-institutional/types": true "@metamask-institutional/sdk>@metamask-institutional/types": true
} }
}, },
"@metamask-institutional/extension>@metamask-institutional/custody-controller": {
"packages": {
"@ethereumjs/tx>@ethereumjs/util": true,
"@metamask-institutional/custody-keyring": true,
"@metamask/obs-store": true
}
},
"@metamask-institutional/institutional-features": { "@metamask-institutional/institutional-features": {
"globals": { "globals": {
"chrome.runtime.id": true, "chrome.runtime.id": true,

View File

@ -218,7 +218,7 @@
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@metamask-institutional/custody-controller": "0.2.6", "@metamask-institutional/custody-controller": "0.2.6",
"@metamask-institutional/custody-keyring": "^0.0.25", "@metamask-institutional/custody-keyring": "^0.0.25",
"@metamask-institutional/extension": "^0.1.3", "@metamask-institutional/extension": "^0.1.6",
"@metamask-institutional/institutional-features": "^1.1.8", "@metamask-institutional/institutional-features": "^1.1.8",
"@metamask-institutional/portfolio-dashboard": "^1.1.3", "@metamask-institutional/portfolio-dashboard": "^1.1.3",
"@metamask-institutional/rpc-allowlist": "^1.0.0", "@metamask-institutional/rpc-allowlist": "^1.0.0",

View File

@ -123,9 +123,10 @@ export default class ConfirmPageContainerContent extends Component {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi) ///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
noteComponent && ( noteComponent && (
<Tab <Tab
data-testid="note-tab"
className="confirm-page-container-content__tab" className="confirm-page-container-content__tab"
name={t('note')} name={t('note')}
pillText={t('new')} tabKey="note"
onClick={() => { onClick={() => {
this.context.trackEvent({ this.context.trackEvent({
category: 'Note to trader', category: 'Note to trader',

View File

@ -17,7 +17,10 @@ import {
setPersonalMessageInProgress, setPersonalMessageInProgress,
} from '../../../store/institutional/institution-background'; } from '../../../store/institutional/institution-background';
import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { checkForUnapprovedMessages } from '../../../store/institutional/institution-actions'; import {
showCustodyConfirmLink,
checkForUnapprovedMessages,
} from '../../../store/institutional/institution-actions';
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../shared/constants/app'; import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../shared/constants/app';
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
import { import {
@ -75,52 +78,6 @@ function mapStateToProps(state, ownProps) {
let mapDispatchToProps = null; let mapDispatchToProps = null;
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
function mmiMapDispatchToProps(dispatch) {
const mmiActions = mmiActionsFactory();
return {
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
setMsgInProgress: (msgId) => dispatch(setPersonalMessageInProgress(msgId)),
showCustodianDeepLink: ({
custodyId,
fromAddress,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
}) =>
showCustodianDeepLink({
dispatch,
mmiActions,
txId: undefined,
fromAddress,
custodyId,
isSignature: true,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
}),
showTransactionsFailedModal: ({
errorMessage,
closeNotification,
operationFailed,
}) =>
dispatch(
showModal({
name: 'TRANSACTION_FAILED',
errorMessage,
closeNotification,
operationFailed,
}),
),
setWaitForConfirmDeepLinkDialog: (wait) =>
dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)),
goHome: () => dispatch(goHome()),
};
}
mapDispatchToProps = mmiMapDispatchToProps;
///: END:ONLY_INCLUDE_IN
mapDispatchToProps = function (dispatch) { mapDispatchToProps = function (dispatch) {
return { return {
goHome: () => dispatch(goHome()), goHome: () => dispatch(goHome()),
@ -150,6 +107,75 @@ mapDispatchToProps = function (dispatch) {
}; };
}; };
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
function mmiMapDispatchToProps(dispatch) {
const mmiActions = mmiActionsFactory();
return {
setMsgInProgress: (msgId) => dispatch(setPersonalMessageInProgress(msgId)),
showCustodianDeepLink: ({
custodyId,
fromAddress,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
}) =>
showCustodianDeepLink({
dispatch,
mmiActions,
txId: undefined,
fromAddress,
custodyId,
isSignature: true,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
showCustodyConfirmLink,
}),
showTransactionsFailedModal: ({
errorMessage,
closeNotification,
operationFailed,
}) =>
dispatch(
showModal({
name: 'TRANSACTION_FAILED',
errorMessage,
closeNotification,
operationFailed,
}),
),
setWaitForConfirmDeepLinkDialog: (wait) =>
dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)),
goHome: () => dispatch(goHome()),
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
showRejectTransactionsConfirmationModal: ({
onSubmit,
unapprovedTxCount: messagesCount,
}) => {
return dispatch(
showModal({
name: 'REJECT_TRANSACTIONS',
onSubmit,
unapprovedTxCount: messagesCount,
isRequestType: true,
}),
);
},
completedTx: (txId) => dispatch(completedTx(txId)),
resolvePendingApproval: (id) => {
dispatch(resolvePendingApproval(id));
},
rejectPendingApproval: (id, error) =>
dispatch(rejectPendingApproval(id, error)),
cancelAllApprovals: (messagesList) => {
dispatch(rejectAllMessages(messagesList));
},
};
}
mapDispatchToProps = mmiMapDispatchToProps;
///: END:ONLY_INCLUDE_IN
function mergeProps(stateProps, dispatchProps, ownProps) { function mergeProps(stateProps, dispatchProps, ownProps) {
const { txData } = ownProps; const { txData } = ownProps;

View File

@ -30,7 +30,10 @@ import {
setTypedMessageInProgress, setTypedMessageInProgress,
} from '../../../store/institutional/institution-background'; } from '../../../store/institutional/institution-background';
import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { checkForUnapprovedMessages } from '../../../store/institutional/institution-actions'; import {
showCustodyConfirmLink,
checkForUnapprovedMessages,
} from '../../../store/institutional/institution-actions';
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
import { import {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi) ///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
@ -101,52 +104,6 @@ function mapStateToProps(state, ownProps) {
let mapDispatchToProps = null; let mapDispatchToProps = null;
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
function mmiMapDispatchToProps(dispatch) {
const mmiActions = mmiActionsFactory();
return {
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
setMsgInProgress: (msgId) => dispatch(setTypedMessageInProgress(msgId)),
showCustodianDeepLink: ({
custodyId,
fromAddress,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
}) =>
showCustodianDeepLink({
dispatch,
mmiActions,
txId: undefined,
fromAddress,
custodyId,
isSignature: true,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
}),
showTransactionsFailedModal: ({
errorMessage,
closeNotification,
operationFailed,
}) =>
dispatch(
showModal({
name: 'TRANSACTION_FAILED',
errorMessage,
closeNotification,
operationFailed,
}),
),
setWaitForConfirmDeepLinkDialog: (wait) =>
dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)),
goHome: () => dispatch(goHome()),
};
}
mapDispatchToProps = mmiMapDispatchToProps;
///: END:ONLY_INCLUDE_IN
mapDispatchToProps = function (dispatch) { mapDispatchToProps = function (dispatch) {
return { return {
resolvePendingApproval: (id) => dispatch(resolvePendingApproval(id)), resolvePendingApproval: (id) => dispatch(resolvePendingApproval(id)),
@ -173,6 +130,73 @@ mapDispatchToProps = function (dispatch) {
}; };
}; };
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
function mmiMapDispatchToProps(dispatch) {
const mmiActions = mmiActionsFactory();
return {
setMsgInProgress: (msgId) => dispatch(setTypedMessageInProgress(msgId)),
showCustodianDeepLink: ({
custodyId,
fromAddress,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
}) =>
showCustodianDeepLink({
dispatch,
mmiActions,
txId: undefined,
fromAddress,
custodyId,
isSignature: true,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
showCustodyConfirmLink,
}),
showTransactionsFailedModal: ({
errorMessage,
closeNotification,
operationFailed,
}) =>
dispatch(
showModal({
name: 'TRANSACTION_FAILED',
errorMessage,
closeNotification,
operationFailed,
}),
),
setWaitForConfirmDeepLinkDialog: (wait) =>
dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)),
goHome: () => dispatch(goHome()),
resolvePendingApproval: (id) => dispatch(resolvePendingApproval(id)),
completedTx: (id) => dispatch(completedTx(id)),
rejectPendingApproval: (id, error) =>
dispatch(rejectPendingApproval(id, error)),
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
showRejectTransactionsConfirmationModal: ({
onSubmit,
unapprovedTxCount: unapprovedMessagesCount,
}) => {
return dispatch(
showModal({
name: 'REJECT_TRANSACTIONS',
onSubmit,
unapprovedTxCount: unapprovedMessagesCount,
isRequestType: true,
}),
);
},
cancelAllApprovals: (unconfirmedMessagesList) => {
dispatch(rejectAllMessages(unconfirmedMessagesList));
},
};
}
mapDispatchToProps = mmiMapDispatchToProps;
///: END:ONLY_INCLUDE_IN
function mergeProps(stateProps, dispatchProps, ownProps) { function mergeProps(stateProps, dispatchProps, ownProps) {
const { const {
allAccounts, allAccounts,

View File

@ -581,7 +581,7 @@ export default class ConfirmTransactionBase extends Component {
}); });
} }
handleCancel() { async handleCancel() {
const { const {
txData, txData,
cancelTransaction, cancelTransaction,
@ -592,9 +592,8 @@ export default class ConfirmTransactionBase extends Component {
this._removeBeforeUnload(); this._removeBeforeUnload();
updateCustomNonce(''); updateCustomNonce('');
cancelTransaction(txData).then(() => { await cancelTransaction(txData);
history.push(mostRecentOverviewPage); history.push(mostRecentOverviewPage);
});
} }
handleSubmit() { handleSubmit() {
@ -705,6 +704,7 @@ export default class ConfirmTransactionBase extends Component {
toAccounts, toAccounts,
toAddress, toAddress,
showCustodianDeepLink, showCustodianDeepLink,
clearConfirmTransaction,
} = this.props; } = this.props;
const { noteText } = this.state; const { noteText } = this.state;
@ -761,7 +761,7 @@ export default class ConfirmTransactionBase extends Component {
}); });
}, },
onDeepLinkShown: () => { onDeepLinkShown: () => {
this.props.clearConfirmTransaction(); clearConfirmTransaction();
this.setState({ submitting: false }, () => { this.setState({ submitting: false }, () => {
history.push(mostRecentOverviewPage); history.push(mostRecentOverviewPage);
updateCustomNonce(''); updateCustomNonce('');

View File

@ -76,6 +76,7 @@ import { CUSTOM_GAS_ESTIMATE } from '../../../shared/constants/gas';
import { getAccountType } from '../../selectors/selectors'; import { getAccountType } from '../../selectors/selectors';
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../shared/constants/app'; import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../shared/constants/app';
import { getIsNoteToTraderSupported } from '../../selectors/institutional/selectors'; import { getIsNoteToTraderSupported } from '../../selectors/institutional/selectors';
import { showCustodyConfirmLink } from '../../store/institutional/institution-actions';
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
import { import {
TransactionStatus, TransactionStatus,
@ -330,15 +331,6 @@ export const mapDispatchToProps = (dispatch) => {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi) ///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
getCustodianConfirmDeepLink: (id) => getCustodianConfirmDeepLink: (id) =>
dispatch(mmiActions.getCustodianConfirmDeepLink(id)), dispatch(mmiActions.getCustodianConfirmDeepLink(id)),
showCustodyConfirmLink: ({ link, address, closeNotification, custodyId }) =>
dispatch(
mmiActions.showCustodyConfirmLink({
link,
address,
closeNotification,
custodyId,
}),
),
showTransactionsFailedModal: (errorMessage, closeNotification) => showTransactionsFailedModal: (errorMessage, closeNotification) =>
dispatch( dispatch(
showModal({ showModal({
@ -362,6 +354,7 @@ export const mapDispatchToProps = (dispatch) => {
closeNotification, closeNotification,
onDeepLinkFetched, onDeepLinkFetched,
onDeepLinkShown, onDeepLinkShown,
showCustodyConfirmLink,
}), }),
setWaitForConfirmDeepLinkDialog: (wait) => setWaitForConfirmDeepLinkDialog: (wait) =>
dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)), dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)),

View File

@ -234,7 +234,7 @@ describe('Confirm Transaction Base', () => {
store, store,
); );
expect(getByTestId('transaction-note')).toBeInTheDocument(); expect(getByTestId('note-tab')).toBeInTheDocument();
}); });
it('handleMainSubmit calls sendTransaction correctly', async () => { it('handleMainSubmit calls sendTransaction correctly', async () => {

View File

@ -1,16 +1,7 @@
import React from 'react'; import React from 'react';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import Box from '../../components/ui/box'; import Box from '../../components/ui/box';
import { CONNECT_HARDWARE_ROUTE } from '../../helpers/constants/routes';
import {
CONNECT_HARDWARE_ROUTE,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
CUSTODY_ACCOUNT_ROUTE,
///: END:ONLY_INCLUDE_IN
} from '../../helpers/constants/routes';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import CustodyPage from '../institutional/custody';
///: END:ONLY_INCLUDE_IN
import ConnectHardwareForm from './connect-hardware'; import ConnectHardwareForm from './connect-hardware';
export default function CreateAccountPage() { export default function CreateAccountPage() {
@ -22,11 +13,6 @@ export default function CreateAccountPage() {
path={CONNECT_HARDWARE_ROUTE} path={CONNECT_HARDWARE_ROUTE}
component={ConnectHardwareForm} component={ConnectHardwareForm}
/> />
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
<Route exact path={CUSTODY_ACCOUNT_ROUTE} component={CustodyPage} />
///: END:ONLY_INCLUDE_IN
}
</Switch> </Switch>
</Box> </Box>
); );

View File

@ -138,7 +138,8 @@ const mapStateToProps = (state) => {
selectedAddress, selectedAddress,
firstPermissionsRequestId, firstPermissionsRequestId,
totalUnapprovedCount, totalUnapprovedCount,
hasApprovalFlows: getApprovalFlows(state).length > 0, hasApprovalFlows:
Array.isArray(getApprovalFlows) && getApprovalFlows(state).length > 0,
connectedStatusPopoverHasBeenShown, connectedStatusPopoverHasBeenShown,
defaultHomeActiveTabName, defaultHomeActiveTabName,
firstTimeFlowType, firstTimeFlowType,

View File

@ -168,11 +168,13 @@ const ConfirmAddCustodianToken = () => {
size={BUTTON_SIZES.LG} size={BUTTON_SIZES.LG}
data-testid="cancel-btn" data-testid="cancel-btn"
onClick={async () => { onClick={async () => {
await mmiActions.removeAddTokenConnectRequest({ await dispatch(
origin: connectRequest.origin, mmiActions.removeAddTokenConnectRequest({
apiUrl: connectRequest.apiUrl, origin: connectRequest.origin,
token: connectRequest.token, apiUrl: connectRequest.apiUrl,
}); token: connectRequest.token,
}),
);
trackEvent({ trackEvent({
category: 'MMI', category: 'MMI',
@ -220,11 +222,13 @@ const ConfirmAddCustodianToken = () => {
}), }),
); );
await mmiActions.removeAddTokenConnectRequest({ await dispatch(
origin: connectRequest.origin, mmiActions.removeAddTokenConnectRequest({
apiUrl: connectRequest.apiUrl, origin: connectRequest.origin,
token: connectRequest.token, apiUrl: connectRequest.apiUrl,
}); token: connectRequest.token,
}),
);
trackEvent({ trackEvent({
category: 'MMI', category: 'MMI',

View File

@ -4,6 +4,16 @@ import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../test/lib/render-helpers'; import { renderWithProvider } from '../../../../test/lib/render-helpers';
import ConfirmAddCustodianToken from './confirm-add-custodian-token'; import ConfirmAddCustodianToken from './confirm-add-custodian-token';
const mockedRemoveAddTokenConnectRequest = jest
.fn()
.mockReturnValue({ type: 'TYPE' });
jest.mock('../../../store/institutional/institution-background', () => ({
mmiActionsFactory: () => ({
removeAddTokenConnectRequest: mockedRemoveAddTokenConnectRequest,
}),
}));
describe('Confirm Add Custodian Token', () => { describe('Confirm Add Custodian Token', () => {
const mockStore = { const mockStore = {
metamask: { metamask: {

View File

@ -20,72 +20,75 @@ exports[`CustodyPage renders CustodyPage 1`] = `
exports[`CustodyPage renders CustodyPage 2`] = ` exports[`CustodyPage renders CustodyPage 2`] = `
<div> <div>
<div <div
class="mm-box mm-box--padding-4 mm-box--display-flex mm-box--flex-direction-column mm-box--background-color-background-default" class="mm-box page-container"
style="box-shadow: var(--shadow-size-xs) var(--color-shadow-default);"
> >
<div <div
class="mm-box mm-box--margin-top-4 mm-box--margin-bottom-4 mm-box--display-flex mm-box--align-items-center" class="mm-box page-container__content mm-box--padding-4 mm-box--display-flex mm-box--flex-direction-column mm-box--width-full"
> >
<button <div
aria-label="Back" class="mm-box mm-box--margin-top-4 mm-box--margin-bottom-4 mm-box--display-flex mm-box--align-items-center"
class="box mm-button-icon mm-button-icon--size-sm box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-default box--background-color-transparent box--rounded-lg"
> >
<span <button
class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit" aria-label="Back"
style="mask-image: url('./images/icons/arrow-left.svg');" class="box mm-button-icon mm-button-icon--size-sm box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-default box--background-color-transparent box--rounded-lg"
/> >
</button> <span
<p class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit"
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default" style="mask-image: url('./images/icons/arrow-left.svg');"
/>
</button>
<p
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
>
Back
</p>
</div>
<h4
class="box mm-text mm-text--body-lg-medium box--margin-top-4 box--flex-direction-row box--color-text-default"
> >
Back Custodial Accounts
</p> </h4>
</div> <h6
<h4 class="box mm-text mm-text--body-md box--margin-top-2 box--margin-bottom-5 box--flex-direction-row box--color-text-default"
class="box mm-text mm-text--body-lg-medium box--margin-top-4 box--flex-direction-row box--color-text-default"
>
Custodial Accounts
</h4>
<h6
class="box mm-text mm-text--body-md box--margin-top-2 box--margin-bottom-5 box--flex-direction-row box--color-text-default"
>
Please choose the custodian you want to connect in order to add or refresh a token.
</h6>
<div
class="mm-box"
>
<ul
width="full"
> >
<div Please choose the custodian you want to connect in order to add or refresh a token.
class="mm-box mm-box--margin-bottom-4 mm-box--padding-4 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between mm-box--align-items-center mm-box--rounded-sm mm-box--border-color-border-default box--border-style-solid box--border-width-1" </h6>
<div
class="mm-box"
>
<ul
width="full"
> >
<div <div
class="mm-box mm-box--display-flex mm-box--align-items-center" class="mm-box mm-box--margin-bottom-4 mm-box--padding-4 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-space-between mm-box--align-items-center mm-box--rounded-sm mm-box--border-color-border-default box--border-style-solid box--border-width-1"
> >
<img <div
alt="Saturn Custody" class="mm-box mm-box--display-flex mm-box--align-items-center"
height="32"
src="https://saturn-custody-ui.dev.metamask-institutional.io/saturn.svg"
width="32"
/>
<p
class="box mm-text mm-text--body-md box--margin-left-2 box--flex-direction-row box--color-text-default"
> >
Saturn Custody <img
</p> alt="Saturn Custody"
height="32"
src="https://saturn-custody-ui.dev.metamask-institutional.io/saturn.svg"
width="32"
/>
<p
class="box mm-text mm-text--body-md box--margin-left-2 box--flex-direction-row box--color-text-default"
>
Saturn Custody
</p>
</div>
<button
class="box mm-text mm-button-base mm-button-base--size-sm mm-button-primary mm-text--body-md-medium box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-inverse box--background-color-primary-default box--rounded-pill"
data-testid="custody-connect-button"
>
Select
</button>
</div> </div>
<button </ul>
class="box mm-text mm-button-base mm-button-base--size-sm mm-button-primary mm-text--body-md-medium box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-inverse box--background-color-primary-default box--rounded-pill" </div>
data-testid="custody-connect-button"
>
Select
</button>
</div>
</ul>
</div> </div>
</div> </div>
</div> </div>

View File

@ -259,8 +259,7 @@ const CustodyPage = () => {
if (Object.keys(connectRequestValue).length) { if (Object.keys(connectRequestValue).length) {
setConnectRequest(connectRequestValue); setConnectRequest(connectRequestValue);
setCurrentJwt( setCurrentJwt(
connectRequestValue.token || connectRequestValue.token || dispatch(mmiActions.getCustodianToken()),
(await dispatch(mmiActions.getCustodianToken())),
); );
setSelectedCustodianType(connectRequestValue.custodianType); setSelectedCustodianType(connectRequestValue.custodianType);
setSelectedCustodianName(connectRequestValue.custodianName); setSelectedCustodianName(connectRequestValue.custodianName);
@ -345,7 +344,7 @@ const CustodyPage = () => {
} }
return ( return (
<> <Box className="page-container">
{connectError && ( {connectError && (
<Text textAlign={TextAlign.Center} marginTop={3} padding={[2, 7, 5]}> <Text textAlign={TextAlign.Center} marginTop={3} padding={[2, 7, 5]}>
{connectError} {connectError}
@ -362,10 +361,8 @@ const CustodyPage = () => {
padding={4} padding={4}
display={Display.Flex} display={Display.Flex}
flexDirection={FlexDirection.Column} flexDirection={FlexDirection.Column}
backgroundColor={Color.backgroundDefault} className="page-container__content"
style={{ width={BlockSize.Full}
boxShadow: 'var(--shadow-size-xs) var(--color-shadow-default)',
}}
> >
<Box <Box
display={Display.Flex} display={Display.Flex}
@ -405,10 +402,8 @@ const CustodyPage = () => {
padding={4} padding={4}
display={Display.Flex} display={Display.Flex}
flexDirection={FlexDirection.Column} flexDirection={FlexDirection.Column}
backgroundColor={Color.backgroundDefault} className="page-container__content"
style={{ width={BlockSize.Full}
boxShadow: 'var(--shadow-size-xs) var(--color-shadow-default)',
}}
> >
<Box <Box
display={Display.Flex} display={Display.Flex}
@ -426,19 +421,19 @@ const CustodyPage = () => {
/> />
<Text>{t('back')}</Text> <Text>{t('back')}</Text>
</Box> </Box>
<Text as="h4"> {selectedCustodianImage && (
<Box display={Display.Flex} alignItems={AlignItems.center}> <Box display={Display.Flex} alignItems={AlignItems.center}>
{selectedCustodianImage && ( <img
<img width={32}
width={32} height={32}
height={32} src={selectedCustodianImage}
src={selectedCustodianImage} alt={selectedCustodianDisplayName}
alt={selectedCustodianDisplayName} />
/> <Text as="h4" marginLeft={2}>
)} {selectedCustodianDisplayName}
<Text marginLeft={2}>{selectedCustodianDisplayName}</Text> </Text>
</Box> </Box>
</Text> )}
<Text marginTop={4}> <Text marginTop={4}>
{t('enterCustodianToken', [selectedCustodianDisplayName])} {t('enterCustodianToken', [selectedCustodianDisplayName])}
</Text> </Text>
@ -454,35 +449,37 @@ const CustodyPage = () => {
])} ])}
onUrlChange={(url) => setApiUrl(url)} onUrlChange={(url) => setApiUrl(url)}
/> />
<Box </Box>
display={Display.Flex} </Box>
flexDirection={FlexDirection.Row} <Box as="footer" className="page-container__footer" padding={4}>
justifyContent={JustifyContent.center} {loading ? (
padding={0} <PulseLoader />
> ) : (
<Box display={Display.Flex} gap={4}>
<Button <Button
block
variant={BUTTON_VARIANT.SECONDARY} variant={BUTTON_VARIANT.SECONDARY}
marginRight={4} size={BUTTON_SIZES.LG}
onClick={() => { onClick={() => {
cancelConnectCustodianToken(); cancelConnectCustodianToken();
}} }}
block
> >
{t('cancel')} {t('cancel')}
</Button> </Button>
<Button <Button
block
data-testid="jwt-form-connect-button" data-testid="jwt-form-connect-button"
size={BUTTON_SIZES.LG}
onClick={connect} onClick={connect}
disabled={ disabled={
!selectedCustodianName || !selectedCustodianName ||
(addNewTokenClicked && !currentJwt) (addNewTokenClicked && !currentJwt)
} }
block
> >
{t('connect')} {t('connect')}
</Button> </Button>
</Box> </Box>
</Box> )}
</Box> </Box>
</> </>
)} )}
@ -625,6 +622,7 @@ const CustodyPage = () => {
className="custody-accounts-empty__footer" className="custody-accounts-empty__footer"
> >
<Button <Button
block
size={BUTTON_SIZES.LG} size={BUTTON_SIZES.LG}
type={BUTTON_VARIANT.SECONDARY} type={BUTTON_VARIANT.SECONDARY}
onClick={() => history.push(DEFAULT_ROUTE)} onClick={() => history.push(DEFAULT_ROUTE)}
@ -634,7 +632,7 @@ const CustodyPage = () => {
</Box> </Box>
</Box> </Box>
)} )}
</> </Box>
); );
}; };

View File

@ -57,6 +57,7 @@ import InteractiveReplacementTokenNotification from '../../components/institutio
import ConfirmAddInstitutionalFeature from '../institutional/confirm-add-institutional-feature'; import ConfirmAddInstitutionalFeature from '../institutional/confirm-add-institutional-feature';
import ConfirmAddCustodianToken from '../institutional/confirm-add-custodian-token'; import ConfirmAddCustodianToken from '../institutional/confirm-add-custodian-token';
import InteractiveReplacementTokenPage from '../institutional/interactive-replacement-token-page'; import InteractiveReplacementTokenPage from '../institutional/interactive-replacement-token-page';
import CustodyPage from '../institutional/custody';
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
import { import {
@ -89,6 +90,7 @@ import {
CONFIRM_INSTITUTIONAL_FEATURE_CONNECT, CONFIRM_INSTITUTIONAL_FEATURE_CONNECT,
CONFIRM_ADD_CUSTODIAN_TOKEN, CONFIRM_ADD_CUSTODIAN_TOKEN,
INTERACTIVE_REPLACEMENT_TOKEN_PAGE, INTERACTIVE_REPLACEMENT_TOKEN_PAGE,
CUSTODY_ACCOUNT_ROUTE,
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(snaps) ///: BEGIN:ONLY_INCLUDE_IN(snaps)
NOTIFICATIONS_ROUTE, NOTIFICATIONS_ROUTE,
@ -337,6 +339,11 @@ export default class Routes extends Component {
path={CONFIRM_ADD_CUSTODIAN_TOKEN} path={CONFIRM_ADD_CUSTODIAN_TOKEN}
component={ConfirmAddCustodianToken} component={ConfirmAddCustodianToken}
/> />
<Authenticated
path={CUSTODY_ACCOUNT_ROUTE}
component={CustodyPage}
exact
/>
{ {
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
} }

View File

@ -1065,8 +1065,9 @@ export function updateAndApproveTx(
dispatch(completedTx(txMeta.id)); dispatch(completedTx(txMeta.id));
dispatch(hideLoadingIndication()); dispatch(hideLoadingIndication());
dispatch(updateCustomNonce('')); dispatch(updateCustomNonce(''));
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
dispatch(closeCurrentNotificationWindow()); dispatch(closeCurrentNotificationWindow());
///: END:ONLY_INCLUDE_IN
return txMeta; return txMeta;
}) })
.catch((err) => { .catch((err) => {

View File

@ -146,7 +146,12 @@ describe('#InstitutionActions', () => {
]; ];
await store.dispatch( await store.dispatch(
showCustodyConfirmLink('link', '0x1', false, 'custodyId'), showCustodyConfirmLink({
link: 'link',
address: '0x1',
closeNotification: false,
custodyId: 'custodyId',
}),
); );
expect(store.getActions()).toStrictEqual(expectedActions); expect(store.getActions()).toStrictEqual(expectedActions);

View File

@ -28,12 +28,17 @@ export function showInteractiveReplacementTokenModal(): ThunkAction<
}; };
} }
export function showCustodyConfirmLink( export function showCustodyConfirmLink({
link: string, link,
address: string, address,
closeNotification: boolean, closeNotification,
custodyId: string, custodyId,
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> { }: {
link: string;
address: string;
closeNotification: boolean;
custodyId: string;
}): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
return (dispatch) => { return (dispatch) => {
dispatch( dispatch(
showModal({ showModal({

View File

@ -106,17 +106,14 @@ export function mmiActionsFactory() {
}; };
} }
function createAction(name: string, payload: any): Promise<void> { function createAction(name: string, payload: any) {
return new Promise((resolve, reject) => { return () => {
callBackgroundMethod(name, [payload], (error) => { callBackgroundMethod(name, [payload], (err) => {
if (error) { if (isErrorWithMessage(err)) {
reject(error); throw new Error(err.message);
return;
} }
resolve();
}); });
}); };
} }
return { return {

View File

@ -3693,7 +3693,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask-institutional/custody-controller@npm:0.2.6, @metamask-institutional/custody-controller@npm:^0.2.5": "@metamask-institutional/custody-controller@npm:0.2.6":
version: 0.2.6 version: 0.2.6
resolution: "@metamask-institutional/custody-controller@npm:0.2.6" resolution: "@metamask-institutional/custody-controller@npm:0.2.6"
dependencies: dependencies:
@ -3706,6 +3706,19 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask-institutional/custody-controller@npm:^0.2.8":
version: 0.2.8
resolution: "@metamask-institutional/custody-controller@npm:0.2.8"
dependencies:
"@ethereumjs/util": ^8.0.5
"@metamask-institutional/custody-keyring": ^0.0.25
"@metamask-institutional/sdk": ^0.1.18
"@metamask-institutional/types": ^1.0.3
"@metamask/obs-store": ^8.0.0
checksum: 5950dd7d6497edd90b17a9265bc2006791a8c2f53c3282bc6f5f85bf92bb794d6ac440bb0019b529fe225c28093471bee9104034b1b977ff6bc5ccb6c6287af7
languageName: node
linkType: hard
"@metamask-institutional/custody-keyring@npm:^0.0.22": "@metamask-institutional/custody-keyring@npm:^0.0.22":
version: 0.0.22 version: 0.0.22
resolution: "@metamask-institutional/custody-keyring@npm:0.0.22" resolution: "@metamask-institutional/custody-keyring@npm:0.0.22"
@ -3738,20 +3751,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask-institutional/extension@npm:^0.1.3": "@metamask-institutional/extension@npm:^0.1.6":
version: 0.1.3 version: 0.1.6
resolution: "@metamask-institutional/extension@npm:0.1.3" resolution: "@metamask-institutional/extension@npm:0.1.6"
dependencies: dependencies:
"@ethereumjs/util": ^8.0.5 "@ethereumjs/util": ^8.0.5
"@metamask-institutional/custody-controller": ^0.2.5 "@metamask-institutional/custody-controller": ^0.2.8
"@metamask-institutional/custody-keyring": ^0.0.22 "@metamask-institutional/custody-keyring": ^0.0.25
"@metamask-institutional/portfolio-dashboard": ^1.1.3 "@metamask-institutional/portfolio-dashboard": ^1.4.0
"@metamask-institutional/sdk": ^0.1.16 "@metamask-institutional/sdk": ^0.1.18
"@metamask-institutional/transaction-update": ^0.1.20 "@metamask-institutional/transaction-update": ^0.1.23
"@metamask-institutional/types": ^1.0.2 "@metamask-institutional/types": ^1.0.3
jest-create-mock-instance: ^2.0.0 jest-create-mock-instance: ^2.0.0
jest-fetch-mock: 3.0.3 jest-fetch-mock: 3.0.3
checksum: a9a4d3183b972b992649081f586a9d92e92d5366afb9be445fcdeb0b9afd99e765e6a8ad63abddc0d222a800c1a6b02e847e0f705908581950cf1bc791be50ca checksum: d62e30658fe6be6c6ee955148bbebf729f13070cb049f0080be3a0e2f97f9ba735fe390ad26855cdb2badcdf2bbc089e2dc9641caf5c1970adceb8e71199384f
languageName: node languageName: node
linkType: hard linkType: hard
@ -3766,10 +3779,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask-institutional/portfolio-dashboard@npm:^1.1.3": "@metamask-institutional/portfolio-dashboard@npm:^1.1.3, @metamask-institutional/portfolio-dashboard@npm:^1.4.0":
version: 1.1.3 version: 1.4.0
resolution: "@metamask-institutional/portfolio-dashboard@npm:1.1.3" resolution: "@metamask-institutional/portfolio-dashboard@npm:1.4.0"
checksum: ea5918426372f66c5ba575551658ebf443b77e528317aede5d79a21208718d90e756fa86389c366162a58363b38e8d47bed0ebb27938285705723cfa35295287 checksum: f6bc1801cd5ea945dacdd3404cb4d2da31049746eff7e5436091d238a4a39abae8522cd58958f3b1bc61a558bac2b1c4f38f024976dcf87025d6b9d51a78fbf0
languageName: node languageName: node
linkType: hard linkType: hard
@ -3780,7 +3793,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask-institutional/sdk@npm:^0.1.14, @metamask-institutional/sdk@npm:^0.1.15, @metamask-institutional/sdk@npm:^0.1.16, @metamask-institutional/sdk@npm:^0.1.17, @metamask-institutional/sdk@npm:^0.1.18": "@metamask-institutional/sdk@npm:^0.1.14, @metamask-institutional/sdk@npm:^0.1.15, @metamask-institutional/sdk@npm:^0.1.17, @metamask-institutional/sdk@npm:^0.1.18":
version: 0.1.18 version: 0.1.18
resolution: "@metamask-institutional/sdk@npm:0.1.18" resolution: "@metamask-institutional/sdk@npm:0.1.18"
dependencies: dependencies:
@ -3801,17 +3814,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask-institutional/transaction-update@npm:^0.1.20, @metamask-institutional/transaction-update@npm:^0.1.21": "@metamask-institutional/transaction-update@npm:^0.1.21, @metamask-institutional/transaction-update@npm:^0.1.23":
version: 0.1.21 version: 0.1.23
resolution: "@metamask-institutional/transaction-update@npm:0.1.21" resolution: "@metamask-institutional/transaction-update@npm:0.1.23"
dependencies: dependencies:
"@metamask-institutional/custody-keyring": ^0.0.22 "@metamask-institutional/custody-keyring": ^0.0.25
"@metamask-institutional/sdk": ^0.1.15 "@metamask-institutional/sdk": ^0.1.18
"@metamask-institutional/types": ^1.0.2 "@metamask-institutional/types": ^1.0.3
"@metamask-institutional/websocket-client": ^0.1.23 "@metamask-institutional/websocket-client": ^0.1.25
"@metamask/obs-store": ^8.0.0 "@metamask/obs-store": ^8.0.0
ethereumjs-util: ^7.1.5 ethereumjs-util: ^7.1.5
checksum: 22190a114279e365cdb89a58ae75c610dfa77e7bca365c85544af5f9d6977da0d0f7410b6acc76defdc08b608f6981a54e4df63b66a2e7fd2cd869ce0cd85945 checksum: 73fad3dde4358f4b4b94dba76775c25dc789570d0ec218a682e206739effa5e6f8a809210743e0f8fdbad58900fee6ea3b8accca6a9f74c7052f2e8a25392163
languageName: node languageName: node
linkType: hard linkType: hard
@ -3822,15 +3835,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask-institutional/websocket-client@npm:^0.1.23": "@metamask-institutional/websocket-client@npm:^0.1.25":
version: 0.1.23 version: 0.1.25
resolution: "@metamask-institutional/websocket-client@npm:0.1.23" resolution: "@metamask-institutional/websocket-client@npm:0.1.25"
dependencies: dependencies:
"@metamask-institutional/custody-keyring": ^0.0.22 "@metamask-institutional/custody-keyring": ^0.0.25
"@metamask-institutional/sdk": ^0.1.15 "@metamask-institutional/sdk": ^0.1.18
"@metamask-institutional/types": ^1.0.2 "@metamask-institutional/types": ^1.0.3
mock-socket: ^9.2.1 mock-socket: ^9.2.1
checksum: 7b7f091ab43287aa12bdfb75ff9ffc4b2272f1e404a3eab3a3a962b777ef47d374fd62ec3227023b858ce5cedabc00b06a9efcd973dbd71226049835236cca5d checksum: f42334200fc46dab5b049330cb42881f8e08952a6ff15fceb9fd561653561de303d756fb9267c97e4bf658bf5e2ae040fb7370319208368049cd0c252bffb0ed
languageName: node languageName: node
linkType: hard linkType: hard
@ -24625,7 +24638,7 @@ __metadata:
"@material-ui/core": ^4.11.0 "@material-ui/core": ^4.11.0
"@metamask-institutional/custody-controller": 0.2.6 "@metamask-institutional/custody-controller": 0.2.6
"@metamask-institutional/custody-keyring": ^0.0.25 "@metamask-institutional/custody-keyring": ^0.0.25
"@metamask-institutional/extension": ^0.1.3 "@metamask-institutional/extension": ^0.1.6
"@metamask-institutional/institutional-features": ^1.1.8 "@metamask-institutional/institutional-features": ^1.1.8
"@metamask-institutional/portfolio-dashboard": ^1.1.3 "@metamask-institutional/portfolio-dashboard": ^1.1.3
"@metamask-institutional/rpc-allowlist": ^1.0.0 "@metamask-institutional/rpc-allowlist": ^1.0.0