Add New Networks (#1259)

* Add new networks.

* Handle typing issues with DPath being possibly undefined.

* Remove copied nodes from tests.

* Review comments, refactor makeExplorer.

* Remove ETSC

* Re-add import

* Update snapshot
This commit is contained in:
William O'Beirne 2018-04-06 10:02:02 -04:00 committed by Daniel Ternyak
parent ab2b559dd2
commit 8fb0e03d8d
13 changed files with 245 additions and 129 deletions

View File

@ -1,21 +1,22 @@
import './LedgerNano.scss';
import React, { PureComponent } from 'react';
import translate from 'translations';
import DeterministicWalletsModal from './DeterministicWalletsModal';
import { LedgerWallet } from 'libs/wallet';
import ledger from 'ledgerco';
import translate, { translateRaw } from 'translations';
import DeterministicWalletsModal from './DeterministicWalletsModal';
import UnsupportedNetwork from './UnsupportedNetwork';
import { LedgerWallet } from 'libs/wallet';
import { Spinner, NewTabLink } from 'components/ui';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { SecureWalletName, ledgerReferralURL } from 'config';
import { getPaths, getSingleDPath } from 'selectors/config/wallet';
import './LedgerNano.scss';
interface OwnProps {
onUnlock(param: any): void;
}
interface StateProps {
dPath: DPath;
dPath: DPath | undefined;
dPaths: DPath[];
}
@ -34,7 +35,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
public state: State = {
publicKey: '',
chainCode: '',
dPath: this.props.dPath.value,
dPath: this.props.dPath ? this.props.dPath.value : '',
error: null,
isLoading: false,
showTip: false
@ -48,7 +49,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
public componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath) {
this.setState({ dPath: nextProps.dPath.value });
this.setState({ dPath: nextProps.dPath ? nextProps.dPath.value : '' });
}
}
@ -56,6 +57,10 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
const { dPath, publicKey, chainCode, error, isLoading, showTip } = this.state;
const showErr = error ? 'is-showing' : '';
if (!dPath) {
return <UnsupportedNetwork walletType={translateRaw('x_Ledger')} />;
}
if (window.location.protocol !== 'https:') {
return (
<div className="LedgerDecrypt">
@ -167,7 +172,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
this.setState({
publicKey: '',
chainCode: '',
dPath: this.props.dPath.value
dPath: this.props.dPath ? this.props.dPath.value : ''
});
}
}

View File

@ -155,7 +155,8 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
function mapStateToProps(state: AppState): StateProps {
return {
dPath: getSingleDPath(state, InsecureWalletName.MNEMONIC_PHRASE),
// Mnemonic dPath is guaranteed to always be provided
dPath: getSingleDPath(state, InsecureWalletName.MNEMONIC_PHRASE) as DPath,
dPaths: getPaths(state, InsecureWalletName.MNEMONIC_PHRASE)
};
}

View File

@ -1,14 +1,15 @@
import { TrezorWallet, TREZOR_MINIMUM_FIRMWARE } from 'libs/wallet';
import React, { PureComponent } from 'react';
import translate from 'translations';
import translate, { translateRaw } from 'translations';
import TrezorConnect from 'vendor/trezor-connect';
import DeterministicWalletsModal from './DeterministicWalletsModal';
import './Trezor.scss';
import UnsupportedNetwork from './UnsupportedNetwork';
import { Spinner, NewTabLink } from 'components/ui';
import { AppState } from 'reducers';
import { connect } from 'react-redux';
import { SecureWalletName, trezorReferralURL } from 'config';
import { getSingleDPath, getPaths } from 'selectors/config/wallet';
import './Trezor.scss';
//todo: conflicts with comment in walletDecrypt -> onUnlock method
interface OwnProps {
@ -16,7 +17,7 @@ interface OwnProps {
}
interface StateProps {
dPath: DPath;
dPath: DPath | undefined;
dPaths: DPath[];
}
@ -35,14 +36,14 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
public state: State = {
publicKey: '',
chainCode: '',
dPath: this.props.dPath.value,
dPath: this.props.dPath ? this.props.dPath.value : '',
error: null,
isLoading: false
};
public componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath) {
this.setState({ dPath: nextProps.dPath.value });
this.setState({ dPath: nextProps.dPath ? nextProps.dPath.value : '' });
}
}
@ -50,6 +51,10 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
const { dPath, publicKey, chainCode, error, isLoading } = this.state;
const showErr = error ? 'is-showing' : '';
if (!dPath) {
return <UnsupportedNetwork walletType={translateRaw('x_Trezor')} />;
}
return (
<div className="TrezorDecrypt">
<button
@ -141,7 +146,7 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
this.setState({
publicKey: '',
chainCode: '',
dPath: this.props.dPath.value
dPath: this.props.dPath ? this.props.dPath.value : ''
});
}
}

