mirror of
https://github.com/status-im/MyCrypto.git
synced 2025-01-11 03:26:14 +00:00
Custom Nodes - Final Touches (#501)
* Add warning about matching nodes, only allow one url:port combination of nodes. * Fix up alert styling. * Custom network form. * Add custom network to redux store. Setup infrastructure for removal and display. * Persist custom networks to LS, show them in display. * Force chain id, make typing happy. * Display custom networks in network dropdown. * Fix form validation, purge unused custom networks.
This commit is contained in:
parent
eb490a78b3
commit
b638b746de
@ -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 } from 'config/data';
|
import { NodeConfig, CustomNodeConfig, CustomNetworkConfig } from 'config/data';
|
||||||
|
|
||||||
export type TForceOfflineConfig = typeof forceOfflineConfig;
|
export type TForceOfflineConfig = typeof forceOfflineConfig;
|
||||||
export function forceOfflineConfig(): interfaces.ForceOfflineAction {
|
export function forceOfflineConfig(): interfaces.ForceOfflineAction {
|
||||||
@ -80,6 +80,26 @@ export function removeCustomNode(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TAddCustomNetwork = typeof addCustomNetwork;
|
||||||
|
export function addCustomNetwork(
|
||||||
|
payload: CustomNetworkConfig
|
||||||
|
): interfaces.AddCustomNetworkAction {
|
||||||
|
return {
|
||||||
|
type: TypeKeys.CONFIG_ADD_CUSTOM_NETWORK,
|
||||||
|
payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TRemoveCustomNetwork = typeof removeCustomNetwork;
|
||||||
|
export function removeCustomNetwork(
|
||||||
|
payload: CustomNetworkConfig
|
||||||
|
): interfaces.RemoveCustomNetworkAction {
|
||||||
|
return {
|
||||||
|
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NETWORK,
|
||||||
|
payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type TSetLatestBlock = typeof setLatestBlock;
|
export type TSetLatestBlock = typeof setLatestBlock;
|
||||||
export function setLatestBlock(
|
export function setLatestBlock(
|
||||||
payload: string
|
payload: string
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { TypeKeys } from './constants';
|
import { TypeKeys } from './constants';
|
||||||
import { CustomNodeConfig, NodeConfig } from 'config/data';
|
import { NodeConfig, CustomNodeConfig, CustomNetworkConfig } from 'config/data';
|
||||||
|
|
||||||
/*** Toggle Offline ***/
|
/*** Toggle Offline ***/
|
||||||
export interface ToggleOfflineAction {
|
export interface ToggleOfflineAction {
|
||||||
@ -56,6 +56,18 @@ export interface RemoveCustomNodeAction {
|
|||||||
payload: CustomNodeConfig;
|
payload: CustomNodeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** Add Custom Network ***/
|
||||||
|
export interface AddCustomNetworkAction {
|
||||||
|
type: TypeKeys.CONFIG_ADD_CUSTOM_NETWORK;
|
||||||
|
payload: CustomNetworkConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Remove Custom Network ***/
|
||||||
|
export interface RemoveCustomNetworkAction {
|
||||||
|
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NETWORK;
|
||||||
|
payload: CustomNetworkConfig;
|
||||||
|
}
|
||||||
|
|
||||||
/*** Set Latest Block ***/
|
/*** Set Latest Block ***/
|
||||||
export interface SetLatestBlockAction {
|
export interface SetLatestBlockAction {
|
||||||
type: TypeKeys.CONFIG_SET_LATEST_BLOCK;
|
type: TypeKeys.CONFIG_SET_LATEST_BLOCK;
|
||||||
@ -78,5 +90,7 @@ export type ConfigAction =
|
|||||||
| ChangeNodeIntentAction
|
| ChangeNodeIntentAction
|
||||||
| AddCustomNodeAction
|
| AddCustomNodeAction
|
||||||
| RemoveCustomNodeAction
|
| RemoveCustomNodeAction
|
||||||
|
| AddCustomNetworkAction
|
||||||
|
| RemoveCustomNetworkAction
|
||||||
| SetLatestBlockAction
|
| SetLatestBlockAction
|
||||||
| Web3UnsetNodeAction;
|
| Web3UnsetNodeAction;
|
||||||
|
@ -8,6 +8,8 @@ export enum TypeKeys {
|
|||||||
CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS',
|
CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS',
|
||||||
CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_CUSTOM_NODE',
|
CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_CUSTOM_NODE',
|
||||||
CONFIG_REMOVE_CUSTOM_NODE = 'CONFIG_REMOVE_CUSTOM_NODE',
|
CONFIG_REMOVE_CUSTOM_NODE = 'CONFIG_REMOVE_CUSTOM_NODE',
|
||||||
|
CONFIG_ADD_CUSTOM_NETWORK = 'CONFIG_ADD_CUSTOM_NETWORK',
|
||||||
|
CONFIG_REMOVE_CUSTOM_NETWORK = 'CONFIG_REMOVE_CUSTOM_NETWORK',
|
||||||
CONFIG_SET_LATEST_BLOCK = 'CONFIG_SET_LATEST_BLOCK',
|
CONFIG_SET_LATEST_BLOCK = 'CONFIG_SET_LATEST_BLOCK',
|
||||||
CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET'
|
CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET'
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,12 @@ 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 } from 'config/data';
|
import { NETWORKS, CustomNodeConfig, CustomNetworkConfig } from 'config/data';
|
||||||
|
import { makeCustomNodeId } from 'utils/node';
|
||||||
|
import { makeCustomNetworkId } from 'utils/network';
|
||||||
|
|
||||||
const NETWORK_KEYS = Object.keys(NETWORKS);
|
const NETWORK_KEYS = Object.keys(NETWORKS);
|
||||||
|
const CUSTOM = 'custom';
|
||||||
|
|
||||||
interface Input {
|
interface Input {
|
||||||
name: string;
|
name: string;
|
||||||
@ -13,7 +16,10 @@ interface Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
customNodes: CustomNodeConfig[];
|
||||||
|
customNetworks: CustomNetworkConfig[];
|
||||||
handleAddCustomNode(node: CustomNodeConfig): void;
|
handleAddCustomNode(node: CustomNodeConfig): void;
|
||||||
|
handleAddCustomNetwork(node: CustomNetworkConfig): void;
|
||||||
handleClose(): void;
|
handleClose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +28,9 @@ interface State {
|
|||||||
url: string;
|
url: string;
|
||||||
port: string;
|
port: string;
|
||||||
network: string;
|
network: string;
|
||||||
|
customNetworkName: string;
|
||||||
|
customNetworkUnit: string;
|
||||||
|
customNetworkChainId: string;
|
||||||
hasAuth: boolean;
|
hasAuth: boolean;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
@ -33,13 +42,17 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
url: '',
|
url: '',
|
||||||
port: '',
|
port: '',
|
||||||
network: NETWORK_KEYS[0],
|
network: NETWORK_KEYS[0],
|
||||||
|
customNetworkName: '',
|
||||||
|
customNetworkUnit: '',
|
||||||
|
customNetworkChainId: '',
|
||||||
hasAuth: false,
|
hasAuth: false,
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { handleClose } = this.props;
|
const { customNetworks, handleClose } = this.props;
|
||||||
|
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();
|
||||||
|
|
||||||
@ -56,6 +69,8 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const conflictedNode = this.getConflictedNode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={translate('NODE_Title')}
|
title={translate('NODE_Title')}
|
||||||
@ -65,11 +80,18 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{isHttps && (
|
{isHttps && (
|
||||||
<div className="alert alert-danger small">
|
<div className="alert alert-warning small">
|
||||||
{translate('NODE_Warning')}
|
{translate('NODE_Warning')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{conflictedNode && (
|
||||||
|
<div className="alert alert-warning small">
|
||||||
|
You already have a node called '{conflictedNode.name}' that
|
||||||
|
matches this one, saving this will overwrite it
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<form>
|
<form>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-7">
|
<div className="col-sm-7">
|
||||||
@ -87,7 +109,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
<select
|
<select
|
||||||
className="form-control"
|
className="form-control"
|
||||||
name="network"
|
name="network"
|
||||||
value={this.state.network}
|
value={network}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
>
|
>
|
||||||
{NETWORK_KEYS.map(net => (
|
{NETWORK_KEYS.map(net => (
|
||||||
@ -95,10 +117,56 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
{net}
|
{net}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
{customNetworks.map(net => {
|
||||||
|
const id = makeCustomNetworkId(net);
|
||||||
|
return (
|
||||||
|
<option key={id} value={id}>
|
||||||
|
{net.name} (Custom)
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<option value={CUSTOM}>Custom...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{network === CUSTOM && (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-sm-6">
|
||||||
|
<label className="is-required">Network Name</label>
|
||||||
|
{this.renderInput(
|
||||||
|
{
|
||||||
|
name: 'customNetworkName',
|
||||||
|
placeholder: 'My Custom Network'
|
||||||
|
},
|
||||||
|
invalids
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-3">
|
||||||
|
<label className="is-required">Currency</label>
|
||||||
|
{this.renderInput(
|
||||||
|
{
|
||||||
|
name: 'customNetworkUnit',
|
||||||
|
placeholder: 'ETH'
|
||||||
|
},
|
||||||
|
invalids
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-3">
|
||||||
|
<label>Chain ID</label>
|
||||||
|
{this.renderInput(
|
||||||
|
{
|
||||||
|
name: 'customNetworkChainId',
|
||||||
|
placeholder: 'e.g. 1'
|
||||||
|
},
|
||||||
|
invalids
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-9">
|
<div className="col-sm-9">
|
||||||
<label>URL</label>
|
<label>URL</label>
|
||||||
@ -123,6 +191,7 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<label>
|
<label>
|
||||||
@ -139,11 +208,11 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
{this.state.hasAuth && (
|
{this.state.hasAuth && (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<label>Username</label>
|
<label className="is-required">Username</label>
|
||||||
{this.renderInput({ name: 'username' }, invalids)}
|
{this.renderInput({ name: 'username' }, invalids)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<label>Password</label>
|
<label className="is-required">Password</label>
|
||||||
{this.renderInput(
|
{this.renderInput(
|
||||||
{
|
{
|
||||||
name: 'password',
|
name: 'password',
|
||||||
@ -175,7 +244,17 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getInvalids(): { [key: string]: boolean } {
|
private getInvalids(): { [key: string]: boolean } {
|
||||||
const { url, port, hasAuth, username, password } = this.state;
|
const {
|
||||||
|
url,
|
||||||
|
port,
|
||||||
|
hasAuth,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
network,
|
||||||
|
customNetworkName,
|
||||||
|
customNetworkUnit,
|
||||||
|
customNetworkChainId
|
||||||
|
} = this.state;
|
||||||
const required = ['name', 'url', 'port', 'network'];
|
const required = ['name', 'url', 'port', 'network'];
|
||||||
const invalids: { [key: string]: boolean } = {};
|
const invalids: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
@ -207,9 +286,64 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If they have a custom network, make sure info is provided
|
||||||
|
if (network === CUSTOM) {
|
||||||
|
if (!customNetworkName) {
|
||||||
|
invalids.customNetworkName = true;
|
||||||
|
}
|
||||||
|
if (!customNetworkUnit) {
|
||||||
|
invalids.customNetworkUnit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric chain ID (if provided)
|
||||||
|
const iChainId = parseInt(customNetworkChainId, 10);
|
||||||
|
if (!iChainId || iChainId < 0) {
|
||||||
|
invalids.customNetworkChainId = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return invalids;
|
return invalids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private makeCustomNetworkConfigFromState(): CustomNetworkConfig {
|
||||||
|
return {
|
||||||
|
name: this.state.customNetworkName,
|
||||||
|
unit: this.state.customNetworkUnit,
|
||||||
|
chainId: this.state.customNetworkChainId
|
||||||
|
? parseInt(this.state.customNetworkChainId, 10)
|
||||||
|
: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeCustomNodeConfigFromState(): CustomNodeConfig {
|
||||||
|
const { network } = this.state;
|
||||||
|
const node: CustomNodeConfig = {
|
||||||
|
name: this.state.name.trim(),
|
||||||
|
url: this.state.url.trim(),
|
||||||
|
port: parseInt(this.state.port, 10),
|
||||||
|
network:
|
||||||
|
network === CUSTOM
|
||||||
|
? makeCustomNetworkId(this.makeCustomNetworkConfigFromState())
|
||||||
|
: network
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.hasAuth) {
|
||||||
|
node.auth = {
|
||||||
|
username: this.state.username,
|
||||||
|
password: this.state.password
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getConflictedNode(): CustomNodeConfig | undefined {
|
||||||
|
const { customNodes } = this.props;
|
||||||
|
const config = this.makeCustomNodeConfigFromState();
|
||||||
|
const thisId = makeCustomNodeId(config);
|
||||||
|
return customNodes.find(conf => makeCustomNodeId(conf) === thisId);
|
||||||
|
}
|
||||||
|
|
||||||
private handleChange = (
|
private handleChange = (
|
||||||
ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>
|
ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>
|
||||||
) => {
|
) => {
|
||||||
@ -223,18 +357,11 @@ export default class CustomNodeModal extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private saveAndAdd = () => {
|
private saveAndAdd = () => {
|
||||||
const node: CustomNodeConfig = {
|
const node = this.makeCustomNodeConfigFromState();
|
||||||
name: this.state.name.trim(),
|
|
||||||
url: this.state.url.trim(),
|
|
||||||
port: parseInt(this.state.port, 10),
|
|
||||||
network: this.state.network
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.state.hasAuth) {
|
if (this.state.network === CUSTOM) {
|
||||||
node.auth = {
|
const network = this.makeCustomNetworkConfigFromState();
|
||||||
username: this.state.username,
|
this.props.handleAddCustomNetwork(network);
|
||||||
password: this.state.password
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.handleAddCustomNode(node);
|
this.props.handleAddCustomNode(node);
|
||||||
|
@ -3,7 +3,8 @@ import {
|
|||||||
TChangeLanguage,
|
TChangeLanguage,
|
||||||
TChangeNodeIntent,
|
TChangeNodeIntent,
|
||||||
TAddCustomNode,
|
TAddCustomNode,
|
||||||
TRemoveCustomNode
|
TRemoveCustomNode,
|
||||||
|
TAddCustomNetwork
|
||||||
} 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';
|
||||||
@ -14,17 +15,18 @@ import {
|
|||||||
ANNOUNCEMENT_MESSAGE,
|
ANNOUNCEMENT_MESSAGE,
|
||||||
ANNOUNCEMENT_TYPE,
|
ANNOUNCEMENT_TYPE,
|
||||||
languages,
|
languages,
|
||||||
NETWORKS,
|
|
||||||
NODES,
|
NODES,
|
||||||
VERSION,
|
VERSION,
|
||||||
NodeConfig,
|
NodeConfig,
|
||||||
CustomNodeConfig
|
CustomNodeConfig,
|
||||||
} from '../../config/data';
|
CustomNetworkConfig
|
||||||
|
} from 'config/data';
|
||||||
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';
|
||||||
import { getKeyByValue } from 'utils/helpers';
|
import { getKeyByValue } from 'utils/helpers';
|
||||||
import { makeCustomNodeId } from 'utils/node';
|
import { makeCustomNodeId } from 'utils/node';
|
||||||
|
import { getNetworkConfigFromId } from 'utils/network';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -34,12 +36,14 @@ interface Props {
|
|||||||
isChangingNode: boolean;
|
isChangingNode: boolean;
|
||||||
gasPriceGwei: number;
|
gasPriceGwei: number;
|
||||||
customNodes: CustomNodeConfig[];
|
customNodes: CustomNodeConfig[];
|
||||||
|
customNetworks: CustomNetworkConfig[];
|
||||||
|
|
||||||
changeLanguage: TChangeLanguage;
|
changeLanguage: TChangeLanguage;
|
||||||
changeNodeIntent: TChangeNodeIntent;
|
changeNodeIntent: TChangeNodeIntent;
|
||||||
changeGasPrice: TChangeGasPrice;
|
changeGasPrice: TChangeGasPrice;
|
||||||
addCustomNode: TAddCustomNode;
|
addCustomNode: TAddCustomNode;
|
||||||
removeCustomNode: TRemoveCustomNode;
|
removeCustomNode: TRemoveCustomNode;
|
||||||
|
addCustomNetwork: TAddCustomNetwork;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -58,40 +62,47 @@ export default class Header extends Component<Props, State> {
|
|||||||
node,
|
node,
|
||||||
nodeSelection,
|
nodeSelection,
|
||||||
isChangingNode,
|
isChangingNode,
|
||||||
customNodes
|
customNodes,
|
||||||
|
customNetworks
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isAddingCustomNode } = this.state;
|
const { isAddingCustomNode } = this.state;
|
||||||
const selectedLanguage = languageSelection;
|
const selectedLanguage = languageSelection;
|
||||||
const selectedNetwork = NETWORKS[node.network];
|
const selectedNetwork = getNetworkConfigFromId(
|
||||||
|
node.network,
|
||||||
|
customNetworks
|
||||||
|
);
|
||||||
const LanguageDropDown = Dropdown as new () => Dropdown<
|
const LanguageDropDown = Dropdown as new () => Dropdown<
|
||||||
typeof selectedLanguage
|
typeof selectedLanguage
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const nodeOptions = Object.keys(NODES)
|
const nodeOptions = Object.keys(NODES)
|
||||||
.map(key => {
|
.map(key => {
|
||||||
|
const n = NODES[key];
|
||||||
|
const network = getNetworkConfigFromId(n.network, customNetworks);
|
||||||
return {
|
return {
|
||||||
value: key,
|
value: key,
|
||||||
name: (
|
name: (
|
||||||
<span>
|
<span>
|
||||||
{NODES[key].network} <small>({NODES[key].service})</small>
|
{network && network.name} <small>({n.service})</small>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
color: NETWORKS[NODES[key].network].color,
|
color: network && network.color,
|
||||||
hidden: NODES[key].hidden
|
hidden: n.hidden
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.concat(
|
.concat(
|
||||||
customNodes.map(customNode => {
|
customNodes.map(cn => {
|
||||||
|
const network = getNetworkConfigFromId(cn.network, customNetworks);
|
||||||
return {
|
return {
|
||||||
value: makeCustomNodeId(customNode),
|
value: makeCustomNodeId(cn),
|
||||||
name: (
|
name: (
|
||||||
<span>
|
<span>
|
||||||
{customNode.network} - {customNode.name} <small>(custom)</small>
|
{network && network.name} - {cn.name} <small>(custom)</small>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
color: '#000',
|
color: network && network.color,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
onRemove: () => this.props.removeCustomNode(customNode)
|
onRemove: () => this.props.removeCustomNode(cn)
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -161,8 +172,8 @@ export default class Header extends Component<Props, State> {
|
|||||||
>
|
>
|
||||||
<ColorDropdown
|
<ColorDropdown
|
||||||
ariaLabel={`
|
ariaLabel={`
|
||||||
change node. current node ${node.network}
|
change node. current node is on the ${node.network} network
|
||||||
node by ${node.service}
|
provided by ${node.service}
|
||||||
`}
|
`}
|
||||||
options={nodeOptions}
|
options={nodeOptions}
|
||||||
value={nodeSelection}
|
value={nodeSelection}
|
||||||
@ -182,11 +193,14 @@ export default class Header extends Component<Props, State> {
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Navigation color={selectedNetwork.color} />
|
<Navigation color={selectedNetwork && selectedNetwork.color} />
|
||||||
|
|
||||||
{isAddingCustomNode && (
|
{isAddingCustomNode && (
|
||||||
<CustomNodeModal
|
<CustomNodeModal
|
||||||
|
customNodes={customNodes}
|
||||||
|
customNetworks={customNetworks}
|
||||||
handleAddCustomNode={this.addCustomNode}
|
handleAddCustomNode={this.addCustomNode}
|
||||||
|
handleAddCustomNetwork={this.props.addCustomNetwork}
|
||||||
handleClose={this.closeCustomNodeModal}
|
handleClose={this.closeCustomNodeModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -79,6 +79,12 @@ export interface NetworkConfig {
|
|||||||
contracts: NetworkContract[] | null;
|
contracts: NetworkContract[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CustomNetworkConfig {
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
chainId: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NodeConfig {
|
export interface NodeConfig {
|
||||||
network: string;
|
network: string;
|
||||||
lib: RPCNode | Web3Node;
|
lib: RPCNode | Web3Node;
|
||||||
|
@ -1,40 +1,49 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
changeGasPrice as dChangeGasPrice,
|
changeGasPrice as dChangeGasPrice,
|
||||||
changeLanguage as dChangeLanguage,
|
changeLanguage as dChangeLanguage,
|
||||||
changeNodeIntent as dChangeNodeIntent,
|
changeNodeIntent as dChangeNodeIntent,
|
||||||
addCustomNode as dAddCustomNode,
|
addCustomNode as dAddCustomNode,
|
||||||
removeCustomNode as dRemoveCustomNode,
|
removeCustomNode as dRemoveCustomNode,
|
||||||
|
addCustomNetwork as dAddCustomNetwork,
|
||||||
TChangeGasPrice,
|
TChangeGasPrice,
|
||||||
TChangeLanguage,
|
TChangeLanguage,
|
||||||
TChangeNodeIntent,
|
TChangeNodeIntent,
|
||||||
TAddCustomNode,
|
TAddCustomNode,
|
||||||
TRemoveCustomNode
|
TRemoveCustomNode,
|
||||||
|
TAddCustomNetwork
|
||||||
} from 'actions/config';
|
} from 'actions/config';
|
||||||
import { AlphaAgreement, Footer, Header } from 'components';
|
import { AlphaAgreement, Footer, Header } from 'components';
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { AppState } from 'reducers';
|
import { AppState } from 'reducers';
|
||||||
import Notifications from './Notifications';
|
import Notifications from './Notifications';
|
||||||
import { NodeConfig, CustomNodeConfig } from 'config/data';
|
|
||||||
|
|
||||||
interface Props {
|
interface ReduxProps {
|
||||||
// FIXME
|
languageSelection: AppState['config']['languageSelection'];
|
||||||
children: any;
|
node: AppState['config']['node'];
|
||||||
|
nodeSelection: AppState['config']['nodeSelection'];
|
||||||
languageSelection: string;
|
isChangingNode: AppState['config']['isChangingNode'];
|
||||||
node: NodeConfig;
|
gasPriceGwei: AppState['config']['gasPriceGwei'];
|
||||||
nodeSelection: string;
|
customNodes: AppState['config']['customNodes'];
|
||||||
isChangingNode: boolean;
|
customNetworks: AppState['config']['customNetworks'];
|
||||||
gasPriceGwei: number;
|
latestBlock: AppState['config']['latestBlock'];
|
||||||
customNodes: CustomNodeConfig[];
|
}
|
||||||
latestBlock: string;
|
|
||||||
|
|
||||||
|
interface ActionProps {
|
||||||
changeLanguage: TChangeLanguage;
|
changeLanguage: TChangeLanguage;
|
||||||
changeNodeIntent: TChangeNodeIntent;
|
changeNodeIntent: TChangeNodeIntent;
|
||||||
changeGasPrice: TChangeGasPrice;
|
changeGasPrice: TChangeGasPrice;
|
||||||
addCustomNode: TAddCustomNode;
|
addCustomNode: TAddCustomNode;
|
||||||
removeCustomNode: TRemoveCustomNode;
|
removeCustomNode: TRemoveCustomNode;
|
||||||
|
addCustomNetwork: TAddCustomNetwork;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
// FIXME
|
||||||
|
children: any;
|
||||||
|
} & ReduxProps &
|
||||||
|
ActionProps;
|
||||||
|
|
||||||
class TabSection extends Component<Props, {}> {
|
class TabSection extends Component<Props, {}> {
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const {
|
||||||
@ -46,13 +55,15 @@ class TabSection extends Component<Props, {}> {
|
|||||||
languageSelection,
|
languageSelection,
|
||||||
gasPriceGwei,
|
gasPriceGwei,
|
||||||
customNodes,
|
customNodes,
|
||||||
|
customNetworks,
|
||||||
latestBlock,
|
latestBlock,
|
||||||
|
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
changeNodeIntent,
|
changeNodeIntent,
|
||||||
changeGasPrice,
|
changeGasPrice,
|
||||||
addCustomNode,
|
addCustomNode,
|
||||||
removeCustomNode
|
removeCustomNode,
|
||||||
|
addCustomNetwork
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const headerProps = {
|
const headerProps = {
|
||||||
@ -62,12 +73,14 @@ class TabSection extends Component<Props, {}> {
|
|||||||
isChangingNode,
|
isChangingNode,
|
||||||
gasPriceGwei,
|
gasPriceGwei,
|
||||||
customNodes,
|
customNodes,
|
||||||
|
customNetworks,
|
||||||
|
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
changeNodeIntent,
|
changeNodeIntent,
|
||||||
changeGasPrice,
|
changeGasPrice,
|
||||||
addCustomNode,
|
addCustomNode,
|
||||||
removeCustomNode
|
removeCustomNode,
|
||||||
|
addCustomNetwork
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -84,7 +97,7 @@ class TabSection extends Component<Props, {}> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: AppState) {
|
function mapStateToProps(state: AppState): ReduxProps {
|
||||||
return {
|
return {
|
||||||
node: state.config.node,
|
node: state.config.node,
|
||||||
nodeSelection: state.config.nodeSelection,
|
nodeSelection: state.config.nodeSelection,
|
||||||
@ -92,6 +105,7 @@ function mapStateToProps(state: AppState) {
|
|||||||
languageSelection: state.config.languageSelection,
|
languageSelection: state.config.languageSelection,
|
||||||
gasPriceGwei: state.config.gasPriceGwei,
|
gasPriceGwei: state.config.gasPriceGwei,
|
||||||
customNodes: state.config.customNodes,
|
customNodes: state.config.customNodes,
|
||||||
|
customNetworks: state.config.customNetworks,
|
||||||
latestBlock: state.config.latestBlock
|
latestBlock: state.config.latestBlock
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -101,5 +115,6 @@ export default connect(mapStateToProps, {
|
|||||||
changeLanguage: dChangeLanguage,
|
changeLanguage: dChangeLanguage,
|
||||||
changeNodeIntent: dChangeNodeIntent,
|
changeNodeIntent: dChangeNodeIntent,
|
||||||
addCustomNode: dAddCustomNode,
|
addCustomNode: dAddCustomNode,
|
||||||
removeCustomNode: dRemoveCustomNode
|
removeCustomNode: dRemoveCustomNode,
|
||||||
|
addCustomNetwork: dAddCustomNetwork
|
||||||
})(TabSection);
|
})(TabSection);
|
||||||
|
@ -20,18 +20,21 @@ export interface IWithTx {
|
|||||||
showNotification: TShowNotification;
|
showNotification: TShowNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => ({
|
const mapStateToProps = (state: AppState) => {
|
||||||
wallet: state.wallet.inst,
|
const network = configSelectors.getNetworkConfig(state);
|
||||||
balance: state.wallet.balance,
|
return {
|
||||||
node: configSelectors.getNodeConfig(state),
|
wallet: state.wallet.inst,
|
||||||
nodeLib: configSelectors.getNodeLib(state),
|
balance: state.wallet.balance,
|
||||||
chainId: configSelectors.getNetworkConfig(state).chainId,
|
node: configSelectors.getNodeConfig(state),
|
||||||
networkName: configSelectors.getNetworkConfig(state).name,
|
nodeLib: configSelectors.getNodeLib(state),
|
||||||
gasPrice: toWei(
|
chainId: network ? network.chainId : 0,
|
||||||
`${configSelectors.getGasPriceGwei(state)}`,
|
networkName: network ? network.name : 'Unknown network',
|
||||||
getDecimal('gwei')
|
gasPrice: toWei(
|
||||||
)
|
`${configSelectors.getGasPriceGwei(state)}`,
|
||||||
});
|
getDecimal('gwei')
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const withTx = passedComponent =>
|
export const withTx = passedComponent =>
|
||||||
connect(mapStateToProps, {
|
connect(mapStateToProps, {
|
||||||
|
@ -198,7 +198,7 @@ export async function generateCompleteTransactionFromRawTransaction(
|
|||||||
to: toChecksumAddress(cleanHex(to)),
|
to: toChecksumAddress(cleanHex(to)),
|
||||||
value: token ? '0x00' : cleanHex(value.toString(16)),
|
value: token ? '0x00' : cleanHex(value.toString(16)),
|
||||||
data: data ? cleanHex(data) : '',
|
data: data ? cleanHex(data) : '',
|
||||||
chainId: chainId || 1
|
chainId: chainId || 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sign the transaction
|
// Sign the transaction
|
||||||
|
@ -4,23 +4,35 @@ import {
|
|||||||
ChangeNodeAction,
|
ChangeNodeAction,
|
||||||
AddCustomNodeAction,
|
AddCustomNodeAction,
|
||||||
RemoveCustomNodeAction,
|
RemoveCustomNodeAction,
|
||||||
|
AddCustomNetworkAction,
|
||||||
|
RemoveCustomNetworkAction,
|
||||||
SetLatestBlockAction,
|
SetLatestBlockAction,
|
||||||
ConfigAction
|
ConfigAction
|
||||||
} from 'actions/config';
|
} from 'actions/config';
|
||||||
import { TypeKeys } from 'actions/config/constants';
|
import { TypeKeys } from 'actions/config/constants';
|
||||||
import { NODES, NodeConfig, CustomNodeConfig } from '../config/data';
|
import {
|
||||||
|
NODES,
|
||||||
|
NETWORKS,
|
||||||
|
NodeConfig,
|
||||||
|
CustomNodeConfig,
|
||||||
|
NetworkConfig,
|
||||||
|
CustomNetworkConfig
|
||||||
|
} from '../config/data';
|
||||||
import { makeCustomNodeId } from 'utils/node';
|
import { makeCustomNodeId } from 'utils/node';
|
||||||
|
import { makeCustomNetworkId } from 'utils/network';
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
// FIXME
|
// FIXME
|
||||||
languageSelection: string;
|
languageSelection: string;
|
||||||
nodeSelection: string;
|
nodeSelection: string;
|
||||||
node: NodeConfig;
|
node: NodeConfig;
|
||||||
|
network: NetworkConfig;
|
||||||
isChangingNode: boolean;
|
isChangingNode: boolean;
|
||||||
gasPriceGwei: number;
|
gasPriceGwei: number;
|
||||||
offline: boolean;
|
offline: boolean;
|
||||||
forceOffline: boolean;
|
forceOffline: boolean;
|
||||||
customNodes: CustomNodeConfig[];
|
customNodes: CustomNodeConfig[];
|
||||||
|
customNetworks: CustomNetworkConfig[];
|
||||||
latestBlock: string;
|
latestBlock: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,11 +41,13 @@ export const INITIAL_STATE: State = {
|
|||||||
languageSelection: 'en',
|
languageSelection: 'en',
|
||||||
nodeSelection: defaultNode,
|
nodeSelection: defaultNode,
|
||||||
node: NODES[defaultNode],
|
node: NODES[defaultNode],
|
||||||
|
network: NETWORKS[NODES[defaultNode].network],
|
||||||
isChangingNode: false,
|
isChangingNode: false,
|
||||||
gasPriceGwei: 21,
|
gasPriceGwei: 21,
|
||||||
offline: false,
|
offline: false,
|
||||||
forceOffline: false,
|
forceOffline: false,
|
||||||
customNodes: [],
|
customNodes: [],
|
||||||
|
customNetworks: [],
|
||||||
latestBlock: '???'
|
latestBlock: '???'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,9 +96,13 @@ function forceOffline(state: State): State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addCustomNode(state: State, action: AddCustomNodeAction): State {
|
function addCustomNode(state: State, action: AddCustomNodeAction): State {
|
||||||
|
const newId = makeCustomNodeId(action.payload);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
customNodes: [...state.customNodes, action.payload]
|
customNodes: [
|
||||||
|
...state.customNodes.filter(node => makeCustomNodeId(node) !== newId),
|
||||||
|
action.payload
|
||||||
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +116,29 @@ function removeCustomNode(state: State, action: RemoveCustomNodeAction): State {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCustomNetwork(state: State, action: AddCustomNetworkAction): State {
|
||||||
|
const newId = makeCustomNetworkId(action.payload);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
customNetworks: [
|
||||||
|
...state.customNetworks.filter(
|
||||||
|
node => makeCustomNetworkId(node) !== newId
|
||||||
|
),
|
||||||
|
action.payload
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCustomNetwork(
|
||||||
|
state: State,
|
||||||
|
action: RemoveCustomNetworkAction
|
||||||
|
): State {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
customNetworks: state.customNetworks.filter(cn => cn !== action.payload)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function setLatestBlock(state: State, action: SetLatestBlockAction): State {
|
function setLatestBlock(state: State, action: SetLatestBlockAction): State {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -126,6 +167,10 @@ export function config(
|
|||||||
return addCustomNode(state, action);
|
return addCustomNode(state, action);
|
||||||
case TypeKeys.CONFIG_REMOVE_CUSTOM_NODE:
|
case TypeKeys.CONFIG_REMOVE_CUSTOM_NODE:
|
||||||
return removeCustomNode(state, action);
|
return removeCustomNode(state, action);
|
||||||
|
case TypeKeys.CONFIG_ADD_CUSTOM_NETWORK:
|
||||||
|
return addCustomNetwork(state, action);
|
||||||
|
case TypeKeys.CONFIG_REMOVE_CUSTOM_NETWORK:
|
||||||
|
return removeCustomNetwork(state, action);
|
||||||
case TypeKeys.CONFIG_SET_LATEST_BLOCK:
|
case TypeKeys.CONFIG_SET_LATEST_BLOCK:
|
||||||
return setLatestBlock(state, action);
|
return setLatestBlock(state, action);
|
||||||
default:
|
default:
|
||||||
|
@ -16,10 +16,12 @@ import {
|
|||||||
getCustomNodeConfigFromId,
|
getCustomNodeConfigFromId,
|
||||||
makeNodeConfigFromCustomConfig
|
makeNodeConfigFromCustomConfig
|
||||||
} from 'utils/node';
|
} from 'utils/node';
|
||||||
|
import { makeCustomNetworkId } from 'utils/network';
|
||||||
import {
|
import {
|
||||||
getNode,
|
getNode,
|
||||||
getNodeConfig,
|
getNodeConfig,
|
||||||
getCustomNodeConfigs,
|
getCustomNodeConfigs,
|
||||||
|
getCustomNetworkConfigs,
|
||||||
getOffline,
|
getOffline,
|
||||||
getForceOffline
|
getForceOffline
|
||||||
} from 'selectors/config';
|
} from 'selectors/config';
|
||||||
@ -30,6 +32,7 @@ import {
|
|||||||
changeNode,
|
changeNode,
|
||||||
changeNodeIntent,
|
changeNodeIntent,
|
||||||
setLatestBlock,
|
setLatestBlock,
|
||||||
|
removeCustomNetwork,
|
||||||
AddCustomNodeAction,
|
AddCustomNodeAction,
|
||||||
ChangeNodeIntentAction
|
ChangeNodeIntentAction
|
||||||
} from 'actions/config';
|
} from 'actions/config';
|
||||||
@ -188,6 +191,22 @@ export function* switchToNewNode(action: AddCustomNodeAction): SagaIterator {
|
|||||||
yield put(changeNodeIntent(nodeId));
|
yield put(changeNodeIntent(nodeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there are any orphaned custom networks, purge them
|
||||||
|
export function* cleanCustomNetworks(): SagaIterator {
|
||||||
|
const customNodes = yield select(getCustomNodeConfigs);
|
||||||
|
const customNetworks = yield select(getCustomNetworkConfigs);
|
||||||
|
const networksInUse = customNodes.reduce((prev, conf) => {
|
||||||
|
prev[conf.network] = true;
|
||||||
|
return prev;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
for (const net of customNetworks) {
|
||||||
|
if (!networksInUse[makeCustomNetworkId(net)]) {
|
||||||
|
yield put(removeCustomNetwork(net));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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* unsetWeb3Node(action): SagaIterator {
|
export function* unsetWeb3Node(action): SagaIterator {
|
||||||
const node = yield select(getNode);
|
const node = yield select(getNode);
|
||||||
@ -230,6 +249,7 @@ export default function* configSaga(): SagaIterator {
|
|||||||
yield takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent);
|
yield takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent);
|
||||||
yield takeEvery(TypeKeys.CONFIG_LANGUAGE_CHANGE, reload);
|
yield takeEvery(TypeKeys.CONFIG_LANGUAGE_CHANGE, reload);
|
||||||
yield takeEvery(TypeKeys.CONFIG_ADD_CUSTOM_NODE, switchToNewNode);
|
yield takeEvery(TypeKeys.CONFIG_ADD_CUSTOM_NODE, switchToNewNode);
|
||||||
|
yield takeEvery(TypeKeys.CONFIG_REMOVE_CUSTOM_NODE, cleanCustomNetworks);
|
||||||
yield takeEvery(WalletTypeKeys.WALLET_SET, unsetWeb3Node);
|
yield takeEvery(WalletTypeKeys.WALLET_SET, unsetWeb3Node);
|
||||||
yield takeEvery(WalletTypeKeys.WALLET_RESET, unsetWeb3Node);
|
yield takeEvery(WalletTypeKeys.WALLET_RESET, unsetWeb3Node);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,19 @@
|
|||||||
@import "common/sass/mixins";
|
@import "common/sass/mixins";
|
||||||
@import "~bootstrap-sass/assets/stylesheets/bootstrap/alerts";
|
@import "~bootstrap-sass/assets/stylesheets/bootstrap/alerts";
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #FFF;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #FFF;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Alert icons
|
// Alert icons
|
||||||
.alert:after {
|
.alert:after {
|
||||||
content: '';
|
content: '';
|
||||||
@ -23,15 +36,6 @@
|
|||||||
@media screen and (max-width: $screen-xs) {
|
@media screen and (max-width: $screen-xs) {
|
||||||
left: 1%;
|
left: 1%;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: #FFF;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #FFF;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert,
|
.alert,
|
||||||
|
@ -4,6 +4,12 @@
|
|||||||
label {
|
label {
|
||||||
margin-bottom: $space-xs;
|
margin-bottom: $space-xs;
|
||||||
font-size: $font-size-bump-more;
|
font-size: $font-size-bump-more;
|
||||||
|
|
||||||
|
&.is-required:after {
|
||||||
|
content: '*';
|
||||||
|
padding-left: 2px;
|
||||||
|
color: $brand-warning;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label + .form-control,
|
label + .form-control,
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
NetworkConfig,
|
NetworkConfig,
|
||||||
NetworkContract,
|
NetworkContract,
|
||||||
NETWORKS,
|
|
||||||
NodeConfig,
|
NodeConfig,
|
||||||
CustomNodeConfig
|
CustomNodeConfig,
|
||||||
|
CustomNetworkConfig
|
||||||
} from 'config/data';
|
} from 'config/data';
|
||||||
import { INode } from 'libs/nodes/INode';
|
import { INode } from 'libs/nodes/INode';
|
||||||
import { AppState } from 'reducers';
|
import { AppState } from 'reducers';
|
||||||
|
import { getNetworkConfigFromId } from 'utils/network';
|
||||||
|
|
||||||
export function getNode(state: AppState): string {
|
export function getNode(state: AppState): string {
|
||||||
return state.config.nodeSelection;
|
return state.config.nodeSelection;
|
||||||
@ -20,12 +21,16 @@ export function getNodeLib(state: AppState): INode {
|
|||||||
return getNodeConfig(state).lib;
|
return getNodeConfig(state).lib;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNetworkConfig(state: AppState): NetworkConfig {
|
export function getNetworkConfig(state: AppState): NetworkConfig | undefined {
|
||||||
return NETWORKS[getNodeConfig(state).network];
|
return getNetworkConfigFromId(
|
||||||
|
getNodeConfig(state).network,
|
||||||
|
getCustomNetworkConfigs(state)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNetworkContracts(state: AppState): NetworkContract[] | null {
|
export function getNetworkContracts(state: AppState): NetworkContract[] | null {
|
||||||
return getNetworkConfig(state).contracts;
|
const network = getNetworkConfig(state);
|
||||||
|
return network ? network.contracts : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGasPriceGwei(state: AppState): number {
|
export function getGasPriceGwei(state: AppState): number {
|
||||||
@ -40,6 +45,12 @@ export function getCustomNodeConfigs(state: AppState): CustomNodeConfig[] {
|
|||||||
return state.config.customNodes;
|
return state.config.customNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCustomNetworkConfigs(
|
||||||
|
state: AppState
|
||||||
|
): CustomNetworkConfig[] {
|
||||||
|
return state.config.customNetworks;
|
||||||
|
}
|
||||||
|
|
||||||
export function getOffline(state: AppState): boolean {
|
export function getOffline(state: AppState): boolean {
|
||||||
return state.config.offline;
|
return state.config.offline;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ export type MergedToken = Token & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function getTokens(state: AppState): MergedToken[] {
|
export function getTokens(state: AppState): MergedToken[] {
|
||||||
const tokens: Token[] = getNetworkConfig(state).tokens;
|
const network = getNetworkConfig(state);
|
||||||
|
const tokens: Token[] = network ? network.tokens : [];
|
||||||
return tokens.concat(
|
return tokens.concat(
|
||||||
state.customTokens.map((token: Token) => {
|
state.customTokens.map((token: Token) => {
|
||||||
const mergedToken = { ...token, custom: true };
|
const mergedToken = { ...token, custom: true };
|
||||||
|
@ -109,7 +109,8 @@ const configureStore = () => {
|
|||||||
gasPriceGwei: state.config.gasPriceGwei,
|
gasPriceGwei: state.config.gasPriceGwei,
|
||||||
nodeSelection: state.config.nodeSelection,
|
nodeSelection: state.config.nodeSelection,
|
||||||
languageSelection: state.config.languageSelection,
|
languageSelection: state.config.languageSelection,
|
||||||
customNodes: state.config.customNodes
|
customNodes: state.config.customNodes,
|
||||||
|
customNetworks: state.config.customNetworks
|
||||||
},
|
},
|
||||||
swap: { ...state.swap, bityRates: {} },
|
swap: { ...state.swap, bityRates: {} },
|
||||||
customTokens: state.customTokens
|
customTokens: state.customTokens
|
||||||
|
30
common/utils/network.ts
Normal file
30
common/utils/network.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { NETWORKS, NetworkConfig, CustomNetworkConfig } from 'config/data';
|
||||||
|
|
||||||
|
export function makeCustomNetworkId(config: CustomNetworkConfig): string {
|
||||||
|
return config.chainId ? `${config.chainId}` : `${config.name}:${config.unit}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeNetworkConfigFromCustomConfig(
|
||||||
|
config: CustomNetworkConfig
|
||||||
|
): NetworkConfig {
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
color: '#000',
|
||||||
|
tokens: [],
|
||||||
|
contracts: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNetworkConfigFromId(
|
||||||
|
id: string,
|
||||||
|
configs: CustomNetworkConfig[]
|
||||||
|
): NetworkConfig | undefined {
|
||||||
|
if (NETWORKS[id]) {
|
||||||
|
return NETWORKS[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
const customConfig = configs.find(conf => makeCustomNetworkId(conf) === id);
|
||||||
|
if (customConfig) {
|
||||||
|
return makeNetworkConfigFromCustomConfig(customConfig);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user