Fix up components to use selectors, work on fixing sagas

This commit is contained in:
HenryNguyen5 2018-01-29 22:52:19 -05:00
parent 139cf405e7
commit 7fbe1966de
18 changed files with 374 additions and 290 deletions

View File

@ -24,10 +24,12 @@ export function changeLanguage(sign: string): interfaces.ChangeLanguageAction {
} }
export type TChangeNode = typeof changeNode; export type TChangeNode = typeof changeNode;
export function changeNode(networkName: string, nodeName: string): interfaces.ChangeNodeAction { export function changeNode(
payload: interfaces.ChangeNodeAction['payload']
): interfaces.ChangeNodeAction {
return { return {
type: TypeKeys.CONFIG_NODE_CHANGE, type: TypeKeys.CONFIG_NODE_CHANGE,
payload: { networkName, nodeName } payload
}; };
} }

View File

@ -2,11 +2,19 @@ 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';
import { makeCustomNodeId } from 'utils/node';
import { makeCustomNetworkId } from 'utils/network'; import { makeCustomNetworkId } from 'utils/network';
import { CustomNetworkConfig } from 'types/network';
import { CustomNodeConfig } from 'types/node';
import { TAddCustomNetwork, addCustomNetwork, AddCustomNodeAction } from 'actions/config';
import { connect, Omit } from 'react-redux';
import { AppState } from 'reducers';
import {
getCustomNetworkConfigs,
getCustomNodeConfigs,
getStaticNetworkConfigs
} from 'selectors/config';
import { CustomNode } from 'libs/nodes';
const NETWORK_KEYS = Object.keys(NETWORKS);
const CUSTOM = 'custom'; const CUSTOM = 'custom';
interface Input { interface Input {
@ -15,14 +23,21 @@ interface Input {
type?: string; type?: string;
} }
interface Props { interface OwnProps {
customNodes: CustomNodeConfig[]; addCustomNode(payload: AddCustomNodeAction['payload']): void;
customNetworks: CustomNetworkConfig[];
handleAddCustomNode(node: CustomNodeConfig): void;
handleAddCustomNetwork(node: CustomNetworkConfig): void;
handleClose(): void; handleClose(): void;
} }
interface DispatchProps {
addCustomNetwork: TAddCustomNetwork;
}
interface StateProps {
customNodes: AppState['config']['nodes']['customNodes'];
customNetworks: AppState['config']['networks']['customNetworks'];
staticNetworks: AppState['config']['networks']['staticNetworks'];
}
interface State { interface State {
name: string; name: string;
url: string; url: string;
@ -36,12 +51,14 @@ interface State {
password: string; password: string;
} }
export default class CustomNodeModal extends React.Component<Props, State> { type Props = OwnProps & StateProps & DispatchProps;
class CustomNodeModal extends React.Component<Props, State> {
public state: State = { public state: State = {
name: '', name: '',
url: '', url: '',
port: '', port: '',
network: NETWORK_KEYS[0], network: Object.keys(this.props.staticNetworks)[0],
customNetworkName: '', customNetworkName: '',
customNetworkUnit: '', customNetworkUnit: '',
customNetworkChainId: '', customNetworkChainId: '',
@ -51,7 +68,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
}; };
public render() { public render() {
const { customNetworks, handleClose } = this.props; const { customNetworks, handleClose, staticNetworks } = this.props;
const { network } = this.state; const { network } = this.state;
const isHttps = window.location.protocol.includes('https'); const isHttps = window.location.protocol.includes('https');
const invalids = this.getInvalids(); const invalids = this.getInvalids();
@ -109,12 +126,12 @@ export default class CustomNodeModal extends React.Component<Props, State> {
value={network} value={network}
onChange={this.handleChange} onChange={this.handleChange}
> >
{NETWORK_KEYS.map(net => ( {Object.keys(staticNetworks).map(net => (
<option key={net} value={net}> <option key={net} value={net}>
{net} {net}
</option> </option>
))} ))}
{customNetworks.map(net => { {Object.values(customNetworks).map(net => {
const id = makeCustomNetworkId(net); const id = makeCustomNetworkId(net);
return ( return (
<option key={id} value={id}> <option key={id} value={id}>
@ -173,7 +190,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
placeholder: 'http://127.0.0.1/' placeholder: 'http://127.0.0.1/'
}, },
invalids invalids
)} )}node
</div> </div>
<div className="col-sm-3"> <div className="col-sm-3">
@ -303,12 +320,13 @@ export default class CustomNodeModal extends React.Component<Props, State> {
} }
private makeCustomNetworkConfigFromState(): CustomNetworkConfig { private makeCustomNetworkConfigFromState(): CustomNetworkConfig {
const similarNetworkConfig = Object.values(NETWORKS).find( const similarNetworkConfig = Object.values(this.props.staticNetworks).find(
n => n.chainId === +this.state.customNetworkChainId n => n.chainId === +this.state.customNetworkChainId
); );
const dPathFormats = similarNetworkConfig ? similarNetworkConfig.dPathFormats : null; const dPathFormats = similarNetworkConfig ? similarNetworkConfig.dPathFormats : null;
return { return {
isCustom: true,
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,
@ -318,14 +336,22 @@ export default class CustomNodeModal extends React.Component<Props, State> {
private makeCustomNodeConfigFromState(): CustomNodeConfig { private makeCustomNodeConfigFromState(): CustomNodeConfig {
const { network } = this.state; const { network } = this.state;
const node: CustomNodeConfig = { const networkId =
network === CUSTOM ? makeCustomNetworkId(this.makeCustomNetworkConfigFromState()) : network;
const port = parseInt(this.state.port, 10);
const url = this.state.url.trim();
const node: Omit<CustomNodeConfig, 'lib'> = {
isCustom: true,
service: 'your custom node',
id: `${url}:${port}`,
name: this.state.name.trim(), name: this.state.name.trim(),
url: this.state.url.trim(), url,
port: parseInt(this.state.port, 10), port,
network: network: networkId
network === CUSTOM ? makeCustomNetworkId(this.makeCustomNetworkConfigFromState()) : network
}; };
const lib = new CustomNode(node);
if (this.state.hasAuth) { if (this.state.hasAuth) {
node.auth = { node.auth = {
username: this.state.username, username: this.state.username,
@ -333,14 +359,14 @@ export default class CustomNodeModal extends React.Component<Props, State> {
}; };
} }
return node; return { ...node, lib };
} }
private getConflictedNode(): CustomNodeConfig | undefined { private getConflictedNode(): CustomNodeConfig | undefined {
const { customNodes } = this.props; const { customNodes } = this.props;
const config = this.makeCustomNodeConfigFromState(); const config = this.makeCustomNodeConfigFromState();
const thisId = makeCustomNodeId(config);
return customNodes.find(conf => makeCustomNodeId(conf) === thisId); return customNodes[config.id];
} }
private handleChange = (ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>) => { private handleChange = (ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>) => {
@ -359,9 +385,21 @@ 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.addCustomNetwork({ config: network, id: node.network });
} }
this.props.handleAddCustomNode(node); this.props.addCustomNode({ config: node, id: node.id });
}; };
} }
const mapStateToProps = (state: AppState): StateProps => ({
customNetworks: getCustomNetworkConfigs(state),
customNodes: getCustomNodeConfigs(state),
staticNetworks: getStaticNetworkConfigs(state)
});
const mapDispatchToProps: DispatchProps = {
addCustomNetwork
};
export default connect(mapStateToProps, mapDispatchToProps)(CustomNodeModal);

View File

@ -46,7 +46,7 @@ const tabs: TabLink[] = [
]; ];
interface Props { interface Props {
color?: string; color?: string | false;
} }
interface State { interface State {

View File

@ -3,40 +3,44 @@ import {
TChangeNodeIntent, TChangeNodeIntent,
TAddCustomNode, TAddCustomNode,
TRemoveCustomNode, TRemoveCustomNode,
TAddCustomNetwork TAddCustomNetwork,
AddCustomNodeAction,
changeLanguage,
changeNodeIntent,
addCustomNode,
removeCustomNode,
addCustomNetwork
} from 'actions/config'; } from 'actions/config';
import logo from 'assets/images/logo-myetherwallet.svg'; import logo from 'assets/images/logo-myetherwallet.svg';
import { Dropdown, ColorDropdown } from 'components/ui'; import { Dropdown, ColorDropdown } from 'components/ui';
import React, { Component } from 'react'; import React, { Component } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { TSetGasPriceField } from 'actions/transaction'; import { TSetGasPriceField, setGasPriceField } from 'actions/transaction';
import { import { ANNOUNCEMENT_MESSAGE, ANNOUNCEMENT_TYPE, languages } from 'config';
ANNOUNCEMENT_MESSAGE,
ANNOUNCEMENT_TYPE,
languages,
NODES,
NodeConfig,
CustomNodeConfig,
CustomNetworkConfig
} from 'config';
import Navigation from './components/Navigation'; import Navigation from './components/Navigation';
import CustomNodeModal from './components/CustomNodeModal'; import CustomNodeModal from './components/CustomNodeModal';
import OnlineStatus from './components/OnlineStatus'; import OnlineStatus from './components/OnlineStatus';
import Version from './components/Version'; import Version from './components/Version';
import { getKeyByValue } from 'utils/helpers'; import { getKeyByValue } from 'utils/helpers';
import { makeCustomNodeId } from 'utils/node'; import { NodeConfig } from 'types/node';
import { getNetworkConfigFromId } from 'utils/network';
import './index.scss'; import './index.scss';
import { AppState } from 'reducers';
import {
getOffline,
isNodeChanging,
getLanguageSelection,
getNodeName,
getNodeConfig,
CustomNodeOption,
NodeOption,
getNodeOptions,
getNetworkConfig
} from 'selectors/config';
import { NetworkConfig } from 'types/network';
import { connect } from 'react-redux';
interface Props { interface DispatchProps {
languageSelection: string;
node: NodeConfig;
nodeSelection: string;
isChangingNode: boolean;
isOffline: boolean;
customNodes: CustomNodeConfig[];
customNetworks: CustomNetworkConfig[];
changeLanguage: TChangeLanguage; changeLanguage: TChangeLanguage;
changeNodeIntent: TChangeNodeIntent; changeNodeIntent: TChangeNodeIntent;
setGasPriceField: TSetGasPriceField; setGasPriceField: TSetGasPriceField;
@ -45,11 +49,42 @@ interface Props {
addCustomNetwork: TAddCustomNetwork; addCustomNetwork: TAddCustomNetwork;
} }
interface StateProps {
network: NetworkConfig;
languageSelection: AppState['config']['meta']['languageSelection'];
node: NodeConfig;
nodeSelection: AppState['config']['nodes']['selectedNode']['nodeName'];
isChangingNode: AppState['config']['nodes']['selectedNode']['pending'];
isOffline: AppState['config']['meta']['offline'];
nodeOptions: (CustomNodeOption | NodeOption)[];
}
const mapStateToProps = (state: AppState): StateProps => ({
isOffline: getOffline(state),
isChangingNode: isNodeChanging(state),
languageSelection: getLanguageSelection(state),
nodeSelection: getNodeName(state),
node: getNodeConfig(state),
nodeOptions: getNodeOptions(state),
network: getNetworkConfig(state)
});
const mapDispatchToProps: DispatchProps = {
setGasPriceField,
changeLanguage,
changeNodeIntent,
addCustomNode,
removeCustomNode,
addCustomNetwork
};
interface State { interface State {
isAddingCustomNode: boolean; isAddingCustomNode: boolean;
} }
export default class Header extends Component<Props, State> { type Props = StateProps & DispatchProps;
class Header extends Component<Props, State> {
public state = { public state = {
isAddingCustomNode: false isAddingCustomNode: false
}; };
@ -57,50 +92,40 @@ export default class Header extends Component<Props, State> {
public render() { public render() {
const { const {
languageSelection, languageSelection,
changeNodeIntent,
node, node,
nodeSelection, nodeSelection,
isChangingNode, isChangingNode,
isOffline, isOffline,
customNodes, nodeOptions,
customNetworks network
} = this.props; } = this.props;
const { isAddingCustomNode } = this.state; const { isAddingCustomNode } = this.state;
const selectedLanguage = languageSelection; const selectedLanguage = languageSelection;
const selectedNetwork = getNetworkConfigFromId(node.network, customNetworks);
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>; const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
const options = nodeOptions.map(n => {
const nodeOptions = Object.keys(NODES) if (n.isCustom) {
.map(key => { const { name: { networkName, nodeName }, isCustom, id, ...rest } = n;
const n = NODES[key];
const network = getNetworkConfigFromId(n.network, customNetworks);
return { return {
value: key, ...rest,
name: ( name: (
<span> <span>
{network && network.name} <small>({n.service})</small> {networkName} - {nodeName} <small>(custom)</small>
</span> </span>
), ),
color: network && network.color, onRemove: () => this.props.removeCustomNode({ id })
hidden: n.hidden
}; };
}) } else {
.concat( const { name: { networkName, service }, isCustom, ...rest } = n;
customNodes.map(cn => {
const network = getNetworkConfigFromId(cn.network, customNetworks);
return { return {
value: makeCustomNodeId(cn), ...rest,
name: ( name: (
<span> <span>
{network && network.name} - {cn.name} <small>(custom)</small> {networkName} <small>({service})</small>
</span> </span>
), )
color: network && network.color,
hidden: false,
onRemove: () => this.props.removeCustomNode(cn)
}; };
}) }
); });
return ( return (
<div className="Header"> <div className="Header">
@ -162,8 +187,8 @@ export default class Header extends Component<Props, State> {
change node. current node is on the ${node.network} network change node. current node is on the ${node.network} network
provided by ${node.service} provided by ${node.service}
`} `}
options={nodeOptions} options={options}
value={nodeSelection} value={nodeSelection || ''}
extra={ extra={
<li> <li>
<a onClick={this.openCustomNodeModal}>Add Custom Node</a> <a onClick={this.openCustomNodeModal}>Add Custom Node</a>
@ -180,14 +205,11 @@ export default class Header extends Component<Props, State> {
</section> </section>
</section> </section>
<Navigation color={selectedNetwork && selectedNetwork.color} /> <Navigation color={!network.isCustom && network.color} />
{isAddingCustomNode && ( {isAddingCustomNode && (
<CustomNodeModal <CustomNodeModal
customNodes={customNodes} addCustomNode={this.addCustomNode}
customNetworks={customNetworks}
handleAddCustomNode={this.addCustomNode}
handleAddCustomNetwork={this.props.addCustomNetwork}
handleClose={this.closeCustomNodeModal} handleClose={this.closeCustomNodeModal}
/> />
)} )}
@ -210,8 +232,10 @@ export default class Header extends Component<Props, State> {
this.setState({ isAddingCustomNode: false }); this.setState({ isAddingCustomNode: false });
}; };
private addCustomNode = (node: CustomNodeConfig) => { private addCustomNode = (payload: AddCustomNodeAction['payload']) => {
this.setState({ isAddingCustomNode: false }); this.setState({ isAddingCustomNode: false });
this.props.addCustomNode(node); this.props.addCustomNode(payload);
}; };
} }
export default connect(mapStateToProps, mapDispatchToProps)(Header);

View File

@ -8,7 +8,7 @@ interface Option<T> {
name: any; name: any;
value: T; value: T;
color?: string; color?: string;
hidden: boolean | undefined; hidden?: boolean | undefined;
onRemove?(): void; onRemove?(): void;
} }

View File

@ -1,51 +1,31 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import {
changeLanguage as dChangeLanguage,
changeNodeIntent as dChangeNodeIntent,
addCustomNode as dAddCustomNode,
removeCustomNode as dRemoveCustomNode,
addCustomNetwork as dAddCustomNetwork,
TChangeLanguage,
TChangeNodeIntent,
TAddCustomNode,
TRemoveCustomNode,
TAddCustomNetwork
} from 'actions/config';
import { TSetGasPriceField, setGasPriceField as dSetGasPriceField } from 'actions/transaction';
import { AlphaAgreement, Footer, Header } from 'components'; import { AlphaAgreement, Footer, Header } from 'components';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import Notifications from './Notifications'; import Notifications from './Notifications';
import OfflineTab from './OfflineTab'; import OfflineTab from './OfflineTab';
import { import { getOffline, getLatestBlock } from 'selectors/config';
getOffline,
getLanguageSelection,
getCustomNodeConfigs,
getCustomNetworkConfigs,
getLatestBlock,
isNodeChanging
} from 'selectors/config';
import { NodeConfig, CustomNodeConfig } from 'types/node';
import { CustomNetworkConfig } from 'types/network';
interface Props { interface StateProps {
isOffline: AppState['config']['meta']['offline'];
latestBlock: AppState['config']['meta']['latestBlock'];
}
interface OwnProps {
isUnavailableOffline?: boolean; isUnavailableOffline?: boolean;
children: string | React.ReactElement<string> | React.ReactElement<string>[]; children: string | React.ReactElement<string> | React.ReactElement<string>[];
} }
type Props = OwnProps & StateProps;
class TabSection extends Component<Props, {}> { class TabSection extends Component<Props, {}> {
public render() { public render() {
const { const { isUnavailableOffline, children, isOffline, latestBlock } = this.props;
isUnavailableOffline,
children,
// APP
isOffline
} = this.props;
return ( return (
<div className="page-layout"> <div className="page-layout">
<main> <main>
<Header {...headerProps} /> <Header />
<div className="Tab container"> <div className="Tab container">
{isUnavailableOffline && isOffline ? <OfflineTab /> : children} {isUnavailableOffline && isOffline ? <OfflineTab /> : children}
</div> </div>
@ -58,15 +38,9 @@ class TabSection extends Component<Props, {}> {
} }
} }
function mapStateToProps(state: AppState): ReduxProps { function mapStateToProps(state: AppState): StateProps {
return { return {
node: state.config.node,
nodeSelection: state.config.nodeSelection,
isChangingNode: isNodeChanging(state),
isOffline: getOffline(state), isOffline: getOffline(state),
languageSelection: getLanguageSelection(state),
customNodes: getCustomNodeConfigs(state),
customNetworks: getCustomNetworkConfigs(state),
latestBlock: getLatestBlock(state) latestBlock: getLatestBlock(state)
}; };
} }

View File

@ -1,11 +1,11 @@
import RPCNode from '../rpc'; import RPCNode from '../rpc';
import RPCClient from '../rpc/client'; import RPCClient from '../rpc/client';
import { CustomNodeConfig } from 'types/node'; import { CustomNodeConfig } from 'types/node';
import { Omit } from 'react-router';
export default class CustomNode extends RPCNode { export default class CustomNode extends RPCNode {
constructor(config: CustomNodeConfig) { constructor(config: Omit<CustomNodeConfig, 'lib'>) {
const endpoint = `${config.url}:${config.port}`; super(config.id);
super(endpoint);
const headers: { [key: string]: string } = {}; const headers: { [key: string]: string } = {};
if (config.auth) { if (config.auth) {
@ -13,6 +13,6 @@ export default class CustomNode extends RPCNode {
headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`; headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`;
} }
this.client = new RPCClient(endpoint, headers); this.client = new RPCClient(config.id, headers);
} }
} }

View File

@ -7,7 +7,7 @@ interface NodeLoaded {
interface NodeChangePending { interface NodeChangePending {
pending: true; pending: true;
nodeName: undefined; nodeName: string;
} }
export type State = NodeLoaded | NodeChangePending; export type State = NodeLoaded | NodeChangePending;
@ -22,8 +22,8 @@ const changeNode = (_: State, { payload }: ChangeNodeAction): State => ({
pending: false pending: false
}); });
const changeNodeIntent = (_: State, _2: ChangeNodeIntentAction): State => ({ const changeNodeIntent = (state: State, _: ChangeNodeIntentAction): State => ({
nodeName: undefined, ...state,
pending: true pending: true
}); });

View File

@ -7,72 +7,84 @@ export type State = NonWeb3NodeConfigs & Web3NodeConfig;
export const INITIAL_STATE: State = { export const INITIAL_STATE: State = {
eth_mew: { eth_mew: {
network: 'ETH', network: 'ETH',
isCustom: false,
lib: new RPCNode('https://api.myetherapi.com/eth'), lib: new RPCNode('https://api.myetherapi.com/eth'),
service: 'MyEtherWallet', service: 'MyEtherWallet',
estimateGas: true estimateGas: true
}, },
eth_mycrypto: { eth_mycrypto: {
network: 'ETH', network: 'ETH',
isCustom: false,
lib: new RPCNode('https://api.mycryptoapi.com/eth'), lib: new RPCNode('https://api.mycryptoapi.com/eth'),
service: 'MyCrypto', service: 'MyCrypto',
estimateGas: true estimateGas: true
}, },
eth_ethscan: { eth_ethscan: {
network: 'ETH', network: 'ETH',
isCustom: false,
service: 'Etherscan.io', service: 'Etherscan.io',
lib: new EtherscanNode('https://api.etherscan.io/api'), lib: new EtherscanNode('https://api.etherscan.io/api'),
estimateGas: false estimateGas: false
}, },
eth_infura: { eth_infura: {
network: 'ETH', network: 'ETH',
isCustom: false,
service: 'infura.io', service: 'infura.io',
lib: new InfuraNode('https://mainnet.infura.io/mew'), lib: new InfuraNode('https://mainnet.infura.io/mew'),
estimateGas: false estimateGas: false
}, },
rop_mew: { rop_mew: {
network: 'Ropsten', network: 'Ropsten',
isCustom: false,
service: 'MyEtherWallet', service: 'MyEtherWallet',
lib: new RPCNode('https://api.myetherapi.com/rop'), lib: new RPCNode('https://api.myetherapi.com/rop'),
estimateGas: false estimateGas: false
}, },
rop_infura: { rop_infura: {
network: 'Ropsten', network: 'Ropsten',
isCustom: false,
service: 'infura.io', service: 'infura.io',
lib: new InfuraNode('https://ropsten.infura.io/mew'), lib: new InfuraNode('https://ropsten.infura.io/mew'),
estimateGas: false estimateGas: false
}, },
kov_ethscan: { kov_ethscan: {
network: 'Kovan', network: 'Kovan',
isCustom: false,
service: 'Etherscan.io', service: 'Etherscan.io',
lib: new EtherscanNode('https://kovan.etherscan.io/api'), lib: new EtherscanNode('https://kovan.etherscan.io/api'),
estimateGas: false estimateGas: false
}, },
rin_ethscan: { rin_ethscan: {
network: 'Rinkeby', network: 'Rinkeby',
isCustom: false,
service: 'Etherscan.io', service: 'Etherscan.io',
lib: new EtherscanNode('https://rinkeby.etherscan.io/api'), lib: new EtherscanNode('https://rinkeby.etherscan.io/api'),
estimateGas: false estimateGas: false
}, },
rin_infura: { rin_infura: {
network: 'Rinkeby', network: 'Rinkeby',
isCustom: false,
service: 'infura.io', service: 'infura.io',
lib: new InfuraNode('https://rinkeby.infura.io/mew'), lib: new InfuraNode('https://rinkeby.infura.io/mew'),
estimateGas: false estimateGas: false
}, },
etc_epool: { etc_epool: {
network: 'ETC', network: 'ETC',
isCustom: false,
service: 'Epool.io', service: 'Epool.io',
lib: new RPCNode('https://mewapi.epool.io'), lib: new RPCNode('https://mewapi.epool.io'),
estimateGas: false estimateGas: false
}, },
ubq: { ubq: {
network: 'UBQ', network: 'UBQ',
isCustom: false,
service: 'ubiqscan.io', service: 'ubiqscan.io',
lib: new RPCNode('https://pyrus2.ubiqscan.io'), lib: new RPCNode('https://pyrus2.ubiqscan.io'),
estimateGas: true estimateGas: true
}, },
exp_tech: { exp_tech: {
network: 'EXP', network: 'EXP',
isCustom: false,
service: 'Expanse.tech', service: 'Expanse.tech',
lib: new RPCNode('https://node.expanse.tech/'), lib: new RPCNode('https://node.expanse.tech/'),
estimateGas: true estimateGas: true

View File

@ -8,20 +8,22 @@ import {
takeLatest, takeLatest,
takeEvery, takeEvery,
select, select,
race race,
apply
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import { import { makeCustomNetworkId } from 'utils/network';
makeCustomNodeId,
getCustomNodeConfigFromId,
makeNodeConfigFromCustomConfig
} from 'utils/node';
import { makeCustomNetworkId, getNetworkConfigFromId } from 'utils/network';
import { import {
getNodeName, getNodeName,
getNodeConfig, getNodeConfig,
getCustomNodeConfigs, getCustomNodeConfigs,
getCustomNetworkConfigs, getCustomNetworkConfigs,
getOffline getOffline,
getNetworkConfig,
isStaticNodeName,
getCustomNodeFromId,
getStaticNodeFromId,
getNetworkConfigById,
getStaticAltNodeToWeb3
} from 'selectors/config'; } from 'selectors/config';
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { TypeKeys } from 'actions/config/constants'; import { TypeKeys } from 'actions/config/constants';
@ -38,9 +40,10 @@ import { showNotification } from 'actions/notifications';
import { translateRaw } from 'translations'; import { translateRaw } from 'translations';
import { Web3Wallet } from 'libs/wallet'; import { Web3Wallet } from 'libs/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 } from 'reducers/config';
import { StaticNodeConfig, CustomNodeConfig } from 'types/node'; import { StaticNodeConfig, CustomNodeConfig, NodeConfig } from 'types/node';
import { CustomNetworkConfig } from 'types/network'; import { CustomNetworkConfig, StaticNetworkConfig } from 'types/network';
import { Web3Service } from 'reducers/config/nodes/typings';
export const getConfig = (state: AppState): ConfigState => state.config; export const getConfig = (state: AppState): ConfigState => state.config;
@ -113,41 +116,45 @@ export function* reload(): SagaIterator {
setTimeout(() => location.reload(), 1150); setTimeout(() => location.reload(), 1150);
} }
export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIterator { export function* handleNodeChangeIntent({
const currentNode: string = yield select(getNodeName); payload: nodeIdToSwitchTo
const currentConfig: StaticNodeConfig = yield select(getNodeConfig); }: ChangeNodeIntentAction): SagaIterator {
const customNets: CustomNetworkConfig[] = yield select(getCustomNetworkConfigs); const isStaticNode: boolean = yield select(isStaticNodeName, nodeIdToSwitchTo);
const currentNetwork = const currentConfig: NodeConfig = yield select(getNodeConfig);
getNetworkConfigFromId(currentConfig.network, customNets) || NETWORKS[currentConfig.network];
function* bailOut(message: string) { function* bailOut(message: string) {
const currentNodeName: string = yield select(getNodeName);
yield put(showNotification('danger', message, 5000)); yield put(showNotification('danger', message, 5000));
yield put(changeNode(currentNode, currentConfig, currentNetwork)); yield put(changeNode({ networkName: currentConfig.network, nodeName: currentNodeName }));
} }
let actionConfig = NODES[action.payload]; let nextNodeConfig: CustomNodeConfig | StaticNodeConfig;
if (!actionConfig) {
const customConfigs: CustomNodeConfig[] = yield select(getCustomNodeConfigs); if (!isStaticNode) {
const config = getCustomNodeConfigFromId(action.payload, customConfigs); const config: CustomNodeConfig | undefined = yield select(
getCustomNodeFromId,
nodeIdToSwitchTo
);
if (config) { if (config) {
actionConfig = makeNodeConfigFromCustomConfig(config); nextNodeConfig = config;
} else {
return yield* bailOut(`Attempted to switch to unknown node '${nodeIdToSwitchTo}'`);
} }
} else {
nextNodeConfig = yield select(getStaticNodeFromId, nodeIdToSwitchTo);
} }
if (!actionConfig) { // Grab current block from the node, before switching, to confirm it's online
return yield* bailOut(`Attempted to switch to unknown node '${action.payload}'`);
}
// Grab latest block from the node, before switching, to confirm it's online
// Give it 5 seconds before we call it offline // Give it 5 seconds before we call it offline
let latestBlock; let currentBlock;
let timeout; let timeout;
try { try {
const { lb, to } = yield race({ const { lb, to } = yield race({
lb: call(actionConfig.lib.getCurrentBlock.bind(actionConfig.lib)), lb: apply(nextNodeConfig, nextNodeConfig.lib.getCurrentBlock),
to: call(delay, 5000) to: call(delay, 5000)
}); });
latestBlock = lb; currentBlock = lb;
timeout = to; timeout = to;
} catch (err) { } catch (err) {
// Whether it times out or errors, same message // Whether it times out or errors, same message
@ -158,16 +165,19 @@ export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIte
return yield* bailOut(translateRaw('ERROR_32')); return yield* bailOut(translateRaw('ERROR_32'));
} }
const actionNetwork = getNetworkConfigFromId(actionConfig.network, customNets); const nextNetwork: StaticNetworkConfig | CustomNetworkConfig = yield select(
getNetworkConfigById,
nextNodeConfig.network
);
if (!actionNetwork) { if (!nextNetwork) {
return yield* bailOut( return yield* bailOut(
`Unknown custom network for your node '${action.payload}', try re-adding it` `Unknown custom network for your node '${nodeIdToSwitchTo}', try re-adding it`
); );
} }
yield put(setLatestBlock(latestBlock)); yield put(setLatestBlock(currentBlock));
yield put(changeNode(action.payload, actionConfig, actionNetwork)); yield put(changeNode({ networkName: nextNodeConfig.network, nodeName: nodeIdToSwitchTo }));
// TODO - re-enable once DeterministicWallet state is fixed to flush properly. // 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 // DeterministicWallet keeps path related state we need to flush before we can stop reloading
@ -176,8 +186,8 @@ export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIte
// 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) {
const isNewNetwork = currentConfig.network !== actionConfig.network; const isNewNetwork = currentConfig.network !== nextNodeConfig.network;
const newIsWeb3 = actionConfig.service === Web3Service; const newIsWeb3 = nextNodeConfig.service === Web3Service;
// don't reload when web3 is selected; node will automatically re-set and state is not an issue here // don't reload when web3 is selected; node will automatically re-set and state is not an issue here
if (isNewNetwork && !newIsWeb3) { if (isNewNetwork && !newIsWeb3) {
yield call(reload); yield call(reload);
@ -185,8 +195,7 @@ export function* handleNodeChangeIntent(action: ChangeNodeIntentAction): SagaIte
} }
export function* switchToNewNode(action: AddCustomNodeAction): SagaIterator { export function* switchToNewNode(action: AddCustomNodeAction): SagaIterator {
const nodeId = makeCustomNodeId(action.payload); yield put(changeNodeIntent(action.payload.id));
yield put(changeNodeIntent(nodeId));
} }
// If there are any orphaned custom networks, purge them // If there are any orphaned custom networks, purge them
@ -208,7 +217,6 @@ export function* cleanCustomNetworks(): SagaIterator {
// unset web3 as the selected node if a non-web3 wallet has been selected // unset web3 as the selected node if a non-web3 wallet has been selected
export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator { export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
const node = yield select(getNodeName); const node = yield select(getNodeName);
const nodeConfig = yield select(getNodeConfig);
const newWallet = action.payload; const newWallet = action.payload;
const isWeb3Wallet = newWallet instanceof Web3Wallet; const isWeb3Wallet = newWallet instanceof Web3Wallet;
@ -216,8 +224,9 @@ export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
return; return;
} }
const altNode = yield select(getStaticAltNodeToWeb3);
// switch back to a node with the same network as MetaMask/Mist // switch back to a node with the same network as MetaMask/Mist
yield put(changeNodeIntent(equivalentNodeOrDefault(nodeConfig))); yield put(changeNodeIntent(altNode));
} }
export function* unsetWeb3Node(): SagaIterator { export function* unsetWeb3Node(): SagaIterator {
@ -227,30 +236,11 @@ export function* unsetWeb3Node(): SagaIterator {
return; return;
} }
const nodeConfig: StaticNodeConfig = yield select(getNodeConfig); const altNode = yield select(getStaticAltNodeToWeb3);
const newNode = equivalentNodeOrDefault(nodeConfig); // switch back to a node with the same network as MetaMask/Mist
yield put(changeNodeIntent(altNode));
yield put(changeNodeIntent(newNode));
} }
export const equivalentNodeOrDefault = (nodeConfig: StaticNodeConfig) => {
const node = Object.keys(NODES)
.filter(key => key !== 'web3')
.reduce((found, key) => {
const config = NODES[key];
if (found.length) {
return found;
}
if (nodeConfig.network === config.network) {
return (found = key);
}
return found;
}, '');
// if no equivalent node was found, use the app default
return node.length ? node : configInitialState.nodeSelection;
};
export default function* configSaga(): SagaIterator { export default function* configSaga(): SagaIterator {
yield takeLatest(TypeKeys.CONFIG_POLL_OFFLINE_STATUS, handlePollOfflineStatus); yield takeLatest(TypeKeys.CONFIG_POLL_OFFLINE_STATUS, handlePollOfflineStatus);
yield takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent); yield takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent);

View File

@ -38,7 +38,7 @@ import {
} from 'libs/wallet'; } from 'libs/wallet';
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, getWeb3Node } from 'selectors/config';
import { import {
getTokens, getTokens,
getWalletInst, getWalletInst,
@ -51,6 +51,7 @@ import Web3Node, { isWeb3Node } from 'libs/nodes/web3';
import { loadWalletConfig, saveWalletConfig } from 'utils/localStorage'; import { loadWalletConfig, saveWalletConfig } from 'utils/localStorage';
import { getTokenBalances, filterScannedTokenBalances } from './helpers'; import { getTokenBalances, filterScannedTokenBalances } from './helpers';
import { Token } from 'types/network'; import { Token } from 'types/network';
import { Web3NodeConfig } from '../../../shared/types/node';
export interface TokenBalanceLookup { export interface TokenBalanceLookup {
[symbol: string]: TokenBalance; [symbol: string]: TokenBalance;
@ -265,11 +266,12 @@ 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) { const web3Node: Web3NodeConfig | null = yield select(getWeb3Node);
if (!web3Node) {
throw Error('Web3 node config not found!'); throw Error('Web3 node config not found!');
} }
const network = NODES.web3.network; const network = web3Node.network;
const nodeLib: INode | Web3Node = yield select(getNodeLib); const nodeLib: Web3Node = web3Node.lib;
if (!isWeb3Node(nodeLib)) { if (!isWeb3Node(nodeLib)) {
throw new Error('Cannot use Web3 wallet without a Web3 node.'); throw new Error('Cannot use Web3 wallet without a Web3 node.');

View File

@ -9,14 +9,24 @@ import {
export const getNetworks = (state: AppState) => getConfig(state).networks; export const getNetworks = (state: AppState) => getConfig(state).networks;
export const getNetworkConfigById = (state: AppState, networkId: string) =>
isStaticNetworkName(state, networkId)
? getStaticNetworkConfigs(state)[networkId]
: getCustomNetworkConfigs(state)[networkId];
export const getStaticNetworkNames = (state: AppState): StaticNetworkNames[] => export const getStaticNetworkNames = (state: AppState): StaticNetworkNames[] =>
Object.keys(getNetworks(state).staticNetworks) as StaticNetworkNames[]; Object.keys(getNetworks(state).staticNetworks) as StaticNetworkNames[];
export const isStaticNetworkName = (
state: AppState,
networkName: string
): networkName is StaticNetworkNames =>
Object.keys(getStaticNetworkConfigs(state)).includes(networkName);
export const getStaticNetworkConfig = (state: AppState): StaticNetworkConfig | undefined => { export const getStaticNetworkConfig = (state: AppState): StaticNetworkConfig | undefined => {
const { staticNetworks, selectedNetwork } = getNetworks(state); const { staticNetworks, selectedNetwork } = getNetworks(state);
const isDefaultNetworkName = (networkName: string): networkName is StaticNetworkNames =>
Object.keys(staticNetworks).includes(networkName); const defaultNetwork = isStaticNetworkName(state, selectedNetwork)
const defaultNetwork = isDefaultNetworkName(selectedNetwork)
? staticNetworks[selectedNetwork] ? staticNetworks[selectedNetwork]
: undefined; : undefined;
return defaultNetwork; return defaultNetwork;

View File

@ -1,29 +1,70 @@
import { AppState } from 'reducers'; import { AppState } from 'reducers';
import { getConfig, getStaticNetworkConfigs } from 'selectors/config'; import {
import { CustomNodeConfig, StaticNodeConfig, StaticNodeName } from 'types/node'; getConfig,
getStaticNetworkConfigs,
getCustomNetworkConfigs,
isStaticNetworkName
} from 'selectors/config';
import { CustomNodeConfig, StaticNodeConfig, StaticNodeName, Web3NodeConfig } from 'types/node';
import { INITIAL_STATE as SELECTED_NODE_INITIAL_STATE } from 'reducers/config/nodes/selectedNode';
export const getNodes = (state: AppState) => getConfig(state).nodes; export const getNodes = (state: AppState) => getConfig(state).nodes;
export const getStaticNodeConfig = (state: AppState): StaticNodeConfig | undefined => { export function isNodeCustom(state: AppState, nodeName: string): CustomNodeConfig | undefined {
const { staticNodes, selectedNode: { nodeName } } = getNodes(state); return getCustomNodeConfigs(state)[nodeName];
if (nodeName === undefined) {
return nodeName;
} }
const isStaticNodeName = (networkName: string): networkName is StaticNodeName => export const getCustomNodeFromId = (
Object.keys(staticNodes).includes(networkName); state: AppState,
nodeName: string
): CustomNodeConfig | undefined => getCustomNodeConfigs(state)[nodeName];
const defaultNetwork = isStaticNodeName(nodeName) ? staticNodes[nodeName] : undefined; export const getStaticAltNodeToWeb3 = (state: AppState) => {
const { web3, ...configs } = getStaticNodeConfigs(state);
if (!web3) {
return SELECTED_NODE_INITIAL_STATE.nodeName;
}
const res = Object.entries(configs).find(
([_, config]: [StaticNodeName, StaticNodeConfig]) => web3.network === config.network
);
if (res) {
return res[0];
}
return SELECTED_NODE_INITIAL_STATE.nodeName;
};
export const getStaticNodeFromId = (state: AppState, nodeName: StaticNodeName) =>
getStaticNodeConfigs(state)[nodeName];
export const isStaticNodeName = (state: AppState, nodeName: string): nodeName is StaticNodeName =>
Object.keys(getStaticNodeConfigs(state)).includes(nodeName);
const getStaticNodeConfigs = (state: AppState) => getNodes(state).staticNodes;
export const getStaticNodeConfig = (state: AppState): StaticNodeConfig | undefined => {
const { staticNodes, selectedNode: { nodeName } } = getNodes(state);
const defaultNetwork = isStaticNodeName(state, nodeName) ? staticNodes[nodeName] : undefined;
return defaultNetwork; return defaultNetwork;
}; };
export const getWeb3Node = (state: AppState): Web3NodeConfig | null => {
const currNode = getStaticNodeConfig(state);
const currNodeName = getNodeName(state);
if (
currNode &&
currNodeName &&
isStaticNodeName(state, currNodeName) &&
currNodeName === 'web3'
) {
return currNode;
}
return null;
};
export const getCustomNodeConfig = (state: AppState): CustomNodeConfig | undefined => { export const getCustomNodeConfig = (state: AppState): CustomNodeConfig | undefined => {
const { customNodes, selectedNode: { nodeName } } = getNodes(state); const { customNodes, selectedNode: { nodeName } } = getNodes(state);
if (nodeName === undefined) {
return nodeName;
}
const customNode = customNodes[nodeName]; const customNode = customNodes[nodeName];
return customNode; return customNode;
}; };
@ -40,7 +81,7 @@ export function isNodeChanging(state): boolean {
return getNodes(state).selectedNode.pending; return getNodes(state).selectedNode.pending;
} }
export function getNodeName(state: AppState): string | undefined { export function getNodeName(state: AppState): string {
return getNodes(state).selectedNode.nodeName; return getNodes(state).selectedNode.nodeName;
} }
@ -48,15 +89,15 @@ export function getIsWeb3Node(state: AppState): boolean {
return getNodeName(state) === 'web3'; return getNodeName(state) === 'web3';
} }
export function getNodeConfig(state: AppState): StaticNodeConfig | CustomNodeConfig | undefined { export function getNodeConfig(state: AppState): StaticNodeConfig | CustomNodeConfig {
const config = getStaticNodeConfig(state) || getCustomNodeConfig(state); const config = getStaticNodeConfig(state) || getCustomNodeConfig(state);
/*
if (!config) { if (!config) {
const { selectedNode } = getNodes(state); const { selectedNode } = getNodes(state);
throw Error( throw Error(
`No node config found for ${selectedNode.nodeName} in either static or custom nodes` `No node config found for ${selectedNode.nodeName} in either static or custom nodes`
); );
}*/ }
return config; return config;
} }
@ -68,25 +109,63 @@ export function getNodeLib(state: AppState) {
return config.lib; return config.lib;
} }
interface NodeOption { export interface NodeOption {
isCustom: false;
value: string; value: string;
name: { networkName?: string; service: string }; name: { networkName?: string; service: string };
color?: string; color?: string;
hidden?: boolean; hidden?: boolean;
} }
export function getStaticNodeOptions(state: AppState) { export function getStaticNodeOptions(state: AppState): NodeOption[] {
const staticNetworkConfigs = getStaticNetworkConfigs(state); const staticNetworkConfigs = getStaticNetworkConfigs(state);
Object.entries(getStaticNodes(state)).map( return Object.entries(getStaticNodes(state)).map(
([nodeName, nodeConfig]: [string, StaticNodeConfig]) => { ([nodeName, node]: [string, StaticNodeConfig]) => {
const networkName = nodeConfig.network; const networkName = node.network;
const associatedNetwork = staticNetworkConfigs[networkName]; const associatedNetwork = staticNetworkConfigs[networkName];
return { const opt: NodeOption = {
isCustom: node.isCustom,
value: nodeName, value: nodeName,
name: { networkName, service: nodeConfig.service }, name: { networkName, service: node.service },
color: associatedNetwork.color, color: associatedNetwork.color,
hidden: nodeConfig.hidden hidden: node.hidden
}; };
return opt;
} }
); );
} }
export interface CustomNodeOption {
isCustom: true;
id: string;
value: string;
name: { networkName?: string; nodeName: string };
color?: string;
hidden?: boolean;
}
export function getCustomNodeOptions(state: AppState): CustomNodeOption[] {
const staticNetworkConfigs = getStaticNetworkConfigs(state);
const customNetworkConfigs = getCustomNetworkConfigs(state);
return Object.entries(getCustomNodeConfigs(state)).map(
([nodeName, node]: [string, CustomNodeConfig]) => {
const networkName = node.network;
const associatedNetwork = isStaticNetworkName(state, networkName)
? staticNetworkConfigs[networkName]
: customNetworkConfigs[networkName];
const opt: CustomNodeOption = {
isCustom: node.isCustom,
value: node.id,
name: { networkName, nodeName },
color: associatedNetwork.isCustom ? undefined : associatedNetwork.color,
hidden: false,
id: node.id
};
return opt;
}
);
}
export function getNodeOptions(state: AppState) {
return [...getStaticNodeOptions(state), ...getCustomNodeOptions(state)];
}

View File

@ -65,7 +65,7 @@ const configureStore = () => {
// If they have a saved node, make sure we assign that too. The node selected // If they have a saved node, make sure we assign that too. The node selected
// isn't serializable, so we have to assign it here. // isn't serializable, so we have to assign it here.
if (savedConfigState && savedConfigState.nodeSelection) { if (savedConfigState && savedConfigState.nodes.selectedNode.nodeName) {
const savedNode = getNodeConfigFromId( const savedNode = getNodeConfigFromId(
savedConfigState.nodeSelection, savedConfigState.nodeSelection,
savedConfigState.customNodes savedConfigState.customNodes

View File

@ -39,20 +39,6 @@ export function makeNetworkConfigFromCustomConfig(
return customConfig; return customConfig;
} }
export function getNetworkConfigFromId(
id: string,
configs: CustomNetworkConfig[]
): StaticNetworkConfig | undefined {
if (NETWORKS[id]) {
return NETWORKS[id];
}
const customConfig = configs.find(conf => makeCustomNetworkId(conf) === id);
if (customConfig) {
return makeNetworkConfigFromCustomConfig(customConfig);
}
}
type PathType = keyof DPathFormats; type PathType = keyof DPathFormats;
type DPathFormat = type DPathFormat =

View File

@ -1,42 +0,0 @@
import { CustomNode } from 'libs/nodes';
import { CustomNodeConfig, StaticNodeConfig } from 'types/node';
export function makeCustomNodeId(config: CustomNodeConfig): string {
return `${config.url}:${config.port}`;
}
export function getCustomNodeConfigFromId(
id: string,
configs: CustomNodeConfig[]
): CustomNodeConfig | undefined {
return configs.find(node => makeCustomNodeId(node) === id);
}
export function getNodeConfigFromId(
id: string,
configs: CustomNodeConfig[]
): StaticNodeConfig | undefined {
if (NODES[id]) {
return NODES[id];
}
const config = getCustomNodeConfigFromId(id, configs);
if (config) {
return makeNodeConfigFromCustomConfig(config);
}
}
export function makeNodeConfigFromCustomConfig(config: CustomNodeConfig): StaticNodeConfig {
interface Override extends StaticNodeConfig {
network: any;
}
const customConfig: Override = {
network: config.network,
lib: new CustomNode(config),
service: 'your custom node',
estimateGas: true
};
return customConfig;
}

View File

@ -28,7 +28,6 @@ interface DPathFormats {
} }
interface StaticNetworkConfig { interface StaticNetworkConfig {
// TODO really try not to allow strings due to custom networks
isCustom: false; // used for type guards isCustom: false; // used for type guards
name: StaticNetworkNames; name: StaticNetworkNames;
unit: string; unit: string;

View File

@ -1,9 +1,14 @@
import { RPCNode, Web3Node } from 'libs/nodes'; import { RPCNode, Web3Node } from 'libs/nodes';
import { StaticNetworkNames } from './network'; import { StaticNetworkNames } from './network';
import { StaticNodesState, CustomNodesState } from 'reducers/config/nodes'; import { StaticNodesState, CustomNodesState } from 'reducers/config/nodes';
import CustomNode from 'libs/nodes/custom';
interface CustomNodeConfig { interface CustomNodeConfig {
id: string;
isCustom: true;
name: string; name: string;
lib: CustomNode;
service: 'your custom node';
url: string; url: string;
port: number; port: number;
network: string; network: string;
@ -14,6 +19,7 @@ interface CustomNodeConfig {
} }
interface StaticNodeConfig { interface StaticNodeConfig {
isCustom: false;
network: StaticNetworkNames; network: StaticNetworkNames;
lib: RPCNode | Web3Node; lib: RPCNode | Web3Node;
service: string; service: string;
@ -21,6 +27,10 @@ interface StaticNodeConfig {
hidden?: boolean; hidden?: boolean;
} }
interface Web3NodeConfig extends StaticNodeConfig {
lib: Web3Node;
}
declare enum StaticNodeName { declare enum StaticNodeName {
ETH_MEW = 'eth_mew', ETH_MEW = 'eth_mew',
ETH_MYCRYPTO = 'eth_mycrypto', ETH_MYCRYPTO = 'eth_mycrypto',
@ -39,7 +49,7 @@ declare enum StaticNodeName {
type NonWeb3NodeConfigs = { [key in StaticNodeName]: StaticNodeConfig }; type NonWeb3NodeConfigs = { [key in StaticNodeName]: StaticNodeConfig };
interface Web3NodeConfig { interface Web3NodeConfig {
web3?: StaticNodeConfig; web3?: Web3NodeConfig;
} }
type NodeConfig = StaticNodesState[StaticNodeName] | CustomNodesState[string]; type NodeConfig = StaticNodesState[StaticNodeName] | CustomNodesState[string];