Provide consistency in naming, fix more sagas

This commit is contained in:
HenryNguyen5 2018-01-30 20:04:36 -05:00
parent 7fbe1966de
commit 46cbade177
23 changed files with 324 additions and 320 deletions

View File

@ -96,6 +96,13 @@ export function setLatestBlock(payload: string): interfaces.SetLatestBlockAction
};
}
export function web3SetNode(payload: interfaces.Web3setNodeAction['payload']) {
return {
type: TypeKeys.CONFIG_NODE_WEB3_SET,
payload
};
}
export type TWeb3UnsetNode = typeof web3UnsetNode;
export function web3UnsetNode(): interfaces.Web3UnsetNodeAction {
return {

View File

@ -1,5 +1,5 @@
import { TypeKeys } from './constants';
import { CustomNodeConfig } from 'types/node';
import { CustomNodeConfig, Web3NodeConfig } from 'types/node';
import { CustomNetworkConfig } from 'types/network';
/*** Toggle Offline ***/
@ -22,8 +22,8 @@ export interface ChangeNodeAction {
type: TypeKeys.CONFIG_NODE_CHANGE;
// FIXME $keyof?
payload: {
nodeName: string;
networkName: string;
nodeId: string;
networkId: string;
};
}
@ -73,11 +73,21 @@ export interface Web3UnsetNodeAction {
type: TypeKeys.CONFIG_NODE_WEB3_UNSET;
}
/*** Set Web3 as a Node ***/
export interface Web3setNodeAction {
type: TypeKeys.CONFIG_NODE_WEB3_SET;
payload: { id: 'web3'; config: Web3NodeConfig };
}
export type CustomNetworkAction = AddCustomNetworkAction | RemoveCustomNetworkAction;
export type CustomNodeAction = AddCustomNodeAction | RemoveCustomNodeAction;
export type NodeAction = ChangeNodeAction | ChangeNodeIntentAction | Web3UnsetNodeAction;
export type NodeAction =
| ChangeNodeAction
| ChangeNodeIntentAction
| Web3UnsetNodeAction
| Web3setNodeAction;
export type MetaAction =
| ChangeLanguageAction

View File

@ -1,14 +1,19 @@
export enum TypeKeys {
CONFIG_LANGUAGE_CHANGE = 'CONFIG_LANGUAGE_CHANGE',
CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE',
CONFIG_NODE_CHANGE_INTENT = 'CONFIG_NODE_CHANGE_INTENT',
CONFIG_TOGGLE_OFFLINE = 'CONFIG_TOGGLE_OFFLINE',
CONFIG_TOGGLE_AUTO_GAS_LIMIT = 'CONFIG_TOGGLE_AUTO_GAS_LIMIT',
CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS',
CONFIG_SET_LATEST_BLOCK = 'CONFIG_SET_LATEST_BLOCK',
CONFIG_NODE_WEB3_SET = 'CONFIG_NODE_WEB3_SET',
CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET',
CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE',
CONFIG_NODE_CHANGE_INTENT = 'CONFIG_NODE_CHANGE_INTENT',
CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_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_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET'
CONFIG_REMOVE_CUSTOM_NETWORK = 'CONFIG_REMOVE_CUSTOM_NETWORK'
}

View File

@ -43,7 +43,7 @@ interface State {
url: string;
port: string;
network: string;
customNetworkName: string;
customNetworkId: string;
customNetworkUnit: string;
customNetworkChainId: string;
hasAuth: boolean;
@ -59,7 +59,7 @@ class CustomNodeModal extends React.Component<Props, State> {
url: '',
port: '',
network: Object.keys(this.props.staticNetworks)[0],
customNetworkName: '',
customNetworkId: '',
customNetworkUnit: '',
customNetworkChainId: '',
hasAuth: false,
@ -150,7 +150,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="is-required">Network Name</label>
{this.renderInput(
{
name: 'customNetworkName',
name: 'customNetworkId',
placeholder: 'My Custom Network'
},
invalids
@ -265,7 +265,7 @@ class CustomNodeModal extends React.Component<Props, State> {
username,
password,
network,
customNetworkName,
customNetworkId,
customNetworkUnit,
customNetworkChainId
} = this.state;
@ -302,8 +302,8 @@ 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 (!customNetworkId) {
invalids.customNetworkId = true;
}
if (!customNetworkUnit) {
invalids.customNetworkUnit = true;
@ -327,7 +327,7 @@ class CustomNodeModal extends React.Component<Props, State> {
return {
isCustom: true,
name: this.state.customNetworkName,
name: this.state.customNetworkId,
unit: this.state.customNetworkUnit,
chainId: this.state.customNetworkChainId ? parseInt(this.state.customNetworkChainId, 10) : 0,
dPathFormats

View File

@ -30,7 +30,7 @@ import {
getOffline,
isNodeChanging,
getLanguageSelection,
getNodeName,
getNodeId,
getNodeConfig,
CustomNodeOption,
NodeOption,
@ -53,7 +53,7 @@ interface StateProps {
network: NetworkConfig;
languageSelection: AppState['config']['meta']['languageSelection'];
node: NodeConfig;
nodeSelection: AppState['config']['nodes']['selectedNode']['nodeName'];
nodeSelection: AppState['config']['nodes']['selectedNode']['nodeId'];
isChangingNode: AppState['config']['nodes']['selectedNode']['pending'];
isOffline: AppState['config']['meta']['offline'];
nodeOptions: (CustomNodeOption | NodeOption)[];
@ -63,7 +63,7 @@ const mapStateToProps = (state: AppState): StateProps => ({
isOffline: getOffline(state),
isChangingNode: isNodeChanging(state),
languageSelection: getLanguageSelection(state),
nodeSelection: getNodeName(state),
nodeSelection: getNodeId(state),
node: getNodeConfig(state),
nodeOptions: getNodeOptions(state),
network: getNetworkConfig(state)
@ -104,23 +104,23 @@ class Header extends Component<Props, State> {
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
const options = nodeOptions.map(n => {
if (n.isCustom) {
const { name: { networkName, nodeName }, isCustom, id, ...rest } = n;
const { name: { networkId, nodeId }, isCustom, id, ...rest } = n;
return {
...rest,
name: (
<span>
{networkName} - {nodeName} <small>(custom)</small>
{networkId} - {nodeId} <small>(custom)</small>
</span>
),
onRemove: () => this.props.removeCustomNode({ id })
};
} else {
const { name: { networkName, service }, isCustom, ...rest } = n;
const { name: { networkId, service }, isCustom, ...rest } = n;
return {
...rest,
name: (
<span>
{networkName} <small>({service})</small>
{networkId} <small>({service})</small>
</span>
)
};

View File

@ -49,6 +49,7 @@ import {
} from 'config';
import { unSupportedWalletFormatsOnNetwork } from 'utils/network';
import { getNetworkConfig, getOffline } from '../../selectors/config';
import { isWeb3NodeAvailable } from 'libs/nodes/web3';
interface OwnProps {
hidden?: boolean;

View File

@ -53,3 +53,38 @@ export default class Web3Node extends RPCNode {
export function isWeb3Node(nodeLib: INode | Web3Node): nodeLib is Web3Node {
return nodeLib instanceof Web3Node;
}
export const Web3Service = 'MetaMask / Mist';
export async function setupWeb3Node() {
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;
}
}

View File

@ -1,7 +1,7 @@
import { Wei, toTokenBase } from 'libs/units';
import { addHexPrefix } from 'ethereumjs-util';
import BN from 'bn.js';
import { StaticNetworkNames } from 'types/network';
import { StaticNetworkIds } from 'types/network';
export function stripHexPrefix(value: string) {
return value.replace('0x', '');
@ -24,7 +24,7 @@ export function sanitizeHex(hex: string) {
return hex !== '' ? `0x${padLeftEven(hexStr)}` : '';
}
export function networkIdToName(networkId: string | number): StaticNetworkNames {
export function networkIdToName(networkId: string | number): StaticNetworkIds {
switch (networkId.toString()) {
case '1':
return 'ETH';

View File

@ -2,15 +2,15 @@ import { NodeAction, TypeKeys, ChangeNodeAction } from 'actions/config';
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/staticNodes';
import { NonWeb3NodeConfigs } from 'types/node';
import { StaticNetworkNames } from 'types/network';
import { StaticNetworkIds } from 'types/network';
const initalNode =
INITIAL_DEFAULT_NODE_STATE[INITIAL_NODE_STATE.nodeName as keyof NonWeb3NodeConfigs];
INITIAL_DEFAULT_NODE_STATE[INITIAL_NODE_STATE.nodeId as keyof NonWeb3NodeConfigs];
export type State = string | StaticNetworkNames;
export type State = string | StaticNetworkIds;
const INITIAL_STATE: State = initalNode.network;
const handleNodeChange = (_: State, { payload }: ChangeNodeAction) => payload.networkName;
const handleNodeChange = (_: State, { payload }: ChangeNodeAction) => payload.networkId;
export const selectedNetwork = (state: State = INITIAL_STATE, action: NodeAction) => {
switch (action.type) {

View File

@ -10,9 +10,9 @@ import {
UBQ_DEFAULT
} from 'config/dpaths';
import { ConfigAction } from 'actions/config';
import { StaticNetworkNames, StaticNetworkConfig, BlockExplorerConfig } from 'types/network';
import { StaticNetworkIds, StaticNetworkConfig, BlockExplorerConfig } from 'types/network';
export type State = { [key in StaticNetworkNames]: StaticNetworkConfig };
export type State = { [key in StaticNetworkIds]: StaticNetworkConfig };
// Must be a website that follows the ethplorer convention of /tx/[hash] and
// address/[address] to generate the correct functions.

View File

@ -2,23 +2,23 @@ import { ChangeNodeAction, ChangeNodeIntentAction, NodeAction, TypeKeys } from '
interface NodeLoaded {
pending: false;
nodeName: string;
nodeId: string;
}
interface NodeChangePending {
pending: true;
nodeName: string;
nodeId: string;
}
export type State = NodeLoaded | NodeChangePending;
export const INITIAL_STATE: NodeLoaded = {
nodeName: 'eth_mew',
nodeId: 'eth_mew',
pending: false
};
const changeNode = (_: State, { payload }: ChangeNodeAction): State => ({
nodeName: payload.networkName,
nodeId: payload.networkId,
pending: false
});

View File

@ -1,8 +1,8 @@
import { EtherscanNode, InfuraNode, RPCNode } from 'libs/nodes';
import { ConfigAction } from 'actions/config';
import { Web3NodeConfig, NonWeb3NodeConfigs } from 'types/node';
import { ConfigAction, TypeKeys } from 'actions/config';
import { NonWeb3NodeConfigs, Web3NodeConfigs } from 'types/node';
export type State = NonWeb3NodeConfigs & Web3NodeConfig;
export type State = NonWeb3NodeConfigs & Web3NodeConfigs;
export const INITIAL_STATE: State = {
eth_mew: {
@ -93,6 +93,12 @@ export const INITIAL_STATE: State = {
export const staticNodes = (state: State = INITIAL_STATE, action: ConfigAction) => {
switch (action.type) {
case TypeKeys.CONFIG_NODE_WEB3_SET:
return { ...state, [action.payload.id]: action.payload.config };
case TypeKeys.CONFIG_NODE_WEB3_UNSET:
const stateCopy = { ...state };
Reflect.deleteProperty(stateCopy, 'web3');
return stateCopy;
default:
return state;
}

View File

@ -1,58 +0,0 @@
import { Web3Node } from 'libs/nodes';
import { networkIdToName } from 'libs/values';
/**
* 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 async function initWeb3Node(): Promise<void> {
const { networkId, lib } = await setupWeb3Node();
const web3 = {
network: networkIdToName(networkId),
service: Web3Service,
lib,
estimateGas: false,
hidden: true
};
NODES.web3 = web3;
}

View File

@ -13,19 +13,16 @@ import {
} from 'redux-saga/effects';
import { makeCustomNetworkId } from 'utils/network';
import {
getNodeName,
getNodeId,
getNodeConfig,
getCustomNodeConfigs,
getCustomNetworkConfigs,
getOffline,
getNetworkConfig,
isStaticNodeName,
isStaticNodeId,
getCustomNodeFromId,
getStaticNodeFromId,
getNetworkConfigById,
getStaticAltNodeToWeb3
getNetworkConfigById
} from 'selectors/config';
import { AppState } from 'reducers';
import { TypeKeys } from 'actions/config/constants';
import {
toggleOfflineConfig,
@ -38,14 +35,9 @@ import {
} from 'actions/config';
import { showNotification } from 'actions/notifications';
import { translateRaw } from 'translations';
import { Web3Wallet } from 'libs/wallet';
import { TypeKeys as WalletTypeKeys } from 'actions/wallet/constants';
import { State as ConfigState } from 'reducers/config';
import { StaticNodeConfig, CustomNodeConfig, NodeConfig } from 'types/node';
import { CustomNetworkConfig, StaticNetworkConfig } from 'types/network';
import { Web3Service } from 'reducers/config/nodes/typings';
export const getConfig = (state: AppState): ConfigState => state.config;
import { Web3Service } from 'libs/nodes/web3';
let hasCheckedOnline = false;
export function* pollOfflineStatus(): SagaIterator {
@ -119,13 +111,13 @@ export function* reload(): SagaIterator {
export function* handleNodeChangeIntent({
payload: nodeIdToSwitchTo
}: ChangeNodeIntentAction): SagaIterator {
const isStaticNode: boolean = yield select(isStaticNodeName, nodeIdToSwitchTo);
const isStaticNode: boolean = yield select(isStaticNodeId, nodeIdToSwitchTo);
const currentConfig: NodeConfig = yield select(getNodeConfig);
function* bailOut(message: string) {
const currentNodeName: string = yield select(getNodeName);
const currentNodeId: string = yield select(getNodeId);
yield put(showNotification('danger', message, 5000));
yield put(changeNode({ networkName: currentConfig.network, nodeName: currentNodeName }));
yield put(changeNode({ networkId: currentConfig.network, nodeId: currentNodeId }));
}
let nextNodeConfig: CustomNodeConfig | StaticNodeConfig;
@ -177,7 +169,7 @@ export function* handleNodeChangeIntent({
}
yield put(setLatestBlock(currentBlock));
yield put(changeNode({ networkName: nextNodeConfig.network, nodeName: nodeIdToSwitchTo }));
yield put(changeNode({ networkId: nextNodeConfig.network, nodeId: nodeIdToSwitchTo }));
// TODO - re-enable once DeterministicWallet state is fixed to flush properly.
// DeterministicWallet keeps path related state we need to flush before we can stop reloading
@ -214,40 +206,10 @@ export function* cleanCustomNetworks(): SagaIterator {
}
}
// unset web3 as the selected node if a non-web3 wallet has been selected
export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
const node = yield select(getNodeName);
const newWallet = action.payload;
const isWeb3Wallet = newWallet instanceof Web3Wallet;
if (node !== 'web3' || isWeb3Wallet) {
return;
}
const altNode = yield select(getStaticAltNodeToWeb3);
// switch back to a node with the same network as MetaMask/Mist
yield put(changeNodeIntent(altNode));
}
export function* unsetWeb3Node(): SagaIterator {
const node = yield select(getNodeName);
if (node !== 'web3') {
return;
}
const altNode = yield select(getStaticAltNodeToWeb3);
// switch back to a node with the same network as MetaMask/Mist
yield put(changeNodeIntent(altNode));
}
export default function* configSaga(): SagaIterator {
yield takeLatest(TypeKeys.CONFIG_POLL_OFFLINE_STATUS, handlePollOfflineStatus);
yield takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent);
yield takeEvery(TypeKeys.CONFIG_LANGUAGE_CHANGE, reload);
yield takeEvery(TypeKeys.CONFIG_ADD_CUSTOM_NODE, switchToNewNode);
yield takeEvery(TypeKeys.CONFIG_REMOVE_CUSTOM_NODE, cleanCustomNetworks);
yield takeEvery(TypeKeys.CONFIG_NODE_WEB3_UNSET, unsetWeb3Node);
yield takeEvery(WalletTypeKeys.WALLET_SET, unsetWeb3NodeOnWalletEvent);
yield takeEvery(WalletTypeKeys.WALLET_RESET, unsetWeb3NodeOnWalletEvent);
}

57
common/sagas/web3.ts Normal file
View File

@ -0,0 +1,57 @@
import { networkIdToName } from 'libs/values';
import { TypeKeys as WalletTypeKeys } from 'actions/wallet/constants';
import { Web3Wallet } from 'libs/wallet';
import { SagaIterator } from 'redux-saga';
import { select, put, takeEvery, call } from 'redux-saga/effects';
import { changeNodeIntent, TypeKeys, web3SetNode } from 'actions/config';
import { getNodeId, getStaticAltNodeToWeb3 } from 'selectors/config';
import { setupWeb3Node, Web3Service } from 'libs/nodes/web3';
import { Web3NodeConfig } from '../../shared/types/node';
export function* initWeb3Node(): SagaIterator {
const { networkId, lib } = yield call(setupWeb3Node);
const config: Web3NodeConfig = {
isCustom: false,
network: networkIdToName(networkId),
service: Web3Service,
lib,
estimateGas: false,
hidden: true
};
yield put(web3SetNode({ id: 'web3', config }));
}
// unset web3 as the selected node if a non-web3 wallet has been selected
export function* unsetWeb3NodeOnWalletEvent(action): SagaIterator {
const node = yield select(getNodeId);
const newWallet = action.payload;
const isWeb3Wallet = newWallet instanceof Web3Wallet;
if (node !== 'web3' || isWeb3Wallet) {
return;
}
const altNode = yield select(getStaticAltNodeToWeb3);
// switch back to a node with the same network as MetaMask/Mist
yield put(changeNodeIntent(altNode));
}
export function* unsetWeb3Node(): SagaIterator {
const node = yield select(getNodeId);
if (node !== 'web3') {
return;
}
const altNode = yield select(getStaticAltNodeToWeb3);
// switch back to a node with the same network as MetaMask/Mist
yield put(changeNodeIntent(altNode));
}
export const web3 = [
takeEvery(TypeKeys.CONFIG_NODE_WEB3_SET, initWeb3Node),
takeEvery(TypeKeys.CONFIG_NODE_WEB3_UNSET, unsetWeb3Node),
takeEvery(WalletTypeKeys.WALLET_SET, unsetWeb3NodeOnWalletEvent),
takeEvery(WalletTypeKeys.WALLET_RESET, unsetWeb3NodeOnWalletEvent)
];

View File

@ -3,30 +3,29 @@ import { getConfig } from 'selectors/config';
import {
CustomNetworkConfig,
StaticNetworkConfig,
StaticNetworkNames,
StaticNetworkIds,
NetworkContract
} from 'types/network';
export const getNetworks = (state: AppState) => getConfig(state).networks;
export const getNetworkConfigById = (state: AppState, networkId: string) =>
isStaticNetworkName(state, networkId)
isStaticNetworkId(state, networkId)
? getStaticNetworkConfigs(state)[networkId]
: getCustomNetworkConfigs(state)[networkId];
export const getStaticNetworkNames = (state: AppState): StaticNetworkNames[] =>
Object.keys(getNetworks(state).staticNetworks) as StaticNetworkNames[];
export const getStaticNetworkIds = (state: AppState): StaticNetworkIds[] =>
Object.keys(getNetworks(state).staticNetworks) as StaticNetworkIds[];
export const isStaticNetworkName = (
export const isStaticNetworkId = (
state: AppState,
networkName: string
): networkName is StaticNetworkNames =>
Object.keys(getStaticNetworkConfigs(state)).includes(networkName);
networkId: string
): networkId is StaticNetworkIds => Object.keys(getStaticNetworkConfigs(state)).includes(networkId);
export const getStaticNetworkConfig = (state: AppState): StaticNetworkConfig | undefined => {
const { staticNetworks, selectedNetwork } = getNetworks(state);
const defaultNetwork = isStaticNetworkName(state, selectedNetwork)
const defaultNetwork = isStaticNetworkId(state, selectedNetwork)
? staticNetworks[selectedNetwork]
: undefined;
return defaultNetwork;

View File

@ -3,69 +3,64 @@ import {
getConfig,
getStaticNetworkConfigs,
getCustomNetworkConfigs,
isStaticNetworkName
isStaticNetworkId
} from 'selectors/config';
import { CustomNodeConfig, StaticNodeConfig, StaticNodeName, Web3NodeConfig } from 'types/node';
import { CustomNodeConfig, StaticNodeConfig, StaticNodeId, Web3NodeConfig } from 'types/node';
import { INITIAL_STATE as SELECTED_NODE_INITIAL_STATE } from 'reducers/config/nodes/selectedNode';
export const getNodes = (state: AppState) => getConfig(state).nodes;
export function isNodeCustom(state: AppState, nodeName: string): CustomNodeConfig | undefined {
return getCustomNodeConfigs(state)[nodeName];
export function isNodeCustom(state: AppState, nodeId: string): CustomNodeConfig | undefined {
return getCustomNodeConfigs(state)[nodeId];
}
export const getCustomNodeFromId = (
state: AppState,
nodeName: string
): CustomNodeConfig | undefined => getCustomNodeConfigs(state)[nodeName];
nodeId: string
): CustomNodeConfig | undefined => getCustomNodeConfigs(state)[nodeId];
export const getStaticAltNodeToWeb3 = (state: AppState) => {
const { web3, ...configs } = getStaticNodeConfigs(state);
if (!web3) {
return SELECTED_NODE_INITIAL_STATE.nodeName;
return SELECTED_NODE_INITIAL_STATE.nodeId;
}
const res = Object.entries(configs).find(
([_, config]: [StaticNodeName, StaticNodeConfig]) => web3.network === config.network
([_, config]: [StaticNodeId, StaticNodeConfig]) => web3.network === config.network
);
if (res) {
return res[0];
}
return SELECTED_NODE_INITIAL_STATE.nodeName;
return SELECTED_NODE_INITIAL_STATE.nodeId;
};
export const getStaticNodeFromId = (state: AppState, nodeName: StaticNodeName) =>
getStaticNodeConfigs(state)[nodeName];
export const getStaticNodeFromId = (state: AppState, nodeId: StaticNodeId) =>
getStaticNodeConfigs(state)[nodeId];
export const isStaticNodeName = (state: AppState, nodeName: string): nodeName is StaticNodeName =>
Object.keys(getStaticNodeConfigs(state)).includes(nodeName);
export const isStaticNodeId = (state: AppState, nodeId: string): nodeId is StaticNodeId =>
Object.keys(getStaticNodeConfigs(state)).includes(nodeId);
const getStaticNodeConfigs = (state: AppState) => getNodes(state).staticNodes;
export const getStaticNodeConfig = (state: AppState): StaticNodeConfig | undefined => {
const { staticNodes, selectedNode: { nodeName } } = getNodes(state);
const { staticNodes, selectedNode: { nodeId } } = getNodes(state);
const defaultNetwork = isStaticNodeName(state, nodeName) ? staticNodes[nodeName] : undefined;
const defaultNetwork = isStaticNodeId(state, nodeId) ? staticNodes[nodeId] : undefined;
return defaultNetwork;
};
export const getWeb3Node = (state: AppState): Web3NodeConfig | null => {
const currNode = getStaticNodeConfig(state);
const currNodeName = getNodeName(state);
if (
currNode &&
currNodeName &&
isStaticNodeName(state, currNodeName) &&
currNodeName === 'web3'
) {
const currNodeId = getNodeId(state);
if (currNode && currNodeId && isStaticNodeId(state, currNodeId) && currNodeId === 'web3') {
return currNode;
}
return null;
};
export const getCustomNodeConfig = (state: AppState): CustomNodeConfig | undefined => {
const { customNodes, selectedNode: { nodeName } } = getNodes(state);
const { customNodes, selectedNode: { nodeId } } = getNodes(state);
const customNode = customNodes[nodeName];
const customNode = customNodes[nodeId];
return customNode;
};
@ -81,12 +76,12 @@ export function isNodeChanging(state): boolean {
return getNodes(state).selectedNode.pending;
}
export function getNodeName(state: AppState): string {
return getNodes(state).selectedNode.nodeName;
export function getNodeId(state: AppState): string {
return getNodes(state).selectedNode.nodeId;
}
export function getIsWeb3Node(state: AppState): boolean {
return getNodeName(state) === 'web3';
return getNodeId(state) === 'web3';
}
export function getNodeConfig(state: AppState): StaticNodeConfig | CustomNodeConfig {
@ -94,9 +89,7 @@ export function getNodeConfig(state: AppState): StaticNodeConfig | CustomNodeCon
if (!config) {
const { selectedNode } = getNodes(state);
throw Error(
`No node config found for ${selectedNode.nodeName} in either static or custom nodes`
);
throw Error(`No node config found for ${selectedNode.nodeId} in either static or custom nodes`);
}
return config;
}
@ -112,34 +105,32 @@ export function getNodeLib(state: AppState) {
export interface NodeOption {
isCustom: false;
value: string;
name: { networkName?: string; service: string };
name: { networkId?: string; service: string };
color?: string;
hidden?: boolean;
}
export function getStaticNodeOptions(state: AppState): NodeOption[] {
const staticNetworkConfigs = getStaticNetworkConfigs(state);
return Object.entries(getStaticNodes(state)).map(
([nodeName, node]: [string, StaticNodeConfig]) => {
const networkName = node.network;
const associatedNetwork = staticNetworkConfigs[networkName];
const opt: NodeOption = {
isCustom: node.isCustom,
value: nodeName,
name: { networkName, service: node.service },
color: associatedNetwork.color,
hidden: node.hidden
};
return opt;
}
);
return Object.entries(getStaticNodes(state)).map(([nodeId, node]: [string, StaticNodeConfig]) => {
const networkId = node.network;
const associatedNetwork = staticNetworkConfigs[networkId];
const opt: NodeOption = {
isCustom: node.isCustom,
value: nodeId,
name: { networkId, service: node.service },
color: associatedNetwork.color,
hidden: node.hidden
};
return opt;
});
}
export interface CustomNodeOption {
isCustom: true;
id: string;
value: string;
name: { networkName?: string; nodeName: string };
name: { networkId?: string; nodeId: string };
color?: string;
hidden?: boolean;
}
@ -148,15 +139,15 @@ export function getCustomNodeOptions(state: AppState): CustomNodeOption[] {
const staticNetworkConfigs = getStaticNetworkConfigs(state);
const customNetworkConfigs = getCustomNetworkConfigs(state);
return Object.entries(getCustomNodeConfigs(state)).map(
([nodeName, node]: [string, CustomNodeConfig]) => {
const networkName = node.network;
const associatedNetwork = isStaticNetworkName(state, networkName)
? staticNetworkConfigs[networkName]
: customNetworkConfigs[networkName];
([nodeId, node]: [string, CustomNodeConfig]) => {
const networkId = node.network;
const associatedNetwork = isStaticNetworkId(state, networkId)
? staticNetworkConfigs[networkId]
: customNetworkConfigs[networkId];
const opt: CustomNodeOption = {
isCustom: node.isCustom,
value: node.id,
name: { networkName, nodeName },
name: { networkId, nodeId },
color: associatedNetwork.isCustom ? undefined : associatedNetwork.color,
hidden: false,
id: node.id

View File

@ -0,0 +1,86 @@
import { InsecureWalletName, SecureWalletName, WalletName, walletNames } from 'config';
import { EXTRA_PATHS } from 'config/dpaths';
import sortedUniq from 'lodash/sortedUniq';
import difference from 'lodash/difference';
import {
CustomNetworkConfig,
StaticNetworkConfig,
NetworkConfig,
DPathFormats
} from 'types/network';
import { AppState } from 'reducers';
import { getStaticNetworkConfigs, getStaticNetworkConfig } from 'selectors/config';
type PathType = keyof DPathFormats;
type DPathFormat =
| SecureWalletName.TREZOR
| SecureWalletName.LEDGER_NANO_S
| InsecureWalletName.MNEMONIC_PHRASE;
export function getPaths(state: AppState, pathType: PathType): DPath[] {
const paths = Object.values(getStaticNetworkConfigs(state))
.reduce(
(networkPaths: DPath[], { dPathFormats }) =>
dPathFormats ? [...networkPaths, dPathFormats[pathType]] : networkPaths,
[]
)
.concat(EXTRA_PATHS);
return sortedUniq(paths);
}
export function getSingleDPath(state: AppState, format: DPathFormat): DPath {
const network = getStaticNetworkConfig(state);
if (!network) {
throw Error('No static network config loaded');
}
const dPathFormats = network.dPathFormats;
return dPathFormats[format];
}
export function isNetworkUnit(state: AppState, unit: string) {
const currentNetwork = getStaticNetworkConfig(state);
//TODO: logic check
if (!currentNetwork) {
return false;
}
const networks = getStaticNetworkConfigs(state);
const validNetworks = Object.values(networks).filter((n: StaticNetworkConfig) => n.unit === unit);
return validNetworks.includes(currentNetwork);
}
export function isWalletFormatSupportedOnNetwork(state: AppState, format: WalletName): boolean {
const network = getStaticNetworkConfig(state);
const CHECK_FORMATS: DPathFormat[] = [
SecureWalletName.LEDGER_NANO_S,
SecureWalletName.TREZOR,
InsecureWalletName.MNEMONIC_PHRASE
];
const isHDFormat = (f: string): f is DPathFormat => CHECK_FORMATS.includes(f as DPathFormat);
// Ensure DPath's are found
if (isHDFormat(format)) {
if (!network) {
return false;
}
const dPath = network.dPathFormats && network.dPathFormats[format];
return !!dPath;
}
// Ensure Web3 is only enabled on ETH or ETH Testnets (MetaMask does not support other networks)
if (format === SecureWalletName.WEB3) {
return isNetworkUnit(state, 'ETH');
}
// All other wallet formats are supported
return true;
}
export function unSupportedWalletFormatsOnNetwork(state: AppState): WalletName[] {
const supportedFormats = walletNames.filter(walletName =>
isWalletFormatSupportedOnNetwork(state, walletName)
);
return difference(walletNames, supportedFormats);
}

View File

@ -17,8 +17,6 @@ import createSagaMiddleware from 'redux-saga';
import { loadStatePropertyOrEmptyObject, saveState } from 'utils/localStorage';
import RootReducer from './reducers';
import promiseMiddleware from 'redux-promise-middleware';
import { getNodeConfigFromId } from 'utils/node';
import { getNetworkConfigFromId } from 'utils/network';
import { dedupeCustomTokens } from 'utils/tokens';
import sagas from './sagas';
import { gasPricetoBase } from 'libs/units';
@ -62,7 +60,7 @@ const configureStore = () => {
const savedTransactionState = loadStatePropertyOrEmptyObject<TransactionState>('transaction');
const savedConfigState = loadStatePropertyOrEmptyObject<ConfigState>('config');
/*
// If they have a saved node, make sure we assign that too. The node selected
// isn't serializable, so we have to assign it here.
if (savedConfigState && savedConfigState.nodes.selectedNode.nodeName) {
@ -88,7 +86,7 @@ const configureStore = () => {
const initialNetwork =
(savedConfigState && savedConfigState.network) || configInitialState.network;
const customTokens = dedupeCustomTokens(initialNetwork.tokens, savedCustomTokensState);
*/
const persistedInitialState = {
config: {
...configInitialState,
@ -107,7 +105,7 @@ const configureStore = () => {
: transactionInitialState.fields.gasPrice
}
},
customTokens,
// customTokens,
// ONLY LOAD SWAP STATE FROM LOCAL STORAGE IF STEP WAS 3
swap: swapState
};
@ -129,13 +127,14 @@ const configureStore = () => {
throttle(() => {
const state = store.getState();
saveState({
/*
config: {
nodeSelection: state.config.nodeSelection,
languageSelection: state.config.languageSelection,
customNodes: state.config.customNodes,
customNetworks: state.config.customNetworks,
setGasLimit: state.config.setGasLimit
},
},*/
transaction: {
fields: {
gasPrice: state.transaction.fields.gasPrice

View File

@ -2,104 +2,8 @@ import { InsecureWalletName, SecureWalletName, WalletName, walletNames } from 'c
import { EXTRA_PATHS } from 'config/dpaths';
import sortedUniq from 'lodash/sortedUniq';
import difference from 'lodash/difference';
import {
CustomNetworkConfig,
StaticNetworkConfig,
NetworkConfig,
DPathFormats
} from 'types/network';
import { CustomNetworkConfig } from 'types/network';
export function makeCustomNetworkId(config: CustomNetworkConfig): string {
return config.chainId ? `${config.chainId}` : `${config.name}:${config.unit}`;
}
export function makeNetworkConfigFromCustomConfig(
config: CustomNetworkConfig
): StaticNetworkConfig {
// TODO - re-enable this block and classify customConfig after user-inputted dPaths are implemented
// -------------------------------------------------
// this still provides the type safety we want
// as we know config coming in is CustomNetworkConfig
// meaning name will be a string
// then we cast it as any to keep it as a network key
// interface Override extends StaticNetworkConfig {
// name: any;
// }
// -------------------------------------------------
// TODO - allow for user-inputted dPaths so we don't need to use any below and can use supplied dPaths
// In the meantime, networks with an unknown chainId will have HD wallets disabled
const customConfig: any = {
...config,
color: '#000',
tokens: [],
contracts: []
};
return customConfig;
}
type PathType = keyof DPathFormats;
type DPathFormat =
| SecureWalletName.TREZOR
| SecureWalletName.LEDGER_NANO_S
| InsecureWalletName.MNEMONIC_PHRASE;
export function getPaths(pathType: PathType): DPath[] {
const networkPaths: DPath[] = [];
Object.values(NETWORKS).forEach(networkConfig => {
const path = networkConfig.dPathFormats ? networkConfig.dPathFormats[pathType] : [];
if (path) {
networkPaths.push(path as DPath);
}
});
const paths = networkPaths.concat(EXTRA_PATHS);
return sortedUniq(paths);
}
export function getSingleDPath(format: DPathFormat, network: NetworkConfig): DPath {
const dPathFormats = network.dPathFormats;
return dPathFormats[format];
}
export function isNetworkUnit(network: NetworkConfig, unit: string) {
const validNetworks = Object.values(NETWORKS).filter((n: NetworkConfig) => n.unit === unit);
return validNetworks.includes(network);
}
export function isWalletFormatSupportedOnNetwork(
format: WalletName,
network: NetworkConfig
): boolean {
const CHECK_FORMATS: DPathFormat[] = [
SecureWalletName.LEDGER_NANO_S,
SecureWalletName.TREZOR,
InsecureWalletName.MNEMONIC_PHRASE
];
const isHDFormat = (f: string): f is DPathFormat => CHECK_FORMATS.includes(f as DPathFormat);
// Ensure DPath's are found
if (isHDFormat(format)) {
const dPath = network.dPathFormats && network.dPathFormats[format];
return !!dPath;
}
// Ensure Web3 is only enabled on ETH or ETH Testnets (MetaMask does not support other networks)
if (format === SecureWalletName.WEB3) {
return isNetworkUnit(network, 'ETH');
}
// All other wallet formats are supported
return true;
}
export function allWalletFormatsSupportedOnNetwork(network: NetworkConfig): WalletName[] {
return walletNames.filter(walletName => isWalletFormatSupportedOnNetwork(walletName, network));
}
export function unSupportedWalletFormatsOnNetwork(network: NetworkConfig): WalletName[] {
const supportedFormats = allWalletFormatsSupportedOnNetwork(network);
return difference(walletNames, supportedFormats);
}

View File

@ -1,6 +1,6 @@
import { StaticNetworksState, CustomNetworksState } from 'reducers/config/networks';
type StaticNetworkNames = 'ETH' | 'Ropsten' | 'Kovan' | 'Rinkeby' | 'ETC' | 'UBQ' | 'EXP';
type StaticNetworkIds = 'ETH' | 'Ropsten' | 'Kovan' | 'Rinkeby' | 'ETC' | 'UBQ' | 'EXP';
interface BlockExplorerConfig {
origin: string;
@ -16,7 +16,7 @@ interface Token {
}
interface NetworkContract {
name: StaticNetworkNames;
name: StaticNetworkIds;
address?: string;
abi: string;
}
@ -29,7 +29,7 @@ interface DPathFormats {
interface StaticNetworkConfig {
isCustom: false; // used for type guards
name: StaticNetworkNames;
name: StaticNetworkIds;
unit: string;
color?: string;
blockExplorer?: BlockExplorerConfig;
@ -53,4 +53,4 @@ interface CustomNetworkConfig {
dPathFormats: DPathFormats | null;
}
type NetworkConfig = StaticNetworksState[StaticNetworkNames] | CustomNetworksState[string];
type NetworkConfig = StaticNetworksState[StaticNetworkIds] | CustomNetworksState[string];

View File

@ -1,5 +1,5 @@
import { RPCNode, Web3Node } from 'libs/nodes';
import { StaticNetworkNames } from './network';
import { StaticNetworkIds } from './network';
import { StaticNodesState, CustomNodesState } from 'reducers/config/nodes';
import CustomNode from 'libs/nodes/custom';
@ -20,7 +20,7 @@ interface CustomNodeConfig {
interface StaticNodeConfig {
isCustom: false;
network: StaticNetworkNames;
network: StaticNetworkIds;
lib: RPCNode | Web3Node;
service: string;
estimateGas?: boolean;
@ -31,7 +31,7 @@ interface Web3NodeConfig extends StaticNodeConfig {
lib: Web3Node;
}
declare enum StaticNodeName {
declare enum StaticNodeId {
ETH_MEW = 'eth_mew',
ETH_MYCRYPTO = 'eth_mycrypto',
ETH_ETHSCAN = 'eth_ethscan',
@ -46,10 +46,10 @@ declare enum StaticNodeName {
EXP_TECH = 'exp_tech'
}
type NonWeb3NodeConfigs = { [key in StaticNodeName]: StaticNodeConfig };
type NonWeb3NodeConfigs = { [key in StaticNodeId]: StaticNodeConfig };
interface Web3NodeConfig {
interface Web3NodeConfigs {
web3?: Web3NodeConfig;
}
type NodeConfig = StaticNodesState[StaticNodeName] | CustomNodesState[string];
type NodeConfig = StaticNodesState[StaticNodeId] | CustomNodesState[string];

View File

@ -13,7 +13,7 @@ import {
reload
} from 'sagas/config';
import {
getNodeName,
getNodeId,
getNodeConfig,
getOffline,
getCustomNodeConfigs,
@ -176,7 +176,7 @@ describe('handleNodeChangeIntent*', () => {
});
it('should select getNode', () => {
expect(data.gen.next().value).toEqual(select(getNodeName));
expect(data.gen.next().value).toEqual(select(getNodeId));
});
it('should select nodeConfig', () => {
@ -263,7 +263,7 @@ describe('unsetWeb3Node*', () => {
const gen = unsetWeb3Node();
it('should select getNode', () => {
expect(gen.next().value).toEqual(select(getNodeName));
expect(gen.next().value).toEqual(select(getNodeId));
});
it('should select getNodeConfig', () => {
@ -293,7 +293,7 @@ describe('unsetWeb3NodeOnWalletEvent*', () => {
const gen = unsetWeb3NodeOnWalletEvent(fakeAction);
it('should select getNode', () => {
expect(gen.next().value).toEqual(select(getNodeName));
expect(gen.next().value).toEqual(select(getNodeId));
});
it('should select getNodeConfig', () => {