No Private Keys Online (#1466)

* Alternative generate options

* Back button when choosing wallet options

* Disable insecure wallets online.

* Style adjustments.

* Splash of color, generate on html build too.

* Removed unnecessary image

* Delete file
This commit is contained in:
William O'Beirne 2018-06-13 20:21:02 -04:00 committed by Daniel Ternyak
parent 8a19e89f04
commit a3fe8db5cf
15 changed files with 579 additions and 228 deletions

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path fill="#000" d="M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zm-22.6 22.7c2.1 2.1 3.5 4.6 4.2 7.4H256V32.5c2.8.7 5.3 2.1 7.4 4.2l83.9 83.9zM336 480H48c-8.8 0-16-7.2-16-16V48c0-8.8 7.2-16 16-16h176v104c0 13.3 10.7 24 24 24h104v304c0 8.8-7.2 16-16 16zm-48-244v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm0 64v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm0 64v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12z"></path>
</svg>

After

Width:  |  Height:  |  Size: 724 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="393px" height="642px" viewBox="0 0 393 642" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49.1 (51147) - http://www.bohemiancoding.com/sketch -->
<title>Artboard</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="hardware" transform="translate(197.000000, 320.500000) rotate(-90.000000) translate(-197.000000, -320.500000) translate(-124.000000, 124.000000)" fill="#000000" fill-rule="nonzero">
<path d="M641.5,196 C641.5,199.1 639.8,202.1 637,203.5 L547.9,257 C546.5,257.8 545.1,258.4 543.4,258.4 C542,258.4 540.3,258.1 538.9,257.3 C536.1,255.6 534.4,252.8 534.4,249.5 L534.4,213.9 L295.7,213.9 C321,253.5 336.2,320.8 365.3,320.8 L392,320.8 L392,294 C392,289 395.9,285.1 400.9,285.1 L490,285.1 C495,285.1 498.9,289 498.9,294 L498.9,383.1 C498.9,388.1 495,392 490,392 L400.9,392 C395.9,392 392,388.1 392,383.1 L392,356.4 L365.3,356.4 C289.9,356.4 284.2,213.9 240.6,213.9 L140.3,213.9 C132.2,244.5 104.4,267.4 71.3,267.4 C32,267.3 0,235.3 0,196 C0,156.7 32,124.7 71.3,124.7 C104.4,124.7 132.3,147.5 140.3,178.2 C179.4,178.2 184.2,187.7 214.9,117.8 C255,28.7 273,35.7 323.8,35.7 C331.3,14.8 350.8,0.1 374.2,0.1 C403.7,0.1 427.7,24 427.7,53.6 C427.7,83.2 403.8,107.1 374.2,107.1 C350.8,107.1 331.3,92.3 323.8,71.5 L294,71.5 C264.9,71.5 249.7,138.9 224.4,178.4 L534.5,178.4 L534.5,142.8 C534.5,139.5 536.2,136.7 539,135 C541.8,133.3 545.4,133.6 547.9,135.3 L637,188.8 C639.8,189.9 641.5,192.9 641.5,196 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -111,6 +111,17 @@ $speed: 500ms;
overflow: hidden;
text-overflow: ellipsis;
}
&-override {
position: absolute;
bottom: 0;
right: 0;
opacity: 0.3;
&:hover {
opacity: 1;
}
}
}
}

View File

