[FLASK] Snaps Insight (#15814)

Co-authored-by: Frederik Bolding <frederik.bolding@gmail.com>
Co-authored-by: Hassan Malik <41640681+hmalik88@users.noreply.github.com>
feature/default_network_editable
Guillaume Roux 2 years ago committed by GitHub
parent 8b5630025b
commit c9dc59ea2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      app/_locales/en/messages.json
  2. 4
      app/scripts/metamask-controller.js
  3. 1
      ui/components/app/app-components.scss
  4. 35
      ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
  5. 18
      ui/components/app/confirm-page-container/confirm-page-container-content/index.scss
  6. 9
      ui/components/app/confirm-page-container/confirm-page-container.component.js
  7. 1
      ui/components/app/confirm-page-container/flask/index.js
  8. 3
      ui/components/app/confirm-page-container/flask/index.scss
  9. 111
      ui/components/app/confirm-page-container/flask/snap-insight.js
  10. 3
      ui/components/app/confirm-page-container/index.js
  11. 3
      ui/components/app/confirm-page-container/index.scss
  12. 1
      ui/components/app/flask/snap-content-footer/index.js
  13. 13
      ui/components/app/flask/snap-content-footer/index.scss
  14. 56
      ui/components/app/flask/snap-content-footer/snap-content-footer.js
  15. 17
      ui/components/app/flask/snap-content-footer/snap-content-footer.stories.js
  16. 63
      ui/components/ui/tabs/dropdown-tab/dropdown-tab.js
  17. 3
      ui/components/ui/tabs/dropdown-tab/index.js
  18. 23
      ui/components/ui/tabs/dropdown-tab/index.scss
  19. 3
      ui/components/ui/tabs/index.js
  20. 1
      ui/components/ui/tabs/index.scss
  21. 9
      ui/components/ui/tabs/tabs.stories.js
  22. 5
      ui/helpers/utils/permission.js
  23. 37
      ui/hooks/flask/useTransactionInsightSnap.js
  24. 88
      ui/pages/confirm-transaction-base/confirm-transaction-base.component.js
  25. 10
      ui/pages/confirm-transaction-base/confirm-transaction-base.container.js
  26. 14
      ui/selectors/selectors.js
  27. 4
      ui/store/actions.js

@ -2684,6 +2684,10 @@
"message": "Show notifications.",
"description": "The description for the `snap_notify` permission"
},
"permission_transactionInsight": {
"message": "Fetch and display transaction insights.",
"description": "The description for the `endowment:transaction-insight` permission"
},
"permission_unknown": {
"message": "Unknown permission: $1",
"description": "$1 is the name of a requested permission that is not recognized."
@ -3239,6 +3243,10 @@
"message": "Added on $1 from $2",
"description": "$1 represents the date the snap was installed, $2 represents which origin installed the snap."
},
"snapContent": {
"message": "This content is coming from $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
},
"snapError": {
"message": "Snap Error: '$1'. Error Code: '$2'",
"description": "This is shown when a snap encounters an error. $1 is the error message from the snap, and $2 is the error code."
@ -3269,6 +3277,12 @@
"snaps": {
"message": "Snaps"
},
"snapsInsightLoading": {
"message": "Loading transaction insight..."
},
"snapsNoInsight": {
"message": "The snap didn't return any insight"
},
"snapsSettingsDescription": {
"message": "Manage your Snaps"
},

@ -1867,6 +1867,10 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger,
'SnapController:remove',
),
handleSnapRequest: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:handleRequest',
),
dismissNotifications: this.dismissNotifications.bind(this),
markNotificationsAsRead: this.markNotificationsAsRead.bind(this),
///: END:ONLY_INCLUDE_IN

@ -32,6 +32,7 @@
@import 'edit-gas-fee-popover/edit-gas-tooltip/index';
@import 'flask/experimental-area/index';
@import 'flask/snaps-authorship-pill/index';
@import 'flask/snap-content-footer/index';
@import 'flask/snap-install-warning/index';
@import 'flask/snap-remove-warning/index';
@import 'flask/snap-settings-card/index';