View File

@ -0,0 +1,27 @@
import React from 'react';
import { connect } from 'react-redux';
import { getNetworkConfig } from 'selectors/config';
import { NetworkConfig } from 'types/network';
import { AppState } from 'reducers';
interface StateProps {
network: NetworkConfig;
}
interface OwnProps {
walletType: string | React.ReactElement<string>;
}
type Props = OwnProps & StateProps;
const UnsupportedNetwork: React.SFC<Props> = ({ walletType, network }) => {
return (
<h2>
{walletType} does not support the {network.name} network
</h2>
);
};
export default connect((state: AppState): StateProps => ({
network: getNetworkConfig(state)
}))(UnsupportedNetwork);

View File

@ -38,6 +38,21 @@ export const UBQ_DEFAULT: DPath = {
value: "m/44'/108'/0'/0"
};
export const POA_DEFAULT: DPath = {
label: 'Default (POA)',
value: "m/44'/60'/0'/0"
};
export const TOMO_DEFAULT: DPath = {
label: 'Default (TOMO)',
value: "m/44'/1'/0'/0"
};
export const ELLA_DEFAULT: DPath = {
label: 'Default (ELLA)',
value: "m/44'/163'/0'/0"
};
export const ETH_SINGULAR: DPath = {
label: 'SingularDTV',
value: "m/0'/0'/0'"
@ -51,7 +66,10 @@ export const DPaths: DPath[] = [
ETC_TREZOR,
ETH_TESTNET,
EXP_DEFAULT,
UBQ_DEFAULT
UBQ_DEFAULT,
POA_DEFAULT,
TOMO_DEFAULT,
ELLA_DEFAULT
];
// PATHS TO BE INCLUDED REGARDLESS OF WALLET FORMAT

View File

@ -13,7 +13,10 @@ import {
ETC_TREZOR,
ETH_TESTNET,
EXP_DEFAULT,
UBQ_DEFAULT
UBQ_DEFAULT,
POA_DEFAULT,
TOMO_DEFAULT,
ELLA_DEFAULT
} from 'config/dpaths';
import { ConfigAction } from 'actions/config';
import { BlockExplorerConfig } from 'types/network';
@ -22,17 +25,29 @@ import { StaticNetworksState as State } from './types';
// Must be a website that follows the ethplorer convention of /tx/[hash] and
// address/[address] to generate the correct functions.
// TODO: put this in utils / libs
export function makeExplorer(
name: string,
origin: string,
addressPath: string = 'address'
): BlockExplorerConfig {
interface ExplorerConfig {
name: string;
origin: string;
txPath?: string;
addressPath?: string;
blockPath?: string;
}
export function makeExplorer(expConfig: ExplorerConfig): BlockExplorerConfig {
const config: ExplorerConfig = {
// Defaults
txPath: 'tx',
addressPath: 'address',
blockPath: 'block',
...expConfig
};
return {
name,
origin,
txUrl: hash => `${origin}/tx/${hash}`,
addressUrl: address => `${origin}/${addressPath}/${address}`,
blockUrl: blockNum => `${origin}/block/${blockNum}`
name: config.origin,
origin: config.origin,
txUrl: hash => `${config.origin}/${config.txPath}/${hash}`,
addressUrl: address => `${config.origin}/${config.addressPath}/${address}`,
blockUrl: blockNum => `${config.origin}/${config.blockPath}/${blockNum}`
};
}
@ -49,7 +64,10 @@ export const INITIAL_STATE: State = {
chainId: 1,
isCustom: false,
color: '#007896',
blockExplorer: makeExplorer('Etherscan', 'https://etherscan.io'),
blockExplorer: makeExplorer({
name: 'Etherscan',
origin: 'https://etherscan.io'
}),
tokenExplorer: {
name: ethPlorer,
address: ETHTokenExplorer
@ -70,7 +88,10 @@ export const INITIAL_STATE: State = {
chainId: 3,
isCustom: false,
color: '#adc101',
blockExplorer: makeExplorer('Etherscan', 'https://ropsten.etherscan.io'),
blockExplorer: makeExplorer({
name: 'Etherscan',
origin: 'https://ropsten.etherscan.io'
}),
tokens: require('config/tokens/ropsten.json'),
contracts: require('config/contracts/ropsten.json'),
isTestnet: true,
@ -87,7 +108,10 @@ export const INITIAL_STATE: State = {
chainId: 42,
isCustom: false,
color: '#adc101',
blockExplorer: makeExplorer('Etherscan', 'https://kovan.etherscan.io'),
blockExplorer: makeExplorer({
name: 'Etherscan',
origin: 'https://kovan.etherscan.io'
}),
tokens: require('config/tokens/ropsten.json'),
contracts: require('config/contracts/ropsten.json'),
isTestnet: true,
@ -104,7 +128,10 @@ export const INITIAL_STATE: State = {
chainId: 4,
isCustom: false,
color: '#adc101',
blockExplorer: makeExplorer('Etherscan', 'https://rinkeby.etherscan.io'),
blockExplorer: makeExplorer({
name: 'Etherscan',
origin: 'https://rinkeby.etherscan.io'
}),
tokens: require('config/tokens/rinkeby.json'),
contracts: require('config/contracts/rinkeby.json'),
isTestnet: true,
@ -121,7 +148,11 @@ export const INITIAL_STATE: State = {
chainId: 61,
isCustom: false,
color: '#669073',
blockExplorer: makeExplorer('GasTracker', 'https://gastracker.io', 'addr'),
blockExplorer: makeExplorer({
name: 'GasTracker',
origin: 'https://gastracker.io',
addressPath: 'addr'
}),
tokens: require('config/tokens/etc.json'),
contracts: require('config/contracts/etc.json'),
dPathFormats: {
@ -141,7 +172,10 @@ export const INITIAL_STATE: State = {
chainId: 8,
isCustom: false,
color: '#b37aff',
blockExplorer: makeExplorer('Ubiqscan', 'https://ubiqscan.io/en'),
blockExplorer: makeExplorer({
name: 'Ubiqscan',
origin: 'https://ubiqscan.io/en'
}),
tokens: require('config/tokens/ubq.json'),
contracts: require('config/contracts/ubq.json'),
dPathFormats: {
@ -161,7 +195,10 @@ export const INITIAL_STATE: State = {
chainId: 2,
isCustom: false,
color: '#673ab7',
blockExplorer: makeExplorer('Gander', 'https://www.gander.tech'),
blockExplorer: makeExplorer({
name: 'Gander',
origin: 'https://www.gander.tech'
}),
tokens: require('config/tokens/exp.json'),
contracts: require('config/contracts/exp.json'),
dPathFormats: {
@ -174,6 +211,76 @@ export const INITIAL_STATE: State = {
max: 20,
initial: 2
}
},
POA: {
name: 'POA',
unit: 'POA',
chainId: 99,
isCustom: false,
color: '#6d2eae',
blockExplorer: makeExplorer({
name: 'Etherchain Light',
origin: 'https://poaexplorer.com',
addressPath: 'address/search',
blockPath: 'blocks/block'
}),
tokens: [],
contracts: [],
dPathFormats: {
[SecureWalletName.TREZOR]: POA_DEFAULT,
[SecureWalletName.LEDGER_NANO_S]: POA_DEFAULT,
[InsecureWalletName.MNEMONIC_PHRASE]: POA_DEFAULT
},
gasPriceSettings: {
min: 0.1,
max: 10,
initial: 1
}
},
TOMO: {
name: 'TOMO',
unit: 'TOMO',
chainId: 40686,
isCustom: false,
color: '#6a488d',
blockExplorer: makeExplorer({
name: 'Tomochain Explorer',
origin: 'https://explorer.tomocoin.io/#'
}),
tokens: [],
contracts: [],
dPathFormats: {
[SecureWalletName.TREZOR]: TOMO_DEFAULT,
[SecureWalletName.LEDGER_NANO_S]: TOMO_DEFAULT,
[InsecureWalletName.MNEMONIC_PHRASE]: TOMO_DEFAULT
},
gasPriceSettings: {
min: 1,
max: 60,
initial: 20
}
},
ELLA: {
name: 'ELLA',
unit: 'ELLA',
chainId: 64,
isCustom: false,
color: '#046111',
blockExplorer: makeExplorer({
name: 'Ellaism Explorer',
origin: 'https://explorer.ellaism.org'
}),
tokens: [],
contracts: [],
dPathFormats: {
[SecureWalletName.TREZOR]: ELLA_DEFAULT,
[InsecureWalletName.MNEMONIC_PHRASE]: ELLA_DEFAULT
},
gasPriceSettings: {
min: 1,
max: 60,
initial: 20
}
}
};

View File

@ -79,6 +79,27 @@ export const INITIAL_STATE: State = {
service: 'Expanse.tech',
lib: new RPCNode('https://node.expanse.tech/'),
estimateGas: true
},
poa: {
network: 'POA',
isCustom: false,
service: 'poa.network',
lib: new RPCNode('https://core.poa.network'),
estimateGas: true
},
tomo: {
network: 'TOMO',
isCustom: false,
service: 'tomocoin.io',
lib: new RPCNode('https://core.tomocoin.io'),
estimateGas: true
},
ella: {
network: 'ELLA',
isCustom: false,
service: 'ellaism.org',
lib: new RPCNode('https://jsonrpc.ellaism.org'),
estimateGas: true
}
};

View File

@ -15,17 +15,17 @@ type DPathFormat =
export function getPaths(state: AppState, pathType: PathType): DPath[] {
const paths = Object.values(getStaticNetworkConfigs(state))
.reduce(
(networkPaths: DPath[], { dPathFormats }) =>
dPathFormats ? [...networkPaths, dPathFormats[pathType]] : networkPaths,
[]
)
.reduce((networkPaths: DPath[], { dPathFormats }): DPath[] => {
if (dPathFormats && dPathFormats[pathType]) {
return [...networkPaths, dPathFormats[pathType] as DPath];
}
return networkPaths;
}, [])
.concat(EXTRA_PATHS);
return sortedUniq(paths);
}
export function getSingleDPath(state: AppState, format: DPathFormat): DPath {
export function getSingleDPath(state: AppState, format: DPathFormat): DPath | undefined {
const network = getStaticNetworkConfig(state);
if (!network) {
throw Error('No static network config loaded');

View File

@ -166,7 +166,7 @@ export function getDisabledWallets(state: AppState): DisabledWallets {
// Some wallets don't support some networks
addReason(
unSupportedWalletFormatsOnNetwork(state),
`${network.name} does not support this wallet`
`This wallet doesnt support the ${network.name} network`
);
// Some wallets are unavailable offline

View File

@ -1,6 +1,16 @@
import { StaticNetworksState, CustomNetworksState } from 'reducers/config/networks';
type StaticNetworkIds = 'ETH' | 'Ropsten' | 'Kovan' | 'Rinkeby' | 'ETC' | 'UBQ' | 'EXP';
type StaticNetworkIds =
| 'ETH'
| 'Ropsten'
| 'Kovan'
| 'Rinkeby'
| 'ETC'
| 'UBQ'
| 'EXP'
| 'POA'
| 'TOMO'
| 'ELLA';
export interface BlockExplorerConfig {
name: string;
@ -24,8 +34,8 @@ interface NetworkContract {
}
interface DPathFormats {
trezor: DPath;
ledgerNanoS: DPath;
trezor?: DPath;
ledgerNanoS?: DPath;
mnemonicPhrase: DPath;
}

View File

@ -41,7 +41,10 @@ declare enum StaticNodeId {
RIN_INFURA = 'rin_infura',
ETC_EPOOL = 'etc_epool',
UBQ = 'ubq',
EXP_TECH = 'exp_tech'
EXP_TECH = 'exp_tech',
POA = 'poa',
TOMO = 'tomo',
ELLA = 'ella'
}
type StaticNodeWithWeb3Id = StaticNodeId | 'web3';

View File

@ -14,7 +14,7 @@ Object {
"call": [Function],
"createHeaders": [Function],
"decorateRequest": [Function],
"endpoint": "https://node.expanse.tech/",
"endpoint": "https://jsonrpc.ellaism.org",
"headers": Object {},
},
"requests": RPCRequests {},

View File

@ -1,91 +1,10 @@
import { configuredStore } from 'store';
import { web3SetNode, web3UnsetNode } from 'actions/config';
import { staticNodes, INITIAL_STATE } from 'reducers/config/nodes/staticNodes';
import { EtherscanNode, InfuraNode, RPCNode } from 'libs/nodes';
import { Web3NodeConfig } from 'types/node';
import { Web3Service } from 'libs/nodes/web3';
configuredStore.getState();
const expectedInitialState = {
eth_mycrypto: {
network: 'ETH',
isCustom: false,
lib: new RPCNode('https://api.mycryptoapi.com/eth'),
service: 'MyCrypto',
estimateGas: true
},
eth_ethscan: {
network: 'ETH',
isCustom: false,
service: 'Etherscan.io',
lib: new EtherscanNode('https://api.etherscan.io/api'),
estimateGas: false
},
eth_infura: {
network: 'ETH',
isCustom: false,
service: 'infura.io',
lib: new InfuraNode('https://mainnet.infura.io/mycrypto'),
estimateGas: false
},
eth_blockscale: {
network: 'ETH',
isCustom: false,
lib: new RPCNode('https://api.dev.blockscale.net/dev/parity'),
service: 'Blockscale beta',
estimateGas: true
},
rop_infura: {
network: 'Ropsten',
isCustom: false,
service: 'infura.io',
lib: new InfuraNode('https://ropsten.infura.io/mycrypto'),
estimateGas: false
},
kov_ethscan: {
network: 'Kovan',
isCustom: false,
service: 'Etherscan.io',
lib: new EtherscanNode('https://kovan.etherscan.io/api'),
estimateGas: false
},
rin_ethscan: {
network: 'Rinkeby',
isCustom: false,
service: 'Etherscan.io',
lib: new EtherscanNode('https://rinkeby.etherscan.io/api'),
estimateGas: false
},
rin_infura: {
network: 'Rinkeby',
isCustom: false,
service: 'infura.io',
lib: new InfuraNode('https://rinkeby.infura.io/mycrypto'),
estimateGas: false
},
etc_epool: {
network: 'ETC',
isCustom: false,
service: 'Epool.io',
lib: new RPCNode('https://mewapi.epool.io'),
estimateGas: false
},
ubq: {
network: 'UBQ',
isCustom: false,
service: 'ubiqscan.io',
lib: new RPCNode('https://pyrus2.ubiqscan.io'),
estimateGas: true
},
exp_tech: {
network: 'EXP',
isCustom: false,
service: 'Expanse.tech',
lib: new RPCNode('https://node.expanse.tech/'),
estimateGas: true
}
};
const web3Id = 'web3';
const web3Node: Web3NodeConfig = {
isCustom: false,
@ -97,7 +16,7 @@ const web3Node: Web3NodeConfig = {
};
const expectedState = {
initialState: expectedInitialState,
initialState: staticNodes(undefined, {} as any),
setWeb3: { ...INITIAL_STATE, [web3Id]: web3Node },
unsetWeb3: { ...INITIAL_STATE }
};