@ -79,8 +79,8 @@ type Props = OwnProps & StateProps & DispatchProps & RouteComponentProps<{}>;
type UnlockParams = {} | PrivateKeyValue;
interface State {
selectedWalletKey: WalletName | null;
isInsecureOverridden: boolean;
value: UnlockParams | null;
hasAcknowledgedInsecure: boolean;
}
interface BaseWalletInfo {
@ -204,8 +204,8 @@ const WalletDecrypt = withRouter<Props>(
public state: State = {
selectedWalletKey: null,
value: null,
hasAcknowledgedInsecure: false
isInsecureOverridden: false,
value: null
};
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
@ -228,20 +228,28 @@ const WalletDecrypt = withRouter<Props>(
}
public getDecryptionComponent() {
const { selectedWalletKey, hasAcknowledgedInsecure } = this.state;
const { selectedWalletKey, isInsecureOverridden } = this.state;
const selectedWallet = this.getSelectedWallet();
if (!selectedWalletKey || !selectedWallet) {
return null;
}
if (INSECURE_WALLETS.includes(selectedWalletKey) && !hasAcknowledgedInsecure) {
const isInsecure = INSECURE_WALLETS.includes(selectedWalletKey);
if (isInsecure && !isInsecureOverridden && !process.env.BUILD_DOWNLOADABLE) {
return (
<div className="WalletDecrypt-decrypt">
<InsecureWalletWarning
walletType={translateRaw(selectedWallet.lid)}
onContinue={this.handleAcknowledgeInsecure}
onCancel={this.clearWalletChoice}
/>
{process.env.NODE_ENV !== 'production' && (
<button
className="WalletDecrypt-decrypt-override"
onClick={this.overrideInsecureWarning}
>
I'm a dev, override this
</button>
)}
</div>
);
}
@ -289,10 +297,6 @@ const WalletDecrypt = withRouter<Props>(
);
}
public handleAcknowledgeInsecure = () => {
this.setState({ hasAcknowledgedInsecure: true });
};
public buildWalletOptions() {
const { computedDisabledWallets } = this.props;
const { reasons } = computedDisabledWallets;
@ -386,8 +390,7 @@ const WalletDecrypt = withRouter<Props>(
window.setTimeout(() => {
this.setState({
selectedWalletKey: walletType,
value: wallet.initialParams,
hasAcknowledgedInsecure: false
value: wallet.initialParams
});
}, timeout);
};
@ -395,8 +398,7 @@ const WalletDecrypt = withRouter<Props>(
public clearWalletChoice = () => {
this.setState({
selectedWalletKey: null,
value: null,
hasAcknowledgedInsecure: false
value: null
});
};
@ -448,6 +450,12 @@ const WalletDecrypt = withRouter<Props>(
private isWalletDisabled = (walletKey: WalletName) => {
return this.props.computedDisabledWallets.wallets.indexOf(walletKey) !== -1;
};
private overrideInsecureWarning = () => {
if (process.env.NODE_ENV !== 'production') {
this.setState({ isInsecureOverridden: true });
}
};
}
);

View File

@ -1,12 +0,0 @@
import React from 'react';
import translate from 'translations';
const DeprecationWarning: React.SFC<{}> = () => {
if (process.env.BUILD_DOWNLOADABLE) {
return null;
}
return <div className="alert alert-warning">{translate('INSECURE_WALLET_DEPRECATION')}</div>;
};
export default DeprecationWarning;

View File

@ -1,36 +1,48 @@
@import 'common/sass/variables';
@import 'common/sass/mixins';
.WalletWarning {
max-width: 780px;
max-width: 820px;
margin: 0 auto;
text-align: left;
text-align: center;
&-title {
color: $brand-danger;
margin-top: 0;
margin: 0 0 $space;
}
&-desc {
margin-bottom: $space;
}
&-check {
margin-bottom: $space * 2;
}
&-checkboxes {
margin-bottom: $space * 2;
}
&-buttons {
display: flex;
flex-direction: column;
flex-wrap: wrap;
margin-bottom: -$space-sm;
justify-content: center;
max-width: 440px;
margin: 0 auto #{-$space};
.btn {
flex: 1;
min-width: 280px;
margin: 0 $space-sm $space-sm;
&-btn {
width: 100%;
margin: 0 0 $space;
&.is-cancel {
@include reset-button;
opacity: 0.4;
transition: $transition;
&:hover {
opacity: 1;
}
.fa {
position: relative;
top: -1px;
margin-right: $space-xs;
font-size: 11px;
}
}
}
}
}

View File

@ -1,129 +1,43 @@
import React from 'react';
import { HELP_ARTICLE, DOWNLOAD_MYCRYPTO_LINK } from 'config';
import './InsecureWalletWarning.scss';
import translate from 'translations';
import { knowledgeBaseURL } from 'config/data';
import { NewTabLink } from 'components/ui';
import './InsecureWalletWarning.scss';
interface Props {
walletType: string;
onContinue(): void;
onCancel(): void;
}
interface State {
hasConfirmedSite: boolean;
hasAcknowledgedDownload: boolean;
hasAcknowledgedWallets: boolean;
}
interface Checkbox {
name: keyof State;
label: string | React.ReactElement<string>;
}
export class InsecureWalletWarning extends React.Component<Props, State> {
public state: State = {
hasConfirmedSite: false,
hasAcknowledgedDownload: false,
hasAcknowledgedWallets: false
};
export class InsecureWalletWarning extends React.Component<Props> {
constructor(props: Props) {
super(props);
if (process.env.BUILD_DOWNLOADABLE) {
props.onContinue();
}
}
public render() {
if (process.env.BUILD_DOWNLOADABLE) {
return null;
}
const { walletType, onContinue, onCancel } = this.props;
const checkboxes: Checkbox[] = [
{
name: 'hasAcknowledgedWallets',
label: translate('INSECURE_WALLET_WARNING_1')
},
{
name: 'hasAcknowledgedDownload',
label: translate('INSECURE_WALLET_WARNING_2')
},
{
name: 'hasConfirmedSite',
label: translate('INSECURE_WALLET_WARNING_3')
}
];
const canContinue = checkboxes.reduce(
(prev, checkbox) => prev && this.state[checkbox.name],
true
);
const { walletType, onCancel } = this.props;
return (
<div className="WalletWarning">
<h2 className="WalletWarning-title">{translate('INSECURE_WALLET_TYPE_TITLE')}</h2>
<h2 className="WalletWarning-title">
{translate('INSECURE_WALLET_TYPE_TITLE', { $wallet_type: walletType })}
</h2>
<p className="WalletWarning-desc">
{translate('INSECURE_WALLET_TYPE_DESC', { $wallet_type: walletType })}
</p>
<ul className="WalletWarning-bullets">
<li>
{translate('INSECURE_WALLET_RECOMMEND_1', {
$metamask_article: knowledgeBaseURL + '/' + HELP_ARTICLE.MIGRATE_TO_METAMASK,
$hardware_wallet_article:
knowledgeBaseURL + '/' + HELP_ARTICLE.HARDWARE_WALLET_RECOMMENDATIONS
})}
</li>
<li>
{translate('INSECURE_WALLET_RECOMMEND_2', {
$download_mycrypto: DOWNLOAD_MYCRYPTO_LINK
})}
</li>
<li>
{translate('INSECURE_WALLET_RECOMMEND_3', {
$secure_your_eth_article: knowledgeBaseURL + '/' + HELP_ARTICLE.SECURING_YOUR_ETH
})}
</li>
</ul>
<p className="WalletWarning-check">
{translate('WALLET_WARNING_CHECK', { $wallet_type: walletType })}
</p>
<div className="WalletWarning-checkboxes">{checkboxes.map(this.makeCheckbox)}</div>
<div className="WalletWarning-buttons">
<button className="WalletWarning-cancel btn btn-lg btn-default" onClick={onCancel}>
Go Back
</button>
<button
className="WalletWarning-continue btn btn-lg btn-primary"
onClick={onContinue}
disabled={!canContinue}
<NewTabLink
href="https://download.mycrypto.com/"
className="WalletWarning-buttons-btn is-download btn btn-lg btn-primary"
>
Continue
{translate('WALLET_SUGGESTION_DESKTOP_APP')}
</NewTabLink>
<button className="WalletWarning-buttons-btn is-cancel" onClick={onCancel}>
<i className="fa fa-arrow-left" />
{translate('INSECURE_WALLET_GO_BACK')}
</button>
</div>
</div>
);
}
private makeCheckbox = (checkbox: Checkbox) => {
return (
<label className="AcknowledgeCheckbox" key={checkbox.name}>
<input
type="checkbox"
name={checkbox.name}
className="AcknowledgeCheckbox-checkbox"
onChange={this.handleCheckboxChange}
checked={this.state[checkbox.name]}
/>
<span className="AcknowledgeCheckbox-label">{checkbox.label}</span>
</label>
);
};
private handleCheckboxChange = (ev: React.FormEvent<HTMLInputElement>) => {
this.setState({
[ev.currentTarget.name as any]: !!ev.currentTarget.checked
});
};
}

View File

@ -4,7 +4,6 @@ import translate, { translateRaw } from 'translations';
import Spinner from 'components/ui/Spinner';
import { TShowNotification } from 'actions/notifications';
import { Input } from 'components/ui';
import DeprecationWarning from './DeprecationWarning';
export interface KeystoreValue {
file: string;
@ -45,7 +44,6 @@ export class KeystoreDecrypt extends PureComponent {
return (
<form onSubmit={this.unlock}>
<DeprecationWarning />
<div className="form-group">
<input
className="hidden"

View File

@ -9,7 +9,6 @@ import { connect } from 'react-redux';
import { getSingleDPath, getPaths } from 'selectors/config/wallet';
import { TogglablePassword } from 'components';
import { Input } from 'components/ui';
import DeprecationWarning from './DeprecationWarning';
interface OwnProps {
onUnlock(param: any): void;
@ -51,7 +50,6 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
return (
<React.Fragment>
<DeprecationWarning />
<div id="selectedTypeKey">
<div className="form-group">
<TogglablePassword

View File

@ -4,7 +4,6 @@ import React, { PureComponent } from 'react';
import translate, { translateRaw } from 'translations';
import { TogglablePassword } from 'components';
import { Input } from 'components/ui';
import DeprecationWarning from './DeprecationWarning';
export interface PrivateKeyValue {
key: string;
@ -55,7 +54,6 @@ export class PrivateKeyDecrypt extends PureComponent<Props> {
return (
<form id="selectedTypeKey" onSubmit={this.unlock}>
<DeprecationWarning />
<div className="input-group-wrapper">
<label className="input-group">
<TogglablePassword

View File

@ -4,15 +4,19 @@ import './Template.scss';
import translate from 'translations';
interface Props {
hideBack?: boolean;
children: React.ReactElement<any>;
onBack?(ev: React.MouseEvent<HTMLAnchorElement>): void;
}
const GenerateWalletTemplate: React.SFC<Props> = ({ children }) => (
const GenerateWalletTemplate: React.SFC<Props> = ({ children, hideBack, onBack }) => (
<div className="GenerateWallet Tab-content-pane">
{children}
<Link className="GenerateWallet-back" to="/generate">
{!hideBack && (
<Link className="GenerateWallet-back" to="/generate" onClick={onBack}>
<i className="fa fa-arrow-left" /> {translate('MODAL_BACK')}
</Link>
)}
</div>
);

View File

@ -1,10 +1,9 @@
@import 'common/sass/variables';
$divider-size: 60px;
.WalletTypes {
&-title,
&-subtitle {
text-align: center;
}
&-title {
margin-bottom: $space;
@ -12,23 +11,199 @@
&-subtitle {
max-width: 1040px;
margin: 0 auto;
margin-bottom: $space;
margin: 0 auto $space * 2;
}
&-suggestions {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: $space 0;
text-align: left;
}
&-types {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: $space * 2 0 $space;
}
&-divider {
position: relative;
margin: 3rem auto 4rem;
background: #fff;
font-size: 1.8rem;
font-weight: 300;
color: $gray-light;
@media (max-width: $screen-xs) {
margin: 1.5rem auto 2.5rem;
}
&:before,
&:after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: calc(50% - 2rem);
max-width: 600px;
background: rgba($gray-light, 0.4);
height: 1px;
}
&:before {
transform: translateX(-100%) translateX(-$divider-size / 2);
}
&:after {
transform: translateX(0%) translateX($divider-size / 2);
}
}
&-download {
padding-bottom: $space;
&-button {
font-size: $font-size-medium-bump;
border-radius: 4px;
margin-bottom: $space-md;
padding: 1.3rem 2.5rem;
@media (max-width: $screen-xs) {
padding: 1rem 1.5rem;
font-size: $font-size-base;
}
}
&-desc {
color: $gray;
@media (max-width: $screen-xs) {
font-size: $font-size-small;
}
}
}
}
.WalletSuggestion,
.WalletType {
display: flex;
flex-direction: column;
border: 1px solid $gray-lighter;
box-shadow: 0 3px 2px rgba(0, 0, 0, 0.12);
background: #FFF;
border-radius: 4px;
transition: transform 100ms ease, box-shadow 100ms ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
}
}
.WalletSuggestion {
display: flex;
flex-direction: column;
flex: 1;
max-width: 340px;
min-width: 300px;
margin: 0 $space $space * 2;
padding: $space;
border: 1px solid $gray-lighter;
box-shadow: 0 3px 2px rgba(0, 0, 0, 0.12);
border-radius: 4px;
transition: transform 100ms ease, box-shadow 100ms ease;
border-top: 3px solid;
&.is-hardware {
border-top-color: #41ccb4;
}
&.is-metamask {
border-top-color: #F79220;
}
&.is-parity {
border-top-color: #444;
}
&.is-generate {
border-top-color: $brand-danger;
}
@media (max-width: $screen-sm) {
flex: auto;
width: 100%;
max-width: none;
min-width: 0;
}
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
}
&-name {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
margin: $space-xs 0 $space;
&-icon {
height: 28px;
margin-right: $space-md;
opacity: 0.8;
}
}
&-features {
flex: 1;
padding-left: 1.25rem;
.fa {
margin-right: 4px;
}
.is-danger {
color: $brand-danger;
}
}
&-buttons {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
&-button {
flex: 1;
margin-bottom: $space-sm;
&:last-child {
margin: 0;
}
}
}
}
.WalletType {
margin-bottom: $space * 2;
padding: $space;
text-align: left;
margin: 0 $space $space * 2;
&-title {
margin: 0 0 $space;
text-align: center;
}
&-features {
padding-left: 20px;
padding-left: 28px;
}
&-select {
@media screen and (min-width: $screen-md) {
padding: 0 10px;
}
&-btn {
padding-left: 0;
padding-right: 0;

View File

@ -1,75 +1,276 @@
import React from 'react';
import { Link } from 'react-router-dom';
import translate, { translateRaw } from 'translations';
import { WalletType } from '../GenerateWallet';
import { Link } from 'react-router-dom';
import { NewTabLink } from 'components/ui';
import { ledgerReferralURL, trezorReferralURL } from 'config';
import Template from './Template';
import MetamaskIcon from 'assets/images/wallets/metamask.svg';
import HardwareWalletIcon from 'assets/images/wallets/hardware.svg';
import ParitySignerIcon from 'assets/images/wallets/parity-signer.svg';
import FileIcon from 'assets/images/wallets/file.svg';
import './WalletTypes.scss';
import { HelpLink } from 'components/ui';
import { HELP_ARTICLE, ledgerReferralURL, trezorReferralURL } from 'config';
const WalletTypes: React.SFC<{}> = () => {
const typeInfo = {
[WalletType.Keystore]: {
name: 'X_KEYSTORE2',
bullets: [
'An encrypted JSON file, protected by a password',
'Back it up on a USB drive',
'Cannot be written, printed, or easily transferred to mobile',
'Compatible with Mist, Parity, Geth',
'Provides a single address for sending and receiving'
]
},
[WalletType.Mnemonic]: {
name: 'X_MNEMONIC',
bullets: [
'A 12-word private seed phrase',
'Back it up on paper or USB drive',
'Can be written, printed, and easily typed on mobile, too',
'Compatible with MetaMask, Jaxx, imToken, and more',
'Provides unlimited addresses for sending and receiving'
]
}
interface State {
isShowingGenerate: boolean;
}
export default class WalletTypes extends React.Component<{}, State> {
public state: State = {
isShowingGenerate: false
};
public render() {
const { isShowingGenerate } = this.state;
return (
<div className="WalletTypes Tab-content-pane">
<h1 className="WalletTypes-title">{translate('NAV_GENERATEWALLET')}</h1>
<p className="WalletTypes-subtitle alert alert-warning">
<strong>{translate('NOTIFICATION_TYPE_WARNING')}</strong>:{' '}
{translate('GENERATE_WALLET_WARNING', {
$metamask_link: 'https://metamask.io/',
$ledger_link: ledgerReferralURL,
$trezor_link: trezorReferralURL
})}{' '}
<HelpLink article={HELP_ARTICLE.DIFFERENCE_BETWEEN_PKEY_AND_KEYSTORE}>
{translate('GENERATE_WALLET_HELPLINK_1')}
</HelpLink>
</p>
<Template hideBack={!isShowingGenerate} onBack={this.handleBack}>
{isShowingGenerate ? (
<GenerateOptions />
) : (
<WalletSuggestions showGenerate={this.showGenerate} />
)}
</Template>
);
}
<div className="WalletTypes-types row">
<div className="col-md-1" />
{Object.keys(typeInfo).map((type: keyof typeof typeInfo) => (
<div key={type} className="WalletType col-md-5">
<h2 className="WalletType-title">{translate(typeInfo[type].name)}</h2>
private showGenerate = () => {
this.setState({ isShowingGenerate: true });
};
private handleBack = (ev: React.MouseEvent<HTMLAnchorElement>) => {
ev.preventDefault();
this.setState({ isShowingGenerate: false });
};
}
interface WalletSuggestionsProps {
showGenerate(): void;
}
interface WalletSuggestion {
name: React.ReactElement<string>;
type: string;
icon: string;
bullets: React.ReactElement<string>[];
links: {
text: React.ReactElement<string>;
href?: string;
onClick?(): void;
}[];
}
const WalletSuggestions: React.SFC<WalletSuggestionsProps> = ({ showGenerate }) => {
const suggestions: WalletSuggestion[] = [
{
name: translate('X_HARDWARE_WALLET'),
type: 'hardware',
icon: HardwareWalletIcon,
bullets: [
translate('WALLET_SUGGESTION_HARDWARE_1'),
translate('WALLET_SUGGESTION_HARDWARE_2'),
translate('WALLET_SUGGESTION_HARDWARE_3'),
translate('WALLET_SUGGESTION_HARDWARE_4')
],
links: [
{
text: translate('LEDGER_REFERRAL_1'),
href: ledgerReferralURL
},
{
text: translate('TREZOR_REFERAL'),
href: trezorReferralURL
}
]
},
{
name: translate('X_METAMASK'),
type: 'metamask',
icon: MetamaskIcon,
bullets: [
translate('WALLET_SUGGESTION_METAMASK_1'),
translate('WALLET_SUGGESTION_METAMASK_2'),
translate('WALLET_SUGGESTION_METAMASK_3'),
translate('WALLET_SUGGESTION_METAMASK_4'),
translate('WALLET_SUGGESTION_METAMASK_5')
],
links: [
{
text: translate('ACTION_13', {
$thing: translateRaw('X_METAMASK')
}),
href: 'https://metamask.io/'
}
]
},
{
name: translate('X_PARITYSIGNER'),
type: 'parity',
icon: ParitySignerIcon,
bullets: [
translate('WALLET_SUGGESTION_PARITYSIGNER_1'),
translate('WALLET_SUGGESTION_PARITYSIGNER_2'),
translate('WALLET_SUGGESTION_PARITYSIGNER_3'),
translate('WALLET_SUGGESTION_PARITYSIGNER_4')
],
links: [
{
text: translate('DOWNLOAD_PHONE_APP', { $os: 'iOS' }),
href: 'https://itunes.apple.com/us/app/parity-signer/id1218174838'
},
{
text: translate('DOWNLOAD_PHONE_APP', { $os: 'Android' }),
href: 'https://play.google.com/store/apps/details?id=com.nativesigner'
}
]
}
];
if (process.env.BUILD_DOWNLOADABLE) {
suggestions[1] = {
name: translate('NAV_GENERATEWALLET'),
type: 'generate',
icon: FileIcon,
bullets: [
translate('WALLET_SUGGESTION_GENERATE_1'),
translate('WALLET_SUGGESTION_GENERATE_2'),
translate('WALLET_SUGGESTION_GENERATE_3'),
translate('WALLET_SUGGESTION_GENERATE_4'),
<span key="warning" className="is-danger">
<i className="fa fa-exclamation-triangle" />
{translate('WALLET_SUGGESTION_GENERATE_5')}
</span>
],
links: [
{
text: translate('Generate a Wallet'),
onClick: showGenerate
}
]
};
}
return (
<React.Fragment>
<h1 className="WalletTypes-title">{translate('GENERATE_WALLET_TITLE')}</h1>
<p className="WalletTypes-subtitle">{translate('GENERATE_WALLET_SUGGESTIONS')}</p>
<div className="WalletTypes-suggestions">
{suggestions.map(sug => (
<div className={`WalletSuggestion is-${sug.type}`}>
<h3 className="WalletSuggestion-name">
<img className="WalletSuggestion-name-icon" src={sug.icon} />
{sug.name}
</h3>
<ul className="WalletSuggestion-features">
{sug.bullets.map((b, idx) => (
<li key={idx} className="WalletSuggestion-features-feature">
{b}
</li>
))}
</ul>
<div className="WalletSuggestion-buttons">
{sug.links.map(link => {
if (link.onClick) {
return (
<button
onClick={link.onClick}
className="WalletSuggestion-buttons-button btn btn-default"
>
{link.text}
</button>
);
}
if (link.href) {
return (
<NewTabLink
href={link.href}
className="WalletSuggestion-buttons-button btn btn-default"
>
{link.text}
</NewTabLink>
);
}
})}
</div>
</div>
))}
</div>
{!process.env.BUILD_DOWNLOADABLE && (
<React.Fragment>
<div className="WalletTypes-divider">{translate('OR')}</div>
<div className="WalletTypes-download">
<NewTabLink
href="https://download.mycrypto.com"
className="WalletTypes-download-button btn btn-primary btn-lg"
>
{translate('WALLET_SUGGESTION_DESKTOP_APP')}
</NewTabLink>
<p className="WalletTypes-download-desc">
{translate('WALLET_SUGGESTION_DESKTOP_APP_DESC')}
</p>
</div>
</React.Fragment>
)}
</React.Fragment>
);
};
const GenerateOptions: React.SFC<{}> = () => {
const walletTypes = [
{
type: WalletType.Keystore,
name: translateRaw('X_KEYSTORE2'),
bullets: [
translate('GENERATE_WALLET_KEYSTORE_1'),
translate('GENERATE_WALLET_KEYSTORE_2'),
translate('GENERATE_WALLET_KEYSTORE_3'),
translate('GENERATE_WALLET_KEYSTORE_4'),
translate('GENERATE_WALLET_KEYSTORE_5')
]
},
{
type: WalletType.Mnemonic,
name: translateRaw('X_MNEMONIC'),
bullets: [
translate('GENERATE_WALLET_MNEMONIC_1'),
translate('GENERATE_WALLET_MNEMONIC_2'),
translate('GENERATE_WALLET_MNEMONIC_3'),
translate('GENERATE_WALLET_MNEMONIC_4'),
translate('GENERATE_WALLET_MNEMONIC_5')
]
}
];
return (
<React.Fragment>
<h1 className="WalletTypes-title">{translate('NAV_GENERATEWALLET')}</h1>
<div className="WalletTypes-types">
{walletTypes.map(wallet => (
<div key={wallet.type} className="WalletType">
<h3 className="WalletType-title">{wallet.name}</h3>
<ul className="WalletType-features">
{typeInfo[type].bullets.map(bullet => (
<li key={bullet} className="WalletType-features-feature">
{translate(bullet)}
{wallet.bullets.map((bullet, idx) => (
<li key={idx} className="WalletType-features-feature">
{bullet}
</li>
))}
</ul>
<div className="WalletType-select">
<Link
className="WalletType-select-btn btn btn-primary btn-block"
to={`/generate/${type}`}
to={`/generate/${wallet.type}`}
>
{translate('GENERATE_THING', { $thing: translateRaw(typeInfo[type].name) })}
{translate('GENERATE_THING', { $thing: wallet.name })}
</Link>
</div>
</div>
))}
</div>
</div>
</React.Fragment>
);
};
export default WalletTypes;

View File

@ -70,7 +70,8 @@ export function translateRaw(key: string, variables?: { [name: string]: string }
// Find each variable and replace it in the translated string
let str = translatedString;
Object.keys(variables).forEach(v => {
str = str.replace(v, variables[v]);
const re = new RegExp(v.replace('$', '\\$'), 'g');
str = str.replace(re, variables[v]);
});
return str;
}

View File

@ -56,6 +56,7 @@
"X_PRIVKEY2": "Private Key",
"X_PRIVKEYDESC": "This is the unencrypted text version of your private key, meaning no password is necessary. If someone were to find your unencrypted private key, they could access your wallet without a password. For this reason, encrypted versions are typically recommended. ",
"X_SAVE": "Save ",
"X_WALLET": "Wallet ",
"X_TRY_AGAIN": "Try again",
"X_CUSTOM": "Custom ",
"CX_WARNING_1": "Make sure you have **external backups** of any wallets you store here. Many things could happen that would cause you to lose the data in this Chrome Extension, including uninstalling and reinstalling the extension. This extension is a way to easily access your wallets, **not** a way to back them up. ",
@ -73,6 +74,7 @@
"ADD_LEDGER_SCAN": "Connect to Ledger Wallet ",
"ADD_METAMASK": "Connect to MetaMask ",
"X_TREZOR": "TREZOR ",
"X_HARDWARE_WALLET": "Hardware Wallet",
"ADD_TREZOR_SCAN": "Connect to TREZOR ",
"X_PARITYSIGNER": "Parity Signer ",
"ADD_PARITY_DESC": "Connect & sign via your Parity Signer mobile app ",
@ -392,8 +394,40 @@
"ONBOARD_RESUME": "It looks like you didn't finish reading through these slides last time. ProTip: Finish reading through the slides 😉",
"DONATE_CURRENCY": "Donate $currency",
"NOTIFICATION_TYPE_WARNING": "Warning",
"GENERATE_WALLET_TITLE": "Choose The Wallet Thats Right For You",
"GENERATE_WALLET_SUGGESTIONS": "We suggest using one of the following secure wallets",
"GENERATE_WALLET_WARNING": "Managing your own keys can be risky and a single mistake can lead to irrecoverable loss. If you are new to cryptocurrencies, we strongly recommend using [MetaMask]($metamask_link) or purchasing a [Ledger]($ledger_link) or [TREZOR]($trezor_link) hardware wallet.",
"GENERATE_WALLET_HELPLINK_1": "Learn more about different wallet types & staying secure",
"GENERATE_WALLET_KEYSTORE_1": "An encrypted JSON file, protected by a password",
"GENERATE_WALLET_KEYSTORE_2": "Back it up on a USB drive",
"GENERATE_WALLET_KEYSTORE_3": "Cannot be written, printed, or easily transferred to mobile",
"GENERATE_WALLET_KEYSTORE_4": "Compatible with Mist, Parity, Geth",
"GENERATE_WALLET_KEYSTORE_5": "Provides a single address for sending and receiving",
"GENERATE_WALLET_MNEMONIC_1": "A 12-word private seed phrase",
"GENERATE_WALLET_MNEMONIC_2": "Back it up on paper or USB drive",
"GENERATE_WALLET_MNEMONIC_3": "Can be written, printed, and easily typed on mobile, too",
"GENERATE_WALLET_MNEMONIC_4": "Compatible with MetaMask, Jaxx, imToken, and more",
"GENERATE_WALLET_MNEMONIC_5": "Provides unlimited addresses for sending and receiving",
"WALLET_SUGGESTION_HARDWARE_1": "A dedicated device that generates and holds onto your private key",
"WALLET_SUGGESTION_HARDWARE_2": "Transactions are signed on the device",
"WALLET_SUGGESTION_HARDWARE_3": "Choice between Trezor or Ledger",
"WALLET_SUGGESTION_HARDWARE_4": "Costs money, may be on backorder",
"WALLET_SUGGESTION_METAMASK_1": "A browser extension that generates and holds onto your private key",
"WALLET_SUGGESTION_METAMASK_2": "Transactions are signed and sent through the extension",
"WALLET_SUGGESTION_METAMASK_3": "Works on any Web3 enabled site",
"WALLET_SUGGESTION_METAMASK_4": "Doesnt work on desktop app",
"WALLET_SUGGESTION_METAMASK_5": "Free to download",
"WALLET_SUGGESTION_PARITYSIGNER_1": "A mobile app that generates and holds onto your private key",
"WALLET_SUGGESTION_PARITYSIGNER_2": "Transactions are signed via QR codes",
"WALLET_SUGGESTION_PARITYSIGNER_3": "Generate multiple addresses",
"WALLET_SUGGESTION_PARITYSIGNER_4": "Free to download",
"WALLET_SUGGESTION_GENERATE_1": "Generate your own private key",
"WALLET_SUGGESTION_GENERATE_2": "Choice between keystore file and mnemonic phrase",
"WALLET_SUGGESTION_GENERATE_3": "Compatible with almost all wallets",
"WALLET_SUGGESTION_GENERATE_4": "Free to generate",
"WALLET_SUGGESTION_GENERATE_5": "Commonly a target for phishing and hacking",
"WALLET_SUGGESTION_DESKTOP_APP": "Download the MyCrypto Desktop App",
"WALLET_SUGGESTION_DESKTOP_APP_DESC": "Where you can generate your own private key in a secure, sandboxed environment",
"PREFOOTER_WARNING": "MyCrypto.com does not hold your keys for you. We cannot access accounts, recover keys, reset passwords, nor reverse transactions. Protect your keys & always check that you are on the correct URL.",
"PREFOOTER_SECURITY_WARNING": "You are responsible for your security.",
"FOOTER_ABOUT": "MyCrypto is an open-source, client-side tool for generating ether wallets, handling ERC-20 tokens, and interacting with the blockchain more easily. Developed by and for the community since 2015, were focused on building awesome products that put the power in peoples hands.",
@ -458,6 +492,7 @@
"ACTION_12": "Download",
"ACTION_13": "Download $thing",
"ACTION_14": "Continue",
"DOWNLOAD_PHONE_APP": "Download for $os",
"CONFIRM_HARDWARE_WALLET_TRANSACTION": "Confirm transaction on hardware wallet",
"WALLET_LOGOUT_MODAL_TITLE": "You are about to log out",
"WALLET_LOGOUT_MODAL_DESC": "Leaving this page will log you out. Are you sure you want to continue?",
@ -477,16 +512,9 @@
"VIEW_ONLY_RECENT": "Select a recent address",
"VIEW_ONLY_ENTER": "Enter an address (e.g. 0x4bbeEB066eD09...)",
"GO_TO_ACCOUNT": "Go to Account",
"INSECURE_WALLET_TYPE_TITLE": "This is not a recommended way to access your wallet",
"INSECURE_WALLET_TYPE_DESC": "Entering your $wallet_type on a website is dangerous. If our website is compromised, or you accidentally visit a phishing website, you could lose your funds. Before you continue, consider:",
"WALLET_WARNING_CHECK": "If you must use your $wallet_type online, please check the URL & SSL certificate. It should say `https://www.mycrypto.com` & `MyCrypto, Inc (US)` in your URL bar.",
"INSECURE_WALLET_WARNING_1": "I acknowledge that I can and should use MetaMask or a Hardware Wallet",
"INSECURE_WALLET_WARNING_2": "I acknowledge that I can and should download and run MyCrypto locally",
"INSECURE_WALLET_WARNING_3": "I have checked the URL and SSL certificate to make sure this is the real MyCrypto",
"INSECURE_WALLET_RECOMMEND_1": "Using [MetaMask]($metamask_article) or a [Hardware Wallet]($hardware_wallet_article) to access your wallet",
"INSECURE_WALLET_RECOMMEND_2": "[Downloading MyCrypto and running it offline & locally]($download_mycrypto)",
"INSECURE_WALLET_RECOMMEND_3": "Reading [How to Protect Yourself and Your Funds]($secure_your_eth_article)",
"INSECURE_WALLET_DEPRECATION": "**Warning:** Accessing this type of wallet via the web interface will be disabled soon. Youll need to run [MyCrypto Desktop](https://download.mycrypto.com), [Parity Signer](https://paritytech.io/send-transactions-with-mycrypto-beta-and-parity-signer/), or [MetaMask](https://metamask.io/) going forward. You can learn more [here](https://github.com/MyCryptoHQ/MyCrypto/pull/1466).",
"INSECURE_WALLET_TYPE_TITLE": "$wallet_type wallets are disabled online",
"INSECURE_WALLET_TYPE_DESC": "Entering your $wallet_type on any website is dangerous. If MyCrypto.com was compromised, or you accidentally visited a phishing website, you could lose your funds. Because of that, we have disabled the use of $wallet_type wallets through the website. In order to access your account, please download MyCrypto and run it locally.",
"INSECURE_WALLET_GO_BACK": "Go back to wallet selection",
"DONT_HAVE_WALLET_PROMPT": "Dont have a wallet?",
"DL_WALLET_WARNING_1": "**Don't lose it!** It can't be recovered if you lose it.",
"DL_WALLET_WARNING_2": "**Don't share it!** Your funds will be stolen if you use this file on a malicious site.",