@ -23,6 +23,9 @@ export default class ConfirmPageContainerContent extends Component {
dataComponent: PropTypes.node,
dataHexComponent: PropTypes.node,
detailsComponent: PropTypes.node,
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightComponent: PropTypes.node,
///: END:ONLY_INCLUDE_IN
errorKey: PropTypes.string,
errorMessage: PropTypes.string,
hideSubtitle: PropTypes.bool,
@ -59,15 +62,37 @@ export default class ConfirmPageContainerContent extends Component {
renderContent() {
const { detailsComponent, dataComponent } = this.props;
///: BEGIN:ONLY_INCLUDE_IN(flask)
const { insightComponent } = this.props;
if (insightComponent && (detailsComponent || dataComponent)) {
return this.renderTabs();
}
///: END:ONLY_INCLUDE_IN
if (detailsComponent && dataComponent) {
return this.renderTabs();
}
return detailsComponent || dataComponent;
return (
detailsComponent ||
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightComponent ||
///: END:ONLY_INCLUDE_IN
dataComponent
);
}
renderTabs() {
const { t } = this.context;
const { detailsComponent, dataComponent, dataHexComponent } = this.props;
const {
detailsComponent,
dataComponent,
dataHexComponent,
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightComponent,
///: END:ONLY_INCLUDE_IN
} = this.props;
return (
<Tabs>
@ -88,6 +113,12 @@ export default class ConfirmPageContainerContent extends Component {
{dataHexComponent}
</Tab>
)}
{
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightComponent
///: END:ONLY_INCLUDE_IN
}
</Tabs>
);
}

@ -82,12 +82,28 @@
color: var(--color-text-alternative);
text-transform: uppercase;
margin: 0 8px;
& button {
font-size: unset;
color: var(--color-text-alternative);
text-transform: uppercase;
max-width: 170px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
& .dropdown__select {
color: var(--color-text-alternative);
text-transform: uppercase;
option {
text-transform: none;
}
}
& .dropdown__icon-caret-down {
top: 40%;
}
}

@ -64,6 +64,9 @@ export default class ConfirmPageContainer extends Component {
dataComponent: PropTypes.node,
dataHexComponent: PropTypes.node,
detailsComponent: PropTypes.node,
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightComponent: PropTypes.node,
///: END:ONLY_INCLUDE_IN
tokenAddress: PropTypes.string,
nonce: PropTypes.string,
warning: PropTypes.string,
@ -155,6 +158,9 @@ export default class ConfirmPageContainer extends Component {
isBuyableChain,
networkIdentifier,
setApproveForAllArg,
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightComponent,
///: END:ONLY_INCLUDE_IN
} = this.props;
const showAddToAddressDialog =
@ -242,6 +248,9 @@ export default class ConfirmPageContainer extends Component {
detailsComponent={detailsComponent}
dataComponent={dataComponent}
dataHexComponent={dataHexComponent}
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightComponent={insightComponent}
///: END:ONLY_INCLUDE_IN
errorMessage={errorMessage}
errorKey={errorKey}
tokenAddress={tokenAddress}

@ -0,0 +1 @@
export { SnapInsight } from './snap-insight';

@ -0,0 +1,3 @@
.snap-insight {
word-wrap: break-word;
}

@ -0,0 +1,111 @@
import React from 'react';
import PropTypes from 'prop-types';
import Preloader from '../../../ui/icon/preloader/preloader-icon.component';
import Typography from '../../../ui/typography/typography';
import {
ALIGN_ITEMS,
COLORS,
FLEX_DIRECTION,
JUSTIFY_CONTENT,
TEXT_ALIGN,
TYPOGRAPHY,
} from '../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { useTransactionInsightSnap } from '../../../../hooks/flask/useTransactionInsightSnap';
import SnapContentFooter from '../../flask/snap-content-footer/snap-content-footer';
import Box from '../../../ui/box/box';
export const SnapInsight = ({ transaction, chainId, selectedSnap }) => {
const t = useI18nContext();
const response = useTransactionInsightSnap({
transaction,
chainId,
snapId: selectedSnap.id,
});
const data = response?.insights;
const hasNoData = !data || !Object.keys(data).length;
return (
<Box
flexDirection={FLEX_DIRECTION.COLUMN}
height="full"
marginTop={hasNoData && 12}
marginBottom={hasNoData && 12}
alignItems={hasNoData && ALIGN_ITEMS.CENTER}
justifyContent={hasNoData && JUSTIFY_CONTENT.CENTER}
textAlign={hasNoData && TEXT_ALIGN.CENTER}
className="snap-insight"
>
{data ? (
<Box
height="full"
flexDirection={FLEX_DIRECTION.COLUMN}
className="snap-insight__container"
>
{Object.keys(data).length ? (
<>
<Box
flexDirection={FLEX_DIRECTION.COLUMN}
paddingTop={0}
paddingRight={6}
paddingBottom={3}
paddingLeft={6}
className="snap-insight__container__data"
>
{Object.keys(data).map((key, i) => (
<div key={i}>
<Typography
fontWeight="bold"
marginTop={3}
variant={TYPOGRAPHY.H6}
>
{key}
</Typography>
<Typography variant={TYPOGRAPHY.H6}>{data[key]}</Typography>
</div>
))}
</Box>
<SnapContentFooter
snapName={selectedSnap.manifest.proposedName}
snapId={selectedSnap.id}
/>
</>
) : (
<Typography color={COLORS.TEXT_ALTERNATIVE} variant={TYPOGRAPHY.H6}>
{t('snapsNoInsight')}
</Typography>
)}
</Box>
) : (
<>
<Preloader size={40} />
<Typography
marginTop={3}
color={COLORS.TEXT_ALTERNATIVE}
variant={TYPOGRAPHY.H6}
>
{t('snapsInsightLoading')}
</Typography>
</>
)}
</Box>
);
};
SnapInsight.propTypes = {
/*
* The transaction data object
*/
transaction: PropTypes.object,
/*
* CAIP2 Chain ID
*/
chainId: PropTypes.string,
/*
* The insight snap selected
*/
selectedSnap: PropTypes.object,
};

@ -7,3 +7,6 @@ export {
default as ConfirmPageContainerContent,
ConfirmPageContainerSummary,
} from './confirm-page-container-content';
///: BEGIN:ONLY_INCLUDE_IN(flask)
export { SnapInsight } from './flask/snap-insight';
///: END:ONLY_INCLUDE_IN

@ -2,6 +2,9 @@
@import 'confirm-page-container-header/index';
@import 'confirm-detail-row/index';
@import 'confirm-page-container-navigation/index';
///: BEGIN:ONLY_INCLUDE_IN(flask)
@import 'flask/index';
///: END:ONLY_INCLUDE_IN
.confirm-page-container {
&__dialog {

@ -0,0 +1 @@
export { default } from './snap-content-footer';

@ -0,0 +1,13 @@
.snap-content-footer {
i {
color: var(--color-icon-muted);
padding-right: 4px;
}
.button {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100px;
}
}

@ -0,0 +1,56 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import Typography from '../../../ui/typography/typography';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { SNAPS_VIEW_ROUTE } from '../../../../helpers/constants/routes';
import {
COLORS,
TYPOGRAPHY,
JUSTIFY_CONTENT,
ALIGN_ITEMS,
} from '../../../../helpers/constants/design-system';
import Button from '../../../ui/button';
import Box from '../../../ui/box/box';
export default function SnapContentFooter({ snapName, snapId }) {
const t = useI18nContext();
const history = useHistory();
const handleNameClick = (e) => {
e.stopPropagation();
history.push(`${SNAPS_VIEW_ROUTE}/${encodeURIComponent(snapId)}`);
};
// TODO: add truncation to the snap name, need to pick a character length at which to cut off
return (
<Box
justifyContent={JUSTIFY_CONTENT.CENTER}
alignItems={ALIGN_ITEMS.CENTER}
paddingTop={4}
paddingBottom={4}
className="snap-content-footer"
>
<i className="fas fa-exclamation-circle fa-sm" />
<Typography color={COLORS.TEXT_MUTED} variant={TYPOGRAPHY.H7}>
{t('snapContent', [
<Button type="inline" onClick={handleNameClick} key="button">
{snapName}
</Button>,
])}
</Typography>
</Box>
);
}
SnapContentFooter.propTypes = {
/**
* The name of the snap who's content is displayed
*/
snapName: PropTypes.string,
/**
* The id of the snap
*/
snapId: PropTypes.string,
};

@ -0,0 +1,17 @@
import React from 'react';
import SnapContentFooter from '.';
export default {
title: 'Components/App/Flask/SnapContentFooter',
id: __filename,
component: SnapContentFooter,
args: {
snapName: 'Test Snap',
snapId: 'local:test-snap',
},
};
export const DefaultStory = (args) => <SnapContentFooter {...args} />;
DefaultStory.storyName = 'Default';

@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Dropdown from '../../dropdown';
export const DropdownTab = (props) => {
const {
activeClassName,
className,
'data-testid': dataTestId,
isActive,
onClick,
onChange,
tabIndex,
options,
selectedOption,
} = props;
return (
<li
className={classnames('tab', className, {
'tab--active': isActive,
[activeClassName]: activeClassName && isActive,
})}
data-testid={dataTestId}
onClick={(event) => {
event.preventDefault();
onClick(tabIndex);
}}
>
<Dropdown
options={options}
selectedOption={selectedOption}
onChange={onChange}
/>
</li>
);
};
DropdownTab.propTypes = {
activeClassName: PropTypes.string,
className: PropTypes.string,
'data-testid': PropTypes.string,
isActive: PropTypes.bool, // required, but added using React.cloneElement
options: PropTypes.arrayOf(
PropTypes.exact({
name: PropTypes.string,
value: PropTypes.string.isRequired,
}),
).isRequired,
selectedOption: PropTypes.string,
onChange: PropTypes.func,
onClick: PropTypes.func,
tabIndex: PropTypes.number, // required, but added using React.cloneElement
};
DropdownTab.defaultProps = {
activeClassName: undefined,
className: undefined,
onChange: undefined,
onClick: undefined,
selectedOption: undefined,
};

@ -0,0 +1,3 @@
import { DropdownTab } from './dropdown-tab';
export default DropdownTab;

@ -0,0 +1,23 @@
.tab {
.dropdown__select {
border: none;
font-size: unset;
width: 100%;
background-color: unset;
padding-left: 8px;
padding-right: 20px;
line-height: unset;
option {
background-color: var(--color-background-default);
}
&:focus-visible {
outline: none;
}
}
.dropdown__icon-caret-down {
right: 0;
}
}

@ -1,4 +1,5 @@
import Tabs from './tabs.component';
import Tab from './tab';
import DropdownTab from './dropdown-tab';
export { Tabs, Tab };
export { Tabs, Tab, DropdownTab };

@ -1,4 +1,5 @@
@import 'tab/index';
@import 'dropdown-tab/index';
.tabs {
flex-grow: 1;

@ -1,4 +1,5 @@
import React from 'react';
import DropdownTab from './dropdown-tab';
import Tab from './tab/tab.component';
import Tabs from './tabs.component';
@ -41,6 +42,14 @@ export const DefaultStory = (args) => {
onTabClick={args.onTabClick}
>
{args.tabs.map((tabProps, i) => renderTab(tabProps, i))}
<DropdownTab
options={[
{ name: 'Insight Snap', value: 'Insight Snap' },
{ name: 'Tenderly Insight', value: 'Tenderly Insight' },
]}
>
This is a dropdown Tab
</DropdownTab>
</Tabs>
);
};

@ -94,6 +94,11 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({
leftIcon: 'fas fa-infinity',
rightIcon: null,
},
[EndowmentPermissions['endowment:transaction-insight']]: {
label: (t) => t('permission_transactionInsight'),
leftIcon: 'fas fa-info',
rightIcon: null,
},
///: END:ONLY_INCLUDE_IN
[UNKNOWN_PERMISSION]: {
label: (t, permissionName) =>

@ -0,0 +1,37 @@
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { handleSnapRequest } from '../../store/actions';
import { getPermissionSubjects } from '../../selectors';
const INSIGHT_PERMISSION = 'endowment:transaction-insight';
export function useTransactionInsightSnap({ transaction, chainId, snapId }) {
const subjects = useSelector(getPermissionSubjects);
if (!subjects[snapId]?.permissions[INSIGHT_PERMISSION]) {
throw new Error(
'This snap does not have the transaction insight endowment.',
);
}
const [data, setData] = useState(undefined);
useEffect(() => {
async function fetchInsight() {
const d = await handleSnapRequest({
snapId,
origin: 'test',
handler: 'onTransaction',
request: {
jsonrpc: '2.0',
method: ' ',
params: { transaction, chainId },
},
});
setData(d);
}
if (transaction) {
fetchInsight();
}
}, [snapId, transaction, chainId]);
return data;
}

@ -1,5 +1,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
///: BEGIN:ONLY_INCLUDE_IN(flask)
import { stripHexPrefix } from 'ethereumjs-util';
///: END:ONLY_INCLUDE_IN
import ConfirmPageContainer from '../../components/app/confirm-page-container';
import TransactionDecoding from '../../components/app/transaction-decoding';
import { isBalanceSufficient } from '../send/send.utils';
@ -42,7 +45,7 @@ import GasDetailsItem from '../../components/app/gas-details-item';
import GasTiming from '../../components/app/gas-timing/gas-timing.component';
import LedgerInstructionField from '../../components/app/ledger-instruction-field';
import MultiLayerFeeMessage from '../../components/app/multilayer-fee-message';
import Typography from '../../components/ui/typography/typography';
import {
COLORS,
FONT_STYLE,
@ -55,12 +58,21 @@ import {
removePollingTokenFromAppState,
} from '../../store/actions';
import Typography from '../../components/ui/typography/typography';
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants';
import { NETWORK_TO_NAME_MAP } from '../../../shared/constants/network';
import { hexToDecimal } from '../../../shared/lib/metamask-controller-utils';
import { hexWEIToDecGWEI } from '../../../shared/lib/transactions-controller-utils';
///: BEGIN:ONLY_INCLUDE_IN(flask)
import { SnapInsight } from '../../components/app/confirm-page-container/flask/snap-insight';
import { DropdownTab, Tab } from '../../components/ui/tabs';
///: END:ONLY_INCLUDE_IN
import {
NETWORK_TO_NAME_MAP,
///: BEGIN:ONLY_INCLUDE_IN(flask)
CHAIN_ID_TO_NETWORK_ID_MAP,
///: END:ONLY_INCLUDE_IN
} from '../../../shared/constants/network';
import TransactionAlerts from './transaction-alerts';
const renderHeartBeatIfNotInTest = () =>
@ -150,6 +162,9 @@ export default class ConfirmTransactionBase extends Component {
showBuyModal: PropTypes.func,
isBuyableChain: PropTypes.bool,
setApproveForAllArg: PropTypes.bool,
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightSnaps: PropTypes.arrayOf(PropTypes.object),
///: END:ONLY_INCLUDE_IN
};
state = {
@ -159,6 +174,9 @@ export default class ConfirmTransactionBase extends Component {
ethGasPriceWarning: '',
editingGas: false,
userAcknowledgedGasMissing: false,
///: BEGIN:ONLY_INCLUDE_IN(flask)
selectedInsightSnapId: this.props.insightSnaps[0]?.id,
///: END:ONLY_INCLUDE_IN
};
componentDidUpdate(prevProps) {
@ -302,6 +320,12 @@ export default class ConfirmTransactionBase extends Component {
this.setState({ editingGas: false });
}
///: BEGIN:ONLY_INCLUDE_IN(flask)
handleSnapSelected(snapId) {
this.setState({ selectedInsightSnapId: snapId });
}
///: END:ONLY_INCLUDE_IN
setUserAcknowledgedGasMissing() {
this.setState({ userAcknowledgedGasMissing: true });
}
@ -729,6 +753,61 @@ export default class ConfirmTransactionBase extends Component {
);
}
///: BEGIN:ONLY_INCLUDE_IN(flask)
renderInsight() {
const { txData, insightSnaps } = this.props;
const { selectedInsightSnapId } = this.state;
const { txParams, chainId } = txData;
const selectedSnap = insightSnaps.find(
({ id }) => id === selectedInsightSnapId,
);
const networkId = CHAIN_ID_TO_NETWORK_ID_MAP[chainId];
const caip2ChainId = `eip155:${networkId ?? stripHexPrefix(chainId)}`;
if (
txData.type !== TRANSACTION_TYPES.CONTRACT_INTERACTION ||
!insightSnaps.length
) {
return null;
}
const dropdownOptions = insightSnaps.map(
({ id, manifest: { proposedName } }) => ({
value: id,
name: proposedName,
}),
);
return insightSnaps.length > 1 ? (
<DropdownTab
className="confirm-page-container-content__tab"
options={dropdownOptions}
selectedOption={selectedInsightSnapId}
onChange={(snapId) => this.handleSnapSelected(snapId)}
>
<SnapInsight
transaction={txParams}
chainId={caip2ChainId}
selectedSnap={selectedSnap}
/>
</DropdownTab>
) : (
<Tab
className="confirm-page-container-content__tab"
name={selectedSnap.manifest.proposedName}
>
<SnapInsight
transaction={txParams}
chainId={caip2ChainId}
selectedSnap={selectedSnap}
/>
</Tab>
);
}
///: END:ONLY_INCLUDE_IN
handleEdit() {
const {
txData,
@ -1111,6 +1190,9 @@ export default class ConfirmTransactionBase extends Component {
detailsComponent={this.renderDetails()}
dataComponent={this.renderData(functionType)}
dataHexComponent={this.renderDataHex(functionType)}
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightComponent={this.renderInsight()}
///: END:ONLY_INCLUDE_IN
contentComponent={contentComponent}
nonce={customNonceValue || nonce}
unapprovedTxCount={unapprovedTxCount}

@ -35,6 +35,9 @@ import {
getEIP1559V2Enabled,
getIsBuyableChain,
getEnsResolutionByAddress,
///: BEGIN:ONLY_INCLUDE_IN(flask)
getInsightSnaps,
///: END:ONLY_INCLUDE_IN
} from '../../selectors';
import { getMostRecentOverviewPage } from '../../ducks/history/history';
import {
@ -198,6 +201,10 @@ const mapStateToProps = (state, ownProps) => {
const isMultiLayerFeeNetwork = getIsMultiLayerFeeNetwork(state);
const eip1559V2Enabled = getEIP1559V2Enabled(state);
///: BEGIN:ONLY_INCLUDE_IN(flask)
const insightSnaps = getInsightSnaps(state);
///: END:ONLY_INCLUDE_IN
return {
balance,
fromAddress,
@ -250,6 +257,9 @@ const mapStateToProps = (state, ownProps) => {
chainId,
eip1559V2Enabled,
isBuyableChain,
///: BEGIN:ONLY_INCLUDE_IN(flask)
insightSnaps,
///: END:ONLY_INCLUDE_IN
};
};

@ -62,10 +62,11 @@ import {
getLedgerTransportStatus,
} from '../ducks/app/app';
import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
import { hexToDecimal } from '../../shared/lib/metamask-controller-utils';
///: BEGIN:ONLY_INCLUDE_IN(flask)
import { SNAPS_VIEW_ROUTE } from '../helpers/constants/routes';
import { getPermissionSubjects } from './permissions';
///: END:ONLY_INCLUDE_IN
import { hexToDecimal } from '../../shared/lib/metamask-controller-utils';
/**
* One of the only remaining valid uses of selecting the network subkey of the
@ -754,6 +755,17 @@ export function getSnaps(state) {
return state.metamask.snaps;
}
export function getInsightSnaps(state) {
const snaps = Object.values(state.metamask.snaps);
const subjects = getPermissionSubjects(state);
const insightSnaps = snaps.filter(
({ id }) => subjects[id]?.permissions['endowment:transaction-insight'],
);
return insightSnaps;
}
export const getSnapsRouteObjects = createSelector(getSnaps, (snaps) => {
return Object.values(snaps).map((snap) => {
return {

@ -1056,6 +1056,10 @@ export async function removeSnapError(msgData) {
return submitRequestToBackground('removeSnapError', [msgData]);
}
export async function handleSnapRequest(args) {
return submitRequestToBackground('handleSnapRequest', [args]);
}
export function dismissNotifications(ids) {
return async (dispatch) => {
await submitRequestToBackground('dismissNotifications', [ids]);

Loading…
Cancel
Save