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 {
|
||||
return {
|
||||
type: TypeKeys.CONFIG_NODE_CHANGE,
|
||||
payload: { nodeSelection, node, network }
|
||||
payload: { nodeSelection, nodeName, networkName }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TypeKeys } from './constants';
|
||||
import { NodeConfig, CustomNodeConfig, NetworkConfig, CustomNetworkConfig } from 'config';
|
||||
import { CustomNodeConfig } from 'config';
|
||||
import { CustomNetworkConfig } from 'reducers/config/networks/typings';
|
||||
|
||||
/*** Toggle Offline ***/
|
||||
export interface ToggleOfflineAction {
|
||||
|
@ -21,9 +22,8 @@ export interface ChangeNodeAction {
|
|||
type: TypeKeys.CONFIG_NODE_CHANGE;
|
||||
// FIXME $keyof?
|
||||
payload: {
|
||||
nodeSelection: string;
|
||||
node: NodeConfig;
|
||||
network: NetworkConfig;
|
||||
nodeName: string;
|
||||
networkName: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -41,25 +41,25 @@ export interface ChangeNodeIntentAction {
|
|||
/*** Add Custom Node ***/
|
||||
export interface AddCustomNodeAction {
|
||||
type: TypeKeys.CONFIG_ADD_CUSTOM_NODE;
|
||||
payload: CustomNodeConfig;
|
||||
payload: { id: string; config: CustomNodeConfig };
|
||||
}
|
||||
|
||||
/*** Remove Custom Node ***/
|
||||
export interface RemoveCustomNodeAction {
|
||||
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NODE;
|
||||
payload: CustomNodeConfig;
|
||||
payload: { id: string };
|
||||
}
|
||||
|
||||
/*** Add Custom Network ***/
|
||||
export interface AddCustomNetworkAction {
|
||||
type: TypeKeys.CONFIG_ADD_CUSTOM_NETWORK;
|
||||
payload: CustomNetworkConfig;
|
||||
payload: { id: string; config: CustomNetworkConfig };
|
||||
}
|
||||
|
||||
/*** Remove Custom Network ***/
|
||||
export interface RemoveCustomNetworkAction {
|
||||
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NETWORK;
|
||||
payload: CustomNetworkConfig;
|
||||
payload: { id: string };
|
||||
}
|
||||
|
||||
/*** Set Latest Block ***/
|
||||
|
@ -73,17 +73,18 @@ export interface Web3UnsetNodeAction {
|
|||
type: TypeKeys.CONFIG_NODE_WEB3_UNSET;
|
||||
}
|
||||
|
||||
/*** Union Type ***/
|
||||
export type ConfigAction =
|
||||
| ChangeNodeAction
|
||||
export type CustomNetworkAction = AddCustomNetworkAction | RemoveCustomNetworkAction;
|
||||
|
||||
export type CustomNodeAction = AddCustomNodeAction | RemoveCustomNodeAction;
|
||||
|
||||
export type NodeAction = ChangeNodeAction | ChangeNodeIntentAction | Web3UnsetNodeAction;
|
||||
|
||||
export type MetaAction =
|
||||
| ChangeLanguageAction
|
||||
| ToggleOfflineAction
|
||||
| ToggleAutoGasLimitAction
|
||||
| PollOfflineStatus
|
||||
| ChangeNodeIntentAction
|
||||
| AddCustomNodeAction
|
||||
| RemoveCustomNodeAction
|
||||
| AddCustomNetworkAction
|
||||
| RemoveCustomNetworkAction
|
||||
| SetLatestBlockAction
|
||||
| Web3UnsetNodeAction;
|
||||
| SetLatestBlockAction;
|
||||
|
||||
/*** Union Type ***/
|
||||
export type ConfigAction = CustomNetworkAction | CustomNodeAction | NodeAction | MetaAction;
|
||||
|
|
|
@ -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 {
|
||||
ChangeLanguageAction,
|
||||
ChangeNodeAction,
|
||||
AddCustomNodeAction,
|
||||
RemoveCustomNodeAction,
|
||||
AddCustomNetworkAction,
|
||||
RemoveCustomNetworkAction,
|
||||
SetLatestBlockAction,
|
||||
ConfigAction
|
||||
} from 'actions/config';
|
||||
import { ChangeLanguageAction, SetLatestBlockAction, ConfigAction } from 'actions/config';
|
||||
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 {
|
||||
// FIXME
|
||||
languageSelection: string;
|
||||
nodeSelection: string;
|
||||
node: NodeConfig;
|
||||
network: NetworkConfig;
|
||||
isChangingNode: boolean;
|
||||
offline: boolean;
|
||||
autoGasLimit: boolean;
|
||||
customNodes: CustomNodeConfig[];
|
||||
customNetworks: CustomNetworkConfig[];
|
||||
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 {
|
||||
return {
|
||||
...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 {
|
||||
return {
|
||||
...state,
|
||||
|
@ -135,22 +57,12 @@ export function config(state: State = INITIAL_STATE, action: ConfigAction): Stat
|
|||
switch (action.type) {
|
||||
case TypeKeys.CONFIG_LANGUAGE_CHANGE:
|
||||
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:
|
||||
return toggleOffline(state);
|
||||
case TypeKeys.CONFIG_TOGGLE_AUTO_GAS_LIMIT:
|
||||
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:
|
||||
return setLatestBlock(state, action);
|
||||
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 {
|
||||
NetworkConfig,
|
||||
BlockExplorerConfig,
|
||||
DefaultNetworkKeys
|
||||
DefaultNetworkNames
|
||||
} 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
|
||||
// address/[address] to generate the correct functions.
|
||||
// TODO: put this in utils / libs
|
||||
function makeExplorer(origin: string): BlockExplorerConfig {
|
||||
return {
|
||||
origin,
|
||||
|
@ -27,7 +29,7 @@ function makeExplorer(origin: string): BlockExplorerConfig {
|
|||
};
|
||||
}
|
||||
|
||||
const INITIAL_STATE = {
|
||||
const INITIAL_STATE: State = {
|
||||
ETH: {
|
||||
name: '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';
|
||||
|
||||
export type DefaultNetworkKeys = 'ETH' | 'Ropsten' | 'Kovan' | 'Rinkeby' | 'ETC' | 'UBQ' | 'EXP';
|
||||
export type DefaultNetworkNames = 'ETH' | 'Ropsten' | 'Kovan' | 'Rinkeby' | 'ETC' | 'UBQ' | 'EXP';
|
||||
|
||||
export interface BlockExplorerConfig {
|
||||
origin: string;
|
||||
|
@ -16,7 +16,7 @@ export interface Token {
|
|||
}
|
||||
|
||||
export interface NetworkContract {
|
||||
name: DefaultNetworkKeys;
|
||||
name: DefaultNetworkNames;
|
||||
address?: string;
|
||||
abi: string;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export interface DPathFormats {
|
|||
|
||||
export interface NetworkConfig {
|
||||
// TODO really try not to allow strings due to custom networks
|
||||
name: DefaultNetworkKeys;
|
||||
name: DefaultNetworkNames;
|
||||
unit: string;
|
||||
color?: string;
|
||||
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