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

Use ActionableMessage for ConfirmAddSuggestionToken warnings (#13402)

* ConfirmAddSuggestedToken: update duplicate logic

* ConfirmAddSuggestedToken: update duplicate fn names

* ConfirmAddSuggestedToken: add ActionableMessage
- fixes #13347

* base-styles.scss: deprecate .warning

* ConfirmAddSuggestedTokens: update @param desc

Co-authored-by: Alex Donesky <adonesky@gmail.com>

* ConfirmAddSuggestedTokens: update @param desc

Co-authored-by: Alex Donesky <adonesky@gmail.com>

* ConfirmAddSuggestedTokens: update @param desc

Co-authored-by: Alex Donesky <adonesky@gmail.com>

* ConfirmAddSuggestedTokens: update @param desc

Co-authored-by: Alex Donesky <adonesky@gmail.com>

* ConfirmAddSuggestedToken: clean JSDoc comments
- following GitHub suggestion commits

* ConfirmAddSuggestedToken: warning Learn More btn

* add zendesk-url constants
- use for ConfirmAddSuggestedToken & ImportToken pages

* storybook: add ConfirmAddSuggestedToken controls

* stories: add ConfirmAddSuggestedTokens states

* stories: rm unused args ConfirmAddSuggestedToken

* rn ZENDESK_URL -> ZENDESK_URLS

* AddSuggestedToken: check case-insensitive symbols

Co-authored-by: Alex Donesky <adonesky@gmail.com>
This commit is contained in:
Ariella Vu 2022-02-01 15:30:15 +00:00 committed by GitHub
parent 3a75f68a15
commit a5b114c4f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 207 additions and 89 deletions

View File

@ -907,7 +907,7 @@ const state = {
'0xaD6D458402F60fD3Bd25163575031ACDce07538D': './sai.svg',
},
hiddenTokens: [],
suggestedAssets: {},
suggestedAssets: [],
useNonceField: false,
usePhishDetect: true,
lostIdentities: {},

View File

@ -1444,7 +1444,7 @@
"message": "Γνωστή διεύθυνση συμβολαίου."
},
"knownTokenWarning": {
"message": "Αυτή η ενέργεια θα επεξεργαστεί tokens που είναι ήδη εισηγμένα στο πορτοφόλι σας, τα οποίαο μπορεί να χρησιμοποιηθούν για να σας κλέψουν στοιχεία. Αποδεχθείτε μόνο αν είστε σίγουροι ότι θέλετε να αλλάξετε αυτό που αντιπροσωπεύουν αυτά τα νομίσματα."
"message": "Αυτή η ενέργεια θα επεξεργαστεί tokens που είναι ήδη εισηγμένα στο πορτοφόλι σας, τα οποίαο μπορεί να χρησιμοποιηθούν για να σας κλέψουν στοιχεία. Αποδεχθείτε μόνο αν είστε σίγουροι ότι θέλετε να αλλάξετε αυτό που αντιπροσωπεύουν αυτά τα νομίσματα. Μάθετε περισσότερά γι'αυτό $1"
},
"kovan": {
"message": "Δίκτυο Δοκιμής Kovan"

View File

@ -1547,7 +1547,7 @@
"message": "Known contract address."
},
"knownTokenWarning": {
"message": "This action will edit tokens that are already listed in your wallet, which can be used to phish you. Only approve if you are certain that you mean to change what these tokens represent."
"message": "This action will edit tokens that are already listed in your wallet, which can be used to phish you. Only approve if you are certain that you mean to change what these tokens represent. Learn more about $1"
},
"kovan": {
"message": "Kovan Test Network"

View File

@ -1444,7 +1444,7 @@
"message": "Adresse contractuelle connue."
},
"knownTokenWarning": {
"message": "Cette action modifiera les jetons déjà présents dans votre portefeuille, et risque de favoriser les tentatives dhameçonnage. Napprouvez que si vous êtes certain·e de vouloir modifier ce que ces jetons représentent."
"message": "Cette action modifiera les jetons déjà présents dans votre portefeuille, et risque de favoriser les tentatives dhameçonnage. Napprouvez que si vous êtes certain·e de vouloir modifier ce que ces jetons représentent. En savoir plus sur $1"
},
"kovan": {
"message": "Réseau de test Kovan"

View File

@ -1444,7 +1444,7 @@
"message": "ज्ञात अनुबंध पता।"
},
"knownTokenWarning": {
"message": "यह कार्रवाई उन टोकन को संपादित करेगी, जो पहले से ही आपके वॉलेट में सूचीबद्ध हैं, जिसका उपयोग आपको फ़िश करने के लिए किया जा सकता है। केवल तभी अनुमोदित करें, जब आप इस बात को लेकर सुनिश्चित हों कि आप इन टोकन का प्रतिनिधित्व बदलना चाहते हैं।"
"message": "यह कार्रवाई उन टोकन को संपादित करेगी, जो पहले से ही आपके वॉलेट में सूचीबद्ध हैं, जिसका उपयोग आपको फ़िश करने के लिए किया जा सकता है। केवल तभी अनुमोदित करें, जब आप इस बात को लेकर सुनिश्चित हों कि आप इन टोकन का प्रतिनिधित्व बदलना चाहते हैं। $1 के बारे में और अधिक जानें"
},
"kovan": {
"message": "Kovan टेस्ट नेटवर्क"

View File

@ -1444,7 +1444,7 @@
"message": "Alamat kontrak yang diketahui."
},
"knownTokenWarning": {
"message": "Tindakan ini akan mengedit token yang telah terdaftar dalam dompet Anda, yang dapat digunakan untuk menipu Anda. Setujui hanya jika Anda yakin bahwa Anda ingin mengubah apa yang diwakili token ini."
"message": "Tindakan ini akan mengedit token yang telah terdaftar dalam dompet Anda, yang dapat digunakan untuk menipu Anda. Setujui hanya jika Anda yakin bahwa Anda ingin mengubah apa yang diwakili token ini. Pelajari selengkapnya seputar $1"
},
"kovan": {
"message": "Jaringan Uji Kovan"

View File

@ -1444,7 +1444,7 @@
"message": "既知のコントラクト アドレスです。"
},
"knownTokenWarning": {
"message": "このアクションは、ウォレットに既に一覧表示されているトークンを編集します。これは、フィッシングに使用される可能性があります。これらのトークンの表す内容を変更する意図が確実な場合にのみ承認します。"
"message": "このアクションは、ウォレットに既に一覧表示されているトークンを編集します。これは、フィッシングに使用される可能性があります。これらのトークンの表す内容を変更する意図が確実な場合にのみ承認します。$1に関する詳細をご覧ください"
},
"kovan": {
"message": "Kovan テスト ネットワーク"

View File

@ -1444,7 +1444,7 @@
"message": "알려진 계약 주소입니다."
},
"knownTokenWarning": {
"message": "이 작업은 지갑에 이미 나열되어 있고 피싱에 사용될 수 있는 토큰을 편집합니다. 해당 토큰이 나타내는 내용을 변경하려는 경우에만 작업을 승인하세요."
"message": "이 작업은 지갑에 이미 나열되어 있고 피싱에 사용될 수 있는 토큰을 편집합니다. 해당 토큰이 나타내는 내용을 변경하려는 경우에만 작업을 승인하세요. $1에 대해 자세히 알아보기"
},
"kovan": {
"message": "Kovan 테스트 네트워크"

View File

@ -1444,7 +1444,7 @@
"message": "Известный адрес контракта."
},
"knownTokenWarning": {
"message": "Это действие изменит токены, уже указанные в вашем кошельке, которые можно использовать для фишинга. Утверждайте, только если вы уверены, что хотите изменить то, что представляют эти токены."
"message": "Это действие изменит токены, уже указанные в вашем кошельке, которые можно использовать для фишинга. Утверждайте, только если вы уверены, что хотите изменить то, что представляют эти токены. Узнайте подробнее о $1"
},
"kovan": {
"message": "Тестовая сеть Kovan"

View File

@ -1444,7 +1444,7 @@
"message": "Kilalang address ng kontrata."
},
"knownTokenWarning": {
"message": "Mae-edit ng aksyong ito ang mga token na nakalista na sa iyong wallet, na puwedeng gamitin para i-phish ka. Aprubahan lang kung sigurado kang gusto mong baguhin kung ano ang kinakatawan ng mga token na ito."
"message": "Mae-edit ng aksyong ito ang mga token na nakalista na sa iyong wallet, na puwedeng gamitin para i-phish ka. Aprubahan lang kung sigurado kang gusto mong baguhin kung ano ang kinakatawan ng mga token na ito. Alamin pa ang tungkol sa $1"
},
"kovan": {
"message": "Kovan Test Network"

View File

@ -1444,7 +1444,7 @@
"message": "Bilinen sözleşme adresi."
},
"knownTokenWarning": {
"message": "Bu eylem kimlik avı için kullanılabilecek şekilde cüzdanınızda zaten listelenmiş olan tokenleri düzenleyecektir. Sadece bu tokenlerin neyi temsil ettiğini değiştirmek istediğinizden eminseniz onaylayın."
"message": "Bu eylem kimlik avı için kullanılabilecek şekilde cüzdanınızda zaten listelenmiş olan tokenleri düzenleyecektir. Sadece bu tokenlerin neyi temsil ettiğini değiştirmek istediğinizden eminseniz onaylayın. $1 hakkında daha fazla bilgi edinin"
},
"kovan": {
"message": "Kovan Test Ağı"

View File

@ -1444,7 +1444,7 @@
"message": "Địa chỉ hợp đồng đã biết."
},
"knownTokenWarning": {
"message": "Hành động này sẽ chỉnh sửa các token đã niêm yết trong ví của bạn, kẻ xấu có thể lợi dụng việc này để lừa đảo bạn. Chỉ phê duyệt nếu bạn chắc chắn rằng bạn muốn thay đổi giá trị mà những token này đại diện cho."
"message": "Hành động này sẽ chỉnh sửa các token đã niêm yết trong ví của bạn, kẻ xấu có thể lợi dụng việc này để lừa đảo bạn. Chỉ phê duyệt nếu bạn chắc chắn rằng bạn muốn thay đổi giá trị mà những token này đại diện cho. Tìm hiểu thêm về $1"
},
"kovan": {
"message": "Mạng thử nghiệm Kovan"

View File

@ -1444,7 +1444,7 @@
"message": "已知接收方地址。"
},
"knownTokenWarning": {
"message": "此操作将编辑已经在您的钱包中列出的代币,有肯能被用来欺骗您。只有确定要更改这些代币的内容时,才通过此操作。"
"message": "此操作将编辑已经在您的钱包中列出的代币,有肯能被用来欺骗您。只有确定要更改这些代币的内容时,才通过此操作。了解更多关于 $1"
},
"kovan": {
"message": "Kovan 测试网络"

View File

@ -43,14 +43,6 @@ html {
margin-bottom: 9px;
}
/*
This warning class is used in the following files still:
/ui/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
*/
.warning {
color: #ffae00;
}
/* stylelint-disable */
#app-content {
overflow-x: hidden;

View File

@ -0,0 +1,6 @@
const ZENDESK_URLS = {
TOKEN_SAFETY_PRACTICES:
'https://metamask.zendesk.com/hc/en-us/articles/4403988839451',
};
export default ZENDESK_URLS;

View File

@ -1,15 +1,56 @@
import React, { useContext, useEffect } from 'react';
import React, { useContext, useEffect, useMemo } from 'react';
import PropTypes from 'prop-types';
import ActionableMessage from '../../components/ui/actionable-message/actionable-message';
import Button from '../../components/ui/button';
import Identicon from '../../components/ui/identicon';
import TokenBalance from '../../components/ui/token-balance';
import { I18nContext } from '../../contexts/i18n';
import { MetaMetricsContext } from '../../contexts/metametrics';
import ZENDESK_URLS from '../../helpers/constants/zendesk-url';
import { isEqualCaseInsensitive } from '../../helpers/utils/util';
function getTokenName(name, symbol) {
return typeof name === 'undefined' ? symbol : `${name} (${symbol})`;
}
/**
* @param {Array} suggestedAssets - an array of assets suggested to add to the user's wallet
* via the RPC method `wallet_watchAsset`
* @param {Array} tokens - the list of tokens currently tracked in state
* @returns {boolean} Returns true when the list of suggestedAssets contains an entry with
* an address that matches an existing token.
*/
function hasDuplicateAddress(suggestedAssets, tokens) {
const duplicate = suggestedAssets.find(({ asset }) => {
const dupe = tokens.find(({ address }) => {
return isEqualCaseInsensitive(address, asset.address);
});
return Boolean(dupe);
});
return Boolean(duplicate);
}
/**
* @param {Array} suggestedAssets - a list of assets suggested to add to the user's wallet
* via RPC method `wallet_watchAsset`
* @param {Array} tokens - the list of tokens currently tracked in state
* @returns {boolean} Returns true when the list of suggestedAssets contains an entry with both
* 1. a symbol that matches an existing token
* 2. an address that does not match an existing token
*/
function hasDuplicateSymbolAndDiffAddress(suggestedAssets, tokens) {
const duplicate = suggestedAssets.find(({ asset }) => {
const dupe = tokens.find((token) => {
return (
isEqualCaseInsensitive(token.symbol, asset.symbol) &&
!isEqualCaseInsensitive(token.address, asset.address)
);
});
return Boolean(dupe);
});
return Boolean(duplicate);
}
const ConfirmAddSuggestedToken = (props) => {
const {
acceptWatchAsset,
@ -37,38 +78,44 @@ const ConfirmAddSuggestedToken = (props) => {
});
};
/**
* Returns true if any suggestedAssets both:
* - Share a symbol with an existing `tokens` member.
* - Does not share an address with that same `tokens` member.
* This should be flagged as possibly deceptive or confusing.
*/
const checkNameReuse = () => {
const duplicates = suggestedAssets.filter(({ asset }) => {
const dupes = tokens.filter(
(old) =>
old.symbol === asset.symbol &&
!isEqualCaseInsensitive(old.address, asset.address),
);
return dupes.length > 0;
});
return duplicates.length > 0;
};
const checkTokenDuplicates = () => {
const pending = suggestedAssets.map(({ asset }) =>
asset.address.toUpperCase(),
const knownTokenActionableMessage = useMemo(() => {
return (
hasDuplicateAddress(suggestedAssets, tokens) && (
<ActionableMessage
message={t('knownTokenWarning', [
<Button
type="link"
key="confirm-add-suggested-token-duplicate-warning"
className="confirm-add-suggested-token__link"
rel="noopener noreferrer"
target="_blank"
href={ZENDESK_URLS.TOKEN_SAFETY_PRACTICES}
>
{t('learnScamRisk')}
</Button>,
])}
type="warning"
withRightButton
useIcon
iconFillColor="#f8c000"
/>
)
);
const existing = tokens.map((token) => token.address.toUpperCase());
const dupes = pending.filter((proposed) => {
return existing.includes(proposed);
});
}, [suggestedAssets, tokens, t]);
return dupes.length > 0;
};
const hasTokenDuplicates = checkTokenDuplicates();
const reusesName = checkNameReuse();
const reusedTokenNameActionableMessage = useMemo(() => {
return (
hasDuplicateSymbolAndDiffAddress(suggestedAssets, tokens) && (
<ActionableMessage
message={t('reusedTokenNameWarning')}
type="warning"
withRightButton
useIcon
iconFillColor="#f8c000"
/>
)
);
}, [suggestedAssets, tokens, t]);
useEffect(() => {
if (!suggestedAssets.length) {
@ -83,12 +130,8 @@ const ConfirmAddSuggestedToken = (props) => {
<div className="page-container__subtitle">
{t('likeToImportTokens')}
</div>
{hasTokenDuplicates ? (
<div className="warning">{t('knownTokenWarning')}</div>
) : null}
{reusesName ? (
<div className="warning">{t('reusedTokenNameWarning')}</div>
) : null}
{knownTokenActionableMessage}
{reusedTokenNameActionableMessage}
</div>
<div className="page-container__content">
<div className="confirm-import-token">

View File

@ -2,61 +2,127 @@
import React, { useEffect } from 'react';
import { text } from '@storybook/addon-knobs';
import { store, getNewState } from '../../../.storybook/preview';
import { suggestedAssets } from '../../../.storybook/initial-states/approval-screens/add-suggested-token';
import { suggestedAssets as mockSuggestedAssets } from '../../../.storybook/initial-states/approval-screens/add-suggested-token';
import { updateMetamaskState } from '../../store/actions';
import ConfirmAddSuggestedToken from '.';
export default {
title: 'Pages/ConfirmAddSuggestedToken',
id: __filename,
argTypes: {
// Data
tokens: {
control: 'array',
table: { category: 'Data' },
},
suggestedAssets: {
control: 'array',
table: { category: 'Data' },
},
// Text
mostRecentOverviewPage: {
control: { type: 'text', disable: true },
table: { category: 'Text' },
},
// Events
acceptWatchAsset: {
action: 'acceptWatchAsset',
table: { category: 'Events' },
},
history: {
action: 'history',
table: { category: 'Events' },
},
rejectWatchAsset: {
action: 'rejectWatchAsset',
table: { category: 'Events' },
},
},
};
const PageSet = ({ children }) => {
const { metamask: state } = store.getState();
const PageSet = ({ children, suggestedAssets, tokens }) => {
const symbol = text('symbol', 'META');
const image = text('Icon URL', 'metamark.svg');
const state = store.getState();
const suggestedAssetsState = state.metamask.suggestedAssets;
useEffect(() => {
if (!suggestedAssets?.length) {
return;
}
suggestedAssets[0].asset.image = image;
suggestedAssets[0].asset.symbol = symbol;
store.dispatch(
updateMetamaskState(
getNewState(state, {
suggestedAssets,
}),
),
);
}, [image, suggestedAssets, symbol]);
useEffect(() => {
suggestedAssetsState[0].symbol = symbol;
store.dispatch(
updateMetamaskState(
getNewState(state.metamask, {
suggestedAssets: suggestedAssetsState,
getNewState(state, {
tokens,
}),
),
);
}, [symbol, suggestedAssetsState, state.metamask]);
useEffect(() => {
suggestedAssetsState[0].image = image;
store.dispatch(
updateMetamaskState(
getNewState(state.metamask, {
suggestedAssets: suggestedAssetsState,
}),
),
);
}, [image, suggestedAssetsState, state.metamask]);
}, [tokens]);
return children;
};
export const DefaultStory = () => {
const state = store.getState();
store.dispatch(
updateMetamaskState(
getNewState(state.metamask, {
suggestedAssets,
}),
),
);
export const DefaultStory = ({ suggestedAssets, tokens }) => {
return (
<PageSet>
<PageSet suggestedAssets={suggestedAssets} tokens={tokens}>
<ConfirmAddSuggestedToken />
</PageSet>
);
};
DefaultStory.storyName = 'Default';
DefaultStory.args = {
suggestedAssets: [...mockSuggestedAssets],
tokens: [],
};
export const WithDuplicateAddress = ({ suggestedAssets, tokens }) => {
return (
<PageSet suggestedAssets={suggestedAssets} tokens={tokens}>
<ConfirmAddSuggestedToken />
</PageSet>
);
};
WithDuplicateAddress.args = {
suggestedAssets: [...mockSuggestedAssets],
tokens: [
{
...mockSuggestedAssets[0].asset,
},
],
};
export const WithDuplicateSymbolAndDifferentAddress = ({
suggestedAssets,
tokens,
}) => {
return (
<PageSet suggestedAssets={suggestedAssets} tokens={tokens}>
<ConfirmAddSuggestedToken />
</PageSet>
);
};
WithDuplicateSymbolAndDifferentAddress.args = {
suggestedAssets: [...mockSuggestedAssets],
tokens: [
{
...mockSuggestedAssets[0].asset,
address: '0xNonSuggestedAddress',
},
],
};

View File

@ -0,0 +1,9 @@
.confirm-add-suggested-token {
&__link {
@include H7;
display: inline;
color: var(--primary-blue);
padding-left: 0;
}
}

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { getTokenTrackerLink } from '@metamask/etherscan-link';
import contractMap from '@metamask/contract-metadata';
import ZENDESK_URLS from '../../helpers/constants/zendesk-url';
import {
checkExistingAddresses,
getURLHostName,
@ -359,7 +360,7 @@ class ImportToken extends Component {
className="import-token__link"
rel="noopener noreferrer"
target="_blank"
href="https://metamask.zendesk.com/hc/en-us/articles/4403988839451"
href={ZENDESK_URLS.TOKEN_SAFETY_PRACTICES}
>
{this.context.t('learnScamRisk')}
</Button>,

View File

@ -2,6 +2,7 @@
@import 'import-token/index';
@import 'asset/asset';
@import 'confirm-import-token/index';
@import 'confirm-add-suggested-token/index';
@import 'confirm-approve/index';
@import 'confirm-decrypt-message/confirm-decrypt-message';
@import 'confirm-encryption-public-key/confirm-encryption-public-key';