configAndTokens.ts
1 import { dedupeCustomTokens } from 'utils/tokens'; 2 import { loadStatePropertyOrEmptyObject } from 'utils/localStorage'; 3 import { CustomNodeConfig } from 'types/node'; 4 import { shepherd, makeProviderConfig, isAutoNode } from 'libs/nodes'; 5 import RootReducer, { AppState } from './reducers'; 6 import { getLanguageSelection, getTheme } from './config/meta/selectors'; 7 import { getCustomNetworkConfigs } from './config/networks/custom/selectors'; 8 import { isStaticNetworkId } from './config/networks/static/selectors'; 9 import { isStaticNodeId } from './config/nodes/static/selectors'; 10 import { getCustomNodeConfigs } from './config/nodes/custom/selectors'; 11 import { getSelectedNode } from './config/nodes/selected/selectors'; 12 import { ConfigState } from './config/types'; 13 import { configReducer } from './config/reducer'; 14 import { CustomTokensState } from './customTokens/types'; 15 import { INITIAL_STATE as customTokensInitialState } from './customTokens/reducer'; 16 17 const appInitialState = RootReducer(undefined as any, { type: 'inital_state' }); 18 19 type DeepPartial<T> = { [P in keyof T]?: DeepPartial<T[P]> }; 20 export function getConfigAndCustomTokensStateToSubscribe( 21 state: AppState 22 ): Pick<DeepPartial<AppState>, 'config' | 'customTokens'> { 23 const subscribedConfig: DeepPartial<ConfigState> = { 24 meta: { 25 languageSelection: getLanguageSelection(state), 26 theme: getTheme(state) 27 }, 28 nodes: { customNodes: getCustomNodeConfigs(state), selectedNode: getSelectedNode(state) }, 29 networks: { 30 customNetworks: getCustomNetworkConfigs(state) 31 } 32 }; 33 34 const subscribedTokens = state.customTokens; 35 36 return { config: subscribedConfig, customTokens: subscribedTokens }; 37 } 38 39 export function rehydrateConfigAndCustomTokenState() { 40 const configInitialState = configReducer(undefined as any, { type: 'inital_state' }); 41 const savedConfigState = loadStatePropertyOrEmptyObject<ConfigState>('config'); 42 const nextConfigState = { ...configInitialState }; 43 44 // If they have a saved node, make sure we assign that too. The node selected 45 // isn't serializable, so we have to assign it here. 46 if (savedConfigState) { 47 // we assign networks first so that when we re-hydrate custom nodes, we can check that the network exists 48 nextConfigState.networks = rehydrateNetworks( 49 configInitialState.networks, 50 savedConfigState.networks 51 ); 52 nextConfigState.nodes = rehydrateNodes( 53 configInitialState.nodes, 54 savedConfigState.nodes, 55 nextConfigState.networks 56 ); 57 nextConfigState.meta = { ...nextConfigState.meta, ...savedConfigState.meta }; 58 } 59 60 const { customNodes, selectedNode: { nodeId }, staticNodes } = nextConfigState.nodes; 61 const selectedNode = isStaticNodeId(appInitialState, nodeId) 62 ? staticNodes[nodeId] 63 : customNodes[nodeId]; 64 65 if (!selectedNode) { 66 return { config: configInitialState, customTokens: customTokensInitialState }; 67 } 68 69 const nextCustomTokenState = rehydrateCustomTokens( 70 nextConfigState.networks, 71 selectedNode.network 72 ); 73 74 return { config: nextConfigState, customTokens: nextCustomTokenState }; 75 } 76 77 function rehydrateCustomTokens(networkState: ConfigState['networks'], selectedNetwork: string) { 78 // Dedupe custom tokens initially 79 const savedCustomTokensState = 80 loadStatePropertyOrEmptyObject<CustomTokensState>('customTokens') || customTokensInitialState; 81 82 const { customNetworks, staticNetworks } = networkState; 83 const network = isStaticNetworkId(appInitialState, selectedNetwork) 84 ? staticNetworks[selectedNetwork] 85 : customNetworks[selectedNetwork]; 86 87 return network.isCustom 88 ? savedCustomTokensState 89 : dedupeCustomTokens(network.tokens, savedCustomTokensState); 90 } 91 92 function rehydrateNetworks( 93 initialState: ConfigState['networks'], 94 savedState: ConfigState['networks'] 95 ): ConfigState['networks'] { 96 const nextNetworkState = { ...initialState }; 97 nextNetworkState.customNetworks = savedState.customNetworks; 98 return nextNetworkState; 99 } 100 101 function rehydrateNodes( 102 initalState: ConfigState['nodes'], 103 savedState: ConfigState['nodes'], 104 networkState: ConfigState['networks'] 105 ): ConfigState['nodes'] { 106 const nextNodeState = { ...initalState }; 107 108 // re-assign the hydrated nodes 109 nextNodeState.customNodes = rehydrateCustomNodes(savedState.customNodes, networkState); 110 const { customNodes, staticNodes } = nextNodeState; 111 nextNodeState.selectedNode = getSavedSelectedNode( 112 nextNodeState.selectedNode, 113 savedState.selectedNode, 114 customNodes, 115 staticNodes 116 ); 117 118 return nextNodeState; 119 } 120 121 function getSavedSelectedNode( 122 initialState: ConfigState['nodes']['selectedNode'], 123 savedState: ConfigState['nodes']['selectedNode'], 124 customNodes: ConfigState['nodes']['customNodes'], 125 staticNodes: ConfigState['nodes']['staticNodes'] 126 ): ConfigState['nodes']['selectedNode'] { 127 const { nodeId: savedNodeId } = savedState; 128 129 // if 'web3' has persisted as node selection, reset to app default 130 // necessary because web3 is only initialized as a node upon MetaMask / Mist unlock 131 132 if (savedNodeId === 'web3') { 133 return { nodeId: initialState.nodeId, prevNode: initialState.nodeId, pending: false }; 134 } 135 136 const nodeConfigExists = isStaticNodeId(appInitialState, savedNodeId) 137 ? staticNodes[savedNodeId] 138 : customNodes[savedNodeId]; 139 140 if (nodeConfigExists) { 141 if (isAutoNode(savedNodeId)) { 142 shepherd.switchNetworks(nodeConfigExists.network); 143 } else { 144 shepherd.manual(savedNodeId, false); 145 } 146 } 147 const nodeId = nodeConfigExists ? savedNodeId : initialState.nodeId; 148 return { nodeId, prevNode: nodeId, pending: false }; 149 } 150 151 function rehydrateCustomNodes( 152 state: ConfigState['nodes']['customNodes'], 153 networkState: ConfigState['networks'] 154 ) { 155 const networkExists = (networkId: string) => 156 Object.keys(networkState.customNetworks).includes(networkId) || 157 Object.keys(networkState.staticNetworks).includes(networkId); 158 159 const rehydratedCustomNodes = Object.entries(state).reduce( 160 (hydratedNodes, [customNodeId, configToHydrate]) => { 161 if (!networkExists(configToHydrate.network)) { 162 return hydratedNodes; 163 } 164 165 shepherd.useProvider( 166 'myccustom', 167 configToHydrate.id, 168 makeProviderConfig({ network: configToHydrate.network }), 169 configToHydrate 170 ); 171 172 const hydratedNode: CustomNodeConfig = { ...configToHydrate }; 173 return { ...hydratedNodes, [customNodeId]: hydratedNode }; 174 }, 175 {} as ConfigState['nodes']['customNodes'] 176 ); 177 return rehydratedCustomNodes; 178 }