1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-30 08:09:15 +01:00
metamask-extension/ui/pages/send/send-content/add-recipient/add-recipient.component.js
Dan J Miller 0c163dd8aa
Show users a warning when they are sending directly to a token contract (#13588)
* Fix warning dialog when sending tokens to a known token contract address

Fixing after rebase

Covering missed cases

Rebased and ran yarn setup

Rebased

Fix checkContractAddress condition

Lint fix

Applied requested changes

Fix unit tests

Applying requested changes

Applied requested changes

Refactor and update

Lint fix

Use V2 of ActionableMessage component

Adding Learn More Link

Updating warning copy

Addressing review feedback

Fix up copy changes

Simplify validation of pasted addresses

Improve detection of whether this is a token contract

Refactor to leave updateRecipient unchanged, and to prevent the double calling of update recipient

Update tests

fix

* Fix unit tests

* Fix e2e tests

* Ensure next button is disabled while recipient type is loading

* Add optional chaining and a fallback to getRecipientWarningAcknowledgement

* Fix lint

* Don't reset recipient warning on asset change, because we should show recipient warnings regardless of asset

* Update unit tests

* Update unit tests

Co-authored-by: Filip Sekulic <filip.sekulic@consensys.net>
2022-07-13 19:45:38 -02:30

256 lines
7.2 KiB
JavaScript

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Fuse from 'fuse.js';
import Identicon from '../../../../components/ui/identicon';
import Dialog from '../../../../components/ui/dialog';
import ContactList from '../../../../components/app/contact-list';
import RecipientGroup from '../../../../components/app/contact-list/recipient-group/recipient-group.component';
import { ellipsify } from '../../send.utils';
import Button from '../../../../components/ui/button';
import IconCaretLeft from '../../../../components/ui/icon/icon-caret-left';
import Confusable from '../../../../components/ui/confusable';
export default class AddRecipient extends Component {
static propTypes = {
userInput: PropTypes.string,
ownedAccounts: PropTypes.array,
addressBook: PropTypes.array,
updateRecipient: PropTypes.func,
ensResolution: PropTypes.string,
ensError: PropTypes.string,
ensWarning: PropTypes.string,
addressBookEntryName: PropTypes.string,
contacts: PropTypes.array,
nonContacts: PropTypes.array,
addHistoryEntry: PropTypes.func,
useMyAccountsForRecipientSearch: PropTypes.func,
useContactListForRecipientSearch: PropTypes.func,
isUsingMyAccountsForRecipientSearch: PropTypes.bool,
recipient: PropTypes.shape({
address: PropTypes.string,
nickname: PropTypes.nickname,
error: PropTypes.string,
warning: PropTypes.string,
}),
updateRecipientUserInput: PropTypes.func,
};
constructor(props) {
super(props);
this.recentFuse = new Fuse(props.nonContacts, {
shouldSort: true,
threshold: 0.45,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{ name: 'address', weight: 0.5 }],
});
this.contactFuse = new Fuse(props.contacts, {
shouldSort: true,
threshold: 0.45,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
{ name: 'name', weight: 0.5 },
{ name: 'address', weight: 0.5 },
],
});
}
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
};
selectRecipient = (address, nickname = '', type = 'user input') => {
this.props.addHistoryEntry(
`sendFlow - User clicked recipient from ${type}. address: ${address}, nickname ${nickname}`,
);
this.props.updateRecipient({ address, nickname });
this.props.updateRecipientUserInput(address);
};
searchForContacts = () => {
const { userInput, contacts } = this.props;
let _contacts = contacts;
if (userInput) {
this.contactFuse.setCollection(contacts);
_contacts = this.contactFuse.search(userInput);
}
return _contacts;
};
searchForRecents = () => {
const { userInput, nonContacts } = this.props;
let _nonContacts = nonContacts;
if (userInput) {
this.recentFuse.setCollection(nonContacts);
_nonContacts = this.recentFuse.search(userInput);
}
return _nonContacts;
};
render() {
const {
ensResolution,
recipient,
userInput,
addressBookEntryName,
isUsingMyAccountsForRecipientSearch,
} = this.props;
let content;
if (recipient.address) {
content = this.renderExplicitAddress(
recipient.address,
recipient.nickname,
'validated user input',
);
} else if (ensResolution && !recipient.error) {
content = this.renderExplicitAddress(
ensResolution,
addressBookEntryName || userInput,
'ENS resolution',
);
} else if (isUsingMyAccountsForRecipientSearch) {
content = this.renderTransfer();
}
return (
<div className="send__select-recipient-wrapper">
{this.renderDialogs()}
{content || this.renderMain()}
</div>
);
}
renderExplicitAddress(address, name, type) {
return (
<div
key={address}
className="send__select-recipient-wrapper__group-item"
onClick={() => this.selectRecipient(address, name, type)}
>
<Identicon address={address} diameter={28} />
<div className="send__select-recipient-wrapper__group-item__content">
<div className="send__select-recipient-wrapper__group-item__title">
{name ? <Confusable input={name} /> : ellipsify(address)}
</div>
{name && (
<div className="send__select-recipient-wrapper__group-item__subtitle">
{ellipsify(address)}
</div>
)}
</div>
</div>
);
}
renderTransfer() {
let { ownedAccounts } = this.props;
const {
userInput,
useContactListForRecipientSearch,
isUsingMyAccountsForRecipientSearch,
} = this.props;
const { t } = this.context;
if (isUsingMyAccountsForRecipientSearch && userInput) {
ownedAccounts = ownedAccounts.filter(
(item) =>
item.name.toLowerCase().indexOf(userInput.toLowerCase()) > -1 ||
item.address.toLowerCase().indexOf(userInput.toLowerCase()) > -1,
);
}
return (
<div className="send__select-recipient-wrapper__list">
<Button
type="link"
className="send__select-recipient-wrapper__list__link"
onClick={useContactListForRecipientSearch}
>
<IconCaretLeft className="send__select-recipient-wrapper__list__back-caret" />
{t('backToAll')}
</Button>
<RecipientGroup
label={t('myAccounts')}
items={ownedAccounts}
onSelect={(address, name) =>
this.selectRecipient(address, name, 'my accounts')
}
/>
</div>
);
}
renderMain() {
const { t } = this.context;
const {
userInput,
ownedAccounts = [],
addressBook,
useMyAccountsForRecipientSearch,
} = this.props;
return (
<div className="send__select-recipient-wrapper__list">
<ContactList
addressBook={addressBook}
searchForContacts={this.searchForContacts.bind(this)}
searchForRecents={this.searchForRecents.bind(this)}
selectRecipient={(address, name) => {
this.selectRecipient(
address,
name,
`${name ? 'contact' : 'recent'} list`,
);
}}
>
{ownedAccounts && ownedAccounts.length > 1 && !userInput && (
<Button
type="link"
className="send__select-recipient-wrapper__list__link"
onClick={useMyAccountsForRecipientSearch}
>
{t('transferBetweenAccounts')}
</Button>
)}
</ContactList>
</div>
);
}
renderDialogs() {
const { ensError, recipient, ensWarning } = this.props;
const { t } = this.context;
if (ensError || (recipient.error && recipient.error !== 'required')) {
return (
<Dialog type="error" className="send__error-dialog">
{t(ensError ?? recipient.error)}
</Dialog>
);
} else if (ensWarning || recipient.warning) {
return (
<Dialog type="warning" className="send__error-dialog">
{t(ensWarning ?? recipient.warning)}
</Dialog>
);
}
return null;
}
}