mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
[FLASK] Snaps Insight (#15814)
Co-authored-by: Frederik Bolding <frederik.bolding@gmail.com> Co-authored-by: Hassan Malik <41640681+hmalik88@users.noreply.github.com>
This commit is contained in:
parent
8b5630025b
commit
c9dc59ea2a
14
app/_locales/en/messages.json
generated
14
app/_locales/en/messages.json
generated
@ -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}
|
||||
|
1
ui/components/app/confirm-page-container/flask/index.js
Normal file
1
ui/components/app/confirm-page-container/flask/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { SnapInsight } from './snap-insight';
|
@ -0,0 +1,3 @@
|
||||
.snap-insight {
|
||||
word-wrap: break-word;
|
||||
}
|
111
ui/components/app/confirm-page-container/flask/snap-insight.js
Normal file
111
ui/components/app/confirm-page-container/flask/snap-insight.js
Normal file
@ -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 {
|
||||
|
1
ui/components/app/flask/snap-content-footer/index.js
Normal file
1
ui/components/app/flask/snap-content-footer/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './snap-content-footer';
|
13
ui/components/app/flask/snap-content-footer/index.scss
Normal file
13
ui/components/app/flask/snap-content-footer/index.scss
Normal file
@ -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';
|
63
ui/components/ui/tabs/dropdown-tab/dropdown-tab.js
Normal file
63
ui/components/ui/tabs/dropdown-tab/dropdown-tab.js
Normal file
@ -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,
|
||||
};
|
3
ui/components/ui/tabs/dropdown-tab/index.js
Normal file
3
ui/components/ui/tabs/dropdown-tab/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import { DropdownTab } from './dropdown-tab';
|
||||
|
||||
export default DropdownTab;
|
23
ui/components/ui/tabs/dropdown-tab/index.scss
Normal file
23
ui/components/ui/tabs/dropdown-tab/index.scss
Normal file
@ -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) =>
|
||||
|
37
ui/hooks/flask/useTransactionInsightSnap.js
Normal file
37
ui/hooks/flask/useTransactionInsightSnap.js
Normal file
@ -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…
Reference in New Issue
Block a user