1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 09:23:21 +01:00

[MMI] Removed compliance feature from the codebase (#20088)

This commit is contained in:
Albert Olivé 2023-07-20 10:11:14 +02:00 committed by GitHub
parent 88cb8ce0f5
commit 26225aabe0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 18 additions and 2217 deletions

View File

@ -153,9 +153,6 @@
"accountSelectionRequired": {
"message": "You need to select an account!"
},
"activated": {
"message": "Active"
},
"active": {
"message": "Active"
},
@ -376,9 +373,6 @@
"message": "Allow $1 to withdraw and spend up to the following amount:",
"description": "The url of the site that requested permission to 'withdraw and spend'"
},
"amlCompliance": {
"message": "AML/CFT Compliance"
},
"amount": {
"message": "Amount"
},
@ -676,45 +670,9 @@
"close": {
"message": "Close"
},
"codefiCompliance": {
"message": "Codefi Compliance"
},
"coingecko": {
"message": "CoinGecko"
},
"complianceActivatedDesc": {
"message": "You can now use compliance in MetaMask Institutional. Receiving AML/CFT analysis within the confirmation screen on all the addresses you interact with."
},
"complianceActivatedTitle": {
"message": "Your compliance feature is activated"
},
"complianceBlurb0": {
"message": "DeFi raises AML/CFT risk for institutions, given the decentralised pools and pseudonymous counterparties."
},
"complianceBlurb1": {
"message": "Codefi Compliance is the only product capable of running AML/CFT analysis on DeFi pools. This allows you to identify and avoid pools and counterparties that fail your risk setting."
},
"complianceBlurbStep1": {
"message": "Sign up to Codefi Compliance below"
},
"complianceBlurbStep2": {
"message": "Create an organisation"
},
"complianceBlurbStep3": {
"message": "Create a project"
},
"complianceBlurbStep4": {
"message": "Set your compliance settings"
},
"complianceBlurbStep5": {
"message": "Click the \"Enable Compliance in MMI\" button"
},
"complianceBlurpStep0": {
"message": "Steps to enable AML/CFT Compliance:"
},
"complianceSettingsExplanation": {
"message": "Change your settings or view reports by opening up Codefi Compliance or disconnect below."
},
"configureSnapPopupDescription": {
"message": "You're now leaving MetaMask to configure this snap."
},
@ -838,9 +796,6 @@
"connectingToSepolia": {
"message": "Connecting to Sepolia test network"
},
"connectionError": {
"message": "Connection error"
},
"connectionFailed": {
"message": "Connection failed"
},
@ -1974,9 +1929,6 @@
"message": "Installed on $1",
"description": "$1 is the date when the snap has been installed"
},
"institutionalFeatures": {
"message": "Institutional Features"
},
"insufficientBalance": {
"message": "Insufficient balance."
},
@ -2315,9 +2267,6 @@
"mmiAddToken": {
"message": "The page at $1 would like to authorise the following custodian token in MetaMask Institutional"
},
"mmiAuthenticate": {
"message": "The page at $1 would like to authorise the following projects compliance settings in MetaMask Institutional"
},
"mmiBuiltAroundTheWorld": {
"message": "MetaMask Institutional is designed and built around the world."
},
@ -2544,9 +2493,6 @@
"noNFTs": {
"message": "No NFTs yet"
},
"noReport": {
"message": "No Report"
},
"noSnaps": {
"message": "You don't have any snaps installed."
},
@ -2988,9 +2934,6 @@
"onlyConnectTrust": {
"message": "Only connect with sites you trust."
},
"openCodefiCompliance": {
"message": "Open Codefi Compliance"
},
"openFullScreenForLedgerWebHid": {
"message": "Go to full screen to connect your Ledger.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
@ -3326,12 +3269,6 @@
"proceedWithTransaction": {
"message": "I want to proceed anyway"
},
"projectIdInvalid": {
"message": "Provided Project ID is invalid"
},
"projectName": {
"message": "Project Name"
},
"proposedApprovalLimit": {
"message": "Proposed approval limit"
},
@ -3450,12 +3387,6 @@
"replace": {
"message": "replace"
},
"reportLastRun": {
"message": "Report last run"
},
"reportLastRunTooltip": {
"message": "The date and time of when the last AML/CFT report was run"
},
"requestFlaggedAsMaliciousFallbackCopyReason": {
"message": "The security provider has not shared additional details"
},
@ -3594,18 +3525,9 @@
"revokeSpendingCapTooltipText": {
"message": "This third party will be unable to spend any more of your current or future tokens."
},
"riskRating": {
"message": "Risk rating"
},
"riskRatingTooltip": {
"message": "The risk rating of the address you are interacting with based on your risk settings"
},
"rpcUrl": {
"message": "New RPC URL"
},
"runReport": {
"message": "Run report"
},
"safeTransferFrom": {
"message": "Safe transfer from"
},
@ -3830,9 +3752,6 @@
"showPrivateKeys": {
"message": "Show Private Keys"
},
"showReport": {
"message": "Show report"
},
"showTestnetNetworks": {
"message": "Show test networks"
},

View File

@ -225,15 +225,6 @@ export default class MMIController extends EventEmitter {
) {
this.transactionUpdateController.getCustomerProofForAddresses(addresses);
}
try {
if (this.institutionalFeaturesController.getComplianceProjectId()) {
this.institutionalFeaturesController.startPolling();
}
} catch (e) {
log.error('Failed to start Compliance polling');
log.error(e);
}
}
async connectCustodyAddresses(custodianType, custodianName, accounts) {

View File

@ -2471,30 +2471,6 @@ export default class MetamaskController extends EventEmitter {
this.mmiConfigurationController.getConfiguration.bind(
this.mmiConfigurationController,
),
setComplianceAuthData:
this.institutionalFeaturesController.setComplianceAuthData.bind(
this.institutionalFeaturesController,
),
deleteComplianceAuthData:
this.institutionalFeaturesController.deleteComplianceAuthData.bind(
this.institutionalFeaturesController,
),
generateComplianceReport:
this.institutionalFeaturesController.generateComplianceReport.bind(
this.institutionalFeaturesController,
),
syncReportsInProgress:
this.institutionalFeaturesController.syncReportsInProgress.bind(
this.institutionalFeaturesController,
),
removeConnectInstitutionalFeature:
this.institutionalFeaturesController.removeConnectInstitutionalFeature.bind(
this.institutionalFeaturesController,
),
getComplianceHistoricalReportsByAddress:
this.institutionalFeaturesController.getComplianceHistoricalReportsByAddress.bind(
this.institutionalFeaturesController,
),
removeAddTokenConnectRequest:
this.institutionalFeaturesController.removeAddTokenConnectRequest.bind(
this.institutionalFeaturesController,

View File

@ -587,7 +587,7 @@ export enum MetaMetricsEventName {
ConnectCustodialAccountClicked = 'Connect Custodial Account Clicked',
MMIPortfolioButtonClicked = 'MMI Portfolio Button Clicked',
StakeButtonClicked = 'Stake Button Clicked',
ComplianceButtonClicked = 'Compliance Button Clicked',
InteractiveReplacementTokenButtonClicked = 'Interactive Replacement Token Button Clicked',
RefreshTokenListClicked = 'Refresh Token List Clicked',
SignatureDeeplinkDisplayed = 'Signature Deeplink Displayed',
InstitutionalFeatureConnected = 'Institutional Feature Connected',

View File

@ -98,8 +98,9 @@
@import 'network-account-balance-header/index';
@import 'approve-content-card/index';
@import 'transaction-alerts/transaction-alerts';
@import '../institutional/compliance-details/index';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
@import '../institutional/interactive-replacement-token-notification/index';
@import '../institutional/confirm-remove-jwt-modal/index';
@import '../institutional/custody-confirm-link-modal/index';
@import '../institutional/transaction-failed-modal/index';
///: END:ONLY_INCLUDE_IN

View File

@ -12,11 +12,11 @@
@import 'convert-token-to-nft-modal/index';
@import 'contract-details-modal/index';
@import 'hold-to-reveal-modal/index';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
@import '../../institutional/confirm-remove-jwt-modal/index';
@import '../../institutional/custody-confirm-link-modal/index';
@import '../../institutional/compliance-modal/index';
@import '../../institutional/compliance-details/index';
@import '../../institutional/transaction-failed-modal/index';
///: END:ONLY_INCLUDE_IN
@import 'eth-sign-modal/index';
.modal {

View File

@ -13,8 +13,6 @@ import { mmiActionsFactory } from '../../../store/institutional/institution-back
// Modal Components
import AddNetworkModal from '../../../pages/onboarding-flow/add-network-modal';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import ComplianceDetailsModal from '../../institutional/compliance-details';
import ComplianceModal from '../../institutional/compliance-modal';
import ConfirmRemoveJWT from '../../institutional/confirm-remove-jwt-modal';
import CustodyConfirmLink from '../../institutional/custody-confirm-link-modal';
import InteractiveReplacementTokenModal from '../../institutional/interactive-replacement-token-modal';
@ -330,35 +328,6 @@ const MODALS = {
},
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
COMPLIANCE: {
contents: <ComplianceModal />,
onHide: (props) => props.hideWarning(),
mobileModalStyle: {
...modalContainerMobileStyle,
},
laptopModalStyle: {
...modalContainerLaptopStyle,
},
contentStyle: {
borderRadius: '8px',
},
},
COMPLIANCE_DETAILS: {
contents: <ComplianceDetailsModal />,
onHide: (props) => props.hideWarning(),
mobileModalStyle: {
...modalContainerMobileStyle,
},
laptopModalStyle: {
...modalContainerLaptopStyle,
},
contentStyle: {
padding: '0px',
borderRadius: '8px',
},
},
CONFIRM_REMOVE_JWT: {
contents: <ConfirmRemoveJWT />,
mobileModalStyle: {

View File

@ -108,7 +108,6 @@ export enum IconName {
People = 'people',
ProgrammingArrows = 'programming-arrows',
MmmiPortfolioDashboard = 'portfolio-dashboard',
Compliance = 'compliance',
Custody = 'custody',
Question = 'question',
Received = 'received',

View File

@ -1,36 +0,0 @@
import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import { useDispatch } from 'react-redux';
import Modal from '../../app/modal';
import { hideModal } from '../../../store/actions';
import ComplianceDetails from '../compliance-details';
import { I18nContext } from '../../../contexts/i18n';
export default function ComplianceDetailsModal({
onGenerateComplianceReport,
reportAddress,
}) {
const dispatch = useDispatch();
const handleClose = () => dispatch(hideModal);
const t = useContext(I18nContext);
return (
<Modal
headerText={t('amlCompliance')}
hideFooter="true"
onClose={handleClose}
contentClass="compliance-details-modal-content"
>
<ComplianceDetails
address={reportAddress}
onClose={handleClose}
onGenerate={onGenerateComplianceReport}
/>
</Modal>
);
}
ComplianceDetailsModal.propTypes = {
reportAddress: PropTypes.func.isRequired,
onGenerateComplianceReport: PropTypes.func.isRequired,
};

View File

@ -1,31 +0,0 @@
import React from 'react';
import { renderWithProvider } from '../../../../test/jest';
import configureStore from '../../../store/store';
import mockState from '../../../../test/data/mock-state.json';
import ComplianceDetailsModal from './compliance-details-modal';
const props = {
hideModal: jest.fn(),
onGenerateComplianceReport: jest.fn(),
reportAddress: '0xAddress',
};
const render = () => {
const store = configureStore({
...mockState,
metamask: {},
history: {
mostRecentOverviewPage: 'test',
},
});
return renderWithProvider(<ComplianceDetailsModal {...props} />, store);
};
describe('Compliance Modal', function () {
it('render correctly with the correct header', function () {
const { getByText } = render();
expect(getByText('AML/CFT Compliance')).toBeVisible();
});
});

View File

@ -1 +0,0 @@
export { default } from './compliance-details-modal';

View File

@ -1,3 +0,0 @@
.compliance-details-modal-content {
padding: 0;
}

View File

@ -1,146 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ComplianceDetails should render correctly 1`] = `
<div>
<div
class="mm-box compliance-details mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--flex-direction-column"
>
<div
class="mm-box compliance-details__row mm-box--padding-top-4 mm-box--padding-bottom-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--height-2/3"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
Address
</p>
<p
class="mm-box mm-text mm-text--body-xs mm-box--color-text-default"
>
0xAddress
</p>
</div>
<div
class="mm-box compliance-details__row mm-box--padding-top-4 mm-box--padding-bottom-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--height-2/3"
>
<div
class="mm-box mm-box--margin-bottom-1 mm-box--display-flex mm-box--align-items-center mm-box--color-text-alternative"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--margin-right-2 mm-box--color-text-default"
>
Risk rating
</p>
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div
aria-describedby="tippy-tooltip-1"
class="info-tooltip__tooltip-container"
data-original-title="null"
data-tooltipped=""
style="display: inline;"
tabindex="0"
>
<svg
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
fill="var(--color-icon-alternative)"
/>
</svg>
</div>
</div>
</div>
</div>
<div
class="mm-box compliance-row__column-risk compliance-row__column-risk--green"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
low
</p>
</div>
</div>
<div
class="mm-box compliance-details__row mm-box--padding-top-4 mm-box--padding-bottom-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--height-2/3"
>
<div
class="mm-box mm-box--display-flex mm-box--align-items-center mm-box--color-text-alternative"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--margin-right-2 mm-box--color-text-default"
>
Report last run
</p>
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div
aria-describedby="tippy-tooltip-2"
class="info-tooltip__tooltip-container"
data-original-title="null"
data-tooltipped=""
style="display: inline;"
tabindex="0"
>
<svg
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
fill="var(--color-icon-alternative)"
/>
</svg>
</div>
</div>
</div>
</div>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
/>
</div>
<div
class="mm-box"
>
<div
class="swaps-footer"
>
<div
class="swaps-footer__buttons swaps-footer__buttons--border"
>
<div
class="page-container__footer swaps-footer__custom-page-container-footer-class"
>
<footer>
<button
class="button btn--rounded btn-secondary page-container__footer-button page-container__footer-button__cancel swaps-footer__custom-page-container-footer-button-class"
data-testid="page-container-footer-cancel"
role="button"
tabindex="0"
>
Show report
</button>
<button
class="button btn--rounded btn-primary page-container__footer-button swaps-footer__custom-page-container-footer-button-class"
data-testid="page-container-footer-next"
role="button"
tabindex="0"
>
Run report
</button>
</footer>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -1,159 +0,0 @@
import React, { useContext, useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { I18nContext } from '../../../contexts/i18n';
import InfoTooltip from '../../ui/info-tooltip';
import SwapsFooter from '../../../pages/swaps/swaps-footer';
import {
fetchHistoricalReports,
getComplianceHistoricalReportsByAddress,
getComplianceTenantSubdomain,
} from '../../../ducks/institutional/institutional';
import { formatDate } from '../../../helpers/utils/util';
import { Box, Text } from '../../component-library';
import {
TextColor,
TextVariant,
JustifyContent,
AlignItems,
BlockSize,
Display,
FlexDirection,
} from '../../../helpers/constants/design-system';
const ComplianceDetails = ({ address, onClose, onGenerate }) => {
const t = useContext(I18nContext);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchHistoricalReports(address));
}, [address, dispatch]);
const [lastReport, setLastReport] = useState(null);
const historicalReports = useSelector(
getComplianceHistoricalReportsByAddress(address),
);
useEffect(() => {
if (historicalReports && historicalReports.length) {
setLastReport(
historicalReports.reduce((prev, cur) =>
prev.createTime > cur.createTime ? prev : cur,
),
);
}
}, [historicalReports]);
const complianceTenantSubdomain = useSelector(getComplianceTenantSubdomain);
return (
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
paddingLeft={4}
paddingRight={4}
className="compliance-details"
>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
justifyContent={JustifyContent.center}
height={BlockSize.TwoThirds}
paddingTop={4}
paddingBottom={4}
className="compliance-details__row"
>
<Text>{t('address')}</Text>
<Text variant={TextVariant.bodyXs}>{address}</Text>
</Box>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
justifyContent={JustifyContent.center}
height={BlockSize.TwoThirds}
paddingTop={4}
paddingBottom={4}
className="compliance-details__row"
>
<Box
display={Display.Flex}
alignItems={AlignItems.center}
marginBottom={1}
color={TextColor.textAlternative}
>
<Text marginRight={2}>{t('riskRating')}</Text>
<InfoTooltip
position="bottom"
contentText={<span>{t('riskRatingTooltip')}</span>}
/>
</Box>
<Box
className={classnames('compliance-row__column-risk', {
'compliance-row__column-risk--green': lastReport?.risk === 'low',
'compliance-row__column-risk--yellow':
lastReport?.risk === 'medium',
'compliance-row__column-risk--orange': lastReport?.risk === 'high',
'compliance-row__column-risk--red':
lastReport?.risk === 'unacceptable',
})}
>
<Text>{lastReport ? lastReport.risk : t('noReport')}</Text>
</Box>
</Box>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
justifyContent={JustifyContent.center}
height={BlockSize.TwoThirds}
paddingTop={4}
paddingBottom={4}
className="compliance-details__row"
>
<Box
display={Display.Flex}
alignItems={AlignItems.center}
color={TextColor.textAlternative}
>
<Text marginRight={2}>{t('reportLastRun')}</Text>
<InfoTooltip
position="bottom"
contentText={<span>{t('reportLastRunTooltip')}</span>}
/>
</Box>
<Text color={TextColor.textDefault}>
{lastReport
? formatDate(new Date(lastReport.createTime).getTime())
: 'N/A'}
</Text>
</Box>
<Box>
<SwapsFooter
onSubmit={() => {
onGenerate(address);
onClose();
}}
submitText={t('runReport')}
onCancel={() =>
global.platform.openTab({
url: `https://${complianceTenantSubdomain}.compliance.codefi.network/app/kyt/addresses/${lastReport.address}/${lastReport.reportId}`,
})
}
cancelText={t('showReport')}
hideCancel={!lastReport}
approveActive={lastReport}
showTopBorder
/>
</Box>
</Box>
);
};
ComplianceDetails.propTypes = {
address: PropTypes.string,
onClose: PropTypes.func,
onGenerate: PropTypes.func,
};
export default ComplianceDetails;

View File

@ -1,48 +0,0 @@
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
import testData from '../../../../.storybook/test-data';
import ComplianceDetails from '.';
const customData = {
...testData,
metamask: {
institutionalFeatures: {
complianceProjectId: '',
complianceClientId: '',
reportsInProgress: {},
historicalReports: {
'0xAddress': [
{
reportId: 'reportId',
address: '0xAddress',
risk: 'low',
creatTime: new Date(),
},
],
},
},
},
};
const store = configureStore(customData);
export default {
title: 'Components/Institutional/ComplianceDetails',
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
component: ComplianceDetails,
args: {
address: '0xAddress',
onClose: () => undefined,
onGenerate: () => undefined,
},
argTypes: {
onClick: {
action: 'onClick',
},
},
};
export const DefaultStory = (args) => <ComplianceDetails {...args} />;
DefaultStory.storyName = 'ComplianceDetails';

View File

@ -1,67 +0,0 @@
import React from 'react';
import configureStore from 'redux-mock-store';
import { fireEvent, screen } from '@testing-library/react';
import thunk from 'redux-thunk';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import ComplianceDetails from './compliance-details';
const initState = {
metamask: {
institutionalFeatures: {
complianceProjectId: '',
complianceClientId: '',
reportsInProgress: {},
historicalReports: {
'0xAddress': [
{
reportId: 'reportId',
address: '0xAddress',
risk: 'low',
creatTime: new Date(),
},
],
},
},
},
};
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
describe('ComplianceDetails', () => {
const props = {
address: '0xAddress',
onClose: jest.fn(),
onGenerate: jest.fn(),
};
const store = mockStore(initState);
it('should render correctly', () => {
const { container } = renderWithProvider(
<ComplianceDetails
address={props.address}
onClose={props.onClose}
onGenerate={props.onGenerate}
/>,
store,
);
expect(container).toMatchSnapshot();
});
it('runs onGenerate fuction', () => {
renderWithProvider(
<ComplianceDetails
address={props.address}
onClose={props.onClose}
onGenerate={props.onGenerate}
/>,
store,
);
fireEvent.click(screen.queryByTestId('page-container-footer-next'));
expect(props.onGenerate).toHaveBeenCalledTimes(1);
expect(props.onGenerate).toHaveBeenCalledWith(props.address);
});
});

View File

@ -1 +0,0 @@
export { default } from './compliance-details';

View File

@ -1,5 +0,0 @@
.compliance-details {
&__row {
border-top: 1px solid var(--color-border-muted);
}
}

View File

@ -1,93 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ComplianceModal should render the correct content 1`] = `
<div>
<div
class="modal-container"
>
<div
class="modal-container__content"
>
<div
class="mm-box"
>
<header
class="mm-box mm-box--display-flex mm-box--justify-content-space-between"
>
<div
class="mm-box mm-box--display-flex mm-box--justify-content-flex-start mm-box--align-items-center"
>
<img
alt="Codefi Compliance"
height="32"
src="images/compliance-logo-small.svg"
width="32"
/>
<h4
class="mm-box mm-text mm-text--body-md mm-box--margin-left-2 mm-box--color-text-default"
>
[codefiCompliance]
</h4>
</div>
<button
aria-label="[close]"
class="box mm-button-icon mm-button-icon--size-sm box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-default box--background-color-transparent box--rounded-lg"
data-testid="compliance-modal-close"
>
<span
class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit"
style="mask-image: url('./images/icons/close.svg');"
/>
</button>
</header>
<p
class="mm-box mm-text mm-text--body-md mm-box--padding-bottom-3 mm-box--color-text-alternative"
data-testid="compliance-info"
>
[complianceBlurb0]
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--padding-bottom-3 mm-box--color-text-alternative"
>
[complianceBlurb1]
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--padding-bottom-3 mm-box--color-text-alternative"
>
[complianceBlurpStep0]
</p>
<ol
data-testid="compliance-bullets"
>
<li>
[complianceBlurbStep1]
</li>
<li>
[complianceBlurbStep2]
</li>
<li>
[complianceBlurbStep3]
</li>
<li>
[complianceBlurbStep4]
</li>
<li>
[complianceBlurbStep5]
</li>
</ol>
</div>
</div>
<div
class="modal-container__footer"
>
<button
class="button btn--rounded btn-primary modal-container__footer-button"
role="button"
tabindex="0"
>
[openCodefiCompliance]
</button>
</div>
</div>
</div>
`;

View File

@ -1,95 +0,0 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { hideModal } from '../../../store/actions';
import Modal from '../../app/modal';
import {
ButtonIcon,
IconSize,
IconName,
Text,
Box,
} from '../../component-library';
import {
AlignItems,
JustifyContent,
TextColor,
Display,
} from '../../../helpers/constants/design-system';
const ComplianceModal = () => {
const dispatch = useDispatch();
const t = useI18nContext();
const handleSubmit = () => {
global.platform.openTab({
url: 'https://start.compliance.codefi.network/',
});
};
const handleClose = () => dispatch(hideModal());
return (
<Modal
onClose={handleClose}
onSubmit={handleSubmit}
submitText={t('openCodefiCompliance')}
submitType="primary"
>
<Box>
<Box
as="header"
display={Display.Flex}
justifyContent={JustifyContent.spaceBetween}
>
<Box
display={Display.Flex}
justifyContent={JustifyContent.flexStart}
alignItems={AlignItems.center}
>
<img
height={32}
width={32}
src="images/compliance-logo-small.svg"
alt="Codefi Compliance"
/>
<Text as="h4" marginLeft={2} color={TextColor.textDefault}>
{t('codefiCompliance')}
</Text>
</Box>
<ButtonIcon
data-testid="compliance-modal-close"
iconName={IconName.Close}
size={IconSize.Sm}
ariaLabel={t('close')}
onClick={handleClose}
/>
</Box>
<Text
data-testid="compliance-info"
paddingBottom={3}
color={TextColor.textAlternative}
>
{t('complianceBlurb0')}
</Text>
<Text paddingBottom={3} color={TextColor.textAlternative}>
{t('complianceBlurb1')}
</Text>
<Text paddingBottom={3} color={TextColor.textAlternative}>
{t('complianceBlurpStep0')}
</Text>
<ol data-testid="compliance-bullets">
<li>{t('complianceBlurbStep1')}</li>
<li>{t('complianceBlurbStep2')}</li>
<li>{t('complianceBlurbStep3')}</li>
<li>{t('complianceBlurbStep4')}</li>
<li>{t('complianceBlurbStep5')}</li>
</ol>
</Box>
</Modal>
);
};
export default ComplianceModal;

View File

@ -1,22 +0,0 @@
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
import testData from '../../../../.storybook/test-data';
import ComplianceModal from '.';
const store = configureStore(testData);
export default {
title: 'Components/Institutional/ComplianceModal',
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
component: ComplianceModal,
argTypes: {
onClick: {
action: 'onClick',
},
},
};
export const DefaultStory = (args) => <ComplianceModal {...args} />;
DefaultStory.storyName = 'ComplianceModal';

View File

@ -1,55 +0,0 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { useDispatch } from 'react-redux';
import sinon from 'sinon';
import { hideModal } from '../../../store/actions';
import ComplianceModal from '.';
jest.mock('react-redux', () => ({
useDispatch: jest.fn(),
}));
jest.mock('../../../store/actions', () => ({
hideModal: jest.fn(),
}));
describe('ComplianceModal', () => {
let dispatchMock;
beforeEach(() => {
dispatchMock = jest.fn();
useDispatch.mockReturnValue(dispatchMock);
});
afterEach(() => {
jest.resetAllMocks();
});
it('should render the correct content', () => {
const { container, getByTestId } = render(<ComplianceModal />);
expect(getByTestId('compliance-info')).toBeInTheDocument();
expect(getByTestId('compliance-bullets')).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
it('should close the modal when close button is clicked', () => {
const { getByTestId } = render(<ComplianceModal />);
fireEvent.click(getByTestId('compliance-modal-close'));
expect(hideModal).toHaveBeenCalled();
expect(dispatchMock).toHaveBeenCalledWith(hideModal());
});
it('should open the Compliance page when submit button is clicked', () => {
global.platform = { openTab: sinon.spy() };
const { container } = render(<ComplianceModal />);
const btn = container.getElementsByClassName('btn-primary')[0];
fireEvent.click(btn);
expect(global.platform.openTab.called).toBeTruthy();
});
});

View File

@ -1 +0,0 @@
export { default } from './compliance-modal';

View File

@ -1,6 +0,0 @@
.compliance-modal {
ol {
list-style: decimal;
list-style-position: inside;
}
}

View File

@ -1,92 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Compliance Settings shows disconnect when Compliance is activated 1`] = `
<div>
<div
class="mm-box"
>
<h6
class="mm-box mm-text institutional-feature__content mm-text--body-sm mm-box--color-text-default"
>
Change your settings or view reports by opening up Codefi Compliance or disconnect below.
</h6>
<div
class="mm-box institutional-feature__footer mm-box--padding-4 mm-box--sm:padding-6 mm-box--border-style-solid mm-box--border-color-border-muted mm-box--border-width-1"
>
<button
class="mm-box mm-text mm-button-base mm-button-base--size-lg mm-button-primary mm-text--body-md-medium mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-inverse mm-box--background-color-primary-default mm-box--rounded-pill"
data-testid="disconnect-compliance"
>
Disconnect
</button>
<button
class="mm-box mm-text mm-button-base mm-button-base--size-lg mm-button-link mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
data-testid="start-compliance"
>
Open Codefi Compliance
</button>
</div>
</div>
</div>
`;
exports[`Compliance Settings shows start btn when Compliance its not activated 1`] = `
<div>
<div
class="mm-box mm-box--padding-0 mm-box--sm:padding-6 mm-box--color-text-alternative"
data-testid="institutional-content"
>
<div
class="mm-box institutional-feature__content"
variant="body-sm"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--padding-bottom-3 mm-box--color-text-default"
>
DeFi raises AML/CFT risk for institutions, given the decentralised pools and pseudonymous counterparties.
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--padding-bottom-3 mm-box--color-text-default"
>
Codefi Compliance is the only product capable of running AML/CFT analysis on DeFi pools. This allows you to identify and avoid pools and counterparties that fail your risk setting.
</p>
<p
class="mm-box mm-text mm-text--body-md mm-box--padding-bottom-3 mm-box--color-text-default"
>
Steps to enable AML/CFT Compliance:
</p>
<ol>
<li>
Sign up to Codefi Compliance below
</li>
<li>
Create an organisation
</li>
<li>
Create a project
</li>
<li>
Set your compliance settings
</li>
<li>
Click the "Enable Compliance in MMI" button
</li>
</ol>
</div>
<div
class="mm-box mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-center"
>
<div
class="mm-box institutional-feature__footer mm-box--padding-4 mm-box--sm:padding-6 mm-box--border-style-solid mm-box--border-color-border-muted mm-box--border-width-1"
>
<button
class="mm-box mm-text mm-button-base mm-button-base--size-lg mm-button-link mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
data-testid="start-compliance"
>
Open Codefi Compliance
</button>
</div>
</div>
</div>
</div>
`;

View File

@ -1,114 +0,0 @@
import React, { useContext } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
JustifyContent,
Display,
TextColor,
FlexDirection,
TextVariant,
BorderStyle,
BorderColor,
} from '../../../helpers/constants/design-system';
import { I18nContext } from '../../../contexts/i18n';
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
import {
Button,
BUTTON_VARIANT,
BUTTON_SIZES,
Text,
Box,
} from '../../component-library';
const ComplianceSettings = () => {
const t = useContext(I18nContext);
const dispatch = useDispatch();
const mmiActions = mmiActionsFactory();
const complianceActivated = useSelector((state) =>
Boolean(state.metamask.institutionalFeatures?.complianceProjectId),
);
const linkButton = (
<Button
variant={BUTTON_VARIANT.LINK}
size={BUTTON_SIZES.LG}
data-testid="start-compliance"
onClick={() => {
global.platform.openTab({
url: 'https://start.compliance.codefi.network/',
});
}}
>
{t('openCodefiCompliance')}
</Button>
);
return complianceActivated ? (
<Box>
<Text
variant={TextVariant.bodySm}
as="h6"
className="institutional-feature__content"
>
{t('complianceSettingsExplanation')}
</Text>
<Box
padding={[4, 6]}
borderWidth={1}
borderStyle={BorderStyle.solid}
borderColor={BorderColor.borderMuted}
className="institutional-feature__footer"
>
<Button
size={BUTTON_SIZES.LG}
onClick={() => {
dispatch(mmiActions.deleteComplianceAuthData());
}}
data-testid="disconnect-compliance"
>
{t('disconnect')}
</Button>
{linkButton}
</Box>
</Box>
) : (
<Box
padding={[0, 6]}
color={TextColor.textAlternative}
data-testid="institutional-content"
>
<Box
variant={TextVariant.bodySm}
className="institutional-feature__content"
>
<Text paddingBottom={3}>{t('complianceBlurb0')}</Text>
<Text paddingBottom={3}>{t('complianceBlurb1')}</Text>
<Text paddingBottom={3}>{t('complianceBlurpStep0')}</Text>
<ol>
<li>{t('complianceBlurbStep1')}</li>
<li>{t('complianceBlurbStep2')}</li>
<li>{t('complianceBlurbStep3')}</li>
<li>{t('complianceBlurbStep4')}</li>
<li>{t('complianceBlurbStep5')}</li>
</ol>
</Box>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.center}
>
<Box
padding={[4, 6]}
borderWidth={1}
borderStyle={BorderStyle.solid}
borderColor={BorderColor.borderMuted}
className="institutional-feature__footer"
>
{linkButton}
</Box>
</Box>
</Box>
);
};
export default ComplianceSettings;

View File

@ -1,34 +0,0 @@
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
import testData from '../../../../.storybook/test-data';
import ComplianceSettings from '.';
const customData = {
...testData,
metamask: {
...testData.metamask,
institutionalFeatures: {
complianceProjectId: '',
complianceClientId: '',
reportsInProgress: {},
},
},
};
const store = configureStore(customData);
export default {
title: 'Components/Institutional/ComplianceSettings',
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
component: ComplianceSettings,
argTypes: {
onClick: {
action: 'onClick',
},
},
};
export const DefaultStory = (args) => <ComplianceSettings {...args} />;
DefaultStory.storyName = 'ComplianceSettings';

View File

@ -1,67 +0,0 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import ComplianceSettings from '.';
const mockedDeleteComplianceAuthData = jest
.fn()
.mockReturnValue({ type: 'TYPE' });
jest.mock('../../../store/institutional/institution-background', () => ({
mmiActionsFactory: () => ({
deleteComplianceAuthData: mockedDeleteComplianceAuthData,
}),
}));
const mockStore = {
metamask: {
providerConfig: {
type: 'test',
},
institutionalFeatures: {
complianceProjectId: '',
complianceClientId: '',
reportsInProgress: {},
},
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
},
};
describe('Compliance Settings', () => {
it('shows start btn when Compliance its not activated', () => {
const store = configureMockStore()(mockStore);
const { container, getByTestId } = renderWithProvider(
<ComplianceSettings />,
store,
);
expect(getByTestId('start-compliance')).toBeVisible();
expect(container).toMatchSnapshot();
});
it('shows disconnect when Compliance is activated', () => {
const customMockStore = {
...mockStore,
metamask: {
...mockStore.metamask,
institutionalFeatures: {
complianceProjectId: '123',
complianceClientId: '123',
reportsInProgress: {},
},
},
};
const store = configureMockStore()(customMockStore);
const { container, getByTestId } = renderWithProvider(
<ComplianceSettings />,
store,
);
expect(getByTestId('disconnect-compliance')).toBeVisible();
expect(container).toMatchSnapshot();
});
});

View File

@ -1 +0,0 @@
export { default } from './compliance-settings';

View File

@ -1,19 +0,0 @@
.institutional-feature {
&__footer {
button {
min-width: 0;
margin-right: 16px;
&:last-of-type {
margin-right: 0;
}
}
}
&__content {
ol {
list-style: decimal;
list-style-position: inside;
}
}
}

View File

@ -4,8 +4,6 @@
* This will help improve specificity and reduce the chance of
* unintended overrides.
**/
@import 'compliance-details/index';
@import 'compliance-settings/index';
@import 'jwt-dropdown/jwt-dropdown';
@import 'jwt-url-form/jwt-url-form';
@import 'note-to-trader/index';

View File

@ -60,7 +60,7 @@ const InteractiveReplacementTokenModal = () => {
trackEvent({
category: MetaMetricsEventCategory.MMI,
event: MetaMetricsEventName.ComplianceButtonClicked,
event: MetaMetricsEventName.InteractiveReplacementTokenButtonClicked,
});
};

View File

@ -1,106 +1,17 @@
import { createSelector } from 'reselect';
import { createSlice } from '@reduxjs/toolkit';
import { captureException } from '@sentry/browser';
import { mmiActionsFactory } from '../../store/institutional/institution-background';
const name = 'institutionalFeatures';
const initialState = {
historicalReports: {},
complianceProjectId: '',
complianceClientId: '',
reportsInProgress: {},
};
const initialState = {};
const slice = createSlice({
name,
initialState,
reducers: {
setHistoricalReports(state, action) {
state.historicalReports[action.payload.address] = [
...action.payload.reports,
];
},
},
});
const { actions, reducer } = slice;
const { reducer } = slice;
export default reducer;
export const getComplianceProjectId = (state) =>
state.metamask[name]?.complianceProjectId;
export const getComplianceClientId = (state) =>
state.metamask[name]?.complianceClientId;
export const getComplianceTenantSubdomain = (state) =>
state.metamask[name]?.complianceTenantSubdomain;
export const getComplianceHistoricalReports = (state) =>
state.metamask[name]?.historicalReports;
export const getComplianceReportsInProgress = (state) =>
state.metamask[name]?.reportsInProgress;
export const getInstitutionalConnectRequests = (state) =>
state.metamask[name]?.connectRequests;
export const complianceActivated = (state) =>
Boolean(state.metamask[name]?.complianceProjectId);
export const getComplianceHistoricalReportsByAddress = (address) =>
createSelector(getComplianceHistoricalReports, (reports) =>
reports ? reports[address] : [],
);
export const getComplianceReportsInProgressByAddress = (address) =>
createSelector(getComplianceReportsInProgress, (reports) =>
reports ? reports[address.toLowerCase()] : undefined,
);
export const fetchHistoricalReports = (address, testProjectId = undefined) => {
return async (dispatch, getState) => {
const state = getState();
const mmiActions = mmiActionsFactory();
let projectId;
// testProjectId is provided to make a test request, which checks if projectId is correct
if (!testProjectId) {
projectId = getComplianceProjectId(state);
if (!projectId) {
return;
}
}
try {
const result = await dispatch(
mmiActions.getComplianceHistoricalReportsByAddress(address, projectId),
);
dispatch(
mmiActions.syncReportsInProgress({
address,
historicalReports: result.items ? result.items : [],
}),
);
dispatch(
actions.setHistoricalReports({
address,
reports: result.items
? result.items.filter((report) => report.status !== 'inProgress')
: [],
}),
);
} catch (error) {
console.error(error);
captureException(error);
}
};
};
export function generateComplianceReport(address) {
return (dispatch, _getState) => {
const mmiActions = mmiActionsFactory();
dispatch(mmiActions.generateComplianceReport(address));
};
}
const { setHistoricalReports } = actions;
export { setHistoricalReports };

View File

@ -1,34 +1,9 @@
import InstitutionalReducer, {
fetchHistoricalReports,
getComplianceClientId,
getComplianceProjectId,
getComplianceTenantSubdomain,
getComplianceHistoricalReports,
getComplianceReportsInProgress,
getInstitutionalConnectRequests,
complianceActivated,
getComplianceReportsInProgressByAddress,
generateComplianceReport,
} from './institutional';
const mockSyncReportsInProgress = jest.fn();
const mockGenerateComplianceReport = jest.fn();
jest.mock('../../store/institutional/institution-background', () => ({
mmiActionsFactory: () => ({
generateComplianceReport: mockGenerateComplianceReport,
getComplianceHistoricalReportsByAddress: jest.fn(),
syncReportsInProgress: mockSyncReportsInProgress,
}),
}));
describe('Institutional Duck', () => {
const initState = {
historicalReports: {},
complianceProjectId: '',
complianceClientId: '',
reportsInProgress: {},
};
const initState = {};
describe('InstitutionalReducer', () => {
it('should initialize state', () => {
@ -39,32 +14,11 @@ describe('Institutional Duck', () => {
const state = {
metamask: {
institutionalFeatures: {
complianceProjectId: 'complianceProjectId',
complianceClientId: 'complianceClientId',
complianceTenantSubdomain: 'subdomain',
reportsInProgress: { id: [{ reportId: 'id' }] },
connectRequests: [{ id: 'id' }],
historicalReports: { id: [{ reportId: 'id' }] },
},
},
};
expect(getComplianceProjectId(state)).toBe('complianceProjectId');
expect(getComplianceClientId(state)).toBe('complianceClientId');
expect(getComplianceTenantSubdomain(state)).toBe('subdomain');
expect(getComplianceHistoricalReports(state).id[0].reportId).toBe('id');
expect(getComplianceReportsInProgress(state).id).toHaveLength(1);
expect(getInstitutionalConnectRequests(state)).toHaveLength(1);
expect(complianceActivated(state)).toBe(true);
expect(getComplianceReportsInProgressByAddress('id')(state)).toHaveLength(
1,
);
await fetchHistoricalReports('0xAddress', 'projectId')(
jest.fn().mockReturnValue({ items: [{ status: 'test' }] }),
() => state,
);
expect(mockSyncReportsInProgress).toHaveBeenCalled();
await generateComplianceReport('0xAddress')(jest.fn(), () => state);
expect(mockGenerateComplianceReport).toHaveBeenCalled();
});
});
});

View File

@ -32,9 +32,6 @@ const CONNECT_HARDWARE_ROUTE = '/new-account/connect';
const CUSTODY_ACCOUNT_ROUTE = '/new-account/custody';
const INSTITUTIONAL_FEATURES_DONE_ROUTE = '/institutional-features/done';
const CUSTODY_ACCOUNT_DONE_ROUTE = '/new-account/custody/done';
const COMPLIANCE_FEATURE_ROUTE = '/compliance-feature';
const CONFIRM_INSTITUTIONAL_FEATURE_CONNECT =
'/confirm-institutional-feature-connect';
const CONFIRM_ADD_CUSTODIAN_TOKEN = '/confirm-add-custodian-token';
const INTERACTIVE_REPLACEMENT_TOKEN_PAGE =
'/interactive-replacement-token-page';
@ -144,9 +141,6 @@ const PATH_NAME_MAP = {
[INSTITUTIONAL_FEATURES_DONE_ROUTE]: 'Institutional Features Done Page',
[CUSTODY_ACCOUNT_ROUTE]: 'Connect Custody',
[CUSTODY_ACCOUNT_DONE_ROUTE]: 'Connect Custody Account done',
[COMPLIANCE_FEATURE_ROUTE]: 'Compliance Feature Page',
[CONFIRM_INSTITUTIONAL_FEATURE_CONNECT]:
'Confirm Institutional Feature Connect',
[CONFIRM_ADD_CUSTODIAN_TOKEN]: 'Confirm Add Custodian Token',
[INTERACTIVE_REPLACEMENT_TOKEN_PAGE]: 'Interactive replacement token page',
///: END:ONLY_INCLUDE_IN
@ -237,8 +231,6 @@ export {
CUSTODY_ACCOUNT_DONE_ROUTE,
CUSTODY_ACCOUNT_ROUTE,
INSTITUTIONAL_FEATURES_DONE_ROUTE,
COMPLIANCE_FEATURE_ROUTE,
CONFIRM_INSTITUTIONAL_FEATURE_CONNECT,
CONFIRM_ADD_CUSTODIAN_TOKEN,
INTERACTIVE_REPLACEMENT_TOKEN_PAGE,
///: END:ONLY_INCLUDE_IN

View File

@ -64,7 +64,6 @@ import {
CONFIRMATION_V_NEXT_ROUTE,
ONBOARDING_SECURE_YOUR_WALLET_ROUTE,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
CONFIRM_INSTITUTIONAL_FEATURE_CONNECT,
CONFIRM_ADD_CUSTODIAN_TOKEN,
INTERACTIVE_REPLACEMENT_TOKEN_PAGE,
///: END:ONLY_INCLUDE_IN
@ -100,7 +99,7 @@ function shouldCloseNotificationPopup({
shouldCLose &&=
// MMI User must be shown a deeplink
!waitForConfirmDeepLinkDialog &&
// MMI User is connecting to custodian or compliance
// MMI User is connecting to custodian
institutionalConnectRequests.length === 0;
///: END:ONLY_INCLUDE_IN
@ -247,12 +246,6 @@ export default class Home extends PureComponent {
) {
history.push(CONFIRM_ADD_CUSTODIAN_TOKEN);
}
} else if (
institutionalConnectRequests &&
institutionalConnectRequests.length > 0 &&
institutionalConnectRequests[0].feature !== 'custodian'
) {
history.push(CONFIRM_INSTITUTIONAL_FEATURE_CONNECT);
}
}

View File

@ -1,95 +0,0 @@
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { I18nContext } from '../../../contexts/i18n';
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes';
import {
JustifyContent,
DISPLAY,
AlignItems,
TextColor,
TEXT_ALIGN,
BackgroundColor,
Color,
FLEX_DIRECTION,
} from '../../../helpers/constants/design-system';
import {
ButtonIcon,
ButtonIconSize,
IconName,
} from '../../../components/component-library';
import { Text } from '../../../components/component-library/text/deprecated';
import Box from '../../../components/ui/box';
import ComplianceSettings from '../../../components/institutional/compliance-settings';
const ComplianceFeaturePage = () => {
const t = useContext(I18nContext);
const history = useHistory();
const complianceActivated = useSelector((state) =>
Boolean(state.metamask.institutionalFeatures?.complianceProjectId),
);
return (
<Box
className="institutional-entity"
backgroundColor={BackgroundColor.backgroundDefault}
>
<>
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
padding={[0, 6, 6]}
className="feature-connect__header"
>
<ButtonIcon
ariaLabel={t('back')}
iconName={IconName.ArrowLeft}
size={ButtonIconSize.Sm}
className="settings-page__back-button"
color={Color.iconDefault}
onClick={() => history.push(DEFAULT_ROUTE)}
display={[DISPLAY.FLEX]}
/>
<Text
as="h4"
marginTop={4}
marginBottom={4}
className="feature-connect__header__title"
>
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
color={TextColor.textDefault}
>
<img
className="feature-connect__list__list-item__img"
src="images/compliance-logo-small.svg"
alt="Codefi Compliance"
/>
{t('codefiCompliance')}
{complianceActivated && (
<Text
as="h6"
margin={[2, 2, 0, 2]}
color={TextColor.textMuted}
display={DISPLAY.FLEX}
textAlign={TEXT_ALIGN.LEFT}
flexDirection={FLEX_DIRECTION.COLUMN}
justifyContent={JustifyContent.center}
className="feature-connect__label__text feature-connect__label__text--activated"
data-testid="activated-label"
>
{t('activated')}
</Text>
)}
</Box>
</Text>
</Box>
<ComplianceSettings />
</>
</Box>
);
};
export default ComplianceFeaturePage;

View File

@ -1,35 +0,0 @@
import React from 'react';
import { Provider } from 'react-redux';
import { action } from '@storybook/addon-actions';
import configureStore from '../../../store/store';
import testData from '../../../../.storybook/test-data';
import ComplianceFeaturePage from '.';
const customData = {
...testData,
metamask: {
...testData.metamask,
institutionalFeatures: {
complianceProjectId: '',
complianceClientId: '',
reportsInProgress: {},
},
},
};
const store = configureStore(customData);
export default {
title: 'Pages/Institutional/ComplianceFeaturePage',
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
component: ComplianceFeaturePage,
args: {
onClick: () => {
action('onClick');
},
},
};
export const DefaultStory = (args) => <ComplianceFeaturePage {...args} />;
DefaultStory.storyName = 'ComplianceFeaturePage';

View File

@ -1,107 +0,0 @@
import React from 'react';
import sinon from 'sinon';
import configureMockStore from 'redux-mock-store';
import { fireEvent, waitFor } from '@testing-library/react';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import ComplianceFeaturePage from '.';
const mockedDeleteComplianceAuthData = jest
.fn()
.mockReturnValue({ type: 'TYPE' });
jest.mock('../../../store/institutional/institution-background', () => ({
mmiActionsFactory: () => ({
deleteComplianceAuthData: mockedDeleteComplianceAuthData,
}),
}));
describe('Compliance Feature, connect', function () {
const mockStore = {
metamask: {
providerConfig: {
type: 'test',
},
institutionalFeatures: {
complianceProjectId: '',
},
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
},
};
it('shows compliance feature button as activated', () => {
const customMockStore = {
...mockStore,
metamask: {
...mockStore.metamask,
institutionalFeatures: {
complianceProjectId: '123',
complianceClientId: '123',
reportsInProgress: {},
},
},
};
const store = configureMockStore()(customMockStore);
const { getByText, getByTestId } = renderWithProvider(
<ComplianceFeaturePage />,
store,
);
expect(getByTestId('activated-label')).toBeVisible();
expect(getByText('Active')).toBeInTheDocument();
});
it('shows ComplianceSettings when feature is not activated', () => {
const store = configureMockStore()(mockStore);
const { getByTestId } = renderWithProvider(
<ComplianceFeaturePage />,
store,
);
expect(getByTestId('institutional-content')).toBeVisible();
});
it('opens new tab on Open Codefi Compliance click', async () => {
global.platform = { openTab: sinon.spy() };
const store = configureMockStore()(mockStore);
const { queryByTestId } = renderWithProvider(
<ComplianceFeaturePage />,
store,
);
const startBtn = queryByTestId('start-compliance');
fireEvent.click(startBtn);
await waitFor(() => {
expect(global.platform.openTab.calledOnce).toStrictEqual(true);
});
});
it('calls deleteComplianceAuthData on disconnect click', async () => {
const customMockStore = {
...mockStore,
metamask: {
...mockStore.metamask,
institutionalFeatures: {
complianceProjectId: '123',
complianceClientId: '123',
reportsInProgress: {},
},
},
};
const store = configureMockStore()(customMockStore);
const { getByTestId } = renderWithProvider(
<ComplianceFeaturePage />,
store,
);
const disconnectBtn = getByTestId('disconnect-compliance');
fireEvent.click(disconnectBtn);
expect(mockedDeleteComplianceAuthData).toHaveBeenCalled();
});
});

View File

@ -1 +0,0 @@
export { default } from './compliance-feature-page';

View File

@ -1,29 +0,0 @@
.institutional-entity {
box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.08);
}
.feature-connect {
&__list {
&__list-item {
&__img {
height: 32px;
width: 32px;
margin: 0 10px 0 0;
}
}
}
&__label {
&__text {
font-size: 0.6rem;
&--activated {
color: var(--color-text-default);
background: var(--color-primary-default);
padding: 3px 10px;
border-radius: 10px;
margin-top: 0;
}
}
}
}

View File

@ -30,10 +30,7 @@ import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import {
complianceActivated,
getInstitutionalConnectRequests,
} from '../../../ducks/institutional/institutional';
import { getInstitutionalConnectRequests } from '../../../ducks/institutional/institutional';
const ConfirmAddCustodianToken = () => {
const t = useContext(I18nContext);
@ -44,7 +41,6 @@ const ConfirmAddCustodianToken = () => {
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
const connectRequests = useSelector(getInstitutionalConnectRequests, isEqual);
const isComplianceActivated = useSelector(complianceActivated);
const [showMore, setShowMore] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [connectError, setConnectError] = useState('');
@ -160,13 +156,11 @@ const ConfirmAddCustodianToken = () => {
)}
</Box>
{!isComplianceActivated && (
<Box marginTop={4} data-testid="connect-custodian-token-error">
<Text data-testid="error-message" textAlign={TextAlign.Center}>
{connectError}
</Text>
</Box>
)}
<Box marginTop={4} data-testid="connect-custodian-token-error">
<Text data-testid="error-message" textAlign={TextAlign.Center}>
{connectError}
</Text>
</Box>
<Box as="footer" className="page-container__footer" padding={4}>
{isLoading ? (

View File

@ -9,7 +9,6 @@ const customData = {
metamask: {
...testData.metamask,
institutionalFeatures: {
complianceProjectId: '',
connectRequests: [
{
labels: [

View File

@ -24,7 +24,6 @@ describe('Confirm Add Custodian Token', () => {
useNativeCurrencyAsPrimaryCurrency: true,
},
institutionalFeatures: {
complianceProjectId: '',
connectRequests: [
{
labels: [
@ -78,7 +77,6 @@ describe('Confirm Add Custodian Token', () => {
useNativeCurrencyAsPrimaryCurrency: true,
},
institutionalFeatures: {
complianceProjectId: '',
connectRequests: [
{
labels: [

View File

@ -1,69 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Confirm Add Institutional Feature opens confirm institutional sucessfully 1`] = `
<div>
<div
class="box page-container box--flex-direction-row"
data-testid="confirm-add-institutional-feature"
>
<div
class="box page-container__header box--flex-direction-row"
>
<p
class="box mm-text page-container__title mm-text--body-md box--flex-direction-row box--color-text-default"
>
Institutional Features
</p>
<p
class="box mm-text page-container__subtitle mm-text--body-md box--flex-direction-row box--color-text-default"
>
The page at origin would like to authorise the following projects compliance settings in MetaMask Institutional
</p>
</div>
<div
class="box page-container__content box--flex-direction-row"
>
<p
class="box mm-text mm-text--body-sm box--margin-top-3 box--margin-right-8 box--margin-left-8 box--flex-direction-row box--color-text-default"
>
Project Name
</p>
<p
class="box mm-text mm-text--body-lg-medium mm-text--overflow-wrap-break-word box--margin-top-1 box--margin-right-8 box--margin-bottom-1 box--margin-left-8 box--flex-direction-row box--color-text-default"
>
projectName
</p>
<p
class="box mm-text mm-text--body-xs mm-text--overflow-wrap-break-word box--margin-right-8 box--margin-left-8 box--flex-direction-row box--color-text-muted"
>
Id
:
projectId
</p>
</div>
<footer
class="box page-container__footer box--padding-4 box--flex-direction-row"
>
<div
class="box box--display-flex box--gap-4 box--flex-direction-row"
>
<button
class="button btn--rounded btn-secondary"
role="button"
tabindex="0"
>
Cancel
</button>
<button
class="button btn--rounded btn-primary"
role="button"
tabindex="0"
>
Confirm
</button>
</div>
</footer>
</div>
</div>
`;

View File

@ -1,217 +0,0 @@
import React, { useState, useContext, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import { useI18nContext } from '../../../hooks/useI18nContext';
import Button from '../../../components/ui/button';
import PulseLoader from '../../../components/ui/pulse-loader';
import { INSTITUTIONAL_FEATURES_DONE_ROUTE } from '../../../helpers/constants/routes';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
import {
BUTTON_SIZES,
BUTTON_VARIANT,
} from '../../../components/component-library';
import { Text } from '../../../components/component-library/text/deprecated';
import {
TextColor,
TextVariant,
OVERFLOW_WRAP,
TEXT_ALIGN,
DISPLAY,
} from '../../../helpers/constants/design-system';
import Box from '../../../components/ui/box';
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
export default function ConfirmAddInstitutionalFeature({ history }) {
const t = useI18nContext();
const dispatch = useDispatch();
const mmiActions = mmiActionsFactory();
const [isLoading, setIsLoading] = useState(false);
const [connectError, setConnectError] = useState('');
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
const connectRequests = useSelector(
(state) => state.metamask.institutionalFeatures?.connectRequests,
);
const trackEvent = useContext(MetaMetricsContext);
const connectRequest = connectRequests[0];
useEffect(() => {
if (!connectRequest) {
history.push(mostRecentOverviewPage);
}
}, [connectRequest, history, mostRecentOverviewPage]);
if (!connectRequest) {
return null;
}
const serviceLabel = connectRequest.labels.find(
(label) => label.key === 'service',
);
const sendEvent = ({ actions, service }) => {
trackEvent({
category: MetaMetricsEventCategory.MMI,
event: MetaMetricsEventName.InstitutionalFeatureConnected,
properties: {
actions,
service,
},
});
};
const handleConnectError = ({ message }) => {
let error = message;
if (message.startsWith('401')) {
error = t('projectIdInvalid');
}
if (!error) {
error = t('connectionError');
}
setIsLoading(false);
setConnectError(error);
sendEvent({ actions: 'Institutional feature RPC error' });
};
const removeConnectInstitutionalFeature = ({ actions, service, push }) => {
dispatch(
mmiActions.removeConnectInstitutionalFeature({
origin: connectRequest.origin,
projectId: connectRequest.token.projectId,
}),
);
sendEvent({ actions, service });
history.push(push);
};
const confirmAddInstitutionalFeature = async () => {
setIsLoading(true);
setConnectError('');
try {
await dispatch(
mmiActions.setComplianceAuthData({
clientId: connectRequest.token.clientId,
projectId: connectRequest.token.projectId,
}),
);
removeConnectInstitutionalFeature({
actions: 'Institutional feature RPC confirm',
service: serviceLabel.value,
push: {
pathname: INSTITUTIONAL_FEATURES_DONE_ROUTE,
state: {
imgSrc: 'images/compliance-logo.png',
title: t('complianceActivatedTitle'),
description: t('complianceActivatedDesc'),
},
},
});
} catch (e) {
handleConnectError(e);
}
};
sendEvent({
actions: 'Institutional feature RPC request',
service: serviceLabel.value,
});
return (
<Box
className="page-container"
data-testid="confirm-add-institutional-feature"
>
<Box className="page-container__header">
<Text className="page-container__title">
{t('institutionalFeatures')}
</Text>
<Text className="page-container__subtitle">
{t('mmiAuthenticate', [connectRequest.origin, serviceLabel.value])}
</Text>
</Box>
<Box className="page-container__content">
<Text
variant={TextVariant.bodySm}
marginTop={3}
marginRight={8}
marginBottom={0}
marginLeft={8}
>
{t('projectName')}
</Text>
<Text
variant={TextVariant.bodyLgMedium}
color={TextColor.textDefault}
marginTop={1}
marginRight={8}
marginBottom={1}
marginLeft={8}
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
>
{connectRequest?.token?.projectName}
</Text>
<Text
variant={TextVariant.bodyXs}
marginRight={8}
marginLeft={8}
overflowWrap={OVERFLOW_WRAP.BREAK_WORD}
color={TextColor.textMuted}
>
{t('id')}: {connectRequest?.token?.projectId}
</Text>
</Box>
{connectError && (
<Text
textAlign={TEXT_ALIGN.CENTER}
marginTop={4}
data-testid="connect-error-message"
>
{connectError}
</Text>
)}
<Box as="footer" className="page-container__footer" padding={4}>
{isLoading ? (
<PulseLoader />
) : (
<Box display={DISPLAY.FLEX} gap={4}>
<Button
block
type={BUTTON_VARIANT.SECONDARY}
size={BUTTON_SIZES.LG}
onClick={() => {
removeConnectInstitutionalFeature({
actions: 'Institutional feature RPC cancel',
service: serviceLabel.value,
push: mostRecentOverviewPage,
});
}}
>
{t('cancel')}
</Button>
<Button
type="primary"
block
size={BUTTON_SIZES.LG}
onClick={confirmAddInstitutionalFeature}
>
{t('confirm')}
</Button>
</Box>
)}
</Box>
</Box>
);
}
ConfirmAddInstitutionalFeature.propTypes = {
history: PropTypes.object,
};

View File

@ -1,57 +0,0 @@
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
import testData from '../../../../.storybook/test-data';
import ConfirmAddInstitutionalFeature from '.';
const customData = {
...testData,
metamask: {
providerConfig: {
type: 'test',
},
institutionalFeatures: {
complianceProjectId: '',
connectRequests: [
{
labels: [
{
key: 'service',
value: 'test',
},
],
origin: 'origin',
token: {
projectName: 'projectName',
projectId: 'projectId',
clientId: 'clientId',
},
},
],
},
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
},
};
const store = configureStore(customData);
export default {
title: 'Pages/Institutional/ConfirmAddInstitutionalFeature',
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
component: ConfirmAddInstitutionalFeature,
args: {
history: {
push: () => {
/**/
},
},
},
};
export const DefaultStory = (args) => (
<ConfirmAddInstitutionalFeature {...args} />
);
DefaultStory.storyName = 'ConfirmAddInstitutionalFeature';

View File

@ -1,122 +0,0 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import mockState from '../../../../test/data/mock-state.json';
import ConfirmAddInstitutionalFeature from '.';
const mockRemoveConnectInstitutionalFeature = jest
.fn()
.mockReturnValue({ type: 'TYPE' });
let mockSetComplianceAuthData = jest.fn().mockReturnValue({ type: 'TYPE' });
jest.mock('../../../store/institutional/institution-background', () => ({
mmiActionsFactory: () => ({
setComplianceAuthData: mockSetComplianceAuthData,
removeConnectInstitutionalFeature: mockRemoveConnectInstitutionalFeature,
}),
}));
const connectRequests = [
{
labels: [
{
key: 'service',
value: 'test',
},
],
origin: 'origin',
token: {
projectName: 'projectName',
projectId: 'projectId',
clientId: 'clientId',
},
},
];
const props = {
history: {
push: jest.fn(),
},
};
const render = ({ newState } = {}) => {
const state = {
...mockState,
metamask: {
providerConfig: {
type: 'test',
},
institutionalFeatures: {
complianceProjectId: '',
connectRequests,
},
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
...newState,
},
};
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore(state);
return renderWithProvider(
<ConfirmAddInstitutionalFeature {...props} />,
store,
);
};
describe('Confirm Add Institutional Feature', function () {
it('opens confirm institutional sucessfully', () => {
const { container } = render();
expect(container).toMatchSnapshot();
expect(
screen.getByText(`Id: ${connectRequests[0].token.projectId}`),
).toBeInTheDocument();
});
it('runs removeConnectInstitutionalFeature on cancel click', () => {
render();
fireEvent.click(screen.queryByText('Cancel'));
expect(mockRemoveConnectInstitutionalFeature).toHaveBeenCalledTimes(1);
expect(mockRemoveConnectInstitutionalFeature).toHaveBeenCalledWith({
origin: connectRequests[0].origin,
projectId: connectRequests[0].token.projectId,
});
expect(props.history.push).toHaveBeenCalledTimes(1);
});
it('runs setComplianceAuthData on confirm click', () => {
render();
fireEvent.click(screen.queryByText('Confirm'));
expect(mockSetComplianceAuthData).toHaveBeenCalledTimes(1);
expect(mockSetComplianceAuthData).toHaveBeenCalledWith({
clientId: connectRequests[0].token.clientId,
projectId: connectRequests[0].token.projectId,
});
});
it('handles error', () => {
mockSetComplianceAuthData = jest
.fn()
.mockReturnValue(new Error('Async error message'));
const { queryByTestId } = render();
fireEvent.click(screen.queryByText('Confirm'));
expect(queryByTestId('connect-error-message')).toBeInTheDocument();
});
it('does not render without connectRequest', () => {
const newState = {
institutionalFeatures: {
connectRequests: [],
},
};
const { queryByTestId } = render({ newState });
expect(
queryByTestId('confirm-add-institutional-feature'),
).not.toBeInTheDocument();
});
});

View File

@ -1,3 +0,0 @@
import ConfirmAddInstitutionalFeature from './confirm-add-institutional-feature';
export default ConfirmAddInstitutionalFeature;

View File

@ -30,7 +30,6 @@ const customData = {
],
},
institutionalFeatures: {
complianceProjectId: '',
connectRequests: [
{
labels: [

View File

@ -70,11 +70,6 @@ const connectRequests = [
labels,
origin: 'origin',
apiUrl: 'apiUrl',
token: {
projectName: 'projectName',
projectId: 'projectId',
clientId: 'clientId',
},
},
];
@ -109,7 +104,6 @@ const render = ({ newState } = {}) => {
],
},
institutionalFeatures: {
complianceProjectId: '',
connectRequests,
},
...newState,

View File

@ -13,13 +13,12 @@
@import 'connected-accounts/index';
@import 'connected-sites/index';
@import 'create-account/connect-hardware/index';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
@import "institutional/connect-custody/index";
@import "institutional/institutional-entity-done-page/index";
@import "institutional/compliance-feature-page/index";
@import "institutional/confirm-add-custodian-token/index";
@import "institutional/interactive-replacement-token-page/index";
@import 'institutional/interactive-replacement-token-page/index';
@import 'institutional/confirm-add-custodian-token/index';
///: END:ONLY_INCLUDE_IN
@import 'error/index';
@import 'send/gas-display/index';
@import 'home/index';

View File

@ -51,10 +51,8 @@ import DesktopErrorPage from '../desktop-error';
import DesktopPairingPage from '../desktop-pairing';
///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import ComplianceFeaturePage from '../institutional/compliance-feature-page';
import InstitutionalEntityDonePage from '../institutional/institutional-entity-done-page';
import InteractiveReplacementTokenNotification from '../../components/institutional/interactive-replacement-token-notification';
import ConfirmAddInstitutionalFeature from '../institutional/confirm-add-institutional-feature';
import ConfirmAddCustodianToken from '../institutional/confirm-add-custodian-token';
import InteractiveReplacementTokenPage from '../institutional/interactive-replacement-token-page';
import CustodyPage from '../institutional/custody';
@ -83,10 +81,8 @@ import {
ONBOARDING_UNLOCK_ROUTE,
TOKEN_DETAILS,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
COMPLIANCE_FEATURE_ROUTE,
INSTITUTIONAL_FEATURES_DONE_ROUTE,
CUSTODY_ACCOUNT_DONE_ROUTE,
CONFIRM_INSTITUTIONAL_FEATURE_CONNECT,
CONFIRM_ADD_CUSTODIAN_TOKEN,
INTERACTIVE_REPLACEMENT_TOKEN_PAGE,
CUSTODY_ACCOUNT_ROUTE,
@ -327,14 +323,6 @@ export default class Routes extends Component {
component={InteractiveReplacementTokenPage}
exact
/>
<Authenticated
path={COMPLIANCE_FEATURE_ROUTE}
component={ComplianceFeaturePage}
/>
<Authenticated
path={CONFIRM_INSTITUTIONAL_FEATURE_CONNECT}
component={ConfirmAddInstitutionalFeature}
/>
<Authenticated
path={CONFIRM_ADD_CUSTODIAN_TOKEN}
component={ConfirmAddCustodianToken}

View File

@ -12,12 +12,6 @@ describe('Institution Actions', () => {
getCustodianSignMessageDeepLink: jest.fn(),
getCustodianToken: jest.fn(),
getCustodianJWTList: jest.fn(),
setComplianceAuthData: jest.fn(),
deleteComplianceAuthData: jest.fn(),
generateComplianceReport: jest.fn(),
getComplianceHistoricalReportsByAddress: jest.fn(),
syncReportsInProgress: jest.fn(),
removeConnectInstitutionalFeature: jest.fn(),
removeAddTokenConnectRequest: jest.fn(),
setCustodianConnectRequest: jest.fn(),
getCustodianConnectRequest: jest.fn(),
@ -78,24 +72,6 @@ describe('Institution Actions', () => {
custodianType: 'custodianType',
token: 'token',
});
mmiActions.setComplianceAuthData({
clientId: 'id',
projectId: 'projectId',
});
mmiActions.deleteComplianceAuthData();
mmiActions.generateComplianceReport('0xAddress');
mmiActions.getComplianceHistoricalReportsByAddress(
'0xAddress',
'projectId',
);
mmiActions.syncReportsInProgress({
address: '0xAddress',
historicalReports: [],
});
mmiActions.removeConnectInstitutionalFeature({
origin: 'origin',
projectId: 'projectId',
});
mmiActions.removeAddTokenConnectRequest({
origin: 'origin',
apiUrl: 'https://jupiter-custody.codefi.network',

View File

@ -189,24 +189,6 @@ export function mmiActionsFactory() {
'setWaitForConfirmDeepLinkDialog',
waitForConfirmDeepLinkDialog,
),
setComplianceAuthData: (clientId: string, projectId: string) =>
createAsyncAction('setComplianceAuthData', [{ clientId, projectId }]),
deleteComplianceAuthData: () =>
createAsyncAction('deleteComplianceAuthData', []),
generateComplianceReport: (address: string) =>
createAction('generateComplianceReport', address),
getComplianceHistoricalReportsByAddress: (
address: string,
projectId: string,
) =>
createAsyncAction('getComplianceHistoricalReportsByAddress', [
address,
projectId,
]),
syncReportsInProgress: (address: string, historicalReports: []) =>
createAction('syncReportsInProgress', { address, historicalReports }),
removeConnectInstitutionalFeature: (origin: string, projectId: string) =>
createAction('removeConnectInstitutionalFeature', { origin, projectId }),
removeAddTokenConnectRequest: ({
origin,
apiUrl,