on master help sign in
Merge pull request #157 from urbit/feat/extract-wallet
2bfe551Matt Condon
src/bridge/Bridge.js+50−129
@@ -1,44 +1,50 @@
1import React from 'react';
1import React from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3−import { includes } from 'lodash';
4
3
5import Footer from './components/Footer';
4import Footer from './components/Footer';
6import Header from './components/Header';
5import Header from './components/Header';
7import { Container, Row, Col } from './components/Base';
6import { Container, Row, Col } from './components/Base';
8
7
8+import nest from './lib/nest';
9import { router } from './lib/router';
9import { router } from './lib/router';
10import { ROUTE_NAMES } from './lib/routeNames';
10import { ROUTE_NAMES } from './lib/routeNames';
11−import { HistoryProvider, withHistory, useHistory } from './store/history';
12−import { TxnConfirmationsProvider } from './store/txnConfirmations';
13import { NETWORK_TYPES } from './lib/network';
11import { NETWORK_TYPES } from './lib/network';
14−import {
15− WALLET_NAMES,
16− DEFAULT_HD_PATH,
17− walletFromMnemonic,
12+import { walletFromMnemonic } from './lib/wallet';
18− addressFromSecp256k1Public,
19−} from './lib/wallet';
20−import { BRIDGE_ERROR } from './lib/error';
21import { isDevelopment } from './lib/flags';
13import { isDevelopment } from './lib/flags';
22−import nest from './lib/nest';
14+
15+import { HistoryProvider, useHistory } from './store/history';
16+import { TxnConfirmationsProvider } from './store/txnConfirmations';
23import { OnlineProvider } from './store/online';
17import { OnlineProvider } from './store/online';
24import { NetworkProvider } from './store/network';
18import { NetworkProvider } from './store/network';
19+import { WalletProvider } from './store/wallet';
25
20
21+const kInitialNetworkType = isDevelopment
22+ ? NETWORK_TYPES.LOCAL
23+ : NETWORK_TYPES.MAINNET;
24+
26−// NB(shrugs): toggle this variable to use the default local state.
25+// NB(shrugs): toggle these variables to change the default local state.
27−// don't commit changes to this line, but there shouldn't be a problem
26+// try not to commit changes to this line, but there shouldn't be a problem
28// if you do because we'll never stub on a production build.
27// if you do because we'll never stub on a production build.
29const shouldStubLocal = false;
28const shouldStubLocal = false;
30−const isStubbed = isDevelopment && shouldStubLocal;
29+const kIsStubbed = isDevelopment && shouldStubLocal;
31−const kInitialRoutes = isStubbed
30+const kInitialRoutes = kIsStubbed
32 ? [
31 ? [
33− { name: ROUTE_NAMES.SHIPS },
34− { name: ROUTE_NAMES.WALLET },
35− { name: ROUTE_NAMES.NETWORK },
36 { name: ROUTE_NAMES.LANDING },
32 { name: ROUTE_NAMES.LANDING },
33+ { name: ROUTE_NAMES.NETWORK },
34+ { name: ROUTE_NAMES.WALLET },
35+ { name: ROUTE_NAMES.SHIPS },
37 ]
36 ]
38 : [{ name: ROUTE_NAMES.LANDING }];
37 : [{ name: ROUTE_NAMES.LANDING }];
39−const kInitialNetworkType = isDevelopment
38+
40− ? NETWORK_TYPES.LOCAL
39+const kInitialWallet = kIsStubbed
41− : NETWORK_TYPES.MAINNET;
40+ ? walletFromMnemonic(
41+ process.env.REACT_APP_DEV_MNEMONIC,
42+ process.env.REACT_APP_HD_PATH
43+ )
44+ : undefined;
45+const kInitialMnemonic = kIsStubbed
46+ ? process.env.REACT_APP_DEV_MNEMONIC
47+ : Nothing();
42
48
43// the router itself is just a component that renders a specific view
49// the router itself is just a component that renders a specific view
44// depending on the history
50// depending on the history
@@ -49,23 +55,26 @@ const Router = function(props) {
49 return <Route {...props} />;
55 return <Route {...props} />;
50};
56};
51
57
52−// NB(shrugs): separate component because it needs withHistory
58+// NB(shrugs): separate component because it needs useHistory
53// this will be must better structured as part of the UI overhaul
59// this will be must better structured as part of the UI overhaul
54−const VariableWidthColumn = withHistory(({ history, children }) => (
60+const VariableWidthColumn = function({ children }) {
61+ const history = useHistory();
55 // For the invite acceptance flow, widen the screen to use the full
62 // For the invite acceptance flow, widen the screen to use the full
56 // container, and hide the breadcrumbs
63 // container, and hide the breadcrumbs
64+ return (
57− <Col
65+ <Col
58− className={
66+ className={
59− history.includes(ROUTE_NAMES.INVITE_TICKET)
67+ history.includes(ROUTE_NAMES.INVITE_TICKET)
60− ? 'col-md-12'
68+ ? 'col-md-12'
61− : 'col-md-offset-1 col-md-10'
69+ : 'col-md-offset-1 col-md-10'
62− }
70+ }
63− style={
71+ style={
64− history.includes(ROUTE_NAMES.INVITE_TICKET) ? {} : { maxWidth: '620px' }
72+ history.includes(ROUTE_NAMES.INVITE_TICKET) ? {} : { maxWidth: '620px' }
65− }>
73+ }>
66− {children}
74+ {children}
67− </Col>
75+ </Col>
68−));
76+ );
77+};
69
78
70// nest all of the providers within each other to avoid hella depth
79// nest all of the providers within each other to avoid hella depth
71const AllProviders = nest([
80const AllProviders = nest([
@@ -73,33 +82,14 @@ const AllProviders = nest([
73 TxnConfirmationsProvider,
82 TxnConfirmationsProvider,
74 OnlineProvider,
83 OnlineProvider,
75 NetworkProvider,
84 NetworkProvider,
85+ WalletProvider,
76]);
86]);
77
87
78class Bridge extends React.Component {
88class Bridge extends React.Component {
79 constructor(props) {
89 constructor(props) {
80 super(props);
90 super(props);
81
91
82− // NB (jtobin):
83− //
84− // Note that the 'wallet' prop has type depending on the 'walletType' prop.
85− // These could be bound together in a single structure (so that
86− // 'walletType' acted more explicitly as a data constructor of sorts) but
87− // it doesn't necessarily help us much, since we branch on 'walletType'
88− // before setting 'wallet'.
89− //
90− // Wallets are always wrapped in Maybe. For most authentication
91− // mechanisms, Maybe wraps a BIP32 HD wallet; for Ethereum private keys,
92− // JSON keystore files, and Metamask authentication, it wraps an
93− // 'EthereumWallet'.
94−
95 this.state = {
92 this.state = {
96− // wallet
97− walletType: WALLET_NAMES.MNEMONIC,
98− wallet: Nothing(),
99− walletHdPath: DEFAULT_HD_PATH,
100− // urbit wallet-related
101− urbitWallet: Nothing(),
102− authMnemonic: Nothing(),
103 networkSeedCache: null,
93 networkSeedCache: null,
104 // point
94 // point
105 pointCursor: Nothing(),
95 pointCursor: Nothing(),
@@ -109,11 +99,6 @@ class Bridge extends React.Component {
109 txnConfirmations: {},
99 txnConfirmations: {},
110 };
100 };
111
101
112− this.setWalletType = this.setWalletType.bind(this);
113− this.setWallet = this.setWallet.bind(this);
114− this.setWalletHdPath = this.setWalletHdPath.bind(this);
115− this.setUrbitWallet = this.setUrbitWallet.bind(this);
116− this.setAuthMnemonic = this.setAuthMnemonic.bind(this);
117 this.setPointCursor = this.setPointCursor.bind(this);
102 this.setPointCursor = this.setPointCursor.bind(this);
118 this.setTxnHashCursor = this.setTxnHashCursor.bind(this);
103 this.setTxnHashCursor = this.setTxnHashCursor.bind(this);
119 this.setNetworkSeedCache = this.setNetworkSeedCache.bind(this);
104 this.setNetworkSeedCache = this.setNetworkSeedCache.bind(this);
@@ -125,16 +110,9 @@ class Bridge extends React.Component {
125 //
110 //
126 // If running in development, the following can be tweaked to get you set
111 // If running in development, the following can be tweaked to get you set
127 // up with a desirable initial state.
112 // up with a desirable initial state.
128− if (isStubbed) {
113+ if (kIsStubbed) {
129− const mnemonic = process.env.REACT_APP_DEV_MNEMONIC;
130− const hdpath = process.env.REACT_APP_HD_PATH;
131−
132 this.setState({
114 this.setState({
133 pointCursor: Just(0),
115 pointCursor: Just(0),
134− walletType: WALLET_NAMES.MNEMONIC,
135− wallet: walletFromMnemonic(mnemonic, hdpath),
136− urbitWallet: Nothing(),
137− authMnemonic: Just(mnemonic),
138 });
116 });
139 }
117 }
140 }
118 }
@@ -146,50 +124,6 @@ class Bridge extends React.Component {
146 });
124 });
147 }
125 }
148
126
149− setWalletType(symbol) {
150− if (includes(WALLET_NAMES, symbol)) {
151− this.setState({
152− walletType: symbol,
153− });
154− } else {
155− throw BRIDGE_ERROR.INVALID_WALLET_TYPE;
156− }
157− }
158−
159− setWallet(wallet) {
160− wallet.map(wal => {
161− wal.address = wal.address || addressFromSecp256k1Public(wal.publicKey);
162− return wal;
163− });
164− this.setState({ wallet });
165− }
166−
167− setWalletHdPath(walletHdPath) {
168− this.setState({ walletHdPath });
169− }
170−
171− // also sets wallet to ownership address
172− setUrbitWallet(urbitWallet) {
173− let wallet = this.state.wallet;
174− if (Just.hasInstance(urbitWallet)) {
175− const mnemonic = urbitWallet.value.ownership.seed;
176− wallet = walletFromMnemonic(
177− mnemonic,
178− DEFAULT_HD_PATH,
179− urbitWallet.value.meta.passphrase
180− );
181− wallet.map(wal => {
182− wal.address = urbitWallet.value.ownership.keys.address;
183− return wal;
184− });
185− }
186− this.setState({ urbitWallet, wallet });
187− }
188−
189− setAuthMnemonic(authMnemonic) {
190− this.setState({ authMnemonic });
191− }
192−
193 setPointCursor(pointCursor) {
127 setPointCursor(pointCursor) {
194 this.setState({ pointCursor });
128 this.setState({ pointCursor });
195 }
129 }
@@ -206,11 +140,6 @@ class Bridge extends React.Component {
206
140
207 render() {
141 render() {
208 const {
142 const {
209− walletType,
210− walletHdPath,
211− wallet,
212− urbitWallet,
213− authMnemonic,
214 networkSeedCache,
143 networkSeedCache,
215 networkRevisionCache,
144 networkRevisionCache,
216 pointCursor,
145 pointCursor,
@@ -221,24 +150,16 @@ class Bridge extends React.Component {
221 return (
150 return (
222 <AllProviders
151 <AllProviders
223 initialRoutes={kInitialRoutes}
152 initialRoutes={kInitialRoutes}
224− initialNetworkType={kInitialNetworkType}>
153+ initialNetworkType={kInitialNetworkType}
154+ initialWallet={kInitialWallet}
155+ initialMnemonic={kInitialMnemonic}>
225 <Container>
156 <Container>
226 <Row>
157 <Row>
227 <VariableWidthColumn>
158 <VariableWidthColumn>
228− <Header wallet={wallet} pointCursor={pointCursor} />
159+ <Header pointCursor={pointCursor} />
229
160
230 <Row className={'row wrapper'}>
161 <Row className={'row wrapper'}>
231 <Router
162 <Router
232− setWalletType={this.setWalletType}
233− setWalletHdPath={this.setWalletHdPath}
234− setWallet={this.setWallet}
235− walletType={walletType}
236− walletHdPath={walletHdPath}
237− wallet={wallet}
238− urbitWallet={urbitWallet}
239− setUrbitWallet={this.setUrbitWallet}
240− authMnemonic={authMnemonic}
241− setAuthMnemonic={this.setAuthMnemonic}
242 setPointCursor={this.setPointCursor}
163 setPointCursor={this.setPointCursor}
243 addToPointCache={this.addToPointCache}
164 addToPointCache={this.addToPointCache}
244 pointCursor={pointCursor}
165 pointCursor={pointCursor}
src/bridge/components/Header.js+7−2
@@ -6,10 +6,15 @@ import { ROUTE_NAMES } from '../lib/routeNames';
6import { useHistory } from '../store/history';
6import { useHistory } from '../store/history';
7import { isLast } from '../lib/lib';
7import { isLast } from '../lib/lib';
8import { useNetwork } from '../store/network';
8import { useNetwork } from '../store/network';
9+import { useWallet } from '../store/wallet';
9
10
11+// hook to create a breadcrumb builder function based on the current
12+// global states
10function useRouteBreadcrumbBuilder(props) {
13function useRouteBreadcrumbBuilder(props) {
11 const { networkType } = useNetwork();
14 const { networkType } = useNetwork();
15+ const { wallet } = useWallet();
16+
12− return getRouteBreadcrumb({ ...props, networkType });
17+ return getRouteBreadcrumb({ ...props, networkType, wallet });
13}
18}
14
19
15function Crumbs(props) {
20function Crumbs(props) {
@@ -48,7 +53,7 @@ function Header(props) {
48 if (showCrumbs) {
53 if (showCrumbs) {
49 return (
54 return (
50 <div className={'flex items-center h-10'}>
55 <div className={'flex items-center h-10'}>
51− <Crumbs wallet={props.wallet} pointCursor={props.pointCursor} />
56+ <Crumbs pointCursor={props.pointCursor} />
52 </div>
57 </div>
53 );
58 );
54 }
59 }
src/bridge/components/Passport.js+3−1
@@ -8,6 +8,8 @@ import { Button } from './Base';
8
8
9import { WALLET_STATES } from '../lib/invite';
9import { WALLET_STATES } from '../lib/invite';
10import { downloadWallet } from '../lib/invite';
10import { downloadWallet } from '../lib/invite';
11+import { compose } from '../lib/lib';
12+import { withWallet } from '../store/wallet';
11
13
12class Passport extends React.Component {
14class Passport extends React.Component {
13 constructor(props) {
15 constructor(props) {
@@ -231,4 +233,4 @@ Passport.defaultProps = {
231 wallet: null,
233 wallet: null,
232};
234};
233
235
234−export default Passport;
236+export default compose(withWallet)(Passport);
src/bridge/components/StatelessTransaction.js+3−1
@@ -24,6 +24,7 @@ import {
24import { withTxnConfirmations } from '../store/txnConfirmations';
24import { withTxnConfirmations } from '../store/txnConfirmations';
25import { withNetwork } from '../store/network';
25import { withNetwork } from '../store/network';
26import { withHistory } from '../store/history';
26import { withHistory } from '../store/history';
27+import { withWallet } from '../store/wallet';
27
28
28const SUBMISSION_STATES = {
29const SUBMISSION_STATES = {
29 PROMPT: 'Send transaction',
30 PROMPT: 'Send transaction',
@@ -584,5 +585,6 @@ class StatelessTransaction extends React.Component {
584export default compose(
585export default compose(
585 withNetwork,
586 withNetwork,
586 withTxnConfirmations,
587 withTxnConfirmations,
587− withHistory
588+ withHistory,
589+ withWallet
588)(StatelessTransaction);
590)(StatelessTransaction);
src/bridge/lib/invite.js+2−2
@@ -12,7 +12,7 @@ import saveAs from 'file-saver';
12import { sendSignedTransaction } from './txn';
12import { sendSignedTransaction } from './txn';
13import { BRIDGE_ERROR } from '../lib/error';
13import { BRIDGE_ERROR } from '../lib/error';
14import { attemptSeedDerivation } from './keys';
14import { attemptSeedDerivation } from './keys';
15−import { addHexPrefix, WALLET_NAMES } from './wallet';
15+import { addHexPrefix, WALLET_TYPES } from './wallet';
16
16
17const INVITE_STAGES = {
17const INVITE_STAGES = {
18 INVITE_LOGIN: 'invite login',
18 INVITE_LOGIN: 'invite login',
@@ -213,7 +213,7 @@ async function startTransactions(args) {
213 // configure networking public keys
213 // configure networking public keys
214 //TODO feels like more of this logic should live in a lib?
214 //TODO feels like more of this logic should live in a lib?
215 let networkSeed = await attemptSeedDerivation(true, {
215 let networkSeed = await attemptSeedDerivation(true, {
216− walletType: WALLET_NAMES.TICKET,
216+ walletType: WALLET_TYPES.TICKET,
217 urbitWallet: Just(realWallet),
217 urbitWallet: Just(realWallet),
218 pointCursor: Just(point),
218 pointCursor: Just(point),
219 pointCache: { [point]: { keyRevisionNumber: 0 } },
219 pointCache: { [point]: { keyRevisionNumber: 0 } },
src/bridge/lib/keys.js+4−5
@@ -7,7 +7,7 @@ import * as serial from '../nockjs/serial';
7import * as kg from 'urbit-key-generation/dist/index';
7import * as kg from 'urbit-key-generation/dist/index';
8
8
9import { BRIDGE_ERROR } from './error';
9import { BRIDGE_ERROR } from './error';
10−import { WALLET_NAMES, eqAddr } from './wallet';
10+import { WALLET_TYPES, eqAddr } from './wallet';
11
11
12// ctsy joemfb
12// ctsy joemfb
13const b64 = buf => {
13const b64 = buf => {
@@ -67,8 +67,7 @@ const genKey = (networkSeed, point, revision) => {
67// 'next' refers to setting the next set of keys
67// 'next' refers to setting the next set of keys
68// if false, we use revision - 1
68// if false, we use revision - 1
69const attemptSeedDerivation = async (next, args) => {
69const attemptSeedDerivation = async (next, args) => {
70− const { walletType } = args;
71− const { urbitWallet, authMnemonic } = args;
70+ const { walletType, urbitWallet, authMnemonic } = args;
72
71
73 // NB (jtobin):
72 // NB (jtobin):
74 //
73 //
@@ -84,7 +83,7 @@ const attemptSeedDerivation = async (next, args) => {
84
83
85 let managementSeed = '';
84 let managementSeed = '';
86
85
87− const ticketLike = [WALLET_NAMES.TICKET, WALLET_NAMES.SHARDS];
86+ const ticketLike = [WALLET_TYPES.TICKET, WALLET_TYPES.SHARDS];
88
87
89 if (ticketLike.includes(walletType)) {
88 if (ticketLike.includes(walletType)) {
90 const uwal = urbitWallet.matchWith({
89 const uwal = urbitWallet.matchWith({
@@ -95,7 +94,7 @@ const attemptSeedDerivation = async (next, args) => {
95 });
94 });
96
95
97 managementSeed = uwal.management.seed;
96 managementSeed = uwal.management.seed;
98− } else if (walletType === WALLET_NAMES.MNEMONIC) {
97+ } else if (walletType === WALLET_TYPES.MNEMONIC) {
99 const walProxy = need.address(args);
98 const walProxy = need.address(args);
100
99
101 const mnemonic = authMnemonic.matchWith({
100 const mnemonic = authMnemonic.matchWith({
src/bridge/lib/txn.js+4−4
@@ -7,7 +7,7 @@ import { BRIDGE_ERROR } from '../lib/error';
7import { NETWORK_TYPES } from '../lib/network';
7import { NETWORK_TYPES } from '../lib/network';
8import { ledgerSignTransaction } from '../lib/ledger';
8import { ledgerSignTransaction } from '../lib/ledger';
9import { trezorSignTransaction } from '../lib/trezor';
9import { trezorSignTransaction } from '../lib/trezor';
10−import { WALLET_NAMES, addHexPrefix } from '../lib/wallet';
10+import { WALLET_TYPES, addHexPrefix } from '../lib/wallet';
11
11
12const TXN_PURPOSE = {
12const TXN_PURPOSE = {
13 SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
13 SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
@@ -96,7 +96,7 @@ const signTransaction = async config => {
96 ];
96 ];
97
97
98 const needEip155Params =
98 const needEip155Params =
99− walletType === WALLET_NAMES.LEDGER &&
99+ walletType === WALLET_TYPES.LEDGER &&
100 defaultEip155Networks.includes(networkType);
100 defaultEip155Networks.includes(networkType);
101
101
102 const signingParams = needEip155Params
102 const signingParams = needEip155Params
@@ -121,9 +121,9 @@ const signTransaction = async config => {
121
121
122 const stx = new Tx(utx);
122 const stx = new Tx(utx);
123
123
124− if (walletType === WALLET_NAMES.LEDGER) {
124+ if (walletType === WALLET_TYPES.LEDGER) {
125 await ledgerSignTransaction(stx, walletHdPath);
125 await ledgerSignTransaction(stx, walletHdPath);
126− } else if (walletType === WALLET_NAMES.TREZOR) {
126+ } else if (walletType === WALLET_TYPES.TREZOR) {
127 await trezorSignTransaction(stx, walletHdPath);
127 await trezorSignTransaction(stx, walletHdPath);
128 } else {
128 } else {
129 stx.sign(sec);
129 stx.sign(sec);
src/bridge/lib/wallet.js+9−9
@@ -14,7 +14,7 @@ const ETH_ZERO_ADDR = '0x0000000000000000000000000000000000000000';
14const CURVE_ZERO_ADDR =
14const CURVE_ZERO_ADDR =
15 '0x0000000000000000000000000000000000000000000000000000000000000000';
15 '0x0000000000000000000000000000000000000000000000000000000000000000';
16
16
17−const WALLET_NAMES = {
17+const WALLET_TYPES = {
18 MNEMONIC: Symbol('MNEMONIC'),
18 MNEMONIC: Symbol('MNEMONIC'),
19 TICKET: Symbol('TICKET'),
19 TICKET: Symbol('TICKET'),
20 SHARDS: Symbol('SHARDS'),
20 SHARDS: Symbol('SHARDS'),
@@ -32,19 +32,19 @@ function EthereumWallet(privateKey) {
32}
32}
33
33
34const renderWalletType = wallet =>
34const renderWalletType = wallet =>
35− wallet === WALLET_NAMES.MNEMONIC
35+ wallet === WALLET_TYPES.MNEMONIC
36 ? 'Mnemonic'
36 ? 'Mnemonic'
37− : wallet === WALLET_NAMES.TICKET
37+ : wallet === WALLET_TYPES.TICKET
38 ? 'Ticket'
38 ? 'Ticket'
39− : wallet === WALLET_NAMES.SHARDS
39+ : wallet === WALLET_TYPES.SHARDS
40 ? 'Ticket Shards'
40 ? 'Ticket Shards'
41− : wallet === WALLET_NAMES.LEDGER
41+ : wallet === WALLET_TYPES.LEDGER
42 ? 'Ledger'
42 ? 'Ledger'
43− : wallet === WALLET_NAMES.TREZOR
43+ : wallet === WALLET_TYPES.TREZOR
44 ? 'Trezor'
44 ? 'Trezor'
45− : wallet === WALLET_NAMES.PRIVATE_KEY
45+ : wallet === WALLET_TYPES.PRIVATE_KEY
46 ? 'Private Key'
46 ? 'Private Key'
47− : wallet === WALLET_NAMES.KEYSTORE
47+ : wallet === WALLET_TYPES.KEYSTORE
48 ? 'Keystore File'
48 ? 'Keystore File'
49 : 'Wallet';
49 : 'Wallet';
50
50
@@ -135,7 +135,7 @@ const walletFromMnemonic = (mnemonic, hdpath, passphrase) => {
135
135
136export {
136export {
137 DEFAULT_HD_PATH,
137 DEFAULT_HD_PATH,
138− WALLET_NAMES,
138+ WALLET_TYPES,
139 ETH_ZERO_ADDR,
139 ETH_ZERO_ADDR,
140 CURVE_ZERO_ADDR,
140 CURVE_ZERO_ADDR,
141 renderWalletType,
141 renderWalletType,
src/bridge/store/wallet.jsnew file+114
@@ -0,0 +1,114 @@
1+import React, { createContext, forwardRef, useContext, useState } from 'react';
2+import Maybe from 'folktale/maybe';
3+import { includes } from 'lodash';
4+
5+import {
6+ WALLET_TYPES,
7+ DEFAULT_HD_PATH,
8+ addressFromSecp256k1Public,
9+ walletFromMnemonic,
10+} from '../lib/wallet';
11+import { BRIDGE_ERROR } from '../lib/error';
12+
13+export const WalletContext = createContext(null);
14+
15+// NB (jtobin):
16+//
17+// Note that the 'wallet' prop has type depending on the 'walletType' prop.
18+// These could be bound together in a single structure (so that
19+// 'walletType' acted more explicitly as a data constructor of sorts) but
20+// it doesn't necessarily help us much, since we branch on 'walletType'
21+// before setting 'wallet'.
22+//
23+// Wallets are always wrapped in Maybe. For most authentication
24+// mechanisms, Maybe wraps a BIP32 HD wallet; for Ethereum private keys,
25+// JSON keystore files, and Metamask authentication, it wraps an
26+// 'EthereumWallet'.
27+function _useWallet(
28+ initialWallet = Maybe.Nothing(),
29+ initialMnemonic = Maybe.Nothing()
30+) {
31+ const [walletType, _setWalletType] = useState(WALLET_TYPES.MNEMONIC);
32+ const [walletHdPath, setWalletHdPath] = useState(DEFAULT_HD_PATH);
33+ const [wallet, _setWallet] = useState(initialWallet);
34+ const [urbitWallet, _setUrbitWallet] = useState(Maybe.Nothing());
35+ const [authMnemonic, setAuthMnemonic] = useState(initialMnemonic);
36+
37+ const setWalletType = walletType => {
38+ if (!includes(WALLET_TYPES, walletType)) {
39+ throw BRIDGE_ERROR.INVALID_WALLET_TYPE;
40+ }
41+
42+ _setWalletType(walletType);
43+ };
44+
45+ const setWallet = wallet => {
46+ // force that public addresses are derived for each wallet
47+ wallet.map(wal => {
48+ wal.address = wal.address || addressFromSecp256k1Public(wal.publicKey);
49+ return wal;
50+ });
51+
52+ _setWallet(wallet);
53+ };
54+
55+ const setUrbitWallet = urbitWallet => {
56+ if (Maybe.Just.hasInstance(urbitWallet)) {
57+ // when an urbit wallet is set, also derive
58+ // a normal bip32 wallet using the ownership address
59+ const wallet = walletFromMnemonic(
60+ urbitWallet.value.ownership.seed,
61+ DEFAULT_HD_PATH,
62+ urbitWallet.value.meta.passphrase
63+ );
64+
65+ // manually set the public address
66+ wallet.map(wal => {
67+ wal.address = urbitWallet.value.ownership.keys.address;
68+ return wal;
69+ });
70+
71+ setWallet(wallet);
72+ }
73+
74+ _setUrbitWallet(urbitWallet);
75+ };
76+
77+ return {
78+ //
79+ walletType,
80+ setWalletType,
81+ //
82+ walletHdPath,
83+ setWalletHdPath,
84+ //
85+ wallet,
86+ setWallet,
87+ //
88+ urbitWallet,
89+ setUrbitWallet,
90+ authMnemonic,
91+ setAuthMnemonic,
92+ };
93+}
94+
95+export function WalletProvider({ initialWallet, initialMnemonic, children }) {
96+ const wallet = _useWallet(initialWallet, initialMnemonic);
97+
98+ return (
99+ <WalletContext.Provider value={wallet}>{children}</WalletContext.Provider>
100+ );
101+}
102+
103+// Hook version
104+export function useWallet() {
105+ return useContext(WalletContext);
106+}
107+
108+// HOC version
109+export const withWallet = Component =>
110+ forwardRef((props, ref) => (
111+ <WalletContext.Consumer>
112+ {wallet => <Component ref={ref} {...wallet} {...props} />}
113+ </WalletContext.Consumer>
114+ ));
src/bridge/views/AcceptTransfer.js+5−1
@@ -16,6 +16,7 @@ import { NETWORK_TYPES } from '../lib/network';
16import { isValidAddress } from '../lib/wallet';
16import { isValidAddress } from '../lib/wallet';
17import { withNetwork } from '../store/network';
17import { withNetwork } from '../store/network';
18import { compose } from '../lib/lib';
18import { compose } from '../lib/lib';
19+import { withWallet } from '../store/wallet';
19
20
20class AcceptTransfer extends React.Component {
21class AcceptTransfer extends React.Component {
21 constructor(props) {
22 constructor(props) {
@@ -119,4 +120,7 @@ class AcceptTransfer extends React.Component {
119 }
120 }
120}
121}
121
122
122−export default compose(withNetwork)(AcceptTransfer);
123+export default compose(
124+ withNetwork,
125+ withWallet
126+)(AcceptTransfer);
src/bridge/views/CreateGalaxy.js+5−1
@@ -22,6 +22,7 @@ import { ETH_ZERO_ADDR, isValidAddress, eqAddr } from '../lib/wallet';
22
22
23import { isValidGalaxy, compose } from '../lib/lib';
23import { isValidGalaxy, compose } from '../lib/lib';
24import { withNetwork } from '../store/network';
24import { withNetwork } from '../store/network';
25+import { withWallet } from '../store/wallet';
25
26
26const buttonTriState = status => {
27const buttonTriState = status => {
27 if (status === null) return 'blue';
28 if (status === null) return 'blue';
@@ -195,4 +196,7 @@ class CreateGalaxy extends React.Component {
195 }
196 }
196}
197}
197
198
198−export default compose(withNetwork)(CreateGalaxy);
199+export default compose(
200+ withNetwork,
201+ withWallet
202+)(CreateGalaxy);
src/bridge/views/GenKeyfile.js+3−1
@@ -11,6 +11,8 @@ import saveAs from 'file-saver';
11
11
12import { attemptSeedDerivation, genKey } from '../lib/keys';
12import { attemptSeedDerivation, genKey } from '../lib/keys';
13import { addHexPrefix } from '../lib/wallet';
13import { addHexPrefix } from '../lib/wallet';
14+import { compose } from '../lib/lib';
15+import { withWallet } from '../store/wallet';
14
16
15class GenKeyfile extends React.Component {
17class GenKeyfile extends React.Component {
16 constructor(props) {
18 constructor(props) {
@@ -132,4 +134,4 @@ class GenKeyfile extends React.Component {
132 }
134 }
133}
135}
134
136
135−export default GenKeyfile;
137+export default compose(withWallet)(GenKeyfile);
src/bridge/views/InviteTicket.js+3−1
@@ -29,6 +29,7 @@ import {
29} from '../lib/invite';
29} from '../lib/invite';
30import { generateWallet, startTransactions } from '../lib/invite';
30import { generateWallet, startTransactions } from '../lib/invite';
31import { withNetwork } from '../store/network';
31import { withNetwork } from '../store/network';
32+import { withWallet } from '../store/wallet';
32
33
33class InviteTicket extends React.Component {
34class InviteTicket extends React.Component {
34 constructor(props) {
35 constructor(props) {
@@ -425,5 +426,6 @@ class InviteTicket extends React.Component {
425
426
426export default compose(
427export default compose(
427 withNetwork,
428 withNetwork,
428− withHistory
429+ withHistory,
430+ withWallet
429)(InviteTicket);
431)(InviteTicket);
src/bridge/views/InvitesSend.js+5−1
@@ -21,6 +21,7 @@ import {
21import * as tank from '../lib/tank';
21import * as tank from '../lib/tank';
22import { withNetwork } from '../store/network';
22import { withNetwork } from '../store/network';
23import { compose } from '../lib/lib';
23import { compose } from '../lib/lib';
24+import { withWallet } from '../store/wallet';
24
25
25const GAS_PRICE_GWEI = 20; // we pay the premium for faster ux
26const GAS_PRICE_GWEI = 20; // we pay the premium for faster ux
26const GAS_LIMIT = 350000;
27const GAS_LIMIT = 350000;
@@ -493,4 +494,7 @@ class InvitesSend extends React.Component {
493 }
494 }
494}
495}
495
496
496−export default compose(withNetwork)(InvitesSend);
497+export default compose(
498+ withNetwork,
499+ withWallet
500+)(InvitesSend);
src/bridge/views/Keystore.js+5−1
@@ -11,6 +11,7 @@ import { ROUTE_NAMES } from '../lib/routeNames';
11import { withHistory } from '../store/history';
11import { withHistory } from '../store/history';
12import { EthereumWallet } from '../lib/wallet';
12import { EthereumWallet } from '../lib/wallet';
13import { compose } from '../lib/lib';
13import { compose } from '../lib/lib';
14+import { withWallet } from '../store/wallet';
14
15
15class Keystore extends React.Component {
16class Keystore extends React.Component {
16 constructor(props) {
17 constructor(props) {
@@ -160,4 +161,7 @@ class Keystore extends React.Component {
160 }
161 }
161}
162}
162
163
163−export default compose(withHistory)(Keystore);
164+export default compose(
165+ withHistory,
166+ withWallet
167+)(Keystore);
src/bridge/views/Ledger.js+5−1
@@ -12,6 +12,7 @@ import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../lib/ledger';
12import { ROUTE_NAMES } from '../lib/routeNames';
12import { ROUTE_NAMES } from '../lib/routeNames';
13import { withHistory } from '../store/history';
13import { withHistory } from '../store/history';
14import { compose } from '../lib/lib';
14import { compose } from '../lib/lib';
15+import { withWallet } from '../store/wallet';
15
16
16const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
17const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
17
18
@@ -218,4 +219,7 @@ class Ledger extends React.Component {
218 }
219 }
219}
220}
220
221
221−export default compose(withHistory)(Ledger);
222+export default compose(
223+ withHistory,
224+ withWallet
225+)(Ledger);
src/bridge/views/Mnemonic.js+5−1
@@ -14,6 +14,7 @@ import { ROUTE_NAMES } from '../lib/routeNames';
14import { withHistory } from '../store/history';
14import { withHistory } from '../store/history';
15import { DEFAULT_HD_PATH, walletFromMnemonic } from '../lib/wallet';
15import { DEFAULT_HD_PATH, walletFromMnemonic } from '../lib/wallet';
16import { compose } from '../lib/lib';
16import { compose } from '../lib/lib';
17+import { withWallet } from '../store/wallet';
17
18
18class Mnemonic extends React.Component {
19class Mnemonic extends React.Component {
19 constructor(props) {
20 constructor(props) {
@@ -161,4 +162,7 @@ class Mnemonic extends React.Component {
161 }
162 }
162}
163}
163
164
164−export default compose(withHistory)(Mnemonic);
165+export default compose(
166+ withHistory,
167+ withWallet
168+)(Mnemonic);
src/bridge/views/Point.js+3−2
@@ -13,6 +13,7 @@ import { Row, Col, H1, H3 } from '../components/Base';
13import { withHistory } from '../store/history';
13import { withHistory } from '../store/history';
14import { withNetwork } from '../store/network';
14import { withNetwork } from '../store/network';
15import { compose } from '../lib/lib';
15import { compose } from '../lib/lib';
16+import { withWallet } from '../store/wallet';
16
17
17class Point extends React.Component {
18class Point extends React.Component {
18 constructor(props) {
19 constructor(props) {
@@ -122,7 +123,6 @@ class Point extends React.Component {
122 {authenticated ? (
123 {authenticated ? (
123 <Actions
124 <Actions
124 online={online}
125 online={online}
125− wallet={wallet}
126 point={point}
126 point={point}
127 pointDetails={pointDetails}
127 pointDetails={pointDetails}
128 invites={this.state.invites}
128 invites={this.state.invites}
@@ -139,5 +139,6 @@ class Point extends React.Component {
139
139
140export default compose(
140export default compose(
141 withNetwork,
141 withNetwork,
142− withHistory
142+ withHistory,
143+ withWallet
143)(Point);
144)(Point);
src/bridge/views/Point/Actions.js+3−1
@@ -7,6 +7,7 @@ import { Row, Col, H2, P } from '../../components/Base';
7import { Button } from '../../components/Base';
7import { Button } from '../../components/Base';
8import { ROUTE_NAMES } from '../../lib/routeNames';
8import { ROUTE_NAMES } from '../../lib/routeNames';
9import { useHistory } from '../../store/history';
9import { useHistory } from '../../store/history';
10+import { useWallet } from '../../store/wallet';
10
11
11const isPlanet = point =>
12const isPlanet = point =>
12 azimuth.getPointSize(point) === azimuth.PointSize.Planet;
13 azimuth.getPointSize(point) === azimuth.PointSize.Planet;
@@ -14,9 +15,10 @@ const isStar = point => azimuth.getPointSize(point) === azimuth.PointSize.Star;
14
15
15function Actions(props) {
16function Actions(props) {
16 const history = useHistory();
17 const history = useHistory();
18+ const { wallet } = useWallet();
17 const { online, point, pointDetails, invites } = props;
19 const { online, point, pointDetails, invites } = props;
18
20
19− const addr = need.address(props);
21+ const addr = need.address({ wallet });
20
22
21 const isOwner = pointDetails.matchWith({
23 const isOwner = pointDetails.matchWith({
22 Nothing: _ => false,
24 Nothing: _ => false,
src/bridge/views/Points.js+4−3
@@ -10,6 +10,7 @@ import { withHistory } from '../store/history';
10import { ETH_ZERO_ADDR, eqAddr } from '../lib/wallet';
10import { ETH_ZERO_ADDR, eqAddr } from '../lib/wallet';
11import { withNetwork } from '../store/network';
11import { withNetwork } from '../store/network';
12import { compose } from '../lib/lib';
12import { compose } from '../lib/lib';
13+import { withWallet } from '../store/wallet';
13
14
14const hasTransferProxy = (cache, point) =>
15const hasTransferProxy = (cache, point) =>
15 point in cache ? !eqAddr(cache[point].transferProxy, ETH_ZERO_ADDR) : false;
16 point in cache ? !eqAddr(cache[point].transferProxy, ETH_ZERO_ADDR) : false;
@@ -82,8 +83,7 @@ class Points extends React.Component {
82 }
83 }
83
84
84 render() {
85 render() {
85− const { setPointCursor, history } = this.props;
86+ const { setPointCursor, history, pointCache } = this.props;
86− const { pointCache } = this.props;
87
87
88 const {
88 const {
89 points,
89 points,
@@ -259,5 +259,6 @@ class Points extends React.Component {
259
259
260export default compose(
260export default compose(
261 withNetwork,
261 withNetwork,
262− withHistory
262+ withHistory,
263+ withWallet
263)(Points);
264)(Points);
src/bridge/views/PrivateKey.js+5−1
@@ -8,6 +8,7 @@ import { ROUTE_NAMES } from '../lib/routeNames';
8import { withHistory } from '../store/history';
8import { withHistory } from '../store/history';
9import { EthereumWallet } from '../lib/wallet';
9import { EthereumWallet } from '../lib/wallet';
10import { compose } from '../lib/lib';
10import { compose } from '../lib/lib';
11+import { withWallet } from '../store/wallet';
11
12
12class PrivateKey extends React.Component {
13class PrivateKey extends React.Component {
13 constructor(props) {
14 constructor(props) {
@@ -74,4 +75,7 @@ class PrivateKey extends React.Component {
74 }
75 }
75}
76}
76
77
77−export default compose(withHistory)(PrivateKey);
78+export default compose(
79+ withHistory,
80+ withWallet
81+)(PrivateKey);
src/bridge/views/SetKeys.js+8−4
@@ -3,17 +3,18 @@ import Maybe from 'folktale/maybe';
3import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
4import * as ob from 'urbit-ob';
4import * as ob from 'urbit-ob';
5import * as need from '../lib/need';
5import * as need from '../lib/need';
6+import * as kg from 'urbit-key-generation/dist/index';
6import { Row, Col, H1, P, Warning, CheckboxButton } from '../components/Base';
7import { Row, Col, H1, P, Warning, CheckboxButton } from '../components/Base';
7
8
8import StatelessTransaction from '../components/StatelessTransaction';
9import StatelessTransaction from '../components/StatelessTransaction';
9import { attemptSeedDerivation } from '../lib/keys';
10import { attemptSeedDerivation } from '../lib/keys';
10
11
11−import * as kg from 'urbit-key-generation/dist/index';
12−
13import { addHexPrefix } from '../lib/wallet';
12import { addHexPrefix } from '../lib/wallet';
14−import { withNetwork } from '../store/network';
15import { compose } from '../lib/lib';
13import { compose } from '../lib/lib';
16
14
15+import { withNetwork } from '../store/network';
16+import { withWallet } from '../store/wallet';
17+
17class SetKeys extends React.Component {
18class SetKeys extends React.Component {
18 constructor(props) {
19 constructor(props) {
19 super(props);
20 super(props);
@@ -198,4 +199,7 @@ class SetKeys extends React.Component {
198 }
199 }
199}
200}
200
201
201−export default compose(withNetwork)(SetKeys);
202+export default compose(
203+ withNetwork,
204+ withWallet
205+)(SetKeys);
src/bridge/views/Shards.js+5−1
@@ -17,6 +17,7 @@ import { ROUTE_NAMES } from '../lib/routeNames';
17import { withHistory } from '../store/history';
17import { withHistory } from '../store/history';
18import { urbitWalletFromTicket } from '../lib/wallet';
18import { urbitWalletFromTicket } from '../lib/wallet';
19import { compose } from '../lib/lib';
19import { compose } from '../lib/lib';
20+import { withWallet } from '../store/wallet';
20
21
21const placeholder = len => {
22const placeholder = len => {
22 let bytes = window.crypto.getRandomValues(new Uint8Array(len));
23 let bytes = window.crypto.getRandomValues(new Uint8Array(len));
@@ -212,4 +213,7 @@ class Shards extends React.Component {
212 }
213 }
213}
214}
214
215
215−export default compose(withHistory)(Shards);
216+export default compose(
217+ withHistory,
218+ withWallet
219+)(Shards);
src/bridge/views/Ticket.js+5−1
@@ -15,6 +15,7 @@ import { randomPatq, compose } from '../lib/lib';
15import { ROUTE_NAMES } from '../lib/routeNames';
15import { ROUTE_NAMES } from '../lib/routeNames';
16import { withHistory } from '../store/history';
16import { withHistory } from '../store/history';
17import { urbitWalletFromTicket } from '../lib/wallet';
17import { urbitWalletFromTicket } from '../lib/wallet';
18+import { withWallet } from '../store/wallet';
18
19
19class Ticket extends React.Component {
20class Ticket extends React.Component {
20 constructor(props) {
21 constructor(props) {
@@ -150,4 +151,7 @@ class Ticket extends React.Component {
150 }
151 }
151}
152}
152
153
153−export default compose(withHistory)(Ticket);
154+export default compose(
155+ withHistory,
156+ withWallet
157+)(Ticket);
src/bridge/views/Trezor.js+5−1
@@ -11,6 +11,7 @@ import { TREZOR_PATH } from '../lib/trezor';
11import { ROUTE_NAMES } from '../lib/routeNames';
11import { ROUTE_NAMES } from '../lib/routeNames';
12import { withHistory } from '../store/history';
12import { withHistory } from '../store/history';
13import { compose } from '../lib/lib';
13import { compose } from '../lib/lib';
14+import { withWallet } from '../store/wallet';
14
15
15class Trezor extends React.Component {
16class Trezor extends React.Component {
16 constructor(props) {
17 constructor(props) {
@@ -140,4 +141,7 @@ class Trezor extends React.Component {
140 }
141 }
141}
142}
142
143
143−export default compose(withHistory)(Trezor);
144+export default compose(
145+ withHistory,
146+ withWallet
147+)(Trezor);
src/bridge/views/Wallet.js+20−16
@@ -6,8 +6,9 @@ import { Row, Col } from '../components/Base';
6
6
7import { ROUTE_NAMES } from '../lib/routeNames';
7import { ROUTE_NAMES } from '../lib/routeNames';
8import { withHistory } from '../store/history';
8import { withHistory } from '../store/history';
9−import { WALLET_NAMES, renderWalletType } from '../lib/wallet';
9+import { WALLET_TYPES, renderWalletType } from '../lib/wallet';
10import { compose } from '../lib/lib';
10import { compose } from '../lib/lib';
11+import { withWallet } from '../store/wallet';
11
12
12class Wallet extends React.Component {
13class Wallet extends React.Component {
13 componentDidMount() {
14 componentDidMount() {
@@ -19,37 +20,37 @@ class Wallet extends React.Component {
19 return [
20 return [
20 {
21 {
21 title: 'Urbit Master Ticket',
22 title: 'Urbit Master Ticket',
22− value: WALLET_NAMES.TICKET,
23+ value: WALLET_TYPES.TICKET,
23 },
24 },
24 {
25 {
25 title: 'Urbit Master Ticket (Shards)',
26 title: 'Urbit Master Ticket (Shards)',
26− value: WALLET_NAMES.SHARDS,
27+ value: WALLET_TYPES.SHARDS,
27 },
28 },
28 {
29 {
29 type: 'divider',
30 type: 'divider',
30 },
31 },
31 {
32 {
32 title: 'Ledger',
33 title: 'Ledger',
33− value: WALLET_NAMES.LEDGER,
34+ value: WALLET_TYPES.LEDGER,
34 },
35 },
35 {
36 {
36 title: 'Trezor',
37 title: 'Trezor',
37− value: WALLET_NAMES.TREZOR,
38+ value: WALLET_TYPES.TREZOR,
38 },
39 },
39 {
40 {
40 type: 'divider',
41 type: 'divider',
41 },
42 },
42 {
43 {
43 title: 'BIP39 Mnemonic',
44 title: 'BIP39 Mnemonic',
44− value: WALLET_NAMES.MNEMONIC,
45+ value: WALLET_TYPES.MNEMONIC,
45 },
46 },
46 {
47 {
47 title: 'Ethereum Private Key',
48 title: 'Ethereum Private Key',
48− value: WALLET_NAMES.PRIVATE_KEY,
49+ value: WALLET_TYPES.PRIVATE_KEY,
49 },
50 },
50 {
51 {
51 title: 'Ethereum Keystore File',
52 title: 'Ethereum Keystore File',
52− value: WALLET_NAMES.KEYSTORE,
53+ value: WALLET_TYPES.KEYSTORE,
53 },
54 },
54 ];
55 ];
55 }
56 }
@@ -80,19 +81,19 @@ class Wallet extends React.Component {
80 className={'mt-10'}
81 className={'mt-10'}
81 onClick={() =>
82 onClick={() =>
82 props.history.push(
83 props.history.push(
83− props.walletType === WALLET_NAMES.MNEMONIC
84+ props.walletType === WALLET_TYPES.MNEMONIC
84 ? ROUTE_NAMES.MNEMONIC
85 ? ROUTE_NAMES.MNEMONIC
85− : props.walletType === WALLET_NAMES.TICKET
86+ : props.walletType === WALLET_TYPES.TICKET
86 ? ROUTE_NAMES.TICKET
87 ? ROUTE_NAMES.TICKET
87− : props.walletType === WALLET_NAMES.SHARDS
88+ : props.walletType === WALLET_TYPES.SHARDS
88 ? ROUTE_NAMES.SHARDS
89 ? ROUTE_NAMES.SHARDS
89− : props.walletType === WALLET_NAMES.LEDGER
90+ : props.walletType === WALLET_TYPES.LEDGER
90 ? ROUTE_NAMES.LEDGER
91 ? ROUTE_NAMES.LEDGER
91− : props.walletType === WALLET_NAMES.TREZOR
92+ : props.walletType === WALLET_TYPES.TREZOR
92 ? ROUTE_NAMES.TREZOR
93 ? ROUTE_NAMES.TREZOR
93− : props.walletType === WALLET_NAMES.PRIVATE_KEY
94+ : props.walletType === WALLET_TYPES.PRIVATE_KEY
94 ? ROUTE_NAMES.PRIVATE_KEY
95 ? ROUTE_NAMES.PRIVATE_KEY
95− : props.walletType === WALLET_NAMES.KEYSTORE
96+ : props.walletType === WALLET_TYPES.KEYSTORE
96 ? ROUTE_NAMES.KEYSTORE
97 ? ROUTE_NAMES.KEYSTORE
97 : ROUTE_NAMES.DEFAULT
98 : ROUTE_NAMES.DEFAULT
98 )
99 )
@@ -105,4 +106,7 @@ class Wallet extends React.Component {
105 }
106 }
106}
107}
107
108
108−export default compose(withHistory)(Wallet);
109+export default compose(
110+ withHistory,
111+ withWallet
112+)(Wallet);