on master help sign in
Merge pull request #154 from urbit/feat/network-state

Extract Network State

commit51fd14c34b118d70f58dd7413baef3aecba4cf54 authorMatt Condon <mattgcondon@gmail.com> dateWed 12 Jun 2019, 15:43 (7 years ago) parent77325bb621eafb
42 files changed+542−437
src/bridge/Bridge.js+58−88 src/bridge/components/Header.js+9−7 src/bridge/components/StatelessTransaction.js+9−3 src/bridge/lib/flags.js+2−1 src/bridge/lib/invite.js+2−2 src/bridge/lib/nest.js+7 src/bridge/lib/network.js+6−6 src/bridge/lib/router.js+7−5 src/bridge/lib/txn.js+4−4 src/bridge/lib/web3.js−23 src/bridge/store/history.js+8−2 src/bridge/store/network.js+119 src/bridge/store/online.js+66 src/bridge/store/txnConfirmations.js+13−8 src/bridge/views/AcceptTransfer.js+7−5 src/bridge/views/CancelTransfer.js+3−1 src/bridge/views/CreateGalaxy.js+7−6 src/bridge/views/InviteTicket.js+8−4 src/bridge/views/InvitesManage.js+3−1 src/bridge/views/InvitesSend.js+3−1 src/bridge/views/IssueChild.js+7−5 src/bridge/views/Keystore.js+2−1 src/bridge/views/Ledger.js+2−1 src/bridge/views/Mnemonic.js+2−1 src/bridge/views/Network.js+59−138 src/bridge/views/Point.js+6−1 src/bridge/views/Points.js+8−3 src/bridge/views/PrivateKey.js+2−1 src/bridge/views/SentTransaction.js+15−10 src/bridge/views/SetKeys.js+3−1 src/bridge/views/SetProxy.js+20−18 src/bridge/views/Shards.js+2−1 src/bridge/views/Ticket.js+2−2 src/bridge/views/Transfer.js+7−5 src/bridge/views/Trezor.js+2−1 src/bridge/views/ViewPoint.js+2−1 src/bridge/views/Wallet.js+2−1 src/walletgen/Walletgen.js+21−41 src/walletgen/components/Header.js+35−24 src/walletgen/lib/constants.js−7 src/walletgen/lib/lib.js+1−6 src/walletgen/views/Upload.js+1−1
src/bridge/Bridge.js+58−88
@@ -7,11 +7,10 @@ import Header from './components/Header';
77import { Container, Row, Col } from './components/Base';
88
99import { router } from './lib/router';
10−import { initWeb3 } from './lib/web3';
1110import { ROUTE_NAMES } from './lib/routeNames';
1211import { HistoryProvider, withHistory, useHistory } from './store/history';
1312import { TxnConfirmationsProvider } from './store/txnConfirmations';
14−import { NETWORK_NAMES } from './lib/network';
13+import { NETWORK_TYPES } from './lib/network';
1514import {
1615 WALLET_NAMES,
1716 DEFAULT_HD_PATH,
@@ -19,13 +18,16 @@ import {
1918 addressFromSecp256k1Public,
2019} from './lib/wallet';
2120import { BRIDGE_ERROR } from './lib/error';
22−import { isLocal } from './lib/flags';
21+import { isDevelopment } from './lib/flags';
22+import nest from './lib/nest';
23+import { OnlineProvider } from './store/online';
24+import { NetworkProvider } from './store/network';
2325
2426// NB(shrugs): toggle this variable to use the default local state.
2527// don't commit changes to this line, but there shouldn't be a problem
2628// if you do because we'll never stub on a production build.
2729const shouldStubLocal = false;
28−const isStubbed = isLocal && shouldStubLocal;
30+const isStubbed = isDevelopment && shouldStubLocal;
2931const kInitialRoutes = isStubbed
3032 ? [
3133 { name: ROUTE_NAMES.SHIPS },
@@ -34,9 +36,12 @@ const kInitialRoutes = isStubbed
3436 { name: ROUTE_NAMES.LANDING },
3537 ]
3638 : [{ name: ROUTE_NAMES.LANDING }];
39+const kInitialNetworkType = isDevelopment
40+ ? NETWORK_TYPES.LOCAL
41+ : NETWORK_TYPES.MAINNET;
3742
38−// the router itself is a component that renders a specific view
39−// based on the latest history state
43+// the router itself is just a component that renders a specific view
44+// depending on the history
4045const Router = function(props) {
4146 const history = useHistory();
4247 const Route = router(history.peek());
@@ -62,16 +67,18 @@ const VariableWidthColumn = withHistory(({ history, children }) => (
6267 </Col>
6368));
6469
70+// nest all of the providers within each other to avoid hella depth
71+const AllProviders = nest([
72+ HistoryProvider,
73+ TxnConfirmationsProvider,
74+ OnlineProvider,
75+ NetworkProvider,
76+]);
77+
6578class Bridge extends React.Component {
6679 constructor(props) {
6780 super(props);
6881
69− const networkType = isLocal ? NETWORK_NAMES.LOCAL : NETWORK_NAMES.MAINNET;
70−
71− // Sidestepping full network selection to allow for point viewing,
72− // but still respecting the networkType
73− const { web3, contracts } = initWeb3(networkType);
74−
7582 // NB (jtobin):
7683 //
7784 // Note that the 'wallet' prop has type depending on the 'walletType' prop.
@@ -86,10 +93,6 @@ class Bridge extends React.Component {
8693 // 'EthereumWallet'.
8794
8895 this.state = {
89− // network
90− networkType: networkType,
91− web3: Just(web3),
92− contracts: Just(contracts),
9396 // wallet
9497 walletType: WALLET_NAMES.MNEMONIC,
9598 wallet: Nothing(),
@@ -106,8 +109,6 @@ class Bridge extends React.Component {
106109 txnConfirmations: {},
107110 };
108111
109− this.setNetworkType = this.setNetworkType.bind(this);
110− this.setNetwork = this.setNetwork.bind(this);
111112 this.setWalletType = this.setWalletType.bind(this);
112113 this.setWallet = this.setWallet.bind(this);
113114 this.setWalletHdPath = this.setWalletHdPath.bind(this);
@@ -138,16 +139,6 @@ class Bridge extends React.Component {
138139 }
139140 }
140141
141− setNetworkType(symbol) {
142− if (includes(NETWORK_NAMES, symbol)) {
143− this.setState({
144− networkType: symbol,
145− });
146− } else {
147− throw BRIDGE_ERROR.INVALID_NETWORK_TYPE;
148− }
149− }
150−
151142 setNetworkSeedCache(networkSeed, revision) {
152143 this.setState({
153144 networkSeedCache: networkSeed,
@@ -155,13 +146,6 @@ class Bridge extends React.Component {
155146 });
156147 }
157148
158− setNetwork(web3, contracts) {
159− this.setState({
160− web3: web3,
161− contracts: contracts,
162− });
163− }
164−
165149 setWalletType(symbol) {
166150 if (includes(WALLET_NAMES, symbol)) {
167151 this.setState({
@@ -222,7 +206,6 @@ class Bridge extends React.Component {
222206
223207 render() {
224208 const {
225− networkType,
226209 walletType,
227210 walletHdPath,
228211 wallet,
@@ -233,66 +216,53 @@ class Bridge extends React.Component {
233216 pointCursor,
234217 pointCache,
235218 txnHashCursor,
236− web3,
237− contracts,
238219 } = this.state;
239220
240221 return (
241− <HistoryProvider initialRoutes={kInitialRoutes}>
242− <TxnConfirmationsProvider>
243− <Container>
244− <Row>
245− <VariableWidthColumn>
246− <Header
247− skipRoute={this.skipRoute}
248− networkType={networkType}
222+ <AllProviders
223+ initialRoutes={kInitialRoutes}
224+ initialNetworkType={kInitialNetworkType}>
225+ <Container>
226+ <Row>
227+ <VariableWidthColumn>
228+ <Header
229+ skipRoute={this.skipRoute}
230+ wallet={wallet}
231+ pointCursor={pointCursor}
232+ />
233+
234+ <Row className={'row wrapper'}>
235+ <Router
236+ setWalletType={this.setWalletType}
237+ setWalletHdPath={this.setWalletHdPath}
238+ setWallet={this.setWallet}
239+ walletType={walletType}
240+ walletHdPath={walletHdPath}
249241 wallet={wallet}
242+ urbitWallet={urbitWallet}
243+ setUrbitWallet={this.setUrbitWallet}
244+ authMnemonic={authMnemonic}
245+ setAuthMnemonic={this.setAuthMnemonic}
246+ setPointCursor={this.setPointCursor}
247+ addToPointCache={this.addToPointCache}
250248 pointCursor={pointCursor}
249+ pointCache={pointCache}
250+ networkSeedCache={networkSeedCache}
251+ networkRevisionCache={networkRevisionCache}
252+ setNetworkSeedCache={this.setNetworkSeedCache}
253+ onSent={this.setTxnHashCursor}
254+ setTxnHashCursor={this.setTxnHashCursor}
255+ txnHashCursor={txnHashCursor}
251256 />
252257
253− <Row className={'row wrapper'}>
254− <Router
255− // network
256− setNetworkType={this.setNetworkType}
257− setNetwork={this.setNetwork}
258− networkType={networkType}
259− web3={web3}
260− contracts={contracts}
261− // wallet
262− setWalletType={this.setWalletType}
263− setWalletHdPath={this.setWalletHdPath}
264− setWallet={this.setWallet}
265− walletType={walletType}
266− walletHdPath={walletHdPath}
267− wallet={wallet}
268− // urbit wallet
269− urbitWallet={urbitWallet}
270− setUrbitWallet={this.setUrbitWallet}
271− authMnemonic={authMnemonic}
272− setAuthMnemonic={this.setAuthMnemonic}
273− // point
274− setPointCursor={this.setPointCursor}
275− addToPointCache={this.addToPointCache}
276− pointCursor={pointCursor}
277− pointCache={pointCache}
278− networkSeedCache={networkSeedCache}
279− networkRevisionCache={networkRevisionCache}
280− setNetworkSeedCache={this.setNetworkSeedCache}
281− // txn
282− onSent={this.setTxnHashCursor}
283− setTxnHashCursor={this.setTxnHashCursor}
284− txnHashCursor={txnHashCursor}
285− />
286−
287− <div className={'push'} />
288− </Row>
258+ <div className={'push'} />
259+ </Row>
289260
290− <Footer />
291− </VariableWidthColumn>
292− </Row>
293− </Container>
294− </TxnConfirmationsProvider>
295− </HistoryProvider>
261+ <Footer />
262+ </VariableWidthColumn>
263+ </Row>
264+ </Container>
265+ </AllProviders>
296266 );
297267 }
298268}
src/bridge/components/Header.js+9−7
@@ -5,9 +5,16 @@ import { getRouteBreadcrumb } from '../lib/router';
55import { ROUTE_NAMES } from '../lib/routeNames';
66import { useHistory } from '../store/history';
77import { isLast } from '../lib/lib';
8+import { useNetwork } from '../store/network';
9+
10+function useRouteBreadcrumbBuilder(props) {
11+ const { networkType } = useNetwork();
12+ return getRouteBreadcrumb({ ...props, networkType });
13+}
814
915function Crumbs(props) {
1016 const history = useHistory();
17+ const breadcrumbBuilder = useRouteBreadcrumbBuilder(props);
1118
1219 return (
1320 <>
@@ -17,9 +24,8 @@ function Crumbs(props) {
1724 <Button
1825 prop-type={'link'}
1926 prop-size={'sm'}
20− key={`history-button-${idx}`}
2127 onClick={() => history.pop(history.size - idx - 1)}>
22− {getRouteBreadcrumb(props, route)}
28+ {breadcrumbBuilder(route)}
2329 </Button>
2430
2531 {isLast(history.size, idx) ? (
@@ -42,11 +48,7 @@ function Header(props) {
4248 if (showCrumbs) {
4349 return (
4450 <div className={'flex items-center h-10'}>
45− <Crumbs
46− networkType={props.networkType}
47− wallet={props.wallet}
48− pointCursor={props.pointCursor}
49− />
51+ <Crumbs wallet={props.wallet} pointCursor={props.pointCursor} />
5052 </div>
5153 );
5254 }
src/bridge/components/StatelessTransaction.js+9−3
@@ -10,9 +10,8 @@ import { Warning } from '../components/Base';
1010
1111import { BRIDGE_ERROR } from '../lib/error';
1212import { ROUTE_NAMES } from '../lib/routeNames';
13−import { withHistory } from '../store/history';
13+import { compose } from '../lib/lib';
1414import * as tank from '../lib/tank';
15−
1615import {
1716 sendSignedTransaction,
1817 fromWei,
@@ -21,7 +20,10 @@ import {
2120 renderSignedTx,
2221 signTransaction,
2322} from '../lib/txn';
23+
2424import { withTxnConfirmations } from '../store/txnConfirmations';
25+import { withNetwork } from '../store/network';
26+import { withHistory } from '../store/history';
2527
2628const SUBMISSION_STATES = {
2729 PROMPT: 'Send transaction',
@@ -579,4 +581,8 @@ class StatelessTransaction extends React.Component {
579581 }
580582}
581583
582−export default withTxnConfirmations(withHistory(StatelessTransaction));
584+export default compose(
585+ withNetwork,
586+ withTxnConfirmations,
587+ withHistory
588+)(StatelessTransaction);
src/bridge/lib/flags.js+2−1
@@ -1 +1,2 @@
1−export const isLocal = process.env.NODE_ENV === 'development';
1+// are we running bridge in a development build
2+export const isDevelopment = process.env.NODE_ENV === 'development';
src/bridge/lib/invite.js+2−2
@@ -105,8 +105,8 @@ async function downloadWallet(paper) {
105105async function startTransactions(args) {
106106 let {
107107 realPointM,
108− web3M,
109− contractsM,
108+ web3: web3M,
109+ contracts: contractsM,
110110 inviteWalletM,
111111 realWalletM,
112112 setUrbitWallet,
src/bridge/lib/nest.jsnew file+7
@@ -0,0 +1,7 @@
1+import { createFactory } from 'react';
2+
3+// inspired by recompose/nest
4+export default (components = []) => ({ children, ...props }) =>
5+ components
6+ .map(createFactory)
7+ .reduceRight((child, factory) => factory(props, child), children);
src/bridge/lib/network.js+6−6
@@ -1,4 +1,4 @@
1−const NETWORK_NAMES = {
1+const NETWORK_TYPES = {
22 OFFLINE: Symbol('OFFLINE'),
33 LOCAL: Symbol('LOCAL'),
44 ROPSTEN: Symbol('ROPSTEN'),
@@ -6,14 +6,14 @@ const NETWORK_NAMES = {
66};
77
88const renderNetworkType = network =>
9− network === NETWORK_NAMES.OFFLINE
9+ network === NETWORK_TYPES.OFFLINE
1010 ? 'Offline'
11− : network === NETWORK_NAMES.ROPSTEN
11+ : network === NETWORK_TYPES.ROPSTEN
1212 ? 'Ropsten'
13− : network === NETWORK_NAMES.MAINNET
13+ : network === NETWORK_TYPES.MAINNET
1414 ? 'Main Network'
15− : network === NETWORK_NAMES.LOCAL
15+ : network === NETWORK_TYPES.LOCAL
1616 ? 'Local Node'
1717 : 'Offline';
1818
19−export { NETWORK_NAMES, renderNetworkType };
19+export { NETWORK_TYPES, renderNetworkType };
src/bridge/lib/router.js+7−5
@@ -67,9 +67,11 @@ const ROUTES = {
6767 [ROUTE_NAMES.GEN_KEYFILE]: GenKeyfile,
6868};
6969
70−export const getRouteBreadcrumb = (props, route) => {
71− const { wallet, networkType } = props;
72−
70+export const getRouteBreadcrumb = ({
71+ networkType,
72+ wallet,
73+ pointCursor,
74+}) => route => {
7375 switch (route.name) {
7476 case ROUTE_NAMES.INVITE_TICKET:
7577 return 'Invite code';
@@ -80,7 +82,7 @@ export const getRouteBreadcrumb = (props, route) => {
8082 case ROUTE_NAMES.INVITES_MANAGE:
8183 return 'Manage invites';
8284 case ROUTE_NAMES.NETWORK:
83− return `${renderNetworkType(networkType)}`;
85+ return renderNetworkType(networkType);
8486 case ROUTE_NAMES.WALLET:
8587 return wallet.matchWith({
8688 Nothing: () => 'Wallet',
@@ -110,7 +112,7 @@ export const getRouteBreadcrumb = (props, route) => {
110112 case ROUTE_NAMES.VIEW_SHIP:
111113 return 'View';
112114 case ROUTE_NAMES.SHIP:
113− return ob.patp(need.pointCursor(props));
115+ return ob.patp(need.pointCursor({ pointCursor }));
114116 case ROUTE_NAMES.SET_TRANSFER_PROXY:
115117 case ROUTE_NAMES.SET_MANAGEMENT_PROXY:
116118 case ROUTE_NAMES.SET_SPAWN_PROXY:
src/bridge/lib/txn.js+4−4
@@ -4,7 +4,7 @@ import Tx from 'ethereumjs-tx';
44import { toWei, fromWei, toHex } from 'web3-utils';
55
66import { BRIDGE_ERROR } from '../lib/error';
7−import { NETWORK_NAMES } from '../lib/network';
7+import { NETWORK_TYPES } from '../lib/network';
88import { ledgerSignTransaction } from '../lib/ledger';
99import { trezorSignTransaction } from '../lib/trezor';
1010import { WALLET_NAMES, addHexPrefix } from '../lib/wallet';
@@ -90,9 +90,9 @@ const signTransaction = async config => {
9090 };
9191
9292 const defaultEip155Networks = [
93− NETWORK_NAMES.MAINNET,
94− NETWORK_NAMES.ROPSTEN,
95− NETWORK_NAMES.OFFLINE,
93+ NETWORK_TYPES.MAINNET,
94+ NETWORK_TYPES.ROPSTEN,
95+ NETWORK_TYPES.OFFLINE,
9696 ];
9797
9898 const needEip155Params =
src/bridge/lib/web3.jsdeleted−23
@@ -1,23 +0,0 @@
1−import * as azimuth from 'azimuth-js';
2−import Web3 from 'web3';
3−
4−import { CONTRACT_ADDRESSES } from './contracts';
5−import { NETWORK_NAMES } from './network';
6−
7−export const initWeb3 = networkType => {
8− if (networkType === NETWORK_NAMES.MAINNET) {
9− const endpoint = `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
10−
11− const provider = new Web3.providers.HttpProvider(endpoint);
12− const web3 = new Web3(provider);
13− const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.MAINNET);
14− return { web3: web3, contracts: contracts };
15− } else if (networkType === NETWORK_NAMES.LOCAL) {
16− const protocol = process.env.NODE_ENV === 'development' ? 'ws' : 'wss';
17− const endpoint = `${protocol}://localhost:8545`;
18− const provider = new Web3.providers.WebsocketProvider(endpoint);
19− const web3 = new Web3(provider);
20− const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.DEV);
21− return { web3: web3, contracts: contracts };
22− }
23−};
src/bridge/store/history.js+8−2
@@ -1,10 +1,16 @@
1−import React, { forwardRef, useContext, useState, useEffect } from 'react';
1+import React, {
2+ createContext,
3+ forwardRef,
4+ useContext,
5+ useState,
6+ useEffect,
7+} from 'react';
28import { last, includes } from 'lodash';
39
410import { BRIDGE_ERROR } from '../lib/error';
511import { ROUTE_NAMES } from '../lib/routeNames';
612
7−export const HistoryContext = React.createContext(null);
13+export const HistoryContext = createContext(null);
814
915function _useHistory(initialRoutes = []) {
1016 const [history, setHistory] = useState(initialRoutes);
src/bridge/store/network.jsnew file+119
@@ -0,0 +1,119 @@
1+import React, {
2+ createContext,
3+ forwardRef,
4+ useContext,
5+ useMemo,
6+ useState,
7+} from 'react';
8+import * as azimuth from 'azimuth-js';
9+import Web3 from 'web3';
10+import Maybe from 'folktale/maybe';
11+import { includes } from 'lodash';
12+
13+import { CONTRACT_ADDRESSES } from '../lib/contracts';
14+import { NETWORK_TYPES } from '../lib/network';
15+import { isDevelopment } from '../lib/flags';
16+import { BRIDGE_ERROR } from '../lib/error';
17+
18+function _useNetwork(initialNetworkType = null) {
19+ const [networkType, _setNetworkType] = useState(initialNetworkType);
20+
21+ const setNetworkType = networkType => {
22+ if (!includes(NETWORK_TYPES, networkType)) {
23+ throw BRIDGE_ERROR.INVALID_NETWORK_TYPE;
24+ }
25+ _setNetworkType(networkType);
26+ };
27+
28+ const { web3, contracts } = useMemo(() => {
29+ // given a web3 provider and contract addresses,
30+ // build the web3 and contracts objects
31+ const initWeb3 = (provider, contractAddresses) => {
32+ const web3 = new Web3(provider);
33+ const contracts = azimuth.initContracts(web3, contractAddresses);
34+
35+ return {
36+ web3: Maybe.Just(web3),
37+ contracts: Maybe.Just(contracts),
38+ };
39+ };
40+
41+ switch (networkType) {
42+ case NETWORK_TYPES.LOCAL: {
43+ const protocol = isDevelopment ? 'ws' : 'wss';
44+ const endpoint = `${protocol}://localhost:8545`;
45+
46+ return initWeb3(
47+ new Web3.providers.WebsocketProvider(endpoint),
48+ CONTRACT_ADDRESSES.DEV
49+ );
50+ }
51+ case NETWORK_TYPES.ROPSTEN: {
52+ const endpoint = `https://ropsten.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
53+
54+ return initWeb3(
55+ new Web3.providers.HttpProvider(endpoint),
56+ CONTRACT_ADDRESSES.ROPSTEN
57+ );
58+ }
59+ case NETWORK_TYPES.MAINNET: {
60+ const endpoint = `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
61+
62+ return initWeb3(
63+ new Web3.providers.HttpProvider(endpoint),
64+ CONTRACT_ADDRESSES.MAINNET
65+ );
66+ }
67+ case NETWORK_TYPES.OFFLINE:
68+ default: {
69+ // NB (jtobin):
70+ //
71+ // The 'offline' network type targets the mainnet contracts, but does not
72+ // actually use a provider to connect. We use a web3 instance to
73+ // initalise the contracts, but the network itself is set to Nothing.
74+ //
75+ // We may want to offer the ability to select a target network for
76+ // transactions when offline.
77+
78+ // Note: example.com:3456 doesn't actually point to anything, we just need
79+ // a provider to initialize the Web3 object
80+ return {
81+ ...initWeb3(
82+ new Web3.providers.HttpProvider('http://example.com:3456'),
83+ isDevelopment ? CONTRACT_ADDRESSES.DEV : CONTRACT_ADDRESSES.MAINNET
84+ ),
85+ web3: Maybe.Nothing(),
86+ // ^ overwrite the web3 object from initWeb3 with a Maybe.Nothing
87+ // to indicate that there is no valid web3 connection
88+ };
89+ }
90+ }
91+ }, [networkType]);
92+
93+ return { networkType, setNetworkType, web3, contracts };
94+}
95+
96+const NetworkContext = createContext(null);
97+
98+// provider
99+export function NetworkProvider({ initialNetworkType, children }) {
100+ const value = _useNetwork(initialNetworkType);
101+ return (
102+ <NetworkContext.Provider value={value}>{children}</NetworkContext.Provider>
103+ );
104+}
105+
106+// hook consumer
107+export function useNetwork() {
108+ return useContext(NetworkContext);
109+}
110+
111+// hoc consumer
112+export const withNetwork = Component =>
113+ forwardRef((props, ref) => {
114+ return (
115+ <NetworkContext.Consumer>
116+ {value => <Component ref={ref} {...value} {...props} />}
117+ </NetworkContext.Consumer>
118+ );
119+ });
src/bridge/store/online.jsnew file+66
@@ -0,0 +1,66 @@
1+import React, {
2+ createContext,
3+ forwardRef,
4+ useContext,
5+ useEffect,
6+ useState,
7+} from 'react';
8+
9+export const ONLINE_STATUS = {
10+ ONLINE: Symbol('ONLINE'),
11+ OFFLINE: Symbol('OFFLINE'),
12+ UNKNOWN: Symbol('UNKNOWN'),
13+};
14+
15+const getNetworkState = () => {
16+ if ('onLine' in navigator) {
17+ return navigator.onLine ? ONLINE_STATUS.ONLINE : ONLINE_STATUS.OFFLINE;
18+ }
19+
20+ return ONLINE_STATUS.UNKNOWN;
21+};
22+
23+function _useOnline() {
24+ const [online, setOnline] = useState(getNetworkState());
25+
26+ useEffect(() => {
27+ function handleOnlineStatus(event) {
28+ setOnline(getNetworkState());
29+ }
30+
31+ window.addEventListener('online', handleOnlineStatus);
32+ window.addEventListener('offline', handleOnlineStatus);
33+
34+ return () => {
35+ window.removeEventListener('online', handleOnlineStatus);
36+ window.removeEventListener('offline', handleOnlineStatus);
37+ };
38+ }, []);
39+
40+ return online;
41+}
42+
43+const OnlineContext = createContext(null);
44+
45+// provider
46+export function OnlineProvider({ children }) {
47+ const online = _useOnline();
48+ return (
49+ <OnlineContext.Provider value={online}>{children}</OnlineContext.Provider>
50+ );
51+}
52+
53+// hook consumer
54+export function useOnline() {
55+ return useContext(OnlineContext);
56+}
57+
58+// hoc consumer
59+export const withOnline = Component =>
60+ forwardRef((props, ref) => {
61+ return (
62+ <OnlineContext.Consumer>
63+ {online => <Component ref={ref} online={online} {...props} />}
64+ </OnlineContext.Consumer>
65+ );
66+ });
src/bridge/store/txnConfirmations.js+13−8
@@ -1,8 +1,8 @@
1−import React, { forwardRef, useContext, useState } from 'react';
1+import React, { createContext, forwardRef, useContext, useState } from 'react';
22
3−export const TxnConfirmationsContext = React.createContext(null);
3+export const TxnConfirmationsContext = createContext(null);
44
5−export function TxnConfirmationsProvider({ children }) {
5+function _useTxnConfirmations() {
66 const [confirmations, setConfirmations] = useState({});
77
88 const setTxnConfirmations = (txnHash, txnConfirmations) => {
@@ -12,12 +12,17 @@ export function TxnConfirmationsProvider({ children }) {
1212 });
1313 };
1414
15+ return {
16+ txnConfirmations: confirmations,
17+ setTxnConfirmations,
18+ };
19+}
20+
21+export function TxnConfirmationsProvider({ children }) {
22+ const value = _useTxnConfirmations();
23+
1524 return (
16− <TxnConfirmationsContext.Provider
17− value={{
18− txnConfirmations: confirmations,
19− setTxnConfirmations,
20− }}>
25+ <TxnConfirmationsContext.Provider value={value}>
2126 {children}
2227 </TxnConfirmationsContext.Provider>
2328 );
src/bridge/views/AcceptTransfer.js+7−5
@@ -12,8 +12,10 @@ import * as ob from 'urbit-ob';
1212import * as need from '../lib/need';
1313
1414import StatelessTransaction from '../components/StatelessTransaction';
15−import { NETWORK_NAMES } from '../lib/network';
15+import { NETWORK_TYPES } from '../lib/network';
1616import { isValidAddress } from '../lib/wallet';
17+import { withNetwork } from '../store/network';
18+import { compose } from '../lib/lib';
1719
1820class AcceptTransfer extends React.Component {
1921 constructor(props) {
@@ -61,11 +63,11 @@ class AcceptTransfer extends React.Component {
6163 const canGenerate = validAddress === true;
6264
6365 const esvisible =
64− props.networkType === NETWORK_NAMES.ROPSTEN ||
65− props.networkType === NETWORK_NAMES.MAINNET;
66+ props.networkType === NETWORK_TYPES.ROPSTEN ||
67+ props.networkType === NETWORK_TYPES.MAINNET;
6668
6769 const esdomain =
68− props.networkType === NETWORK_NAMES.ROPSTEN
70+ props.networkType === NETWORK_TYPES.ROPSTEN
6971 ? 'ropsten.etherscan.io'
7072 : 'etherscan.io';
7173
@@ -117,4 +119,4 @@ class AcceptTransfer extends React.Component {
117119 }
118120}
119121
120−export default AcceptTransfer;
122+export default compose(withNetwork)(AcceptTransfer);
src/bridge/views/CancelTransfer.js+3−1
@@ -7,6 +7,8 @@ import * as need from '../lib/need';
77
88import StatelessTransaction from '../components/StatelessTransaction';
99import { ETH_ZERO_ADDR } from '../lib/wallet';
10+import { withNetwork } from '../store/network';
11+import { compose } from '../lib/lib';
1012
1113class CancelTransfer extends React.Component {
1214 constructor(props) {
@@ -73,4 +75,4 @@ class CancelTransfer extends React.Component {
7375 }
7476}
7577
76−export default CancelTransfer;
78+export default compose(withNetwork)(CancelTransfer);
src/bridge/views/CreateGalaxy.js+7−6
@@ -15,12 +15,13 @@ import {
1515} from '../components/Base';
1616import StatelessTransaction from '../components/StatelessTransaction';
1717
18−import { NETWORK_NAMES } from '../lib/network';
18+import { NETWORK_TYPES } from '../lib/network';
1919import { canDecodePatp } from '../lib/txn';
2020
2121import { ETH_ZERO_ADDR, isValidAddress, eqAddr } from '../lib/wallet';
2222
23−import { isValidGalaxy } from '../lib/lib';
23+import { isValidGalaxy, compose } from '../lib/lib';
24+import { withNetwork } from '../store/network';
2425
2526const buttonTriState = status => {
2627 if (status === null) return 'blue';
@@ -117,11 +118,11 @@ class CreateGalaxy extends React.Component {
117118 state.isAvailable === true;
118119
119120 const esvisible =
120− props.networkType === NETWORK_NAMES.ROPSTEN ||
121− props.networkType === NETWORK_NAMES.MAINNET;
121+ props.networkType === NETWORK_TYPES.ROPSTEN ||
122+ props.networkType === NETWORK_TYPES.MAINNET;
122123
123124 const esdomain =
124− props.networkType === NETWORK_NAMES.ROPSTEN
125+ props.networkType === NETWORK_TYPES.ROPSTEN
125126 ? 'ropsten.etherscan.io'
126127 : 'etherscan.io';
127128
@@ -194,4 +195,4 @@ class CreateGalaxy extends React.Component {
194195 }
195196}
196197
197−export default CreateGalaxy;
198+export default compose(withNetwork)(CreateGalaxy);
src/bridge/views/InviteTicket.js+8−4
@@ -13,7 +13,7 @@ import {
1313import * as azimuth from 'azimuth-js';
1414import * as need from '../lib/need';
1515
16−import { randomPatq } from '../lib/lib';
16+import { randomPatq, compose } from '../lib/lib';
1717import { ROUTE_NAMES } from '../lib/routeNames';
1818import { withHistory } from '../store/history';
1919import {
@@ -28,6 +28,7 @@ import {
2828 TRANSACTION_STATES,
2929} from '../lib/invite';
3030import { generateWallet, startTransactions } from '../lib/invite';
31+import { withNetwork } from '../store/network';
3132
3233class InviteTicket extends React.Component {
3334 constructor(props) {
@@ -305,8 +306,8 @@ class InviteTicket extends React.Component {
305306 realWalletM: realWallet,
306307 realPointM: realPoint,
307308 realTicket: verifyTicket,
308− web3M: web3,
309− contractsM: contracts,
309+ web3,
310+ contracts,
310311 setUrbitWallet,
311312 updateProgress: this.updateProgress,
312313 }).then(() => {
@@ -422,4 +423,7 @@ class InviteTicket extends React.Component {
422423 }
423424}
424425
425−export default withHistory(InviteTicket);
426+export default compose(
427+ withNetwork,
428+ withHistory
429+)(InviteTicket);
src/bridge/views/InvitesManage.js+3−1
@@ -14,6 +14,8 @@ import {
1414 ValidatedSigil,
1515} from '../components/Base';
1616import StatelessTransaction from '../components/StatelessTransaction';
17+import { withNetwork } from '../store/network';
18+import { compose } from '../lib/lib';
1719
1820class InvitesManage extends React.Component {
1921 constructor(props) {
@@ -182,4 +184,4 @@ class InvitesManage extends React.Component {
182184 }
183185}
184186
185−export default InvitesManage;
187+export default compose(withNetwork)(InvitesManage);
src/bridge/views/InvitesSend.js+3−1
@@ -19,6 +19,8 @@ import {
1919 toWei,
2020} from '../lib/txn';
2121import * as tank from '../lib/tank';
22+import { withNetwork } from '../store/network';
23+import { compose } from '../lib/lib';
2224
2325const GAS_PRICE_GWEI = 20; // we pay the premium for faster ux
2426const GAS_LIMIT = 350000;
@@ -491,4 +493,4 @@ class InvitesSend extends React.Component {
491493 }
492494}
493495
494−export default InvitesSend;
496+export default compose(withNetwork)(InvitesSend);
src/bridge/views/IssueChild.js+7−5
@@ -19,11 +19,13 @@ import {
1919
2020import StatelessTransaction from '../components/StatelessTransaction';
2121
22−import { NETWORK_NAMES } from '../lib/network';
22+import { NETWORK_TYPES } from '../lib/network';
2323import { getSpawnCandidate } from '../lib/child';
2424import { canDecodePatp } from '../lib/txn';
2525
2626import { isValidAddress } from '../lib/wallet';
27+import { withNetwork } from '../store/network';
28+import { compose } from '../lib/lib';
2729
2830const setFind = (set, pred) => {
2931 for (const e of set) {
@@ -151,11 +153,11 @@ class IssueChild extends React.Component {
151153 });
152154
153155 const esvisible =
154− props.networkType === NETWORK_NAMES.ROPSTEN ||
155− props.networkType === NETWORK_NAMES.MAINNET;
156+ props.networkType === NETWORK_TYPES.ROPSTEN ||
157+ props.networkType === NETWORK_TYPES.MAINNET;
156158
157159 const esdomain =
158− props.networkType === NETWORK_NAMES.ROPSTEN
160+ props.networkType === NETWORK_TYPES.ROPSTEN
159161 ? 'ropsten.etherscan.io'
160162 : 'etherscan.io';
161163
@@ -253,4 +255,4 @@ class IssueChild extends React.Component {
253255 }
254256}
255257
256−export default IssueChild;
258+export default compose(withNetwork)(IssueChild);
src/bridge/views/Keystore.js+2−1
@@ -10,6 +10,7 @@ import { BRIDGE_ERROR } from '../lib/error';
1010import { ROUTE_NAMES } from '../lib/routeNames';
1111import { withHistory } from '../store/history';
1212import { EthereumWallet } from '../lib/wallet';
13+import { compose } from '../lib/lib';
1314
1415class Keystore extends React.Component {
1516 constructor(props) {
@@ -159,4 +160,4 @@ class Keystore extends React.Component {
159160 }
160161}
161162
162−export default withHistory(Keystore);
163+export default compose(withHistory)(Keystore);
src/bridge/views/Ledger.js+2−1
@@ -11,6 +11,7 @@ import * as secp256k1 from 'secp256k1';
1111import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../lib/ledger';
1212import { ROUTE_NAMES } from '../lib/routeNames';
1313import { withHistory } from '../store/history';
14+import { compose } from '../lib/lib';
1415
1516const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
1617
@@ -217,4 +218,4 @@ class Ledger extends React.Component {
217218 }
218219}
219220
220−export default withHistory(Ledger);
221+export default compose(withHistory)(Ledger);
src/bridge/views/Mnemonic.js+2−1
@@ -13,6 +13,7 @@ import { Row, Col, H1 } from '../components/Base';
1313import { ROUTE_NAMES } from '../lib/routeNames';
1414import { withHistory } from '../store/history';
1515import { DEFAULT_HD_PATH, walletFromMnemonic } from '../lib/wallet';
16+import { compose } from '../lib/lib';
1617
1718class Mnemonic extends React.Component {
1819 constructor(props) {
@@ -160,4 +161,4 @@ class Mnemonic extends React.Component {
160161 }
161162}
162163
163−export default withHistory(Mnemonic);
164+export default compose(withHistory)(Mnemonic);
src/bridge/views/Network.js+59−138
@@ -1,145 +1,66 @@
1−import Maybe from 'folktale/maybe';
21import React from 'react';
2+
33import { Button } from '../components/Base';
44import { InnerLabelDropdown } from '../components/Base';
55import { Row, Col, H1, P } from '../components/Base';
6−import * as azimuth from 'azimuth-js';
7−import Web3 from 'web3';
8−
9−import { CONTRACT_ADDRESSES } from '../lib/contracts';
10−import { NETWORK_NAMES, renderNetworkType } from '../lib/network';
6+import { NETWORK_TYPES, renderNetworkType } from '../lib/network';
117import { ROUTE_NAMES } from '../lib/routeNames';
12−import { withHistory } from '../store/history';
13−
14−class Network extends React.Component {
15− constructor(props) {
16− super(props);
17−
18− this.handleNetworkChange = this.handleNetworkChange.bind(this);
19− }
20−
21− handleNetworkChange(network) {
22− const { setNetworkType, setNetwork } = this.props;
23−
24− if (network === NETWORK_NAMES.LOCAL) {
25− setNetworkType(network);
26−
27− const endpoint = 'ws://localhost:8545';
28− const provider = new Web3.providers.WebsocketProvider(endpoint);
29− const web3 = new Web3(provider);
30− const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.DEV);
31−
32− setNetwork(Maybe.Just(web3), Maybe.Just(contracts));
33− }
34−
35− if (network === NETWORK_NAMES.ROPSTEN) {
36− setNetworkType(network);
37−
38− const endpoint = `https://ropsten.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
39−
40− const provider = new Web3.providers.HttpProvider(endpoint);
41− const web3 = new Web3(provider);
42− const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.ROPSTEN);
43−
44− setNetwork(Maybe.Just(web3), Maybe.Just(contracts));
45− }
46−
47− if (network === NETWORK_NAMES.MAINNET) {
48− setNetworkType(network);
49−
50− const endpoint = `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
51−
52− const provider = new Web3.providers.HttpProvider(endpoint);
53− const web3 = new Web3(provider);
54− const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.MAINNET);
55−
56− setNetwork(Maybe.Just(web3), Maybe.Just(contracts));
57− }
58−
59− if (network === NETWORK_NAMES.OFFLINE) {
60− setNetworkType(network);
61−
62− // NB (jtobin):
63− //
64− // The 'offline' network type targets the mainnet contracts, but does not
65− // actually use a provider to connect. We use a web3 instance to
66− // initalise the contracts, but the network itself is set to Nothing.
67− //
68− // We may want to offer the ability to select a target network for
69− // transactions when offline.
70−
71− // Note: localhost:3456 doesn't actually point to anything, we just need
72− // a provider to initialize the Web3 object
73− const provider = new Web3.providers.HttpProvider('http://localhost:3456');
74− const web3 = new Web3(provider);
75−
76− const target =
77− process.env.NODE_ENV === 'development'
78− ? CONTRACT_ADDRESSES.DEV
79− : CONTRACT_ADDRESSES.MAINNET;
80−
81− const contracts = azimuth.initContracts(web3, target);
82−
83− setNetwork(Maybe.Nothing(), Maybe.Just(contracts));
84− }
85− }
86−
87− getNetworkOptions() {
88− return [
89− {
90− title: 'Main Network (default)',
91− value: NETWORK_NAMES.MAINNET,
92− },
93− {
94− title: 'Local Node',
95− value: NETWORK_NAMES.LOCAL,
96− },
97− {
98− title: 'Ropsten',
99− value: NETWORK_NAMES.ROPSTEN,
100− },
101− {
102− type: 'divider',
103− },
104− {
105− title: 'Offline',
106− value: NETWORK_NAMES.OFFLINE,
107− },
108− ];
109− }
110−
111− render() {
112− const { networkType, history } = this.props;
113− const networkOptions = this.getNetworkOptions();
114−
115− return (
116− <Row>
117− <Col>
118− <H1>{'Select Network'}</H1>
119−
120− <P>
121− {"Please select the Ethereum node you'd like to send " +
122− 'transactions to. For highly valuable keys, please select ' +
123− 'offline mode.'}
124− </P>
125−
126− <InnerLabelDropdown
127− className={'mt-6'}
128− title={'Node:'}
129− handleUpdate={this.handleNetworkChange}
130− options={networkOptions}
131− currentSelectionTitle={renderNetworkType(networkType)}
132− />
133−
134− <Button
135− className={'mt-10'}
136− onClick={() => history.push(ROUTE_NAMES.WALLET)}>
137− {'Continue →'}
138− </Button>
139− </Col>
140− </Row>
141− );
142− }
8+import { useHistory } from '../store/history';
9+import { useNetwork } from '../store/network';
10+
11+const kNetworkOptions = [
12+ {
13+ title: 'Main Network (default)',
14+ value: NETWORK_TYPES.MAINNET,
15+ },
16+ {
17+ title: 'Ropsten',
18+ value: NETWORK_TYPES.ROPSTEN,
19+ },
20+ {
21+ title: 'Local Node',
22+ value: NETWORK_TYPES.LOCAL,
23+ },
24+ {
25+ type: 'divider',
26+ },
27+ {
28+ title: 'Offline',
29+ value: NETWORK_TYPES.OFFLINE,
30+ },
31+];
32+
33+function Network() {
34+ const { networkType, setNetworkType } = useNetwork();
35+ const history = useHistory();
36+
37+ return (
38+ <Row>
39+ <Col>
40+ <H1>{'Select Network'}</H1>
41+
42+ <P>
43+ {"Please select the Ethereum node you'd like to send " +
44+ 'transactions to. For highly valuable keys, please select ' +
45+ 'offline mode.'}
46+ </P>
47+
48+ <InnerLabelDropdown
49+ className={'mt-6'}
50+ title={'Node:'}
51+ handleUpdate={setNetworkType}
52+ options={kNetworkOptions}
53+ currentSelectionTitle={renderNetworkType(networkType)}
54+ />
55+
56+ <Button
57+ className={'mt-10'}
58+ onClick={() => history.push(ROUTE_NAMES.WALLET)}>
59+ {'Continue →'}
60+ </Button>
61+ </Col>
62+ </Row>
63+ );
14364}
14465
145−export default withHistory(Network);
66+export default Network;
src/bridge/views/Point.js+6−1
@@ -11,6 +11,8 @@ import KeysAndMetadata from './Point/KeysAndMetadata';
1111import Actions from './Point/Actions';
1212import { Row, Col, H1, H3 } from '../components/Base';
1313import { withHistory } from '../store/history';
14+import { withNetwork } from '../store/network';
15+import { compose } from '../lib/lib';
1416
1517class Point extends React.Component {
1618 constructor(props) {
@@ -135,4 +137,7 @@ class Point extends React.Component {
135137 }
136138}
137139
138−export default withHistory(Point);
140+export default compose(
141+ withNetwork,
142+ withHistory
143+)(Point);
src/bridge/views/Points.js+8−3
@@ -4,10 +4,12 @@ import { Button } from '../components/Base';
44import * as azimuth from 'azimuth-js';
55
66import PointList from '../components/PointList';
7−import { NETWORK_NAMES } from '../lib/network';
7+import { NETWORK_TYPES } from '../lib/network';
88import { ROUTE_NAMES } from '../lib/routeNames';
99import { withHistory } from '../store/history';
1010import { ETH_ZERO_ADDR, eqAddr } from '../lib/wallet';
11+import { withNetwork } from '../store/network';
12+import { compose } from '../lib/lib';
1113
1214const hasTransferProxy = (cache, point) =>
1315 point in cache ? !eqAddr(cache[point].transferProxy, ETH_ZERO_ADDR) : false;
@@ -17,7 +19,7 @@ class Points extends React.Component {
1719 super(props);
1820
1921 const { networkType } = props;
20− const loading = networkType !== NETWORK_NAMES.OFFLINE;
22+ const loading = networkType !== NETWORK_TYPES.OFFLINE;
2123
2224 this.state = {
2325 points: [],
@@ -255,4 +257,7 @@ class Points extends React.Component {
255257 }
256258}
257259
258−export default withHistory(Points);
260+export default compose(
261+ withNetwork,
262+ withHistory
263+)(Points);
src/bridge/views/PrivateKey.js+2−1
@@ -7,6 +7,7 @@ import { Row, Col, H1 } from '../components/Base';
77import { ROUTE_NAMES } from '../lib/routeNames';
88import { withHistory } from '../store/history';
99import { EthereumWallet } from '../lib/wallet';
10+import { compose } from '../lib/lib';
1011
1112class PrivateKey extends React.Component {
1213 constructor(props) {
@@ -73,4 +74,4 @@ class PrivateKey extends React.Component {
7374 }
7475}
7576
76−export default withHistory(PrivateKey);
77+export default compose(withHistory)(PrivateKey);
src/bridge/views/SentTransaction.js+15−10
@@ -7,8 +7,9 @@ import { Button } from '../components/Base';
77import { ROUTE_NAMES } from '../lib/routeNames';
88import { useHistory } from '../store/history';
99import { BRIDGE_ERROR, renderTxnError } from '../lib/error';
10−import { NETWORK_NAMES } from '../lib/network';
11−import { withTxnConfirmations } from '../store/txnConfirmations';
10+import { NETWORK_TYPES } from '../lib/network';
11+import { useTxnConfirmations } from '../store/txnConfirmations';
12+import { useNetwork } from '../store/network';
1213
1314class Success extends React.Component {
1415 constructor(props) {
@@ -39,11 +40,11 @@ class Success extends React.Component {
3940 const { pending } = state;
4041
4142 const esvisible =
42− networkType === NETWORK_NAMES.ROPSTEN ||
43− networkType === NETWORK_NAMES.MAINNET;
43+ networkType === NETWORK_TYPES.ROPSTEN ||
44+ networkType === NETWORK_TYPES.MAINNET;
4445
4546 const esdomain =
46− networkType === NETWORK_NAMES.ROPSTEN
47+ networkType === NETWORK_TYPES.ROPSTEN
4748 ? 'ropsten.etherscan.io'
4849 : 'etherscan.io';
4950
@@ -109,14 +110,18 @@ const Failure = props => (
109110 </Row>
110111);
111112
112−const SentTransaction = props => {
113+function SentTransaction(props) {
113114 const history = useHistory();
114− const { txnHashCursor, networkType, txnConfirmations } = props;
115+ const { txnConfirmations } = useTxnConfirmations();
116+ const { web3 } = useNetwork();
117+
118+ const { txnHashCursor, networkType } = props;
115119 const { setPointCursor, pointCursor } = props;
116120
117121 const promptKeyfile = history.data && history.data.promptKeyfile;
118122
119− const w3 = need.web3(props);
123+ // TODO: remove need's assumption about accepting the props object
124+ const w3 = need.web3({ web3 });
120125
121126 const result = txnHashCursor.matchWith({
122127 Nothing: _ => {
@@ -174,6 +179,6 @@ const SentTransaction = props => {
174179 {keyfile}
175180 </div>
176181 );
177−};
182+}
178183
179−export default withTxnConfirmations(SentTransaction);
184+export default SentTransaction;
src/bridge/views/SetKeys.js+3−1
@@ -11,6 +11,8 @@ import { attemptSeedDerivation } from '../lib/keys';
1111import * as kg from 'urbit-key-generation/dist/index';
1212
1313import { addHexPrefix } from '../lib/wallet';
14+import { withNetwork } from '../store/network';
15+import { compose } from '../lib/lib';
1416
1517class SetKeys extends React.Component {
1618 constructor(props) {
@@ -196,4 +198,4 @@ class SetKeys extends React.Component {
196198 }
197199}
198200
199−export default SetKeys;
201+export default compose(withNetwork)(SetKeys);
src/bridge/views/SetProxy.js+20−18
@@ -17,25 +17,15 @@ import * as ob from 'urbit-ob';
1717
1818import { PROXY_TYPE, renderProxyType } from '../lib/proxy';
1919
20−import { NETWORK_NAMES } from '../lib/network';
20+import { NETWORK_TYPES } from '../lib/network';
2121
2222import StatelessTransaction from '../components/StatelessTransaction';
2323
2424import { isValidAddress } from '../lib/wallet';
25+import { withNetwork } from '../store/network';
26+import { compose } from '../lib/lib';
2527
26−const SetManagementProxy = props => (
27− <SetProxy {...props} proxyType={PROXY_TYPE.MANAGEMENT_PROXY} />
28−);
29−
30−const SetSpawnProxy = props => (
31− <SetProxy {...props} proxyType={PROXY_TYPE.SPAWN_PROXY} />
32−);
33−
34−const SetTransferProxy = props => (
35− <SetProxy {...props} proxyType={PROXY_TYPE.TRANSFER_PROXY} />
36−);
37−
38−class SetProxy extends React.Component {
28+class _SetProxy extends React.Component {
3929 constructor(props) {
4030 super(props);
4131
@@ -99,11 +89,11 @@ class SetProxy extends React.Component {
9989 const canGenerate = validAddress === true;
10090
10191 const esvisible =
102− props.networkType === NETWORK_NAMES.ROPSTEN ||
103− props.networkType === NETWORK_NAMES.MAINNET;
92+ props.networkType === NETWORK_TYPES.ROPSTEN ||
93+ props.networkType === NETWORK_TYPES.MAINNET;
10494
10595 const esdomain =
106− props.networkType === NETWORK_NAMES.ROPSTEN
96+ props.networkType === NETWORK_TYPES.ROPSTEN
10797 ? 'ropsten.etherscan.io'
10898 : 'etherscan.io';
10999
@@ -179,4 +169,16 @@ class SetProxy extends React.Component {
179169 }
180170}
181171
182−export { SetManagementProxy, SetSpawnProxy, SetTransferProxy };
172+const SetProxy = compose(withNetwork)(_SetProxy);
173+
174+export const SetManagementProxy = props => (
175+ <SetProxy {...props} proxyType={PROXY_TYPE.MANAGEMENT_PROXY} />
176+);
177+
178+export const SetSpawnProxy = props => (
179+ <SetProxy {...props} proxyType={PROXY_TYPE.SPAWN_PROXY} />
180+);
181+
182+export const SetTransferProxy = props => (
183+ <SetProxy {...props} proxyType={PROXY_TYPE.TRANSFER_PROXY} />
184+);
src/bridge/views/Shards.js+2−1
@@ -16,6 +16,7 @@ import * as ob from 'urbit-ob';
1616import { ROUTE_NAMES } from '../lib/routeNames';
1717import { withHistory } from '../store/history';
1818import { urbitWalletFromTicket } from '../lib/wallet';
19+import { compose } from '../lib/lib';
1920
2021const placeholder = len => {
2122 let bytes = window.crypto.getRandomValues(new Uint8Array(len));
@@ -211,4 +212,4 @@ class Shards extends React.Component {
211212 }
212213}
213214
214−export default withHistory(Shards);
215+export default compose(withHistory)(Shards);
src/bridge/views/Ticket.js+2−2
@@ -11,7 +11,7 @@ import {
1111} from '../components/Base';
1212import { Row, Col, H1, P } from '../components/Base';
1313
14−import { randomPatq } from '../lib/lib';
14+import { randomPatq, compose } from '../lib/lib';
1515import { ROUTE_NAMES } from '../lib/routeNames';
1616import { withHistory } from '../store/history';
1717import { urbitWalletFromTicket } from '../lib/wallet';
@@ -150,4 +150,4 @@ class Ticket extends React.Component {
150150 }
151151}
152152
153−export default withHistory(Ticket);
153+export default compose(withHistory)(Ticket);
src/bridge/views/Transfer.js+7−5
@@ -16,9 +16,11 @@ import {
1616import { AddressInput } from '../components/Base';
1717import StatelessTransaction from '../components/StatelessTransaction';
1818
19−import { NETWORK_NAMES } from '../lib/network';
19+import { NETWORK_TYPES } from '../lib/network';
2020
2121import { isValidAddress } from '../lib/wallet';
22+import { withNetwork } from '../store/network';
23+import { compose } from '../lib/lib';
2224
2325class Transfer extends React.Component {
2426 constructor(props) {
@@ -70,11 +72,11 @@ class Transfer extends React.Component {
7072 const canGenerate = validAddress === true;
7173
7274 const esvisible =
73− props.networkType === NETWORK_NAMES.ROPSTEN ||
74− props.networkType === NETWORK_NAMES.MAINNET;
75+ props.networkType === NETWORK_TYPES.ROPSTEN ||
76+ props.networkType === NETWORK_TYPES.MAINNET;
7577
7678 const esdomain =
77− props.networkType === NETWORK_NAMES.ROPSTEN
79+ props.networkType === NETWORK_TYPES.ROPSTEN
7880 ? 'ropsten.etherscan.io'
7981 : 'etherscan.io';
8082
@@ -130,4 +132,4 @@ class Transfer extends React.Component {
130132 }
131133}
132134
133−export default Transfer;
135+export default compose(withNetwork)(Transfer);
src/bridge/views/Trezor.js+2−1
@@ -10,6 +10,7 @@ import * as secp256k1 from 'secp256k1';
1010import { TREZOR_PATH } from '../lib/trezor';
1111import { ROUTE_NAMES } from '../lib/routeNames';
1212import { withHistory } from '../store/history';
13+import { compose } from '../lib/lib';
1314
1415class Trezor extends React.Component {
1516 constructor(props) {
@@ -139,4 +140,4 @@ class Trezor extends React.Component {
139140 }
140141}
141142
142−export default withHistory(Trezor);
143+export default compose(withHistory)(Trezor);
src/bridge/views/ViewPoint.js+2−1
@@ -8,6 +8,7 @@ import { InnerLabel, PointInput, ValidatedSigil } from '../components/Base';
88
99import { ROUTE_NAMES } from '../lib/routeNames';
1010import { withHistory } from '../store/history';
11+import { compose } from '../lib/lib';
1112
1213class ViewPoint extends React.Component {
1314 constructor(props) {
@@ -80,4 +81,4 @@ class ViewPoint extends React.Component {
8081 }
8182}
8283
83−export default withHistory(ViewPoint);
84+export default compose(withHistory)(ViewPoint);
src/bridge/views/Wallet.js+2−1
@@ -7,6 +7,7 @@ import { Row, Col } from '../components/Base';
77import { ROUTE_NAMES } from '../lib/routeNames';
88import { withHistory } from '../store/history';
99import { WALLET_NAMES, renderWalletType } from '../lib/wallet';
10+import { compose } from '../lib/lib';
1011
1112class Wallet extends React.Component {
1213 componentDidMount() {
@@ -104,4 +105,4 @@ class Wallet extends React.Component {
104105 }
105106}
106107
107−export default withHistory(Wallet);
108+export default compose(withHistory)(Wallet);
src/walletgen/Walletgen.js+21−41
@@ -4,7 +4,7 @@ import * as lodash from 'lodash';
44import Header from './components/Header';
55import Footer from './components/Footer';
66
7−import { NETWORK_STATES, PROFILE_STATES, GEN_STATES } from './lib/constants';
7+import { PROFILE_STATES, GEN_STATES } from './lib/constants';
88
99import PrivacyPolicy from './views/PrivacyPolicy';
1010import Welcome from './views/Welcome';
@@ -19,8 +19,7 @@ import Done from './views/Done';
1919
2020import './style/index.css';
2121
22−const checkNetwork = () =>
23− navigator.onLine ? NETWORK_STATES.ONLINE : NETWORK_STATES.OFFLINE;
22+import { OnlineProvider } from '../bridge/store/online';
2423
2524const ROUTES = [
2625 { path: '/Welcome', Component: Welcome },
@@ -41,7 +40,6 @@ class Walletgen extends React.Component {
4140 constructor(props) {
4241 super(props);
4342 this.state = {
44− network: checkNetwork(),
4543 'profile.state': PROFILE_STATES.NOSTART,
4644 'profile.value': null,
4745 'profile.shipCount': 0,
@@ -65,18 +63,9 @@ class Walletgen extends React.Component {
6563 };
6664 }
6765
68− componentDidMount = () => {
69− window.addEventListener('online', () =>
70− this.setState({ network: NETWORK_STATES.ONLINE })
71− );
72− window.addEventListener('offline', () =>
73− this.setState({ network: NETWORK_STATES.OFFLINE })
74− );
75− };
76−
7766 setGlobalState = state => this.setState(state);
7867
79− router = (network, path) => {
68+ router = path => {
8069 const route = key => {
8170 const { Component } = lodash.find(ROUTES, { path: key });
8271 const globals = {
@@ -87,39 +76,30 @@ class Walletgen extends React.Component {
8776 };
8877
8978 return route(path);
90−
91− // return network === NETWORK_STATES.ONLINE
92− // ? <NetworkWarning />
93− // : !(network === NETWORK_STATES.OFFLINE)
94− // ? <h1>Detecting your network state.</h1>
95− // : route(path)
9679 };
9780
9881 render() {
99− const { network, route, currentStep, lastRoute } = this.state;
82+ const { route, currentStep, lastRoute } = this.state;
10083
101− const routed = this.router(network, route); // hacky prod
102− // const routed = this.router(NETWORK_STATES.OFFLINE, true, route) // hacky dev
84+ const routed = this.router(route);
10385
10486 return (
105− <main className={'container'}>
106− <Header
107− network={network}
108− currentStep={currentStep}
109− totalSteps={TOTAL_STEPS}
110− />
111−
112− <div className={'row wrapper'}>
113− <div className={'col-'}>{routed}</div>
114− <div className={'push'} />
115− </div>
116−
117− <Footer
118− route={route}
119− lastRoute={lastRoute}
120− setGlobalState={this.setGlobalState}
121− />
122− </main>
87+ <OnlineProvider>
88+ <main className={'container'}>
89+ <Header currentStep={currentStep} totalSteps={TOTAL_STEPS} />
90+
91+ <div className={'row wrapper'}>
92+ <div className={'col-'}>{routed}</div>
93+ <div className={'push'} />
94+ </div>
95+
96+ <Footer
97+ route={route}
98+ lastRoute={lastRoute}
99+ setGlobalState={this.setGlobalState}
100+ />
101+ </main>
102+ </OnlineProvider>
123103 );
124104 }
125105}
src/walletgen/components/Header.js+35−24
@@ -1,7 +1,7 @@
11import React from 'react';
2−import { seq } from '../lib/lib';
32
4−import { NETWORK_STATES } from '../lib/constants';
3+import { useOnline, ONLINE_STATUS } from '../../bridge/store/online';
4+import { seq } from '../../bridge/lib/lib';
55
66const StatusIndicator = props => (
77 <div className={`flex h-9 align-center ${props.c2} flex-shrink-0`}>
@@ -12,27 +12,38 @@ const StatusIndicator = props => (
1212 </div>
1313);
1414
15−const ChooseIndicator = props =>
16− props.network === NETWORK_STATES.ONLINE ? (
17− <StatusIndicator
18− message={'You are online.'}
19− c1={'bg-red'}
20− c2={'bg-red'}
21− error
22− />
23− ) : props.network === NETWORK_STATES.OFFLINE ? (
24− <StatusIndicator
25− message={'You are offline.'}
26− c1={'bg-green'}
27− c2={'bg-green'}
28− />
29− ) : (
30− <StatusIndicator
31− message={'Checking network connection..'}
32− c1={'bg-orange'}
33− c2={'bg-orange'}
34− />
35− );
15+function ChooseIndicator(props) {
16+ const online = useOnline();
17+
18+ switch (online) {
19+ case ONLINE_STATUS.ONLINE:
20+ return (
21+ <StatusIndicator
22+ message={'You are online.'}
23+ c1={'bg-red'}
24+ c2={'bg-red'}
25+ error
26+ />
27+ );
28+ case ONLINE_STATUS.OFFLINE:
29+ return (
30+ <StatusIndicator
31+ message={'You are offline.'}
32+ c1={'bg-green'}
33+ c2={'bg-green'}
34+ />
35+ );
36+ case ONLINE_STATUS.UNKNOWN:
37+ default:
38+ return (
39+ <StatusIndicator
40+ message={'Checking network connection..'}
41+ c1={'bg-orange'}
42+ c2={'bg-orange'}
43+ />
44+ );
45+ }
46+}
3647
3748const ProgressIndicator = ({ currentStep, totalSteps }) => (
3849 <div className={'relative h-9 col- mb-0'}>
@@ -61,7 +72,7 @@ const Header = props => (
6172 totalSteps={props.totalSteps}
6273 currentStep={props.currentStep}
6374 />
64− <ChooseIndicator network={props.network} />
75+ <ChooseIndicator />
6576 </div>
6677 <div className={'col-md-8 h-15 flex align-center'}>
6778 <h1 className={'m-0 p-0'}>{'Wallet Generator'}</h1>
src/walletgen/lib/constants.js−7
@@ -11,12 +11,6 @@ const GALAXY_ENTROPY_BITS = 384;
1111
1212const SEED_ENTROPY_BITS = 128;
1313
14−const NETWORK_STATES = {
15− ONLINE: Symbol('ONLINE'),
16− OFFLINE: Symbol('OFFLINE'),
17− UNKNOWN: Symbol('UNKNOWN'),
18−};
19−
2014const GEN_STATES = {
2115 ENY_NOSTART: Symbol('ENY_NOSTART'),
2216 ENY_PENDING: Symbol('ENY_PENDING'),
@@ -48,7 +42,6 @@ const PROFILE_STATES = {
4842};
4943
5044export {
51− NETWORK_STATES,
5245 GEN_STATES,
5346 BUTTON_STATES,
5447 PROFILE_STATES,
src/walletgen/lib/lib.js+1−6
@@ -66,9 +66,4 @@ const generateWallet = async (point, ticket, boot) => {
6666const generateOwnershipWallet = (ship, ticket) =>
6767 kg.generateOwnershipWallet({ ship, ticket });
6868
69−const seq = num => Array.from(Array(num), (_, i) => i);
70−
71−const compose = (...fs) =>
72− fs.reduceRight((pF, nF) => (...args) => nF(pF(...args)), v => v);
73−
74−export { makeTicket, generateWallet, generateOwnershipWallet, compose, seq };
69+export { makeTicket, generateWallet, generateOwnershipWallet };
src/walletgen/views/Upload.js+1−1
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
33import Button from '../components/Button';
44import UploadButton from '../components/UploadButton';
55
6−import { compose } from '../lib/lib';
6+import { compose } from '../../bridge/lib/lib';
77
88import { PROFILE_STATES } from '../lib/constants';
99