on master help sign in
Merge pull request #152 from urbit/feat/simpler-routing

Simpler Routing

commit77325bbfd92cd9fa01219168f4aa43ed0f90e841 authorMatt Condon <mattgcondon@gmail.com> dateWed 12 Jun 2019, 11:21 (7 years ago) parent5dd541f0591411
38 files changed+607−689
package-lock.json+11−103 package.json+2−4 src/bridge/Bridge.js+116−192 src/bridge/components/DropdownItem.js+1−3 src/bridge/components/Header.js+46−43 src/bridge/components/HorizontalSelector.js+1−3 src/bridge/components/OldHeader.js+2−2 src/bridge/components/RenderedPoint.js+1−1 src/bridge/components/StatelessTransaction.js+6−4 src/bridge/components/icons/Chevron.js+1−3 src/bridge/lib/flags.js+1 src/bridge/lib/invite.js+1−1 src/bridge/lib/keys.js+3−3 src/bridge/lib/routeNames.js+33 src/bridge/lib/router.js+92−134 src/bridge/lib/wallet.js+1−1 src/bridge/lib/web3.js+23 src/bridge/store/history.js+73 src/bridge/store/txnConfirmations.js+39 src/bridge/views/GenKeyfile.js+1−1 src/bridge/views/InviteTicket.js+6−5 src/bridge/views/Keystore.js+5−7 src/bridge/views/Landing.js+50−57 src/bridge/views/Ledger.js+5−7 src/bridge/views/Mnemonic.js+5−7 src/bridge/views/Network.js+7−10 src/bridge/views/Point.js+4−16 src/bridge/views/Point/Actions.js+18−17 src/bridge/views/Points.js+12−11 src/bridge/views/PrivateKey.js+5−7 src/bridge/views/SentTransaction.js+9−15 src/bridge/views/SetKeys.js+1−1 src/bridge/views/Shards.js+6−8 src/bridge/views/Ticket.js+5−7 src/bridge/views/Trezor.js+5−7 src/bridge/views/ViewPoint.js+5−5 src/bridge/views/Wallet.js+4−3 src/walletgen/lib/lib.js+1−1
package-lock.json+11−103
@@ -7702,28 +7702,6 @@
77027702 "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
77037703 "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
77047704 },
7705− "history": {
7706− "version": "4.7.2",
7707− "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz",
7708− "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==",
7709− "requires": {
7710− "invariant": "^2.2.1",
7711− "loose-envify": "^1.2.0",
7712− "resolve-pathname": "^2.2.0",
7713− "value-equal": "^0.4.0",
7714− "warning": "^3.0.0"
7715− },
7716− "dependencies": {
7717− "warning": {
7718− "version": "3.0.0",
7719− "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
7720− "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
7721− "requires": {
7722− "loose-envify": "^1.0.0"
7723− }
7724− }
7725− }
7726− },
77277705 "hmac-drbg": {
77287706 "version": "1.0.1",
77297707 "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -7739,11 +7717,6 @@
77397717 "resolved": "http://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
77407718 "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA=="
77417719 },
7742− "hoist-non-react-statics": {
7743− "version": "2.5.5",
7744− "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
7745− "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
7746− },
77477720 "home-or-tmp": {
77487721 "version": "2.0.0",
77497722 "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
@@ -8515,11 +8488,6 @@
85158488 "resolved": "https://registry.npmjs.org/immer/-/immer-1.7.2.tgz",
85168489 "integrity": "sha512-4Urocwu9+XLDJw4Tc6ZCg7APVjjLInCFvO4TwGsAYV5zT6YYSor14dsZR0+0tHlDIN92cFUOq+i7fC00G5vTxA=="
85178490 },
8518− "immutable": {
8519− "version": "4.0.0-rc.12",
8520− "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz",
8521− "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A=="
8522− },
85238491 "import-cwd": {
85248492 "version": "2.1.0",
85258493 "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@@ -15979,14 +15947,14 @@
1597915947 }
1598015948 },
1598115949 "react": {
15982− "version": "16.6.3",
15983− "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz",
15984− "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==",
15950+ "version": "16.8.6",
15951+ "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz",
15952+ "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==",
1598515953 "requires": {
1598615954 "loose-envify": "^1.1.0",
1598715955 "object-assign": "^4.1.1",
1598815956 "prop-types": "^15.6.2",
15989− "scheduler": "^0.11.2"
15957+ "scheduler": "^0.13.6"
1599015958 }
1599115959 },
1599215960 "react-app-polyfill": {
@@ -16188,14 +16156,14 @@
1618816156 }
1618916157 },
1619016158 "react-dom": {
16191− "version": "16.6.3",
16192− "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz",
16193− "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==",
16159+ "version": "16.8.6",
16160+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz",
16161+ "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==",
1619416162 "requires": {
1619516163 "loose-envify": "^1.1.0",
1619616164 "object-assign": "^4.1.1",
1619716165 "prop-types": "^15.6.2",
16198− "scheduler": "^0.11.2"
16166+ "scheduler": "^0.13.6"
1619916167 }
1620016168 },
1620116169 "react-error-overlay": {
@@ -16203,48 +16171,6 @@
1620316171 "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.1.tgz",
1620416172 "integrity": "sha512-gJcOyak5ri9mzhpFObSAkCqNNBCVZE8LaR/mX37agULgSL0dobgX11NVSIetvNzhHMkr6CIqV0xiutvvcjBtkw=="
1620516173 },
16206− "react-router": {
16207− "version": "4.3.1",
16208− "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
16209− "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
16210− "requires": {
16211− "history": "^4.7.2",
16212− "hoist-non-react-statics": "^2.5.0",
16213− "invariant": "^2.2.4",
16214− "loose-envify": "^1.3.1",
16215− "path-to-regexp": "^1.7.0",
16216− "prop-types": "^15.6.1",
16217− "warning": "^4.0.1"
16218− },
16219− "dependencies": {
16220− "isarray": {
16221− "version": "0.0.1",
16222− "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
16223− "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
16224− },
16225− "path-to-regexp": {
16226− "version": "1.7.0",
16227− "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
16228− "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
16229− "requires": {
16230− "isarray": "0.0.1"
16231− }
16232− }
16233− }
16234− },
16235− "react-router-dom": {
16236− "version": "4.3.1",
16237− "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
16238− "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
16239− "requires": {
16240− "history": "^4.7.2",
16241− "invariant": "^2.2.4",
16242− "loose-envify": "^1.3.1",
16243− "prop-types": "^15.6.1",
16244− "react-router": "^4.3.1",
16245− "warning": "^4.0.1"
16246− }
16247− },
1624816174 "react-scripts": {
1624916175 "version": "2.1.2",
1625016176 "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-2.1.2.tgz",
@@ -16951,11 +16877,6 @@
1695116877 "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
1695216878 "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
1695316879 },
16954− "resolve-pathname": {
16955− "version": "2.2.0",
16956− "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
16957− "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
16958− },
1695916880 "resolve-url": {
1696016881 "version": "0.2.1",
1696116882 "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@@ -17432,9 +17353,9 @@
1743217353 }
1743317354 },
1743417355 "scheduler": {
17435− "version": "0.11.3",
17436− "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.3.tgz",
17437− "integrity": "sha512-i9X9VRRVZDd3xZw10NY5Z2cVMbdYg6gqFecfj79USv1CFN+YrJ3gIPRKf1qlY+Sxly4djoKdfx1T+m9dnRB8kQ==",
17356+ "version": "0.13.6",
17357+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
17358+ "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
1743817359 "requires": {
1743917360 "loose-envify": "^1.1.0",
1744017361 "object-assign": "^4.1.1"
@@ -19517,11 +19438,6 @@
1951719438 "spdx-expression-parse": "^3.0.0"
1951819439 }
1951919440 },
19520− "value-equal": {
19521− "version": "0.4.0",
19522− "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
19523− "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
19524− },
1952519441 "vary": {
1952619442 "version": "1.1.2",
1952719443 "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -19576,14 +19492,6 @@
1957619492 "makeerror": "1.0.x"
1957719493 }
1957819494 },
19579− "warning": {
19580− "version": "4.0.3",
19581− "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
19582− "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
19583− "requires": {
19584− "loose-envify": "^1.0.0"
19585− }
19586− },
1958719495 "watch": {
1958819496 "version": "0.18.0",
1958919497 "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz",
package.json+2−4
@@ -29,16 +29,14 @@
2929 "ethereumjs-tx": "^1.3.7",
3030 "file-saver": "^2.0.0",
3131 "folktale": "^2.3.1",
32− "immutable": "^4.0.0-rc.12",
3332 "jszip": "^3.1.5",
3433 "keccak": "^1.4.0",
3534 "keythereum": "^1.0.4",
3635 "lodash": "^4.17.11",
3736 "more-entropy": "^0.0.7",
3837 "prop-types": "^15.6.2",
39− "react": "^16.6.0",
40− "react-dom": "^16.6.0",
41− "react-router-dom": "^4.3.1",
38+ "react": "^16.8.6",
39+ "react-dom": "^16.8.6",
4240 "react-scripts": "^2.1.1",
4341 "secp256k1": "^3.5.2",
4442 "sigil-js": "github:urbit/sigil-js#f7f29c5",
src/bridge/Bridge.js+116−192
@@ -1,16 +1,16 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import * as lodash from 'lodash';
3−import * as azimuth from 'azimuth-js';
4−import { Stack } from 'immutable';
5−import { CONTRACT_ADDRESSES } from './lib/contracts';
61import React from 'react';
2+import { Just, Nothing } from 'folktale/maybe';
3+import { includes } from 'lodash';
74
8−import Web3 from 'web3';
95import Footer from './components/Footer';
106import Header from './components/Header';
117import { Container, Row, Col } from './components/Base';
128
13−import { ROUTE_NAMES, router } from './lib/router';
9+import { router } from './lib/router';
10+import { initWeb3 } from './lib/web3';
11+import { ROUTE_NAMES } from './lib/routeNames';
12+import { HistoryProvider, withHistory, useHistory } from './store/history';
13+import { TxnConfirmationsProvider } from './store/txnConfirmations';
1414import { NETWORK_NAMES } from './lib/network';
1515import {
1616 WALLET_NAMES,
@@ -19,35 +19,54 @@ import {
1919 addressFromSecp256k1Public,
2020} from './lib/wallet';
2121import { BRIDGE_ERROR } from './lib/error';
22−
23−const initWeb3 = networkType => {
24− if (networkType === NETWORK_NAMES.MAINNET) {
25− const endpoint = `https://mainnet.infura.io/v3/${
26− process.env.REACT_APP_INFURA_ENDPOINT
27− }`;
28−
29− const provider = new Web3.providers.HttpProvider(endpoint);
30− const web3 = new Web3(provider);
31− const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.MAINNET);
32− return { web3: web3, contracts: contracts };
33− } else if (networkType === NETWORK_NAMES.LOCAL) {
34− const protocol = process.env.NODE_ENV === 'development' ? 'ws' : 'wss';
35− const endpoint = `${protocol}://localhost:8545`;
36− const provider = new Web3.providers.WebsocketProvider(endpoint);
37− const web3 = new Web3(provider);
38− const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.DEV);
39− return { web3: web3, contracts: contracts };
40− }
22+import { isLocal } from './lib/flags';
23+
24+// NB(shrugs): toggle this variable to use the default local state.
25+// don't commit changes to this line, but there shouldn't be a problem
26+// if you do because we'll never stub on a production build.
27+const shouldStubLocal = false;
28+const isStubbed = isLocal && shouldStubLocal;
29+const kInitialRoutes = isStubbed
30+ ? [
31+ { name: ROUTE_NAMES.SHIPS },
32+ { name: ROUTE_NAMES.WALLET },
33+ { name: ROUTE_NAMES.NETWORK },
34+ { name: ROUTE_NAMES.LANDING },
35+ ]
36+ : [{ name: ROUTE_NAMES.LANDING }];
37+
38+// the router itself is a component that renders a specific view
39+// based on the latest history state
40+const Router = function(props) {
41+ const history = useHistory();
42+ const Route = router(history.peek());
43+
44+ return <Route {...props} />;
4145};
4246
47+// NB(shrugs): separate component because it needs withHistory
48+// this will be must better structured as part of the UI overhaul
49+const VariableWidthColumn = withHistory(({ history, children }) => (
50+ // For the invite acceptance flow, widen the screen to use the full
51+ // container, and hide the breadcrumbs
52+ <Col
53+ className={
54+ history.includes(ROUTE_NAMES.INVITE_TICKET)
55+ ? 'col-md-12'
56+ : 'col-md-offset-1 col-md-10'
57+ }
58+ style={
59+ history.includes(ROUTE_NAMES.INVITE_TICKET) ? {} : { maxWidth: '620px' }
60+ }>
61+ {children}
62+ </Col>
63+));
64+
4365class Bridge extends React.Component {
4466 constructor(props) {
4567 super(props);
4668
47− const networkType =
48− process.env.NODE_ENV === 'development'
49− ? NETWORK_NAMES.LOCAL
50− : NETWORK_NAMES.MAINNET;
69+ const networkType = isLocal ? NETWORK_NAMES.LOCAL : NETWORK_NAMES.MAINNET;
5170
5271 // Sidestepping full network selection to allow for point viewing,
5372 // but still respecting the networkType
@@ -67,9 +86,6 @@ class Bridge extends React.Component {
6786 // 'EthereumWallet'.
6887
6988 this.state = {
70− // routes
71− routeCrumbs: Stack([ROUTE_NAMES.LANDING]),
72− routeData: {},
7389 // network
7490 networkType: networkType,
7591 web3: Just(web3),
@@ -90,9 +106,6 @@ class Bridge extends React.Component {
90106 txnConfirmations: {},
91107 };
92108
93− this.pushRoute = this.pushRoute.bind(this);
94− this.popRoute = this.popRoute.bind(this);
95− this.skipRoute = this.skipRoute.bind(this);
96109 this.setNetworkType = this.setNetworkType.bind(this);
97110 this.setNetwork = this.setNetwork.bind(this);
98111 this.setWalletType = this.setWalletType.bind(this);
@@ -102,84 +115,31 @@ class Bridge extends React.Component {
102115 this.setAuthMnemonic = this.setAuthMnemonic.bind(this);
103116 this.setPointCursor = this.setPointCursor.bind(this);
104117 this.setTxnHashCursor = this.setTxnHashCursor.bind(this);
105− this.setTxnConfirmations = this.setTxnConfirmations.bind(this);
106118 this.setNetworkSeedCache = this.setNetworkSeedCache.bind(this);
107119 this.addToPointCache = this.addToPointCache.bind(this);
108120 }
109121
110122 componentDidMount() {
111− window.history.pushState(null, null, null);
112−
113− window.onpopstate = e => {
114− window.history.pushState(null, null, null);
115− this.popRoute();
116− };
117−
118− // NB (jtobin):
123+ // NB (jtobin, shrugs):
119124 //
120125 // If running in development, the following can be tweaked to get you set
121126 // up with a desirable initial state.
127+ if (isStubbed) {
128+ const mnemonic = process.env.REACT_APP_DEV_MNEMONIC;
129+ const hdpath = process.env.REACT_APP_HD_PATH;
122130
123− // if (process.env.NODE_ENV === 'development') {
124− //
125− // const socket = 'ws://localhost:8545'
126− // const provider = new Web3.providers.WebsocketProvider(socket)
127− // const web3 = new Web3(provider)
128− // const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.DEV)
129− //
130− // const mnemonic = process.env.REACT_APP_DEV_MNEMONIC
131− // const hdpath = process.env.REACT_APP_HD_PATH
132− //
133− // this.setState({
134− // routeCrumbs: Stack([
135− // // ROUTE_NAMES.CREATE_GALAXY,
136− // // ROUTE_NAMES.SHIP,
137− // ROUTE_NAMES.SHIPS,
138− // // ROUTE_NAMES.MNEMONIC,
139− // ROUTE_NAMES.WALLET,
140− // ROUTE_NAMES.NETWORK,
141− // ROUTE_NAMES.LANDING
142− // ]),
143− // networkType: NETWORK_NAMES.LOCAL,
144− // pointCursor: Just(0),
145− // web3: Just(web3),
146− // contracts: Just(contracts),
147− // walletType: WALLET_NAMES.MNEMONIC,
148− // wallet: walletFromMnemonic(mnemonic, hdpath),
149− // urbitWallet: Nothing(),
150− // authMnemonic: Just('benefit crew supreme gesture quantum web media hazard theory mercy wing kitten')
151− // })
152− // }
153− }
154−
155− pushRoute(symbol, routeData) {
156− if (lodash.includes(ROUTE_NAMES, symbol)) {
157− this.setState((state, _) => ({
158− routeCrumbs: state.routeCrumbs.push(symbol),
159− routeData: routeData,
160− }));
161−
162− // Scroll to top of page with each route transition.
163− window.scrollTo(0, 0);
164− } else {
165− throw BRIDGE_ERROR.INVALID_ROUTE;
131+ this.setState({
132+ pointCursor: Just(0),
133+ walletType: WALLET_NAMES.MNEMONIC,
134+ wallet: walletFromMnemonic(mnemonic, hdpath),
135+ urbitWallet: Nothing(),
136+ authMnemonic: Just(mnemonic),
137+ });
166138 }
167139 }
168140
169− skipRoute(len) {
170− this.setState((state, _) => ({
171− routeCrumbs: state.routeCrumbs.skip(len),
172− }));
173− }
174−
175− popRoute() {
176− this.setState((state, _) => ({
177− routeCrumbs: state.routeCrumbs.shift(),
178− }));
179− }
180−
181141 setNetworkType(symbol) {
182− if (lodash.includes(NETWORK_NAMES, symbol)) {
142+ if (includes(NETWORK_NAMES, symbol)) {
183143 this.setState({
184144 networkType: symbol,
185145 });
@@ -203,7 +163,7 @@ class Bridge extends React.Component {
203163 }
204164
205165 setWalletType(symbol) {
206− if (lodash.includes(WALLET_NAMES, symbol)) {
166+ if (includes(WALLET_NAMES, symbol)) {
207167 this.setState({
208168 walletType: symbol,
209169 });
@@ -260,23 +220,8 @@ class Bridge extends React.Component {
260220 this.setState({ txnHashCursor });
261221 }
262222
263− setTxnConfirmations(txnHash, txnConfirmations) {
264− this.setState((prevState, _) => {
265− return prevState.routeCrumbs.peek() === ROUTE_NAMES.SENT_TRANSACTION
266− ? {
267− txnConfirmations: {
268− ...prevState.txnConfirmations,
269− [txnHash]: txnConfirmations,
270− },
271− }
272− : null;
273− });
274− }
275−
276223 render() {
277224 const {
278− routeCrumbs,
279− routeData,
280225 networkType,
281226 walletType,
282227 walletHdPath,
@@ -288,87 +233,66 @@ class Bridge extends React.Component {
288233 pointCursor,
289234 pointCache,
290235 txnHashCursor,
291− txnConfirmations,
292236 web3,
293237 contracts,
294238 } = this.state;
295239
296− const View = router(routeCrumbs.peek());
297−
298− // For the invite acceptance flow, widen the screen to use the full
299− // container, and hide the breadcrumbs
300− const colClass = routeCrumbs.contains(ROUTE_NAMES.INVITE_TICKET)
301− ? 'col-md-12'
302− : 'col-md-offset-1 col-md-10';
303−
304− const colStyle = routeCrumbs.contains(ROUTE_NAMES.INVITE_TICKET)
305− ? {}
306− : { maxWidth: '620px' };
307−
308− const showCrumbs = routeCrumbs.contains(ROUTE_NAMES.INVITE_TICKET)
309− ? false
310− : true;
311−
312240 return (
313− <Container>
314− <Row>
315− <Col className={colClass} style={colStyle}>
316− <Header
317− routeCrumbs={routeCrumbs}
318− skipRoute={this.skipRoute}
319− networkType={networkType}
320− wallet={wallet}
321− showCrumbs={showCrumbs}
322− pointCursor={pointCursor}
323− />
324−
325− <Row className={'row wrapper'}>
326− <View
327− // router
328− pushRoute={this.pushRoute}
329− popRoute={this.popRoute}
330− routeData={routeData}
331− // network
332− setNetworkType={this.setNetworkType}
333− setNetwork={this.setNetwork}
334− networkType={networkType}
335− web3={web3}
336− contracts={contracts}
337− // wallet
338− setWalletType={this.setWalletType}
339− setWalletHdPath={this.setWalletHdPath}
340− setWallet={this.setWallet}
341− walletType={walletType}
342− walletHdPath={walletHdPath}
343− wallet={wallet}
344− // urbit wallet
345− urbitWallet={urbitWallet}
346− setUrbitWallet={this.setUrbitWallet}
347− authMnemonic={authMnemonic}
348− setAuthMnemonic={this.setAuthMnemonic}
349− // point
350− setPointCursor={this.setPointCursor}
351− addToPointCache={this.addToPointCache}
352− pointCursor={pointCursor}
353− pointCache={pointCache}
354− networkSeedCache={networkSeedCache}
355− networkRevisionCache={networkRevisionCache}
356− setNetworkSeedCache={this.setNetworkSeedCache}
357− // txn
358− onSent={this.setTxnHashCursor}
359− setTxnHashCursor={this.setTxnHashCursor}
360− txnHashCursor={txnHashCursor}
361− setTxnConfirmations={this.setTxnConfirmations}
362− txnConfirmations={txnConfirmations}
363− />
364−
365− <div className={'push'} />
241+ <HistoryProvider initialRoutes={kInitialRoutes}>
242+ <TxnConfirmationsProvider>
243+ <Container>
244+ <Row>
245+ <VariableWidthColumn>
246+ <Header
247+ skipRoute={this.skipRoute}
248+ networkType={networkType}
249+ wallet={wallet}
250+ pointCursor={pointCursor}
251+ />
252+
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>
289+
290+ <Footer />
291+ </VariableWidthColumn>
366292 </Row>
367−
368− <Footer />
369− </Col>
370− </Row>
371− </Container>
293+ </Container>
294+ </TxnConfirmationsProvider>
295+ </HistoryProvider>
372296 );
373297 }
374298}
src/bridge/components/DropdownItem.js+1−3
@@ -10,9 +10,7 @@ const DropdownItem = props => {
1010 <button
1111 disabled={props.disabled}
1212 style={props.style}
13− className={`h-10 m-0 p-0 ph-2 bg-white flex align-center items-start bs-solid brw-2 blw-2 btw-0 bbw-0 sans ${_disabled} ${
14− props.className
15− }`}
13+ className={`h-10 m-0 p-0 ph-2 bg-white flex align-center items-start bs-solid brw-2 blw-2 btw-0 bbw-0 sans ${_disabled} ${props.className}`}
1614 onClick={props.onClick}>
1715 {props.children}
1816 </button>
src/bridge/components/Header.js+46−43
@@ -1,58 +1,61 @@
11import React from 'react';
22import { Button, Chevron } from '../components/Base';
33
4−import { renderRoute } from '../lib/router';
4+import { getRouteBreadcrumb } from '../lib/router';
5+import { ROUTE_NAMES } from '../lib/routeNames';
6+import { useHistory } from '../store/history';
57import { isLast } from '../lib/lib';
68
7−const Crumbs = props => {
8− const { routeCrumbs, skipRoute } = props;
9− const history = routeCrumbs.reverse();
9+function Crumbs(props) {
10+ const history = useHistory();
11+
12+ return (
13+ <>
14+ {history.routes.map((route, idx) => {
15+ return (
16+ <div key={`history-${idx}`} className={'flex items-center'}>
17+ <Button
18+ prop-type={'link'}
19+ prop-size={'sm'}
20+ key={`history-button-${idx}`}
21+ onClick={() => history.pop(history.size - idx - 1)}>
22+ {getRouteBreadcrumb(props, route)}
23+ </Button>
24+
25+ {isLast(history.size, idx) ? (
26+ <div />
27+ ) : (
28+ <Chevron className={'h-4 mh-2'} />
29+ )}
30+ </div>
31+ );
32+ })}
33+ </>
34+ );
35+}
36+
37+function Header(props) {
38+ const history = useHistory();
39+
40+ const showCrumbs = !history.includes(ROUTE_NAMES.INVITE_TICKET);
1041
11− // FIXME probably more straightforward to render them normally and just
12− // return the reversed array of renders
13− const rendered = history.map((route, idx) => {
42+ if (showCrumbs) {
1443 return (
15− <div className={'flex items-center'} key={`history-${idx}`}>
16− <Button
17− prop-type={'link'}
18− prop-size={'sm'}
19− key={`history-button-${idx}`}
20− onClick={() => skipRoute(history.size - idx - 1)}>
21− {renderRoute(props, route)}
22− </Button>
23−
24− {isLast(history.size, idx) ? (
25− <div />
26− ) : (
27− <Chevron className={'h-4 mh-2'} />
28− )}
44+ <div className={'flex items-center h-10'}>
45+ <Crumbs
46+ networkType={props.networkType}
47+ wallet={props.wallet}
48+ pointCursor={props.pointCursor}
49+ />
2950 </div>
3051 );
31− });
32−
33− return rendered;
34−};
35−
36−const Header = props => {
37− const crumbs = (
38− <div className={'flex items-center h-10'}>
39− <Crumbs
40− routeCrumbs={props.routeCrumbs}
41− skipRoute={props.skipRoute}
42− networkType={props.networkType}
43− wallet={props.wallet}
44− pointCursor={props.pointCursor}
45− />
46− </div>
47− );
52+ }
4853
49− const noCrumbs = (
50− <h2 className="mt-9 clickable" onClick={() => props.skipRoute(1)}>
54+ return (
55+ <h2 className="mt-9 clickable" onClick={() => history.pop()}>
5156 Bridge
5257 </h2>
5358 );
54−
55− return props.showCrumbs ? crumbs : noCrumbs;
56−};
59+}
5760
5861export default Header;
src/bridge/components/HorizontalSelector.js+1−3
@@ -22,9 +22,7 @@ class HorizontalSelector extends React.Component {
2222 let active = opt.value === this.state.option ? 'btn-primary active' : '';
2323 return (
2424 <button
25− className={`btn ${active} h-10 ph-4 flex sans ${
26− this.props.className
27− }`}
25+ className={`btn ${active} h-10 ph-4 flex sans ${this.props.className}`}
2826 value={opt.value}
2927 onClick={() => this.selectOption(opt.value)}>
3028 {opt.title}
src/bridge/components/OldHeader.js+2−2
@@ -1,7 +1,7 @@
11import React from 'react';
22import { Button, Chevron } from '../components/Base';
33
4−import { renderRoute } from '../lib/router';
4+import { getRouteBreadcrumb } from '../lib/router';
55import { isLast } from '../lib/lib';
66
77const Crumbs = props => {
@@ -19,7 +19,7 @@ const Crumbs = props => {
1919 color={'black'}
2020 key={`history-button-${idx}`}
2121 onClick={() => skipRoute(history.size - idx - 1)}>
22− {`${renderRoute(props, route)}`}
22+ {`${getRouteBreadcrumb(props, route)}`}
2323 </Button>
2424
2525 {isLast(history.size, idx) ? (
src/bridge/components/RenderedPoint.js+1−1
@@ -5,7 +5,7 @@ import { Button } from './Base';
55import { pour } from 'sigil-js';
66import * as ob from 'urbit-ob';
77
8−import { ROUTE_NAMES } from '../lib/router';
8+import { ROUTE_NAMES } from '../lib/routeNames';
99import ReactSVGComponents from './ReactSVGComponents';
1010
1111const RenderedPoint = props => {
src/bridge/components/StatelessTransaction.js+6−4
@@ -9,7 +9,8 @@ import { CheckboxButton, Input, InnerLabel, InnerLabelDropdown } from './Base';
99import { Warning } from '../components/Base';
1010
1111import { BRIDGE_ERROR } from '../lib/error';
12−import { ROUTE_NAMES } from '../lib/router';
12+import { ROUTE_NAMES } from '../lib/routeNames';
13+import { withHistory } from '../store/history';
1314import * as tank from '../lib/tank';
1415
1516import {
@@ -20,6 +21,7 @@ import {
2021 renderSignedTx,
2122 signTransaction,
2223} from '../lib/txn';
24+import { withTxnConfirmations } from '../store/txnConfirmations';
2325
2426const SUBMISSION_STATES = {
2527 PROMPT: 'Send transaction',
@@ -234,14 +236,14 @@ class StatelessTransaction extends React.Component {
234236 )
235237 .then(txHash => {
236238 props.onSent(Just(Ok(txHash)), state.stx.value);
237− props.popRoute();
238239
239240 let routeData = {};
240241 if (props.networkSeed) {
241242 props.setNetworkSeedCache(props.networkSeed);
242243 routeData.promptKeyfile = true;
243244 }
244− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, routeData);
245+
246+ props.history.popAndPush(ROUTE_NAMES.SENT_TRANSACTION, routeData);
245247 })
246248 .catch(err => {
247249 this.setState({
@@ -577,4 +579,4 @@ class StatelessTransaction extends React.Component {
577579 }
578580}
579581
580−export default StatelessTransaction;
582+export default withTxnConfirmations(withHistory(StatelessTransaction));
src/bridge/components/icons/Chevron.js+1−3
@@ -4,9 +4,7 @@ import PropTypes from 'prop-types';
44const Chevron = props => {
55 return (
66 <img
7− src={`data:image/svg+xml;utf8,<svg width="11" height="24" viewBox="0 0 11 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 12L10.387 12.3166L10.646 12L10.387 11.6833L10 12ZM0.613022 1.31662L9.61302 12.3166L10.387 11.6833L1.38698 0.68338L0.613022 1.31662ZM9.61302 11.6833L0.613021 22.6834L1.38698 23.3166L10.387 12.3166L9.61302 11.6833Z" stroke="${
8− props.color
9− }" stroke-width="${props.strokeWidth}" fill="${props.color}"/></svg>`}
7+ src={`data:image/svg+xml;utf8,<svg width="11" height="24" viewBox="0 0 11 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 12L10.387 12.3166L10.646 12L10.387 11.6833L10 12ZM0.613022 1.31662L9.61302 12.3166L10.387 11.6833L1.38698 0.68338L0.613022 1.31662ZM9.61302 11.6833L0.613021 22.6834L1.38698 23.3166L10.387 12.3166L9.61302 11.6833Z" stroke="${props.color}" stroke-width="${props.strokeWidth}" fill="${props.color}"/></svg>`}
108 alt=""
119 style={props.style}
1210 className={props.className}
src/bridge/lib/flags.jsnew file+1
@@ -0,0 +1 @@
1+export const isLocal = process.env.NODE_ENV === 'development';
src/bridge/lib/invite.js+1−1
@@ -2,7 +2,7 @@ import { Just, Nothing } from 'folktale/maybe';
22import Tx from 'ethereumjs-tx';
33
44import * as azimuth from 'azimuth-js';
5−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
5+import * as kg from 'urbit-key-generation/dist/index';
66import * as wg from '../../walletgen/lib/lib.js';
77import * as tank from './tank';
88
src/bridge/lib/keys.js+3−3
@@ -4,7 +4,7 @@ import * as need from '../lib/need';
44
55import * as noun from '../nockjs/noun';
66import * as serial from '../nockjs/serial';
7−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
7+import * as kg from 'urbit-key-generation/dist/index';
88
99import { BRIDGE_ERROR } from './error';
1010import { WALLET_NAMES, eqAddr } from './wallet';
@@ -21,7 +21,7 @@ const b64 = buf => {
2121
2222 // prettier-ignore
2323 const trans = j =>
24− 10 > j
24+ 10 > j
2525 ? j + 48
2626 : 36 > j
2727 ? j + 87
@@ -29,7 +29,7 @@ const b64 = buf => {
2929 ? j + 29
3030 : 62 === j
3131 ? 45
32− : 126
32+ : 126;
3333
3434 return (
3535 '0w' +
src/bridge/lib/routeNames.jsnew file+33
@@ -0,0 +1,33 @@
1+export const ROUTE_NAMES = {
2+ DEFAULT: Symbol('DEFAULT'),
3+ //
4+ INVITE_TICKET: Symbol('INVITE_TICKET'),
5+ INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
6+ INVITES_SEND: Symbol('INVITES_SEND'),
7+ INVITES_MANAGE: Symbol('INVITES_MANAGE'),
8+ //
9+ ACCEPT_TRANSFER: Symbol('ACCEPT_TRANSFER'),
10+ CANCEL_TRANSFER: Symbol('CANCEL_TRANSFER'),
11+ LANDING: Symbol('LANDING'),
12+ NETWORK: Symbol('NETWORK'),
13+ WALLET: Symbol('WALLET'),
14+ MNEMONIC: Symbol('MNEMONIC'),
15+ TICKET: Symbol('TICKET'),
16+ SHARDS: Symbol('SHARDS'),
17+ LEDGER: Symbol('LEDGER'),
18+ TREZOR: Symbol('TREZOR'),
19+ PRIVATE_KEY: Symbol('PRIVATE_KEY'),
20+ KEYSTORE: Symbol('KEYSTORE'),
21+ VIEW_SHIP: Symbol('VIEW_SHIP'),
22+ SHIPS: Symbol('SHIPS'),
23+ SHIP: Symbol('SHIP'),
24+ SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
25+ SET_SPAWN_PROXY: Symbol('SET_SPAWN_PROXY'),
26+ SET_TRANSFER_PROXY: Symbol('SET_TRANSFER_PROXY'),
27+ CREATE_GALAXY: Symbol('CREATE_GALAXY'),
28+ ISSUE_CHILD: Symbol('ISSUE_CHILD'),
29+ SET_KEYS: Symbol('SET_KEYS'),
30+ TRANSFER: Symbol('TRANSFER'),
31+ SENT_TRANSACTION: Symbol('SENT_TRANSACTION'),
32+ GEN_KEYFILE: Symbol('GEN_KEYFILE'),
33+};
src/bridge/lib/router.js+92−134
@@ -1,15 +1,11 @@
1+import React from 'react';
12import * as ob from 'urbit-ob';
23import * as need from '../lib/need';
34
4−import React from 'react';
5−
65import Landing from '../views/Landing';
7−
86import InviteTicket from '../views/InviteTicket';
9−
107import InvitesSend from '../views/InvitesSend.js';
118import InvitesManage from '../views/InvitesManage.js';
12−
139import AcceptTransfer from '../views/AcceptTransfer';
1410import CancelTransfer from '../views/CancelTransfer';
1511import CreateGalaxy from '../views/CreateGalaxy';
@@ -38,94 +34,55 @@ import Wallet from '../views/Wallet';
3834
3935import { EthereumWallet } from './wallet';
4036import { renderNetworkType } from './network';
37+import { ROUTE_NAMES } from './routeNames';
4138
42−const ROUTE_NAMES = {
43− DEFAULT: Symbol('DEFAULT'),
44− //
45− INVITE_TICKET: Symbol('INVITE_TICKET'),
46− INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
47− INVITES_SEND: Symbol('INVITES_SEND'),
48− INVITES_MANAGE: Symbol('INVITES_MANAGE'),
49− //
50− ACCEPT_TRANSFER: Symbol('ACCEPT_TRANSFER'),
51− CANCEL_TRANSFER: Symbol('CANCEL_TRANSFER'),
52− LANDING: Symbol('LANDING'),
53− NETWORK: Symbol('NETWORK'),
54− WALLET: Symbol('WALLET'),
55− MNEMONIC: Symbol('MNEMONIC'),
56− TICKET: Symbol('TICKET'),
57− SHARDS: Symbol('SHARDS'),
58− LEDGER: Symbol('LEDGER'),
59− TREZOR: Symbol('TREZOR'),
60− PRIVATE_KEY: Symbol('PRIVATE_KEY'),
61− KEYSTORE: Symbol('KEYSTORE'),
62− VIEW_SHIP: Symbol('VIEW_SHIP'),
63− SHIPS: Symbol('SHIPS'),
64− SHIP: Symbol('SHIP'),
65− SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
66− SET_SPAWN_PROXY: Symbol('SET_SPAWN_PROXY'),
67− SET_TRANSFER_PROXY: Symbol('SET_TRANSFER_PROXY'),
68− CREATE_GALAXY: Symbol('CREATE_GALAXY'),
69− ISSUE_CHILD: Symbol('ISSUE_CHILD'),
70− SET_KEYS: Symbol('SET_KEYS'),
71− TRANSFER: Symbol('TRANSFER'),
72− SENT_TRANSACTION: Symbol('SENT_TRANSACTION'),
73− GEN_KEYFILE: Symbol('GEN_KEYFILE'),
39+const ROUTES = {
40+ [ROUTE_NAMES.DEFAULT]: Landing,
41+ [ROUTE_NAMES.LANDING]: Landing,
42+ [ROUTE_NAMES.INVITE_TICKET]: InviteTicket,
43+ [ROUTE_NAMES.INVITES_SEND]: InvitesSend,
44+ [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
45+ [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
46+ [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
47+ [ROUTE_NAMES.NETWORK]: Network,
48+ [ROUTE_NAMES.WALLET]: Wallet,
49+ [ROUTE_NAMES.VIEW_SHIP]: ViewPoint,
50+ [ROUTE_NAMES.MNEMONIC]: Mnemonic,
51+ [ROUTE_NAMES.TICKET]: Ticket,
52+ [ROUTE_NAMES.SHARDS]: Shards,
53+ [ROUTE_NAMES.LEDGER]: Ledger,
54+ [ROUTE_NAMES.TREZOR]: Trezor,
55+ [ROUTE_NAMES.PRIVATE_KEY]: PrivateKey,
56+ [ROUTE_NAMES.KEYSTORE]: Keystore,
57+ [ROUTE_NAMES.SHIPS]: Points,
58+ [ROUTE_NAMES.SHIP]: Point,
59+ [ROUTE_NAMES.SET_MANAGEMENT_PROXY]: SetManagementProxy,
60+ [ROUTE_NAMES.SET_SPAWN_PROXY]: SetSpawnProxy,
61+ [ROUTE_NAMES.SET_TRANSFER_PROXY]: SetTransferProxy,
62+ [ROUTE_NAMES.CREATE_GALAXY]: CreateGalaxy,
63+ [ROUTE_NAMES.ISSUE_CHILD]: IssueChild,
64+ [ROUTE_NAMES.SET_KEYS]: SetKeys,
65+ [ROUTE_NAMES.TRANSFER]: Transfer,
66+ [ROUTE_NAMES.SENT_TRANSACTION]: SentTransaction,
67+ [ROUTE_NAMES.GEN_KEYFILE]: GenKeyfile,
7468};
7569
76−const createRoutes = () => {
77− const routes = {};
78− routes[ROUTE_NAMES.DEFAULT] = Landing;
79− //
80− routes[ROUTE_NAMES.INVITE_TICKET] = InviteTicket;
81− routes[ROUTE_NAMES.INVITES_SEND] = InvitesSend;
82− routes[ROUTE_NAMES.INVITES_MANAGE] = InvitesManage;
83− //
84− routes[ROUTE_NAMES.ACCEPT_TRANSFER] = AcceptTransfer;
85− routes[ROUTE_NAMES.CANCEL_TRANSFER] = CancelTransfer;
86− routes[ROUTE_NAMES.LANDING] = Landing;
87− routes[ROUTE_NAMES.NETWORK] = Network;
88− routes[ROUTE_NAMES.WALLET] = Wallet;
89− routes[ROUTE_NAMES.VIEW_SHIP] = ViewPoint;
90− routes[ROUTE_NAMES.MNEMONIC] = Mnemonic;
91− routes[ROUTE_NAMES.TICKET] = Ticket;
92− routes[ROUTE_NAMES.SHARDS] = Shards;
93− routes[ROUTE_NAMES.LEDGER] = Ledger;
94− routes[ROUTE_NAMES.TREZOR] = Trezor;
95− routes[ROUTE_NAMES.PRIVATE_KEY] = PrivateKey;
96− routes[ROUTE_NAMES.KEYSTORE] = Keystore;
97− routes[ROUTE_NAMES.SHIPS] = Points;
98− routes[ROUTE_NAMES.SHIP] = Point;
99− routes[ROUTE_NAMES.SET_MANAGEMENT_PROXY] = SetManagementProxy;
100− routes[ROUTE_NAMES.SET_SPAWN_PROXY] = SetSpawnProxy;
101− routes[ROUTE_NAMES.SET_TRANSFER_PROXY] = SetTransferProxy;
102− routes[ROUTE_NAMES.CREATE_GALAXY] = CreateGalaxy;
103− routes[ROUTE_NAMES.ISSUE_CHILD] = IssueChild;
104− routes[ROUTE_NAMES.SET_KEYS] = SetKeys;
105− routes[ROUTE_NAMES.TRANSFER] = Transfer;
106− routes[ROUTE_NAMES.SENT_TRANSACTION] = SentTransaction;
107− routes[ROUTE_NAMES.GEN_KEYFILE] = GenKeyfile;
108− return routes;
109−};
110−
111−const ROUTES = createRoutes();
112−
113−const renderRoute = (props, route) => {
70+export const getRouteBreadcrumb = (props, route) => {
11471 const { wallet, networkType } = props;
115− return route === ROUTE_NAMES.LANDING
116− ? 'Bridge'
117− : route === ROUTE_NAMES.INVITE_TICKET
118− ? 'Invite code'
119− : route === ROUTE_NAMES.INVITE_TRANSACTIONS
120− ? 'Setting up new wallet'
121− : route === ROUTE_NAMES.INVITES_SEND
122− ? 'Send invites'
123− : route === ROUTE_NAMES.INVITES_MANAGE
124− ? 'Manage invites'
125− : route === ROUTE_NAMES.NETWORK
126− ? `${renderNetworkType(networkType)}`
127− : route === ROUTE_NAMES.WALLET
128− ? wallet.matchWith({
72+
73+ switch (route.name) {
74+ case ROUTE_NAMES.INVITE_TICKET:
75+ return 'Invite code';
76+ case ROUTE_NAMES.INVITE_TRANSACTIONS:
77+ return 'Setting up new wallet';
78+ case ROUTE_NAMES.INVITES_SEND:
79+ return 'Send invites';
80+ case ROUTE_NAMES.INVITES_MANAGE:
81+ return 'Manage invites';
82+ case ROUTE_NAMES.NETWORK:
83+ return `${renderNetworkType(networkType)}`;
84+ case ROUTE_NAMES.WALLET:
85+ return wallet.matchWith({
12986 Nothing: () => 'Wallet',
13087 Just: wal =>
13188 wal.value instanceof EthereumWallet ? (
@@ -133,50 +90,51 @@ const renderRoute = (props, route) => {
13390 ) : (
13491 <span className="text-mono">{wal.value.address}</span>
13592 ),
136− })
137− : route === ROUTE_NAMES.MNEMONIC
138− ? 'Mnemonic'
139− : route === ROUTE_NAMES.TICKET
140− ? 'Urbit Ticket'
141− : route === ROUTE_NAMES.SHARDS
142− ? 'Urbit Ticket'
143− : route === ROUTE_NAMES.LEDGER
144− ? 'Ledger'
145− : route === ROUTE_NAMES.TREZOR
146− ? 'Trezor'
147− : route === ROUTE_NAMES.PRIVATE_KEY
148− ? 'Private Key'
149− : route === ROUTE_NAMES.KEYSTORE
150− ? 'Keystore File'
151− : route === ROUTE_NAMES.SHIPS
152− ? 'Points'
153− : route === ROUTE_NAMES.VIEW_SHIP
154− ? 'View'
155− : route === ROUTE_NAMES.SHIP
156− ? ob.patp(need.pointCursor(props))
157− : route === ROUTE_NAMES.SET_TRANSFER_PROXY ||
158− route === ROUTE_NAMES.SET_MANAGEMENT_PROXY ||
159− route === ROUTE_NAMES.SET_SPAWN_PROXY
160− ? 'Set Proxy'
161− : route === ROUTE_NAMES.CREATE_GALAXY
162− ? 'Create Galaxy'
163− : route === ROUTE_NAMES.ISSUE_CHILD
164− ? 'Issue Child'
165− : route === ROUTE_NAMES.TRANSFER
166− ? 'Transfer'
167− : route === ROUTE_NAMES.ACCEPT_TRANSFER
168− ? 'Accept Transfer'
169− : route === ROUTE_NAMES.CANCEL_TRANSFER
170− ? 'Cancel Transfer'
171− : route === ROUTE_NAMES.GEN_KEYFILE
172− ? 'Keyfile'
173− : route === ROUTE_NAMES.SET_KEYS
174− ? 'Configure Keys'
175− : route === ROUTE_NAMES.SENT_TRANSACTION
176− ? 'Txn'
177− : 'Bridge';
93+ });
94+ case ROUTE_NAMES.MNEMONIC:
95+ return 'Mnemonic';
96+ case ROUTE_NAMES.TICKET:
97+ return 'Urbit Ticket';
98+ case ROUTE_NAMES.SHARDS:
99+ return 'Urbit Ticket';
100+ case ROUTE_NAMES.LEDGER:
101+ return 'Ledger';
102+ case ROUTE_NAMES.TREZOR:
103+ return 'Trezor';
104+ case ROUTE_NAMES.PRIVATE_KEY:
105+ return 'Private Key';
106+ case ROUTE_NAMES.KEYSTORE:
107+ return 'Keystore File';
108+ case ROUTE_NAMES.SHIPS:
109+ return 'Points';
110+ case ROUTE_NAMES.VIEW_SHIP:
111+ return 'View';
112+ case ROUTE_NAMES.SHIP:
113+ return ob.patp(need.pointCursor(props));
114+ case ROUTE_NAMES.SET_TRANSFER_PROXY:
115+ case ROUTE_NAMES.SET_MANAGEMENT_PROXY:
116+ case ROUTE_NAMES.SET_SPAWN_PROXY:
117+ return 'Set Proxy';
118+ case ROUTE_NAMES.CREATE_GALAXY:
119+ return 'Create Galaxy';
120+ case ROUTE_NAMES.ISSUE_CHILD:
121+ return 'Issue Child';
122+ case ROUTE_NAMES.TRANSFER:
123+ return 'Transfer';
124+ case ROUTE_NAMES.ACCEPT_TRANSFER:
125+ return 'Accept Transfer';
126+ case ROUTE_NAMES.CANCEL_TRANSFER:
127+ return 'Cancel Transfer';
128+ case ROUTE_NAMES.GEN_KEYFILE:
129+ return 'Keyfile';
130+ case ROUTE_NAMES.SET_KEYS:
131+ return 'Configure Keys';
132+ case ROUTE_NAMES.SENT_TRANSACTION:
133+ return 'Txn';
134+ case ROUTE_NAMES.LANDING:
135+ default:
136+ return 'Bridge';
137+ }
178138};
179139
180−const router = route => ROUTES[route];
181−
182−export { ROUTE_NAMES, router, renderRoute };
140+export const router = route => ROUTES[route.name];
src/bridge/lib/wallet.js+1−1
@@ -5,7 +5,7 @@ import * as lodash from 'lodash';
55import Maybe from 'folktale/maybe';
66import * as secp256k1 from 'secp256k1';
77import * as ob from 'urbit-ob';
8−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
8+import * as kg from 'urbit-key-generation/dist/index';
99import { isAddress } from 'web3-utils';
1010
1111const DEFAULT_HD_PATH = "m/44'/60'/0'/0/0";
src/bridge/lib/web3.jsnew file+23
@@ -0,0 +1,23 @@
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.jsnew file+73
@@ -0,0 +1,73 @@
1+import React, { forwardRef, useContext, useState, useEffect } from 'react';
2+import { last, includes } from 'lodash';
3+
4+import { BRIDGE_ERROR } from '../lib/error';
5+import { ROUTE_NAMES } from '../lib/routeNames';
6+
7+export const HistoryContext = React.createContext(null);
8+
9+function _useHistory(initialRoutes = []) {
10+ const [history, setHistory] = useState(initialRoutes);
11+
12+ const value = {
13+ routes: history,
14+ push: (name, data) => {
15+ if (!includes(ROUTE_NAMES, name)) {
16+ throw BRIDGE_ERROR.INVALID_ROUTE;
17+ }
18+
19+ setHistory(history => [...history, { name, data }]);
20+ },
21+ popAndPush: (name, data = {}) =>
22+ setHistory(history => [
23+ ...history.slice(0, history.length - 1),
24+ { name, data },
25+ ]),
26+ pop: (count = 1) =>
27+ setHistory(history => [...history.slice(0, history.length - count)]),
28+ peek: () => last(history),
29+ size: history.length,
30+ includes: name => includes(history.map(r => r.name), name),
31+ data: last(history).data,
32+ };
33+
34+ // Scroll to top of page with each route transition.
35+ useEffect(() => window.scrollTo(0, 0), [history]);
36+
37+ // set up handlers
38+ useEffect(() => {
39+ window.history.pushState(null, null, null);
40+
41+ window.onpopstate = e => {
42+ window.history.pushState(null, null, null);
43+ value.pop();
44+ };
45+
46+ // TODO: disposer functon
47+ });
48+
49+ return value;
50+}
51+
52+export function HistoryProvider({ initialRoutes, children }) {
53+ const history = _useHistory(initialRoutes);
54+
55+ return (
56+ <HistoryContext.Provider value={history}>
57+ {children}
58+ </HistoryContext.Provider>
59+ );
60+}
61+
62+// Hook version
63+export function useHistory() {
64+ return useContext(HistoryContext);
65+}
66+
67+// HOC version
68+export const withHistory = Component =>
69+ forwardRef((props, ref) => (
70+ <HistoryContext.Consumer>
71+ {history => <Component ref={ref} history={history} {...props} />}
72+ </HistoryContext.Consumer>
73+ ));
src/bridge/store/txnConfirmations.jsnew file+39
@@ -0,0 +1,39 @@
1+import React, { forwardRef, useContext, useState } from 'react';
2+
3+export const TxnConfirmationsContext = React.createContext(null);
4+
5+export function TxnConfirmationsProvider({ children }) {
6+ const [confirmations, setConfirmations] = useState({});
7+
8+ const setTxnConfirmations = (txnHash, txnConfirmations) => {
9+ setConfirmations({
10+ ...confirmations,
11+ [txnHash]: txnConfirmations,
12+ });
13+ };
14+
15+ return (
16+ <TxnConfirmationsContext.Provider
17+ value={{
18+ txnConfirmations: confirmations,
19+ setTxnConfirmations,
20+ }}>
21+ {children}
22+ </TxnConfirmationsContext.Provider>
23+ );
24+}
25+
26+// HOC version
27+export const withTxnConfirmations = Component =>
28+ forwardRef((props, ref) => (
29+ <TxnConfirmationsContext.Consumer>
30+ {txnConfirmations => (
31+ <Component ref={ref} {...txnConfirmations} {...props} />
32+ )}
33+ </TxnConfirmationsContext.Consumer>
34+ ));
35+
36+// Hook version
37+export function useTxnConfirmations() {
38+ return useContext(TxnConfirmationsContext);
39+}
src/bridge/views/GenKeyfile.js+1−1
@@ -6,7 +6,7 @@ import { Button } from '../components/Base';
66import { Row, Col, H1, P } from '../components/Base';
77
88import * as ob from 'urbit-ob';
9−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
9+import * as kg from 'urbit-key-generation/dist/index';
1010import saveAs from 'file-saver';
1111
1212import { attemptSeedDerivation, genKey } from '../lib/keys';
src/bridge/views/InviteTicket.js+6−5
@@ -14,7 +14,8 @@ import * as azimuth from 'azimuth-js';
1414import * as need from '../lib/need';
1515
1616import { randomPatq } from '../lib/lib';
17−import { ROUTE_NAMES } from '../lib/router';
17+import { ROUTE_NAMES } from '../lib/routeNames';
18+import { withHistory } from '../store/history';
1819import {
1920 DEFAULT_HD_PATH,
2021 ownershipWalletFromTicket,
@@ -63,8 +64,8 @@ class InviteTicket extends React.Component {
6364 }
6465
6566 navigateLogin() {
66− this.props.popRoute();
67− this.props.pushRoute(ROUTE_NAMES.NETWORK);
67+ this.props.history.pop();
68+ this.props.history.push(ROUTE_NAMES.NETWORK);
6869 }
6970
7071 handleInviteTicketInput(inviteTicket) {
@@ -310,7 +311,7 @@ class InviteTicket extends React.Component {
310311 updateProgress: this.updateProgress,
311312 }).then(() => {
312313 setTimeout(() => {
313− this.props.popRoute();
314+ this.props.history.pop();
314315 }, 5000);
315316 });
316317 }
@@ -421,4 +422,4 @@ class InviteTicket extends React.Component {
421422 }
422423}
423424
424−export default InviteTicket;
425+export default withHistory(InviteTicket);
src/bridge/views/Keystore.js+5−7
@@ -7,7 +7,8 @@ import { Input, InnerLabel, InputCaption } from '../components/Base';
77import { Row, Col, H1, H3, Warning } from '../components/Base';
88
99import { BRIDGE_ERROR } from '../lib/error';
10−import { ROUTE_NAMES } from '../lib/router';
10+import { ROUTE_NAMES } from '../lib/routeNames';
11+import { withHistory } from '../store/history';
1112import { EthereumWallet } from '../lib/wallet';
1213
1314class Keystore extends React.Component {
@@ -80,7 +81,7 @@ class Keystore extends React.Component {
8081 };
8182
8283 render() {
83− const { pushRoute, popRoute, wallet } = this.props;
84+ const { history, wallet } = this.props;
8485 const { keystore, password, decryptionProblem } = this.state;
8586
8687 const uploadButtonClass = keystore.matchWith({
@@ -149,10 +150,7 @@ class Keystore extends React.Component {
149150 className={'mt-10'}
150151 prop-size={'wide lg'}
151152 disabled={Nothing.hasInstance(wallet)}
152− onClick={() => {
153− popRoute();
154− pushRoute(ROUTE_NAMES.SHIPS);
155− }}>
153+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}>
156154 {'Continue →'}
157155 </Button>
158156 </Col>
@@ -161,4 +159,4 @@ class Keystore extends React.Component {
161159 }
162160}
163161
164−export default Keystore;
162+export default withHistory(Keystore);
src/bridge/views/Landing.js+50−57
@@ -3,70 +3,63 @@ import { Button } from '../components/Base';
33import { Row, Col } from '../components/Base';
44import { H1, P } from '../components/Base';
55
6−import { ROUTE_NAMES } from '../lib/router';
6+import { ROUTE_NAMES } from '../lib/routeNames';
7+import { useHistory } from '../store/history';
78
8−const Landing = props => (
9− <Row>
10− <Col>
11− <H1>{'Welcome'}</H1>
9+function Landing(props) {
10+ const history = useHistory();
1211
13− <P>
14− {'Bridge is a tool for managing and viewing assets on ' +
15− 'Azimuth, the Urbit address space.'}
16− </P>
12+ return (
13+ <Row>
14+ <Col>
15+ <H1>{'Welcome'}</H1>
1716
18− <Button
19− prop-type="link"
20− prop-size="lg"
21− className={'mb-4 mt-8'}
22− onClick={() => props.pushRoute(ROUTE_NAMES.INVITE_TICKET)}>
23− {'Claim an invite →'}
24− </Button>
17+ <P>
18+ {'Bridge is a tool for managing and viewing assets on ' +
19+ 'Azimuth, the Urbit address space.'}
20+ </P>
2521
26− <P>{'If you were sent an Azimuth invite code, start here.'}</P>
22+ <Button
23+ prop-type="link"
24+ prop-size="lg"
25+ className={'mb-4 mt-8'}
26+ onClick={() => history.push(ROUTE_NAMES.INVITE_TICKET)}>
27+ {'Claim an invite →'}
28+ </Button>
2729
28− <Button
29− prop-type="link"
30− prop-size="lg"
31− className={'mb-4 mt-8'}
32− onClick={() => props.pushRoute(ROUTE_NAMES.NETWORK)}>
33− {'Unlock a Wallet →'}
34− </Button>
30+ <P>{'If you were sent an Azimuth invite code, start here.'}</P>
3531
36− <P>
37− {'If you own Azimuth assets and want to manage them in some ' +
38− "way, start here. You'll need either your Urbit ticket or a " +
39− 'keypair.'}
40− </P>
32+ <Button
33+ prop-type="link"
34+ prop-size="lg"
35+ className={'mb-4 mt-8'}
36+ onClick={() => history.push(ROUTE_NAMES.NETWORK)}>
37+ {'Unlock a Wallet →'}
38+ </Button>
4139
42− {
43− <Row>
44− <Col>
45− <Button
46− prop-type={'link'}
47− prop-size={'lg'}
48− className={'mb-4 mt-8'}
49− onClick={() => props.pushRoute(ROUTE_NAMES.VIEW_SHIP)}>
50− {'View a point →'}
51− </Button>
52− <P>{'View an Azimuth point without signing into a wallet.'}</P>
53− </Col>
54− </Row>
55− // FIXME add address view
56− // <LinkButton
57− // size={'l'}
58− // className={'mb-4 mt-10'}
59− // onClick={ () => props.pushRoute(ROUTE_NAMES.NETWORK) }
60− // >
61− // { 'View an Address →' }
62− // </LinkButton>
40+ <P>
41+ {'If you own Azimuth assets and want to manage them in some ' +
42+ "way, start here. You'll need either your Urbit ticket or a " +
43+ 'keypair.'}
44+ </P>
6345
64− // <p>
65− // { "Lorem" }
66− // </p>
67− }
68− </Col>
69− </Row>
70−);
46+ {
47+ <Row>
48+ <Col>
49+ <Button
50+ prop-type={'link'}
51+ prop-size={'lg'}
52+ className={'mb-4 mt-8'}
53+ onClick={() => history.push(ROUTE_NAMES.VIEW_SHIP)}>
54+ {'View a point →'}
55+ </Button>
56+ <P>{'View an Azimuth point without signing into a wallet.'}</P>
57+ </Col>
58+ </Row>
59+ }
60+ </Col>
61+ </Row>
62+ );
63+}
7164
7265export default Landing;
src/bridge/views/Ledger.js+5−7
@@ -9,7 +9,8 @@ import Eth from '@ledgerhq/hw-app-eth';
99import * as secp256k1 from 'secp256k1';
1010
1111import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../lib/ledger';
12−import { ROUTE_NAMES } from '../lib/router';
12+import { ROUTE_NAMES } from '../lib/routeNames';
13+import { withHistory } from '../store/history';
1314
1415const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
1516
@@ -76,7 +77,7 @@ class Ledger extends React.Component {
7677 }
7778
7879 render() {
79− const { pushRoute, popRoute, wallet } = this.props;
80+ const { history, wallet } = this.props;
8081 const { basePath, hdpath, account } = this.state;
8182 const { handlePathSelection, handleAccountSelection } = this;
8283
@@ -207,10 +208,7 @@ class Ledger extends React.Component {
207208 className={'mt-8'}
208209 prop-size={'wide lg'}
209210 disabled={Maybe.Nothing.hasInstance(wallet)}
210− onClick={() => {
211− popRoute();
212− pushRoute(ROUTE_NAMES.SHIPS);
213− }}>
211+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}>
214212 {'Continue →'}
215213 </Button>
216214 </Col>
@@ -219,4 +217,4 @@ class Ledger extends React.Component {
219217 }
220218}
221219
222−export default Ledger;
220+export default withHistory(Ledger);
src/bridge/views/Mnemonic.js+5−7
@@ -10,7 +10,8 @@ import {
1010} from '../components/Base';
1111import { Row, Col, H1 } from '../components/Base';
1212
13−import { ROUTE_NAMES } from '../lib/router';
13+import { ROUTE_NAMES } from '../lib/routeNames';
14+import { withHistory } from '../store/history';
1415import { DEFAULT_HD_PATH, walletFromMnemonic } from '../lib/wallet';
1516
1617class Mnemonic extends React.Component {
@@ -91,7 +92,7 @@ class Mnemonic extends React.Component {
9192 }
9293
9394 render() {
94− const { pushRoute, popRoute, wallet } = this.props;
95+ const { history, wallet } = this.props;
9596 const { mnemonic, hdpath, exampleMnemonic, passphrase } = this.state;
9697
9798 return (
@@ -150,10 +151,7 @@ class Mnemonic extends React.Component {
150151 <Button
151152 className={'mt-10'}
152153 disabled={Nothing.hasInstance(wallet)}
153− onClick={() => {
154− popRoute();
155− pushRoute(ROUTE_NAMES.SHIPS);
156− }}>
154+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}>
157155 {'Continue →'}
158156 </Button>
159157 </Col>
@@ -162,4 +160,4 @@ class Mnemonic extends React.Component {
162160 }
163161}
164162
165−export default Mnemonic;
163+export default withHistory(Mnemonic);
src/bridge/views/Network.js+7−10
@@ -8,7 +8,8 @@ import Web3 from 'web3';
88
99import { CONTRACT_ADDRESSES } from '../lib/contracts';
1010import { NETWORK_NAMES, renderNetworkType } from '../lib/network';
11−import { ROUTE_NAMES } from '../lib/router';
11+import { ROUTE_NAMES } from '../lib/routeNames';
12+import { withHistory } from '../store/history';
1213
1314class Network extends React.Component {
1415 constructor(props) {
@@ -34,9 +35,7 @@ class Network extends React.Component {
3435 if (network === NETWORK_NAMES.ROPSTEN) {
3536 setNetworkType(network);
3637
37− const endpoint = `https://ropsten.infura.io/v3/${
38− process.env.REACT_APP_INFURA_ENDPOINT
39− }`;
38+ const endpoint = `https://ropsten.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
4039
4140 const provider = new Web3.providers.HttpProvider(endpoint);
4241 const web3 = new Web3(provider);
@@ -48,9 +47,7 @@ class Network extends React.Component {
4847 if (network === NETWORK_NAMES.MAINNET) {
4948 setNetworkType(network);
5049
51− const endpoint = `https://mainnet.infura.io/v3/${
52− process.env.REACT_APP_INFURA_ENDPOINT
53− }`;
50+ const endpoint = `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
5451
5552 const provider = new Web3.providers.HttpProvider(endpoint);
5653 const web3 = new Web3(provider);
@@ -112,7 +109,7 @@ class Network extends React.Component {
112109 }
113110
114111 render() {
115− const { networkType, pushRoute } = this.props;
112+ const { networkType, history } = this.props;
116113 const networkOptions = this.getNetworkOptions();
117114
118115 return (
@@ -136,7 +133,7 @@ class Network extends React.Component {
136133
137134 <Button
138135 className={'mt-10'}
139− onClick={() => pushRoute(ROUTE_NAMES.WALLET)}>
136+ onClick={() => history.push(ROUTE_NAMES.WALLET)}>
140137 {'Continue →'}
141138 </Button>
142139 </Col>
@@ -145,4 +142,4 @@ class Network extends React.Component {
145142 }
146143}
147144
148−export default Network;
145+export default withHistory(Network);
src/bridge/views/Point.js+4−16
@@ -10,6 +10,7 @@ import ReactSVGComponents from '../components/ReactSVGComponents';
1010import KeysAndMetadata from './Point/KeysAndMetadata';
1111import Actions from './Point/Actions';
1212import { Row, Col, H1, H3 } from '../components/Base';
13+import { withHistory } from '../store/history';
1314
1415class Point extends React.Component {
1516 constructor(props) {
@@ -73,14 +74,7 @@ class Point extends React.Component {
7374 };
7475
7576 render() {
76− const {
77− web3,
78− popRoute,
79− pushRoute,
80− wallet,
81− setPointCursor,
82− pointCache,
83− } = this.props;
77+ const { web3, history, wallet, setPointCursor, pointCache } = this.props;
8478
8579 const { spawned } = this.state;
8680
@@ -101,11 +95,6 @@ class Point extends React.Component {
10195
10296 const authenticated = Just.hasInstance(wallet);
10397
104− const routeHandler = route => {
105− popRoute();
106− pushRoute(route);
107− };
108−
10998 const issuedPointList =
11099 spawned.length === 0 ? (
111100 <div />
@@ -115,7 +104,7 @@ class Point extends React.Component {
115104
116105 <PointList
117106 setPointCursor={setPointCursor}
118− routeHandler={routeHandler}
107+ routeHandler={history.popAndPush}
119108 points={spawned}
120109 />
121110 </div>
@@ -130,7 +119,6 @@ class Point extends React.Component {
130119 </H1>
131120 {authenticated ? (
132121 <Actions
133− pushRoute={pushRoute}
134122 online={online}
135123 wallet={wallet}
136124 point={point}
@@ -147,4 +135,4 @@ class Point extends React.Component {
147135 }
148136}
149137
150−export default Point;
138+export default withHistory(Point);
src/bridge/views/Point/Actions.js+18−17
@@ -1,17 +1,20 @@
11import React from 'react';
2+import { azimuth } from 'azimuth-js';
23import * as need from '../../lib/need';
4+
35import { ETH_ZERO_ADDR, CURVE_ZERO_ADDR, eqAddr } from '../../lib/wallet';
46import { Row, Col, H2, P } from '../../components/Base';
57import { Button } from '../../components/Base';
6−import { ROUTE_NAMES } from '../../lib/router';
7−import { azimuth } from 'azimuth-js';
8+import { ROUTE_NAMES } from '../../lib/routeNames';
9+import { useHistory } from '../../store/history';
810
911const isPlanet = point =>
1012 azimuth.getPointSize(point) === azimuth.PointSize.Planet;
1113const isStar = point => azimuth.getPointSize(point) === azimuth.PointSize.Star;
1214
13−const Actions = props => {
14− const { pushRoute, online, point, pointDetails, invites } = props;
15+function Actions(props) {
16+ const history = useHistory();
17+ const { online, point, pointDetails, invites } = props;
1518
1619 const addr = need.address(props);
1720
@@ -105,9 +108,7 @@ const Actions = props => {
105108 disabled={!isActiveOwner || !online || !hasInvites}
106109 prop-size={'sm'}
107110 prop-type={'link'}
108− onClick={() => {
109− pushRoute(ROUTE_NAMES.INVITES_SEND);
110− }}>
111+ onClick={() => history.push(ROUTE_NAMES.INVITES_SEND)}>
111112 {'Send invites ('}
112113 {invites.matchWith({
113114 Nothing: () => '?',
@@ -124,7 +125,7 @@ const Actions = props => {
124125 prop-size={'sm'}
125126 prop-type={'link'}
126127 onClick={() => {
127− pushRoute(ROUTE_NAMES.INVITES_MANAGE);
128+ history.push(ROUTE_NAMES.INVITES_MANAGE);
128129 }}>
129130 {'Manage invites'}
130131 </Button>
@@ -147,7 +148,7 @@ const Actions = props => {
147148 prop-type={'link'}
148149 disabled={(online || planet) && !canIssueChild}
149150 onClick={() => {
150− pushRoute(ROUTE_NAMES.ISSUE_CHILD);
151+ history.push(ROUTE_NAMES.ISSUE_CHILD);
151152 }}>
152153 {'Issue child'}
153154 </Button>
@@ -157,7 +158,7 @@ const Actions = props => {
157158 prop-type={'link'}
158159 disabled={online && !canAcceptTransfer}
159160 onClick={() => {
160− pushRoute(ROUTE_NAMES.ACCEPT_TRANSFER);
161+ history.push(ROUTE_NAMES.ACCEPT_TRANSFER);
161162 }}>
162163 {'Accept incoming transfer'}
163164 </Button>
@@ -167,7 +168,7 @@ const Actions = props => {
167168 prop-type={'link'}
168169 disabled={online && !canCancelTransfer}
169170 onClick={() => {
170− pushRoute(ROUTE_NAMES.CANCEL_TRANSFER);
171+ history.push(ROUTE_NAMES.CANCEL_TRANSFER);
171172 }}>
172173 {'Cancel outgoing transfer'}
173174 </Button>
@@ -177,7 +178,7 @@ const Actions = props => {
177178 prop-type={'link'}
178179 disabled={online && !canGenKeyfile}
179180 onClick={() => {
180− pushRoute(ROUTE_NAMES.GEN_KEYFILE);
181+ history.push(ROUTE_NAMES.GEN_KEYFILE);
181182 }}>
182183 {'Generate Arvo keyfile'}
183184 </Button>
@@ -188,7 +189,7 @@ const Actions = props => {
188189 prop-size={'sm'}
189190 prop-type={'link'}
190191 onClick={() => {
191− pushRoute(ROUTE_NAMES.SET_SPAWN_PROXY);
192+ history.push(ROUTE_NAMES.SET_SPAWN_PROXY);
192193 }}>
193194 {'Change spawn proxy'}
194195 </Button>
@@ -198,7 +199,7 @@ const Actions = props => {
198199 prop-size={'sm'}
199200 prop-type={'link'}
200201 onClick={() => {
201− pushRoute(ROUTE_NAMES.SET_MANAGEMENT_PROXY);
202+ history.push(ROUTE_NAMES.SET_MANAGEMENT_PROXY);
202203 }}>
203204 {'Change management proxy'}
204205 </Button>
@@ -208,7 +209,7 @@ const Actions = props => {
208209 prop-size={'sm'}
209210 prop-type={'link'}
210211 onClick={() => {
211− pushRoute(ROUTE_NAMES.SET_KEYS);
212+ history.push(ROUTE_NAMES.SET_KEYS);
212213 }}>
213214 {'Set network keys'}
214215 </Button>
@@ -218,7 +219,7 @@ const Actions = props => {
218219 prop-size={'sm'}
219220 prop-type={'link'}
220221 onClick={() => {
221− pushRoute(ROUTE_NAMES.TRANSFER);
222+ history.push(ROUTE_NAMES.TRANSFER);
222223 }}>
223224 {'Transfer'}
224225 </Button>
@@ -229,6 +230,6 @@ const Actions = props => {
229230 </Row>
230231 </div>
231232 );
232−};
233+}
233234
234235export default Actions;
src/bridge/views/Points.js+12−11
@@ -5,7 +5,8 @@ import * as azimuth from 'azimuth-js';
55
66import PointList from '../components/PointList';
77import { NETWORK_NAMES } from '../lib/network';
8−import { ROUTE_NAMES } from '../lib/router';
8+import { ROUTE_NAMES } from '../lib/routeNames';
9+import { withHistory } from '../store/history';
910import { ETH_ZERO_ADDR, eqAddr } from '../lib/wallet';
1011
1112const hasTransferProxy = (cache, point) =>
@@ -79,7 +80,7 @@ class Points extends React.Component {
7980 }
8081
8182 render() {
82− const { setPointCursor, pushRoute } = this.props;
83+ const { setPointCursor, history } = this.props;
8384 const { pointCache } = this.props;
8485
8586 const {
@@ -98,7 +99,7 @@ class Points extends React.Component {
9899 <Button
99100 prop-type={'link'}
100101 prop-size={'md'}
101− onClick={() => pushRoute(ROUTE_NAMES.VIEW_SHIP)}>
102+ onClick={() => history.push(ROUTE_NAMES.VIEW_SHIP)}>
102103 {'View a point →'}
103104 </Button>
104105 <P>{'View a point on Azimuth.'}</P>
@@ -115,7 +116,7 @@ class Points extends React.Component {
115116 <Button
116117 prop-type={'link'}
117118 prop-size={'md'}
118− onClick={() => pushRoute(ROUTE_NAMES.CREATE_GALAXY)}>
119+ onClick={() => history.push(ROUTE_NAMES.CREATE_GALAXY)}>
119120 {'Create a galaxy →'}
120121 </Button>
121122 <P>
@@ -141,7 +142,7 @@ class Points extends React.Component {
141142 </P>
142143 <PointList
143144 setPointCursor={setPointCursor}
144− routeHandler={pushRoute}
145+ routeHandler={history.push}
145146 points={outgoing}
146147 />
147148 </React.Fragment>
@@ -159,7 +160,7 @@ class Points extends React.Component {
159160 </P>
160161 <PointList
161162 setPointCursor={setPointCursor}
162− routeHandler={pushRoute}
163+ routeHandler={history.push}
163164 points={incoming}
164165 />
165166 </React.Fragment>
@@ -177,7 +178,7 @@ class Points extends React.Component {
177178 </P>
178179 <PointList
179180 setPointCursor={setPointCursor}
180− routeHandler={pushRoute}
181+ routeHandler={history.push}
181182 points={managing}
182183 />
183184 </React.Fragment>
@@ -195,7 +196,7 @@ class Points extends React.Component {
195196 </P>
196197 <PointList
197198 setPointCursor={setPointCursor}
198− routeHandler={pushRoute}
199+ routeHandler={history.push}
199200 points={voting}
200201 />
201202 </React.Fragment>
@@ -210,7 +211,7 @@ class Points extends React.Component {
210211 <P>{`You can create new child ships under these points.`}</P>
211212 <PointList
212213 setPointCursor={setPointCursor}
213− routeHandler={pushRoute}
214+ routeHandler={history.push}
214215 points={spawning}
215216 />
216217 </React.Fragment>
@@ -238,7 +239,7 @@ class Points extends React.Component {
238239
239240 <PointList
240241 setPointCursor={setPointCursor}
241− routeHandler={pushRoute}
242+ routeHandler={history.push}
242243 points={points}
243244 loading={loading}
244245 />
@@ -254,4 +255,4 @@ class Points extends React.Component {
254255 }
255256}
256257
257−export default Points;
258+export default withHistory(Points);
src/bridge/views/PrivateKey.js+5−7
@@ -4,7 +4,8 @@ import { Button } from '../components/Base';
44import { RequiredInput, InnerLabel, InputCaption } from '../components/Base';
55import { Row, Col, H1 } from '../components/Base';
66
7−import { ROUTE_NAMES } from '../lib/router';
7+import { ROUTE_NAMES } from '../lib/routeNames';
8+import { withHistory } from '../store/history';
89import { EthereumWallet } from '../lib/wallet';
910
1011class PrivateKey extends React.Component {
@@ -35,7 +36,7 @@ class PrivateKey extends React.Component {
3536 }
3637
3738 render() {
38− const { pushRoute, popRoute, wallet } = this.props;
39+ const { history, wallet } = this.props;
3940 const { privateKey } = this.state;
4041
4142 return (
@@ -63,10 +64,7 @@ class PrivateKey extends React.Component {
6364 className={'mt-10'}
6465 prop-size={'wide lg'}
6566 disabled={Maybe.Nothing.hasInstance(wallet)}
66− onClick={() => {
67− popRoute();
68− pushRoute(ROUTE_NAMES.SHIPS);
69− }}>
67+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}>
7068 {'Continue →'}
7169 </Button>
7270 </Col>
@@ -75,4 +73,4 @@ class PrivateKey extends React.Component {
7573 }
7674}
7775
78−export default PrivateKey;
76+export default withHistory(PrivateKey);
src/bridge/views/SentTransaction.js+9−15
@@ -4,9 +4,11 @@ import * as need from '../lib/need';
44import { Row, Col, H1, H3, P, Warning, Anchor } from '../components/Base';
55import { Button } from '../components/Base';
66
7−import { ROUTE_NAMES } from '../lib/router';
7+import { ROUTE_NAMES } from '../lib/routeNames';
8+import { useHistory } from '../store/history';
89import { BRIDGE_ERROR, renderTxnError } from '../lib/error';
910import { NETWORK_NAMES } from '../lib/network';
11+import { withTxnConfirmations } from '../store/txnConfirmations';
1012
1113class Success extends React.Component {
1214 constructor(props) {
@@ -108,16 +110,11 @@ const Failure = props => (
108110);
109111
110112const SentTransaction = props => {
111− const {
112− txnHashCursor,
113− networkType,
114− popRoute,
115− pushRoute,
116− txnConfirmations,
117− } = props;
113+ const history = useHistory();
114+ const { txnHashCursor, networkType, txnConfirmations } = props;
118115 const { setPointCursor, pointCursor } = props;
119116
120− const promptKeyfile = props.routeData && props.routeData.promptKeyfile;
117+ const promptKeyfile = history.data && history.data.promptKeyfile;
121118
122119 const w3 = need.web3(props);
123120
@@ -146,7 +143,7 @@ const SentTransaction = props => {
146143 prop-type={'link'}
147144 onClick={() => {
148145 setPointCursor(pointCursor);
149− popRoute();
146+ history.pop();
150147 }}>
151148 {'Ok →'}
152149 </Button>
@@ -162,10 +159,7 @@ const SentTransaction = props => {
162159 <Col>
163160 <Button
164161 prop-type={'link'}
165− onClick={() => {
166− popRoute();
167− pushRoute(ROUTE_NAMES.GEN_KEYFILE);
168− }}>
162+ onClick={() => history.popAndPush(ROUTE_NAMES.GEN_KEYFILE)}>
169163 {'Download Keyfile →'}
170164 </Button>
171165 </Col>
@@ -182,4 +176,4 @@ const SentTransaction = props => {
182176 );
183177};
184178
185−export default SentTransaction;
179+export default withTxnConfirmations(SentTransaction);
src/bridge/views/SetKeys.js+1−1
@@ -8,7 +8,7 @@ import { Row, Col, H1, P, Warning, CheckboxButton } from '../components/Base';
88import StatelessTransaction from '../components/StatelessTransaction';
99import { attemptSeedDerivation } from '../lib/keys';
1010
11−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
11+import * as kg from 'urbit-key-generation/dist/index';
1212
1313import { addHexPrefix } from '../lib/wallet';
1414
src/bridge/views/Shards.js+6−8
@@ -10,10 +10,11 @@ import {
1010 Input,
1111} from '../components/Base';
1212import { Row, Col, H1, P } from '../components/Base';
13−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
13+import * as kg from 'urbit-key-generation/dist/index';
1414import * as ob from 'urbit-ob';
1515
16−import { ROUTE_NAMES } from '../lib/router';
16+import { ROUTE_NAMES } from '../lib/routeNames';
17+import { withHistory } from '../store/history';
1718import { urbitWalletFromTicket } from '../lib/wallet';
1819
1920const placeholder = len => {
@@ -90,7 +91,7 @@ class Shards extends React.Component {
9091 }
9192
9293 render() {
93− const { popRoute, pushRoute, wallet } = this.props;
94+ const { history, wallet } = this.props;
9495 const { shard1, shard2, shard3, pointName, passphrase } = this.state;
9596
9697 const phPoint = this.pointPlaceholder;
@@ -201,10 +202,7 @@ class Shards extends React.Component {
201202 className={'mt-4'}
202203 prop-size={'xl wide'}
203204 disabled={Nothing.hasInstance(wallet)}
204− onClick={() => {
205− popRoute();
206− pushRoute(ROUTE_NAMES.SHIPS);
207− }}>
205+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}>
208206 {'Continue →'}
209207 </Button>
210208 </Col>
@@ -213,4 +211,4 @@ class Shards extends React.Component {
213211 }
214212}
215213
216−export default Shards;
214+export default withHistory(Shards);
src/bridge/views/Ticket.js+5−7
@@ -12,7 +12,8 @@ import {
1212import { Row, Col, H1, P } from '../components/Base';
1313
1414import { randomPatq } from '../lib/lib';
15−import { ROUTE_NAMES } from '../lib/router';
15+import { ROUTE_NAMES } from '../lib/routeNames';
16+import { withHistory } from '../store/history';
1617import { urbitWalletFromTicket } from '../lib/wallet';
1718
1819class Ticket extends React.Component {
@@ -62,7 +63,7 @@ class Ticket extends React.Component {
6263 }
6364
6465 render() {
65− const { popRoute, pushRoute, wallet } = this.props;
66+ const { history, wallet } = this.props;
6667 const { ticket, pointName, passphrase } = this.state;
6768
6869 const phPoint = this.pointPlaceholder;
@@ -140,10 +141,7 @@ class Ticket extends React.Component {
140141 className={'mt-4'}
141142 prop-size={'xl wide'}
142143 disabled={Nothing.hasInstance(wallet)}
143− onClick={() => {
144− popRoute();
145− pushRoute(ROUTE_NAMES.SHIPS);
146− }}>
144+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}>
147145 {'Continue →'}
148146 </Button>
149147 </Col>
@@ -152,4 +150,4 @@ class Ticket extends React.Component {
152150 }
153151}
154152
155−export default Ticket;
153+export default withHistory(Ticket);
src/bridge/views/Trezor.js+5−7
@@ -8,7 +8,8 @@ import TrezorConnect from 'trezor-connect';
88import * as secp256k1 from 'secp256k1';
99
1010import { TREZOR_PATH } from '../lib/trezor';
11−import { ROUTE_NAMES } from '../lib/router';
11+import { ROUTE_NAMES } from '../lib/routeNames';
12+import { withHistory } from '../store/history';
1213
1314class Trezor extends React.Component {
1415 constructor(props) {
@@ -60,7 +61,7 @@ class Trezor extends React.Component {
6061 }
6162
6263 render() {
63− const { pushRoute, popRoute, wallet } = this.props;
64+ const { history, wallet } = this.props;
6465 const { hdpath, account } = this.state;
6566 const { handleAccountSelection } = this;
6667
@@ -129,10 +130,7 @@ class Trezor extends React.Component {
129130 className={'mt-8'}
130131 prop-size={'wide lg'}
131132 disabled={Maybe.Nothing.hasInstance(wallet)}
132− onClick={() => {
133− popRoute();
134− pushRoute(ROUTE_NAMES.SHIPS);
135− }}>
133+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}>
136134 {'Continue →'}
137135 </Button>
138136 </Col>
@@ -141,4 +139,4 @@ class Trezor extends React.Component {
141139 }
142140}
143141
144−export default Trezor;
142+export default withHistory(Trezor);
src/bridge/views/ViewPoint.js+5−5
@@ -6,7 +6,8 @@ import { Button } from '../components/Base';
66import { Row, Col, H1, P } from '../components/Base';
77import { InnerLabel, PointInput, ValidatedSigil } from '../components/Base';
88
9−import { ROUTE_NAMES } from '../lib/router';
9+import { ROUTE_NAMES } from '../lib/routeNames';
10+import { withHistory } from '../store/history';
1011
1112class ViewPoint extends React.Component {
1213 constructor(props) {
@@ -27,7 +28,7 @@ class ViewPoint extends React.Component {
2728
2829 render() {
2930 const { pointName } = this.state;
30− const { popRoute, pushRoute, setPointCursor } = this.props;
31+ const { history, setPointCursor } = this.props;
3132
3233 // NB (jtobin):
3334 //
@@ -69,8 +70,7 @@ class ViewPoint extends React.Component {
6970 disabled={valid === false}
7071 onClick={() => {
7172 setPointCursor(Maybe.Just(ob.patp2dec(pointName)));
72− popRoute();
73− pushRoute(ROUTE_NAMES.SHIP);
73+ history.popAndPush(ROUTE_NAMES.SHIP);
7474 }}>
7575 {'Continue →'}
7676 </Button>
@@ -80,4 +80,4 @@ class ViewPoint extends React.Component {
8080 }
8181}
8282
83−export default ViewPoint;
83+export default withHistory(ViewPoint);
src/bridge/views/Wallet.js+4−3
@@ -4,7 +4,8 @@ import { Button, H1, P } from '../components/Base';
44import { InnerLabelDropdown } from '../components/Base';
55import { Row, Col } from '../components/Base';
66
7−import { ROUTE_NAMES } from '../lib/router';
7+import { ROUTE_NAMES } from '../lib/routeNames';
8+import { withHistory } from '../store/history';
89import { WALLET_NAMES, renderWalletType } from '../lib/wallet';
910
1011class Wallet extends React.Component {
@@ -77,7 +78,7 @@ class Wallet extends React.Component {
7778 <Button
7879 className={'mt-10'}
7980 onClick={() =>
80− props.pushRoute(
81+ props.history.push(
8182 props.walletType === WALLET_NAMES.MNEMONIC
8283 ? ROUTE_NAMES.MNEMONIC
8384 : props.walletType === WALLET_NAMES.TICKET
@@ -103,4 +104,4 @@ class Wallet extends React.Component {
103104 }
104105}
105106
106−export default Wallet;
107+export default withHistory(Wallet);
src/walletgen/lib/lib.js+1−1
@@ -1,5 +1,5 @@
11import * as ob from 'urbit-ob';
2−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
2+import * as kg from 'urbit-key-generation/dist/index';
33import * as more from 'more-entropy';
44import lodash from 'lodash';
55import {