Split out nodes and networks into their own reducers
This commit is contained in:
parent
63ed9c1871
commit
583654f94c
|
@ -32,7 +32,7 @@ export function changeNode(
|
||||||
): interfaces.ChangeNodeAction {
|
): interfaces.ChangeNodeAction {
|
||||||
return {
|
return {
|
||||||
type: TypeKeys.CONFIG_NODE_CHANGE,
|
type: TypeKeys.CONFIG_NODE_CHANGE,
|
||||||
payload: { nodeSelection, node, network }
|
payload: { nodeSelection, nodeName, networkName }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { TypeKeys } from './constants';
|
import { TypeKeys } from './constants';
|
||||||
import { NodeConfig, CustomNodeConfig, NetworkConfig, CustomNetworkConfig } from 'config';
|
import { CustomNodeConfig } from 'config';
|
||||||
|
import { CustomNetworkConfig } from 'reducers/config/networks/typings';
|
||||||
|
|
||||||
/*** Toggle Offline ***/
|
/*** Toggle Offline ***/
|
||||||
export interface ToggleOfflineAction {
|
export interface ToggleOfflineAction {
|
||||||
|
@ -21,9 +22,8 @@ export interface ChangeNodeAction {
|
||||||
type: TypeKeys.CONFIG_NODE_CHANGE;
|
type: TypeKeys.CONFIG_NODE_CHANGE;
|
||||||
// FIXME $keyof?
|
// FIXME $keyof?
|
||||||
payload: {
|
payload: {
|
||||||
nodeSelection: string;
|
nodeName: string;
|
||||||
node: NodeConfig;
|
networkName: string;
|
||||||
network: NetworkConfig;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,25 +41,25 @@ export interface ChangeNodeIntentAction {
|
||||||
/*** Add Custom Node ***/
|
/*** Add Custom Node ***/
|
||||||
export interface AddCustomNodeAction {
|
export interface AddCustomNodeAction {
|
||||||
type: TypeKeys.CONFIG_ADD_CUSTOM_NODE;
|
type: TypeKeys.CONFIG_ADD_CUSTOM_NODE;
|
||||||
payload: CustomNodeConfig;
|
payload: { id: string; config: CustomNodeConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Remove Custom Node ***/
|
/*** Remove Custom Node ***/
|
||||||
export interface RemoveCustomNodeAction {
|
export interface RemoveCustomNodeAction {
|
||||||
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NODE;
|
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NODE;
|
||||||
payload: CustomNodeConfig;
|
payload: { id: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Add Custom Network ***/
|
/*** Add Custom Network ***/
|
||||||
export interface AddCustomNetworkAction {
|
export interface AddCustomNetworkAction {
|
||||||
type: TypeKeys.CONFIG_ADD_CUSTOM_NETWORK;
|
type: TypeKeys.CONFIG_ADD_CUSTOM_NETWORK;
|
||||||
payload: CustomNetworkConfig;
|
payload: { id: string; config: CustomNetworkConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Remove Custom Network ***/
|
/*** Remove Custom Network ***/
|
||||||
export interface RemoveCustomNetworkAction {
|
export interface RemoveCustomNetworkAction {
|
||||||
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NETWORK;
|
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NETWORK;
|
||||||
payload: CustomNetworkConfig;
|
payload: { id: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Set Latest Block ***/
|
/*** Set Latest Block ***/
|
||||||
|
@ -73,17 +73,18 @@ export interface Web3UnsetNodeAction {
|
||||||
type: TypeKeys.CONFIG_NODE_WEB3_UNSET;
|
type: TypeKeys.CONFIG_NODE_WEB3_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Union Type ***/
|
export type CustomNetworkAction = AddCustomNetworkAction | RemoveCustomNetworkAction;
|
||||||
export type ConfigAction =
|
|
||||||
| ChangeNodeAction
|
export type CustomNodeAction = AddCustomNodeAction | RemoveCustomNodeAction;
|
||||||
|
|
||||||
|
export type NodeAction = ChangeNodeAction | ChangeNodeIntentAction | Web3UnsetNodeAction;
|
||||||
|
|
||||||
|
export type MetaAction =
|
||||||
| ChangeLanguageAction
|
| ChangeLanguageAction
|
||||||
| ToggleOfflineAction
|
| ToggleOfflineAction
|
||||||
| ToggleAutoGasLimitAction
|
| ToggleAutoGasLimitAction
|
||||||
| PollOfflineStatus
|
| PollOfflineStatus
|
||||||
| ChangeNodeIntentAction
|
| SetLatestBlockAction;
|
||||||
| AddCustomNodeAction
|
|
||||||
| RemoveCustomNodeAction
|
/*** Union Type ***/
|
||||||
| AddCustomNetworkAction
|
export type ConfigAction = CustomNetworkAction | CustomNodeAction | NodeAction | MetaAction;
|
||||||
| RemoveCustomNetworkAction
|
|
||||||
| SetLatestBlockAction
|
|
||||||
| Web3UnsetNodeAction;
|
|
||||||
|
|
|
@ -1,176 +0,0 @@
|
||||||
import { EtherscanNode, InfuraNode, RPCNode, Web3Node } from 'libs/nodes';
|
|
||||||
import { networkIdToName } from 'libs/values';
|
|
||||||
|
|
||||||
export interface CustomNodeConfig {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
port: number;
|
|
||||||
network: string;
|
|
||||||
auth?: {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeConfig {
|
|
||||||
network: NetworkKeys;
|
|
||||||
lib: RPCNode | Web3Node;
|
|
||||||
service: string;
|
|
||||||
estimateGas?: boolean;
|
|
||||||
hidden?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NodeName {
|
|
||||||
ETH_MEW = 'eth_mew',
|
|
||||||
ETH_MYCRYPTO = 'eth_mycrypto',
|
|
||||||
ETH_ETHSCAN = 'eth_ethscan',
|
|
||||||
ETH_INFURA = 'eth_infura',
|
|
||||||
ROP_MEW = 'rop_mew',
|
|
||||||
ROP_INFURA = 'rop_infura',
|
|
||||||
KOV_ETHSCAN = 'kov_ethscan',
|
|
||||||
RIN_ETHSCAN = 'rin_ethscan',
|
|
||||||
RIN_INFURA = 'rin_infura',
|
|
||||||
ETC_EPOOL = 'etc_epool',
|
|
||||||
UBQ = 'ubq',
|
|
||||||
EXP_TECH = 'exp_tech'
|
|
||||||
}
|
|
||||||
|
|
||||||
type NonWeb3NodeConfigs = { [key in NodeName]: NodeConfig };
|
|
||||||
|
|
||||||
interface Web3NodeConfig {
|
|
||||||
web3?: NodeConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeConfigs = NonWeb3NodeConfigs & Web3NodeConfig;
|
|
||||||
|
|
||||||
export const NODES: NodeConfigs = {
|
|
||||||
eth_mew: {
|
|
||||||
network: 'ETH',
|
|
||||||
lib: new RPCNode('https://api.myetherapi.com/eth'),
|
|
||||||
service: 'MyEtherWallet',
|
|
||||||
estimateGas: true
|
|
||||||
},
|
|
||||||
eth_mycrypto: {
|
|
||||||
network: 'ETH',
|
|
||||||
lib: new RPCNode('https://api.mycryptoapi.com/eth'),
|
|
||||||
service: 'MyCrypto',
|
|
||||||
estimateGas: true
|
|
||||||
},
|
|
||||||
eth_ethscan: {
|
|
||||||
network: 'ETH',
|
|
||||||
service: 'Etherscan.io',
|
|
||||||
lib: new EtherscanNode('https://api.etherscan.io/api'),
|
|
||||||
estimateGas: false
|
|
||||||
},
|
|
||||||
eth_infura: {
|
|
||||||
network: 'ETH',
|
|
||||||
service: 'infura.io',
|
|
||||||
lib: new InfuraNode('https://mainnet.infura.io/mew'),
|
|
||||||
estimateGas: false
|
|
||||||
},
|
|
||||||
rop_mew: {
|
|
||||||
network: 'Ropsten',
|
|
||||||
service: 'MyEtherWallet',
|
|
||||||
lib: new RPCNode('https://api.myetherapi.com/rop'),
|
|
||||||
estimateGas: false
|
|
||||||
},
|
|
||||||
rop_infura: {
|
|
||||||
network: 'Ropsten',
|
|
||||||
service: 'infura.io',
|
|
||||||
lib: new InfuraNode('https://ropsten.infura.io/mew'),
|
|
||||||
estimateGas: false
|
|
||||||
},
|
|
||||||
kov_ethscan: {
|
|
||||||
network: 'Kovan',
|
|
||||||
service: 'Etherscan.io',
|
|
||||||
lib: new EtherscanNode('https://kovan.etherscan.io/api'),
|
|
||||||
estimateGas: false
|
|
||||||
},
|
|
||||||
rin_ethscan: {
|
|
||||||
network: 'Rinkeby',
|
|
||||||
service: 'Etherscan.io',
|
|
||||||
lib: new EtherscanNode('https://rinkeby.etherscan.io/api'),
|
|
||||||
estimateGas: false
|
|
||||||
},
|
|
||||||
rin_infura: {
|
|
||||||
network: 'Rinkeby',
|
|
||||||
service: 'infura.io',
|
|
||||||
lib: new InfuraNode('https://rinkeby.infura.io/mew'),
|
|
||||||
estimateGas: false
|
|
||||||
},
|
|
||||||
etc_epool: {
|
|
||||||
network: 'ETC',
|
|
||||||
service: 'Epool.io',
|
|
||||||
lib: new RPCNode('https://mewapi.epool.io'),
|
|
||||||
estimateGas: false
|
|
||||||
},
|
|
||||||
ubq: {
|
|
||||||
network: 'UBQ',
|
|
||||||
service: 'ubiqscan.io',
|
|
||||||
lib: new RPCNode('https://pyrus2.ubiqscan.io'),
|
|
||||||
estimateGas: true
|
|
||||||
},
|
|
||||||
exp_tech: {
|
|
||||||
network: 'EXP',
|
|
||||||
service: 'Expanse.tech',
|
|
||||||
lib: new RPCNode('https://node.expanse.tech/'),
|
|
||||||
estimateGas: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Web3NodeInfo {
|
|
||||||
networkId: string;
|
|
||||||
lib: Web3Node;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setupWeb3Node(): Promise<Web3NodeInfo> {
|
|
||||||
const { web3 } = window as any;
|
|
||||||
|
|
||||||
if (!web3 || !web3.currentProvider || !web3.currentProvider.sendAsync) {
|
|
||||||
throw new Error(
|
|
||||||
'Web3 not found. Please check that MetaMask is installed, or that MyEtherWallet is open in Mist.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const lib = new Web3Node();
|
|
||||||
const networkId = await lib.getNetVersion();
|
|
||||||
const accounts = await lib.getAccounts();
|
|
||||||
|
|
||||||
if (!accounts.length) {
|
|
||||||
throw new Error('No accounts found in MetaMask / Mist.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkId === 'loading') {
|
|
||||||
throw new Error('MetaMask / Mist is still loading. Please refresh the page and try again.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { networkId, lib };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isWeb3NodeAvailable(): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
await setupWeb3Node();
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Web3Service = 'MetaMask / Mist';
|
|
||||||
|
|
||||||
export interface NodeConfigOverride extends NodeConfig {
|
|
||||||
network: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initWeb3Node(): Promise<void> {
|
|
||||||
const { networkId, lib } = await setupWeb3Node();
|
|
||||||
const web3: NodeConfigOverride = {
|
|
||||||
network: networkIdToName(networkId),
|
|
||||||
service: Web3Service,
|
|
||||||
lib,
|
|
||||||
estimateGas: false,
|
|
||||||
hidden: true
|
|
||||||
};
|
|
||||||
|
|
||||||
NODES.web3 = web3;
|
|
||||||
}
|
|
|
@ -1,36 +1,13 @@
|
||||||
import {
|
import { ChangeLanguageAction, SetLatestBlockAction, ConfigAction } from 'actions/config';
|
||||||
ChangeLanguageAction,
|
|
||||||
ChangeNodeAction,
|
|
||||||
AddCustomNodeAction,
|
|
||||||
RemoveCustomNodeAction,
|
|
||||||
AddCustomNetworkAction,
|
|
||||||
RemoveCustomNetworkAction,
|
|
||||||
SetLatestBlockAction,
|
|
||||||
ConfigAction
|
|
||||||
} from 'actions/config';
|
|
||||||
import { TypeKeys } from 'actions/config/constants';
|
import { TypeKeys } from 'actions/config/constants';
|
||||||
import {
|
|
||||||
NODES,
|
|
||||||
NETWORKS,
|
|
||||||
NodeConfig,
|
|
||||||
CustomNodeConfig,
|
|
||||||
NetworkConfig,
|
|
||||||
CustomNetworkConfig
|
|
||||||
} from 'config';
|
|
||||||
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;
|
|
||||||
network: NetworkConfig;
|
|
||||||
isChangingNode: boolean;
|
isChangingNode: boolean;
|
||||||
offline: boolean;
|
offline: boolean;
|
||||||
autoGasLimit: boolean;
|
autoGasLimit: boolean;
|
||||||
customNodes: CustomNodeConfig[];
|
|
||||||
customNetworks: CustomNetworkConfig[];
|
|
||||||
latestBlock: string;
|
latestBlock: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,23 +32,6 @@ function changeLanguage(state: State, action: ChangeLanguageAction): State {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeNode(state: State, action: ChangeNodeAction): State {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
nodeSelection: action.payload.nodeSelection,
|
|
||||||
node: action.payload.node,
|
|
||||||
network: action.payload.network,
|
|
||||||
isChangingNode: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeNodeIntent(state: State): State {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isChangingNode: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleOffline(state: State): State {
|
function toggleOffline(state: State): State {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -86,44 +46,6 @@ function toggleAutoGasLimitEstimation(state: State): State {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCustomNode(state: State, action: AddCustomNodeAction): State {
|
|
||||||
const newId = makeCustomNodeId(action.payload);
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
customNodes: [
|
|
||||||
...state.customNodes.filter(node => makeCustomNodeId(node) !== newId),
|
|
||||||
action.payload
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeCustomNode(state: State, action: RemoveCustomNodeAction): State {
|
|
||||||
const id = makeCustomNodeId(action.payload);
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
customNodes: state.customNodes.filter(cn => cn !== action.payload),
|
|
||||||
nodeSelection: id === state.nodeSelection ? defaultNode : state.nodeSelection
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
|
@ -135,22 +57,12 @@ export function config(state: State = INITIAL_STATE, action: ConfigAction): Stat
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case TypeKeys.CONFIG_LANGUAGE_CHANGE:
|
case TypeKeys.CONFIG_LANGUAGE_CHANGE:
|
||||||
return changeLanguage(state, action);
|
return changeLanguage(state, action);
|
||||||
case TypeKeys.CONFIG_NODE_CHANGE:
|
|
||||||
return changeNode(state, action);
|
|
||||||
case TypeKeys.CONFIG_NODE_CHANGE_INTENT:
|
|
||||||
return changeNodeIntent(state);
|
|
||||||
case TypeKeys.CONFIG_TOGGLE_OFFLINE:
|
case TypeKeys.CONFIG_TOGGLE_OFFLINE:
|
||||||
return toggleOffline(state);
|
return toggleOffline(state);
|
||||||
case TypeKeys.CONFIG_TOGGLE_AUTO_GAS_LIMIT:
|
case TypeKeys.CONFIG_TOGGLE_AUTO_GAS_LIMIT:
|
||||||
return toggleAutoGasLimitEstimation(state);
|
return toggleAutoGasLimitEstimation(state);
|
||||||
case TypeKeys.CONFIG_ADD_CUSTOM_NODE:
|
|
||||||
return addCustomNode(state, action);
|
|
||||||
case TypeKeys.CONFIG_REMOVE_CUSTOM_NODE:
|
|
||||||
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:
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
AddCustomNetworkAction,
|
||||||
|
RemoveCustomNetworkAction,
|
||||||
|
CustomNetworkAction,
|
||||||
|
TypeKeys
|
||||||
|
} from 'actions/config';
|
||||||
|
import { CustomNetworkConfig } from 'reducers/config/networks/typings';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
[customNetworkId: string]: CustomNetworkConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCustomNetwork = (state: State, { payload }: AddCustomNetworkAction): State => ({
|
||||||
|
...state,
|
||||||
|
[payload.id]: payload.config
|
||||||
|
});
|
||||||
|
|
||||||
|
function removeCustomNetwork(state: State, { payload }: RemoveCustomNetworkAction): State {
|
||||||
|
const stateCopy = { ...state };
|
||||||
|
Reflect.deleteProperty(stateCopy, payload.id);
|
||||||
|
return stateCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const customNetworks = (state: State = {}, action: CustomNetworkAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case TypeKeys.CONFIG_ADD_CUSTOM_NETWORK:
|
||||||
|
return addCustomNetwork(state, action);
|
||||||
|
case TypeKeys.CONFIG_REMOVE_CUSTOM_NETWORK:
|
||||||
|
return removeCustomNetwork(state, action);
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -12,13 +12,15 @@ import {
|
||||||
import {
|
import {
|
||||||
NetworkConfig,
|
NetworkConfig,
|
||||||
BlockExplorerConfig,
|
BlockExplorerConfig,
|
||||||
DefaultNetworkKeys
|
DefaultNetworkNames
|
||||||
} from 'reducers/config/networks/typings';
|
} from 'reducers/config/networks/typings';
|
||||||
|
import { ConfigAction } from 'actions/config';
|
||||||
|
|
||||||
export type State = { [key in DefaultNetworkKeys]: NetworkConfig };
|
export type State = { [key in DefaultNetworkNames]: NetworkConfig };
|
||||||
|
|
||||||
// Must be a website that follows the ethplorer convention of /tx/[hash] and
|
// Must be a website that follows the ethplorer convention of /tx/[hash] and
|
||||||
// address/[address] to generate the correct functions.
|
// address/[address] to generate the correct functions.
|
||||||
|
// TODO: put this in utils / libs
|
||||||
function makeExplorer(origin: string): BlockExplorerConfig {
|
function makeExplorer(origin: string): BlockExplorerConfig {
|
||||||
return {
|
return {
|
||||||
origin,
|
origin,
|
||||||
|
@ -27,7 +29,7 @@ function makeExplorer(origin: string): BlockExplorerConfig {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_STATE = {
|
const INITIAL_STATE: State = {
|
||||||
ETH: {
|
ETH: {
|
||||||
name: 'ETH',
|
name: 'ETH',
|
||||||
unit: 'ETH',
|
unit: 'ETH',
|
||||||
|
@ -134,3 +136,10 @@ const INITIAL_STATE = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defaultNetworks = (state: State = INITIAL_STATE, action: ConfigAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { customNetworks, State as CustomNetworksState } from './customNetworks';
|
||||||
|
import { defaultNetworks, State as DefaultNetworksState } from './defaultNetworks';
|
||||||
|
import { selectedNetwork, State as SelectedNetworkState } from './selectedNetwork';
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
customNetworks: CustomNetworksState;
|
||||||
|
defaultNetworks: DefaultNetworksState;
|
||||||
|
selectedNetwork: SelectedNetworkState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const networks = combineReducers<State>({
|
||||||
|
customNetworks,
|
||||||
|
defaultNetworks,
|
||||||
|
selectedNetwork
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { NodeAction, TypeKeys, ChangeNodeAction } from 'actions/config';
|
||||||
|
import { DefaultNetworkNames } from 'reducers/config/networks/typings';
|
||||||
|
import { INITIAL_STATE as INITIAL_NODE_STATE } from '../nodes/selectedNode'; // could probably consolidate this in the index file of 'nodes' to make it easier to import
|
||||||
|
import { INITIAL_STATE as INITIAL_DEFAULT_NODE_STATE } from '../nodes/defaultNodes';
|
||||||
|
import { NonWeb3NodeConfigs } from 'reducers/config/nodes/typings';
|
||||||
|
|
||||||
|
const initalNode =
|
||||||
|
INITIAL_DEFAULT_NODE_STATE[INITIAL_NODE_STATE.nodeName as keyof NonWeb3NodeConfigs];
|
||||||
|
|
||||||
|
export type State = string | DefaultNetworkNames;
|
||||||
|
const INITIAL_STATE: State = initalNode.networkName;
|
||||||
|
|
||||||
|
const handleNodeChange = (_: State, { payload }: ChangeNodeAction) => payload.networkName;
|
||||||
|
|
||||||
|
export const selectedNetwork = (state: State = INITIAL_STATE, action: NodeAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case TypeKeys.CONFIG_NODE_CHANGE:
|
||||||
|
return handleNodeChange(state, action);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import { DPath } from 'config/dpaths';
|
import { DPath } from 'config/dpaths';
|
||||||
|
|
||||||
export type DefaultNetworkKeys = 'ETH' | 'Ropsten' | 'Kovan' | 'Rinkeby' | 'ETC' | 'UBQ' | 'EXP';
|
export type DefaultNetworkNames = 'ETH' | 'Ropsten' | 'Kovan' | 'Rinkeby' | 'ETC' | 'UBQ' | 'EXP';
|
||||||
|
|
||||||
export interface BlockExplorerConfig {
|
export interface BlockExplorerConfig {
|
||||||
origin: string;
|
origin: string;
|
||||||
|
@ -16,7 +16,7 @@ export interface Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkContract {
|
export interface NetworkContract {
|
||||||
name: DefaultNetworkKeys;
|
name: DefaultNetworkNames;
|
||||||
address?: string;
|
address?: string;
|
||||||
abi: string;
|
abi: string;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ export interface DPathFormats {
|
||||||
|
|
||||||
export interface NetworkConfig {
|
export interface NetworkConfig {
|
||||||
// TODO really try not to allow strings due to custom networks
|
// TODO really try not to allow strings due to custom networks
|
||||||
name: DefaultNetworkKeys;
|
name: DefaultNetworkNames;
|
||||||
unit: string;
|
unit: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
blockExplorer?: BlockExplorerConfig;
|
blockExplorer?: BlockExplorerConfig;
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { CustomNodeConfig } from 'reducers/config/nodes/typings';
|
||||||
|
import {
|
||||||
|
TypeKeys,
|
||||||
|
CustomNodeAction,
|
||||||
|
AddCustomNodeAction,
|
||||||
|
RemoveCustomNodeAction
|
||||||
|
} from 'actions/config';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
[customNodeId: string]: CustomNodeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCustomNode = (state: State, { payload }: AddCustomNodeAction): State => ({
|
||||||
|
...state,
|
||||||
|
[payload.id]: payload.config
|
||||||
|
});
|
||||||
|
|
||||||
|
function removeCustomNode(state: State, { payload }: RemoveCustomNodeAction): State {
|
||||||
|
const stateCopy = { ...state };
|
||||||
|
Reflect.deleteProperty(stateCopy, payload.id);
|
||||||
|
return stateCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const customNodes = (state: State = {}, action: CustomNodeAction): State => {
|
||||||
|
switch (action.type) {
|
||||||
|
case TypeKeys.CONFIG_ADD_CUSTOM_NODE:
|
||||||
|
return addCustomNode(state, action);
|
||||||
|
case TypeKeys.CONFIG_REMOVE_CUSTOM_NODE:
|
||||||
|
return removeCustomNode(state, action);
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { NonWeb3NodeConfigs, Web3NodeConfig } from 'reducers/config/nodes/typings';
|
||||||
|
import { EtherscanNode, InfuraNode, RPCNode } from 'libs/nodes';
|
||||||
|
import { ConfigAction } from 'actions/config';
|
||||||
|
|
||||||
|
export type State = NonWeb3NodeConfigs & Web3NodeConfig;
|
||||||
|
|
||||||
|
export const INITIAL_STATE: State = {
|
||||||
|
eth_mew: {
|
||||||
|
networkName: 'ETH',
|
||||||
|
lib: new RPCNode('https://api.myetherapi.com/eth'),
|
||||||
|
service: 'MyEtherWallet',
|
||||||
|
estimateGas: true
|
||||||
|
},
|
||||||
|
eth_mycrypto: {
|
||||||
|
networkName: 'ETH',
|
||||||
|
lib: new RPCNode('https://api.mycryptoapi.com/eth'),
|
||||||
|
service: 'MyCrypto',
|
||||||
|
estimateGas: true
|
||||||
|
},
|
||||||
|
eth_ethscan: {
|
||||||
|
networkName: 'ETH',
|
||||||
|
service: 'Etherscan.io',
|
||||||
|
lib: new EtherscanNode('https://api.etherscan.io/api'),
|
||||||
|
estimateGas: false
|
||||||
|
},
|
||||||
|
eth_infura: {
|
||||||
|
networkName: 'ETH',
|
||||||
|
service: 'infura.io',
|
||||||
|
lib: new InfuraNode('https://mainnet.infura.io/mew'),
|
||||||
|
estimateGas: false
|
||||||
|
},
|
||||||
|
rop_mew: {
|
||||||
|
networkName: 'Ropsten',
|
||||||
|
service: 'MyEtherWallet',
|
||||||
|
lib: new RPCNode('https://api.myetherapi.com/rop'),
|
||||||
|
estimateGas: false
|
||||||
|
},
|
||||||
|
rop_infura: {
|
||||||
|
networkName: 'Ropsten',
|
||||||
|
service: 'infura.io',
|
||||||
|
lib: new InfuraNode('https://ropsten.infura.io/mew'),
|
||||||
|
estimateGas: false
|
||||||
|
},
|
||||||
|
kov_ethscan: {
|
||||||
|
networkName: 'Kovan',
|
||||||
|
service: 'Etherscan.io',
|
||||||
|
lib: new EtherscanNode('https://kovan.etherscan.io/api'),
|
||||||
|
estimateGas: false
|
||||||
|
},
|
||||||
|
rin_ethscan: {
|
||||||
|
networkName: 'Rinkeby',
|
||||||
|
service: 'Etherscan.io',
|
||||||
|
lib: new EtherscanNode('https://rinkeby.etherscan.io/api'),
|
||||||
|
estimateGas: false
|
||||||
|
},
|
||||||
|
rin_infura: {
|
||||||
|
networkName: 'Rinkeby',
|
||||||
|
service: 'infura.io',
|
||||||
|
lib: new InfuraNode('https://rinkeby.infura.io/mew'),
|
||||||
|
estimateGas: false
|
||||||
|
},
|
||||||
|
etc_epool: {
|
||||||
|
networkName: 'ETC',
|
||||||
|
service: 'Epool.io',
|
||||||
|
lib: new RPCNode('https://mewapi.epool.io'),
|
||||||
|
estimateGas: false
|
||||||
|
},
|
||||||
|
ubq: {
|
||||||
|
networkName: 'UBQ',
|
||||||
|
service: 'ubiqscan.io',
|
||||||
|
lib: new RPCNode('https://pyrus2.ubiqscan.io'),
|
||||||
|
estimateGas: true
|
||||||
|
},
|
||||||
|
exp_tech: {
|
||||||
|
networkName: 'EXP',
|
||||||
|
service: 'Expanse.tech',
|
||||||
|
lib: new RPCNode('https://node.expanse.tech/'),
|
||||||
|
estimateGas: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultNodes = (state: State = INITIAL_STATE, action: ConfigAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { customNodes, State as CustomNodeState } from './customNodes';
|
||||||
|
import { defaultNodes, State as DefaultNodeState } from './defaultNodes';
|
||||||
|
import { selectedNode, State as SelectedNodeState } from './selectedNode';
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
customNodes: CustomNodeState;
|
||||||
|
defaultNodes: DefaultNodeState;
|
||||||
|
selectedNode: SelectedNodeState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nodes = combineReducers<State>({ customNodes, defaultNodes, selectedNode });
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { ChangeNodeAction, ChangeNodeIntentAction, NodeAction, TypeKeys } from 'actions/config';
|
||||||
|
|
||||||
|
interface NodeLoaded {
|
||||||
|
pending: false;
|
||||||
|
nodeName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NodeChangePending {
|
||||||
|
pending: true;
|
||||||
|
nodeName: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type State = NodeLoaded | NodeChangePending;
|
||||||
|
|
||||||
|
export const INITIAL_STATE: NodeLoaded = {
|
||||||
|
nodeName: 'eth_mew',
|
||||||
|
pending: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeNode = (_: State, { payload }: ChangeNodeAction): State => ({
|
||||||
|
nodeName: payload.networkName,
|
||||||
|
pending: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const changeNodeIntent = (_: State, _2: ChangeNodeIntentAction): State => ({
|
||||||
|
nodeName: null,
|
||||||
|
pending: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectedNode = (state: State = INITIAL_STATE, action: NodeAction) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case TypeKeys.CONFIG_NODE_CHANGE:
|
||||||
|
return changeNode(state, action);
|
||||||
|
case TypeKeys.CONFIG_NODE_CHANGE_INTENT:
|
||||||
|
return changeNodeIntent(state, action);
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { RPCNode, Web3Node } from 'libs/nodes';
|
||||||
|
import { networkIdToName } from 'libs/values';
|
||||||
|
import { DefaultNetworkNames } from 'reducers/config/networks/typings';
|
||||||
|
|
||||||
|
export interface CustomNodeConfig {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
port: number;
|
||||||
|
network: string;
|
||||||
|
auth?: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DefaultNodeConfig {
|
||||||
|
networkName: DefaultNetworkNames;
|
||||||
|
lib: RPCNode | Web3Node;
|
||||||
|
service: string;
|
||||||
|
estimateGas?: boolean;
|
||||||
|
hidden?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DefaultNodeName {
|
||||||
|
ETH_MEW = 'eth_mew',
|
||||||
|
ETH_MYCRYPTO = 'eth_mycrypto',
|
||||||
|
ETH_ETHSCAN = 'eth_ethscan',
|
||||||
|
ETH_INFURA = 'eth_infura',
|
||||||
|
ROP_MEW = 'rop_mew',
|
||||||
|
ROP_INFURA = 'rop_infura',
|
||||||
|
KOV_ETHSCAN = 'kov_ethscan',
|
||||||
|
RIN_ETHSCAN = 'rin_ethscan',
|
||||||
|
RIN_INFURA = 'rin_infura',
|
||||||
|
ETC_EPOOL = 'etc_epool',
|
||||||
|
UBQ = 'ubq',
|
||||||
|
EXP_TECH = 'exp_tech'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NonWeb3NodeConfigs = { [key in DefaultNodeName]: DefaultNodeConfig };
|
||||||
|
|
||||||
|
export interface Web3NodeConfig {
|
||||||
|
web3?: DefaultNodeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Put this in a saga that runs on app mount
|
||||||
|
*/
|
||||||
|
interface Web3NodeInfo {
|
||||||
|
networkId: string;
|
||||||
|
lib: Web3Node;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupWeb3Node(): Promise<Web3NodeInfo> {
|
||||||
|
const { web3 } = window as any;
|
||||||
|
|
||||||
|
if (!web3 || !web3.currentProvider || !web3.currentProvider.sendAsync) {
|
||||||
|
throw new Error(
|
||||||
|
'Web3 not found. Please check that MetaMask is installed, or that MyEtherWallet is open in Mist.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lib = new Web3Node();
|
||||||
|
const networkId = await lib.getNetVersion();
|
||||||
|
const accounts = await lib.getAccounts();
|
||||||
|
|
||||||
|
if (!accounts.length) {
|
||||||
|
throw new Error('No accounts found in MetaMask / Mist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (networkId === 'loading') {
|
||||||
|
throw new Error('MetaMask / Mist is still loading. Please refresh the page and try again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { networkId, lib };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isWeb3NodeAvailable(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await setupWeb3Node();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Web3Service = 'MetaMask / Mist';
|
||||||
|
|
||||||
|
export interface NodeConfigOverride extends DefaultNodeConfig {
|
||||||
|
networkName: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initWeb3Node(): Promise<void> {
|
||||||
|
const { networkId, lib } = await setupWeb3Node();
|
||||||
|
const web3: NodeConfigOverride = {
|
||||||
|
network: networkIdToName(networkId),
|
||||||
|
service: Web3Service,
|
||||||
|
lib,
|
||||||
|
estimateGas: false,
|
||||||
|
hidden: true
|
||||||
|
};
|
||||||
|
|
||||||
|
NODES.web3 = web3;
|
||||||
|
}
|
Loading…
Reference in New Issue