{wallet.index + 1}
diff --git a/common/components/WalletDecrypt/Mnemonic.jsx b/common/components/WalletDecrypt/Mnemonic.jsx
index 2c6bb22a..98cc58ce 100644
--- a/common/components/WalletDecrypt/Mnemonic.jsx
+++ b/common/components/WalletDecrypt/Mnemonic.jsx
@@ -1,27 +1,129 @@
import React, { Component } from 'react';
-import translate from 'translations';
+import translate, { translateRaw } from 'translations';
+import { validateMnemonic, mnemonicToSeed } from 'bip39';
+
+import DeterministicWalletsModal from './DeterministicWalletsModal';
+
+import DPATHS from 'config/dpaths.js';
+const DEFAULT_PATH = DPATHS.MNEMONIC[0].value;
+
+type State = {
+ phrase: string,
+ pass: string,
+ seed: string,
+ dPath: string
+};
export default class MnemonicDecrypt extends Component {
+ props: { onUnlock: any => void };
+ state: State = {
+ phrase: '',
+ pass: '',
+ seed: '',
+ dPath: DEFAULT_PATH
+ };
+
render() {
+ const { phrase, seed, dPath, pass } = this.state;
+ const isValidMnemonic = validateMnemonic(phrase);
+
return (
-
- {translate('ADD_Radio_2_alt')}
-
+
+
+ {translate('ADD_Radio_5')}
+
+
+ Password (optional):
+
+
+ {isValidMnemonic &&
+
+
+ }
+
+
);
}
+
+ onPasswordChange = (e: SyntheticInputEvent) => {
+ this.setState({ pass: e.target.value });
+ };
+
+ onMnemonicChange = (e: SyntheticInputEvent) => {
+ this.setState({ phrase: e.target.value });
+ };
+
+ onDWModalOpen = (e: SyntheticInputEvent) => {
+ const { phrase, pass } = this.state;
+
+ if (!validateMnemonic(phrase)) return;
+
+ try {
+ let seed = mnemonicToSeed(phrase.trim(), pass).toString('hex');
+ this.setState({ seed });
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ _handleCancel = () => {
+ this.setState({ seed: '' });
+ };
+
+ _handlePathChange = (dPath: string) => {
+ this.setState({ dPath });
+ };
+
+ _handleUnlock = (address, index) => {
+ const { phrase, pass, dPath } = this.state;
+
+ this.props.onUnlock({
+ path: `${dPath}/${index}`,
+ pass,
+ phrase,
+ address
+ });
+
+ this.setState({
+ seed: '',
+ pass: '',
+ phrase: ''
+ });
+ };
}
diff --git a/common/components/WalletDecrypt/Trezor.jsx b/common/components/WalletDecrypt/Trezor.jsx
index 28a0ec12..dba45ddc 100644
--- a/common/components/WalletDecrypt/Trezor.jsx
+++ b/common/components/WalletDecrypt/Trezor.jsx
@@ -5,7 +5,7 @@ import translate from 'translations';
import TrezorConnect from 'vendor/trezor-connect';
import DeterministicWalletsModal from './DeterministicWalletsModal';
import TrezorWallet from 'libs/wallet/trezor';
-import DPATHS from 'config/dpaths.json';
+import DPATHS from 'config/dpaths.js';
const DEFAULT_PATH = DPATHS.TREZOR[0].value;
type State = {
@@ -65,8 +65,8 @@ export default class TrezorDecrypt extends Component {
});
};
- _handleUnlock = (address: string) => {
- this.props.onUnlock(new TrezorWallet(address, this.state.dPath));
+ _handleUnlock = (address: string, index: number) => {
+ this.props.onUnlock(new TrezorWallet(address, this.state.dPath, index));
};
render() {
@@ -116,7 +116,7 @@ export default class TrezorDecrypt extends Component {
onCancel={this._handleCancel}
onConfirmAddress={this._handleUnlock}
onPathChange={this._handlePathChange}
- walletType={translate('x_Trezor')}
+ walletType={translate('x_Trezor', true)}
/>
);
diff --git a/common/components/WalletDecrypt/index.jsx b/common/components/WalletDecrypt/index.jsx
index 33d3cdff..4935b81f 100644
--- a/common/components/WalletDecrypt/index.jsx
+++ b/common/components/WalletDecrypt/index.jsx
@@ -9,8 +9,14 @@ import LedgerNanoSDecrypt from './LedgerNano';
import TrezorDecrypt from './Trezor';
import ViewOnlyDecrypt from './ViewOnly';
import map from 'lodash/map';
-import { unlockPrivateKey, unlockKeystore, setWallet } from 'actions/wallet';
+import {
+ unlockPrivateKey,
+ unlockKeystore,
+ unlockMnemonic,
+ setWallet
+} from 'actions/wallet';
import { connect } from 'react-redux';
+import isEmpty from 'lodash/isEmpty';
const WALLETS = {
'keystore-file': {
@@ -34,7 +40,8 @@ const WALLETS = {
'mnemonic-phrase': {
lid: 'x_Mnemonic',
component: MnemonicDecrypt,
- disabled: true
+ initialParams: {},
+ unlock: unlockMnemonic
},
'ledger-nano-s': {
lid: 'x_Ledger',
@@ -129,7 +136,7 @@ export class WalletDecrypt extends Component {
const decryptionComponent = this.getDecryptionComponent();
return (
-
+
{translate('decrypt_Access')}
@@ -164,8 +171,14 @@ export class WalletDecrypt extends Component {
};
onUnlock = (payload: any) => {
+ // some components (TrezorDecrypt) don't take an onChange prop, and thus this.state.value will remain unpopulated.
+ // in this case, we can expect the payload to contain the unlocked wallet info.
+ const unlockValue =
+ this.state.value && !isEmpty(this.state.value)
+ ? this.state.value
+ : payload;
this.props.dispatch(
- WALLETS[this.state.selectedWalletKey].unlock(this.state.value || payload)
+ WALLETS[this.state.selectedWalletKey].unlock(unlockValue)
);
};
}
diff --git a/common/components/ui/Dropdown.jsx b/common/components/ui/Dropdown.jsx
index 7fe238ea..e7948c58 100644
--- a/common/components/ui/Dropdown.jsx
+++ b/common/components/ui/Dropdown.jsx
@@ -27,9 +27,10 @@ export default class DropdownComponent extends Component<
render() {
const { options, value, ariaLabel, extra } = this.props;
+ const { expanded } = this.state;
return (
-
+
extends Component<
{this.formatTitle(value)}
- {this.state.expanded &&
+ {expanded &&
{options.map((option, i) => {
return (
diff --git a/common/components/ui/NewTabLink.jsx b/common/components/ui/NewTabLink.jsx
new file mode 100644
index 00000000..547ca34d
--- /dev/null
+++ b/common/components/ui/NewTabLink.jsx
@@ -0,0 +1,48 @@
+// @flow
+import * as React from 'react';
+
+type AAttributes = {
+ charset?: string,
+ coords?: string,
+ download?: string,
+ href: string,
+ hreflang?: string,
+ media?: string,
+ name?: string,
+ rel?:
+ | 'alternate'
+ | 'author'
+ | 'bookmark'
+ | 'external'
+ | 'help'
+ | 'license'
+ | 'next'
+ | 'nofollow'
+ | 'noreferrer'
+ | 'noopener'
+ | 'prev'
+ | 'search'
+ | 'tag',
+ rev?: string,
+ shape?: 'default' | 'rect' | 'circle' | 'poly',
+ target?: '_blank' | '_parent' | '_self' | '_top',
+ type?: string
+};
+
+type NewTabLinkProps = {
+ content?: React.Element | string,
+ children?: React.Element | string,
+ rest?: AAttributes
+};
+
+const NewTabLink = ({
+ /* eslint-disable */
+ content, // ESLINT: prop-types validation error, to be fixed in #122
+ children /* eslint-enable */,
+ ...rest
+}: NewTabLinkProps) =>
+
+ {content || children} {/* Keep content for short-hand text insertion */}
+ ;
+
+export default NewTabLink;
diff --git a/common/components/ui/SimpleDropDown.jsx b/common/components/ui/SimpleDropDown.jsx
new file mode 100644
index 00000000..fa951cb4
--- /dev/null
+++ b/common/components/ui/SimpleDropDown.jsx
@@ -0,0 +1,62 @@
+// @flow
+import React, { Component } from 'react';
+
+type Props = {
+ value?: string,
+ options: string[],
+ onChange: (value: string) => void
+};
+
+export default class SimpleDropDown extends Component {
+ props: Props;
+ state: {
+ expanded: boolean
+ } = {
+ expanded: false
+ };
+
+ toggleExpanded = () => {
+ this.setState(state => {
+ return { expanded: !state.expanded };
+ });
+ };
+
+ onClick = (event: SyntheticInputEvent) => {
+ const value = event.target.getAttribute('data-value') || '';
+ this.props.onChange(value);
+ this.setState({ expanded: false });
+ };
+
+ render() {
+ const { options, value } = this.props;
+ const { expanded } = this.state;
+ return (
+
+ );
+ }
+}
diff --git a/common/components/ui/SimpleDropdown.jsx b/common/components/ui/SimpleDropdown.jsx
deleted file mode 100644
index d2be05c4..00000000
--- a/common/components/ui/SimpleDropdown.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-// @flow
-import React, { Component } from 'react';
-
-type Props = {
- value?: T,
- options: Array,
- onChange: (event: SyntheticInputEvent) => void
-};
-
-export default class SimpleDropDown extends Component {
- props: Props;
-
- render() {
- return (
-
-
-
- );
- }
-}
diff --git a/common/components/ui/SimpleSelect.jsx b/common/components/ui/SimpleSelect.jsx
new file mode 100644
index 00000000..49cca406
--- /dev/null
+++ b/common/components/ui/SimpleSelect.jsx
@@ -0,0 +1,30 @@
+// @flow
+import React, { Component } from 'react';
+
+type Props = {
+ value?: string,
+ options: string[],
+ onChange: (event: SyntheticInputEvent) => void
+};
+
+export default class SimpleSelect extends Component {
+ props: Props;
+
+ render() {
+ return (
+
+ );
+ }
+}
diff --git a/common/components/ui/UnlockHeader.jsx b/common/components/ui/UnlockHeader.jsx
index 05e6e370..899e8bcf 100644
--- a/common/components/ui/UnlockHeader.jsx
+++ b/common/components/ui/UnlockHeader.jsx
@@ -3,13 +3,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import translate from 'translations';
import WalletDecrypt from 'components/WalletDecrypt';
-import BaseWallet from 'libs/wallet/base';
+import type { IWallet } from 'libs/wallet/IWallet';
import { connect } from 'react-redux';
import type { State } from 'reducers';
type Props = {
title: string,
- wallet: BaseWallet
+ wallet: IWallet
};
export class UnlockHeader extends React.Component {
@@ -48,13 +48,7 @@ export class UnlockHeader extends React.Component {
{translate(this.props.title)}
- {this.state.expanded &&
-
-
- {/* @@if (site === 'cx' ) { }
- @@if (site === 'mew' ) { } */}
- }
-
+ {this.state.expanded && }
{this.state.expanded && }
);
diff --git a/common/components/ui/index.js b/common/components/ui/index.js
index efb77a31..9bcf6caa 100644
--- a/common/components/ui/index.js
+++ b/common/components/ui/index.js
@@ -5,3 +5,4 @@ export { default as Identicon } from './Identicon';
export { default as Modal } from './Modal';
export { default as UnlockHeader } from './UnlockHeader';
export { default as QRCode } from './QRCode';
+export { default as NewTabLink } from './NewTabLink.jsx';
diff --git a/common/config/bity.js b/common/config/bity.js
index 3aeeb40b..ea38a749 100644
--- a/common/config/bity.js
+++ b/common/config/bity.js
@@ -1,39 +1,52 @@
-export default {
- serverURL: 'https://bity.myetherapi.com',
- bityAPI: 'https://bity.com/api',
- ethExplorer: 'https://etherscan.io/tx/[[txHash]]',
- btcExplorer: 'https://blockchain.info/tx/[[txHash]]',
+import { ETHTxExplorer, BTCTxExplorer } from './data';
+
+type SupportedDestinationKind = 'ETH' | 'BTC' | 'REP';
+
+const serverURL = 'https://bity.myetherapi.com';
+const bityURL = 'https://bity.com/api';
+const BTCMin = 0.01;
+const BTCMax = 3;
+
+// while Bity is supposedly OK with any order that is at least 0.01 BTC Worth, the order will fail if you send 0.01 BTC worth of ETH.
+// This is a bad magic number, but will suffice for now
+// value = percent higher/lower than 0.01 BTC worth
+const buffers = {
+ ETH: 0.1,
+ REP: 0.2
+};
+
+// rate must be BTC[KIND]
+export function kindMin(
+ BTCKINDRate: number,
+ kind: SupportedDestinationKind
+): number {
+ const kindMin = BTCKINDRate * BTCMin;
+ return kindMin + kindMin * buffers[kind];
+}
+
+// rate must be BTC[KIND]
+export function kindMax(
+ BTCKINDRate: number,
+ kind: SupportedDestinationKind
+): number {
+ const kindMax = BTCKINDRate * BTCMax;
+ return kindMax - kindMax * buffers[kind];
+}
+
+const info = {
+ serverURL,
+ bityURL,
+ ETHTxExplorer,
+ BTCTxExplorer,
+ BTCMin,
+ BTCMax,
validStatus: ['RCVE', 'FILL', 'CONF', 'EXEC'],
invalidStatus: ['CANC'],
- // while Bity is supposedly OK with any order that is at least 0.01 BTC Worth, the order will fail if you send 0.01 BTC worth of ETH.
- // This is a bad magic number, but will suffice for now
- ETHBuffer: 0.1, // percent higher/lower than 0.01 BTC worth
- REPBuffer: 0.2, // percent higher/lower than 0.01 BTC worth
- BTCMin: 0.01,
- BTCMax: 3,
- ETHMin: function(BTCETHRate: number) {
- const ETHMin = BTCETHRate * this.BTCMin;
- const ETHMinWithPadding = ETHMin + ETHMin * this.ETHBuffer;
- return ETHMinWithPadding;
- },
- ETHMax: function(BTCETHRate: number) {
- const ETHMax = BTCETHRate * this.BTCMax;
- const ETHMaxWithPadding = ETHMax - ETHMax * this.ETHBuffer;
- return ETHMaxWithPadding;
- },
- REPMin: function(BTCREPRate: number) {
- const REPMin = BTCREPRate * this.BTCMin;
- const REPMinWithPadding = REPMin + REPMin * this.REPBuffer;
- return REPMinWithPadding;
- },
- REPMax: function(BTCREPRate: number) {
- const REPMax = BTCREPRate * this.BTCMax;
- const REPMaxWithPadding = REPMax - REPMax * this.ETHBuffer;
- return REPMaxWithPadding;
- },
postConfig: {
headers: {
'Content-Type': 'application/json; charset:UTF-8'
}
}
};
+
+export default info;
diff --git a/common/config/data.js b/common/config/data.js
index a509c051..f13f5c1b 100644
--- a/common/config/data.js
+++ b/common/config/data.js
@@ -2,7 +2,7 @@
import { RPCNode } from 'libs/nodes';
// Displays in the header
-export const VERSION = '4.0.0';
+export const VERSION = '4.0.0 (Alpha 0.0.2)';
// Displays at the top of the site, make message empty string to remove.
// Type can be primary, warning, danger, success, or info.
@@ -15,14 +15,25 @@ export const ANNOUNCEMENT_MESSAGE = `
If you're interested in recieving updates about the MyEtherWallet V4 Alpha, you can subscribe via mailchimp :)
`;
-export const DONATION_ADDRESSES_MAP = {
+const etherScan = 'https://etherscan.io';
+const blockChainInfo = 'https://blockchain.info';
+const ethPlorer = 'https://ethplorer.io';
+
+export const ETHTxExplorer = (txHash: string): string =>
+ `${etherScan}/tx/${txHash}`;
+export const BTCTxExplorer = (txHash: string): string =>
+ `${blockChainInfo}/tx/${txHash}`;
+export const ETHAddressExplorer = (address: string): string =>
+ `${etherScan}/address/${address}`;
+export const ETHTokenExplorer = (address: string): string =>
+ `${ethPlorer}/address/${address}`;
+
+export const donationAddressMap = {
BTC: '1MEWT2SGbqtz6mPCgFcnea8XmWV5Z4Wc6',
ETH: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8',
REP: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8'
};
-export const donationAddressMap = DONATION_ADDRESSES_MAP;
-
export const gasPriceDefaults = {
gasPriceMinGwei: 1,
gasPriceMaxGwei: 60
@@ -142,12 +153,12 @@ export type NetworkConfig = {
unit: string,
blockExplorer?: {
name: string,
- tx: string,
- address: string
+ tx: Function,
+ address: Function
},
tokenExplorer?: {
name: string,
- address: string
+ address: Function
},
chainId: number,
tokens: Token[],
@@ -167,13 +178,13 @@ export const NETWORKS: { [key: string]: NetworkConfig } = {
unit: 'ETH',
chainId: 1,
blockExplorer: {
- name: 'https://etherscan.io',
- tx: 'https://etherscan.io/tx/[[txHash]]',
- address: 'https://etherscan.io/address/[[address]]'
+ name: etherScan,
+ tx: ETHTxExplorer,
+ address: ETHAddressExplorer
},
tokenExplorer: {
- name: 'Ethplorer.io',
- address: 'https://ethplorer.io/address/[[address]]'
+ name: ethPlorer,
+ address: ETHTokenExplorer
},
tokens: require('./tokens/eth').default,
contracts: require('./contracts/eth.json')
diff --git a/common/config/dpaths.js b/common/config/dpaths.js
new file mode 100644
index 00000000..b73ba329
--- /dev/null
+++ b/common/config/dpaths.js
@@ -0,0 +1,50 @@
+const ETH_DEFAULT = {
+ label: 'Default (ETH)',
+ value: "m/44'/60'/0'/0"
+};
+
+const ETH_TREZOR = {
+ label: 'TREZOR (ETH)',
+ value: "m/44'/60'/0'/0"
+};
+
+const ETH_LEDGER = {
+ label: 'Ledger (ETH)',
+ value: "m/44'/60'/0'"
+};
+
+const ETC_LEDGER = {
+ label: 'Ledger (ETC)',
+ value: "m/44'/60'/160720'/0'"
+};
+
+const ETC_TREZOR = {
+ label: 'TREZOR (ETC)',
+ value: "m/44'/61'/0'/0"
+};
+
+const TESTNET = {
+ label: 'Testnet',
+ value: "m/44'/1'/0'/0"
+};
+
+const EXPANSE = {
+ label: 'Expanse',
+ value: "m/44'/40'/0'/0"
+};
+
+const TREZOR = [ETH_TREZOR, ETC_TREZOR, TESTNET];
+
+const MNEMONIC = [
+ ETH_DEFAULT,
+ ETH_LEDGER,
+ ETC_LEDGER,
+ ETC_TREZOR,
+ TESTNET,
+ EXPANSE
+];
+
+export default {
+ TREZOR,
+ MNEMONIC
+};
diff --git a/common/config/dpaths.json b/common/config/dpaths.json
deleted file mode 100644
index 10460eb3..00000000
--- a/common/config/dpaths.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "TREZOR": [
- {
- "label": "TREZOR (ETH)",
- "value": "m/44'/60'/0'/0"
- },
- {
- "label": "TREZOR (ETC)",
- "value": "m/44'/61'/0'/0"
- },
- {
- "label": "Testnet",
- "value": "m/44'/1'/0'/0"
- }
- ]
-}
diff --git a/common/containers/App/Notifications.jsx b/common/containers/App/Notifications.jsx
index a2465693..e623ec72 100644
--- a/common/containers/App/Notifications.jsx
+++ b/common/containers/App/Notifications.jsx
@@ -1,6 +1,8 @@
// @flow
+import './Notifications.scss';
import React from 'react';
import { connect } from 'react-redux';
+import classnames from 'classnames';
import { closeNotification } from 'actions/notifications';
import type { Notification } from 'actions/notifications';
@@ -11,36 +13,23 @@ class NotificationRow extends React.Component {
};
render() {
const { msg, level } = this.props.notification;
- let className = '';
-
- switch (level) {
- case 'danger':
- className = 'alert-danger';
- break;
- case 'success':
- className = 'alert-success';
- break;
- case 'warning':
- className = 'alert-warning';
- break;
- }
+ const notifClass = classnames({
+ Notification: true,
+ alert: true,
+ [`alert-${level}`]: !!level
+ });
return (
-
+
{level}
-
@@ -62,7 +51,7 @@ export class Notifications extends React.Component {
return null;
}
return (
-
+
{this.props.notifications.map((n, i) =>
-
+
{React.cloneElement(children, { languageSelection })}
diff --git a/common/containers/Tabs/Contracts/index.jsx b/common/containers/Tabs/Contracts/index.jsx
index f8736928..37602e5b 100644
--- a/common/containers/Tabs/Contracts/index.jsx
+++ b/common/containers/Tabs/Contracts/index.jsx
@@ -60,32 +60,30 @@ class Contracts extends Component {
}
return (
-
-
-
-
-
- {' '}
- or{' '}
-
-
-
-
- {content}
-
-
-
+
+
+
+ {' '}
+ or{' '}
+
+
+
+
+
+ {content}
+
+
);
}
diff --git a/common/containers/Tabs/Contracts/index.scss b/common/containers/Tabs/Contracts/index.scss
index feb3d6df..8e54c3bc 100644
--- a/common/containers/Tabs/Contracts/index.scss
+++ b/common/containers/Tabs/Contracts/index.scss
@@ -2,29 +2,29 @@
@import "common/sass/mixins";
.Contracts {
- &-header {
- text-align: center;
- margin: 2rem 0;
+ &-header {
+ margin: 0;
+ text-align: center;
- &-tab {
- @include reset-button;
- color: $ether-blue;
+ &-tab {
+ @include reset-button;
+ color: $ether-blue;
- &:hover,
- &:active {
- opacity: 0.8;
- }
+ &:hover,
+ &:active {
+ opacity: 0.8;
+ }
- &.is-active {
- &,
- &:hover,
- &:active {
- color: $text-color;
- cursor: default;
- opacity: 1;
- font-weight: 500;
- }
- }
- }
- }
+ &.is-active {
+ &,
+ &:hover,
+ &:active {
+ color: $text-color;
+ cursor: default;
+ opacity: 1;
+ font-weight: 500;
+ }
+ }
+ }
+ }
}
diff --git a/common/containers/Tabs/ENS/components/ENS.jsx b/common/containers/Tabs/ENS/components/ENS.jsx
new file mode 100644
index 00000000..b99f73fa
--- /dev/null
+++ b/common/containers/Tabs/ENS/components/ENS.jsx
@@ -0,0 +1,28 @@
+// @flow
+import * as React from 'react';
+import Title from './Title';
+import GeneralInfoPanel from './GeneralInfoPanel';
+import UnfinishedBanner from './UnfinishedBanner';
+type ContainerTabPaneActiveProps = {
+ children: React.Element
+};
+
+const ContainerTabPaneActive = ({ children }: ContainerTabPaneActiveProps) =>
+ ;
+
+const ENS = () =>
+
+
+
+
+ ;
+
+export default ENS;
diff --git a/common/containers/Tabs/ENS/components/GeneralInfoPanel/GeneralInfoNode.jsx b/common/containers/Tabs/ENS/components/GeneralInfoPanel/GeneralInfoNode.jsx
new file mode 100644
index 00000000..02aa4ccd
--- /dev/null
+++ b/common/containers/Tabs/ENS/components/GeneralInfoPanel/GeneralInfoNode.jsx
@@ -0,0 +1,52 @@
+// @flow
+import * as React from 'react';
+import type { HeaderProps, ListProps, NodeProps, NodeState } from './types';
+
+const InfoHeader = ({ children, onClickHandler }: HeaderProps) =>
+
+ + {children}
+ ;
+
+const InfoList = ({ children, isOpen }: ListProps) =>
+ isOpen
+ ?
+ : null;
+
+/*
+TODO: After #122: export default class GeneralInfoNode extends React.Component <
+ NodeProps,
+ NodeState
+ >
+*/
+export default class GeneralInfoNode extends React.Component {
+ props: NodeProps;
+ state: NodeState;
+
+ state = {
+ isOpen: false
+ };
+
+ toggleVisibility = () =>
+ this.setState(prevState => ({ isOpen: !prevState.isOpen }));
+
+ render() {
+ const {
+ toggleVisibility,
+ props: { innerList, name, headerContent },
+ state: { isOpen }
+ } = this;
+
+ return (
+
+
+ {headerContent}
+
+
+ {innerList}
+
+
+ );
+ }
+}
diff --git a/common/containers/Tabs/ENS/components/GeneralInfoPanel/index.jsx b/common/containers/Tabs/ENS/components/GeneralInfoPanel/index.jsx
new file mode 100644
index 00000000..844cff11
--- /dev/null
+++ b/common/containers/Tabs/ENS/components/GeneralInfoPanel/index.jsx
@@ -0,0 +1,131 @@
+// @flow
+import * as React from 'react';
+import GeneralInfoNode from './GeneralInfoNode';
+import { NewTabLink } from 'components/ui';
+import type { InfoNode } from './types';
+
+const generalInfoNodes: InfoNode[] = [
+ {
+ name: 'ensPrep',
+ headerContent: '1. Preparation',
+ innerList: [
+
+ Decide which account you wish to own the name & ensure you have multiple
+ backups of that account.
+ ,
+
+ Decide the maximum amount of ETH you are willing to pay for the name
+ (your Bid Amount). Ensure that account has enough to cover your
+ bid + 0.01 ETH for gas.
+
+ ]
+ },
+ {
+ name: 'ensAuct',
+ headerContent: '2. Start an Auction / Place a Bid',
+ innerList: [
+ Bidding period lasts 3 days (72 hours).,
+
+ You will enter the name, Actual Bid Amount,{' '}
+ Bid Mask, which is protected by a Secret Phrase
+ ,
+
+ This places your bid, but this information is kept secret until you
+ reveal it.
+
+ ]
+ },
+ {
+ name: 'ensReveal',
+ headerContent: '3. Reveal your Bid',
+ innerList: [
+
+
+ If you do not reveal your bid, you will not be refunded.
+
+ ,
+ Reveal Period lasts 2 days (48 hours). ,
+
+ You will unlock your account, enter the Bid Amount, and the{' '}
+ Secret Phrase.
+ ,
+
+ In the event that two parties bid exactly the same amount, the first bid
+ revealed will win.
+
+ ]
+ },
+ {
+ name: 'ensFinalize',
+ headerContent: '4. Finalize the Auction',
+ innerList: [
+
+ Once the auction has ended (after 5 days / 120 hours), the winner needs
+ to finalize the auction in order to claim their new name.
+ ,
+
+ The winner will be refunded the difference between their bid and the
+ next-highest bid. If you are the only bidder, you will refunded all but
+ 0.01 ETH.
+
+ ]
+ },
+ {
+ name: 'ensMore',
+ headerContent: 'More Information',
+ innerList: [
+
+ The auction for this registrar is a blind auction, and is described in
+
+ . Basically, no one can see *anything* during the auction.
+ ,
+
+
+ ,
+
+
+
+ ]
+ }
+];
+
+const GeneralInfoList = () =>
+
+ {generalInfoNodes.map((data: InfoNode) =>
+
+ )}
+ ;
+
+const GeneralInfoPanel = () =>
+
+
+ What is the process like?
+
+
+
+ ·
+
+
+
+ Please try the above before relying on support for reveal issues as we
+ are severely backlogged on support tickets. We're so sorry. :(
+
+
+ ;
+
+export default GeneralInfoPanel;
diff --git a/common/containers/Tabs/ENS/components/GeneralInfoPanel/types.js b/common/containers/Tabs/ENS/components/GeneralInfoPanel/types.js
new file mode 100644
index 00000000..03aba6df
--- /dev/null
+++ b/common/containers/Tabs/ENS/components/GeneralInfoPanel/types.js
@@ -0,0 +1,30 @@
+// @flow
+import * as React from 'react';
+
+type InfoNode = {
+ name: string,
+ headerContent: string,
+ innerList: React.Element[]
+};
+
+type HeaderProps = {
+ children: string,
+ onClickHandler: () => void
+};
+
+type NodeProps = {
+ innerList: React.Element[],
+ headerContent: string,
+ name: string
+};
+
+type NodeState = {
+ isOpen: boolean
+};
+
+type ListProps = {
+ children: React.Element[],
+ isOpen: boolean
+};
+
+export type { InfoNode, HeaderProps, NodeProps, NodeState, ListProps };
diff --git a/common/containers/Tabs/ENS/components/Title.jsx b/common/containers/Tabs/ENS/components/Title.jsx
new file mode 100644
index 00000000..541db2cc
--- /dev/null
+++ b/common/containers/Tabs/ENS/components/Title.jsx
@@ -0,0 +1,25 @@
+// @flow
+import * as React from 'react';
+import { NewTabLink } from 'components/ui';
+import translate from 'translations';
+
+const ENSDocsLink = () =>
+ ;
+
+const ENSTitle = () =>
+
+
+ {translate('NAV_ENS')}
+
+
+ The is a distributed, open, and extensible naming system
+ based on the Ethereum blockchain. Once you have a name, you can tell your
+ friends to send ETH to mewtopia.eth instead of
+ 0x7cB57B5A97eAbe942..... .
+
+ ;
+
+export default ENSTitle;
diff --git a/common/containers/Tabs/ENS/components/UnfinishedBanner/index.jsx b/common/containers/Tabs/ENS/components/UnfinishedBanner/index.jsx
new file mode 100644
index 00000000..5cd7a854
--- /dev/null
+++ b/common/containers/Tabs/ENS/components/UnfinishedBanner/index.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import './index.scss';
+
+export default class UnfinishedBanner extends React.Component {
+ state = {
+ isFading: false,
+ hasAcknowledged: false
+ };
+
+ _continue = () => {
+ this.setState({ isFading: true });
+
+ setTimeout(() => {
+ this.setState({ hasAcknowledged: true });
+ }, 1000);
+ };
+ render() {
+ if (this.state.hasAcknowledged) {
+ return null;
+ }
+
+ const isFading = this.state.isFading ? 'is-fading' : '';
+
+ return (
+
+
+ Under Contruction
+ The ENS section is still under contruction
+ Expect unfinished components
+ Click to continue
+
+
+ );
+ }
+}
diff --git a/common/containers/Tabs/ENS/components/UnfinishedBanner/index.scss b/common/containers/Tabs/ENS/components/UnfinishedBanner/index.scss
new file mode 100644
index 00000000..1a0140b5
--- /dev/null
+++ b/common/containers/Tabs/ENS/components/UnfinishedBanner/index.scss
@@ -0,0 +1,47 @@
+@import "common/sass/variables";
+
+.UnfinishedBanner {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: $brand-warning;
+ overflow: auto;
+ z-index: 10000;
+
+ &-content {
+ text-align: center;
+ margin-top: 20%;
+ @media screen and (min-width: 2150px) {
+ margin-top: 15%;
+ }
+ color: #fff;
+ text-shadow: 1px 1px 1px rgba(#000, 0.12);
+ overflow: auto;
+
+ h2 {
+ font-size: 52px;
+ margin-bottom: 20px;
+ }
+
+ p {
+ font-size: 30px;
+ margin-bottom: 15px;
+ }
+ }
+
+ // Fade out
+ &.is-fading {
+ pointer-events: none;
+ opacity: 0;
+ background: #fff;
+ transition: all 500ms ease 400ms;
+
+ .UnfinishedBanner-content {
+ opacity: 0;
+ transform: translateY(15px);
+ transition: all 500ms ease;
+ }
+ }
+}
diff --git a/common/containers/Tabs/ENS/index.jsx b/common/containers/Tabs/ENS/index.jsx
new file mode 100644
index 00000000..a244188f
--- /dev/null
+++ b/common/containers/Tabs/ENS/index.jsx
@@ -0,0 +1,7 @@
+// @flow
+import { connect } from 'react-redux';
+import ENS from './components/ENS';
+
+const mapStateToProps = state => ({});
+
+export default connect(mapStateToProps)(ENS);
diff --git a/common/containers/Tabs/GenerateWallet/components/DownloadWallet.jsx b/common/containers/Tabs/GenerateWallet/components/DownloadWallet.jsx
index 528deca6..13ba5cc6 100644
--- a/common/containers/Tabs/GenerateWallet/components/DownloadWallet.jsx
+++ b/common/containers/Tabs/GenerateWallet/components/DownloadWallet.jsx
@@ -1,10 +1,12 @@
// @flow
+import './DownloadWallet.scss';
import React, { Component } from 'react';
import translate from 'translations';
import type PrivKeyWallet from 'libs/wallet/privkey';
import { makeBlob } from 'utils/blob';
import { getV3Filename } from 'libs/keystore';
import type { UtcKeystore } from 'libs/keystore';
+import Template from './Template';
type Props = {
wallet: PrivKeyWallet,
@@ -59,72 +61,108 @@ export default class DownloadWallet extends Component {
render() {
const { hasDownloadedWallet } = this.state;
+ const filename = this.getFilename();
- return (
- |