on master help sign in
feat: implement very simple router with contextual state
commit1a0f264ef408beeef6740e5e776d672a9babb61f authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateMon 10 Jun 2019, 21:46 (7 years ago) parent5dd541f
41 files changed+1699−1689
package-lock.json+11−103 package.json+2−4 src/bridge/Bridge.js+101−152 src/bridge/components/Header.js+37−33 src/bridge/components/OldHeader.js+30−26 src/bridge/components/RenderedPoint.js+18−15 src/bridge/components/StatelessTransaction.js+105−96 src/bridge/lib/history.js+60 src/bridge/lib/invite.js+67−67 src/bridge/lib/keys.js+21−29 src/bridge/lib/routeNames.js+33 src/bridge/lib/router.js+122−164 src/bridge/lib/wallet.js+38−38 src/bridge/store/txnConfirmations.js+37 src/bridge/views/AcceptTransfer.js+25−23 src/bridge/views/CancelTransfer.js+12−12 src/bridge/views/CreateGalaxy.js+43−39 src/bridge/views/GenKeyfile.js+23−22 src/bridge/views/InviteTicket.js+44−37 src/bridge/views/InvitesManage.js+24−22 src/bridge/views/InvitesSend.js+73−68 src/bridge/views/IssueChild.js+46−42 src/bridge/views/Keystore.js+39−37 src/bridge/views/Landing.js+69−61 src/bridge/views/Ledger.js+69−67 src/bridge/views/Mnemonic.js+34−32 src/bridge/views/Network.js+31−29 src/bridge/views/Point.js+20−32 src/bridge/views/Point/Actions.js+79−68 src/bridge/views/Points.js+40−37 src/bridge/views/PrivateKey.js+22−22 src/bridge/views/SentTransaction.js+42−45 src/bridge/views/SetKeys.js+36−36 src/bridge/views/SetProxy.js+31−29 src/bridge/views/Shards.js+55−50 src/bridge/views/Ticket.js+36−35 src/bridge/views/Transfer.js+27−25 src/bridge/views/Trezor.js+39−38 src/bridge/views/ViewPoint.js+22−20 src/bridge/views/Wallet.js+27−25 src/walletgen/lib/lib.js+9−9
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+101−152
@@ -1,24 +1,26 @@
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';
6−import React from 'react';
7−
8−import Web3 from 'web3';
9−import Footer from './components/Footer';
10−import Header from './components/Header';
11−import { Container, Row, Col } from './components/Base';
12−
13−import { ROUTE_NAMES, router } from './lib/router';
14−import { NETWORK_NAMES } from './lib/network';
1+import { Just, Nothing } from "folktale/maybe";
2+import { includes } from "lodash";
3+import * as azimuth from "azimuth-js";
4+import { CONTRACT_ADDRESSES } from "./lib/contracts";
5+import React from "react";
6+
7+import Web3 from "web3";
8+import Footer from "./components/Footer";
9+import Header from "./components/Header";
10+import { Container, Row, Col } from "./components/Base";
11+
12+import { router } from "./lib/router";
13+import { ROUTE_NAMES } from "./lib/routeNames";
14+import { HistoryProvider, withHistory, useHistory } from "./lib/history";
15+import { TxnConfirmationsProvider } from "./store/txnConfirmations";
16+import { NETWORK_NAMES } from "./lib/network";
1517import {
1618 WALLET_NAMES,
1719 DEFAULT_HD_PATH,
1820 walletFromMnemonic,
1921 addressFromSecp256k1Public,
20−} from './lib/wallet';
21−import { BRIDGE_ERROR } from './lib/error';
22+} from "./lib/wallet";
23+import { BRIDGE_ERROR } from "./lib/error";
2224
2325const initWeb3 = networkType => {
2426 if (networkType === NETWORK_NAMES.MAINNET) {
@@ -31,7 +33,7 @@ const initWeb3 = networkType => {
3133 const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.MAINNET);
3234 return { web3: web3, contracts: contracts };
3335 } else if (networkType === NETWORK_NAMES.LOCAL) {
34− const protocol = process.env.NODE_ENV === 'development' ? 'ws' : 'wss';
36+ const protocol = process.env.NODE_ENV === "development" ? "ws" : "wss";
3537 const endpoint = `${protocol}://localhost:8545`;
3638 const provider = new Web3.providers.WebsocketProvider(endpoint);
3739 const web3 = new Web3(provider);
@@ -40,12 +42,36 @@ const initWeb3 = networkType => {
4042 }
4143};
4244
45+export const View = function(props) {
46+ const history = useHistory();
47+ const Route = router(history.peek());
48+
49+ return <Route {...props} />;
50+};
51+
52+const VariableWidthColumn = withHistory(({ history, children }) => (
53+ // For the invite acceptance flow, widen the screen to use the full
54+ // container, and hide the breadcrumbs
55+ <Col
56+ className={
57+ history.includes(ROUTE_NAMES.INVITE_TICKET)
58+ ? "col-md-12"
59+ : "col-md-offset-1 col-md-10"
60+ }
61+ style={
62+ history.includes(ROUTE_NAMES.INVITE_TICKET) ? {} : { maxWidth: "620px" }
63+ }
64+ >
65+ {children}
66+ </Col>
67+));
68+
4369class Bridge extends React.Component {
4470 constructor(props) {
4571 super(props);
4672
4773 const networkType =
48− process.env.NODE_ENV === 'development'
74+ process.env.NODE_ENV === "development"
4975 ? NETWORK_NAMES.LOCAL
5076 : NETWORK_NAMES.MAINNET;
5177
@@ -67,9 +93,6 @@ class Bridge extends React.Component {
6793 // 'EthereumWallet'.
6894
6995 this.state = {
70− // routes
71− routeCrumbs: Stack([ROUTE_NAMES.LANDING]),
72− routeData: {},
7396 // network
7497 networkType: networkType,
7598 web3: Just(web3),
@@ -90,9 +113,6 @@ class Bridge extends React.Component {
90113 txnConfirmations: {},
91114 };
92115
93− this.pushRoute = this.pushRoute.bind(this);
94− this.popRoute = this.popRoute.bind(this);
95− this.skipRoute = this.skipRoute.bind(this);
96116 this.setNetworkType = this.setNetworkType.bind(this);
97117 this.setNetwork = this.setNetwork.bind(this);
98118 this.setWalletType = this.setWalletType.bind(this);
@@ -102,24 +122,15 @@ class Bridge extends React.Component {
102122 this.setAuthMnemonic = this.setAuthMnemonic.bind(this);
103123 this.setPointCursor = this.setPointCursor.bind(this);
104124 this.setTxnHashCursor = this.setTxnHashCursor.bind(this);
105− this.setTxnConfirmations = this.setTxnConfirmations.bind(this);
106125 this.setNetworkSeedCache = this.setNetworkSeedCache.bind(this);
107126 this.addToPointCache = this.addToPointCache.bind(this);
108127 }
109128
110129 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−
118130 // NB (jtobin):
119131 //
120132 // If running in development, the following can be tweaked to get you set
121133 // up with a desirable initial state.
122−
123134 // if (process.env.NODE_ENV === 'development') {
124135 //
125136 // const socket = 'ws://localhost:8545'
@@ -152,34 +163,8 @@ class Bridge extends React.Component {
152163 // }
153164 }
154165
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;
166− }
167− }
168−
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−
181166 setNetworkType(symbol) {
182− if (lodash.includes(NETWORK_NAMES, symbol)) {
167+ if (includes(NETWORK_NAMES, symbol)) {
183168 this.setState({
184169 networkType: symbol,
185170 });
@@ -203,7 +188,7 @@ class Bridge extends React.Component {
203188 }
204189
205190 setWalletType(symbol) {
206− if (lodash.includes(WALLET_NAMES, symbol)) {
191+ if (includes(WALLET_NAMES, symbol)) {
207192 this.setState({
208193 walletType: symbol,
209194 });
@@ -232,7 +217,7 @@ class Bridge extends React.Component {
232217 wallet = walletFromMnemonic(
233218 mnemonic,
234219 DEFAULT_HD_PATH,
235− urbitWallet.value.meta.passphrase
220+ urbitWallet.value.meta.passphrase,
236221 );
237222 wallet.map(wal => {
238223 wal.address = urbitWallet.value.ownership.keys.address;
@@ -260,23 +245,8 @@ class Bridge extends React.Component {
260245 this.setState({ txnHashCursor });
261246 }
262247
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−
276248 render() {
277249 const {
278− routeCrumbs,
279− routeData,
280250 networkType,
281251 walletType,
282252 walletHdPath,
@@ -288,87 +258,66 @@ class Bridge extends React.Component {
288258 pointCursor,
289259 pointCache,
290260 txnHashCursor,
291− txnConfirmations,
292261 web3,
293262 contracts,
294263 } = this.state;
295264
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−
312265 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'} />
266+ <HistoryProvider initialRoutes={[{ name: ROUTE_NAMES.LANDING }]}>
267+ <TxnConfirmationsProvider>
268+ <Container>
269+ <Row>
270+ <VariableWidthColumn>
271+ <Header
272+ skipRoute={this.skipRoute}
273+ networkType={networkType}
274+ wallet={wallet}
275+ pointCursor={pointCursor}
276+ />
277+
278+ <Row className={"row wrapper"}>
279+ <View
280+ // network
281+ setNetworkType={this.setNetworkType}
282+ setNetwork={this.setNetwork}
283+ networkType={networkType}
284+ web3={web3}
285+ contracts={contracts}
286+ // wallet
287+ setWalletType={this.setWalletType}
288+ setWalletHdPath={this.setWalletHdPath}
289+ setWallet={this.setWallet}
290+ walletType={walletType}
291+ walletHdPath={walletHdPath}
292+ wallet={wallet}
293+ // urbit wallet
294+ urbitWallet={urbitWallet}
295+ setUrbitWallet={this.setUrbitWallet}
296+ authMnemonic={authMnemonic}
297+ setAuthMnemonic={this.setAuthMnemonic}
298+ // point
299+ setPointCursor={this.setPointCursor}
300+ addToPointCache={this.addToPointCache}
301+ pointCursor={pointCursor}
302+ pointCache={pointCache}
303+ networkSeedCache={networkSeedCache}
304+ networkRevisionCache={networkRevisionCache}
305+ setNetworkSeedCache={this.setNetworkSeedCache}
306+ // txn
307+ onSent={this.setTxnHashCursor}
308+ setTxnHashCursor={this.setTxnHashCursor}
309+ txnHashCursor={txnHashCursor}
310+ />
311+
312+ <div className={"push"} />
313+ </Row>
314+
315+ <Footer />
316+ </VariableWidthColumn>
366317 </Row>
367−
368− <Footer />
369− </Col>
370− </Row>
371− </Container>
318+ </Container>
319+ </TxnConfirmationsProvider>
320+ </HistoryProvider>
372321 );
373322 }
374323}
src/bridge/components/Header.js+37−33
@@ -1,58 +1,62 @@
1−import React from 'react';
2−import { Button, Chevron } from '../components/Base';
1+import React from "react";
2+import { Button, Chevron } from "../components/Base";
33
4−import { renderRoute } from '../lib/router';
5−import { isLast } from '../lib/lib';
4+import { getRouteBreadcrumb } from "../lib/router";
5+import { ROUTE_NAMES } from "../lib/routeNames";
6+import { useHistory } from "../lib/history";
7+import { 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();
1011
1112 // FIXME probably more straightforward to render them normally and just
1213 // return the reversed array of renders
13− const rendered = history.map((route, idx) => {
14+ const rendered = history.routes.map((route, idx) => {
1415 return (
15− <div className={'flex items-center'} key={`history-${idx}`}>
16+ <div className={"flex items-center"} key={`history-${idx}`}>
1617 <Button
17− prop-type={'link'}
18− prop-size={'sm'}
18+ prop-type={"link"}
19+ prop-size={"sm"}
1920 key={`history-button-${idx}`}
20− onClick={() => skipRoute(history.size - idx - 1)}>
21− {renderRoute(props, route)}
21+ onClick={() => history.pop(history.size - idx - 1)}
22+ >
23+ {getRouteBreadcrumb(props, route)}
2224 </Button>
2325
2426 {isLast(history.size, idx) ? (
2527 <div />
2628 ) : (
27− <Chevron className={'h-4 mh-2'} />
29+ <Chevron className={"h-4 mh-2"} />
2830 )}
2931 </div>
3032 );
3133 });
3234
3335 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− );
36+}
37+
38+function Header(props) {
39+ const history = useHistory();
40+
41+ const showCrumbs = !history.includes(ROUTE_NAMES.INVITE_TICKET);
42+
43+ if (showCrumbs) {
44+ return (
45+ <div className={"flex items-center h-10"}>
46+ <Crumbs
47+ networkType={props.networkType}
48+ wallet={props.wallet}
49+ pointCursor={props.pointCursor}
50+ />
51+ </div>
52+ );
53+ }
4854
49− const noCrumbs = (
50− <h2 className="mt-9 clickable" onClick={() => props.skipRoute(1)}>
55+ return (
56+ <h2 className="mt-9 clickable" onClick={() => history.pop()}>
5157 Bridge
5258 </h2>
5359 );
54−
55− return props.showCrumbs ? crumbs : noCrumbs;
56−};
60+}
5761
5862export default Header;
src/bridge/components/OldHeader.js+30−26
@@ -1,8 +1,8 @@
11import React from 'react';
22import { Button, Chevron } from '../components/Base';
33
4−import { renderRoute } from '../lib/router';
5−import { isLast } from '../lib/lib';
4+import { getRouteBreadcrumb } from '../lib/router'
5+import { isLast } from '../lib/lib'
66
77const Crumbs = props => {
88 const { routeCrumbs, skipRoute } = props;
@@ -11,30 +11,34 @@ const Crumbs = props => {
1111 // FIXME probably more straightforward to render them normally and just
1212 // return the reversed array of renders
1313 const rendered = history.map((route, idx) => {
14− return (
15− <div className={'flex items-center'} key={`history-${idx}`}>
16− <Button
17− size={'s'}
18− type={'link'}
19− color={'black'}
20− key={`history-button-${idx}`}
21− onClick={() => skipRoute(history.size - idx - 1)}>
22− {`${renderRoute(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− return rendered;
35−};
36−
37−const Header = props => (
14+
15+ return (
16+ <div className={'flex items-center'} key={ `history-${idx}` }>
17+
18+ <Button
19+ size={'s'}
20+ type={'link'}
21+ color={'black'}
22+ key={ `history-button-${idx}` }
23+ onClick={ () => skipRoute(history.size - idx - 1) }
24+ >
25+ { `${getRouteBreadcrumb(props, route)}` }
26+ </Button>
27+
28+ {
29+ isLast(history.size, idx)
30+ ? <div />
31+ : <Chevron className={'h-4 mh-2'} />
32+ }
33+
34+ </div>
35+ )
36+ })
37+
38+ return rendered
39+}
40+
41+const Header = (props) =>
3842 <div className={'flex items-center h-8 pt-4'}>
3943 <Crumbs
4044 routeCrumbs={props.routeCrumbs}
src/bridge/components/RenderedPoint.js+18−15
@@ -1,12 +1,12 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import { Col } from './Base';
4−import { Button } from './Base';
5−import { pour } from 'sigil-js';
6−import * as ob from 'urbit-ob';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import { Col } from "./Base";
4+import { Button } from "./Base";
5+import { pour } from "sigil-js";
6+import * as ob from "urbit-ob";
77
8−import { ROUTE_NAMES } from '../lib/router';
9−import ReactSVGComponents from './ReactSVGComponents';
8+import { ROUTE_NAMES } from "../lib/routeNames";
9+import ReactSVGComponents from "./ReactSVGComponents";
1010
1111const RenderedPoint = props => {
1212 const { setPointCursor, routeHandler, point } = props;
@@ -19,14 +19,15 @@ const RenderedPoint = props => {
1919 });
2020
2121 return (
22− <Col className={'col-md-3 mb-8'} key={`rendered-${point}`}>
22+ <Col className={"col-md-3 mb-8"} key={`rendered-${point}`}>
2323 <div
2424 key={`rendered-sigil-${point}`}
2525 className="clickable"
2626 onClick={() => {
2727 setPointCursor(Maybe.Just(point));
2828 routeHandler(ROUTE_NAMES.SHIP);
29− }}>
29+ }}
30+ >
3031 {sigil}
3132 </div>
3233 <div
@@ -35,19 +36,21 @@ const RenderedPoint = props => {
3536 onClick={() => {
3637 setPointCursor(Maybe.Just(point));
3738 routeHandler(ROUTE_NAMES.SHIP);
38− }}>
39+ }}
40+ >
3941 <code>{name}</code>
4042 </div>
4143 <div key={`rendered-deets-${point}`}>
4244 <Button
4345 key={`rendered-deets-button-${point}`}
44− prop-type={'link'}
45− prop-size={'sm'}
46+ prop-type={"link"}
47+ prop-size={"sm"}
4648 onClick={() => {
4749 setPointCursor(Maybe.Just(point));
4850 routeHandler(ROUTE_NAMES.SHIP);
49− }}>
50− {'Details →'}
51+ }}
52+ >
53+ {"Details →"}
5154 </Button>
5255 </div>
5356 </Col>
src/bridge/components/StatelessTransaction.js+105−96
@@ -1,16 +1,17 @@
1−import { Nothing, Just } from 'folktale/maybe';
2−import { Ok } from 'folktale/result';
3−import React from 'react';
4−import * as need from '../lib/need';
1+import { Nothing, Just } from "folktale/maybe";
2+import { Ok } from "folktale/result";
3+import React from "react";
4+import * as need from "../lib/need";
55
6−import { Code, H3 } from './Base';
7−import { Button } from './Base';
8−import { CheckboxButton, Input, InnerLabel, InnerLabelDropdown } from './Base';
9−import { Warning } from '../components/Base';
6+import { Code, H3 } from "./Base";
7+import { Button } from "./Base";
8+import { CheckboxButton, Input, InnerLabel, InnerLabelDropdown } from "./Base";
9+import { Warning } from "../components/Base";
1010
11−import { BRIDGE_ERROR } from '../lib/error';
12−import { ROUTE_NAMES } from '../lib/router';
13−import * as tank from '../lib/tank';
11+import { BRIDGE_ERROR } from "../lib/error";
12+import { ROUTE_NAMES } from "../lib/routeNames";
13+import { withHistory } from "../lib/history";
14+import * as tank from "../lib/tank";
1415
1516import {
1617 sendSignedTransaction,
@@ -19,13 +20,14 @@ import {
1920 hexify,
2021 renderSignedTx,
2122 signTransaction,
22−} from '../lib/txn';
23+} from "../lib/txn";
24+import { withTxnConfirmations } from "../store/txnConfirmations";
2325
2426const SUBMISSION_STATES = {
25− PROMPT: 'Send transaction',
26− PREPARING: 'Preparing...',
27− FUNDING: 'Finding transaction funding...',
28− SENDING: 'Sending transaction...',
27+ PROMPT: "Send transaction",
28+ PREPARING: "Preparing...",
29+ FUNDING: "Finding transaction funding...",
30+ SENDING: "Sending transaction...",
2931};
3032
3133class StatelessTransaction extends React.Component {
@@ -33,13 +35,13 @@ class StatelessTransaction extends React.Component {
3335 super(props);
3436
3537 this.state = {
36− gasPrice: '5',
37− gasLimit: '600000',
38+ gasPrice: "5",
39+ gasLimit: "600000",
3840 showGasDetails: false,
3941 userApproval: false,
40− chainId: '1',
42+ chainId: "1",
4143 customChain: false,
42− nonce: '0',
44+ nonce: "0",
4345 stx: Nothing(),
4446 txn: Nothing(),
4547 txStatus: SUBMISSION_STATES.PROMPT,
@@ -80,7 +82,7 @@ class StatelessTransaction extends React.Component {
8082 const txMetadata = {
8183 nonce: r[0],
8284 chainId: r[1],
83− gasPrice: fromWei(r[2], 'gwei'),
85+ gasPrice: fromWei(r[2], "gwei"),
8486 };
8587
8688 this.setState({ ...txMetadata });
@@ -186,12 +188,12 @@ class StatelessTransaction extends React.Component {
186188 try {
187189 this.setState({ txStatus: SUBMISSION_STATES.PREPARING });
188190 const res = await props.beforeSend(stx);
189− if (res === false) throw new Error('beforeSend disallowed sending');
191+ if (res === false) throw new Error("beforeSend disallowed sending");
190192 } catch (e) {
191− console.log('beforeSend error', e);
193+ console.log("beforeSend error", e);
192194 this.setState({
193195 txStatus: SUBMISSION_STATES.PROMPT,
194− txError: Just('Something went wrong!'),
196+ txError: Just("Something went wrong!"),
195197 });
196198 return;
197199 }
@@ -200,7 +202,7 @@ class StatelessTransaction extends React.Component {
200202 this.setState({ txStatus: SUBMISSION_STATES.FUNDING });
201203
202204 const rawTx = hexify(stx.serialize());
203− const cost = state.gasLimit * toWei(state.gasPrice, 'gwei');
205+ const cost = state.gasLimit * toWei(state.gasPrice, "gwei");
204206
205207 const address = need.address(props);
206208 let balance = await web3.eth.getBalance(address);
@@ -230,18 +232,18 @@ class StatelessTransaction extends React.Component {
230232 web3,
231233 state.stx,
232234 usedTank,
233− props.setTxnConfirmations
235+ props.setTxnConfirmations,
234236 )
235237 .then(txHash => {
236238 props.onSent(Just(Ok(txHash)), state.stx.value);
237− props.popRoute();
239+ props.history.pop();
238240
239241 let routeData = {};
240242 if (props.networkSeed) {
241243 props.setNetworkSeedCache(props.networkSeed);
242244 routeData.promptKeyfile = true;
243245 }
244− props.pushRoute(ROUTE_NAMES.SENT_TRANSACTION, routeData);
246+ props.history.push(ROUTE_NAMES.SENT_TRANSACTION, routeData);
245247 })
246248 .catch(err => {
247249 this.setState({
@@ -269,13 +271,13 @@ class StatelessTransaction extends React.Component {
269271
270272 getChainTitle(chainId) {
271273 let map = {
272− '1': 'Mainnet - 1',
273− '2': 'Morden - 2',
274− '3': 'Ropsten - 3',
275− '4': 'Goerli - 4',
276− '42': 'Kovan - 42',
277− '1337': 'Geth private chains - 1337',
278− custom: 'Custom',
274+ "1": "Mainnet - 1",
275+ "2": "Morden - 2",
276+ "3": "Ropsten - 3",
277+ "4": "Goerli - 4",
278+ "42": "Kovan - 42",
279+ "1337": "Geth private chains - 1337",
280+ custom: "Custom",
279281 };
280282
281283 return map[chainId];
@@ -284,32 +286,32 @@ class StatelessTransaction extends React.Component {
284286 getChainOptions() {
285287 return [
286288 {
287− title: this.getChainTitle('1'),
288− value: '1',
289+ title: this.getChainTitle("1"),
290+ value: "1",
289291 },
290292 {
291− title: this.getChainTitle('2'),
292− value: '2',
293+ title: this.getChainTitle("2"),
294+ value: "2",
293295 },
294296 {
295− title: this.getChainTitle('3'),
296− value: '3',
297+ title: this.getChainTitle("3"),
298+ value: "3",
297299 },
298300 {
299− title: this.getChainTitle('4'),
300− value: '4',
301+ title: this.getChainTitle("4"),
302+ value: "4",
301303 },
302304 {
303− title: this.getChainTitle('42'),
304− value: '42',
305+ title: this.getChainTitle("42"),
306+ value: "42",
305307 },
306308 {
307− title: this.getChainTitle('1337'),
308− value: '1337',
309+ title: this.getChainTitle("1337"),
310+ value: "1337",
309311 },
310312 {
311− title: this.getChainTitle('custom'),
312− value: 'custom',
313+ title: this.getChainTitle("custom"),
314+ value: "custom",
313315 },
314316 ];
315317 }
@@ -349,26 +351,27 @@ class StatelessTransaction extends React.Component {
349351
350352 const chainOptions = this.getChainOptions();
351353
352− const generateButtonColor = Nothing.hasInstance(txn) ? 'blue' : 'green';
354+ const generateButtonColor = Nothing.hasInstance(txn) ? "blue" : "green";
353355
354− const signerButtonColor = Nothing.hasInstance(stx) ? 'blue' : 'green';
356+ const signerButtonColor = Nothing.hasInstance(stx) ? "blue" : "green";
355357
356358 const generateTxnButton = (
357359 <Button
358− className={'mt-8'}
360+ className={"mt-8"}
359361 disabled={!canGenerate}
360362 prop-color={generateButtonColor}
361− prop-size={'lg wide'}
362− onClick={createUnsignedTxn}>
363− {'Generate Transaction'}
363+ prop-size={"lg wide"}
364+ onClick={createUnsignedTxn}
365+ >
366+ {"Generate Transaction"}
364367 </Button>
365368 );
366369
367370 const unsignedTxnDisplay = txn.matchWith({
368− Nothing: _ => '',
371+ Nothing: _ => "",
369372 Just: tx => (
370373 <React.Fragment>
371− <H3 className={'mt-8'}>{'Unsigned Transaction'}</H3>
374+ <H3 className={"mt-8"}>{"Unsigned Transaction"}</H3>
372375 <Code>{JSON.stringify(tx.value, null, 2)}</Code>
373376 </React.Fragment>
374377 ),
@@ -414,45 +417,48 @@ class StatelessTransaction extends React.Component {
414417
415418 const gasPriceDialogue = (
416419 <Input
417− className={'mono mt-4'}
418− prop-size={'md'}
419− prop-format={'innerLabel'}
420+ className={"mono mt-4"}
421+ prop-size={"md"}
422+ prop-format={"innerLabel"}
420423 value={gasPrice}
421− onChange={setGasPrice}>
422− <InnerLabel>{'Gas Price (gwei)'}</InnerLabel>
424+ onChange={setGasPrice}
425+ >
426+ <InnerLabel>{"Gas Price (gwei)"}</InnerLabel>
423427 </Input>
424428 );
425429
426430 const gasLimitDialogue = (
427431 <Input
428− className={'mono mt-4'}
429− prop-size={'md'}
430− prop-format={'innerLabel'}
432+ className={"mono mt-4"}
433+ prop-size={"md"}
434+ prop-format={"innerLabel"}
431435 value={gasLimit}
432− onChange={setGasLimit}>
433− <InnerLabel>{'Gas Limit'}</InnerLabel>
436+ onChange={setGasLimit}
437+ >
438+ <InnerLabel>{"Gas Limit"}</InnerLabel>
434439 </Input>
435440 );
436441
437442 const nonceDialogue = (
438443 <Input
439− className={'mono mt-4 mb-4'}
440− prop-size={'md'}
441− prop-format={'innerLabel'}
444+ className={"mono mt-4 mb-4"}
445+ prop-size={"md"}
446+ prop-format={"innerLabel"}
442447 value={nonce}
443− onChange={setNonce}>
444− <InnerLabel>{'Nonce'}</InnerLabel>
448+ onChange={setNonce}
449+ >
450+ <InnerLabel>{"Nonce"}</InnerLabel>
445451 </Input>
446452 );
447453
448454 const chainDialogueTitle = customChain
449− ? 'Custom'
455+ ? "Custom"
450456 : this.getChainTitle(chainId);
451457 const chainDialogue = (
452458 <InnerLabelDropdown
453− className={'mt-6'}
459+ className={"mt-6"}
454460 fullWidth={true}
455− title={'Chain ID'}
461+ title={"Chain ID"}
456462 options={chainOptions}
457463 handleUpdate={handleChainUpdate}
458464 currentSelectionTitle={chainDialogueTitle}
@@ -461,12 +467,13 @@ class StatelessTransaction extends React.Component {
461467
462468 const customChainDialogue = !customChain ? null : (
463469 <Input
464− className={'mono mt-4 mb-8'}
465− prop-size={'md'}
466− prop-format={'innerLabel'}
470+ className={"mono mt-4 mb-8"}
471+ prop-size={"md"}
472+ prop-format={"innerLabel"}
467473 value={chainId}
468− onChange={setChainId}>
469− <InnerLabel>{'Chain ID'}</InnerLabel>
474+ onChange={setChainId}
475+ >
476+ <InnerLabel>{"Chain ID"}</InnerLabel>
470477 </Input>
471478 );
472479
@@ -484,21 +491,22 @@ class StatelessTransaction extends React.Component {
484491 const signTxnButton = (
485492 <Button
486493 disabled={!canSign}
487− className={'mt-8'}
488− prop-size={'lg wide'}
494+ className={"mt-8"}
495+ prop-size={"lg wide"}
489496 prop-color={signerButtonColor}
490497 onClick={() =>
491498 signTransaction({ ...this.props, ...this.state, setStx: this.setStx })
492− }>
493− {'Sign Transaction'}
499+ }
500+ >
501+ {"Sign Transaction"}
494502 </Button>
495503 );
496504
497505 const signedTxnDisplay = stx.matchWith({
498− Nothing: _ => '',
506+ Nothing: _ => "",
499507 Just: tx => (
500508 <React.Fragment>
501− <H3 className={'mt-8'}>{'Signed Transaction'}</H3>
509+ <H3 className={"mt-8"}>{"Signed Transaction"}</H3>
502510 <Code>{JSON.stringify(renderSignedTx(tx.value), null, 2)}</Code>
503511 </React.Fragment>
504512 ),
@@ -506,7 +514,7 @@ class StatelessTransaction extends React.Component {
506514
507515 const confirmButton = (
508516 <CheckboxButton
509− className={'mt-8'}
517+ className={"mt-8"}
510518 disabled={!canApprove}
511519 onToggle={setUserApproval}
512520 state={userApproval}
@@ -515,13 +523,14 @@ class StatelessTransaction extends React.Component {
515523 );
516524
517525 const sending = txStatus !== SUBMISSION_STATES.PROMPT;
518− const sendTxnSpinner = sending ? '' : 'hide';
526+ const sendTxnSpinner = sending ? "" : "hide";
519527 const sendTxnButton = (
520528 <Button
521− prop-size={'xl wide'}
522− className={'mt-8'}
529+ prop-size={"xl wide"}
530+ className={"mt-8"}
523531 disabled={!canSend || sending}
524− onClick={sendTxn}>
532+ onClick={sendTxn}
533+ >
525534 <span className="relative">
526535 <span className={`btn-spinner ${sendTxnSpinner}`} />
527536 {txStatus}
@@ -530,7 +539,7 @@ class StatelessTransaction extends React.Component {
530539 );
531540
532541 const sendDialogue = web3.matchWith({
533− Nothing: _ => '',
542+ Nothing: _ => "",
534543 Just: _ => (
535544 <React.Fragment>
536545 {confirmButton}
@@ -540,11 +549,11 @@ class StatelessTransaction extends React.Component {
540549 });
541550
542551 const txnErrorDialogue = Nothing.hasInstance(state.txError) ? (
543− ''
552+ ""
544553 ) : (
545− <Warning className={'mt-8'}>
554+ <Warning className={"mt-8"}>
546555 <H3 style={{ marginTop: 0, paddingTop: 0 }}>
547− {'There was an error sending your transaction.'}
556+ {"There was an error sending your transaction."}
548557 </H3>
549558 {state.txError.value}
550559 </Warning>
@@ -577,4 +586,4 @@ class StatelessTransaction extends React.Component {
577586 }
578587}
579588
580−export default StatelessTransaction;
589+export default withTxnConfirmations(withHistory(StatelessTransaction));
src/bridge/lib/history.jsnew file+60
@@ -0,0 +1,60 @@
1+import React, { useContext, useState, useEffect } from "react";
2+import { last, includes } from "lodash";
3+
4+import { BRIDGE_ERROR } from "./error";
5+import { ROUTE_NAMES } from "./routeNames";
6+
7+export const HistoryContext = React.createContext(null);
8+
9+export function HistoryProvider({ initialRoutes = [], children }) {
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, { name, data }]);
20+ },
21+ popAndPush: (name, data = {}) =>
22+ setHistory([...history.slice(0, history.length - 1), { name, data }]),
23+ pop: (count = 1) =>
24+ setHistory([...history.slice(0, history.length - count)]),
25+ peek: () => last(history),
26+ size: history.length,
27+ includes: name => includes(history.map(r => r.name), name),
28+ data: () => last(history).data,
29+ };
30+
31+ // Scroll to top of page with each route transition.
32+ useEffect(() => {
33+ window.scrollTo(0, 0);
34+ }, [history]);
35+
36+ useEffect(() => {
37+ window.history.pushState(null, null, null);
38+
39+ window.onpopstate = e => {
40+ window.history.pushState(null, null, null);
41+ value.pop();
42+ };
43+ });
44+
45+ return (
46+ <HistoryContext.Provider value={value}>{children}</HistoryContext.Provider>
47+ );
48+}
49+
50+// HOC version
51+export const withHistory = Component => props => (
52+ <HistoryContext.Consumer>
53+ {history => <Component history={history} {...props} />}
54+ </HistoryContext.Consumer>
55+);
56+
57+// Hook version
58+export function useHistory() {
59+ return useContext(HistoryContext);
60+}
src/bridge/lib/invite.js+67−67
@@ -1,67 +1,67 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import Tx from 'ethereumjs-tx';
1+import { Just, Nothing } from "folktale/maybe";
2+import Tx from "ethereumjs-tx";
33
4−import * as azimuth from 'azimuth-js';
5−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
6−import * as wg from '../../walletgen/lib/lib.js';
7−import * as tank from './tank';
4+import * as azimuth from "azimuth-js";
5+import * as kg from "urbit-key-generation/dist/index";
6+import * as wg from "../../walletgen/lib/lib.js";
7+import * as tank from "./tank";
88
9−import JSZip from 'jszip';
10−import saveAs from 'file-saver';
9+import JSZip from "jszip";
10+import saveAs from "file-saver";
1111
12−import { sendSignedTransaction } from './txn';
13−import { BRIDGE_ERROR } from '../lib/error';
14−import { attemptSeedDerivation } from './keys';
15−import { addHexPrefix, WALLET_NAMES } from './wallet';
12+import { sendSignedTransaction } from "./txn";
13+import { BRIDGE_ERROR } from "../lib/error";
14+import { attemptSeedDerivation } from "./keys";
15+import { addHexPrefix, WALLET_NAMES } from "./wallet";
1616
1717const INVITE_STAGES = {
18− INVITE_LOGIN: 'invite login',
19− INVITE_WALLET: 'invite wallet',
20− INVITE_VERIFY: 'invite verify',
21− INVITE_TRANSACTIONS: 'invite transactions',
18+ INVITE_LOGIN: "invite login",
19+ INVITE_WALLET: "invite wallet",
20+ INVITE_VERIFY: "invite verify",
21+ INVITE_TRANSACTIONS: "invite transactions",
2222};
2323
2424const WALLET_STATES = {
25− UNLOCKING: 'Unlocking invite wallet',
26− GENERATING: 'Generating your wallet',
27− RENDERING: 'Creating paper collateral',
28− PAPER_READY: 'Download your wallet',
29− DOWNLOADED: 'Wallet downloaded',
30− TRANSACTIONS: 'Sending transactions',
25+ UNLOCKING: "Unlocking invite wallet",
26+ GENERATING: "Generating your wallet",
27+ RENDERING: "Creating paper collateral",
28+ PAPER_READY: "Download your wallet",
29+ DOWNLOADED: "Wallet downloaded",
30+ TRANSACTIONS: "Sending transactions",
3131};
3232
3333const TRANSACTION_STATES = {
3434 GENERATING: {
35− label: 'Generating transactions',
36− pct: '0%',
35+ label: "Generating transactions",
36+ pct: "0%",
3737 },
3838 SIGNING: {
39− label: 'Signing transactions',
40− pct: '15%',
39+ label: "Signing transactions",
40+ pct: "15%",
4141 },
4242 FUNDING_INVITE: {
43− label: 'Funding invite wallet',
44− pct: '30%',
43+ label: "Funding invite wallet",
44+ pct: "30%",
4545 },
4646 CLAIMING: {
47− label: 'Claiming invite',
48− pct: '55%',
47+ label: "Claiming invite",
48+ pct: "55%",
4949 },
5050 FUNDING_RECIPIENT: {
51− label: 'Funding recipient wallet',
52− pct: '65%',
51+ label: "Funding recipient wallet",
52+ pct: "65%",
5353 },
5454 CONFIGURING: {
55− label: 'Configuring planet',
56− pct: '85%',
55+ label: "Configuring planet",
56+ pct: "85%",
5757 },
5858 CLEANING: {
59− label: 'Cleaning up',
60− pct: '95%',
59+ label: "Cleaning up",
60+ pct: "95%",
6161 },
6262 DONE: {
63− label: 'Done',
64− pct: '100%',
63+ label: "Done",
64+ pct: "100%",
6565 },
6666};
6767
@@ -77,11 +77,11 @@ async function downloadWallet(paper) {
7777 const zip = new JSZip();
7878 //TODO the categories here aren't explained in bridge at all...
7979
80− const bin0 = paper.filter(item => item.bin === '0');
81− const bin1 = paper.filter(item => item.bin === '1');
82− const bin2 = paper.filter(item => item.bin === '2');
83− const bin3 = paper.filter(item => item.bin === '3');
84− const bin4 = paper.filter(item => item.bin === '4');
80+ const bin0 = paper.filter(item => item.bin === "0");
81+ const bin1 = paper.filter(item => item.bin === "1");
82+ const bin2 = paper.filter(item => item.bin === "2");
83+ const bin3 = paper.filter(item => item.bin === "3");
84+ const bin4 = paper.filter(item => item.bin === "4");
8585
8686 const bin0Folder = zip.folder('0. Public');
8787 const bin1Folder = zip.folder('1. Very High Friction Custody');
@@ -95,8 +95,8 @@ async function downloadWallet(paper) {
9595 bin3.forEach(item => bin3Folder.file(`${item.pageTitle}.png`, item.png));
9696 bin4.forEach(item => bin4Folder.file(`${item.pageTitle}.png`, item.png));
9797
98− zip.generateAsync({ type: 'blob' }).then(content => {
99− saveAs(content, 'urbit-wallet.zip');
98+ zip.generateAsync({ type: "blob" }).then(content => {
99+ saveAs(content, "urbit-wallet.zip");
100100 resolve(true);
101101 });
102102 });
@@ -115,7 +115,7 @@ async function startTransactions(args) {
115115
116116 const askForFunding = (address, amount, current) => {
117117 updateProgress({
118− type: 'notify',
118+ type: "notify",
119119 value: `Please make sure ${address} has at least ${amount} wei, we'll continue once that's true. Current balance: ${current}. Waiting`,
120120 });
121121 };
@@ -148,14 +148,14 @@ async function startTransactions(args) {
148148 const point = realPointM.value;
149149
150150 const inviteAddress = inviteWallet.address;
151− console.log('working as', inviteAddress);
151+ console.log("working as", inviteAddress);
152152
153153 // transfer from invite wallet to new wallet
154154
155155 let transferTx = azimuth.ecliptic.transferPoint(
156156 contracts,
157157 point,
158− realWallet.ownership.keys.address
158+ realWallet.ownership.keys.address,
159159 );
160160 transferTx.gas = 500000; //TODO can maybe be lower?
161161
@@ -169,11 +169,11 @@ async function startTransactions(args) {
169169 //NOTE using web3.eth.accounts.signTransaction is broken (1.0.0-beta51)
170170 let transferStx = new Tx(transferTx);
171171 transferStx.sign(inviteWallet.privateKey);
172− let rawTransferStx = '0x' + transferStx.serialize().toString('hex');
172+ let rawTransferStx = "0x" + transferStx.serialize().toString("hex");
173173
174174 const transferCost = transferTx.gas * transferTx.gasPrice;
175175 updateProgress({
176− type: 'progress',
176+ type: "progress",
177177 value: TRANSACTION_STATES.FUNDING_INVITE,
178178 });
179179 let usedTank = await tank.ensureFundsFor(
@@ -182,11 +182,11 @@ async function startTransactions(args) {
182182 inviteAddress,
183183 transferCost,
184184 [rawTransferStx],
185− askForFunding
185+ askForFunding,
186186 );
187187
188188 updateProgress({
189− type: 'progress',
189+ type: "progress",
190190 value: TRANSACTION_STATES.CLAIMING,
191191 });
192192
@@ -205,7 +205,7 @@ async function startTransactions(args) {
205205 let managementTx = azimuth.ecliptic.setManagementProxy(
206206 contracts,
207207 point,
208− realWallet.management.keys.address
208+ realWallet.management.keys.address,
209209 );
210210 managementTx.gas = 200000;
211211 managementTx.nonce = 0;
@@ -219,7 +219,7 @@ async function startTransactions(args) {
219219 pointCache: { [point]: { keyRevisionNumber: 0 } },
220220 });
221221 if (Nothing.hasInstance(networkSeed)) {
222− throw new Error('wtf network seed not derived');
222+ throw new Error("wtf network seed not derived");
223223 }
224224 networkSeed = networkSeed.value;
225225 const networkKeys = kg.deriveNetworkKeys(networkSeed);
@@ -230,7 +230,7 @@ async function startTransactions(args) {
230230 addHexPrefix(networkKeys.crypt.public),
231231 addHexPrefix(networkKeys.auth.public),
232232 1,
233− false
233+ false,
234234 );
235235 keysTx.gas = 150000;
236236 keysTx.nonce = 1;
@@ -247,12 +247,12 @@ async function startTransactions(args) {
247247
248248 let rawStxs = txs.map(tx => {
249249 let stx = new Tx(tx);
250− stx.sign(Buffer.from(realWallet.ownership.keys.private, 'hex'));
251− return '0x' + stx.serialize().toString('hex');
250+ stx.sign(Buffer.from(realWallet.ownership.keys.private, "hex"));
251+ return "0x" + stx.serialize().toString("hex");
252252 });
253253
254254 updateProgress({
255− type: 'progress',
255+ type: "progress",
256256 value: TRANSACTION_STATES.FUNDING_RECIPIENT,
257257 });
258258 usedTank = await tank.ensureFundsFor(
@@ -261,18 +261,18 @@ async function startTransactions(args) {
261261 newAddress,
262262 totalCost,
263263 rawStxs,
264− askForFunding
264+ askForFunding,
265265 );
266266
267267 updateProgress({
268− type: 'progress',
268+ type: "progress",
269269 value: TRANSACTION_STATES.CONFIGURING,
270270 });
271271
272272 await sendAndAwaitConfirm(web3, rawStxs, usedTank);
273273
274274 updateProgress({
275− type: 'progress',
275+ type: "progress",
276276 value: TRANSACTION_STATES.CLEANING,
277277 });
278278
@@ -283,7 +283,7 @@ async function startTransactions(args) {
283283 const sendEthCost = gasPrice * gasLimit;
284284 if (balance > sendEthCost) {
285285 const value = balance - sendEthCost;
286− console.log('sending', value);
286+ console.log("sending", value);
287287 const tx = {
288288 to: newAddress,
289289 value: value,
@@ -293,15 +293,15 @@ async function startTransactions(args) {
293293 };
294294 let stx = new Tx(tx);
295295 stx.sign(inviteWallet.privateKey);
296− const rawTx = '0x' + stx.serialize().toString('hex');
296+ const rawTx = "0x" + stx.serialize().toString("hex");
297297 web3.eth.sendSignedTransaction(rawTx).catch(err => {
298− console.log('error sending value tx, who cares', err);
298+ console.log("error sending value tx, who cares", err);
299299 });
300− console.log('sent old balance');
300+ console.log("sent old balance");
301301 }
302302
303303 updateProgress({
304− type: 'progress',
304+ type: "progress",
305305 value: TRANSACTION_STATES.DONE,
306306 });
307307
@@ -315,7 +315,7 @@ async function sendAndAwaitConfirm(web3, signedTxs, usedTank) {
315315 return new Promise((resolve, reject) => {
316316 sendSignedTransaction(web3, tx, usedTank, resolve);
317317 });
318− })
318+ }),
319319 );
320320}
321321
src/bridge/lib/keys.js+21−29
@@ -1,18 +1,18 @@
1−import BN from 'bn.js';
2−import Maybe from 'folktale/maybe';
3−import * as need from '../lib/need';
1+import BN from "bn.js";
2+import Maybe from "folktale/maybe";
3+import * as need from "../lib/need";
44
5−import * as noun from '../nockjs/noun';
6−import * as serial from '../nockjs/serial';
7−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
5+import * as noun from "../nockjs/noun";
6+import * as serial from "../nockjs/serial";
7+import * as kg from "urbit-key-generation/dist/index";
88
9−import { BRIDGE_ERROR } from './error';
10−import { WALLET_NAMES, eqAddr } from './wallet';
9+import { BRIDGE_ERROR } from "./error";
10+import { WALLET_NAMES, eqAddr } from "./wallet";
1111
1212// ctsy joemfb
1313const b64 = buf => {
14− let hex = buf.reverse().toString('hex');
15− let n = new BN(hex, 'hex');
14+ let hex = buf.reverse().toString("hex");
15+ let n = new BN(hex, "hex");
1616 let c = [];
1717 while (1 === n.cmpn(0)) {
1818 c.push(n.andln(0x3f));
@@ -21,22 +21,14 @@ const b64 = buf => {
2121
2222 // prettier-ignore
2323 const trans = j =>
24− 10 > j
25− ? j + 48
26− : 36 > j
27− ? j + 87
28− : 62 > j
29− ? j + 29
30− : 62 === j
31− ? 45
32− : 126
24+ 10 > j ? j + 48 : 36 > j ? j + 87 : 62 > j ? j + 29 : 62 === j ? 45 : 126;
3325
3426 return (
35− '0w' +
27+ "0w" +
3628 c.reduce(
3729 (a, b, i) =>
38− String.fromCharCode(trans(b)) + (i && 0 === i % 5 ? '.' : '') + a,
39− ''
30+ String.fromCharCode(trans(b)) + (i && 0 === i % 5 ? "." : "") + a,
31+ "",
4032 )
4133 );
4234};
@@ -46,19 +38,19 @@ const jam = seed => {
4638 .jam(seed)
4739 .toString()
4840 .slice(2);
49− const pad = hex.length % 2 === 0 ? hex : '0' + hex;
50− return Buffer.from(pad, 'hex').reverse();
41+ const pad = hex.length % 2 === 0 ? hex : "0" + hex;
42+ return Buffer.from(pad, "hex").reverse();
5143};
5244
5345const genKey = (networkSeed, point, revision) => {
5446 const pair = kg.deriveNetworkKeys(networkSeed);
55− const bnsec = new BN(pair.crypt.private + pair.auth.private + '42', 'hex'); // '42' is curve parameter
47+ const bnsec = new BN(pair.crypt.private + pair.auth.private + "42", "hex"); // '42' is curve parameter
5648
5749 const sed = noun.dwim(
5850 noun.Atom.fromString(point),
5951 noun.Atom.fromInt(revision),
6052 noun.Atom.fromString(bnsec.toString()),
61− noun.Atom.yes
53+ noun.Atom.yes,
6254 );
6355
6456 return b64(jam(sed));
@@ -82,7 +74,7 @@ const attemptSeedDerivation = async (next, args) => {
8274 ? parseInt(pointDetails.keyRevisionNumber)
8375 : parseInt(pointDetails.keyRevisionNumber) - 1;
8476
85− let managementSeed = '';
77+ let managementSeed = "";
8678
8779 const ticketLike = [WALLET_NAMES.TICKET, WALLET_NAMES.SHARDS];
8880
@@ -118,8 +110,8 @@ const attemptSeedDerivation = async (next, args) => {
118110
119111 let networkSeed = Maybe.Nothing();
120112
121− if (managementSeed !== '') {
122− const seed = await kg.deriveNetworkSeed(managementSeed, '', revision);
113+ if (managementSeed !== "") {
114+ const seed = await kg.deriveNetworkSeed(managementSeed, "", revision);
123115
124116 networkSeed = Maybe.Just(seed);
125117 }
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+122−164
@@ -1,182 +1,140 @@
1−import * as ob from 'urbit-ob';
2−import * as need from '../lib/need';
1+import React from "react";
2+import * as ob from "urbit-ob";
3+import * as need from "../lib/need";
34
4−import React from 'react';
5−
6−import Landing from '../views/Landing';
7−
8−import InviteTicket from '../views/InviteTicket';
9−
10−import InvitesSend from '../views/InvitesSend.js';
11−import InvitesManage from '../views/InvitesManage.js';
12−
13−import AcceptTransfer from '../views/AcceptTransfer';
14−import CancelTransfer from '../views/CancelTransfer';
15−import CreateGalaxy from '../views/CreateGalaxy';
16−import GenKeyfile from '../views/GenKeyfile';
17−import IssueChild from '../views/IssueChild';
18−import Network from '../views/Network';
19−import Mnemonic from '../views/Mnemonic';
20−import Ledger from '../views/Ledger';
21−import Trezor from '../views/Trezor';
22−import PrivateKey from '../views/PrivateKey';
23−import Keystore from '../views/Keystore';
24−import ViewPoint from '../views/ViewPoint';
25−import SentTransaction from '../views/SentTransaction';
26−import SetKeys from '../views/SetKeys';
5+import Landing from "../views/Landing";
6+import InviteTicket from "../views/InviteTicket";
7+import InvitesSend from "../views/InvitesSend.js";
8+import InvitesManage from "../views/InvitesManage.js";
9+import AcceptTransfer from "../views/AcceptTransfer";
10+import CancelTransfer from "../views/CancelTransfer";
11+import CreateGalaxy from "../views/CreateGalaxy";
12+import GenKeyfile from "../views/GenKeyfile";
13+import IssueChild from "../views/IssueChild";
14+import Network from "../views/Network";
15+import Mnemonic from "../views/Mnemonic";
16+import Ledger from "../views/Ledger";
17+import Trezor from "../views/Trezor";
18+import PrivateKey from "../views/PrivateKey";
19+import Keystore from "../views/Keystore";
20+import ViewPoint from "../views/ViewPoint";
21+import SentTransaction from "../views/SentTransaction";
22+import SetKeys from "../views/SetKeys";
2723import {
2824 SetManagementProxy,
2925 SetSpawnProxy,
3026 SetTransferProxy,
31−} from '../views/SetProxy';
32−import Point from '../views/Point';
33−import Points from '../views/Points';
34−import Shards from '../views/Shards';
35−import Ticket from '../views/Ticket';
36−import Transfer from '../views/Transfer';
37−import Wallet from '../views/Wallet';
27+} from "../views/SetProxy";
28+import Point from "../views/Point";
29+import Points from "../views/Points";
30+import Shards from "../views/Shards";
31+import Ticket from "../views/Ticket";
32+import Transfer from "../views/Transfer";
33+import Wallet from "../views/Wallet";
3834
39−import { EthereumWallet } from './wallet';
40−import { renderNetworkType } from './network';
35+import { EthereumWallet } from "./wallet";
36+import { 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({
129− Nothing: () => 'Wallet',
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({
86+ Nothing: () => "Wallet",
13087 Just: wal =>
13188 wal.value instanceof EthereumWallet ? (
13289 <span className="text-mono">{wal.value.address}</span>
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+38−38
@@ -1,85 +1,85 @@
1−import * as bip32 from 'bip32';
2−import * as bip39 from 'bip39';
3−import keccak from 'keccak';
4−import * as lodash from 'lodash';
5−import Maybe from 'folktale/maybe';
6−import * as secp256k1 from 'secp256k1';
7−import * as ob from 'urbit-ob';
8−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
9−import { isAddress } from 'web3-utils';
1+import * as bip32 from "bip32";
2+import * as bip39 from "bip39";
3+import keccak from "keccak";
4+import * as lodash from "lodash";
5+import Maybe from "folktale/maybe";
6+import * as secp256k1 from "secp256k1";
7+import * as ob from "urbit-ob";
8+import * as kg from "urbit-key-generation/dist/index";
9+import { isAddress } from "web3-utils";
1010
1111const DEFAULT_HD_PATH = "m/44'/60'/0'/0/0";
1212
13−const ETH_ZERO_ADDR = '0x0000000000000000000000000000000000000000';
13+const ETH_ZERO_ADDR = "0x0000000000000000000000000000000000000000";
1414const CURVE_ZERO_ADDR =
15− '0x0000000000000000000000000000000000000000000000000000000000000000';
15+ "0x0000000000000000000000000000000000000000000000000000000000000000";
1616
1717const WALLET_NAMES = {
18− MNEMONIC: Symbol('MNEMONIC'),
19− TICKET: Symbol('TICKET'),
20− SHARDS: Symbol('SHARDS'),
21− LEDGER: Symbol('LEDGER'),
22− TREZOR: Symbol('TREZOR'),
23− PRIVATE_KEY: Symbol('PRIVATE_KEY'),
24− KEYSTORE: Symbol('KEYSTORE'),
18+ MNEMONIC: Symbol("MNEMONIC"),
19+ TICKET: Symbol("TICKET"),
20+ SHARDS: Symbol("SHARDS"),
21+ LEDGER: Symbol("LEDGER"),
22+ TREZOR: Symbol("TREZOR"),
23+ PRIVATE_KEY: Symbol("PRIVATE_KEY"),
24+ KEYSTORE: Symbol("KEYSTORE"),
2525};
2626
2727function EthereumWallet(privateKey) {
2828 this.privateKey = privateKey;
2929 this.publicKey = secp256k1.publicKeyCreate(this.privateKey);
30− const pub = this.publicKey.toString('hex');
30+ const pub = this.publicKey.toString("hex");
3131 this.address = addressFromSecp256k1Public(pub);
3232}
3333
3434const renderWalletType = wallet =>
3535 wallet === WALLET_NAMES.MNEMONIC
36− ? 'Mnemonic'
36+ ? "Mnemonic"
3737 : wallet === WALLET_NAMES.TICKET
38− ? 'Ticket'
38+ ? "Ticket"
3939 : wallet === WALLET_NAMES.SHARDS
40− ? 'Ticket Shards'
40+ ? "Ticket Shards"
4141 : wallet === WALLET_NAMES.LEDGER
42− ? 'Ledger'
42+ ? "Ledger"
4343 : wallet === WALLET_NAMES.TREZOR
44− ? 'Trezor'
44+ ? "Trezor"
4545 : wallet === WALLET_NAMES.PRIVATE_KEY
46− ? 'Private Key'
46+ ? "Private Key"
4747 : wallet === WALLET_NAMES.KEYSTORE
48− ? 'Keystore File'
49− : 'Wallet';
48+ ? "Keystore File"
49+ : "Wallet";
5050
5151const addressFromSecp256k1Public = pub => {
5252 const compressed = false;
5353 const uncompressed = secp256k1.publicKeyConvert(
54− Buffer.from(pub, 'hex'),
55− compressed
54+ Buffer.from(pub, "hex"),
55+ compressed,
5656 );
5757 const chopped = uncompressed.slice(1); // chop parity byte
5858 const hashed = keccak256(chopped);
59− const addr = addHexPrefix(hashed.slice(-20).toString('hex'));
59+ const addr = addHexPrefix(hashed.slice(-20).toString("hex"));
6060 return toChecksumAddress(addr);
6161};
6262
63−const addHexPrefix = hex => (hex.slice(0, 2) === '0x' ? hex : '0x' + hex);
63+const addHexPrefix = hex => (hex.slice(0, 2) === "0x" ? hex : "0x" + hex);
6464
65−const stripHexPrefix = hex => (hex.slice(0, 2) === '0x' ? hex.slice(2) : hex);
65+const stripHexPrefix = hex => (hex.slice(0, 2) === "0x" ? hex.slice(2) : hex);
6666
6767const keccak256 = str =>
68− keccak('keccak256')
68+ keccak("keccak256")
6969 .update(str)
7070 .digest();
7171
72−const isValidAddress = a => '0x0' === a || isAddress(a);
72+const isValidAddress = a => "0x0" === a || isAddress(a);
7373
7474const toChecksumAddress = address => {
7575 const addr = stripHexPrefix(address).toLowerCase();
76− const hash = keccak256(addr).toString('hex');
76+ const hash = keccak256(addr).toString("hex");
7777
7878 return lodash.reduce(
7979 addr,
8080 (acc, char, idx) =>
8181 parseInt(hash[idx], 16) >= 8 ? acc + char.toUpperCase() : acc + char,
82− '0x'
82+ "0x",
8383 );
8484};
8585
@@ -87,7 +87,7 @@ const eqAddr = (addr0, addr1) =>
8787 toChecksumAddress(addr0) === toChecksumAddress(addr1);
8888
8989const urbitWalletFromTicket = async (ticket, point, passphrase) => {
90− if (typeof point === 'string') {
90+ if (typeof point === "string") {
9191 //TODO why not in kg?
9292 point = ob.patp2dec(point);
9393 }
@@ -100,7 +100,7 @@ const urbitWalletFromTicket = async (ticket, point, passphrase) => {
100100};
101101
102102const ownershipWalletFromTicket = async (ticket, point, passphrase) => {
103− if (typeof point === 'string') {
103+ if (typeof point === "string") {
104104 //TODO why not in kg?
105105 point = ob.patp2dec(point);
106106 }
src/bridge/store/txnConfirmations.jsnew file+37
@@ -0,0 +1,37 @@
1+import React, { 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+ >
22+ {children}
23+ </TxnConfirmationsContext.Provider>
24+ );
25+}
26+
27+// HOC version
28+export const withTxnConfirmations = Component => props => (
29+ <TxnConfirmationsContext.Consumer>
30+ {txnConfirmations => <Component {...txnConfirmations} {...props} />}
31+ </TxnConfirmationsContext.Consumer>
32+);
33+
34+// Hook version
35+export function useTxnConfirmations() {
36+ return useContext(TxnConfirmationsContext);
37+}
src/bridge/views/AcceptTransfer.js+25−23
@@ -1,19 +1,19 @@
1−import { Just } from 'folktale/maybe';
2−import React from 'react';
3−import { Row, Col, H1, P } from '../components/Base';
1+import { Just } from "folktale/maybe";
2+import React from "react";
3+import { Row, Col, H1, P } from "../components/Base";
44import {
55 InnerLabel,
66 AddressInput,
77 ShowBlockie,
88 Anchor,
9−} from '../components/Base';
10−import * as azimuth from 'azimuth-js';
11−import * as ob from 'urbit-ob';
12−import * as need from '../lib/need';
9+} from "../components/Base";
10+import * as azimuth from "azimuth-js";
11+import * as ob from "urbit-ob";
12+import * as need from "../lib/need";
1313
14−import StatelessTransaction from '../components/StatelessTransaction';
15−import { NETWORK_NAMES } from '../lib/network';
16−import { isValidAddress } from '../lib/wallet';
14+import StatelessTransaction from "../components/StatelessTransaction";
15+import { NETWORK_NAMES } from "../lib/network";
16+import { isValidAddress } from "../lib/wallet";
1717
1818class AcceptTransfer extends React.Component {
1919 constructor(props) {
@@ -50,8 +50,8 @@ class AcceptTransfer extends React.Component {
5050 validContracts,
5151 validPoint,
5252 state.receivingAddress,
53− true
54− )
53+ true,
54+ ),
5555 );
5656 }
5757
@@ -66,20 +66,20 @@ class AcceptTransfer extends React.Component {
6666
6767 const esdomain =
6868 props.networkType === NETWORK_NAMES.ROPSTEN
69− ? 'ropsten.etherscan.io'
70− : 'etherscan.io';
69+ ? "ropsten.etherscan.io"
70+ : "etherscan.io";
7171
7272 return (
7373 <Row>
7474 <Col>
7575 <H1>
76− {'Accept Transfer of '}{' '}
76+ {"Accept Transfer of "}{" "}
7777 <code>{` ${ob.patp(state.incomingPoint)} `}</code>
7878 </H1>
7979
8080 <P>
8181 {"By default, the recipient is the address you're logged in " +
82− 'as. But you can transfer to any address you like.'}
82+ "as. But you can transfer to any address you like."}
8383 </P>
8484
8585 <AddressInput
@@ -87,20 +87,22 @@ class AcceptTransfer extends React.Component {
8787 prop-size="lg"
8888 prop-format="innerLabel"
8989 value={state.receivingAddress}
90− onChange={v => this.handleAddressInput(v)}>
91− <InnerLabel>{'Receiving Address'}</InnerLabel>
92− <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
90+ onChange={v => this.handleAddressInput(v)}
91+ >
92+ <InnerLabel>{"Receiving Address"}</InnerLabel>
93+ <ShowBlockie className={"mt-1"} address={state.receivingAddress} />
9394 </AddressInput>
9495
9596 <Anchor
96− className={'mt-1 sm'}
97+ className={"mt-1 sm"}
9798 prop-size="sm"
9899 prop-disabled={
99100 !isValidAddress(state.receivingAddress) || !esvisible
100101 }
101− target={'_blank'}
102− href={`https://${esdomain}/address/${state.receivingAddress}`}>
103− {'View on Etherscan ↗'}
102+ target={"_blank"}
103+ href={`https://${esdomain}/address/${state.receivingAddress}`}
104+ >
105+ {"View on Etherscan ↗"}
104106 </Anchor>
105107
106108 <StatelessTransaction
src/bridge/views/CancelTransfer.js+12−12
@@ -1,12 +1,12 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import { Row, Col, H1, P } from '../components/Base';
4−import * as azimuth from 'azimuth-js';
5−import * as ob from 'urbit-ob';
6−import * as need from '../lib/need';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import { Row, Col, H1, P } from "../components/Base";
4+import * as azimuth from "azimuth-js";
5+import * as ob from "urbit-ob";
6+import * as need from "../lib/need";
77
8−import StatelessTransaction from '../components/StatelessTransaction';
9−import { ETH_ZERO_ADDR } from '../lib/wallet';
8+import StatelessTransaction from "../components/StatelessTransaction";
9+import { ETH_ZERO_ADDR } from "../lib/wallet";
1010
1111class CancelTransfer extends React.Component {
1212 constructor(props) {
@@ -15,7 +15,7 @@ class CancelTransfer extends React.Component {
1515 const pointInTransfer = need.pointCursor(props);
1616
1717 this.state = {
18− proxyAddress: '',
18+ proxyAddress: "",
1919 pointInTransfer: pointInTransfer,
2020 };
2121
@@ -32,7 +32,7 @@ class CancelTransfer extends React.Component {
3232 const txn = azimuth.ecliptic.setTransferProxy(
3333 validContracts,
3434 validPoint,
35− ETH_ZERO_ADDR
35+ ETH_ZERO_ADDR,
3636 );
3737
3838 return Maybe.Just(txn);
@@ -45,7 +45,7 @@ class CancelTransfer extends React.Component {
4545
4646 const proxy = online
4747 ? props.pointCache[state.pointInTransfer].transferProxy
48− : 'any outgoing addresses';
48+ : "any outgoing addresses";
4949
5050 // const canGenerate = validAddress === true
5151
@@ -55,7 +55,7 @@ class CancelTransfer extends React.Component {
5555 <Row>
5656 <Col>
5757 <H1>
58− {'Cancel Transfer of '}{' '}
58+ {"Cancel Transfer of "}{" "}
5959 <code>{` ${ob.patp(state.pointInTransfer)} `}</code>
6060 </H1>
6161
src/bridge/views/CreateGalaxy.js+43−39
@@ -1,37 +1,37 @@
1−import React from 'react';
2−import Maybe from 'folktale/maybe';
3−import * as azimuth from 'azimuth-js';
4−import * as ob from 'urbit-ob';
5−import * as need from '../lib/need';
6−
7−import { Button } from '../components/Base';
8−import { Row, Col, H1, P, Anchor } from '../components/Base';
1+import React from "react";
2+import Maybe from "folktale/maybe";
3+import * as azimuth from "azimuth-js";
4+import * as ob from "urbit-ob";
5+import * as need from "../lib/need";
6+
7+import { Button } from "../components/Base";
8+import { Row, Col, H1, P, Anchor } from "../components/Base";
99import {
1010 InnerLabel,
1111 GalaxyInput,
1212 AddressInput,
1313 ValidatedSigil,
1414 ShowBlockie,
15−} from '../components/Base';
16−import StatelessTransaction from '../components/StatelessTransaction';
15+} from "../components/Base";
16+import StatelessTransaction from "../components/StatelessTransaction";
1717
18−import { NETWORK_NAMES } from '../lib/network';
19−import { canDecodePatp } from '../lib/txn';
18+import { NETWORK_NAMES } from "../lib/network";
19+import { canDecodePatp } from "../lib/txn";
2020
21−import { ETH_ZERO_ADDR, isValidAddress, eqAddr } from '../lib/wallet';
21+import { ETH_ZERO_ADDR, isValidAddress, eqAddr } from "../lib/wallet";
2222
23−import { isValidGalaxy } from '../lib/lib';
23+import { isValidGalaxy } from "../lib/lib";
2424
2525const buttonTriState = status => {
26− if (status === null) return 'blue';
27− if (status === false) return 'yellow';
28− if (status === true) return 'green';
26+ if (status === null) return "blue";
27+ if (status === false) return "yellow";
28+ if (status === true) return "green";
2929};
3030
3131const buttonTriStateText = status => {
32− if (status === null) return 'Confirm Galaxy Availablility';
33− if (status === false) return 'Galaxy is Not Available';
34− if (status === true) return 'Galaxy is Available';
32+ if (status === null) return "Confirm Galaxy Availablility";
33+ if (status === false) return "Galaxy is Not Available";
34+ if (status === true) return "Galaxy is Available";
3535};
3636
3737class CreateGalaxy extends React.Component {
@@ -42,7 +42,7 @@ class CreateGalaxy extends React.Component {
4242
4343 this.state = {
4444 galaxyOwner: galaxyOwner,
45− galaxyName: '',
45+ galaxyName: "",
4646 isAvailable: null,
4747 };
4848
@@ -77,7 +77,7 @@ class CreateGalaxy extends React.Component {
7777 const txn = azimuth.ecliptic.createGalaxy(
7878 validContracts,
7979 galaxyDec,
80− state.galaxyOwner
80+ state.galaxyOwner,
8181 );
8282
8383 return Maybe.Just(txn);
@@ -97,7 +97,7 @@ class CreateGalaxy extends React.Component {
9797
9898 const currentOwner = await azimuth.azimuth.getOwner(
9999 validContracts,
100− galaxyDec
100+ galaxyDec,
101101 );
102102
103103 const available = eqAddr(currentOwner, ETH_ZERO_ADDR);
@@ -122,17 +122,17 @@ class CreateGalaxy extends React.Component {
122122
123123 const esdomain =
124124 props.networkType === NETWORK_NAMES.ROPSTEN
125− ? 'ropsten.etherscan.io'
126− : 'etherscan.io';
125+ ? "ropsten.etherscan.io"
126+ : "etherscan.io";
127127
128128 return (
129129 <Row>
130130 <Col>
131− <H1> {'Create a Galaxy'} </H1>
131+ <H1> {"Create a Galaxy"} </H1>
132132
133133 <P>
134− {'Enter the galaxy to create and the address that will own ' +
135− 'it (defaulting to this account, if not provided).'}
134+ {"Enter the galaxy to create and the address that will own " +
135+ "it (defaulting to this account, if not provided)."}
136136 </P>
137137
138138 <GalaxyInput
@@ -142,8 +142,9 @@ class CreateGalaxy extends React.Component {
142142 autoFocus
143143 placeholder="e.g. ~zod"
144144 value={state.galaxyName}
145− onChange={v => this.handleGalaxyNameInput(v)}>
146− <InnerLabel>{'Galaxy Name'}</InnerLabel>
145+ onChange={v => this.handleGalaxyNameInput(v)}
146+ >
147+ <InnerLabel>{"Galaxy Name"}</InnerLabel>
147148 <ValidatedSigil
148149 className="tr-0 mt-05 mr-0 abs"
149150 patp={state.galaxyName}
@@ -157,18 +158,20 @@ class CreateGalaxy extends React.Component {
157158 prop-size="lg"
158159 prop-format="innerLabel"
159160 value={state.galaxyOwner}
160− onChange={v => this.handleAddressInput(v)}>
161− <InnerLabel>{'Address that will own this galaxy'}</InnerLabel>
162− <ShowBlockie className={'mt-1'} address={state.galaxyOwner} />
161+ onChange={v => this.handleAddressInput(v)}
162+ >
163+ <InnerLabel>{"Address that will own this galaxy"}</InnerLabel>
164+ <ShowBlockie className={"mt-1"} address={state.galaxyOwner} />
163165 </AddressInput>
164166
165167 <Anchor
166− className={'mt-1'}
167− prop-size={'sm'}
168+ className={"mt-1"}
169+ prop-size={"sm"}
168170 prop-disabled={!validAddress || !esvisible}
169− target={'_blank'}
170− href={`https://${esdomain}/address/${state.galaxyOwner}`}>
171− {'View on Etherscan ↗'}
171+ target={"_blank"}
172+ href={`https://${esdomain}/address/${state.galaxyOwner}`}
173+ >
174+ {"View on Etherscan ↗"}
172175 </Anchor>
173176
174177 <Button
@@ -176,7 +179,8 @@ class CreateGalaxy extends React.Component {
176179 className="mt-8"
177180 prop-color={buttonTriState(state.isAvailable)}
178181 disabled={!validGalaxy}
179− onClick={() => this.confirmAvailability()}>
182+ onClick={() => this.confirmAvailability()}
183+ >
180184 {buttonTriStateText(state.isAvailable)}
181185 </Button>
182186
src/bridge/views/GenKeyfile.js+23−22
@@ -1,23 +1,23 @@
1−import React from 'react';
2−import Maybe from 'folktale/maybe';
3−import * as need from '../lib/need';
1+import React from "react";
2+import Maybe from "folktale/maybe";
3+import * as need from "../lib/need";
44
5−import { Button } from '../components/Base';
6−import { Row, Col, H1, P } from '../components/Base';
5+import { Button } from "../components/Base";
6+import { Row, Col, H1, P } from "../components/Base";
77
8−import * as ob from 'urbit-ob';
9−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
10−import saveAs from 'file-saver';
8+import * as ob from "urbit-ob";
9+import * as kg from "urbit-key-generation/dist/index";
10+import saveAs from "file-saver";
1111
12−import { attemptSeedDerivation, genKey } from '../lib/keys';
13−import { addHexPrefix } from '../lib/wallet';
12+import { attemptSeedDerivation, genKey } from "../lib/keys";
13+import { addHexPrefix } from "../lib/wallet";
1414
1515class GenKeyfile extends React.Component {
1616 constructor(props) {
1717 super(props);
1818
1919 this.state = {
20− keyfile: '',
20+ keyfile: "",
2121 loaded: false,
2222 };
2323 }
@@ -42,7 +42,7 @@ class GenKeyfile extends React.Component {
4242
4343 async componentDidMount() {
4444 const { point, pointDetails, revision } = this.getPointDetails();
45− let keyfile = '';
45+ let keyfile = "";
4646
4747 const hexRegExp = /[0-9A-Fa-f]{64}/g;
4848 const networkSeed = await this.deriveSeed();
@@ -77,11 +77,11 @@ class GenKeyfile extends React.Component {
7777 const next = false;
7878 let seed = await attemptSeedDerivation(next, this.props);
7979
80− if (seed.getOrElse('') === '' && this.props.networkSeedCache) {
80+ if (seed.getOrElse("") === "" && this.props.networkSeedCache) {
8181 seed = Maybe.Just(this.props.networkSeedCache);
8282 }
8383
84− return seed.getOrElse('');
84+ return seed.getOrElse("");
8585 }
8686
8787 render() {
@@ -90,14 +90,14 @@ class GenKeyfile extends React.Component {
9090
9191 return (
9292 <Row>
93− <Col className={'col-md-8'}>
94− <H1>{'Generate keyfile'}</H1>
93+ <Col className={"col-md-8"}>
94+ <H1>{"Generate keyfile"}</H1>
9595
96− <P>{'Download a private key file for booting this point in Arvo.'}</P>
96+ <P>{"Download a private key file for booting this point in Arvo."}</P>
9797
98− {keyfile === '' && !loaded && <P>{'Generating keyfile...'}</P>}
98+ {keyfile === "" && !loaded && <P>{"Generating keyfile..."}</P>}
9999
100− {keyfile === '' && loaded && (
100+ {keyfile === "" && loaded && (
101101 <React.Fragment>
102102 <P>
103103 <b>Warning: </b>
@@ -112,16 +112,17 @@ class GenKeyfile extends React.Component {
112112 </React.Fragment>
113113 )}
114114
115− {keyfile !== '' && (
115+ {keyfile !== "" && (
116116 <React.Fragment>
117117 <div className="pb-5 text-code keyfile">{keyfile}</div>
118118 <Button
119119 onClick={() => {
120120 let blob = new Blob([keyfile], {
121− type: 'text/plain;charset=utf-8',
121+ type: "text/plain;charset=utf-8",
122122 });
123123 saveAs(blob, `${ob.patp(point).slice(1)}-${revision}.key`);
124− }}>
124+ }}
125+ >
125126 Download →
126127 </Button>
127128 </React.Fragment>
src/bridge/views/InviteTicket.js+44−37
@@ -1,5 +1,5 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
1+import { Just, Nothing } from "folktale/maybe";
2+import React from "react";
33import {
44 InnerLabel,
55 Warning,
@@ -9,34 +9,35 @@ import {
99 Row,
1010 Col,
1111 Passport,
12−} from '../components/Base';
13−import * as azimuth from 'azimuth-js';
14−import * as need from '../lib/need';
12+} from "../components/Base";
13+import * as azimuth from "azimuth-js";
14+import * as need from "../lib/need";
1515
16−import { randomPatq } from '../lib/lib';
17−import { ROUTE_NAMES } from '../lib/router';
16+import { randomPatq } from "../lib/lib";
17+import { ROUTE_NAMES } from "../lib/routeNames";
18+import { withHistory } from "../lib/history";
1819import {
1920 DEFAULT_HD_PATH,
2021 ownershipWalletFromTicket,
2122 walletFromMnemonic,
22−} from '../lib/wallet';
23−import { BRIDGE_ERROR } from '../lib/error';
23+} from "../lib/wallet";
24+import { BRIDGE_ERROR } from "../lib/error";
2425import {
2526 INVITE_STAGES,
2627 WALLET_STATES,
2728 TRANSACTION_STATES,
28−} from '../lib/invite';
29−import { generateWallet, startTransactions } from '../lib/invite';
29+} from "../lib/invite";
30+import { generateWallet, startTransactions } from "../lib/invite";
3031
3132class InviteTicket extends React.Component {
3233 constructor(props) {
3334 super(props);
3435
3536 this.state = {
36− inviteTicket: '',
37+ inviteTicket: "",
3738 inviteWallet: Nothing(),
38− verifyTicket: '',
39− realTicket: '',
39+ verifyTicket: "",
40+ realTicket: "",
4041 realPoint: Nothing(),
4142 realWallet: Nothing(),
4243 walletReady: false,
@@ -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) {
@@ -91,7 +92,7 @@ class InviteTicket extends React.Component {
9192 //TODO isn't all this accessible in the ownership object?
9293 const inviteWallet = walletFromMnemonic(
9394 mnemonic,
94− DEFAULT_HD_PATH
95+ DEFAULT_HD_PATH,
9596 ).matchWith({
9697 Just: w => w.value,
9798 Nothing: () => {
@@ -129,7 +130,7 @@ class InviteTicket extends React.Component {
129130 }
130131 } else {
131132 errors = [
132− 'Invite code has no claimable ship. Check your invite code and try again.',
133+ "Invite code has no claimable ship. Check your invite code and try again.",
133134 ];
134135 }
135136
@@ -139,7 +140,7 @@ class InviteTicket extends React.Component {
139140 // dev mode: uncomment this out to autofill verified ticket
140141 verifyTicket: !Nothing.hasInstance(realWallet)
141142 ? realWallet.value.ticket
142− : '',
143+ : "",
143144 walletStates: this.state.walletStates.concat(WALLET_STATES.RENDERING),
144145 errors,
145146 });
@@ -156,11 +157,11 @@ class InviteTicket extends React.Component {
156157 let handler = isLogin
157158 ? this.handleInviteTicketInput
158159 : this.handleVerifyTicketInput;
159− let label = isLogin ? 'Activation code' : 'ticket';
160+ let label = isLogin ? "Activation code" : "ticket";
160161
161162 // if realWallet is Just, then VerifyTicketInput appropriate contents; if Nothing, remain Nothing
162163 const Verified = this.state.realWallet.map(realWallet =>
163− VerifyTicketInput(realWallet.ticket)
164+ VerifyTicketInput(realWallet.ticket),
164165 );
165166
166167 // Verified.getOrElse(TicketInput) falls back to TicketInput if Verified is Nothing
@@ -178,7 +179,8 @@ class InviteTicket extends React.Component {
178179 name="invite"
179180 placeholder={`e.g. ${phTick}`}
180181 value={value}
181− onChange={handler}>
182+ onChange={handler}
183+ >
182184 <InnerLabel>{label}</InnerLabel>
183185 </TicketElem>
184186 );
@@ -254,7 +256,7 @@ class InviteTicket extends React.Component {
254256
255257 updateProgress(notification) {
256258 let newState =
257− notification.type === 'progress'
259+ notification.type === "progress"
258260 ? { transactionProgress: notification.value }
259261 : notification.type === 'notify'
260262 ? { errors: [notification.value] }
@@ -276,8 +278,8 @@ class InviteTicket extends React.Component {
276278 const { web3, contracts, setUrbitWallet } = this.props;
277279
278280 const btnColor = this.state.walletStates.includes(WALLET_STATES.PAPER_READY)
279− ? 'green'
280− : 'black';
281+ ? "green"
282+ : "black";
281283
282284 const clickHandler =
283285 stage === INVITE_STAGES.INVITE_LOGIN
@@ -295,7 +297,7 @@ class InviteTicket extends React.Component {
295297 this.setState({
296298 stage: INVITE_STAGES.INVITE_TRANSACTIONS,
297299 walletStates: this.state.walletStates.concat(
298− WALLET_STATES.TRANSACTIONS
300+ WALLET_STATES.TRANSACTIONS,
299301 ),
300302 });
301303
@@ -310,7 +312,7 @@ class InviteTicket extends React.Component {
310312 updateProgress: this.updateProgress,
311313 }).then(() => {
312314 setTimeout(() => {
313− this.props.popRoute();
315+ this.props.history.pop();
314316 }, 5000);
315317 });
316318 }
@@ -331,12 +333,13 @@ class InviteTicket extends React.Component {
331333 const buttonElem =
332334 stage === INVITE_STAGES.INVITE_TRANSACTIONS ? null : (
333335 <Button
334− className={'mt-4'}
335− prop-size={'lg'}
336+ className={"mt-4"}
337+ prop-size={"lg"}
336338 prop-color={btnColor}
337339 disabled={!continueReady}
338− onClick={clickHandler}>
339− {'Continue →'}
340+ onClick={clickHandler}
341+ >
342+ {"Continue →"}
340343 </Button>
341344 );
342345
@@ -352,17 +355,20 @@ class InviteTicket extends React.Component {
352355 <div className="mt-6 mb-8">
353356 <span
354357 className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_WALLET &&
355− 'gray-20'}`}>
358+ "gray-20"}`}
359+ >
356360 1 Passport
357361 </span>
358362 <span
359363 className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_VERIFY &&
360− 'gray-20'}`}>
364+ "gray-20"}`}
365+ >
361366 2 Verify
362367 </span>
363368 <span
364369 className={`fs-35 mr-3 ${stage !==
365− INVITE_STAGES.INVITE_TRANSACTIONS && 'gray-20'}`}>
370+ INVITE_STAGES.INVITE_TRANSACTIONS && "gray-20"}`}
371+ >
366372 3 Invite
367373 </span>
368374 </div>
@@ -376,7 +382,7 @@ class InviteTicket extends React.Component {
376382
377383 return (
378384 <Warning>
379− <h3 className={'mb-2'}>{'Warning'}</h3>
385+ <h3 className={"mb-2"}>{"Warning"}</h3>
380386 {errorElems}
381387 </Warning>
382388 );
@@ -403,7 +409,8 @@ class InviteTicket extends React.Component {
403409 prop-type={`link`}
404410 prop-size={`sm`}
405411 className={`block mt-4`}
406− onClick={this.navigateLogin}>
412+ onClick={this.navigateLogin}
413+ >
407414 {`Log in`}
408415 </Button>
409416 )}
@@ -421,4 +428,4 @@ class InviteTicket extends React.Component {
421428 }
422429}
423430
424−export default InviteTicket;
431+export default withHistory(InviteTicket);
src/bridge/views/InvitesManage.js+24−22
@@ -1,8 +1,8 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
3−import * as ob from 'urbit-ob';
4−import * as azimuth from 'azimuth-js';
5−import * as need from '../lib/need';
1+import { Just, Nothing } from "folktale/maybe";
2+import React from "react";
3+import * as ob from "urbit-ob";
4+import * as azimuth from "azimuth-js";
5+import * as need from "../lib/need";
66
77import {
88 Row,
@@ -12,8 +12,8 @@ import {
1212 PointInput,
1313 InnerLabel,
1414 ValidatedSigil,
15−} from '../components/Base';
16−import StatelessTransaction from '../components/StatelessTransaction';
15+} from "../components/Base";
16+import StatelessTransaction from "../components/StatelessTransaction";
1717
1818class InvitesManage extends React.Component {
1919 constructor(props) {
@@ -21,7 +21,7 @@ class InvitesManage extends React.Component {
2121
2222 this.state = {
2323 invitesWillWork: Nothing(),
24− planetInput: '',
24+ planetInput: "",
2525 targetPlanet: Nothing(),
2626 currentPoolSize: Nothing(),
2727 cachedPoolSizes: {},
@@ -44,7 +44,7 @@ class InvitesManage extends React.Component {
4444 .isSpawnProxy(
4545 this.contracts,
4646 this.point,
47− this.contracts.delegatedSending.address
47+ this.contracts.delegatedSending.address,
4848 )
4949 .then(isSpawnProxy => {
5050 this.setState({ invitesWillWork: Just(isSpawnProxy) });
@@ -77,7 +77,7 @@ class InvitesManage extends React.Component {
7777
7878 checkCurrentInvites(who) {
7979 const cachedPoolSizes = this.state.cachedPoolSizes;
80− if (typeof cachedPoolSizes[who] !== 'undefined') {
80+ if (typeof cachedPoolSizes[who] !== "undefined") {
8181 return Just(cachedPoolSizes[who]);
8282 } else {
8383 azimuth.delegatedSending.getPool(this.contracts, who).then(pool => {
@@ -112,8 +112,8 @@ class InvitesManage extends React.Component {
112112 this.contracts,
113113 this.point,
114114 this.state.targetPlanet.value,
115− this.state.targetPoolSize
116− )
115+ this.state.targetPoolSize,
116+ ),
117117 );
118118 }
119119
@@ -122,18 +122,18 @@ class InvitesManage extends React.Component {
122122 if (this.state.invitesWillWork.value === false) {
123123 spawnProxyWarning = (
124124 <Warning>
125− <h3 className={'mb-2'}>{'Warning'}</h3>
126− {'Planets under this star will not be able to send invites ' +
127− 'until the invite contract ('}
125+ <h3 className={"mb-2"}>{"Warning"}</h3>
126+ {"Planets under this star will not be able to send invites " +
127+ "until the invite contract ("}
128128 <code>{this.contracts.delegatedSending.address}</code>
129− {') is made spawn proxy for this star.'}
129+ {") is made spawn proxy for this star."}
130130 </Warning>
131131 );
132132 }
133133
134134 let poolSizeText = this.state.currentPoolSize.matchWith({
135135 Just: ps => `(currently ${ps.value})`,
136− Nothing: _ => '',
136+ Nothing: _ => "",
137137 });
138138
139139 return (
@@ -141,7 +141,7 @@ class InvitesManage extends React.Component {
141141 <Col>
142142 <p>
143143 {
144− 'manage invites here, for stars. can only give invites to child planets'
144+ "manage invites here, for stars. can only give invites to child planets"
145145 }
146146 </p>
147147
@@ -150,10 +150,11 @@ class InvitesManage extends React.Component {
150150 <PointInput
151151 prop-format="innerlabel"
152152 prop-size="lg"
153− placeholder={'~sampel-sipnem'}
153+ placeholder={"~sampel-sipnem"}
154154 value={this.state.planetInput}
155− onChange={this.handlePointInput}>
156− <InnerLabel>{'Planet to set invites for'}</InnerLabel>
155+ onChange={this.handlePointInput}
156+ >
157+ <InnerLabel>{"Planet to set invites for"}</InnerLabel>
157158 <ValidatedSigil
158159 patp={this.state.targetPlanet}
159160 validator={() => this.validatePoint}
@@ -164,7 +165,8 @@ class InvitesManage extends React.Component {
164165 prop-format="innerlabel"
165166 prop-size="lg"
166167 value={this.state.targetPoolSize}
167− onChange={this.handlePoolSizeInput}>
168+ onChange={this.handlePoolSizeInput}
169+ >
168170 <InnerLabel>{`Available invites ${poolSizeText}`}</InnerLabel>
169171 </Input>
170172
src/bridge/views/InvitesSend.js+73−68
@@ -1,13 +1,13 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
3−import * as azimuth from 'azimuth-js';
4−import * as need from '../lib/need';
1+import { Just, Nothing } from "folktale/maybe";
2+import React from "react";
3+import * as azimuth from "azimuth-js";
4+import * as need from "../lib/need";
55
6−import { hasReceived, sendMail } from '../lib/inviteMail';
7−import { Row, Col, Button, Input, H3, Warning } from '../components/Base';
6+import { hasReceived, sendMail } from "../lib/inviteMail";
7+import { Row, Col, Button, Input, H3, Warning } from "../components/Base";
88
99// for wallet generation
10−import * as wg from '../../walletgen/lib/lib';
10+import * as wg from "../../walletgen/lib/lib";
1111
1212// for transaction generation and signing
1313import {
@@ -17,27 +17,27 @@ import {
1717 hexify,
1818 fromWei,
1919 toWei,
20−} from '../lib/txn';
21−import * as tank from '../lib/tank';
20+} from "../lib/txn";
21+import * as tank from "../lib/tank";
2222
2323const GAS_PRICE_GWEI = 20; // we pay the premium for faster ux
2424const GAS_LIMIT = 350000;
25−const INVITE_COST = toWei((GAS_PRICE_GWEI * GAS_LIMIT).toString(), 'gwei');
25+const INVITE_COST = toWei((GAS_PRICE_GWEI * GAS_LIMIT).toString(), "gwei");
2626
2727const STATUS = {
28− INPUT: 'Generate invites',
29− GENERATING: 'Generating invites',
30− CAN_SEND: 'Send invites',
31− FUNDING: 'Funding invites',
32− SENDING: 'Sending invites',
33− DONE: 'Done',
34− FAIL: 'Some failed',
28+ INPUT: "Generate invites",
29+ GENERATING: "Generating invites",
30+ CAN_SEND: "Send invites",
31+ FUNDING: "Funding invites",
32+ SENDING: "Sending invites",
33+ DONE: "Done",
34+ FAIL: "Some failed",
3535};
3636
3737const EMAIL_STATUS = {
38− BUSY: '⋯',
39− DONE: '✔',
40− FAIL: '×',
38+ BUSY: "⋯",
39+ DONE: "✔",
40+ FAIL: "×",
4141};
4242
4343class InvitesSend extends React.Component {
@@ -47,7 +47,7 @@ class InvitesSend extends React.Component {
4747 this.state = {
4848 invitesAvailable: Nothing(),
4949 invited: Nothing(),
50− targets: [{ email: '', hasReceived: Nothing(), status: Nothing() }],
50+ targets: [{ email: "", hasReceived: Nothing(), status: Nothing() }],
5151 status: STATUS.INPUT,
5252 errors: Nothing(),
5353 //
@@ -89,7 +89,7 @@ class InvitesSend extends React.Component {
8989 async findInvited() {
9090 let invited = await azimuth.delegatedSending.getInvited(
9191 this.contracts,
92− this.point
92+ this.point,
9393 );
9494 invited = invited.map(async point => {
9595 const active = await azimuth.azimuth.isActive(this.contracts, point);
@@ -116,13 +116,13 @@ class InvitesSend extends React.Component {
116116 const planets = await azimuth.delegatedSending.getPlanetsToSend(
117117 this.contracts,
118118 this.point,
119− targets.length
119+ targets.length,
120120 );
121121
122122 // account for the race condition where invites got used up while we were
123123 // composing our target list
124124 if (planets.length < targets.length) {
125− this.addError('Can currently only send ' + planets.length + ' invites!');
125+ this.addError("Can currently only send " + planets.length + " invites!");
126126 return;
127127 }
128128
@@ -132,7 +132,7 @@ class InvitesSend extends React.Component {
132132 // create invite wallets and transactions
133133 let invites = []; // { email, ticket, signedTx, rawTx }
134134 for (let i = 0; i < targets.length; i++) {
135− console.log('generating email', i, targets[i].email);
135+ console.log("generating email", i, targets[i].email);
136136 this.setEmailStatus(i, EMAIL_STATUS.BUSY);
137137
138138 const email = targets[i].email;
@@ -142,7 +142,7 @@ class InvitesSend extends React.Component {
142142 this.contracts,
143143 this.point,
144144 planets[i],
145− wallet.owner
145+ wallet.owner,
146146 );
147147 const signedTx = await signTransaction({
148148 ...this.props,
@@ -183,7 +183,7 @@ class InvitesSend extends React.Component {
183183 this.askForFunding,
184184 () => {
185185 this.setState({ fundingMessage: Nothing() });
186− }
186+ },
187187 );
188188
189189 this.setState({ status: STATUS.SENDING });
@@ -200,11 +200,11 @@ class InvitesSend extends React.Component {
200200 web3,
201201 Just(invite.signedTx),
202202 tankWasUsed,
203− () => {}
203+ () => {},
204204 );
205205 } catch (err) {
206− console.error('invite tx sending failed', err);
207− this.addError('Invite transaction not sent for ' + invite.email);
206+ console.error("invite tx sending failed", err);
207+ this.addError("Invite transaction not sent for " + invite.email);
208208 this.setEmailStatus(i, EMAIL_STATUS.FAIL);
209209 continue;
210210 }
@@ -216,19 +216,19 @@ class InvitesSend extends React.Component {
216216 const mailSuccess = await sendMail(
217217 invite.email,
218218 invite.ticket,
219− invite.rawTx
219+ invite.rawTx,
220220 );
221221 if (!mailSuccess) throw new Error();
222222 this.setEmailStatus(i, EMAIL_STATUS.DONE);
223223 } catch (e) {
224− console.log('email send failed');
224+ console.log("email send failed");
225225 //NOTE this assumes that the transaction succeeds, but we don't know
226226 // that for a fact yet...
227227 this.addError(
228− 'Invite email failed to send for ' +
228+ "Invite email failed to send for " +
229229 invite.email +
230− '. Please give them this ticket: ' +
231− invite.ticket
230+ ". Please give them this ticket: " +
231+ invite.ticket,
232232 );
233233 this.setEmailStatus(i, EMAIL_STATUS.FAIL);
234234 continue;
@@ -240,12 +240,12 @@ class InvitesSend extends React.Component {
240240 if (success) {
241241 this.setEmailStatus(i, EMAIL_STATUS.DONE);
242242 } else {
243− console.log('invite tx rejected');
244− this.addError('Invite transaction failed for ' + invite.email);
243+ console.log("invite tx rejected");
244+ this.addError("Invite transaction failed for " + invite.email);
245245 this.setEmailStatus(i, EMAIL_STATUS.FAIL);
246246 }
247247 return success;
248− })
248+ }),
249249 );
250250 }
251251
@@ -279,9 +279,9 @@ class InvitesSend extends React.Component {
279279 fundingMessage: Just(
280280 `Please make sure ${address} has at least ${fromWei(minBalance)} ETH,` +
281281 `we'll continue once that's true. Current balance: ${fromWei(
282− balance
282+ balance,
283283 )}.` +
284− `Waiting...`
284+ `Waiting...`,
285285 ),
286286 });
287287 }
@@ -328,7 +328,7 @@ class InvitesSend extends React.Component {
328328
329329 addTarget() {
330330 let targets = this.state.targets;
331− targets.push({ email: '', hasReceived: Nothing(), status: Nothing() });
331+ targets.push({ email: "", hasReceived: Nothing(), status: Nothing() });
332332 this.setState({ targets });
333333 }
334334
@@ -343,24 +343,24 @@ class InvitesSend extends React.Component {
343343 this.state.status === STATUS.DONE ? this.state.targets.length : null;
344344
345345 const invitesAvailable = this.state.invitesAvailable.matchWith({
346− Nothing: () => 'Loading...',
346+ Nothing: () => "Loading...",
347347 Just: invites => {
348− const dia = dif ? `(-${dif})` : '';
348+ const dia = dif ? `(-${dif})` : "";
349349 return `You currently have ${invites.value}${dia} invitations left.`;
350350 },
351351 });
352352
353353 const invitesSent = this.state.invited.matchWith({
354− Nothing: () => <>{'Loading...'}</>,
354+ Nothing: () => <>{"Loading..."}</>,
355355 Just: invited => {
356− const dit = dif ? `(+${dif})` : '';
356+ const dit = dif ? `(+${dif})` : "";
357357 return (
358358 <>
359− {'Out of the '}
359+ {"Out of the "}
360360 {invited.value.total + dit}
361− {' invites you sent, '}
361+ {" invites you sent, "}
362362 {invited.value.accepted}
363− {' have been accepted.'}
363+ {" have been accepted."}
364364 </>
365365 );
366366 },
@@ -369,9 +369,9 @@ class InvitesSend extends React.Component {
369369 const error = this.state.errors.matchWith({
370370 Nothing: () => null,
371371 Just: errors => (
372− <Warning className={'mt-8'}>
372+ <Warning className={"mt-8"}>
373373 <H3 style={{ marginTop: 0, paddingTop: 0 }}>
374− {'Something went wrong!'}
374+ {"Something went wrong!"}
375375 </H3>
376376 {errors.value.map(err => (
377377 <p>{err}</p>
@@ -382,30 +382,31 @@ class InvitesSend extends React.Component {
382382
383383 const fundingMessage = this.state.fundingMessage.matchWith({
384384 Nothing: () => null,
385− Just: msg => <Warning className={'mt-8'}>{msg.value}</Warning>,
385+ Just: msg => <Warning className={"mt-8"}>{msg.value}</Warning>,
386386 });
387387
388388 let inputs;
389389 if (this.state.status === STATUS.DONE) {
390390 //TODO proper clickable router thing
391− inputs = '<- Back to Home';
391+ inputs = "<- Back to Home";
392392 } else {
393393 const inputDisabled = this.state.status !== STATUS.INPUT;
394394
395395 const lessButton = (
396396 <Button
397− prop-size={'s narrow'}
398− className={'mt-8'}
397+ prop-size={"s narrow"}
398+ className={"mt-8"}
399399 disabled={inputDisabled || this.state.targets.length === 1}
400− onClick={this.removeTarget}>
401− {'-'}
400+ onClick={this.removeTarget}
401+ >
402+ {"-"}
402403 </Button>
403404 );
404405
405406 const moreButton = (
406407 <Button
407− prop-size={'s narrow'}
408− className={'mt-8'}
408+ prop-size={"s narrow"}
409+ className={"mt-8"}
409410 disabled={
410411 inputDisabled ||
411412 this.state.invitesAvailable.matchWith({
@@ -413,8 +414,9 @@ class InvitesSend extends React.Component {
413414 Just: inv => inv.value <= this.state.targets.length,
414415 })
415416 }
416− onClick={this.addTarget}>
417− {'+'}
417+ onClick={this.addTarget}
418+ >
419+ {"+"}
418420 </Button>
419421 );
420422
@@ -422,7 +424,7 @@ class InvitesSend extends React.Component {
422424 for (let i = 0; i < this.state.targets.length; i++) {
423425 const target = this.state.targets[i];
424426 let progress = target.status.matchWith({
425− Nothing: () => '',
427+ Nothing: () => "",
426428 Just: status => status.value,
427429 });
428430 targetInputs.push(
@@ -432,23 +434,23 @@ class InvitesSend extends React.Component {
432434 onChange={value => {
433435 this.handleEmailInput(i, value);
434436 }}
435− placeholder={'Email address'}
437+ placeholder={"Email address"}
436438 disabled={inputDisabled}
437439 />
438440 {target.hasReceived.matchWith({
439441 Nothing: () => null,
440442 Just: has =>
441− has.value ? <span>{'×'}</span> : <span>{'°'}</span>,
443+ has.value ? <span>{"×"}</span> : <span>{"°"}</span>,
442444 })}
443445 {progress}
444− </>
446+ </>,
445447 );
446448 }
447449
448450 const submitButton = (
449451 <Button
450− prop-size={'xl wide'}
451− className={'mt-8'}
452+ prop-size={"xl wide"}
453+ className={"mt-8"}
452454 disabled={
453455 (inputDisabled && this.state.status !== STATUS.CAN_SEND) ||
454456 !this.canSend()
@@ -456,7 +458,8 @@ class InvitesSend extends React.Component {
456458 onClick={() => {
457459 if (this.state.status === STATUS.INPUT) this.generateInvites();
458460 if (this.state.status === STATUS.CAN_SEND) this.sendInvites();
459− }}>
461+ }}
462+ >
460463 {this.state.status}
461464 </Button>
462465 );
@@ -474,7 +477,9 @@ class InvitesSend extends React.Component {
474477 return (
475478 <Row>
476479 <Col>
477− <p>{'send invites here, for planets'}</p>
480+ <p>{"send invites here, for planets"}</p>
481+
482+ <p>{"send invites here, for planets"}</p>
478483
479484 <p>{invitesAvailable}</p>
480485
src/bridge/views/IssueChild.js+46−42
@@ -1,8 +1,8 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
3−import { azimuth, ecliptic } from 'azimuth-js';
4−import * as ob from 'urbit-ob';
5−import * as need from '../lib/need';
1+import { Just, Nothing } from "folktale/maybe";
2+import React from "react";
3+import { azimuth, ecliptic } from "azimuth-js";
4+import * as ob from "urbit-ob";
5+import * as need from "../lib/need";
66
77import {
88 Row,
@@ -15,15 +15,15 @@ import {
1515 PointInput,
1616 AddressInput,
1717 InnerLabel,
18−} from '../components/Base';
18+} from "../components/Base";
1919
20−import StatelessTransaction from '../components/StatelessTransaction';
20+import StatelessTransaction from "../components/StatelessTransaction";
2121
22−import { NETWORK_NAMES } from '../lib/network';
23−import { getSpawnCandidate } from '../lib/child';
24−import { canDecodePatp } from '../lib/txn';
22+import { NETWORK_NAMES } from "../lib/network";
23+import { getSpawnCandidate } from "../lib/child";
24+import { canDecodePatp } from "../lib/txn";
2525
26−import { isValidAddress } from '../lib/wallet';
26+import { isValidAddress } from "../lib/wallet";
2727
2828const setFind = (set, pred) => {
2929 for (const e of set) {
@@ -50,13 +50,13 @@ class IssueChild extends React.Component {
5050 ];
5151
5252 this.state = {
53− receivingAddress: '',
53+ receivingAddress: "",
5454 issuingPoint: issuingPoint,
55− desiredPoint: '',
55+ desiredPoint: "",
5656 isAvailable: Nothing(), // use Nothing to allow attempt when offline
5757 suggestions: suggestions,
5858 validChildren: new Set(),
59− autoComplete: '',
59+ autoComplete: "",
6060 };
6161
6262 this.handlePointInput = this.handlePointInput.bind(this);
@@ -84,9 +84,9 @@ class IssueChild extends React.Component {
8484 if (desiredPoint.length < 15) {
8585 const suggestedPoint =
8686 setFind(this.state.validChildren, e => e.startsWith(desiredPoint)) ||
87− '';
87+ "";
8888 const rendered =
89− ' '.repeat(desiredPoint.length) +
89+ " ".repeat(desiredPoint.length) +
9090 suggestedPoint.substring(desiredPoint.length);
9191
9292 const available = Just(this.state.validChildren.has(desiredPoint));
@@ -114,7 +114,7 @@ class IssueChild extends React.Component {
114114 const txn = ecliptic.spawn(
115115 validContracts,
116116 pointDec,
117− state.receivingAddress
117+ state.receivingAddress,
118118 );
119119
120120 return Just(txn);
@@ -156,14 +156,14 @@ class IssueChild extends React.Component {
156156
157157 const esdomain =
158158 props.networkType === NETWORK_NAMES.ROPSTEN
159− ? 'ropsten.etherscan.io'
160− : 'etherscan.io';
159+ ? "ropsten.etherscan.io"
160+ : "etherscan.io";
161161
162162 return (
163163 <Row>
164164 <Col>
165165 <H1>
166− {'Issue a Child From '}{' '}
166+ {"Issue a Child From "}{" "}
167167 <code>{`${ob.patp(state.issuingPoint)}`}</code>
168168 </H1>
169169
@@ -177,36 +177,38 @@ class IssueChild extends React.Component {
177177 {`Your point can only issue children with particular names. Some
178178 valid suggestions for `}
179179 {<code>{ob.patp(state.issuingPoint)}</code>}
180− {' are '}
180+ {" are "}
181181 <code>{state.suggestions[0]}</code>
182− {', '}
182+ {", "}
183183 <code>{state.suggestions[1]}</code>
184− {', and '}
184+ {", and "}
185185 <code>{state.suggestions[2]}</code>
186− {'.'}
186+ {"."}
187187 </P>
188188
189189 <PointInput
190190 prop-size="lg"
191191 prop-format="innerLabel"
192− className={'mono mt-8'}
192+ className={"mono mt-8"}
193193 placeholder={`e.g. ${state.suggestions[3]}`}
194194 value={state.desiredPoint}
195− onChange={this.handlePointInput}>
196− <InnerLabel>{'Point to Issue'}</InnerLabel>
195+ onChange={this.handlePointInput}
196+ >
197+ <InnerLabel>{"Point to Issue"}</InnerLabel>
197198 <div
198199 style={{
199− marginTop: '8.8rem',
200− marginLeft: '4.6rem',
201− fontSize: '5rem',
202− color: '#757575',
203− whiteSpace: 'pre-wrap',
200+ marginTop: "8.8rem",
201+ marginLeft: "4.6rem",
202+ fontSize: "5rem",
203+ color: "#757575",
204+ whiteSpace: "pre-wrap",
204205 }}
205− className="abs tl-0 mono">
206+ className="abs tl-0 mono"
207+ >
206208 {state.autoComplete}
207209 </div>
208210 <ValidatedSigil
209− className={'tr-0 mt-05 mr-0 abs'}
211+ className={"tr-0 mt-05 mr-0 abs"}
210212 patp={state.desiredPoint}
211213 size={68}
212214 margin={8}
@@ -223,20 +225,22 @@ class IssueChild extends React.Component {
223225 disabled={
224226 Nothing.hasInstance(state.isAvailable) || !state.isAvailable.value
225227 }
226− onChange={v => this.handleAddressInput(v)}>
227− <InnerLabel>{'Receiving Address'}</InnerLabel>
228− <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
228+ onChange={v => this.handleAddressInput(v)}
229+ >
230+ <InnerLabel>{"Receiving Address"}</InnerLabel>
231+ <ShowBlockie className={"mt-1"} address={state.receivingAddress} />
229232 </AddressInput>
230233
231234 <Anchor
232− className={'mt-1'}
233− prop-size={'sm'}
235+ className={"mt-1"}
236+ prop-size={"sm"}
234237 prop-disabled={
235238 !isValidAddress(state.receivingAddress) || !esvisible
236239 }
237− target={'_blank'}
238− href={`https://${esdomain}/address/${state.receivingAddress}`}>
239− {'View on Etherscan ↗'}
240+ target={"_blank"}
241+ href={`https://${esdomain}/address/${state.receivingAddress}`}
242+ >
243+ {"View on Etherscan ↗"}
240244 </Anchor>
241245
242246 <StatelessTransaction
src/bridge/views/Keystore.js+39−37
@@ -1,14 +1,15 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import { Ok, Error } from 'folktale/result';
3−import React from 'react';
4−import * as keythereum from 'keythereum';
5−import { Button, UploadButton } from '../components/Base';
6−import { Input, InnerLabel, InputCaption } from '../components/Base';
7−import { Row, Col, H1, H3, Warning } from '../components/Base';
8−
9−import { BRIDGE_ERROR } from '../lib/error';
10−import { ROUTE_NAMES } from '../lib/router';
11−import { EthereumWallet } from '../lib/wallet';
1+import { Just, Nothing } from "folktale/maybe";
2+import { Ok, Error } from "folktale/result";
3+import React from "react";
4+import * as keythereum from "keythereum";
5+import { Button, UploadButton } from "../components/Base";
6+import { Input, InnerLabel, InputCaption } from "../components/Base";
7+import { Row, Col, H1, H3, Warning } from "../components/Base";
8+
9+import { BRIDGE_ERROR } from "../lib/error";
10+import { ROUTE_NAMES } from "../lib/routeNames";
11+import { withHistory } from "../lib/history";
12+import { EthereumWallet } from "../lib/wallet";
1213
1314class Keystore extends React.Component {
1415 constructor(props) {
@@ -16,7 +17,7 @@ class Keystore extends React.Component {
1617
1718 this.state = {
1819 keystore: Nothing(), // Maybe<Result<String, String>>
19− password: '',
20+ password: "",
2021 decryptionProblem: false,
2122 };
2223
@@ -69,7 +70,7 @@ class Keystore extends React.Component {
6970 };
7071
7172 const failure = _ => {
72− const message = 'There was a problem uploading your Keystore file';
73+ const message = "There was a problem uploading your Keystore file";
7374 this.setState({ keystore: Just(Error(message)) });
7475 };
7576
@@ -80,15 +81,15 @@ 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({
87− Nothing: _ => 'bg-blue white',
88+ Nothing: _ => "bg-blue white",
8889 Just: ks =>
8990 ks.value.matchWith({
90− Ok: _ => 'bg-green white',
91− Error: _ => 'bg-yellow black',
91+ Ok: _ => "bg-green white",
92+ Error: _ => "bg-yellow black",
9293 }),
9394 });
9495
@@ -96,18 +97,18 @@ class Keystore extends React.Component {
9697 decryptionProblem === false ? (
9798 <div />
9899 ) : (
99− <Warning className={'mt-8'}>
100+ <Warning className={"mt-8"}>
100101 <H3 style={{ marginTop: 0, paddingTop: 0 }}>
101102 {"Couldn't decrypt wallet."}
102103 </H3>
103− {'You may have entered an incorrect password.'}
104+ {"You may have entered an incorrect password."}
104105 </Warning>
105106 );
106107
107108 return (
108109 <Row>
109− <Col className={'measure-md'}>
110− <H1 className={'mb-4'}>{'Upload Your Keystore File'}</H1>
110+ <Col className={"measure-md"}>
111+ <H1 className={"mb-4"}>{"Upload Your Keystore File"}</H1>
111112 <InputCaption>
112113 {`Please upload your Ethereum keystore file. If your keystore
113114 file is encrypted with a password, you'll also need to enter
@@ -116,9 +117,10 @@ class Keystore extends React.Component {
116117
117118 <UploadButton
118119 className={`btn ${uploadButtonClass} mt-10`}
119− onChange={this.handleKeystoreUpload}>
120− <div className={'flex-center-all fs-4 h-11 pointer'}>
121− {'Upload Keystore file'}
120+ onChange={this.handleKeystoreUpload}
121+ >
122+ <div className={"flex-center-all fs-4 h-11 pointer"}>
123+ {"Upload Keystore file"}
122124 </div>
123125 </UploadButton>
124126
@@ -131,29 +133,29 @@ class Keystore extends React.Component {
131133 onChange={this.handlePasswordInput}
132134 value={password}
133135 autocomplete="off"
134− autoFocus>
135− <InnerLabel>{'Password'}</InnerLabel>
136+ autoFocus
137+ >
138+ <InnerLabel>{"Password"}</InnerLabel>
136139 </Input>
137140
138141 <Button
139142 className={` mt-10`}
140− prop-size={'wide lg'}
143+ prop-size={"wide lg"}
141144 disabled={Nothing.hasInstance(keystore)}
142− onClick={this.constructWallet}>
143− {'Decrypt'}
145+ onClick={this.constructWallet}
146+ >
147+ {"Decrypt"}
144148 </Button>
145149
146150 {decryptMessage}
147151
148152 <Button
149− className={'mt-10'}
150− prop-size={'wide lg'}
153+ className={"mt-10"}
154+ prop-size={"wide lg"}
151155 disabled={Nothing.hasInstance(wallet)}
152− onClick={() => {
153− popRoute();
154− pushRoute(ROUTE_NAMES.SHIPS);
155− }}>
156− {'Continue →'}
156+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}
157+ >
158+ {"Continue →"}
157159 </Button>
158160 </Col>
159161 </Row>
@@ -161,4 +163,4 @@ class Keystore extends React.Component {
161163 }
162164}
163165
164−export default Keystore;
166+export default withHistory(Keystore);
src/bridge/views/Landing.js+69−61
@@ -1,72 +1,80 @@
1−import React from 'react';
2−import { Button } from '../components/Base';
3−import { Row, Col } from '../components/Base';
4−import { H1, P } from '../components/Base';
1+import React from "react";
2+import { Button } from "../components/Base";
3+import { Row, Col } from "../components/Base";
4+import { H1, P } from "../components/Base";
55
6−import { ROUTE_NAMES } from '../lib/router';
6+import { ROUTE_NAMES } from "../lib/routeNames";
7+import { useHistory } from "../lib/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+ >
28+ {"Claim an invite →"}
29+ </Button>
2730
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>
31+ <P>{"If you were sent an Azimuth invite code, start here."}</P>
3532
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>
33+ <Button
34+ prop-type="link"
35+ prop-size="lg"
36+ className={"mb-4 mt-8"}
37+ onClick={() => history.push(ROUTE_NAMES.NETWORK)}
38+ >
39+ {"Unlock a Wallet →"}
40+ </Button>
4141
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>
42+ <P>
43+ {"If you own Azimuth assets and want to manage them in some " +
44+ "way, start here. You'll need either your Urbit ticket or a " +
45+ "keypair."}
46+ </P>
6347
64− // <p>
65− // { "Lorem" }
66− // </p>
67− }
68− </Col>
69− </Row>
70−);
48+ {
49+ <Row>
50+ <Col>
51+ <Button
52+ prop-type={"link"}
53+ prop-size={"lg"}
54+ className={"mb-4 mt-8"}
55+ onClick={() => history.push(ROUTE_NAMES.VIEW_SHIP)}
56+ >
57+ {"View a point →"}
58+ </Button>
59+ <P>{"View an Azimuth point without signing into a wallet."}</P>
60+ </Col>
61+ </Row>
62+ // FIXME add address view
63+ // <LinkButton
64+ // size={'l'}
65+ // className={'mb-4 mt-10'}
66+ // onClick={ () => history.push(ROUTE_NAMES.NETWORK) }
67+ // >
68+ // { 'View an Address →' }
69+ // </LinkButton>
70+
71+ // <p>
72+ // { "Lorem" }
73+ // </p>
74+ }
75+ </Col>
76+ </Row>
77+ );
78+}
7179
7280export default Landing;
src/bridge/views/Ledger.js+69−67
@@ -1,19 +1,20 @@
1−import * as bip32 from 'bip32';
2−import React from 'react';
3−import Maybe from 'folktale/maybe';
4−import { Button } from '../components/Base';
5−import { Row, Col, H1, P, H2 } from '../components/Base';
6−import { Input, InnerLabel, InnerLabelDropdown } from '../components/Base';
7−import Transport from '@ledgerhq/hw-transport-u2f';
8−import Eth from '@ledgerhq/hw-app-eth';
9−import * as secp256k1 from 'secp256k1';
1+import * as bip32 from "bip32";
2+import React from "react";
3+import Maybe from "folktale/maybe";
4+import { Button } from "../components/Base";
5+import { Row, Col, H1, P, H2 } from "../components/Base";
6+import { Input, InnerLabel, InnerLabelDropdown } from "../components/Base";
7+import Transport from "@ledgerhq/hw-transport-u2f";
8+import Eth from "@ledgerhq/hw-app-eth";
9+import * as secp256k1 from "secp256k1";
1010
11−import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../lib/ledger';
12−import { ROUTE_NAMES } from '../lib/router';
11+import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from "../lib/ledger";
12+import { ROUTE_NAMES } from "../lib/routeNames";
13+import { withHistory } from "../lib/history";
1314
14−const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
15+const chopHdPrefix = str => (str.slice(0, 2) === "m/" ? str.slice(2) : str);
1516
16−const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
17+const addHdPrefix = str => (str.slice(0, 2) === "m/" ? str : "m/" + str);
1718
1819class Ledger extends React.Component {
1920 constructor(props) {
@@ -42,7 +43,7 @@ class Ledger extends React.Component {
4243
4344 updateHdPath(basePath, account) {
4445 let hdpath = this.state.hdpath;
45− if (basePath !== 'custom') {
46+ if (basePath !== "custom") {
4647 hdpath = basePath.replace(/x/g, account);
4748 }
4849 this.setState({ basePath, account, hdpath });
@@ -62,8 +63,8 @@ class Ledger extends React.Component {
6263
6364 eth.getAddress(path, false, true).then(
6465 info => {
65− const publicKey = Buffer.from(info.publicKey, 'hex');
66− const chainCode = Buffer.from(info.chainCode, 'hex');
66+ const publicKey = Buffer.from(info.publicKey, "hex");
67+ const chainCode = Buffer.from(info.chainCode, "hex");
6768 const pub = secp256k1.publicKeyConvert(publicKey, true);
6869 const hd = bip32.fromPublicKey(pub, chainCode);
6970 setWallet(Maybe.Just(hd));
@@ -71,21 +72,21 @@ class Ledger extends React.Component {
7172 },
7273 _ => {
7374 setWallet(Maybe.Nothing());
74− }
75+ },
7576 );
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
8384 const pathOptions = [
84− { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
85− { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
85+ { title: "Ledger Live", value: LEDGER_LIVE_PATH },
86+ { title: "Ledger Legacy", value: LEDGER_LEGACY_PATH },
8687 {
87− title: 'Custom path',
88− value: 'custom',
88+ title: "Custom path",
89+ value: "custom",
8990 },
9091 ];
9192 let basePathTitle = pathOptions.find(o => o.value === basePath).title;
@@ -93,7 +94,7 @@ class Ledger extends React.Component {
9394 let accountOptions = [];
9495 for (let i = 0; i < 20; i++) {
9596 accountOptions.push({
96− title: 'Account #' + (i + 1),
97+ title: "Account #" + (i + 1),
9798 value: i,
9899 });
99100 }
@@ -104,14 +105,14 @@ class Ledger extends React.Component {
104105 className="mt-8"
105106 options={pathOptions}
106107 handleUpdate={handlePathSelection}
107− title={'Derivation path'}
108+ title={"Derivation path"}
108109 currentSelectionTitle={basePathTitle}
109110 fullWidth={true}
110111 />
111112 );
112113
113114 const truePathSelection =
114− basePath === 'custom' ? (
115+ basePath === "custom" ? (
115116 <Input
116117 className="pt-8 mt-4 text-mono"
117118 prop-size="md"
@@ -119,8 +120,9 @@ class Ledger extends React.Component {
119120 name="hdpath"
120121 value={addHdPrefix(hdpath)}
121122 autocomplete="off"
122− onChange={this.handleHdPathInput}>
123− <InnerLabel>{'HD Path'}</InnerLabel>
123+ onChange={this.handleHdPathInput}
124+ >
125+ <InnerLabel>{"HD Path"}</InnerLabel>
124126 </Input>
125127 ) : (
126128 <InnerLabelDropdown
@@ -129,7 +131,7 @@ class Ledger extends React.Component {
129131 prop-format="innerLabel"
130132 options={accountOptions}
131133 handleUpdate={handleAccountSelection}
132− title={'Account'}
134+ title={"Account"}
133135 currentSelectionTitle={accountTitle}
134136 fullWidth={true}
135137 />
@@ -137,10 +139,10 @@ class Ledger extends React.Component {
137139
138140 return (
139141 <Row>
140− <Col className={'measure-md'}>
141− <H1>{'Authenticate With Your Ledger'}</H1>
142+ <Col className={"measure-md"}>
143+ <H1>{"Authenticate With Your Ledger"}</H1>
142144
143− <H2>{'Running on HTTPS?'}</H2>
145+ <H2>{"Running on HTTPS?"}</H2>
144146
145147 <P>
146148 {`Connect and authenticate to your Ledger, and then open the
@@ -155,41 +157,42 @@ class Ledger extends React.Component {
155157 it below.`}
156158 </P>
157159
158− <H2>{'Running on HTTP?'}</H2>
160+ <H2>{"Running on HTTP?"}</H2>
159161
160162 <P>{`To authenticate and sign transactions with a Ledger, Bridge
161163 must be serving over HTTPS on localhost. You can do this via the
162164 following:`}</P>
163165
164− <ol className={'measure-md'}>
165− <li className={'mt-4'}>
166− {'Install'}{' '}
166+ <ol className={"measure-md"}>
167+ <li className={"mt-4"}>
168+ {"Install"}{" "}
167169 <a
168− target={'_blank'}
169− href={'https://github.com/FiloSottile/mkcert'}>
170− {'mkcert'}
170+ target={"_blank"}
171+ href={"https://github.com/FiloSottile/mkcert"}
172+ >
173+ {"mkcert"}
171174 </a>
172175 </li>
173− <li className={'mt-4'}>
174− {'Install a local certificate authority via '}
175− <code>{'mkcert -install'}</code>
176+ <li className={"mt-4"}>
177+ {"Install a local certificate authority via "}
178+ <code>{"mkcert -install"}</code>
176179 </li>
177− <li className={'mt-4'}>
178− {'In your '}
179− <code>{'bridge'}</code>
180+ <li className={"mt-4"}>
181+ {"In your "}
182+ <code>{"bridge"}</code>
180183 {
181− ' directory, generate a certificate valid for localhost via '
182− }{' '}
183− <code>{'mkcert localhost'}</code>
184− {'.'}
185− {'This will produce two files: '}
186− <code>{'localhost.pem'}</code> {', the local certificate, and '}
187− <code>{'localhost-key.pem'}</code>
188− {', its corresponding private key.'}
184+ " directory, generate a certificate valid for localhost via "
185+ }{" "}
186+ <code>{"mkcert localhost"}</code>
187+ {"."}
188+ {"This will produce two files: "}
189+ <code>{"localhost.pem"}</code> {", the local certificate, and "}
190+ <code>{"localhost-key.pem"}</code>
191+ {", its corresponding private key."}
189192 </li>
190− <li className={'mt-4'}>
191− {'Run '}
192− <code>{'python bridge-https.py'}</code>
193+ <li className={"mt-4"}>
194+ {"Run "}
195+ <code>{"python bridge-https.py"}</code>
193196 </li>
194197 </ol>
195198
@@ -197,21 +200,20 @@ class Ledger extends React.Component {
197200 {truePathSelection}
198201
199202 <Button
200− prop-size={'wide lg'}
201− className={'mt-8'}
202− onClick={this.pollDevice}>
203− {'Authenticate →'}
203+ prop-size={"wide lg"}
204+ className={"mt-8"}
205+ onClick={this.pollDevice}
206+ >
207+ {"Authenticate →"}
204208 </Button>
205209
206210 <Button
207− className={'mt-8'}
208− prop-size={'wide lg'}
211+ className={"mt-8"}
212+ prop-size={"wide lg"}
209213 disabled={Maybe.Nothing.hasInstance(wallet)}
210− onClick={() => {
211− popRoute();
212− pushRoute(ROUTE_NAMES.SHIPS);
213− }}>
214− {'Continue →'}
214+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}
215+ >
216+ {"Continue →"}
215217 </Button>
216218 </Col>
217219 </Row>
@@ -219,4 +221,4 @@ class Ledger extends React.Component {
219221 }
220222}
221223
222−export default Ledger;
224+export default withHistory(Ledger);
src/bridge/views/Mnemonic.js+34−32
@@ -1,25 +1,26 @@
1−import * as bip39 from 'bip39';
2−import { Just, Nothing } from 'folktale/maybe';
3−import React from 'react';
4−import { Button } from '../components/Base';
1+import * as bip39 from "bip39";
2+import { Just, Nothing } from "folktale/maybe";
3+import React from "react";
4+import { Button } from "../components/Base";
55import {
66 Input,
77 MnemonicInput,
88 InnerLabel,
99 InputCaption,
10−} from '../components/Base';
11−import { Row, Col, H1 } from '../components/Base';
10+} from "../components/Base";
11+import { Row, Col, H1 } from "../components/Base";
1212
13−import { ROUTE_NAMES } from '../lib/router';
14−import { DEFAULT_HD_PATH, walletFromMnemonic } from '../lib/wallet';
13+import { ROUTE_NAMES } from "../lib/routeNames";
14+import { withHistory } from "../lib/history";
15+import { DEFAULT_HD_PATH, walletFromMnemonic } from "../lib/wallet";
1516
1617class Mnemonic extends React.Component {
1718 constructor(props) {
1819 super(props);
1920
2021 this.state = {
21− mnemonic: '',
22− passphrase: '',
22+ mnemonic: "",
23+ passphrase: "",
2324 hdpath: DEFAULT_HD_PATH,
2425 };
2526
@@ -43,8 +44,8 @@ class Mnemonic extends React.Component {
4344 const hdpath = state.hdpath;
4445 this.attemptWalletDerivation(
4546 mnemonic,
46− hdpath === '' ? DEFAULT_HD_PATH : hdpath,
47− passphrase
47+ hdpath === "" ? DEFAULT_HD_PATH : hdpath,
48+ passphrase,
4849 );
4950 return {
5051 passphrase,
@@ -58,8 +59,8 @@ class Mnemonic extends React.Component {
5859 const passphrase = state.passphrase;
5960 this.attemptWalletDerivation(
6061 mnemonic,
61− hdpath === '' ? DEFAULT_HD_PATH : hdpath,
62− passphrase
62+ hdpath === "" ? DEFAULT_HD_PATH : hdpath,
63+ passphrase,
6364 );
6465 return {
6566 mnemonic,
@@ -73,8 +74,8 @@ class Mnemonic extends React.Component {
7374 const passphrase = state.passphrase;
7475 this.attemptWalletDerivation(
7576 mnemonic,
76− hdpath === '' ? DEFAULT_HD_PATH : hdpath,
77− passphrase
77+ hdpath === "" ? DEFAULT_HD_PATH : hdpath,
78+ passphrase,
7879 );
7980 return {
8081 hdpath,
@@ -91,15 +92,15 @@ 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 (
9899 <Row>
99100 <Col>
100− <H1 className={'mb-4'}>{'Enter Your Mnemonic'}</H1>
101+ <H1 className={"mb-4"}>{"Enter Your Mnemonic"}</H1>
101102 <InputCaption>
102− {'Please enter your BIP39 mnemonic here.'}
103+ {"Please enter your BIP39 mnemonic here."}
103104 </InputCaption>
104105
105106 <MnemonicInput
@@ -112,8 +113,9 @@ class Mnemonic extends React.Component {
112113 onChange={this.handleMnemonicInput}
113114 value={mnemonic}
114115 autocomplete="off"
115− autoFocus>
116− <InnerLabel>{'Mnemonic'}</InnerLabel>
116+ autoFocus
117+ >
118+ <InnerLabel>{"Mnemonic"}</InnerLabel>
117119 </MnemonicInput>
118120
119121 <InputCaption>
@@ -128,8 +130,9 @@ class Mnemonic extends React.Component {
128130 type="password"
129131 value={passphrase}
130132 autocomplete="off"
131− onChange={this.handlePassphraseInput}>
132− <InnerLabel>{'Passphrase'}</InnerLabel>
133+ onChange={this.handlePassphraseInput}
134+ >
135+ <InnerLabel>{"Passphrase"}</InnerLabel>
133136 </Input>
134137
135138 <InputCaption>
@@ -143,18 +146,17 @@ class Mnemonic extends React.Component {
143146 name="hdpath"
144147 value={hdpath}
145148 autocomplete="off"
146− onChange={this.handleHdPathInput}>
147− <InnerLabel>{'HD Path'}</InnerLabel>
149+ onChange={this.handleHdPathInput}
150+ >
151+ <InnerLabel>{"HD Path"}</InnerLabel>
148152 </Input>
149153
150154 <Button
151− className={'mt-10'}
155+ className={"mt-10"}
152156 disabled={Nothing.hasInstance(wallet)}
153− onClick={() => {
154− popRoute();
155− pushRoute(ROUTE_NAMES.SHIPS);
156− }}>
157− {'Continue →'}
157+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}
158+ >
159+ {"Continue →"}
158160 </Button>
159161 </Col>
160162 </Row>
@@ -162,4 +164,4 @@ class Mnemonic extends React.Component {
162164 }
163165}
164166
165−export default Mnemonic;
167+export default withHistory(Mnemonic);
src/bridge/views/Network.js+31−29
@@ -1,14 +1,15 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import { Button } from '../components/Base';
4−import { InnerLabelDropdown } from '../components/Base';
5−import { 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';
11−import { ROUTE_NAMES } from '../lib/router';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import { Button } from "../components/Base";
4+import { InnerLabelDropdown } from "../components/Base";
5+import { 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";
11+import { ROUTE_NAMES } from "../lib/routeNames";
12+import { withHistory } from "../lib/history";
1213
1314class Network extends React.Component {
1415 constructor(props) {
@@ -23,7 +24,7 @@ class Network extends React.Component {
2324 if (network === NETWORK_NAMES.LOCAL) {
2425 setNetworkType(network);
2526
26− const endpoint = 'ws://localhost:8545';
27+ const endpoint = "ws://localhost:8545";
2728 const provider = new Web3.providers.WebsocketProvider(endpoint);
2829 const web3 = new Web3(provider);
2930 const contracts = azimuth.initContracts(web3, CONTRACT_ADDRESSES.DEV);
@@ -73,11 +74,11 @@ class Network extends React.Component {
7374
7475 // Note: localhost:3456 doesn't actually point to anything, we just need
7576 // a provider to initialize the Web3 object
76− const provider = new Web3.providers.HttpProvider('http://localhost:3456');
77+ const provider = new Web3.providers.HttpProvider("http://localhost:3456");
7778 const web3 = new Web3(provider);
7879
7980 const target =
80− process.env.NODE_ENV === 'development'
81+ process.env.NODE_ENV === "development"
8182 ? CONTRACT_ADDRESSES.DEV
8283 : CONTRACT_ADDRESSES.MAINNET;
8384
@@ -90,54 +91,55 @@ class Network extends React.Component {
9091 getNetworkOptions() {
9192 return [
9293 {
93− title: 'Main Network (default)',
94+ title: "Main Network (default)",
9495 value: NETWORK_NAMES.MAINNET,
9596 },
9697 {
97− title: 'Local Node',
98+ title: "Local Node",
9899 value: NETWORK_NAMES.LOCAL,
99100 },
100101 {
101− title: 'Ropsten',
102+ title: "Ropsten",
102103 value: NETWORK_NAMES.ROPSTEN,
103104 },
104105 {
105− type: 'divider',
106+ type: "divider",
106107 },
107108 {
108− title: 'Offline',
109+ title: "Offline",
109110 value: NETWORK_NAMES.OFFLINE,
110111 },
111112 ];
112113 }
113114
114115 render() {
115− const { networkType, pushRoute } = this.props;
116+ const { networkType, history } = this.props;
116117 const networkOptions = this.getNetworkOptions();
117118
118119 return (
119120 <Row>
120121 <Col>
121− <H1>{'Select Network'}</H1>
122+ <H1>{"Select Network"}</H1>
122123
123124 <P>
124125 {"Please select the Ethereum node you'd like to send " +
125− 'transactions to. For highly valuable keys, please select ' +
126− 'offline mode.'}
126+ "transactions to. For highly valuable keys, please select " +
127+ "offline mode."}
127128 </P>
128129
129130 <InnerLabelDropdown
130− className={'mt-6'}
131− title={'Node:'}
131+ className={"mt-6"}
132+ title={"Node:"}
132133 handleUpdate={this.handleNetworkChange}
133134 options={networkOptions}
134135 currentSelectionTitle={renderNetworkType(networkType)}
135136 />
136137
137138 <Button
138− className={'mt-10'}
139− onClick={() => pushRoute(ROUTE_NAMES.WALLET)}>
140− {'Continue →'}
139+ className={"mt-10"}
140+ onClick={() => history.push(ROUTE_NAMES.WALLET)}
141+ >
142+ {"Continue →"}
141143 </Button>
142144 </Col>
143145 </Row>
@@ -145,4 +147,4 @@ class Network extends React.Component {
145147 }
146148}
147149
148−export default Network;
150+export default withHistory(Network);
src/bridge/views/Point.js+20−32
@@ -1,15 +1,16 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
3−import { pour } from 'sigil-js';
4−import * as ob from 'urbit-ob';
5−import * as azimuth from 'azimuth-js';
6−import * as need from '../lib/need';
7−
8−import PointList from '../components/PointList';
9−import ReactSVGComponents from '../components/ReactSVGComponents';
10−import KeysAndMetadata from './Point/KeysAndMetadata';
11−import Actions from './Point/Actions';
12−import { Row, Col, H1, H3 } from '../components/Base';
1+import { Just, Nothing } from "folktale/maybe";
2+import React from "react";
3+import { pour } from "sigil-js";
4+import * as ob from "urbit-ob";
5+import * as azimuth from "azimuth-js";
6+import * as need from "../lib/need";
7+
8+import PointList from "../components/PointList";
9+import ReactSVGComponents from "../components/ReactSVGComponents";
10+import KeysAndMetadata from "./Point/KeysAndMetadata";
11+import Actions from "./Point/Actions";
12+import { Row, Col, H1, H3 } from "../components/Base";
13+import { withHistory } from "../lib/history";
1314
1415class Point extends React.Component {
1516 constructor(props) {
@@ -54,8 +55,8 @@ class Point extends React.Component {
5455 .getTotalUsableInvites(ctrcs, point)
5556 .then(count => this.setState({ invites: Just(count) }));
5657 this.updateSpawned(ctrcs, point);
57− })
58− )
58+ }),
59+ ),
5960 );
6061 }
6162
@@ -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,21 +95,16 @@ 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 />
112101 ) : (
113102 <div>
114− <H3>{'Issued Points'}</H3>
103+ <H3>{"Issued Points"}</H3>
115104
116105 <PointList
117106 setPointCursor={setPointCursor}
118− routeHandler={routeHandler}
107+ routeHandler={history.popAndPush}
119108 points={spawned}
120109 />
121110 </div>
@@ -124,13 +113,12 @@ class Point extends React.Component {
124113 return (
125114 <Row>
126115 <Col>
127− <div className={'mt-12 pt-6'}>{sigil}</div>
116+ <div className={"mt-12 pt-6"}>{sigil}</div>
128117 <H1>
129118 <code>{name}</code>
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+79−68
@@ -1,17 +1,20 @@
1−import React from 'react';
2−import * as need from '../../lib/need';
3−import { ETH_ZERO_ADDR, CURVE_ZERO_ADDR, eqAddr } from '../../lib/wallet';
4−import { Row, Col, H2, P } from '../../components/Base';
5−import { Button } from '../../components/Base';
6−import { ROUTE_NAMES } from '../../lib/router';
7−import { azimuth } from 'azimuth-js';
1+import React from "react";
2+import { azimuth } from "azimuth-js";
3+import * as need from "../../lib/need";
4+
5+import { ETH_ZERO_ADDR, CURVE_ZERO_ADDR, eqAddr } from "../../lib/wallet";
6+import { Row, Col, H2, P } from "../../components/Base";
7+import { Button } from "../../components/Base";
8+import { ROUTE_NAMES } from "../../lib/routeNames";
9+import { useHistory } from "../../lib/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
@@ -103,17 +106,16 @@ const Actions = props => {
103106 inviteAction = (
104107 <Button
105108 disabled={!isActiveOwner || !online || !hasInvites}
106− prop-size={'sm'}
107− prop-type={'link'}
108− onClick={() => {
109− pushRoute(ROUTE_NAMES.INVITES_SEND);
110− }}>
111− {'Send invites ('}
109+ prop-size={"sm"}
110+ prop-type={"link"}
111+ onClick={() => history.push(ROUTE_NAMES.INVITES_SEND)}
112+ >
113+ {"Send invites ("}
112114 {invites.matchWith({
113− Nothing: () => '?',
115+ Nothing: () => "?",
114116 Just: count => count.value,
115117 })}
116− {')'}
118+ {")"}
117119 </Button>
118120 );
119121 }
@@ -121,114 +123,123 @@ const Actions = props => {
121123 inviteAction = (
122124 <Button
123125 disabled={!(isActiveOwner && canIssueChild) || !online}
124− prop-size={'sm'}
125− prop-type={'link'}
126+ prop-size={"sm"}
127+ prop-type={"link"}
126128 onClick={() => {
127− pushRoute(ROUTE_NAMES.INVITES_MANAGE);
128− }}>
129− {'Manage invites'}
129+ history.push(ROUTE_NAMES.INVITES_MANAGE);
130+ }}
131+ >
132+ {"Manage invites"}
130133 </Button>
131134 );
132135 }
133136
134137 return (
135138 <div>
136− <H2>{'Actions'}</H2>
139+ <H2>{"Actions"}</H2>
137140 {displayReminder ? (
138141 <P>{`Before you can issue child points or generate your Arvo
139142 keyfile, you need to set your public keys.`}</P>
140143 ) : (
141− ''
144+ ""
142145 )}
143146 <Row>
144− <Col className={'flex flex-column items-start col-md-4'}>
147+ <Col className={"flex flex-column items-start col-md-4"}>
145148 <Button
146− prop-size={'sm'}
147− prop-type={'link'}
149+ prop-size={"sm"}
150+ prop-type={"link"}
148151 disabled={(online || planet) && !canIssueChild}
149152 onClick={() => {
150− pushRoute(ROUTE_NAMES.ISSUE_CHILD);
151− }}>
152− {'Issue child'}
153+ history.push(ROUTE_NAMES.ISSUE_CHILD);
154+ }}
155+ >
156+ {"Issue child"}
153157 </Button>
154158
155159 <Button
156− prop-size={'sm'}
157− prop-type={'link'}
160+ prop-size={"sm"}
161+ prop-type={"link"}
158162 disabled={online && !canAcceptTransfer}
159163 onClick={() => {
160− pushRoute(ROUTE_NAMES.ACCEPT_TRANSFER);
161− }}>
162− {'Accept incoming transfer'}
164+ history.push(ROUTE_NAMES.ACCEPT_TRANSFER);
165+ }}
166+ >
167+ {"Accept incoming transfer"}
163168 </Button>
164169
165170 <Button
166− prop-size={'sm'}
167− prop-type={'link'}
171+ prop-size={"sm"}
172+ prop-type={"link"}
168173 disabled={online && !canCancelTransfer}
169174 onClick={() => {
170− pushRoute(ROUTE_NAMES.CANCEL_TRANSFER);
171− }}>
172− {'Cancel outgoing transfer'}
175+ history.push(ROUTE_NAMES.CANCEL_TRANSFER);
176+ }}
177+ >
178+ {"Cancel outgoing transfer"}
173179 </Button>
174180
175181 <Button
176− prop-size={'sm'}
177− prop-type={'link'}
182+ prop-size={"sm"}
183+ prop-type={"link"}
178184 disabled={online && !canGenKeyfile}
179185 onClick={() => {
180− pushRoute(ROUTE_NAMES.GEN_KEYFILE);
181− }}>
182− {'Generate Arvo keyfile'}
186+ history.push(ROUTE_NAMES.GEN_KEYFILE);
187+ }}
188+ >
189+ {"Generate Arvo keyfile"}
183190 </Button>
184191 </Col>
185− <Col className={'flex flex-column items-start col-md-4'}>
192+ <Col className={"flex flex-column items-start col-md-4"}>
186193 <Button
187194 disabled={online && !canSetSpawnProxy}
188− prop-size={'sm'}
189− prop-type={'link'}
195+ prop-size={"sm"}
196+ prop-type={"link"}
190197 onClick={() => {
191− pushRoute(ROUTE_NAMES.SET_SPAWN_PROXY);
192− }}>
193− {'Change spawn proxy'}
198+ history.push(ROUTE_NAMES.SET_SPAWN_PROXY);
199+ }}
200+ >
201+ {"Change spawn proxy"}
194202 </Button>
195203
196204 <Button
197205 disabled={online && !canSetManagementProxy}
198− prop-size={'sm'}
199− prop-type={'link'}
206+ prop-size={"sm"}
207+ prop-type={"link"}
200208 onClick={() => {
201− pushRoute(ROUTE_NAMES.SET_MANAGEMENT_PROXY);
202− }}>
203− {'Change management proxy'}
209+ history.push(ROUTE_NAMES.SET_MANAGEMENT_PROXY);
210+ }}
211+ >
212+ {"Change management proxy"}
204213 </Button>
205214
206215 <Button
207216 disabled={online && !canConfigureKeys}
208− prop-size={'sm'}
209− prop-type={'link'}
217+ prop-size={"sm"}
218+ prop-type={"link"}
210219 onClick={() => {
211− pushRoute(ROUTE_NAMES.SET_KEYS);
212− }}>
213− {'Set network keys'}
220+ history.push(ROUTE_NAMES.SET_KEYS);
221+ }}
222+ >
223+ {"Set network keys"}
214224 </Button>
215225
216226 <Button
217227 disabled={online && !canTransfer}
218− prop-size={'sm'}
219− prop-type={'link'}
228+ prop-size={"sm"}
229+ prop-type={"link"}
220230 onClick={() => {
221− pushRoute(ROUTE_NAMES.TRANSFER);
222− }}>
223− {'Transfer'}
231+ history.push(ROUTE_NAMES.TRANSFER);
232+ }}
233+ >
234+ {"Transfer"}
224235 </Button>
225236 </Col>
226− <Col className={'flex flex-column items-start col-md-4'}>
237+ <Col className={"flex flex-column items-start col-md-4"}>
227238 {inviteAction}
228239 </Col>
229240 </Row>
230241 </div>
231242 );
232−};
243+}
233244
234245export default Actions;
src/bridge/views/Points.js+40−37
@@ -1,12 +1,13 @@
1−import React from 'react';
2−import { Row, Col, H1, H2, P } from '../components/Base';
3−import { Button } from '../components/Base';
4−import * as azimuth from 'azimuth-js';
1+import React from "react";
2+import { Row, Col, H1, H2, P } from "../components/Base";
3+import { Button } from "../components/Base";
4+import * as azimuth from "azimuth-js";
55
6−import PointList from '../components/PointList';
7−import { NETWORK_NAMES } from '../lib/network';
8−import { ROUTE_NAMES } from '../lib/router';
9−import { ETH_ZERO_ADDR, eqAddr } from '../lib/wallet';
6+import PointList from "../components/PointList";
7+import { NETWORK_NAMES } from "../lib/network";
8+import { ROUTE_NAMES } from "../lib/routeNames";
9+import { withHistory } from "../lib/history";
10+import { ETH_ZERO_ADDR, eqAddr } from "../lib/wallet";
1011
1112const hasTransferProxy = (cache, point) =>
1213 point in cache ? !eqAddr(cache[point].transferProxy, ETH_ZERO_ADDR) : false;
@@ -41,7 +42,7 @@ class Points extends React.Component {
4142
4243 const incoming = await azimuth.azimuth.getTransferringFor(
4344 ctrcs,
44− addr
45+ addr,
4546 );
4647
4748 const managing = await azimuth.azimuth.getManagerFor(ctrcs, addr);
@@ -65,8 +66,8 @@ class Points extends React.Component {
6566 });
6667
6768 this.cachePoints(ctrcs, addToPointCache, points);
68− })
69− )
69+ }),
70+ ),
7071 );
7172 }
7273
@@ -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 {
@@ -96,12 +97,13 @@ class Points extends React.Component {
9697 <Row>
9798 <Col>
9899 <Button
99− prop-type={'link'}
100− prop-size={'md'}
101− onClick={() => pushRoute(ROUTE_NAMES.VIEW_SHIP)}>
102− {'View a point →'}
100+ prop-type={"link"}
101+ prop-size={"md"}
102+ onClick={() => history.push(ROUTE_NAMES.VIEW_SHIP)}
103+ >
104+ {"View a point →"}
103105 </Button>
104− <P>{'View a point on Azimuth.'}</P>
106+ <P>{"View a point on Azimuth."}</P>
105107 </Col>
106108 </Row>
107109 );
@@ -113,14 +115,15 @@ class Points extends React.Component {
113115 <Row>
114116 <Col>
115117 <Button
116− prop-type={'link'}
117− prop-size={'md'}
118− onClick={() => pushRoute(ROUTE_NAMES.CREATE_GALAXY)}>
119− {'Create a galaxy →'}
118+ prop-type={"link"}
119+ prop-size={"md"}
120+ onClick={() => history.push(ROUTE_NAMES.CREATE_GALAXY)}
121+ >
122+ {"Create a galaxy →"}
120123 </Button>
121124 <P>
122125 {
123− 'You have the authority to create a new Galaxy and you can do so here.'
126+ "You have the authority to create a new Galaxy and you can do so here."
124127 }
125128 </P>
126129 </Col>
@@ -128,20 +131,20 @@ class Points extends React.Component {
128131 );
129132
130133 const outgoing = points.filter(point =>
131− hasTransferProxy(pointCache, point)
134+ hasTransferProxy(pointCache, point),
132135 );
133136
134137 const outgoingPoints =
135138 outgoing.length !== 0 ? (
136139 <React.Fragment>
137− <H2>{'Outgoing Transfers'}</H2>
140+ <H2>{"Outgoing Transfers"}</H2>
138141 <P>
139142 {`You own these points until the recipient accepts the incoming
140143 transfer. You may cancel the transfer until accepted.`}
141144 </P>
142145 <PointList
143146 setPointCursor={setPointCursor}
144− routeHandler={pushRoute}
147+ routeHandler={history.push}
145148 points={outgoing}
146149 />
147150 </React.Fragment>
@@ -152,14 +155,14 @@ class Points extends React.Component {
152155 const incomingPoints =
153156 incoming.length !== 0 ? (
154157 <React.Fragment>
155− <H2>{'Incoming Transfers'}</H2>
158+ <H2>{"Incoming Transfers"}</H2>
156159 <P>
157160 {`You do not own these points until you accept the incoming transfer.
158161 You may reject any incoming transfers.`}
159162 </P>
160163 <PointList
161164 setPointCursor={setPointCursor}
162− routeHandler={pushRoute}
165+ routeHandler={history.push}
163166 points={incoming}
164167 />
165168 </React.Fragment>
@@ -170,14 +173,14 @@ class Points extends React.Component {
170173 const managingPoints =
171174 managing.length !== 0 ? (
172175 <React.Fragment>
173− <H2>{'You Are a Management Proxy For'}</H2>
176+ <H2>{"You Are a Management Proxy For"}</H2>
174177 <P>
175178 {`You can configure or set network keys and conduct sponsorship
176179 related operations for these points.`}
177180 </P>
178181 <PointList
179182 setPointCursor={setPointCursor}
180− routeHandler={pushRoute}
183+ routeHandler={history.push}
181184 points={managing}
182185 />
183186 </React.Fragment>
@@ -188,14 +191,14 @@ class Points extends React.Component {
188191 const votingPoints =
189192 voting.length !== 0 ? (
190193 <React.Fragment>
191− <H2>{'You Are a Voting Proxy For'}</H2>
194+ <H2>{"You Are a Voting Proxy For"}</H2>
192195 <P>
193196 {`Since you are part of the Galactic Senate, you can cast votes on
194197 new Azimuth proposals on behalf of these points.`}
195198 </P>
196199 <PointList
197200 setPointCursor={setPointCursor}
198− routeHandler={pushRoute}
201+ routeHandler={history.push}
199202 points={voting}
200203 />
201204 </React.Fragment>
@@ -206,11 +209,11 @@ class Points extends React.Component {
206209 const spawningPoints =
207210 spawning.length !== 0 ? (
208211 <React.Fragment>
209− <H2>{'You Are a Spawn Proxy For'}</H2>
212+ <H2>{"You Are a Spawn Proxy For"}</H2>
210213 <P>{`You can create new child ships under these points.`}</P>
211214 <PointList
212215 setPointCursor={setPointCursor}
213− routeHandler={pushRoute}
216+ routeHandler={history.push}
214217 points={spawning}
215218 />
216219 </React.Fragment>
@@ -221,7 +224,7 @@ class Points extends React.Component {
221224 return (
222225 <Row>
223226 <Col>
224− <H1>{'Points'}</H1>
227+ <H1>{"Points"}</H1>
225228
226229 <P>{`A point is an identity on the Ethereum blockchain.
227230 Points declare keys for ships on the Arvo network.`}</P>
@@ -234,11 +237,11 @@ class Points extends React.Component {
234237
235238 {outgoingPoints}
236239
237− <H2>{'Your Points'}</H2>
240+ <H2>{"Your Points"}</H2>
238241
239242 <PointList
240243 setPointCursor={setPointCursor}
241− routeHandler={pushRoute}
244+ routeHandler={history.push}
242245 points={points}
243246 loading={loading}
244247 />
@@ -254,4 +257,4 @@ class Points extends React.Component {
254257 }
255258}
256259
257−export default Points;
260+export default withHistory(Points);
src/bridge/views/PrivateKey.js+22−22
@@ -1,18 +1,19 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import { Button } from '../components/Base';
4−import { RequiredInput, InnerLabel, InputCaption } from '../components/Base';
5−import { Row, Col, H1 } from '../components/Base';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import { Button } from "../components/Base";
4+import { RequiredInput, InnerLabel, InputCaption } from "../components/Base";
5+import { Row, Col, H1 } from "../components/Base";
66
7−import { ROUTE_NAMES } from '../lib/router';
8−import { EthereumWallet } from '../lib/wallet';
7+import { ROUTE_NAMES } from "../lib/routeNames";
8+import { withHistory } from "../lib/history";
9+import { EthereumWallet } from "../lib/wallet";
910
1011class PrivateKey extends React.Component {
1112 constructor(props) {
1213 super(props);
1314
1415 this.state = {
15− privateKey: '',
16+ privateKey: "",
1617 };
1718
1819 this.handlePrivateKeyInput = this.handlePrivateKeyInput.bind(this);
@@ -26,7 +27,7 @@ class PrivateKey extends React.Component {
2627 constructWallet(privateKey) {
2728 const { setWallet } = this.props;
2829 if (/^[0-9A-Fa-f]{64}$/g.test(privateKey) === true) {
29− const sec = Buffer.from(privateKey, 'hex');
30+ const sec = Buffer.from(privateKey, "hex");
3031 const wallet = new EthereumWallet(sec);
3132 setWallet(Maybe.Just(wallet));
3233 } else {
@@ -35,13 +36,13 @@ 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 (
4243 <Row>
43− <Col className={'measure-md'}>
44− <H1 className={'mb-4'}>{'Enter Your Private Key'}</H1>
44+ <Col className={"measure-md"}>
45+ <H1 className={"mb-4"}>{"Enter Your Private Key"}</H1>
4546 <InputCaption>
4647 {`Please enter your raw Ethereum private key here.`}
4748 </InputCaption>
@@ -55,19 +56,18 @@ class PrivateKey extends React.Component {
5556 onChange={this.handlePrivateKeyInput}
5657 value={privateKey}
5758 autocomplete="off"
58− autoFocus>
59− <InnerLabel>{'Private Key'}</InnerLabel>
59+ autoFocus
60+ >
61+ <InnerLabel>{"Private Key"}</InnerLabel>
6062 </RequiredInput>
6163
6264 <Button
63− className={'mt-10'}
64− prop-size={'wide lg'}
65+ className={"mt-10"}
66+ prop-size={"wide lg"}
6567 disabled={Maybe.Nothing.hasInstance(wallet)}
66− onClick={() => {
67− popRoute();
68− pushRoute(ROUTE_NAMES.SHIPS);
69− }}>
70− {'Continue →'}
68+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}
69+ >
70+ {"Continue →"}
7171 </Button>
7272 </Col>
7373 </Row>
@@ -75,4 +75,4 @@ class PrivateKey extends React.Component {
7575 }
7676}
7777
78−export default PrivateKey;
78+export default withHistory(PrivateKey);
src/bridge/views/SentTransaction.js+42−45
@@ -1,25 +1,27 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import * as need from '../lib/need';
4−import { Row, Col, H1, H3, P, Warning, Anchor } from '../components/Base';
5−import { Button } from '../components/Base';
6−
7−import { ROUTE_NAMES } from '../lib/router';
8−import { BRIDGE_ERROR, renderTxnError } from '../lib/error';
9−import { NETWORK_NAMES } from '../lib/network';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import * as need from "../lib/need";
4+import { Row, Col, H1, H3, P, Warning, Anchor } from "../components/Base";
5+import { Button } from "../components/Base";
6+
7+import { ROUTE_NAMES } from "../lib/routeNames";
8+import { useHistory } from "../lib/history";
9+import { BRIDGE_ERROR, renderTxnError } from "../lib/error";
10+import { NETWORK_NAMES } from "../lib/network";
11+import { withTxnConfirmations } from "../store/txnConfirmations";
1012
1113class Success extends React.Component {
1214 constructor(props) {
1315 super(props);
1416
1517 this.state = {
16− pending: '.',
18+ pending: ".",
1719 interval: null,
1820 };
1921 }
2022
2123 componentDidMount() {
22− const nextDot = { '.': '..', '..': '...', '...': '.' };
24+ const nextDot = { ".": "..", "..": "...", "...": "." };
2325
2426 const interval = setInterval(() => {
2527 this.setState(({ pending }) => ({ pending: nextDot[pending] }));
@@ -42,27 +44,28 @@ class Success extends React.Component {
4244
4345 const esdomain =
4446 networkType === NETWORK_NAMES.ROPSTEN
45− ? 'ropsten.etherscan.io'
46− : 'etherscan.io';
47+ ? "ropsten.etherscan.io"
48+ : "etherscan.io";
4749
4850 const esmessage =
4951 esvisible === true
50− ? 'If you’d like to keep track of it, click the Etherscan link below.'
51− : '';
52+ ? "If you’d like to keep track of it, click the Etherscan link below."
53+ : "";
5254
5355 const esanchor =
5456 esvisible === false ? null : (
5557 <Anchor
56− className={'mb-4 mt-1'}
57− prop-size={'sm'}
58− target={'_blank'}
59− href={`https://${esdomain}/tx/${hash}`}>
60− {'View on Etherscan ↗'}
58+ className={"mb-4 mt-1"}
59+ prop-size={"sm"}
60+ target={"_blank"}
61+ href={`https://${esdomain}/tx/${hash}`}
62+ >
63+ {"View on Etherscan ↗"}
6164 </Anchor>
6265 );
6366
6467 const confirmations = Maybe.fromNullable(txnConfirmations[hash]).getOrElse(
65− 0
68+ 0,
6669 );
6770
6871 const requiredConfirmations = 1;
@@ -75,17 +78,17 @@ class Success extends React.Component {
7578 return (
7679 <Row>
7780 <Col>
78− <H1>{'Your Transaction was Sent'}</H1>
81+ <H1>{"Your Transaction was Sent"}</H1>
7982
8083 <P>
8184 {`We sent your transaction to the chain. It can take some time to
8285 execute, especially if the network is busy. ${esmessage}`}
8386 </P>
8487
85− <H3>{'Transaction Hash'}</H3>
88+ <H3>{"Transaction Hash"}</H3>
8689 <P>{hash}</P>
8790
88− <H3>{'Transaction Status'}</H3>
91+ <H3>{"Transaction Status"}</H3>
8992 <P>{status}</P>
9093 <P>{esanchor}</P>
9194 </Col>
@@ -97,10 +100,10 @@ class Success extends React.Component {
97100const Failure = props => (
98101 <Row>
99102 <Col>
100− <H1>{'Error!'}</H1>
103+ <H1>{"Error!"}</H1>
101104
102105 <Warning>
103− <H3>{'There was an error sending your transaction.'}</H3>
106+ <H3>{"There was an error sending your transaction."}</H3>
104107 {renderTxnError(props.web3, props.message)}
105108 </Warning>
106109 </Col>
@@ -108,16 +111,11 @@ const Failure = props => (
108111);
109112
110113const SentTransaction = props => {
111− const {
112− txnHashCursor,
113− networkType,
114− popRoute,
115− pushRoute,
116− txnConfirmations,
117− } = props;
114+ const history = useHistory();
115+ const { txnHashCursor, networkType, txnConfirmations } = props;
118116 const { setPointCursor, pointCursor } = props;
119117
120− const promptKeyfile = props.routeData && props.routeData.promptKeyfile;
118+ const promptKeyfile = history.data() && history.data().promptKeyfile;
121119
122120 const w3 = need.web3(props);
123121
@@ -143,12 +141,13 @@ const SentTransaction = props => {
143141 <Row>
144142 <Col>
145143 <Button
146− prop-type={'link'}
144+ prop-type={"link"}
147145 onClick={() => {
148146 setPointCursor(pointCursor);
149− popRoute();
150− }}>
151− {'Ok →'}
147+ history.pop();
148+ }}
149+ >
150+ {"Ok →"}
152151 </Button>
153152 </Col>
154153 </Row>
@@ -161,12 +160,10 @@ const SentTransaction = props => {
161160 <Row>
162161 <Col>
163162 <Button
164− prop-type={'link'}
165− onClick={() => {
166− popRoute();
167− pushRoute(ROUTE_NAMES.GEN_KEYFILE);
168− }}>
169− {'Download Keyfile →'}
163+ prop-type={"link"}
164+ onClick={() => history.popAndPush(ROUTE_NAMES.GEN_KEYFILE)}
165+ >
166+ {"Download Keyfile →"}
170167 </Button>
171168 </Col>
172169 </Row>
@@ -182,4 +179,4 @@ const SentTransaction = props => {
182179 );
183180};
184181
185−export default SentTransaction;
182+export default withTxnConfirmations(SentTransaction);
src/bridge/views/SetKeys.js+36−36
@@ -1,16 +1,16 @@
1−import React from 'react';
2−import Maybe from 'folktale/maybe';
3−import * as azimuth from 'azimuth-js';
4−import * as ob from 'urbit-ob';
5−import * as need from '../lib/need';
6−import { Row, Col, H1, P, Warning, CheckboxButton } from '../components/Base';
1+import React from "react";
2+import Maybe from "folktale/maybe";
3+import * as azimuth from "azimuth-js";
4+import * as ob from "urbit-ob";
5+import * as need from "../lib/need";
6+import { Row, Col, H1, P, Warning, CheckboxButton } from "../components/Base";
77
8−import StatelessTransaction from '../components/StatelessTransaction';
9−import { attemptSeedDerivation } from '../lib/keys';
8+import StatelessTransaction from "../components/StatelessTransaction";
9+import { 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
13−import { addHexPrefix } from '../lib/wallet';
13+import { addHexPrefix } from "../lib/wallet";
1414
1515class SetKeys extends React.Component {
1616 constructor(props) {
@@ -19,9 +19,9 @@ class SetKeys extends React.Component {
1919 const point = need.pointCursor(props);
2020
2121 this.state = {
22− auth: '',
23− encr: '',
24− networkSeed: '',
22+ auth: "",
23+ encr: "",
24+ networkSeed: "",
2525 nondeterministicSeed: false,
2626 point: point,
2727 cryptoSuiteVersion: 1,
@@ -64,22 +64,22 @@ class SetKeys extends React.Component {
6464 hex =
6565 hex +
6666 [
67− '0',
68− '1',
69− '2',
70− '3',
71− '4',
72− '5',
73− '6',
74− '7',
75− '8',
76− '9',
77− 'A',
78− 'B',
79− 'C',
80− 'D',
81− 'E',
82− 'F',
67+ "0",
68+ "1",
69+ "2",
70+ "3",
71+ "4",
72+ "5",
73+ "6",
74+ "7",
75+ "8",
76+ "9",
77+ "A",
78+ "B",
79+ "C",
80+ "D",
81+ "E",
82+ "F",
8383 ][Math.floor(Math.random() * 16)];
8484 }
8585
@@ -91,13 +91,13 @@ class SetKeys extends React.Component {
9191 let seed = await attemptSeedDerivation(next, this.props);
9292 let nondeterministicSeed = false;
9393
94− if (seed.getOrElse('') === '') {
94+ if (seed.getOrElse("") === "") {
9595 seed = Maybe.Just(this.randomHex(64));
9696 nondeterministicSeed = true;
9797 }
9898
9999 this.setState({
100− networkSeed: seed.getOrElse(''),
100+ networkSeed: seed.getOrElse(""),
101101 nondeterministicSeed: nondeterministicSeed,
102102 });
103103 }
@@ -124,7 +124,7 @@ class SetKeys extends React.Component {
124124 pencr,
125125 pauth,
126126 1,
127− state.discontinuity
127+ state.discontinuity,
128128 );
129129
130130 return Maybe.Just(txn);
@@ -145,7 +145,7 @@ class SetKeys extends React.Component {
145145 <Row>
146146 <Col>
147147 <H1>
148− {'Set Network Keys For '} <code>{`${ob.patp(state.point)}`}</code>
148+ {"Set Network Keys For "} <code>{`${ob.patp(state.point)}`}</code>
149149 </H1>
150150
151151 <P className="mt-10">
@@ -154,7 +154,7 @@ class SetKeys extends React.Component {
154154
155155 {state.nondeterministicSeed && (
156156 <Warning>
157− <h3 className={'mb-2'}>{'Warning'}</h3>
157+ <h3 className={"mb-2"}>{"Warning"}</h3>
158158 {`Your network seed could not be derived automatically. We've
159159 generated a random one for you, so you must download your Arvo
160160 keyfile during this session after setting your keys.`}
@@ -163,8 +163,8 @@ class SetKeys extends React.Component {
163163
164164 {pointDetails.keyRevisionNumber === 0 ? (
165165 <Warning>
166− <h3 className={'mb-2'}>{'Warning'}</h3>
167− {'Once these keys have been set, your point is considered ' +
166+ <h3 className={"mb-2"}>{"Warning"}</h3>
167+ {"Once these keys have been set, your point is considered " +
168168 "'linked'. This operation cannot be undone."}
169169 </Warning>
170170 ) : (
src/bridge/views/SetProxy.js+31−29
@@ -1,7 +1,7 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import * as need from '../lib/need';
4−import * as azimuth from 'azimuth-js';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import * as need from "../lib/need";
4+import * as azimuth from "azimuth-js";
55import {
66 Row,
77 Col,
@@ -11,17 +11,17 @@ import {
1111 ShowBlockie,
1212 Anchor,
1313 HorizontalSelector,
14−} from '../components/Base';
15−import { AddressInput } from '../components/Base';
16−import * as ob from 'urbit-ob';
14+} from "../components/Base";
15+import { AddressInput } from "../components/Base";
16+import * as ob from "urbit-ob";
1717
18−import { PROXY_TYPE, renderProxyType } from '../lib/proxy';
18+import { PROXY_TYPE, renderProxyType } from "../lib/proxy";
1919
20−import { NETWORK_NAMES } from '../lib/network';
20+import { NETWORK_NAMES } from "../lib/network";
2121
22−import StatelessTransaction from '../components/StatelessTransaction';
22+import StatelessTransaction from "../components/StatelessTransaction";
2323
24−import { isValidAddress } from '../lib/wallet';
24+import { isValidAddress } from "../lib/wallet";
2525
2626const SetManagementProxy = props => (
2727 <SetProxy {...props} proxyType={PROXY_TYPE.MANAGEMENT_PROXY} />
@@ -42,7 +42,7 @@ class SetProxy extends React.Component {
4242 const issuingPoint = need.pointCursor(props);
4343
4444 this.state = {
45− proxyAddress: '',
45+ proxyAddress: "",
4646 issuingPoint: issuingPoint,
4747 setProxy: true,
4848 };
@@ -54,10 +54,10 @@ class SetProxy extends React.Component {
5454 }
5555
5656 handleSetUnset(selected) {
57− const set = selected === 'set';
57+ const set = selected === "set";
5858 this.setState({
5959 setProxy: set,
60− proxyAddress: set ? '' : '0x0000000000000000000000000000000000000000',
60+ proxyAddress: set ? "" : "0x0000000000000000000000000000000000000000",
6161 });
6262 }
6363
@@ -104,26 +104,26 @@ class SetProxy extends React.Component {
104104
105105 const esdomain =
106106 props.networkType === NETWORK_NAMES.ROPSTEN
107− ? 'ropsten.etherscan.io'
108− : 'etherscan.io';
107+ ? "ropsten.etherscan.io"
108+ : "etherscan.io";
109109
110110 const ucFirst = w => w.charAt(0).toUpperCase() + w.slice(1);
111111
112112 const setUnset = [
113− { title: 'Set', value: 'set' },
114− { title: 'Unset', value: 'unset' },
113+ { title: "Set", value: "set" },
114+ { title: "Unset", value: "unset" },
115115 ];
116116
117− const titleVerb = this.state.setProxy ? 'Set' : 'Unset';
117+ const titleVerb = this.state.setProxy ? "Set" : "Unset";
118118
119119 let addressInput = null;
120120 if (this.state.setProxy) {
121121 addressInput = (
122122 <>
123123 <P className="mt-8">
124− {'Please provide an Ethereum address to act as the ' +
124+ {"Please provide an Ethereum address to act as the " +
125125 `${renderedProxyType} proxy. You can also use Wallet ` +
126− 'Generator to generate a keypair.'}
126+ "Generator to generate a keypair."}
127127 </P>
128128
129129 <AddressInput
@@ -132,18 +132,20 @@ class SetProxy extends React.Component {
132132 prop-format="innerLabel"
133133 placeholder={`e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f`}
134134 value={state.proxyAddress}
135− onChange={v => this.handleAddressInput(v)}>
136− <InnerLabel>{'Proxy Address'}</InnerLabel>
137− <ShowBlockie className={'mt-1'} address={state.proxyAddress} />
135+ onChange={v => this.handleAddressInput(v)}
136+ >
137+ <InnerLabel>{"Proxy Address"}</InnerLabel>
138+ <ShowBlockie className={"mt-1"} address={state.proxyAddress} />
138139 </AddressInput>
139140
140141 <Anchor
141− className={'mt-1'}
142− prop-size={'sm'}
142+ className={"mt-1"}
143+ prop-size={"sm"}
143144 prop-disabled={!isValidAddress(state.proxyAddress) || !esvisible}
144− target={'_blank'}
145− href={`https://${esdomain}/address/${state.proxyAddress}`}>
146− {'View on Etherscan ↗'}
145+ target={"_blank"}
146+ href={`https://${esdomain}/address/${state.proxyAddress}`}
147+ >
148+ {"View on Etherscan ↗"}
147149 </Anchor>
148150 </>
149151 );
src/bridge/views/Shards.js+55−50
@@ -1,6 +1,6 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
3−import { Button } from '../components/Base';
1+import { Just, Nothing } from "folktale/maybe";
2+import React from "react";
3+import { Button } from "../components/Base";
44import {
55 InnerLabel,
66 ValidatedSigil,
@@ -8,27 +8,28 @@ import {
88 ShardInput,
99 InputCaption,
1010 Input,
11−} from '../components/Base';
12−import { Row, Col, H1, P } from '../components/Base';
13−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
14−import * as ob from 'urbit-ob';
11+} from "../components/Base";
12+import { Row, Col, H1, P } from "../components/Base";
13+import * as kg from "urbit-key-generation/dist/index";
14+import * as ob from "urbit-ob";
1515
16−import { ROUTE_NAMES } from '../lib/router';
17−import { urbitWalletFromTicket } from '../lib/wallet';
16+import { ROUTE_NAMES } from "../lib/routeNames";
17+import { withHistory } from "../lib/history";
18+import { urbitWalletFromTicket } from "../lib/wallet";
1819
1920const placeholder = len => {
2021 let bytes = window.crypto.getRandomValues(new Uint8Array(len));
2122 let hex = bytes.reduce(
22− (acc, byt) => acc + byt.toString(16).padStart(2, '0'),
23− ''
23+ (acc, byt) => acc + byt.toString(16).padStart(2, "0"),
24+ "",
2425 );
2526 return ob.hex2patq(hex);
2627};
2728
2829const SHARDS = {
29− SHARD1: Symbol('SHARD1'),
30− SHARD2: Symbol('SHARD2'),
31− SHARD3: Symbol('SHARD3'),
30+ SHARD1: Symbol("SHARD1"),
31+ SHARD2: Symbol("SHARD2"),
32+ SHARD3: Symbol("SHARD3"),
3233};
3334
3435class Shards extends React.Component {
@@ -36,11 +37,11 @@ class Shards extends React.Component {
3637 super(props);
3738
3839 this.state = {
39− shard1: '',
40− shard2: '',
41− shard3: '',
42− passphrase: '',
43− pointName: '',
40+ shard1: "",
41+ shard2: "",
42+ shard3: "",
43+ passphrase: "",
44+ pointName: "",
4445 };
4546
4647 this.pointPlaceholder = placeholder(4);
@@ -72,9 +73,9 @@ class Shards extends React.Component {
7273 }
7374
7475 async walletFromShards(shard1, shard2, shard3, pointName, passphrase) {
75− const s1 = shard1 === '' ? undefined : shard1;
76− const s2 = shard2 === '' ? undefined : shard2;
77− const s3 = shard3 === '' ? undefined : shard3;
76+ const s1 = shard1 === "" ? undefined : shard1;
77+ const s2 = shard2 === "" ? undefined : shard2;
78+ const s3 = shard3 === "" ? undefined : shard3;
7879
7980 let ticket = undefined;
8081 try {
@@ -90,19 +91,19 @@ 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;
9798 const phTick = this.ticketPlaceholder;
9899
99100 const shards = [shard1, shard2, shard3];
100− const ready = shards.filter(x => x !== '').length > 1;
101+ const ready = shards.filter(x => x !== "").length > 1;
101102
102103 return (
103104 <Row>
104105 <Col>
105− <H1>{'Authenticate'}</H1>
106+ <H1>{"Authenticate"}</H1>
106107
107108 <P>
108109 {`Enter your point and at least two of your three Urbit master
@@ -118,10 +119,11 @@ class Shards extends React.Component {
118119 autoFocus
119120 placeholder={`e.g. ${phPoint}`}
120121 value={pointName}
121− onChange={this.handlePointNameInput}>
122− <InnerLabel>{'Point'}</InnerLabel>
122+ onChange={this.handlePointNameInput}
123+ >
124+ <InnerLabel>{"Point"}</InnerLabel>
123125 <ValidatedSigil
124− className={'tr-0 mt-05 mr-0 abs'}
126+ className={"tr-0 mt-05 mr-0 abs"}
125127 patp={pointName}
126128 size={68}
127129 margin={8}
@@ -136,8 +138,9 @@ class Shards extends React.Component {
136138 name="shard1"
137139 placeholder={`e.g. ${phTick}`}
138140 value={shard1}
139− onChange={inp => this.handleShardInput(SHARDS.SHARD1, inp)}>
140− <InnerLabel>{'Shard 1'}</InnerLabel>
141+ onChange={inp => this.handleShardInput(SHARDS.SHARD1, inp)}
142+ >
143+ <InnerLabel>{"Shard 1"}</InnerLabel>
141144 </ShardInput>
142145
143146 <ShardInput
@@ -148,8 +151,9 @@ class Shards extends React.Component {
148151 name="shard2"
149152 placeholder={`e.g. ${phTick}`}
150153 value={shard2}
151− onChange={inp => this.handleShardInput(SHARDS.SHARD2, inp)}>
152− <InnerLabel>{'Shard 2'}</InnerLabel>
154+ onChange={inp => this.handleShardInput(SHARDS.SHARD2, inp)}
155+ >
156+ <InnerLabel>{"Shard 2"}</InnerLabel>
153157 </ShardInput>
154158
155159 <ShardInput
@@ -160,12 +164,13 @@ class Shards extends React.Component {
160164 name="shard3"
161165 placeholder={`e.g. ${phTick}`}
162166 value={shard3}
163− onChange={inp => this.handleShardInput(SHARDS.SHARD3, inp)}>
164− <InnerLabel>{'Shard 3'}</InnerLabel>
167+ onChange={inp => this.handleShardInput(SHARDS.SHARD3, inp)}
168+ >
169+ <InnerLabel>{"Shard 3"}</InnerLabel>
165170 </ShardInput>
166171
167172 <InputCaption>
168− {'If your wallet requires a passphrase, you may enter it below.'}
173+ {"If your wallet requires a passphrase, you may enter it below."}
169174 </InputCaption>
170175
171176 <Input
@@ -176,14 +181,15 @@ class Shards extends React.Component {
176181 type="password"
177182 value={passphrase}
178183 autocomplete="off"
179− onChange={this.handlePassphraseInput}>
180− <InnerLabel>{'Passphrase'}</InnerLabel>
184+ onChange={this.handlePassphraseInput}
185+ >
186+ <InnerLabel>{"Passphrase"}</InnerLabel>
181187 </Input>
182188
183189 <Button
184190 disabled={!ready}
185− className={'mt-8'}
186− prop-size={'lg wide'}
191+ className={"mt-8"}
192+ prop-size={"lg wide"}
187193 // prop-color={this.buttonTriState(wallet)}
188194 onClick={() =>
189195 this.walletFromShards(
@@ -191,21 +197,20 @@ class Shards extends React.Component {
191197 shard2,
192198 shard3,
193199 pointName,
194− passphrase
200+ passphrase,
195201 )
196− }>
197− {'Unlock Wallet →'}
202+ }
203+ >
204+ {"Unlock Wallet →"}
198205 </Button>
199206
200207 <Button
201− className={'mt-4'}
202− prop-size={'xl wide'}
208+ className={"mt-4"}
209+ prop-size={"xl wide"}
203210 disabled={Nothing.hasInstance(wallet)}
204− onClick={() => {
205− popRoute();
206− pushRoute(ROUTE_NAMES.SHIPS);
207− }}>
208− {'Continue →'}
211+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}
212+ >
213+ {"Continue →"}
209214 </Button>
210215 </Col>
211216 </Row>
@@ -213,4 +218,4 @@ class Shards extends React.Component {
213218 }
214219}
215220
216−export default Shards;
221+export default withHistory(Shards);
src/bridge/views/Ticket.js+36−35
@@ -1,6 +1,6 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
3−import { Button } from '../components/Base';
1+import { Just, Nothing } from "folktale/maybe";
2+import React from "react";
3+import { Button } from "../components/Base";
44import {
55 InnerLabel,
66 ValidatedSigil,
@@ -8,21 +8,22 @@ import {
88 TicketInput,
99 Input,
1010 InputCaption,
11−} from '../components/Base';
12−import { Row, Col, H1, P } from '../components/Base';
11+} from "../components/Base";
12+import { Row, Col, H1, P } from "../components/Base";
1313
14−import { randomPatq } from '../lib/lib';
15−import { ROUTE_NAMES } from '../lib/router';
16−import { urbitWalletFromTicket } from '../lib/wallet';
14+import { randomPatq } from "../lib/lib";
15+import { ROUTE_NAMES } from "../lib/routeNames";
16+import { withHistory } from "../lib/history";
17+import { urbitWalletFromTicket } from "../lib/wallet";
1718
1819class Ticket extends React.Component {
1920 constructor(props) {
2021 super(props);
2122
2223 this.state = {
23− ticket: '',
24− pointName: '',
25− passphrase: '',
24+ ticket: "",
25+ pointName: "",
26+ passphrase: "",
2627 isUnlocking: false,
2728 };
2829
@@ -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;
@@ -71,7 +72,7 @@ class Ticket extends React.Component {
7172 return (
7273 <Row>
7374 <Col>
74− <H1>{'Authenticate'}</H1>
75+ <H1>{"Authenticate"}</H1>
7576
7677 <P>
7778 {`Please enter your point and Urbit master ticket here. This information is written on your Urbit HD paper wallets.`}
@@ -85,10 +86,11 @@ class Ticket extends React.Component {
8586 autoFocus
8687 placeholder={`e.g. ${phPoint}`}
8788 value={pointName}
88− onChange={this.handlePointNameInput}>
89− <InnerLabel>{'Point'}</InnerLabel>
89+ onChange={this.handlePointNameInput}
90+ >
91+ <InnerLabel>{"Point"}</InnerLabel>
9092 <ValidatedSigil
91− className={'tr-0 mt-05 mr-0 abs'}
93+ className={"tr-0 mt-05 mr-0 abs"}
9294 patp={pointName}
9395 size={68}
9496 margin={8}
@@ -103,12 +105,13 @@ class Ticket extends React.Component {
103105 name="ticket"
104106 placeholder={`e.g. ${phTick}`}
105107 value={ticket}
106− onChange={this.handleTicketInput}>
107− <InnerLabel>{'Ticket'}</InnerLabel>
108+ onChange={this.handleTicketInput}
109+ >
110+ <InnerLabel>{"Ticket"}</InnerLabel>
108111 </TicketInput>
109112
110113 <InputCaption>
111− {'If your wallet requires a passphrase, you may enter it below.'}
114+ {"If your wallet requires a passphrase, you may enter it below."}
112115 </InputCaption>
113116
114117 <Input
@@ -119,32 +122,30 @@ class Ticket extends React.Component {
119122 type="password"
120123 value={passphrase}
121124 autocomplete="off"
122− onChange={this.handlePassphraseInput}>
123− <InnerLabel>{'Passphrase'}</InnerLabel>
125+ onChange={this.handlePassphraseInput}
126+ >
127+ <InnerLabel>{"Passphrase"}</InnerLabel>
124128 </Input>
125129
126130 <Button
127− className={'mt-8'}
128− prop-size={'lg wide'}
131+ className={"mt-8"}
132+ prop-size={"lg wide"}
129133 disabled={this.state.isUnlocking || Just.hasInstance(wallet)}
130− onClick={() =>
131− this.walletFromTicket(ticket, pointName, passphrase)
132− }>
134+ onClick={() => this.walletFromTicket(ticket, pointName, passphrase)}
135+ >
133136 <span className="relative">
134137 {this.state.isUnlocking && <span className="btn-spinner" />}
135− {'Unlock Wallet →'}
138+ {"Unlock Wallet →"}
136139 </span>
137140 </Button>
138141
139142 <Button
140− className={'mt-4'}
141− prop-size={'xl wide'}
143+ className={"mt-4"}
144+ prop-size={"xl wide"}
142145 disabled={Nothing.hasInstance(wallet)}
143− onClick={() => {
144− popRoute();
145− pushRoute(ROUTE_NAMES.SHIPS);
146− }}>
147− {'Continue →'}
146+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}
147+ >
148+ {"Continue →"}
148149 </Button>
149150 </Col>
150151 </Row>
@@ -152,4 +153,4 @@ class Ticket extends React.Component {
152153 }
153154}
154155
155−export default Ticket;
156+export default withHistory(Ticket);
src/bridge/views/Transfer.js+27−25
@@ -1,8 +1,8 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import * as azimuth from 'azimuth-js';
4−import * as ob from 'urbit-ob';
5−import * as need from '../lib/need';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import * as azimuth from "azimuth-js";
4+import * as ob from "urbit-ob";
5+import * as need from "../lib/need";
66
77import {
88 Row,
@@ -12,13 +12,13 @@ import {
1212 ShowBlockie,
1313 P,
1414 Anchor,
15−} from '../components/Base';
16−import { AddressInput } from '../components/Base';
17−import StatelessTransaction from '../components/StatelessTransaction';
15+} from "../components/Base";
16+import { AddressInput } from "../components/Base";
17+import StatelessTransaction from "../components/StatelessTransaction";
1818
19−import { NETWORK_NAMES } from '../lib/network';
19+import { NETWORK_NAMES } from "../lib/network";
2020
21−import { isValidAddress } from '../lib/wallet';
21+import { isValidAddress } from "../lib/wallet";
2222
2323class Transfer extends React.Component {
2424 constructor(props) {
@@ -27,7 +27,7 @@ class Transfer extends React.Component {
2727 const issuingPoint = need.pointCursor(props);
2828
2929 this.state = {
30− receivingAddress: '',
30+ receivingAddress: "",
3131 issuingPoint: issuingPoint,
3232 };
3333
@@ -57,7 +57,7 @@ class Transfer extends React.Component {
5757 const txn = azimuth.ecliptic.setTransferProxy(
5858 validContracts,
5959 validPoint,
60− state.receivingAddress
60+ state.receivingAddress,
6161 );
6262
6363 return Maybe.Just(txn);
@@ -75,15 +75,15 @@ class Transfer extends React.Component {
7575
7676 const esdomain =
7777 props.networkType === NETWORK_NAMES.ROPSTEN
78− ? 'ropsten.etherscan.io'
79− : 'etherscan.io';
78+ ? "ropsten.etherscan.io"
79+ : "etherscan.io";
8080
8181 return (
8282 <Row>
8383 <Col>
8484 <H1>
85− {'Transfer'} <code>{` ${ob.patp(state.issuingPoint)} `}</code>
86− {'To a New Owner'}
85+ {"Transfer"} <code>{` ${ob.patp(state.issuingPoint)} `}</code>
86+ {"To a New Owner"}
8787 </H1>
8888
8989 <P>
@@ -93,25 +93,27 @@ class Transfer extends React.Component {
9393 </P>
9494
9595 <AddressInput
96− className={'mono mt-8'}
96+ className={"mono mt-8"}
9797 prop-size="lg"
9898 prop-format="innerLabel"
9999 value={state.receivingAddress}
100100 placeholder={`e.g. 0x84295d5e054d8cff5a22428b195f5a1615bd644f`}
101− onChange={v => this.handleAddressInput(v)}>
102− <InnerLabel>{'New ownership address'}</InnerLabel>
103− <ShowBlockie className={'mt-1'} address={state.receivingAddress} />
101+ onChange={v => this.handleAddressInput(v)}
102+ >
103+ <InnerLabel>{"New ownership address"}</InnerLabel>
104+ <ShowBlockie className={"mt-1"} address={state.receivingAddress} />
104105 </AddressInput>
105106
106107 <Anchor
107− className={'mt-1'}
108− prop-size={'s'}
108+ className={"mt-1"}
109+ prop-size={"s"}
109110 prop-disabled={
110111 !isValidAddress(state.receivingAddress) || !esvisible
111112 }
112− target={'_blank'}
113− href={`https://${esdomain}/address/${state.receivingAddress}`}>
114− {'View on Etherscan ↗'}
113+ target={"_blank"}
114+ href={`https://${esdomain}/address/${state.receivingAddress}`}
115+ >
116+ {"View on Etherscan ↗"}
115117 </Anchor>
116118
117119 <StatelessTransaction
src/bridge/views/Trezor.js+39−38
@@ -1,14 +1,15 @@
1−import * as bip32 from 'bip32';
2−import React from 'react';
3−import Maybe from 'folktale/maybe';
4−import { Button } from '../components/Base';
5−import { Row, Col, H1, P } from '../components/Base';
6−import { InnerLabel, Input, InnerLabelDropdown } from '../components/Base';
7−import TrezorConnect from 'trezor-connect';
8−import * as secp256k1 from 'secp256k1';
9−
10−import { TREZOR_PATH } from '../lib/trezor';
11−import { ROUTE_NAMES } from '../lib/router';
1+import * as bip32 from "bip32";
2+import React from "react";
3+import Maybe from "folktale/maybe";
4+import { Button } from "../components/Base";
5+import { Row, Col, H1, P } from "../components/Base";
6+import { InnerLabel, Input, InnerLabelDropdown } from "../components/Base";
7+import TrezorConnect from "trezor-connect";
8+import * as secp256k1 from "secp256k1";
9+
10+import { TREZOR_PATH } from "../lib/trezor";
11+import { ROUTE_NAMES } from "../lib/routeNames";
12+import { withHistory } from "../lib/history";
1213
1314class Trezor extends React.Component {
1415 constructor(props) {
@@ -26,7 +27,7 @@ class Trezor extends React.Component {
2627
2728 handleAccountSelection(account) {
2829 let hdpath = this.state.hdpath;
29− if (account !== 'custom') {
30+ if (account !== "custom") {
3031 hdpath = TREZOR_PATH.replace(/x/g, account);
3132 }
3233 this.setState({ account, hdpath });
@@ -41,14 +42,14 @@ class Trezor extends React.Component {
4142 const { hdpath } = this.state;
4243
4344 TrezorConnect.manifest({
44− email: 'bridge-trezor@urbit.org',
45− appUrl: 'https://github.com/urbit/bridge',
45+ email: "bridge-trezor@urbit.org",
46+ appUrl: "https://github.com/urbit/bridge",
4647 });
4748 TrezorConnect.getPublicKey({ path: hdpath }).then(info => {
4849 if (info.success === true) {
4950 const payload = info.payload;
50− const publicKey = Buffer.from(payload.publicKey, 'hex');
51− const chainCode = Buffer.from(payload.chainCode, 'hex');
51+ const publicKey = Buffer.from(payload.publicKey, "hex");
52+ const chainCode = Buffer.from(payload.chainCode, "hex");
5253 const pub = secp256k1.publicKeyConvert(publicKey, true);
5354 const hd = bip32.fromPublicKey(pub, chainCode);
5455 setWallet(Maybe.Just(hd));
@@ -60,19 +61,19 @@ 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
6768 let accountOptions = [
6869 {
69− title: 'Custom path',
70− value: 'custom',
70+ title: "Custom path",
71+ value: "custom",
7172 },
7273 ];
7374 for (let i = 0; i < 20; i++) {
7475 accountOptions.push({
75− title: 'Account #' + (i + 1),
76+ title: "Account #" + (i + 1),
7677 value: i,
7778 });
7879 }
@@ -85,14 +86,14 @@ class Trezor extends React.Component {
8586 prop-format="innerLabel"
8687 options={accountOptions}
8788 handleUpdate={handleAccountSelection}
88− title={'Account'}
89+ title={"Account"}
8990 currentSelectionTitle={accountTitle}
9091 fullWidth={true}
9192 />
9293 );
9394
9495 const pathSelection =
95− account !== 'custom' ? null : (
96+ account !== "custom" ? null : (
9697 <Input
9798 className="mt-8 pt-8 text-mono"
9899 prop-size="md"
@@ -100,15 +101,16 @@ class Trezor extends React.Component {
100101 name="hdpath"
101102 value={hdpath}
102103 autocomplete="off"
103− onChange={this.handleHdPathInput}>
104− <InnerLabel>{'HD Path'}</InnerLabel>
104+ onChange={this.handleHdPathInput}
105+ >
106+ <InnerLabel>{"HD Path"}</InnerLabel>
105107 </Input>
106108 );
107109
108110 return (
109111 <Row>
110− <Col className={'measure-md'}>
111− <H1>{'Authenticate With Your Trezor'}</H1>
112+ <Col className={"measure-md"}>
113+ <H1>{"Authenticate With Your Trezor"}</H1>
112114
113115 <P>
114116 {`Connect and authenticate to your Trezor. If you'd like
@@ -119,21 +121,20 @@ class Trezor extends React.Component {
119121 {pathSelection}
120122
121123 <Button
122− className={'mt-8'}
123− prop-size={'wide lg'}
124− onClick={this.pollDevice}>
125− {'Authenticate →'}
124+ className={"mt-8"}
125+ prop-size={"wide lg"}
126+ onClick={this.pollDevice}
127+ >
128+ {"Authenticate →"}
126129 </Button>
127130
128131 <Button
129− className={'mt-8'}
130− prop-size={'wide lg'}
132+ className={"mt-8"}
133+ prop-size={"wide lg"}
131134 disabled={Maybe.Nothing.hasInstance(wallet)}
132− onClick={() => {
133− popRoute();
134− pushRoute(ROUTE_NAMES.SHIPS);
135− }}>
136− {'Continue →'}
135+ onClick={() => history.popAndPush(ROUTE_NAMES.SHIPS)}
136+ >
137+ {"Continue →"}
137138 </Button>
138139 </Col>
139140 </Row>
@@ -141,4 +142,4 @@ class Trezor extends React.Component {
141142 }
142143}
143144
144−export default Trezor;
145+export default withHistory(Trezor);
src/bridge/views/ViewPoint.js+22−20
@@ -1,19 +1,20 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import * as ob from 'urbit-ob';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import * as ob from "urbit-ob";
44
5−import { Button } from '../components/Base';
6−import { Row, Col, H1, P } from '../components/Base';
7−import { InnerLabel, PointInput, ValidatedSigil } from '../components/Base';
5+import { Button } from "../components/Base";
6+import { Row, Col, H1, P } from "../components/Base";
7+import { 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 "../lib/history";
1011
1112class ViewPoint extends React.Component {
1213 constructor(props) {
1314 super(props);
1415
1516 this.state = {
16− pointName: '',
17+ pointName: "",
1718 };
1819
1920 this.handlePointInput = this.handlePointInput.bind(this);
@@ -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 //
@@ -42,9 +43,9 @@ class ViewPoint extends React.Component {
4243 return (
4344 <Row>
4445 <Col>
45− <H1>{'View a Point'}</H1>
46+ <H1>{"View a Point"}</H1>
4647
47− <P>{'Enter a point name to view its public information.'}</P>
48+ <P>{"Enter a point name to view its public information."}</P>
4849
4950 <PointInput
5051 autoFocus
@@ -53,10 +54,11 @@ class ViewPoint extends React.Component {
5354 className="mono"
5455 placeholder="e.g. ~zod"
5556 onChange={this.handlePointInput}
56− value={pointName}>
57− <InnerLabel>{'Point Name'}</InnerLabel>
57+ value={pointName}
58+ >
59+ <InnerLabel>{"Point Name"}</InnerLabel>
5860 <ValidatedSigil
59− className={'tr-0 mt-05 mr-0 abs'}
61+ className={"tr-0 mt-05 mr-0 abs"}
6062 patp={pointName}
6163 show
6264 size={68}
@@ -65,14 +67,14 @@ class ViewPoint extends React.Component {
6567 </PointInput>
6668
6769 <Button
68− className={'mt-8'}
70+ className={"mt-8"}
6971 disabled={valid === false}
7072 onClick={() => {
7173 setPointCursor(Maybe.Just(ob.patp2dec(pointName)));
72− popRoute();
73− pushRoute(ROUTE_NAMES.SHIP);
74− }}>
75− {'Continue →'}
74+ history.popAndPush(ROUTE_NAMES.SHIP);
75+ }}
76+ >
77+ {"Continue →"}
7678 </Button>
7779 </Col>
7880 </Row>
@@ -80,4 +82,4 @@ class ViewPoint extends React.Component {
8082 }
8183}
8284
83−export default ViewPoint;
85+export default withHistory(ViewPoint);
src/bridge/views/Wallet.js+27−25
@@ -1,11 +1,12 @@
1−import Maybe from 'folktale/maybe';
2−import React from 'react';
3−import { Button, H1, P } from '../components/Base';
4−import { InnerLabelDropdown } from '../components/Base';
5−import { Row, Col } from '../components/Base';
1+import Maybe from "folktale/maybe";
2+import React from "react";
3+import { Button, H1, P } from "../components/Base";
4+import { InnerLabelDropdown } from "../components/Base";
5+import { Row, Col } from "../components/Base";
66
7−import { ROUTE_NAMES } from '../lib/router';
8−import { WALLET_NAMES, renderWalletType } from '../lib/wallet';
7+import { ROUTE_NAMES } from "../lib/routeNames";
8+import { withHistory } from "../lib/history";
9+import { WALLET_NAMES, renderWalletType } from "../lib/wallet";
910
1011class Wallet extends React.Component {
1112 componentDidMount() {
@@ -16,37 +17,37 @@ class Wallet extends React.Component {
1617 getWalletOptions() {
1718 return [
1819 {
19− title: 'Urbit Master Ticket',
20+ title: "Urbit Master Ticket",
2021 value: WALLET_NAMES.TICKET,
2122 },
2223 {
23− title: 'Urbit Master Ticket (Shards)',
24+ title: "Urbit Master Ticket (Shards)",
2425 value: WALLET_NAMES.SHARDS,
2526 },
2627 {
27− type: 'divider',
28+ type: "divider",
2829 },
2930 {
30− title: 'Ledger',
31+ title: "Ledger",
3132 value: WALLET_NAMES.LEDGER,
3233 },
3334 {
34− title: 'Trezor',
35+ title: "Trezor",
3536 value: WALLET_NAMES.TREZOR,
3637 },
3738 {
38− type: 'divider',
39+ type: "divider",
3940 },
4041 {
41− title: 'BIP39 Mnemonic',
42+ title: "BIP39 Mnemonic",
4243 value: WALLET_NAMES.MNEMONIC,
4344 },
4445 {
45− title: 'Ethereum Private Key',
46+ title: "Ethereum Private Key",
4647 value: WALLET_NAMES.PRIVATE_KEY,
4748 },
4849 {
49− title: 'Ethereum Keystore File',
50+ title: "Ethereum Keystore File",
5051 value: WALLET_NAMES.KEYSTORE,
5152 },
5253 ];
@@ -59,7 +60,7 @@ class Wallet extends React.Component {
5960 return (
6061 <Row>
6162 <Col>
62− <H1>{'Unlock a Wallet'}</H1>
63+ <H1>{"Unlock a Wallet"}</H1>
6364
6465 <P>
6566 {`To manage your assets, you need to unlock a wallet.
@@ -67,17 +68,17 @@ class Wallet extends React.Component {
6768 </P>
6869
6970 <InnerLabelDropdown
70− className={'mb-10 mt-6'}
71− title={'Wallet Type:'}
71+ className={"mb-10 mt-6"}
72+ title={"Wallet Type:"}
7273 options={walletOptions}
7374 handleUpdate={props.setWalletType}
7475 currentSelectionTitle={renderWalletType(props.walletType)}
7576 />
7677
7778 <Button
78− className={'mt-10'}
79+ 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
@@ -92,10 +93,11 @@ class Wallet extends React.Component {
9293 ? ROUTE_NAMES.PRIVATE_KEY
9394 : props.walletType === WALLET_NAMES.KEYSTORE
9495 ? ROUTE_NAMES.KEYSTORE
95− : ROUTE_NAMES.DEFAULT
96+ : ROUTE_NAMES.DEFAULT,
9697 )
97− }>
98− {'Continue →'}
98+ }
99+ >
100+ {"Continue →"}
99101 </Button>
100102 </Col>
101103 </Row>
@@ -103,4 +105,4 @@ class Wallet extends React.Component {
103105 }
104106}
105107
106−export default Wallet;
108+export default withHistory(Wallet);
src/walletgen/lib/lib.js+9−9
@@ -1,7 +1,7 @@
1−import * as ob from 'urbit-ob';
2−import * as kg from '../../../node_modules/urbit-key-generation/dist/index';
3−import * as more from 'more-entropy';
4−import lodash from 'lodash';
1+import * as ob from "urbit-ob";
2+import * as kg from "urbit-key-generation/dist/index";
3+import * as more from "more-entropy";
4+import lodash from "lodash";
55import {
66 MIN_STAR,
77 MIN_PLANET,
@@ -9,7 +9,7 @@ import {
99 GALAXY_ENTROPY_BITS,
1010 STAR_ENTROPY_BITS,
1111 PLANET_ENTROPY_BITS,
12−} from './constants';
12+} from "./constants";
1313
1414const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8;
1515
@@ -35,9 +35,9 @@ const makeTicket = point => {
3535 const more = lodash.flatMap(desired, arr => arr[0] ^ arr[1]);
3636 const entropy = lodash.zipWith(some, more, (x, y) => x ^ y);
3737 const buf = Buffer.from(entropy);
38− const patq = ob.hex2patq(buf.toString('hex'));
38+ const patq = ob.hex2patq(buf.toString("hex"));
3939 resolve(patq);
40− reject('Entropy generation failed');
40+ reject("Entropy generation failed");
4141 });
4242 });
4343};
@@ -48,7 +48,7 @@ const generateWallet = async (point, ticket, boot) => {
4848 ticket: ticket,
4949 seedSize: SEED_LENGTH_BYTES,
5050 ship: point,
51− password: '',
51+ password: "",
5252 revisions: {},
5353 boot: boot,
5454 };
@@ -57,7 +57,7 @@ const generateWallet = async (point, ticket, boot) => {
5757
5858 // This is here to notify anyone who opens console because the thread
5959 // hangs, blocking UI updates so this cannot be done in the UI
60− console.log('Generating Wallet for point address: ', point);
60+ console.log("Generating Wallet for point address: ", point);
6161
6262 return wallet;
6363};