Support Non-Ethereum Networks (#849)

* Make UnlockHeader a PureComponent

* MVP

* actually disable wallet format if not determined to be valid format for wallet

* default to correct derivation in mnemonic modal

* cleanup

* fix tslint

* use enums for HD wallet getPath

* Add stricter typing

* Fix labels not updating on selector

* Ban hardware wallet support for custom network unsupported chainIds

* Fix type error

* Fix custom node dPath not being saved

* Fix mnemonic modal

* default path bugfixes

* add react-select

* misc fixes; rabbit holing hard.

* fix tslint

* revert identicon changes

* reload on network change :/

* actually reload on network change

* really really reload on network change

* tslint fixes

* Update styles

* set table width

* fix package versioning

* push broken sagas

* Fix saga test

* fix tslint

* address round of review

* move non-selectors out to utilty; adjust reload timer

* cleanup network util comments

* manage wallet disable at WalletDecrypt instead of in both WalletDecrypt and WalletButton

* Separate WalletDecrypt props into ownProps / StateProps

* disable payment requests on non-eth networks

* specialize connect; separate props

* remove unused state prop

* remove bad import

* create tests for networks

* Clarify Lite-Send error on non-ethereum networkS

* remove string option for network config name

* Create concept of always-on 'EXTRA_PATHS'; include SINGULAR_DTV legacy dPath in 'EXTRA_PATHS'

* fix multiple imports

* address PR comments
This commit is contained in:
Daniel Ternyak 2018-01-20 14:06:28 -06:00 committed by GitHub
parent 2420f5488b
commit ab5fa1a799
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 957 additions and 501 deletions

View File

@ -1,6 +1,6 @@
import * as interfaces from './actionTypes'; import * as interfaces from './actionTypes';
import { TypeKeys } from './constants'; import { TypeKeys } from './constants';
import { NodeConfig, CustomNodeConfig, NetworkConfig, CustomNetworkConfig } from 'config/data'; import { NodeConfig, CustomNodeConfig, NetworkConfig, CustomNetworkConfig } from 'config';
export type TToggleOfflineConfig = typeof toggleOfflineConfig; export type TToggleOfflineConfig = typeof toggleOfflineConfig;
export function toggleOfflineConfig(): interfaces.ToggleOfflineAction { export function toggleOfflineConfig(): interfaces.ToggleOfflineAction {

View File

@ -1,5 +1,5 @@
import { TypeKeys } from './constants'; import { TypeKeys } from './constants';
import { NodeConfig, CustomNodeConfig, NetworkConfig, CustomNetworkConfig } from 'config/data'; import { NodeConfig, CustomNodeConfig, NetworkConfig, CustomNetworkConfig } from 'config';
/*** Toggle Offline ***/ /*** Toggle Offline ***/
export interface ToggleOfflineAction { export interface ToggleOfflineAction {

View File

@ -1,4 +1,4 @@
import { Token } from 'config/data'; import { Token } from 'config';
import * as interfaces from './actionTypes'; import * as interfaces from './actionTypes';
import { TypeKeys } from './constants'; import { TypeKeys } from './constants';

View File

@ -1,4 +1,4 @@
import { Token } from 'config/data'; import { Token } from 'config';
import { TypeKeys } from './constants'; import { TypeKeys } from './constants';
/*** Add custom token ***/ /*** Add custom token ***/
export interface AddCustomTokenAction { export interface AddCustomTokenAction {

View File

@ -1,4 +1,4 @@
import bityConfig, { WhitelistedCoins } from 'config/bity'; import { WhitelistedCoins, bityConfig } from 'config';
import { checkHttpStatus, parseJSON, filter } from './utils'; import { checkHttpStatus, parseJSON, filter } from './utils';
import bitcoinIcon from 'assets/images/bitcoin.png'; import bitcoinIcon from 'assets/images/bitcoin.png';
import repIcon from 'assets/images/augur.png'; import repIcon from 'assets/images/augur.png';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { AddressFieldFactory } from './AddressFieldFactory'; import { AddressFieldFactory } from './AddressFieldFactory';
import { donationAddressMap } from 'config/data'; import { donationAddressMap } from 'config';
interface Props { interface Props {
isReadOnly?: boolean; isReadOnly?: boolean;

View File

@ -1,5 +1,5 @@
import { Identicon, UnitDisplay } from 'components/ui'; import { Identicon, UnitDisplay } from 'components/ui';
import { NetworkConfig } from 'config/data'; import { NetworkConfig } from 'config';
import { IWallet, Balance, TrezorWallet, LedgerWallet } from 'libs/wallet'; import { IWallet, Balance, TrezorWallet, LedgerWallet } from 'libs/wallet';
import React from 'react'; import React from 'react';
import translate from 'translations'; import translate from 'translations';
@ -127,11 +127,11 @@ export default class AccountInfo extends React.Component<Props, State> {
{!!blockExplorer && ( {!!blockExplorer && (
<li className="AccountInfo-list-item"> <li className="AccountInfo-list-item">
<a <a
href={blockExplorer.address(address)} href={blockExplorer.addressUrl(address)}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{`${network.name} (${blockExplorer.name})`} {`${network.name} (${blockExplorer.origin})`}
</a> </a>
</li> </li>
)} )}

View File

@ -5,7 +5,7 @@ import { State } from 'reducers/rates';
import { rateSymbols, TFetchCCRates } from 'actions/rates'; import { rateSymbols, TFetchCCRates } from 'actions/rates';
import { TokenBalance } from 'selectors/wallet'; import { TokenBalance } from 'selectors/wallet';
import { Balance } from 'libs/wallet'; import { Balance } from 'libs/wallet';
import { NetworkConfig } from 'config/data'; import { NetworkConfig } from 'config';
import { ETH_DECIMAL, convertTokenBase } from 'libs/units'; import { ETH_DECIMAL, convertTokenBase } from 'libs/units';
import Spinner from 'components/ui/Spinner'; import Spinner from 'components/ui/Spinner';
import UnitDisplay from 'components/ui/UnitDisplay'; import UnitDisplay from 'components/ui/UnitDisplay';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { Token } from 'config/data'; import { Token } from 'config';
import { isPositiveIntegerOrZero, isValidETHAddress } from 'libs/validators'; import { isPositiveIntegerOrZero, isValidETHAddress } from 'libs/validators';
import translate from 'translations'; import translate from 'translations';
import NewTabLink from 'components/ui/NewTabLink'; import NewTabLink from 'components/ui/NewTabLink';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import translate from 'translations'; import translate from 'translations';
import { Token } from 'config/data'; import { Token } from 'config';
import { TokenBalance } from 'selectors/wallet'; import { TokenBalance } from 'selectors/wallet';
import AddCustomTokenForm from './AddCustomTokenForm'; import AddCustomTokenForm from './AddCustomTokenForm';
import TokenRow from './TokenRow'; import TokenRow from './TokenRow';

View File

@ -15,7 +15,7 @@ import {
} from 'actions/wallet'; } from 'actions/wallet';
import { getAllTokens } from 'selectors/config'; import { getAllTokens } from 'selectors/config';
import { getTokenBalances, getWalletInst, getWalletConfig, TokenBalance } from 'selectors/wallet'; import { getTokenBalances, getWalletInst, getWalletConfig, TokenBalance } from 'selectors/wallet';
import { Token } from 'config/data'; import { Token } from 'config';
import translate from 'translations'; import translate from 'translations';
import Balances from './Balances'; import Balances from './Balances';
import Spinner from 'components/ui/Spinner'; import Spinner from 'components/ui/Spinner';

View File

@ -1,5 +1,5 @@
import { fetchCCRates, TFetchCCRates } from 'actions/rates'; import { fetchCCRates, TFetchCCRates } from 'actions/rates';
import { NetworkConfig } from 'config/data'; import { NetworkConfig } from 'config';
import { IWallet, Balance } from 'libs/wallet'; import { IWallet, Balance } from 'libs/wallet';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';

View File

@ -1,4 +1,4 @@
import { NodeConfig } from 'config/data'; import { NodeConfig } from 'config';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { connect } from 'react-redux'; import { connect } from 'react-redux';

View File

@ -1,7 +1,7 @@
import { DataFieldFactory } from './DataFieldFactory'; import { DataFieldFactory } from './DataFieldFactory';
import React from 'react'; import React from 'react';
import translate from 'translations'; import translate from 'translations';
import { donationAddressMap } from 'config/data'; import { donationAddressMap } from 'config';
export const DataField: React.SFC<{}> = () => ( export const DataField: React.SFC<{}> = () => (
<DataFieldFactory <DataFieldFactory

View File

@ -1,4 +1,4 @@
import { BlockExplorerConfig } from 'config/data'; import { BlockExplorerConfig } from 'config';
import React from 'react'; import React from 'react';
import { translateRaw } from 'translations'; import { translateRaw } from 'translations';
@ -9,7 +9,7 @@ export interface TransactionSucceededProps {
const TransactionSucceeded = ({ txHash, blockExplorer }: TransactionSucceededProps) => { const TransactionSucceeded = ({ txHash, blockExplorer }: TransactionSucceededProps) => {
// const checkTxLink = `https://www.myetherwallet.com?txHash=${txHash}/#check-tx-status`; // const checkTxLink = `https://www.myetherwallet.com?txHash=${txHash}/#check-tx-status`;
const txHashLink = blockExplorer.tx(txHash); const txHashLink = blockExplorer.txUrl(txHash);
return ( return (
<div> <div>

View File

@ -7,7 +7,7 @@ import {
donationAddressMap, donationAddressMap,
VERSION, VERSION,
knowledgeBaseURL knowledgeBaseURL
} from 'config/data'; } from 'config';
import React from 'react'; import React from 'react';
import translate from 'translations'; import translate from 'translations';
import './index.scss'; import './index.scss';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import Slider from 'rc-slider'; import Slider from 'rc-slider';
import translate from 'translations'; import translate from 'translations';
import { gasPriceDefaults } from 'config/data'; import { gasPriceDefaults } from 'config';
import FeeSummary from './FeeSummary'; import FeeSummary from './FeeSummary';
import { TInputGasPrice } from 'actions/transaction'; import { TInputGasPrice } from 'actions/transaction';
import './SimpleGas.scss'; import './SimpleGas.scss';

View File

@ -3,7 +3,7 @@ import { generateKeystoreFileInfo, KeystoreFile } from 'utils/keystore';
import Modal from 'components/ui/Modal'; import Modal from 'components/ui/Modal';
import Input from './Input'; import Input from './Input';
import translate, { translateRaw } from 'translations'; import translate, { translateRaw } from 'translations';
import { MINIMUM_PASSWORD_LENGTH } from 'config/data'; import { MINIMUM_PASSWORD_LENGTH } from 'config';
import { isValidPrivKey } from 'libs/validators'; import { isValidPrivKey } from 'libs/validators';
import './index.scss'; import './index.scss';

View File

@ -2,7 +2,7 @@ import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import Modal, { IButton } from 'components/ui/Modal'; import Modal, { IButton } from 'components/ui/Modal';
import translate from 'translations'; import translate from 'translations';
import { NETWORKS, CustomNodeConfig, CustomNetworkConfig } from 'config/data'; import { NETWORKS, CustomNodeConfig, CustomNetworkConfig } from 'config';
import { makeCustomNodeId } from 'utils/node'; import { makeCustomNodeId } from 'utils/node';
import { makeCustomNetworkId } from 'utils/network'; import { makeCustomNetworkId } from 'utils/network';
@ -303,10 +303,16 @@ export default class CustomNodeModal extends React.Component<Props, State> {
} }
private makeCustomNetworkConfigFromState(): CustomNetworkConfig { private makeCustomNetworkConfigFromState(): CustomNetworkConfig {
const similarNetworkConfig = Object.values(NETWORKS).find(
n => n.chainId === +this.state.customNetworkChainId
);
const dPathFormats = similarNetworkConfig ? similarNetworkConfig.dPathFormats : null;
return { return {
name: this.state.customNetworkName, name: this.state.customNetworkName,
unit: this.state.customNetworkUnit, unit: this.state.customNetworkUnit,
chainId: this.state.customNetworkChainId ? parseInt(this.state.customNetworkChainId, 10) : 0 chainId: this.state.customNetworkChainId ? parseInt(this.state.customNetworkChainId, 10) : 0,
dPathFormats
}; };
} }
@ -352,6 +358,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
if (this.state.network === CUSTOM) { if (this.state.network === CUSTOM) {
const network = this.makeCustomNetworkConfigFromState(); const network = this.makeCustomNetworkConfigFromState();
this.props.handleAddCustomNetwork(network); this.props.handleAddCustomNetwork(network);
} }

View File

@ -1,4 +1,4 @@
import { gasPriceDefaults, knowledgeBaseURL } from 'config/data'; import { gasPriceDefaults, knowledgeBaseURL } from 'config';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import React, { Component } from 'react'; import React, { Component } from 'react';
import DropdownShell from 'components/ui/DropdownShell'; import DropdownShell from 'components/ui/DropdownShell';

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import NavigationLink from './NavigationLink'; import NavigationLink from './NavigationLink';
import { knowledgeBaseURL } from 'config/data'; import { knowledgeBaseURL } from 'config';
import './Navigation.scss'; import './Navigation.scss';
export interface TabLink { export interface TabLink {

View File

@ -20,7 +20,7 @@ import {
NodeConfig, NodeConfig,
CustomNodeConfig, CustomNodeConfig,
CustomNetworkConfig CustomNetworkConfig
} from 'config/data'; } from 'config';
import GasPriceDropdown from './components/GasPriceDropdown'; import GasPriceDropdown from './components/GasPriceDropdown';
import Navigation from './components/Navigation'; import Navigation from './components/Navigation';
import CustomNodeModal from './components/CustomNodeModal'; import CustomNodeModal from './components/CustomNodeModal';

View File

@ -19,7 +19,6 @@ import {
import { reset, TReset } from 'actions/transaction'; import { reset, TReset } from 'actions/transaction';
import translate from 'translations'; import translate from 'translations';
import { import {
DigitalBitboxDecrypt,
KeystoreDecrypt, KeystoreDecrypt,
LedgerNanoSDecrypt, LedgerNanoSDecrypt,
MnemonicDecrypt, MnemonicDecrypt,
@ -31,38 +30,53 @@ import {
WalletButton WalletButton
} from './components'; } from './components';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { knowledgeBaseURL, isWeb3NodeAvailable } from 'config/data'; import DISABLES from './disables';
import { IWallet } from 'libs/wallet';
import DISABLES from './disables.json';
import { showNotification, TShowNotification } from 'actions/notifications'; import { showNotification, TShowNotification } from 'actions/notifications';
import DigitalBitboxIcon from 'assets/images/wallets/digital-bitbox.svg';
import LedgerIcon from 'assets/images/wallets/ledger.svg'; import LedgerIcon from 'assets/images/wallets/ledger.svg';
import MetamaskIcon from 'assets/images/wallets/metamask.svg'; import MetamaskIcon from 'assets/images/wallets/metamask.svg';
import MistIcon from 'assets/images/wallets/mist.svg'; import MistIcon from 'assets/images/wallets/mist.svg';
import TrezorIcon from 'assets/images/wallets/trezor.svg'; import TrezorIcon from 'assets/images/wallets/trezor.svg';
import './WalletDecrypt.scss'; import './WalletDecrypt.scss';
import {
SecureWalletName,
InsecureWalletName,
MiscWalletName,
WalletName,
isWeb3NodeAvailable,
knowledgeBaseURL
} from 'config';
import { unSupportedWalletFormatsOnNetwork } from 'utils/network';
import { getNetworkConfig } from '../../selectors/config';
interface Props { interface OwnProps {
resetTransactionState: TReset; hidden?: boolean;
disabledWallets?: WalletName[];
}
interface DispatchProps {
unlockKeystore: TUnlockKeystore; unlockKeystore: TUnlockKeystore;
unlockMnemonic: TUnlockMnemonic; unlockMnemonic: TUnlockMnemonic;
unlockPrivateKey: TUnlockPrivateKey; unlockPrivateKey: TUnlockPrivateKey;
setWallet: TSetWallet;
unlockWeb3: TUnlockWeb3; unlockWeb3: TUnlockWeb3;
setWallet: TSetWallet;
resetWallet: TResetWallet; resetWallet: TResetWallet;
resetTransactionState: TReset;
showNotification: TShowNotification; showNotification: TShowNotification;
wallet: IWallet; }
hidden?: boolean;
interface StateProps {
computedDisabledWallets: WalletName[];
offline: boolean; offline: boolean;
disabledWallets?: string[];
isWalletPending: AppState['wallet']['isWalletPending']; isWalletPending: AppState['wallet']['isWalletPending'];
isPasswordPending: AppState['wallet']['isPasswordPending']; isPasswordPending: AppState['wallet']['isPasswordPending'];
} }
type Props = OwnProps & StateProps & DispatchProps;
type UnlockParams = {} | PrivateKeyValue; type UnlockParams = {} | PrivateKeyValue;
interface State { interface State {
selectedWalletKey: string | null; selectedWalletKey: WalletName | null;
value: UnlockParams | null; value: UnlockParams | null;
} }
@ -71,13 +85,13 @@ interface BaseWalletInfo {
component: any; component: any;
initialParams: object; initialParams: object;
unlock: any; unlock: any;
helpLink?: string; helpLink: string;
isReadOnly?: boolean; isReadOnly?: boolean;
attemptUnlock?: boolean; attemptUnlock?: boolean;
} }
export interface SecureWalletInfo extends BaseWalletInfo { export interface SecureWalletInfo extends BaseWalletInfo {
icon?: string | null; icon?: string;
description: string; description: string;
} }
@ -85,6 +99,9 @@ export interface InsecureWalletInfo extends BaseWalletInfo {
example: string; example: string;
} }
// tslint:disable-next-line:no-empty-interface
interface MiscWalletInfo extends InsecureWalletInfo {}
const WEB3_TYPES = { const WEB3_TYPES = {
MetamaskInpageProvider: { MetamaskInpageProvider: {
lid: 'x_MetaMask', lid: 'x_MetaMask',
@ -95,15 +112,20 @@ const WEB3_TYPES = {
icon: MistIcon icon: MistIcon
} }
}; };
type SecureWallets = { [key in SecureWalletName]: SecureWalletInfo };
type InsecureWallets = { [key in InsecureWalletName]: InsecureWalletInfo };
type MiscWallet = { [key in MiscWalletName]: MiscWalletInfo };
type Wallets = SecureWallets & InsecureWallets & MiscWallet;
const WEB3_TYPE: string | false = const WEB3_TYPE: string | false =
(window as any).web3 && (window as any).web3.currentProvider.constructor.name; (window as any).web3 && (window as any).web3.currentProvider.constructor.name;
const SECURE_WALLETS = ['web3', 'ledger-nano-s', 'trezor'];
const INSECURE_WALLETS = ['private-key', 'keystore-file', 'mnemonic-phrase'];
export class WalletDecrypt extends Component<Props, State> { export class WalletDecrypt extends Component<Props, State> {
public WALLETS: { [key: string]: SecureWalletInfo | InsecureWalletInfo } = { // https://github.com/Microsoft/TypeScript/issues/13042
web3: { // index signature should become [key: Wallets] (from config) once typescript bug is fixed
public WALLETS: Wallets = {
[SecureWalletName.WEB3]: {
lid: WEB3_TYPE ? WEB3_TYPES[WEB3_TYPE].lid : 'x_Web3', lid: WEB3_TYPE ? WEB3_TYPES[WEB3_TYPE].lid : 'x_Web3',
icon: WEB3_TYPE && WEB3_TYPES[WEB3_TYPE].icon, icon: WEB3_TYPE && WEB3_TYPES[WEB3_TYPE].icon,
description: 'ADD_Web3Desc', description: 'ADD_Web3Desc',
@ -113,7 +135,7 @@ export class WalletDecrypt extends Component<Props, State> {
attemptUnlock: true, attemptUnlock: true,
helpLink: `${knowledgeBaseURL}/migration/moving-from-private-key-to-metamask` helpLink: `${knowledgeBaseURL}/migration/moving-from-private-key-to-metamask`
}, },
'ledger-nano-s': { [SecureWalletName.LEDGER_NANO_S]: {
lid: 'x_Ledger', lid: 'x_Ledger',
icon: LedgerIcon, icon: LedgerIcon,
description: 'ADD_HardwareDesc', description: 'ADD_HardwareDesc',
@ -123,7 +145,7 @@ export class WalletDecrypt extends Component<Props, State> {
helpLink: helpLink:
'https://ledger.zendesk.com/hc/en-us/articles/115005200009-How-to-use-MyEtherWallet-with-Ledger' 'https://ledger.zendesk.com/hc/en-us/articles/115005200009-How-to-use-MyEtherWallet-with-Ledger'
}, },
trezor: { [SecureWalletName.TREZOR]: {
lid: 'x_Trezor', lid: 'x_Trezor',
icon: TrezorIcon, icon: TrezorIcon,
description: 'ADD_HardwareDesc', description: 'ADD_HardwareDesc',
@ -132,16 +154,7 @@ export class WalletDecrypt extends Component<Props, State> {
unlock: this.props.setWallet, unlock: this.props.setWallet,
helpLink: 'https://doc.satoshilabs.com/trezor-apps/mew.html' helpLink: 'https://doc.satoshilabs.com/trezor-apps/mew.html'
}, },
'digital-bitbox': { [InsecureWalletName.KEYSTORE_FILE]: {
lid: 'x_DigitalBitbox',
icon: DigitalBitboxIcon,
description: 'ADD_HardwareDesc',
component: DigitalBitboxDecrypt,
initialParams: {},
unlock: this.props.setWallet,
helpLink: 'https://digitalbitbox.com/ethereum'
},
'keystore-file': {
lid: 'x_Keystore2', lid: 'x_Keystore2',
example: 'UTC--2017-12-15T17-35-22.547Z--6be6e49e82425a5aa56396db03512f2cc10e95e8', example: 'UTC--2017-12-15T17-35-22.547Z--6be6e49e82425a5aa56396db03512f2cc10e95e8',
component: KeystoreDecrypt, component: KeystoreDecrypt,
@ -152,7 +165,7 @@ export class WalletDecrypt extends Component<Props, State> {
unlock: this.props.unlockKeystore, unlock: this.props.unlockKeystore,
helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html` helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html`
}, },
'mnemonic-phrase': { [InsecureWalletName.MNEMONIC_PHRASE]: {
lid: 'x_Mnemonic', lid: 'x_Mnemonic',
example: 'brain surround have swap horror cheese file distinct', example: 'brain surround have swap horror cheese file distinct',
component: MnemonicDecrypt, component: MnemonicDecrypt,
@ -160,7 +173,7 @@ export class WalletDecrypt extends Component<Props, State> {
unlock: this.props.unlockMnemonic, unlock: this.props.unlockMnemonic,
helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html` helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html`
}, },
'private-key': { [InsecureWalletName.PRIVATE_KEY]: {
lid: 'x_PrivKey2', lid: 'x_PrivKey2',
example: 'f1d0e0789c6d40f399ca90cc674b7858de4c719e0d5752a60d5d2f6baa45d4c9', example: 'f1d0e0789c6d40f399ca90cc674b7858de4c719e0d5752a60d5d2f6baa45d4c9',
component: PrivateKeyDecrypt, component: PrivateKeyDecrypt,
@ -171,7 +184,7 @@ export class WalletDecrypt extends Component<Props, State> {
unlock: this.props.unlockPrivateKey, unlock: this.props.unlockPrivateKey,
helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html` helpLink: `${knowledgeBaseURL}/private-keys-passwords/difference-beween-private-key-and-keystore-file.html`
}, },
'view-only': { [MiscWalletName.VIEW_ONLY]: {
lid: 'View Address', lid: 'View Address',
example: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8', example: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8',
component: ViewOnlyDecrypt, component: ViewOnlyDecrypt,
@ -181,12 +194,13 @@ export class WalletDecrypt extends Component<Props, State> {
isReadOnly: true isReadOnly: true
} }
}; };
public state: State = { public state: State = {
selectedWalletKey: null, selectedWalletKey: null,
value: null value: null
}; };
public componentWillReceiveProps(nextProps) { public componentWillReceiveProps(nextProps: Props) {
// Reset state when unlock is hidden / revealed // Reset state when unlock is hidden / revealed
if (nextProps.hidden !== this.props.hidden) { if (nextProps.hidden !== this.props.hidden) {
this.setState({ this.setState({
@ -218,10 +232,12 @@ export class WalletDecrypt extends Component<Props, State> {
onUnlock={this.onUnlock} onUnlock={this.onUnlock}
showNotification={this.props.showNotification} showNotification={this.props.showNotification}
isWalletPending={ isWalletPending={
this.state.selectedWalletKey === 'keystore-file' ? this.props.isWalletPending : undefined this.state.selectedWalletKey === InsecureWalletName.KEYSTORE_FILE
? this.props.isWalletPending
: undefined
} }
isPasswordPending={ isPasswordPending={
this.state.selectedWalletKey === 'keystore-file' this.state.selectedWalletKey === InsecureWalletName.KEYSTORE_FILE
? this.props.isPasswordPending ? this.props.isPasswordPending
: undefined : undefined
} }
@ -230,64 +246,72 @@ export class WalletDecrypt extends Component<Props, State> {
} }
public buildWalletOptions() { public buildWalletOptions() {
const viewOnly = this.WALLETS['view-only'] as InsecureWalletInfo; const SECURE_WALLETS = Object.values(SecureWalletName);
const INSECURE_WALLETS = Object.values(InsecureWalletName);
const MISC_WALLETS = Object.values(MiscWalletName);
return ( return (
<div className="WalletDecrypt-wallets"> <div className="WalletDecrypt-wallets">
<h2 className="WalletDecrypt-wallets-title">{translate('decrypt_Access')}</h2> <h2 className="WalletDecrypt-wallets-title">{translate('decrypt_Access')}</h2>
<div className="WalletDecrypt-wallets-row"> <div className="WalletDecrypt-wallets-row">
{SECURE_WALLETS.map(type => { {SECURE_WALLETS.map((walletType: SecureWalletName) => {
const wallet = this.WALLETS[type] as SecureWalletInfo; const wallet = this.WALLETS[walletType];
return ( return (
<WalletButton <WalletButton
key={type} key={walletType}
name={translate(wallet.lid)} name={translate(wallet.lid)}
description={translate(wallet.description)} description={translate(wallet.description)}
icon={wallet.icon} icon={wallet.icon}
helpLink={wallet.helpLink} helpLink={wallet.helpLink}
walletType={type} walletType={walletType}
isSecure={true} isSecure={true}
isDisabled={this.isWalletDisabled(type)} isDisabled={this.isWalletDisabled(walletType)}
onClick={this.handleWalletChoice} onClick={this.handleWalletChoice}
/> />
); );
})} })}
</div> </div>
<div className="WalletDecrypt-wallets-row"> <div className="WalletDecrypt-wallets-row">
{INSECURE_WALLETS.map(type => { {INSECURE_WALLETS.map((walletType: InsecureWalletName) => {
const wallet = this.WALLETS[type] as InsecureWalletInfo; const wallet = this.WALLETS[walletType];
return ( return (
<WalletButton <WalletButton
key={type} key={walletType}
name={translate(wallet.lid)} name={translate(wallet.lid)}
example={wallet.example} example={wallet.example}
helpLink={wallet.helpLink} helpLink={wallet.helpLink}
walletType={type} walletType={walletType}
isSecure={false} isSecure={false}
isDisabled={this.isWalletDisabled(type)} isDisabled={this.isWalletDisabled(walletType)}
onClick={this.handleWalletChoice} onClick={this.handleWalletChoice}
/> />
); );
})} })}
<WalletButton {MISC_WALLETS.map((walletType: MiscWalletName) => {
key="view-only" const wallet = this.WALLETS[walletType];
name={translate(viewOnly.lid)} return (
example={viewOnly.example} <WalletButton
helpLink={viewOnly.helpLink} key={walletType}
walletType="view-only" name={translate(wallet.lid)}
isReadOnly={true} example={wallet.example}
isDisabled={this.isWalletDisabled('view-only')} helpLink={wallet.helpLink}
onClick={this.handleWalletChoice} walletType={walletType}
/> isReadOnly={true}
isDisabled={this.isWalletDisabled(walletType)}
onClick={this.handleWalletChoice}
/>
);
})}
</div> </div>
</div> </div>
); );
} }
public handleWalletChoice = async (walletType: string) => { public handleWalletChoice = async (walletType: WalletName) => {
const wallet = this.WALLETS[walletType]; const wallet = this.WALLETS[walletType];
if (!wallet) { if (!wallet) {
return; return;
} }
@ -301,7 +325,7 @@ export class WalletDecrypt extends Component<Props, State> {
wallet.unlock(); wallet.unlock();
} }
setTimeout(() => { window.setTimeout(() => {
this.setState({ this.setState({
selectedWalletKey: walletType, selectedWalletKey: walletType,
value: wallet.initialParams value: wallet.initialParams
@ -375,28 +399,31 @@ export class WalletDecrypt extends Component<Props, State> {
this.props.resetTransactionState(); this.props.resetTransactionState();
}; };
private isWalletDisabled = (walletKey: string) => { private isWalletDisabled = (walletKey: WalletName) => {
if (this.props.offline && DISABLES.ONLINE_ONLY.includes(walletKey)) { if (this.props.offline && DISABLES.ONLINE_ONLY.includes(walletKey)) {
return true; return true;
} }
if (!this.props.disabledWallets) { return this.props.computedDisabledWallets.indexOf(walletKey) !== -1;
return false;
}
return this.props.disabledWallets.indexOf(walletKey) !== -1;
}; };
} }
function mapStateToProps(state: AppState) { function mapStateToProps(state: AppState, ownProps: Props) {
const { disabledWallets } = ownProps;
const network = getNetworkConfig(state);
const networkDisabledFormats = unSupportedWalletFormatsOnNetwork(network);
const computedDisabledWallets = disabledWallets
? disabledWallets.concat(networkDisabledFormats)
: networkDisabledFormats;
return { return {
computedDisabledWallets,
offline: state.config.offline, offline: state.config.offline,
wallet: state.wallet.inst,
isWalletPending: state.wallet.isWalletPending, isWalletPending: state.wallet.isWalletPending,
isPasswordPending: state.wallet.isPasswordPending isPasswordPending: state.wallet.isPasswordPending
}; };
} }
export default connect(mapStateToProps, { export default connect<StateProps, DispatchProps>(mapStateToProps, {
unlockKeystore, unlockKeystore,
unlockMnemonic, unlockMnemonic,
unlockPrivateKey, unlockPrivateKey,

View File

@ -1,15 +1,14 @@
@import "common/sass/variables"; @import 'common/sass/variables';
@import "common/sass/mixins"; @import 'common/sass/mixins';
.DWModal { .DWModal {
width: 690px;
&-path { &-path {
display: block; display: flex;
margin-bottom: 20px; margin-bottom: 20px;
&-label { &-label {
font-size: $font-size-medium; font-size: $font-size-medium;
margin-right: 16px;
} }
.form-control { .form-control {
@ -17,12 +16,18 @@
width: auto; width: auto;
margin: 0 0 0 10px; margin: 0 0 0 10px;
} }
.Select {
flex-grow: 1;
}
} }
&-addresses { &-addresses {
overflow-y: scroll;
&-table { &-table {
width: 100%; width: 695px;
text-align: center; text-align: center;
margin: auto;
margin-bottom: 10px; margin-bottom: 10px;
&-token { &-token {

View File

@ -8,7 +8,7 @@ import {
} from 'actions/deterministicWallets'; } from 'actions/deterministicWallets';
import Modal, { IButton } from 'components/ui/Modal'; import Modal, { IButton } from 'components/ui/Modal';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { NetworkConfig } from 'config/data'; import { NetworkConfig } from 'config';
import { isValidPath } from 'libs/validators'; import { isValidPath } from 'libs/validators';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -16,6 +16,8 @@ import { getNetworkConfig } from 'selectors/config';
import { getTokens, MergedToken } from 'selectors/wallet'; import { getTokens, MergedToken } from 'selectors/wallet';
import { UnitDisplay } from 'components/ui'; import { UnitDisplay } from 'components/ui';
import './DeterministicWalletsModal.scss'; import './DeterministicWalletsModal.scss';
import { DPath } from 'config/dpaths';
import Select from 'react-select';
const WALLETS_PER_PAGE = 5; const WALLETS_PER_PAGE = 5;
@ -24,7 +26,7 @@ interface Props {
isOpen?: boolean; isOpen?: boolean;
walletType?: string; walletType?: string;
dPath: string; dPath: string;
dPaths: { label: string; value: string }[]; dPaths: DPath[];
publicKey?: string; publicKey?: string;
chainCode?: string; chainCode?: string;
seed?: string; seed?: string;
@ -45,6 +47,7 @@ interface Props {
} }
interface State { interface State {
currentLabel: string;
selectedAddress: string; selectedAddress: string;
selectedAddrIndex: number; selectedAddrIndex: number;
isCustomPath: boolean; isCustomPath: boolean;
@ -52,12 +55,18 @@ interface State {
page: number; page: number;
} }
const customDPath: DPath = {
label: 'custom',
value: 'custom'
};
class DeterministicWalletsModalClass extends React.Component<Props, State> { class DeterministicWalletsModalClass extends React.Component<Props, State> {
public state = { public state: State = {
selectedAddress: '', selectedAddress: '',
selectedAddrIndex: 0, selectedAddrIndex: 0,
isCustomPath: false, isCustomPath: false,
customPath: '', customPath: '',
currentLabel: '',
page: 0 page: 0
}; };
@ -88,7 +97,7 @@ class DeterministicWalletsModalClass extends React.Component<Props, State> {
onCancel, onCancel,
walletType walletType
} = this.props; } = this.props;
const { selectedAddress, isCustomPath, customPath, page } = this.state; const { selectedAddress, customPath, page } = this.state;
const validPathClass = isValidPath(customPath) ? 'is-valid' : 'is-invalid'; const validPathClass = isValidPath(customPath) ? 'is-valid' : 'is-invalid';
const buttons: IButton[] = [ const buttons: IButton[] = [
@ -113,21 +122,20 @@ class DeterministicWalletsModalClass extends React.Component<Props, State> {
handleClose={onCancel} handleClose={onCancel}
> >
<div className="DWModal"> <div className="DWModal">
{/* TODO: replace styles for flexbox with flexbox classes in https://github.com/MyEtherWallet/MyEtherWallet/pull/850/files#diff-2150778b9391533fec7b8afd060c7672 */}
<form className="DWModal-path form-group-sm" onSubmit={this.handleSubmitCustomPath}> <form className="DWModal-path form-group-sm" onSubmit={this.handleSubmitCustomPath}>
<span className="DWModal-path-label">Addresses for</span> <span className="DWModal-path-label">Addresses </span>
<select <Select
className="form-control" name="fieldDPath"
className=""
value={this.state.currentLabel || this.findDPath('value', dPath).value}
onChange={this.handleChangePath} onChange={this.handleChangePath}
value={isCustomPath ? 'custom' : dPath} options={dPaths}
> clearable={false}
{dPaths.map(dp => ( searchable={false}
<option key={dp.value} value={dp.value}> />
{dp.label} {/* TODO/Hack - Custom Paths are temporarily disabled. `false` is used for smallest diff */}
</option> {false && (
))}
<option value="custom">Custom path...</option>
</select>
{isCustomPath && (
<input <input
className={`form-control ${validPathClass}`} className={`form-control ${validPathClass}`}
value={customPath} value={customPath}
@ -163,22 +171,21 @@ class DeterministicWalletsModalClass extends React.Component<Props, State> {
</thead> </thead>
<tbody>{wallets.map(wallet => this.renderWalletRow(wallet))}</tbody> <tbody>{wallets.map(wallet => this.renderWalletRow(wallet))}</tbody>
</table> </table>
</div>
<div className="DWModal-addresses-nav"> <div className="DWModal-addresses-nav">
<button <button
className="DWModal-addresses-nav-btn btn btn-sm btn-default" className="DWModal-addresses-nav-btn btn btn-sm btn-default"
disabled={page === 0} disabled={page === 0}
onClick={this.prevPage} onClick={this.prevPage}
> >
Back Back
</button> </button>
<button <button
className="DWModal-addresses-nav-btn btn btn-sm btn-default" className="DWModal-addresses-nav-btn btn btn-sm btn-default"
onClick={this.nextPage} onClick={this.nextPage}
> >
More More
</button> </button>
</div>
</div> </div>
</div> </div>
</Modal> </Modal>
@ -200,16 +207,19 @@ class DeterministicWalletsModalClass extends React.Component<Props, State> {
} }
} }
private handleChangePath = (ev: React.FormEvent<HTMLSelectElement>) => { private findDPath = (prop: keyof DPath, cmp: string) => {
const { value } = ev.currentTarget; return this.props.dPaths.find(d => d[prop] === cmp) || customDPath;
};
private handleChangePath = (newPath: DPath) => {
const { value: dPathLabel } = newPath;
const { value } = this.findDPath('value', dPathLabel);
if (value === 'custom') { if (value === 'custom') {
this.setState({ isCustomPath: true }); this.setState({ isCustomPath: true, currentLabel: dPathLabel });
} else { } else {
this.setState({ isCustomPath: false }); this.setState({ isCustomPath: false, currentLabel: dPathLabel });
if (this.props.dPath !== value) { this.props.onPathChange(value);
this.props.onPathChange(value);
}
} }
}; };

View File

@ -4,15 +4,22 @@ import translate, { translateRaw } from 'translations';
import DeterministicWalletsModal from './DeterministicWalletsModal'; import DeterministicWalletsModal from './DeterministicWalletsModal';
import { LedgerWallet } from 'libs/wallet'; import { LedgerWallet } from 'libs/wallet';
import ledger from 'ledgerco'; import ledger from 'ledgerco';
import DPATHS from 'config/dpaths';
import { Spinner } from 'components/ui'; import { Spinner } from 'components/ui';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getNetworkConfig } from 'selectors/config';
import { SecureWalletName } from 'config';
import { DPath } from 'config/dpaths';
import { getPaths, getSingleDPath } from 'utils/network';
const DEFAULT_PATH = DPATHS.LEDGER[0].value; interface OwnProps {
interface Props {
onUnlock(param: any): void; onUnlock(param: any): void;
} }
interface StateProps {
dPath: DPath;
}
interface State { interface State {
publicKey: string; publicKey: string;
chainCode: string; chainCode: string;
@ -22,11 +29,13 @@ interface State {
showTip: boolean; showTip: boolean;
} }
export class LedgerNanoSDecrypt extends Component<Props, State> { type Props = OwnProps & StateProps;
class LedgerNanoSDecryptClass extends Component<Props, State> {
public state: State = { public state: State = {
publicKey: '', publicKey: '',
chainCode: '', chainCode: '',
dPath: DEFAULT_PATH, dPath: this.props.dPath.value,
error: null, error: null,
isLoading: false, isLoading: false,
showTip: false showTip: false
@ -114,7 +123,7 @@ export class LedgerNanoSDecrypt extends Component<Props, State> {
publicKey={publicKey} publicKey={publicKey}
chainCode={chainCode} chainCode={chainCode}
dPath={dPath} dPath={dPath}
dPaths={DPATHS.LEDGER} dPaths={getPaths(SecureWalletName.LEDGER_NANO_S)}
onCancel={this.handleCancel} onCancel={this.handleCancel}
onConfirmAddress={this.handleUnlock} onConfirmAddress={this.handleUnlock}
onPathChange={this.handlePathChange} onPathChange={this.handlePathChange}
@ -146,11 +155,11 @@ export class LedgerNanoSDecrypt extends Component<Props, State> {
}); });
}) })
.catch(err => { .catch(err => {
if (err.metaData.code === 5) { if (err.metaData && err.metaData.code === 5) {
this.showTip(); this.showTip();
} }
this.setState({ this.setState({
error: err.metaData.type, error: err.metaData ? err.metaData.type : err,
isLoading: false isLoading: false
}); });
}); });
@ -174,7 +183,16 @@ export class LedgerNanoSDecrypt extends Component<Props, State> {
this.setState({ this.setState({
publicKey: '', publicKey: '',
chainCode: '', chainCode: '',
dPath: DEFAULT_PATH dPath: this.props.dPath.value
}); });
} }
} }
function mapStateToProps(state: AppState): StateProps {
const network = getNetworkConfig(state);
return {
dPath: getSingleDPath(SecureWalletName.LEDGER_NANO_S, network)
};
}
export const LedgerNanoSDecrypt = connect(mapStateToProps)(LedgerNanoSDecryptClass);

View File

@ -1,15 +1,23 @@
import { mnemonicToSeed, validateMnemonic } from 'bip39'; import { mnemonicToSeed, validateMnemonic } from 'bip39';
import DPATHS from 'config/dpaths';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate, { translateRaw } from 'translations'; import translate, { translateRaw } from 'translations';
import DeterministicWalletsModal from './DeterministicWalletsModal'; import DeterministicWalletsModal from './DeterministicWalletsModal';
import { formatMnemonic } from 'utils/formatters'; import { formatMnemonic } from 'utils/formatters';
import { InsecureWalletName } from 'config';
const DEFAULT_PATH = DPATHS.MNEMONIC[0].value; import { AppState } from 'reducers';
import { getNetworkConfig } from 'selectors/config';
import { connect } from 'react-redux';
import { DPath } from 'config/dpaths';
import { getPaths, getSingleDPath } from 'utils/network';
interface Props { interface Props {
onUnlock(param: any): void; onUnlock(param: any): void;
} }
interface StateProps {
dPath: DPath;
}
interface State { interface State {
phrase: string; phrase: string;
formattedPhrase: string; formattedPhrase: string;
@ -18,13 +26,13 @@ interface State {
dPath: string; dPath: string;
} }
export class MnemonicDecrypt extends Component<Props, State> { class MnemonicDecryptClass extends Component<Props & StateProps, State> {
public state: State = { public state: State = {
phrase: '', phrase: '',
formattedPhrase: '', formattedPhrase: '',
pass: '', pass: '',
seed: '', seed: '',
dPath: DEFAULT_PATH dPath: this.props.dPath.value
}; };
public render() { public render() {
@ -70,7 +78,7 @@ export class MnemonicDecrypt extends Component<Props, State> {
isOpen={!!seed} isOpen={!!seed}
seed={seed} seed={seed}
dPath={dPath} dPath={dPath}
dPaths={DPATHS.MNEMONIC} dPaths={getPaths(InsecureWalletName.MNEMONIC_PHRASE)}
onCancel={this.handleCancel} onCancel={this.handleCancel}
onConfirmAddress={this.handleUnlock} onConfirmAddress={this.handleUnlock}
onPathChange={this.handlePathChange} onPathChange={this.handlePathChange}
@ -135,3 +143,12 @@ export class MnemonicDecrypt extends Component<Props, State> {
}); });
}; };
} }
function mapStateToProps(state: AppState): StateProps {
const network = getNetworkConfig(state);
return {
dPath: getSingleDPath(InsecureWalletName.MNEMONIC_PHRASE, network)
};
}
export const MnemonicDecrypt = connect(mapStateToProps)(MnemonicDecryptClass);

View File

@ -1,4 +1,3 @@
import DPATHS from 'config/dpaths';
import { TrezorWallet, TREZOR_MINIMUM_FIRMWARE } from 'libs/wallet'; import { TrezorWallet, TREZOR_MINIMUM_FIRMWARE } from 'libs/wallet';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate, { translateRaw } from 'translations'; import translate, { translateRaw } from 'translations';
@ -6,11 +5,23 @@ import TrezorConnect from 'vendor/trezor-connect';
import DeterministicWalletsModal from './DeterministicWalletsModal'; import DeterministicWalletsModal from './DeterministicWalletsModal';
import './Trezor.scss'; import './Trezor.scss';
import { Spinner } from 'components/ui'; import { Spinner } from 'components/ui';
const DEFAULT_PATH = DPATHS.TREZOR[0].value; import { getNetworkConfig } from 'selectors/config';
import { AppState } from 'reducers';
import { connect } from 'react-redux';
import { SecureWalletName } from 'config';
import { DPath } from 'config/dpaths';
import { getPaths, getSingleDPath } from 'utils/network';
interface Props { //todo: conflicts with comment in walletDecrypt -> onUnlock method
interface OwnProps {
onUnlock(param: any): void; onUnlock(param: any): void;
} }
interface StateProps {
dPath: DPath;
}
// todo: nearly duplicates ledger component props
interface State { interface State {
publicKey: string; publicKey: string;
chainCode: string; chainCode: string;
@ -19,11 +30,13 @@ interface State {
isLoading: boolean; isLoading: boolean;
} }
export class TrezorDecrypt extends Component<Props, State> { type Props = OwnProps & StateProps;
class TrezorDecryptClass extends Component<Props, State> {
public state: State = { public state: State = {
publicKey: '', publicKey: '',
chainCode: '', chainCode: '',
dPath: DEFAULT_PATH, dPath: this.props.dPath.value,
error: null, error: null,
isLoading: false isLoading: false
}; };
@ -76,7 +89,7 @@ export class TrezorDecrypt extends Component<Props, State> {
publicKey={publicKey} publicKey={publicKey}
chainCode={chainCode} chainCode={chainCode}
dPath={dPath} dPath={dPath}
dPaths={DPATHS.TREZOR} dPaths={getPaths(SecureWalletName.TREZOR)}
onCancel={this.handleCancel} onCancel={this.handleCancel}
onConfirmAddress={this.handleUnlock} onConfirmAddress={this.handleUnlock}
onPathChange={this.handlePathChange} onPathChange={this.handlePathChange}
@ -133,7 +146,16 @@ export class TrezorDecrypt extends Component<Props, State> {
this.setState({ this.setState({
publicKey: '', publicKey: '',
chainCode: '', chainCode: '',
dPath: DEFAULT_PATH dPath: this.props.dPath.value
}); });
} }
} }
function mapStateToProps(state: AppState): StateProps {
const network = getNetworkConfig(state);
return {
dPath: getSingleDPath(SecureWalletName.TREZOR, network)
};
}
export const TrezorDecrypt = connect(mapStateToProps)(TrezorDecryptClass);

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate from 'translations'; import translate from 'translations';
import { donationAddressMap } from 'config/data'; import { donationAddressMap } from 'config';
import { isValidETHAddress } from 'libs/validators'; import { isValidETHAddress } from 'libs/validators';
import { AddressOnlyWallet } from 'libs/wallet'; import { AddressOnlyWallet } from 'libs/wallet';

View File

@ -1,23 +1,31 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { translateRaw } from 'translations'; import { translateRaw, TranslateType } from 'translations';
import { NewTabLink, Tooltip } from 'components/ui'; import { NewTabLink, Tooltip } from 'components/ui';
import './WalletButton.scss'; import './WalletButton.scss';
interface Props { import { WalletName } from 'config';
name: React.ReactElement<string> | string;
description?: React.ReactElement<string> | string; interface OwnProps {
example?: React.ReactElement<string> | string; name: TranslateType;
icon?: string | null; description?: TranslateType;
helpLink?: string; example?: TranslateType;
walletType: string; icon?: string;
helpLink: string;
walletType: WalletName;
isSecure?: boolean; isSecure?: boolean;
isReadOnly?: boolean; isReadOnly?: boolean;
isDisabled?: boolean; isDisabled?: boolean;
onClick(walletType: string): void; onClick(walletType: string): void;
} }
export class WalletButton extends React.Component<Props, {}> { interface StateProps {
isFormatDisabled?: boolean;
}
type Props = OwnProps & StateProps;
export class WalletButton extends React.PureComponent<Props> {
public render() { public render() {
const { const {
name, name,
@ -45,27 +53,29 @@ export class WalletButton extends React.Component<Props, {}> {
{icon && <img className="WalletButton-title-icon" src={icon} />} {icon && <img className="WalletButton-title-icon" src={icon} />}
<span>{name}</span> <span>{name}</span>
</div> </div>
{description && <div className="WalletButton-description">{description}</div>} {description && <div className="WalletButton-description">{description}</div>}
{example && <div className="WalletButton-example">{example}</div>} {example && <div className="WalletButton-example">{example}</div>}
<div className="WalletButton-icons"> <div className="WalletButton-icons">
{isSecure === true && ( {isSecure ? (
<span className="WalletButton-icons-icon" onClick={this.stopPropogation}> <span className="WalletButton-icons-icon" onClick={this.stopPropogation}>
<i className="fa fa-shield" /> <i className="fa fa-shield" />
<Tooltip>{translateRaw('This wallet type is secure')}</Tooltip> <Tooltip>{translateRaw('This wallet type is secure')}</Tooltip>
</span> </span>
)} ) : (
{isSecure === false && (
<span className="WalletButton-icons-icon" onClick={this.stopPropogation}> <span className="WalletButton-icons-icon" onClick={this.stopPropogation}>
<i className="fa fa-exclamation-triangle" /> <i className="fa fa-exclamation-triangle" />
<Tooltip>{translateRaw('This wallet type is insecure')}</Tooltip> <Tooltip>{translateRaw('This wallet type is insecure')}</Tooltip>
</span> </span>
)} )}
{isReadOnly === true && ( {isReadOnly && (
<span className="WalletButton-icons-icon" onClick={this.stopPropogation}> <span className="WalletButton-icons-icon" onClick={this.stopPropogation}>
<i className="fa fa-eye" /> <i className="fa fa-eye" />
<Tooltip>{translateRaw('You cannot send using address only')}</Tooltip> <Tooltip>{translateRaw('You cannot send using address only')}</Tooltip>
</span> </span>
)} )}
{helpLink && ( {helpLink && (
<span className="WalletButton-icons-icon"> <span className="WalletButton-icons-icon">
<NewTabLink href={helpLink} onClick={this.stopPropogation}> <NewTabLink href={helpLink} onClick={this.stopPropogation}>
@ -80,14 +90,14 @@ export class WalletButton extends React.Component<Props, {}> {
} }
private handleClick = () => { private handleClick = () => {
if (this.props.isDisabled) { if (this.props.isDisabled || this.props.isFormatDisabled) {
return; return;
} }
this.props.onClick(this.props.walletType); this.props.onClick(this.props.walletType);
}; };
private stopPropogation = (ev: React.SyntheticEvent<any>) => { private stopPropogation = (ev: React.FormEvent<HTMLAnchorElement | HTMLSpanElement>) => {
ev.stopPropagation(); ev.stopPropagation();
}; };
} }

View File

@ -1,5 +0,0 @@
{
"READ_ONLY": ["view-only"],
"UNABLE_TO_SIGN": ["trezor", "view-only"],
"ONLINE_ONLY": ["web3", "trezor"]
}

View File

@ -0,0 +1,15 @@
import { MiscWalletName, SecureWalletName, WalletName } from 'config';
enum WalletMode {
READ_ONLY = 'READ_ONLY',
UNABLE_TO_SIGN = 'UNABLE_TO_SIGN',
ONLINE_ONLY = 'ONLINE_ONLY'
}
const walletModes: { [key in WalletMode]: WalletName[] } = {
[WalletMode.READ_ONLY]: [MiscWalletName.VIEW_ONLY],
[WalletMode.UNABLE_TO_SIGN]: [SecureWalletName.TREZOR, MiscWalletName.VIEW_ONLY],
[WalletMode.ONLINE_ONLY]: [SecureWalletName.WEB3, SecureWalletName.TREZOR]
};
export default walletModes;

View File

@ -1,3 +1,3 @@
import WalletDecrypt from './WalletDecrypt'; import WalletDecrypt from './WalletDecrypt';
export default WalletDecrypt; export default WalletDecrypt;
export { default as DISABLE_WALLETS } from './disables.json'; export { default as DISABLE_WALLETS } from './disables';

View File

@ -1,20 +1,23 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import translate from 'translations'; import translate, { TranslateType } from 'translations';
import WalletDecrypt from 'components/WalletDecrypt'; import WalletDecrypt from 'components/WalletDecrypt';
import { IWallet } from 'libs/wallet/IWallet'; import { IWallet } from 'libs/wallet/IWallet';
import './UnlockHeader.scss'; import './UnlockHeader.scss';
import { WalletName } from 'config';
interface Props { interface Props {
title: React.ReactElement<string> | string; title: TranslateType;
wallet: IWallet; wallet: IWallet;
disabledWallets?: string[]; disabledWallets?: WalletName[];
} }
interface State { interface State {
isExpanded: boolean; isExpanded: boolean;
} }
export class UnlockHeader extends React.Component<Props, State> {
export class UnlockHeader extends React.PureComponent<Props, State> {
public state = { public state = {
isExpanded: !this.props.wallet isExpanded: !this.props.wallet
}; };
@ -57,7 +60,7 @@ export class UnlockHeader extends React.Component<Props, State> {
); );
} }
public toggleisExpanded = () => { public toggleisExpanded = (_: React.FormEvent<HTMLButtonElement>) => {
this.setState(state => { this.setState(state => {
return { isExpanded: !state.isExpanded }; return { isExpanded: !state.isExpanded };
}); });

View File

@ -26,7 +26,7 @@ export function generateKindMax(BTCKINDRate: number, kind: keyof typeof buffers)
return kindMax - kindMax * buffers[kind]; return kindMax - kindMax * buffers[kind];
} }
const info = { export const bityConfig = {
serverURL, serverURL,
bityURL, bityURL,
ETHTxExplorer, ETHTxExplorer,
@ -41,5 +41,3 @@ const info = {
} }
} }
}; };
export default info;

View File

@ -1,6 +1,7 @@
import { EtherscanNode, InfuraNode, RPCNode, Web3Node } from 'libs/nodes'; import { getValues } from '../utils/helpers';
import { networkIdToName } from 'libs/values';
export const languages = require('./languages.json'); export const languages = require('./languages.json');
// Displays in the header // Displays in the header
export const VERSION = '4.0.0 (Alpha 0.1.0)'; export const VERSION = '4.0.0 (Alpha 0.1.0)';
export const N_FACTOR = 1024; export const N_FACTOR = 1024;
@ -24,7 +25,7 @@ export const ANNOUNCEMENT_MESSAGE = `
const etherScan = 'https://etherscan.io'; const etherScan = 'https://etherscan.io';
const blockChainInfo = 'https://blockchain.info'; const blockChainInfo = 'https://blockchain.info';
const ethPlorer = 'https://ethplorer.io'; export const ethPlorer = 'https://ethplorer.io';
export const ETHTxExplorer = (txHash: string): string => `${etherScan}/tx/${txHash}`; export const ETHTxExplorer = (txHash: string): string => `${etherScan}/tx/${txHash}`;
export const BTCTxExplorer = (txHash: string): string => `${blockChainInfo}/tx/${txHash}`; export const BTCTxExplorer = (txHash: string): string => `${blockChainInfo}/tx/${txHash}`;
@ -52,217 +53,32 @@ export const ledgerReferralURL = 'https://www.ledgerwallet.com/r/fa4b?path=/prod
export const trezorReferralURL = 'https://trezor.io/?a=myetherwallet.com'; export const trezorReferralURL = 'https://trezor.io/?a=myetherwallet.com';
export const bitboxReferralURL = 'https://digitalbitbox.com/?ref=mew'; export const bitboxReferralURL = 'https://digitalbitbox.com/?ref=mew';
export interface BlockExplorerConfig { export enum SecureWalletName {
name: string; WEB3 = 'web3',
tx(txHash: string): string; LEDGER_NANO_S = 'ledgerNanoS',
address(address: string): string; TREZOR = 'trezor'
} }
export interface Token { export enum HardwareWalletName {
address: string; LEDGER_NANO_S = 'ledgerNanoS',
symbol: string; TREZOR = 'trezor'
decimal: number;
error?: string | null;
} }
export interface NetworkContract { export enum InsecureWalletName {
name: string; PRIVATE_KEY = 'privateKey',
address?: string; KEYSTORE_FILE = 'keystoreFile',
abi: string; MNEMONIC_PHRASE = 'mnemonicPhrase'
} }
export interface NetworkConfig { export enum MiscWalletName {
name: string; VIEW_ONLY = 'viewOnly'
unit: string;
color?: string;
blockExplorer?: BlockExplorerConfig;
tokenExplorer?: {
name: string;
address(address: string): string;
};
chainId: number;
tokens: Token[];
contracts: NetworkContract[] | null;
isTestnet?: boolean;
} }
export interface CustomNetworkConfig { export const walletNames = getValues(
name: string; SecureWalletName,
unit: string; HardwareWalletName,
chainId: number; InsecureWalletName,
} MiscWalletName
);
export interface NodeConfig { export type WalletName = SecureWalletName | InsecureWalletName | MiscWalletName;
network: string;
lib: RPCNode | Web3Node;
service: string;
estimateGas?: boolean;
hidden?: boolean;
}
export interface CustomNodeConfig {
name: string;
url: string;
port: number;
network: string;
auth?: {
username: string;
password: string;
};
}
// Must be a website that follows the ethplorer convention of /tx/[hash] and
// address/[address] to generate the correct functions.
function makeExplorer(url): BlockExplorerConfig {
return {
name: url,
tx: hash => `${url}/tx/${hash}`,
address: address => `${url}/address/${address}`
};
}
export const NETWORKS: { [key: string]: NetworkConfig } = {
ETH: {
name: 'ETH',
unit: 'ETH',
chainId: 1,
color: '#0e97c0',
blockExplorer: makeExplorer('https://etherscan.io'),
tokenExplorer: {
name: ethPlorer,
address: ETHTokenExplorer
},
tokens: require('./tokens/eth.json'),
contracts: require('./contracts/eth.json')
},
Ropsten: {
name: 'Ropsten',
unit: 'ETH',
chainId: 3,
color: '#adc101',
blockExplorer: makeExplorer('https://ropsten.etherscan.io'),
tokens: require('./tokens/ropsten.json'),
contracts: require('./contracts/ropsten.json'),
isTestnet: true
},
Kovan: {
name: 'Kovan',
unit: 'ETH',
chainId: 42,
color: '#adc101',
blockExplorer: makeExplorer('https://kovan.etherscan.io'),
tokens: require('./tokens/ropsten.json'),
contracts: require('./contracts/ropsten.json'),
isTestnet: true
},
Rinkeby: {
name: 'Rinkeby',
unit: 'ETH',
chainId: 4,
color: '#adc101',
blockExplorer: makeExplorer('https://rinkeby.etherscan.io'),
tokens: require('./tokens/rinkeby.json'),
contracts: require('./contracts/rinkeby.json'),
isTestnet: true
}
};
export const NODES: { [key: string]: NodeConfig } = {
eth_mew: {
network: 'ETH',
lib: new RPCNode('https://api.myetherapi.com/eth'),
service: 'MyEtherWallet',
estimateGas: true
},
eth_ethscan: {
network: 'ETH',
service: 'Etherscan.io',
lib: new EtherscanNode('https://api.etherscan.io/api'),
estimateGas: false
},
eth_infura: {
network: 'ETH',
service: 'infura.io',
lib: new InfuraNode('https://mainnet.infura.io/mew'),
estimateGas: false
},
rop_mew: {
network: 'Ropsten',
service: 'MyEtherWallet',
lib: new RPCNode('https://api.myetherapi.com/rop'),
estimateGas: false
},
rop_infura: {
network: 'Ropsten',
service: 'infura.io',
lib: new InfuraNode('https://ropsten.infura.io/mew'),
estimateGas: false
},
kov_ethscan: {
network: 'Kovan',
service: 'Etherscan.io',
lib: new EtherscanNode('https://kovan.etherscan.io/api'),
estimateGas: false
},
rin_ethscan: {
network: 'Rinkeby',
service: 'Etherscan.io',
lib: new EtherscanNode('https://rinkeby.etherscan.io/api'),
estimateGas: false
},
rin_infura: {
network: 'Rinkeby',
service: 'infura.io',
lib: new InfuraNode('https://rinkeby.infura.io/mew'),
estimateGas: false
}
};
interface Web3NodeInfo {
networkId: string;
lib: Web3Node;
}
export async function setupWeb3Node(): Promise<Web3NodeInfo> {
const { web3 } = window as any;
if (!web3 || !web3.currentProvider || !web3.currentProvider.sendAsync) {
throw new Error(
'Web3 not found. Please check that MetaMask is installed, or that MyEtherWallet is open in Mist.'
);
}
const lib = new Web3Node();
const networkId = await lib.getNetVersion();
const accounts = await lib.getAccounts();
if (!accounts.length) {
throw new Error('No accounts found in MetaMask / Mist.');
}
if (networkId === 'loading') {
throw new Error('MetaMask / Mist is still loading. Please refresh the page and try again.');
}
return { networkId, lib };
}
export async function isWeb3NodeAvailable(): Promise<boolean> {
try {
await setupWeb3Node();
return true;
} catch (e) {
return false;
}
}
export async function initWeb3Node(): Promise<void> {
const { networkId, lib } = await setupWeb3Node();
NODES.web3 = {
network: networkIdToName(networkId),
service: 'MetaMask / Mist',
lib,
estimateGas: false,
hidden: true
};
}

View File

@ -1,46 +1,52 @@
const ETH_DEFAULT = { export interface DPath {
label: string;
value: string; // TODO determine method for more precise typing for path
}
export const ETH_DEFAULT: DPath = {
label: 'Default (ETH)', label: 'Default (ETH)',
value: "m/44'/60'/0'/0" value: "m/44'/60'/0'/0"
}; };
const ETH_TREZOR = { export const ETH_TREZOR: DPath = {
label: 'TREZOR (ETH)', label: 'TREZOR (ETH)',
value: "m/44'/60'/0'/0" value: "m/44'/60'/0'/0"
}; };
const ETH_LEDGER = { export const ETH_LEDGER: DPath = {
label: 'Ledger (ETH)', label: 'Ledger (ETH)',
value: "m/44'/60'/0'" value: "m/44'/60'/0'"
}; };
const ETC_LEDGER = { export const ETC_LEDGER: DPath = {
label: 'Ledger (ETC)', label: 'Ledger (ETC)',
value: "m/44'/60'/160720'/0'" value: "m/44'/60'/160720'/0'"
}; };
const ETC_TREZOR = { export const ETC_TREZOR: DPath = {
label: 'TREZOR (ETC)', label: 'TREZOR (ETC)',
value: "m/44'/61'/0'/0" value: "m/44'/61'/0'/0"
}; };
const TESTNET = { export const ETH_TESTNET: DPath = {
label: 'Testnet', label: 'Testnet (ETH)',
value: "m/44'/1'/0'/0" value: "m/44'/1'/0'/0"
}; };
const EXPANSE = { export const EXP_DEFAULT: DPath = {
label: 'Expanse', label: 'Default (EXP)',
value: "m/44'/40'/0'/0" value: "m/44'/40'/0'/0"
}; };
const TREZOR = [ETH_TREZOR, ETC_TREZOR, TESTNET]; export const UBQ_DEFAULT: DPath = {
label: 'Default (UBQ)',
const MNEMONIC = [ETH_DEFAULT, ETH_LEDGER, ETC_LEDGER, ETC_TREZOR, TESTNET, EXPANSE]; value: "m/44'/108'/0'/0"
const LEDGER = [ETH_LEDGER, ETC_LEDGER, TESTNET];
export default {
TREZOR,
MNEMONIC,
LEDGER
}; };
export const ETH_SINGULAR: DPath = {
label: 'SingularDTV',
value: "m/0'/0'/0'"
};
// PATHS TO BE INCLUDED REGARDLESS OF WALLET FORMAT
export const EXTRA_PATHS = [ETH_SINGULAR];

3
common/config/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './networks';
export * from './data';
export * from './bity';

364
common/config/networks.ts Normal file
View File

@ -0,0 +1,364 @@
import { ethPlorer, ETHTokenExplorer, SecureWalletName, InsecureWalletName } from './data';
import { EtherscanNode, InfuraNode, RPCNode, Web3Node } from 'libs/nodes';
import { networkIdToName } from 'libs/values';
import {
ETH_DEFAULT,
ETH_TREZOR,
ETH_LEDGER,
ETC_LEDGER,
ETC_TREZOR,
ETH_TESTNET,
EXP_DEFAULT,
UBQ_DEFAULT,
DPath
} from 'config/dpaths';
export interface BlockExplorerConfig {
origin: string;
txUrl(txHash: string): string;
addressUrl(address: string): string;
}
export interface Token {
address: string;
symbol: string;
decimal: number;
error?: string | null;
}
export interface NetworkContract {
name: NetworkKeys;
address?: string;
abi: string;
}
export interface DPathFormats {
trezor: DPath;
ledgerNanoS: DPath;
mnemonicPhrase: DPath;
}
export interface NetworkConfig {
// TODO really try not to allow strings due to custom networks
name: NetworkKeys;
unit: string;
color?: string;
blockExplorer?: BlockExplorerConfig;
tokenExplorer?: {
name: string;
address(address: string): string;
};
chainId: number;
tokens: Token[];
contracts: NetworkContract[] | null;
dPathFormats: DPathFormats;
isTestnet?: boolean;
}
export interface CustomNetworkConfig {
name: string;
unit: string;
chainId: number;
dPathFormats: DPathFormats | null;
}
export interface NodeConfig {
network: NetworkKeys;
lib: RPCNode | Web3Node;
service: string;
estimateGas?: boolean;
hidden?: boolean;
}
export interface CustomNodeConfig {
name: string;
url: string;
port: number;
network: string;
auth?: {
username: string;
password: string;
};
}
// Must be a website that follows the ethplorer convention of /tx/[hash] and
// address/[address] to generate the correct functions.
function makeExplorer(origin: string): BlockExplorerConfig {
return {
origin,
txUrl: hash => `${origin}/tx/${hash}`,
addressUrl: address => `${origin}/address/${address}`
};
}
const ETH: NetworkConfig = {
name: 'ETH',
unit: 'ETH',
chainId: 1,
color: '#0e97c0',
blockExplorer: makeExplorer('https://etherscan.io'),
tokenExplorer: {
name: ethPlorer,
address: ETHTokenExplorer
},
tokens: require('./tokens/eth.json'),
contracts: require('./contracts/eth.json'),
dPathFormats: {
[SecureWalletName.TREZOR]: ETH_TREZOR,
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_DEFAULT
}
};
const Ropsten: NetworkConfig = {
name: 'Ropsten',
unit: 'ETH',
chainId: 3,
color: '#adc101',
blockExplorer: makeExplorer('https://ropsten.etherscan.io'),
tokens: require('./tokens/ropsten.json'),
contracts: require('./contracts/ropsten.json'),
isTestnet: true,
dPathFormats: {
[SecureWalletName.TREZOR]: ETH_TESTNET,
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
}
};
const Kovan: NetworkConfig = {
name: 'Kovan',
unit: 'ETH',
chainId: 42,
color: '#adc101',
blockExplorer: makeExplorer('https://kovan.etherscan.io'),
tokens: require('./tokens/ropsten.json'),
contracts: require('./contracts/ropsten.json'),
isTestnet: true,
dPathFormats: {
[SecureWalletName.TREZOR]: ETH_TESTNET,
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
}
};
const Rinkeby: NetworkConfig = {
name: 'Rinkeby',
unit: 'ETH',
chainId: 4,
color: '#adc101',
blockExplorer: makeExplorer('https://rinkeby.etherscan.io'),
tokens: require('./tokens/rinkeby.json'),
contracts: require('./contracts/rinkeby.json'),
isTestnet: true,
dPathFormats: {
[SecureWalletName.TREZOR]: ETH_TESTNET,
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
}
};
const ETC: NetworkConfig = {
name: 'ETC',
unit: 'ETC',
chainId: 61,
color: '#669073',
blockExplorer: makeExplorer('https://gastracker.io'),
tokens: require('./tokens/etc.json'),
contracts: require('./contracts/etc.json'),
dPathFormats: {
[SecureWalletName.TREZOR]: ETC_TREZOR,
[SecureWalletName.LEDGER_NANO_S]: ETC_LEDGER,
[InsecureWalletName.MNEMONIC_PHRASE]: ETC_TREZOR
}
};
const UBQ: NetworkConfig = {
name: 'UBQ',
unit: 'UBQ',
chainId: 8,
color: '#b37aff',
blockExplorer: makeExplorer('https://ubiqscan.io/en'),
tokens: require('./tokens/ubq.json'),
contracts: require('./contracts/ubq.json'),
dPathFormats: {
[SecureWalletName.TREZOR]: UBQ_DEFAULT,
[SecureWalletName.LEDGER_NANO_S]: UBQ_DEFAULT,
[InsecureWalletName.MNEMONIC_PHRASE]: UBQ_DEFAULT
}
};
const EXP: NetworkConfig = {
name: 'EXP',
unit: 'EXP',
chainId: 2,
color: '#673ab7',
blockExplorer: makeExplorer('http://www.gander.tech'),
tokens: require('./tokens/exp.json'),
contracts: require('./contracts/exp.json'),
dPathFormats: {
[SecureWalletName.TREZOR]: EXP_DEFAULT,
[SecureWalletName.LEDGER_NANO_S]: EXP_DEFAULT,
[InsecureWalletName.MNEMONIC_PHRASE]: EXP_DEFAULT
}
};
export const NETWORKS = {
ETH,
Ropsten,
Kovan,
Rinkeby,
ETC,
UBQ,
EXP
};
export type NetworkKeys = keyof typeof NETWORKS;
enum NodeName {
ETH_MEW = 'eth_mew',
ETH_ETHSCAN = 'eth_ethscan',
ETH_INFURA = 'eth_infura',
ROP_MEW = 'rop_mew',
ROP_INFURA = 'rop_infura',
KOV_ETHSCAN = 'kov_ethscan',
RIN_ETHSCAN = 'rin_ethscan',
RIN_INFURA = 'rin_infura',
ETC_EPOOL = 'etc_epool',
UBQ = 'ubq',
EXP_TECH = 'exp_tech'
}
type NonWeb3NodeConfigs = { [key in NodeName]: NodeConfig };
interface Web3NodeConfig {
web3?: NodeConfig;
}
type NodeConfigs = NonWeb3NodeConfigs & Web3NodeConfig;
export const NODES: NodeConfigs = {
eth_mew: {
network: 'ETH',
lib: new RPCNode('https://api.myetherapi.com/eth'),
service: 'MyEtherWallet',
estimateGas: true
},
eth_ethscan: {
network: 'ETH',
service: 'Etherscan.io',
lib: new EtherscanNode('https://api.etherscan.io/api'),
estimateGas: false
},
eth_infura: {
network: 'ETH',
service: 'infura.io',
lib: new InfuraNode('https://mainnet.infura.io/mew'),
estimateGas: false
},
rop_mew: {
network: 'Ropsten',
service: 'MyEtherWallet',
lib: new RPCNode('https://api.myetherapi.com/rop'),
estimateGas: false
},
rop_infura: {
network: 'Ropsten',
service: 'infura.io',
lib: new InfuraNode('https://ropsten.infura.io/mew'),
estimateGas: false
},
kov_ethscan: {
network: 'Kovan',
service: 'Etherscan.io',
lib: new EtherscanNode('https://kovan.etherscan.io/api'),
estimateGas: false
},
rin_ethscan: {
network: 'Rinkeby',
service: 'Etherscan.io',
lib: new EtherscanNode('https://rinkeby.etherscan.io/api'),
estimateGas: false
},
rin_infura: {
network: 'Rinkeby',
service: 'infura.io',
lib: new InfuraNode('https://rinkeby.infura.io/mew'),
estimateGas: false
},
etc_epool: {
network: 'ETC',
service: 'Epool.io',
lib: new RPCNode('https://mewapi.epool.io'),
estimateGas: false
},
ubq: {
network: 'UBQ',
service: 'ubiqscan.io',
lib: new RPCNode('https://pyrus2.ubiqscan.io'),
estimateGas: true
},
exp_tech: {
network: 'EXP',
service: 'Expanse.tech',
lib: new RPCNode('https://node.expanse.tech/'),
estimateGas: true
}
};
interface Web3NodeInfo {
networkId: string;
lib: Web3Node;
}
export async function setupWeb3Node(): Promise<Web3NodeInfo> {
const { web3 } = window as any;
if (!web3 || !web3.currentProvider || !web3.currentProvider.sendAsync) {
throw new Error(
'Web3 not found. Please check that MetaMask is installed, or that MyEtherWallet is open in Mist.'
);
}
const lib = new Web3Node();
const networkId = await lib.getNetVersion();
const accounts = await lib.getAccounts();
if (!accounts.length) {
throw new Error('No accounts found in MetaMask / Mist.');
}
if (networkId === 'loading') {
throw new Error('MetaMask / Mist is still loading. Please refresh the page and try again.');
}
return { networkId, lib };
}
export async function isWeb3NodeAvailable(): Promise<boolean> {
try {
await setupWeb3Node();
return true;
} catch (e) {
return false;
}
}
export const Web3Service = 'MetaMask / Mist';
export interface NodeConfigOverride extends NodeConfig {
network: any;
}
export async function initWeb3Node(): Promise<void> {
const { networkId, lib } = await setupWeb3Node();
const web3: NodeConfigOverride = {
network: networkIdToName(networkId),
service: Web3Service,
lib,
estimateGas: false,
hidden: true
};
NODES.web3 = web3;
}

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate from 'translations'; import translate from 'translations';
import { NetworkContract } from 'config/data'; import { NetworkContract } from 'config';
import { getNetworkContracts } from 'selectors/config'; import { getNetworkContracts } from 'selectors/config';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { AppState } from 'reducers'; import { AppState } from 'reducers';

View File

@ -1,7 +1,7 @@
import { NewTabLink } from 'components/ui'; import { NewTabLink } from 'components/ui';
import React from 'react'; import React from 'react';
import GeneralInfoNode from './GeneralInfoNode'; import GeneralInfoNode from './GeneralInfoNode';
import { knowledgeBaseURL } from 'config/data'; import { knowledgeBaseURL } from 'config';
import { InfoNode } from './types'; import { InfoNode } from './types';
const generalInfoNodes: InfoNode[] = [ const generalInfoNodes: InfoNode[] = [

View File

@ -5,7 +5,7 @@ import translate from 'translations';
import { makeBlob } from 'utils/blob'; import { makeBlob } from 'utils/blob';
import './DownloadWallet.scss'; import './DownloadWallet.scss';
import Template from '../Template'; import Template from '../Template';
import { N_FACTOR } from 'config/data'; import { N_FACTOR } from 'config';
interface Props { interface Props {
wallet: IFullWallet; wallet: IFullWallet;

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate from 'translations'; import translate from 'translations';
import { MINIMUM_PASSWORD_LENGTH } from 'config/data'; import { MINIMUM_PASSWORD_LENGTH } from 'config';
import './EnterPassword.scss'; import './EnterPassword.scss';
import PasswordInput from './PasswordInput'; import PasswordInput from './PasswordInput';
import Template from '../Template'; import Template from '../Template';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import translate from 'translations'; import translate from 'translations';
import TabSection from 'containers/TabSection'; import TabSection from 'containers/TabSection';
import { knowledgeBaseURL } from 'config/data'; import { knowledgeBaseURL } from 'config';
const Help = () => ( const Help = () => (
<TabSection> <TabSection>

View File

@ -7,7 +7,7 @@ import { isValidPrivKey } from 'libs/validators';
import { stripHexPrefix } from 'libs/values'; import { stripHexPrefix } from 'libs/values';
import translate from 'translations'; import translate from 'translations';
import './KeystoreDetails.scss'; import './KeystoreDetails.scss';
import { N_FACTOR } from 'config/data'; import { N_FACTOR } from 'config';
interface State { interface State {
secretKey: string; secretKey: string;

View File

@ -1,4 +1,4 @@
import { donationAddressMap } from 'config/data'; import { donationAddressMap } from 'config';
import React from 'react'; import React from 'react';
import translate from 'translations'; import translate from 'translations';

View File

@ -12,7 +12,7 @@ import {
ICurrentValue ICurrentValue
} from 'selectors/transaction/current'; } from 'selectors/transaction/current';
import BN from 'bn.js'; import BN from 'bn.js';
import { NetworkConfig } from 'config/data'; import { NetworkConfig } from 'config';
import { validNumber, validDecimal } from 'libs/validators'; import { validNumber, validDecimal } from 'libs/validators';
import { getGasLimit } from 'selectors/transaction'; import { getGasLimit } from 'selectors/transaction';
import { AddressField, AmountField, GasLimitField } from 'components'; import { AddressField, AmountField, GasLimitField } from 'components';

View File

@ -3,24 +3,28 @@ import { connect } from 'react-redux';
import translate from 'translations'; import translate from 'translations';
import TabSection from 'containers/TabSection'; import TabSection from 'containers/TabSection';
import { UnlockHeader } from 'components/ui'; import { UnlockHeader } from 'components/ui';
import { SideBar } from './components/index'; import { SideBar } from './components';
import { IReadOnlyWallet, IFullWallet } from 'libs/wallet'; import { IReadOnlyWallet, IFullWallet } from 'libs/wallet';
import { getWalletInst } from 'selectors/wallet'; import { getWalletInst } from 'selectors/wallet';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import tabs from './tabs'; import tabs from './tabs';
import SubTabs, { Props as TabProps } from 'components/SubTabs'; import SubTabs, { Props as TabProps } from 'components/SubTabs';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
import { NetworkConfig } from 'config';
import { getNetworkConfig } from 'selectors/config';
export type WalletTypes = IReadOnlyWallet | IFullWallet | undefined | null;
interface StateProps { interface StateProps {
wallet: AppState['wallet']['inst']; wallet: AppState['wallet']['inst'];
network: AppState['config']['network'];
} }
export interface SubTabProps { export interface SubTabProps {
wallet: WalletTypes; wallet: WalletTypes;
network: NetworkConfig;
} }
export type WalletTypes = IReadOnlyWallet | IFullWallet | undefined | null;
type Props = StateProps & RouteComponentProps<{}>; type Props = StateProps & RouteComponentProps<{}>;
const determineActiveTab = (wallet: WalletTypes, activeTab: string) => { const determineActiveTab = (wallet: WalletTypes, activeTab: string) => {
@ -33,7 +37,7 @@ const determineActiveTab = (wallet: WalletTypes, activeTab: string) => {
class SendTransaction extends React.Component<Props> { class SendTransaction extends React.Component<Props> {
public render() { public render() {
const { wallet, location } = this.props; const { wallet, location, network } = this.props;
const activeTab = location.pathname.split('/')[2]; const activeTab = location.pathname.split('/')[2];
const tabProps: TabProps<SubTabProps> = { const tabProps: TabProps<SubTabProps> = {
@ -41,7 +45,7 @@ class SendTransaction extends React.Component<Props> {
activeTab: determineActiveTab(wallet, activeTab), activeTab: determineActiveTab(wallet, activeTab),
sideBar: <SideBar />, sideBar: <SideBar />,
tabs, tabs,
subTabProps: { wallet } subTabProps: { wallet, network }
}; };
interface IWalletTabs { interface IWalletTabs {
@ -61,4 +65,7 @@ class SendTransaction extends React.Component<Props> {
} }
} }
export default connect((state: AppState) => ({ wallet: getWalletInst(state) }))(SendTransaction); export default connect((state: AppState) => ({
wallet: getWalletInst(state),
network: getNetworkConfig(state)
}))(SendTransaction);

View File

@ -5,6 +5,7 @@ import translate from 'translations';
import { Fields, UnavailableWallets, WalletInfo, RequestPayment } from './components/index'; import { Fields, UnavailableWallets, WalletInfo, RequestPayment } from './components/index';
import { Tab } from 'components/SubTabs'; import { Tab } from 'components/SubTabs';
import { SubTabProps } from 'containers/Tabs/SendTransaction'; import { SubTabProps } from 'containers/Tabs/SendTransaction';
import { isNetworkUnit } from 'utils/network';
const SendTab: Tab<SubTabProps> = { const SendTab: Tab<SubTabProps> = {
path: 'send', path: 'send',
@ -41,6 +42,10 @@ const InfoTab: Tab<SubTabProps> = {
const RequestTab: Tab<SubTabProps> = { const RequestTab: Tab<SubTabProps> = {
path: 'request', path: 'request',
name: translate('Request Payment'), name: translate('Request Payment'),
isDisabled: (props: SubTabProps) => {
const isETHNetwork = isNetworkUnit(props.network, 'ETH');
return !isETHNetwork;
},
render(props: SubTabProps) { render(props: SubTabProps) {
const content = props && props.wallet ? <RequestPayment wallet={props.wallet} /> : null; const content = props && props.wallet ? <RequestPayment wallet={props.wallet} /> : null;
return <div>{content}</div>; return <div>{content}</div>;

View File

@ -6,7 +6,7 @@ import {
SwapInput SwapInput
} from 'reducers/swap/types'; } from 'reducers/swap/types';
import SimpleButton from 'components/ui/SimpleButton'; import SimpleButton from 'components/ui/SimpleButton';
import bityConfig, { generateKindMax, generateKindMin, WhitelistedCoins } from 'config/bity'; import { generateKindMax, generateKindMin, WhitelistedCoins, bityConfig } from 'config/bity';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate from 'translations'; import translate from 'translations';
import { combineAndUpper } from 'utils/formatters'; import { combineAndUpper } from 'utils/formatters';

View File

@ -6,7 +6,7 @@ import {
import bityLogoWhite from 'assets/images/logo-bity-white.svg'; import bityLogoWhite from 'assets/images/logo-bity-white.svg';
import shapeshiftLogoWhite from 'assets/images/logo-shapeshift.svg'; import shapeshiftLogoWhite from 'assets/images/logo-shapeshift.svg';
import Spinner from 'components/ui/Spinner'; import Spinner from 'components/ui/Spinner';
import { bityReferralURL, shapeshiftReferralURL } from 'config/data'; import { bityReferralURL, shapeshiftReferralURL } from 'config';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate from 'translations'; import translate from 'translations';
import './CurrentRates.scss'; import './CurrentRates.scss';

View File

@ -8,7 +8,7 @@ import { configureLiteSend, TConfigureLiteSend } from 'actions/swap';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { shouldDisplayLiteSend } from 'selectors/swap'; import { shouldDisplayLiteSend } from 'selectors/swap';
import { NetworkConfig } from 'config/data'; import { NetworkConfig } from 'config';
interface DispatchProps { interface DispatchProps {
configureLiteSend: TConfigureLiteSend; configureLiteSend: TConfigureLiteSend;
@ -36,10 +36,7 @@ class LiteSendClass extends Component<Props> {
renderMe = ( renderMe = (
<div className="row"> <div className="row">
<div className="col-xs-8 col-xs-push-2 text-center"> <div className="col-xs-8 col-xs-push-2 text-center">
<h5 style={{ color: 'red' }}> <h5>Note: Send is only supported on Ethereum Mainnet.</h5>
WARNING: You are currently not on the Ethereum Mainnet. Please switch nodes in order
for the token swap to function as intended.
</h5>
</div> </div>
</div> </div>
); );

View File

@ -8,7 +8,7 @@ import {
import { SwapInput } from 'reducers/swap/types'; import { SwapInput } from 'reducers/swap/types';
import classnames from 'classnames'; import classnames from 'classnames';
import SimpleButton from 'components/ui/SimpleButton'; import SimpleButton from 'components/ui/SimpleButton';
import { donationAddressMap } from 'config/data'; import { donationAddressMap } from 'config';
import { isValidBTCAddress, isValidETHAddress } from 'libs/validators'; import { isValidBTCAddress, isValidETHAddress } from 'libs/validators';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate from 'translations'; import translate from 'translations';

View File

@ -1,7 +1,7 @@
import { RestartSwapAction } from 'actions/swap'; import { RestartSwapAction } from 'actions/swap';
import bityLogo from 'assets/images/logo-bity.svg'; import bityLogo from 'assets/images/logo-bity.svg';
import shapeshiftLogo from 'assets/images/shapeshift-dark.svg'; import shapeshiftLogo from 'assets/images/shapeshift-dark.svg';
import { bityReferralURL } from 'config/data'; import { bityReferralURL } from 'config';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate from 'translations'; import translate from 'translations';
import './SwapInfoHeader.scss'; import './SwapInfoHeader.scss';

View File

@ -1,5 +1,5 @@
import { TShowNotification } from 'actions/notifications'; import { TShowNotification } from 'actions/notifications';
import bityConfig from 'config/bity'; import { bityConfig } from 'config/bity';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate, { translateRaw } from 'translations'; import translate, { translateRaw } from 'translations';
import './SwapProgress.scss'; import './SwapProgress.scss';

View File

@ -1,4 +1,4 @@
import { Token } from 'config/data'; import { Token } from 'config';
import { Wei, TokenValue } from 'libs/units'; import { Wei, TokenValue } from 'libs/units';
import { IHexStrTransaction } from 'libs/transaction'; import { IHexStrTransaction } from 'libs/transaction';

View File

@ -1,6 +1,6 @@
import RPCNode from '../rpc'; import RPCNode from '../rpc';
import RPCClient from '../rpc/client'; import RPCClient from '../rpc/client';
import { CustomNodeConfig } from 'config/data'; import { CustomNodeConfig } from 'config';
export default class CustomNode extends RPCNode { export default class CustomNode extends RPCNode {
constructor(config: CustomNodeConfig) { constructor(config: CustomNodeConfig) {

View File

@ -1,4 +1,4 @@
import { Token } from 'config/data'; import { Token } from 'config';
import ERC20 from 'libs/erc20'; import ERC20 from 'libs/erc20';
import RPCRequests from '../rpc/requests'; import RPCRequests from '../rpc/requests';
import { import {

View File

@ -1,5 +1,5 @@
import BN from 'bn.js'; import BN from 'bn.js';
import { Token } from 'config/data'; import { Token } from 'config';
import { IHexStrTransaction } from 'libs/transaction'; import { IHexStrTransaction } from 'libs/transaction';
import { Wei, TokenValue } from 'libs/units'; import { Wei, TokenValue } from 'libs/units';
import { stripHexPrefix } from 'libs/values'; import { stripHexPrefix } from 'libs/values';

View File

@ -1,4 +1,4 @@
import { Token } from 'config/data'; import { Token } from 'config';
import ERC20 from 'libs/erc20'; import ERC20 from 'libs/erc20';
import { import {
CallRequest, CallRequest,

View File

@ -1,6 +1,7 @@
import { Wei, toTokenBase } from 'libs/units'; import { Wei, toTokenBase } from 'libs/units';
import { addHexPrefix } from 'ethereumjs-util'; import { addHexPrefix } from 'ethereumjs-util';
import BN from 'bn.js'; import BN from 'bn.js';
import { NetworkKeys } from 'config';
export function stripHexPrefix(value: string) { export function stripHexPrefix(value: string) {
return value.replace('0x', ''); return value.replace('0x', '');
@ -23,7 +24,7 @@ export function sanitizeHex(hex: string) {
return hex !== '' ? `0x${padLeftEven(hexStr)}` : ''; return hex !== '' ? `0x${padLeftEven(hexStr)}` : '';
} }
export function networkIdToName(networkId: string | number): string { export function networkIdToName(networkId: string | number): NetworkKeys {
switch (networkId.toString()) { switch (networkId.toString()) {
case '1': case '1':
return 'ETH'; return 'ETH';

View File

@ -16,7 +16,7 @@ import {
CustomNodeConfig, CustomNodeConfig,
NetworkConfig, NetworkConfig,
CustomNetworkConfig CustomNetworkConfig
} from '../config/data'; } from 'config';
import { makeCustomNodeId } from 'utils/node'; import { makeCustomNodeId } from 'utils/node';
import { makeCustomNetworkId } from 'utils/network'; import { makeCustomNetworkId } from 'utils/network';

View File

@ -4,7 +4,7 @@ import {
RemoveCustomTokenAction RemoveCustomTokenAction
} from 'actions/customTokens'; } from 'actions/customTokens';
import { TypeKeys } from 'actions/customTokens/constants'; import { TypeKeys } from 'actions/customTokens/constants';
import { Token } from 'config/data'; import { Token } from 'config';
export type State = Token[]; export type State = Token[];

View File

@ -1,5 +1,5 @@
import { Option } from 'actions/swap/actionTypes'; import { Option } from 'actions/swap/actionTypes';
import { WhitelistedCoins } from 'config/bity'; import { WhitelistedCoins } from 'config';
export interface SwapInput { export interface SwapInput {
id: WhitelistedCoins; id: WhitelistedCoins;

View File

@ -10,7 +10,14 @@ import {
select, select,
race race
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import { NODES, NETWORKS, NodeConfig, CustomNodeConfig, CustomNetworkConfig } from 'config/data'; import {
NODES,
NETWORKS,
NodeConfig,
CustomNodeConfig,
CustomNetworkConfig,
Web3Service
} from 'config';
import { import {
makeCustomNodeId, makeCustomNodeId,
getCustomNodeConfigFromId, getCustomNodeConfigFromId,
@ -37,12 +44,9 @@ import {
} from 'actions/config'; } from 'actions/config';
import { showNotification } from 'actions/notifications'; import { showNotification } from 'actions/notifications';
import { translateRaw } from 'translations'; import { translateRaw } from 'translations';
import { IWallet, Web3Wallet } from 'libs/wallet'; import { Web3Wallet } from 'libs/wallet';
import { getWalletInst } from 'selectors/wallet';
import { TypeKeys as WalletTypeKeys } from 'actions/wallet/constants'; import { TypeKeys as WalletTypeKeys } from 'actions/wallet/constants';
import { State as ConfigState, INITIAL_STATE as configInitialState } from 'reducers/config'; import { State as ConfigState, INITIAL_STATE as configInitialState } from 'reducers/config';
import { resetWallet } from 'actions/wallet';
import { reset as resetTransaction } from 'actions/transaction';
export const getConfig = (state: AppState): ConfigState => state.config; export const getConfig = (state: AppState): ConfigState => state.config;
@ -171,12 +175,18 @@ export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIte
yield put(setLatestBlock(latestBlock)); yield put(setLatestBlock(latestBlock));
yield put(changeNode(action.payload, actionConfig, actionNetwork)); yield put(changeNode(action.payload, actionConfig, actionNetwork));
const currentWallet: IWallet | null = yield select(getWalletInst); // TODO - re-enable once DeterministicWallet state is fixed to flush properly.
// DeterministicWallet keeps path related state we need to flush before we can stop reloading
// const currentWallet: IWallet | null = yield select(getWalletInst);
// if there's no wallet, do not reload as there's no component state to resync // if there's no wallet, do not reload as there's no component state to resync
if (currentWallet && currentConfig.network !== actionConfig.network) { // if (currentWallet && currentConfig.network !== actionConfig.network) {
yield put(resetWallet());
yield put(resetTransaction()); const isNewNetwork = currentConfig.network !== actionConfig.network;
const newIsWeb3 = actionConfig.service === Web3Service;
// don't reload when web3 is selected; node will automatically re-set and state is not an issue here
if (isNewNetwork && !newIsWeb3) {
yield call(reload);
} }
} }

View File

@ -5,7 +5,7 @@ import {
updateDeterministicWallet updateDeterministicWallet
} from 'actions/deterministicWallets'; } from 'actions/deterministicWallets';
import { showNotification } from 'actions/notifications'; import { showNotification } from 'actions/notifications';
import { Token } from 'config/data'; import { Token } from 'config';
import { publicToAddress, toChecksumAddress } from 'ethereumjs-util'; import { publicToAddress, toChecksumAddress } from 'ethereumjs-util';
import HDKey from 'hdkey'; import HDKey from 'hdkey';
import { INode } from 'libs/nodes/INode'; import { INode } from 'libs/nodes/INode';

View File

@ -12,7 +12,7 @@ import {
TypeKeys as TK TypeKeys as TK
} from 'actions/transaction'; } from 'actions/transaction';
import Tx from 'ethereumjs-tx'; import Tx from 'ethereumjs-tx';
import { NetworkConfig } from 'config/data'; import { NetworkConfig } from 'config';
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { showNotification } from 'actions/notifications'; import { showNotification } from 'actions/notifications';

View File

@ -1,6 +1,6 @@
import { apply, select, call } from 'redux-saga/effects'; import { apply, select, call } from 'redux-saga/effects';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { Token } from 'config/data'; import { Token } from 'config';
import { INode } from 'libs/nodes/INode'; import { INode } from 'libs/nodes/INode';
import { IWallet, WalletConfig } from 'libs/wallet'; import { IWallet, WalletConfig } from 'libs/wallet';
import { TokenBalance } from 'selectors/wallet'; import { TokenBalance } from 'selectors/wallet';

View File

@ -36,7 +36,7 @@ import {
Web3Wallet, Web3Wallet,
WalletConfig WalletConfig
} from 'libs/wallet'; } from 'libs/wallet';
import { NODES, initWeb3Node, Token } from 'config/data'; import { NODES, initWeb3Node, Token } from 'config';
import { SagaIterator, delay, Task } from 'redux-saga'; import { SagaIterator, delay, Task } from 'redux-saga';
import { apply, call, fork, put, select, takeEvery, take, cancel } from 'redux-saga/effects'; import { apply, call, fork, put, select, takeEvery, take, cancel } from 'redux-saga/effects';
import { getNodeLib, getAllTokens, getOffline } from 'selectors/config'; import { getNodeLib, getAllTokens, getOffline } from 'selectors/config';
@ -261,6 +261,9 @@ export function* unlockWeb3(): SagaIterator {
action.type === ConfigTypeKeys.CONFIG_NODE_CHANGE && action.payload.nodeSelection === 'web3' action.type === ConfigTypeKeys.CONFIG_NODE_CHANGE && action.payload.nodeSelection === 'web3'
); );
if (!NODES.web3) {
throw Error('Web3 node config not found!');
}
const network = NODES.web3.network; const network = NODES.web3.network;
const nodeLib: INode | Web3Node = yield select(getNodeLib); const nodeLib: INode | Web3Node = yield select(getNodeLib);

View File

@ -24,6 +24,9 @@
// --- RC SLIDER --- // --- RC SLIDER ---
@import "~rc-slider/assets/index.css"; @import "~rc-slider/assets/index.css";
// --- React Select ---
@import '~react-select/dist/react-select.css';
// --- CUSTOM --- // --- CUSTOM ---
@import "./styles/badbrowser"; @import "./styles/badbrowser";
@import "./styles/noscript"; @import "./styles/noscript";

View File

@ -1,11 +1,11 @@
import { import {
CustomNetworkConfig,
CustomNodeConfig,
NetworkConfig, NetworkConfig,
NetworkContract, NetworkContract,
NodeConfig, NodeConfig,
CustomNodeConfig,
CustomNetworkConfig,
Token Token
} from 'config/data'; } from 'config';
import { INode } from 'libs/nodes/INode'; import { INode } from 'libs/nodes/INode';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { getUnit } from 'selectors/transaction/meta'; import { getUnit } from 'selectors/transaction/meta';

View File

@ -1,5 +1,5 @@
import { TokenValue, Wei } from 'libs/units'; import { TokenValue, Wei } from 'libs/units';
import { Token } from 'config/data'; import { Token } from 'config';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { getNetworkConfig } from 'selectors/config'; import { getNetworkConfig } from 'selectors/config';
import { IWallet, Web3Wallet, LedgerWallet, TrezorWallet, WalletConfig } from 'libs/wallet'; import { IWallet, Web3Wallet, LedgerWallet, TrezorWallet, WalletConfig } from 'libs/wallet';

View File

@ -22,3 +22,6 @@ export function getParam(query: { [key: string]: string }, key: string) {
export function isPositiveInteger(n: number) { export function isPositiveInteger(n: number) {
return Number.isInteger(n) && n > 0; return Number.isInteger(n) && n > 0;
} }
export const getValues = (...args) =>
args.reduce((acc, currArg) => [...acc, ...Object.values(currArg)], []);

View File

@ -2,7 +2,7 @@ import { fromPrivateKey, IFullWallet, fromV3 } from 'ethereumjs-wallet';
import { isValidPrivKey } from 'libs/validators'; import { isValidPrivKey } from 'libs/validators';
import { stripHexPrefix } from 'libs/values'; import { stripHexPrefix } from 'libs/values';
import { makeBlob } from 'utils/blob'; import { makeBlob } from 'utils/blob';
import { N_FACTOR } from 'config/data'; import { N_FACTOR } from 'config';
export interface KeystoreFile { export interface KeystoreFile {
filename: string; filename: string;

View File

@ -1,16 +1,43 @@
import { NETWORKS, NetworkConfig, CustomNetworkConfig } from 'config/data'; import {
CustomNetworkConfig,
DPathFormats,
InsecureWalletName,
NetworkConfig,
NETWORKS,
SecureWalletName,
WalletName,
walletNames
} from 'config';
import { DPath, EXTRA_PATHS } from 'config/dpaths';
import sortedUniq from 'lodash/sortedUniq';
import difference from 'lodash/difference';
export function makeCustomNetworkId(config: CustomNetworkConfig): string { export function makeCustomNetworkId(config: CustomNetworkConfig): string {
return config.chainId ? `${config.chainId}` : `${config.name}:${config.unit}`; return config.chainId ? `${config.chainId}` : `${config.name}:${config.unit}`;
} }
export function makeNetworkConfigFromCustomConfig(config: CustomNetworkConfig): NetworkConfig { export function makeNetworkConfigFromCustomConfig(config: CustomNetworkConfig): NetworkConfig {
return { // TODO - re-enable this block and classify customConfig after user-inputted dPaths are implemented
// -------------------------------------------------
// this still provides the type safety we want
// as we know config coming in is CustomNetworkConfig
// meaning name will be a string
// then we cast it as any to keep it as a network key
// interface Override extends NetworkConfig {
// name: any;
// }
// -------------------------------------------------
// TODO - allow for user-inputted dPaths so we don't need to use any below and can use supplied dPaths
// In the meantime, networks with an unknown chainId will have HD wallets disabled
const customConfig: any = {
...config, ...config,
color: '#000', color: '#000',
tokens: [], tokens: [],
contracts: [] contracts: []
}; };
return customConfig;
} }
export function getNetworkConfigFromId( export function getNetworkConfigFromId(
@ -26,3 +53,68 @@ export function getNetworkConfigFromId(
return makeNetworkConfigFromCustomConfig(customConfig); return makeNetworkConfigFromCustomConfig(customConfig);
} }
} }
type PathType = keyof DPathFormats;
type DPathFormat =
| SecureWalletName.TREZOR
| SecureWalletName.LEDGER_NANO_S
| InsecureWalletName.MNEMONIC_PHRASE;
export function getPaths(pathType: PathType): DPath[] {
const networkPaths: DPath[] = [];
Object.values(NETWORKS).forEach(networkConfig => {
const path = networkConfig.dPathFormats ? networkConfig.dPathFormats[pathType] : [];
if (path) {
networkPaths.push(path as DPath);
}
});
const paths = networkPaths.concat(EXTRA_PATHS);
return sortedUniq(paths);
}
export function getSingleDPath(format: DPathFormat, network: NetworkConfig): DPath {
const dPathFormats = network.dPathFormats;
return dPathFormats[format];
}
export function isNetworkUnit(network: NetworkConfig, unit: string) {
const validNetworks = Object.values(NETWORKS).filter((n: NetworkConfig) => n.unit === unit);
return validNetworks.includes(network);
}
export function isWalletFormatSupportedOnNetwork(
format: WalletName,
network: NetworkConfig
): boolean {
const CHECK_FORMATS: DPathFormat[] = [
SecureWalletName.LEDGER_NANO_S,
SecureWalletName.TREZOR,
InsecureWalletName.MNEMONIC_PHRASE
];
const isHDFormat = (f: string): f is DPathFormat => CHECK_FORMATS.includes(f as DPathFormat);
// Ensure DPath's are found
if (isHDFormat(format)) {
const dPath = network.dPathFormats && network.dPathFormats[format];
return !!dPath;
}
// Ensure Web3 is only enabled on ETH or ETH Testnets (MetaMask does not support other networks)
if (format === SecureWalletName.WEB3) {
return isNetworkUnit(network, 'ETH');
}
// All other wallet formats are supported
return true;
}
export function allWalletFormatsSupportedOnNetwork(network: NetworkConfig): WalletName[] {
return walletNames.filter(walletName => isWalletFormatSupportedOnNetwork(walletName, network));
}
export function unSupportedWalletFormatsOnNetwork(network: NetworkConfig): WalletName[] {
const supportedFormats = allWalletFormatsSupportedOnNetwork(network);
return difference(walletNames, supportedFormats);
}

View File

@ -1,5 +1,5 @@
import { CustomNode } from 'libs/nodes'; import { CustomNode } from 'libs/nodes';
import { NODES, NodeConfig, CustomNodeConfig } from 'config/data'; import { NODES, NodeConfig, CustomNodeConfig } from 'config';
export function makeCustomNodeId(config: CustomNodeConfig): string { export function makeCustomNodeId(config: CustomNodeConfig): string {
return `${config.url}:${config.port}`; return `${config.url}:${config.port}`;
@ -27,10 +27,16 @@ export function getNodeConfigFromId(
} }
export function makeNodeConfigFromCustomConfig(config: CustomNodeConfig): NodeConfig { export function makeNodeConfigFromCustomConfig(config: CustomNodeConfig): NodeConfig {
return { interface Override extends NodeConfig {
network: any;
}
const customConfig: Override = {
network: config.network, network: config.network,
lib: new CustomNode(config), lib: new CustomNode(config),
service: 'your custom node', service: 'your custom node',
estimateGas: true estimateGas: true
}; };
return customConfig;
} }

View File

@ -1,4 +1,4 @@
import { Token } from 'config/data'; import { Token } from 'config';
export function dedupeCustomTokens(networkTokens: Token[], customTokens: Token[]): Token[] { export function dedupeCustomTokens(networkTokens: Token[], customTokens: Token[]): Token[] {
if (!customTokens.length) { if (!customTokens.length) {

View File

@ -37,6 +37,7 @@
"react-redux": "5.0.6", "react-redux": "5.0.6",
"react-router-dom": "4.2.2", "react-router-dom": "4.2.2",
"react-router-redux": "4.0.8", "react-router-redux": "4.0.8",
"react-select": "1.2.1",
"react-stepper-horizontal": "1.0.9", "react-stepper-horizontal": "1.0.9",
"react-transition-group": "2.2.1", "react-transition-group": "2.2.1",
"redux": "3.7.2", "redux": "3.7.2",
@ -62,6 +63,7 @@
"@types/react-redux": "5.0.14", "@types/react-redux": "5.0.14",
"@types/react-router-dom": "4.2.3", "@types/react-router-dom": "4.2.3",
"@types/react-router-redux": "5.0.11", "@types/react-router-redux": "5.0.11",
"@types/react-select": "1.1.0",
"@types/redux-logger": "3.0.5", "@types/redux-logger": "3.0.5",
"@types/redux-promise-middleware": "0.0.9", "@types/redux-promise-middleware": "0.0.9",
"@types/uuid": "3.4.3", "@types/uuid": "3.4.3",

View File

@ -0,0 +1,19 @@
import { NETWORKS, NetworkConfig } from 'config';
describe('Networks', () => {
Object.keys(NETWORKS).forEach(networkId => {
it(`${networkId} contains non-null dPathFormats`, () => {
const network: NetworkConfig = NETWORKS[networkId];
Object.values(network.dPathFormats).forEach(dPathFormat => {
expect(dPathFormat).toBeTruthy();
});
});
});
it(`contain unique chainIds`, () => {
const networkValues = Object.values(NETWORKS);
const chainIds = networkValues.map(a => a.chainId);
const chainIdsSet = new Set(chainIds);
expect(Array.from(chainIdsSet).length).toEqual(chainIds.length);
});
});

View File

@ -1,4 +1,4 @@
import { NODES, NodeConfig } from '../../common/config/data'; import { NODES, NodeConfig } from 'config';
import { RPCNode } from '../../common/libs/nodes'; import { RPCNode } from '../../common/libs/nodes';
import { Validator } from 'jsonschema'; import { Validator } from 'jsonschema';
import { schema } from '../../common/libs/validators'; import { schema } from '../../common/libs/validators';

View File

@ -4,7 +4,7 @@ import Adapter from 'enzyme-adapter-react-16';
import SendTransaction from 'containers/Tabs/SendTransaction'; import SendTransaction from 'containers/Tabs/SendTransaction';
import shallowWithStore from '../utils/shallowWithStore'; import shallowWithStore from '../utils/shallowWithStore';
import { createMockStore } from 'redux-test-utils'; import { createMockStore } from 'redux-test-utils';
import { NODES } from 'config/data'; import { NODES } from 'config';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
import { createMockRouteComponentProps } from '../utils/mockRouteComponentProps'; import { createMockRouteComponentProps } from '../utils/mockRouteComponentProps';

View File

@ -1,6 +1,6 @@
import { config, INITIAL_STATE } from 'reducers/config'; import { config, INITIAL_STATE } from 'reducers/config';
import * as configActions from 'actions/config'; import * as configActions from 'actions/config';
import { NODES, NETWORKS } from 'config/data'; import { NODES, NETWORKS } from 'config';
import { makeCustomNodeId, makeNodeConfigFromCustomConfig } from 'utils/node'; import { makeCustomNodeId, makeNodeConfigFromCustomConfig } from 'utils/node';
const custNode = { const custNode = {

View File

@ -1,5 +1,5 @@
import { customTokens } from 'reducers/customTokens'; import { customTokens } from 'reducers/customTokens';
import { Token } from 'config/data'; import { Token } from 'config';
import * as customTokensActions from 'actions/customTokens'; import * as customTokensActions from 'actions/customTokens';
describe('customTokens reducer', () => { describe('customTokens reducer', () => {

View File

@ -9,9 +9,10 @@ import {
handleNodeChangeIntent, handleNodeChangeIntent,
unsetWeb3Node, unsetWeb3Node,
unsetWeb3NodeOnWalletEvent, unsetWeb3NodeOnWalletEvent,
equivalentNodeOrDefault equivalentNodeOrDefault,
reload
} from 'sagas/config'; } from 'sagas/config';
import { NODES, NodeConfig, NETWORKS } from 'config/data'; import { NODES, NodeConfig, NETWORKS } from 'config';
import { import {
getNode, getNode,
getNodeConfig, getNodeConfig,
@ -20,13 +21,10 @@ import {
getCustomNetworkConfigs getCustomNetworkConfigs
} from 'selectors/config'; } from 'selectors/config';
import { INITIAL_STATE as configInitialState } from 'reducers/config'; import { INITIAL_STATE as configInitialState } from 'reducers/config';
import { getWalletInst } from 'selectors/wallet';
import { Web3Wallet } from 'libs/wallet'; import { Web3Wallet } from 'libs/wallet';
import { RPCNode } from 'libs/nodes'; import { RPCNode } from 'libs/nodes';
import { showNotification } from 'actions/notifications'; import { showNotification } from 'actions/notifications';
import { translateRaw } from 'translations'; import { translateRaw } from 'translations';
import { resetWallet } from 'actions/wallet';
import { reset as resetTransaction } from 'actions/transaction';
// init module // init module
configuredStore.getState(); configuredStore.getState();
@ -143,12 +141,12 @@ describe('handleNodeChangeIntent*', () => {
const customNetworkConfigs = []; const customNetworkConfigs = [];
const defaultNodeNetwork = NETWORKS[defaultNodeConfig.network]; const defaultNodeNetwork = NETWORKS[defaultNodeConfig.network];
const newNode = Object.keys(NODES).reduce( const newNode = Object.keys(NODES).reduce(
(acc, cur) => (NODES[acc].network === defaultNodeConfig.network ? cur : acc) (acc, cur) => (NODES[cur].network !== defaultNodeConfig.network ? cur : acc)
); );
const newNodeConfig = NODES[newNode]; const newNodeConfig = NODES[newNode];
const newNodeNetwork = NETWORKS[newNodeConfig.network]; const newNodeNetwork = NETWORKS[newNodeConfig.network];
const changeNodeIntentAction = changeNodeIntent(newNode); const changeNodeIntentAction = changeNodeIntent(newNode);
const truthyWallet = true;
const latestBlock = '0xa'; const latestBlock = '0xa';
const raceSuccess = { const raceSuccess = {
lb: latestBlock lb: latestBlock
@ -208,15 +206,9 @@ describe('handleNodeChangeIntent*', () => {
); );
}); });
it('should select getWalletInst', () => { it('should call reload if network is new', () => {
expect(data.gen.next().value).toEqual(select(getWalletInst)); expect(data.gen.next().value).toEqual(call(reload));
}); expect(data.gen.next().done).toEqual(true);
it('should call reload if wallet exists and network is new', () => {
data.clone2 = data.gen.clone();
expect(data.clone2.next(truthyWallet).value).toEqual(put(resetWallet()));
expect(data.clone2.next(truthyWallet).value).toEqual(put(resetTransaction()));
expect(data.clone2.next().done).toEqual(true);
}); });
it('should be done', () => { it('should be done', () => {
@ -366,7 +358,7 @@ describe('equivalentNodeOrDefault', () => {
const node = equivalentNodeOrDefault({ const node = equivalentNodeOrDefault({
...mockNodeConfig, ...mockNodeConfig,
network: 'noEqivalentExists' network: 'noEqivalentExists'
}); } as any);
expect(node).toEqual(appDefaultNode); expect(node).toEqual(appDefaultNode);
}); });
@ -374,12 +366,12 @@ describe('equivalentNodeOrDefault', () => {
NODES.web3 = { NODES.web3 = {
...mockNodeConfig, ...mockNodeConfig,
network: 'uniqueToWeb3' network: 'uniqueToWeb3'
}; } as any;
const node = equivalentNodeOrDefault({ const node = equivalentNodeOrDefault({
...mockNodeConfig, ...mockNodeConfig,
network: 'uniqueToWeb3' network: 'uniqueToWeb3'
}); } as any);
expect(node).toEqual(appDefaultNode); expect(node).toEqual(appDefaultNode);
}); });
}); });

View File

@ -7,7 +7,7 @@ import { getDesiredToken, getWallets } from 'selectors/deterministicWallets';
import { getTokens } from 'selectors/wallet'; import { getTokens } from 'selectors/wallet';
import { getNodeLib } from 'selectors/config'; import { getNodeLib } from 'selectors/config';
import * as dWalletActions from 'actions/deterministicWallets'; import * as dWalletActions from 'actions/deterministicWallets';
import { Token } from 'config/data'; import { Token } from 'config';
import { import {
getDeterministicWallets, getDeterministicWallets,
updateWalletValues, updateWalletValues,

View File

@ -13,7 +13,7 @@ import {
import { Wei } from 'libs/units'; import { Wei } from 'libs/units';
import { changeNodeIntent, web3UnsetNode } from 'actions/config'; import { changeNodeIntent, web3UnsetNode } from 'actions/config';
import { INode } from 'libs/nodes/INode'; import { INode } from 'libs/nodes/INode';
import { initWeb3Node, Token, N_FACTOR } from 'config/data'; import { initWeb3Node, Token, N_FACTOR } from 'config';
import { apply, call, fork, put, select, take } from 'redux-saga/effects'; import { apply, call, fork, put, select, take } from 'redux-saga/effects';
import { getNodeLib, getOffline } from 'selectors/config'; import { getNodeLib, getOffline } from 'selectors/config';
import { getWalletInst, getWalletConfigTokens } from 'selectors/wallet'; import { getWalletInst, getWalletConfigTokens } from 'selectors/wallet';