on df0c310 help sign in
bridge/src/store/wallet.jsx
256 lines 6.4 kB history raw
1import React, {
2 createContext,
3 forwardRef,
4 useCallback,
5 useContext,
6 useState,
7 useEffect,
8} from 'react';
9import { Just, Nothing } from 'folktale/maybe';
10import { includes, noop } from 'lodash';
11import { randomHex } from 'web3-utils';
13import { walletFromMnemonic } from 'lib/wallet';
14import {
15 DEFAULT_HD_PATH,
16 NONCUSTODIAL_WALLETS,
17 WALLET_TYPES,
18} from 'lib/constants';
19import { getAuthToken } from 'lib/authToken';
20import { BRIDGE_ERROR } from 'lib/error';
22import { useNetwork } from 'store/network';
23import { publicToAddress } from 'lib/utils/address';
25const initialContext = {
26 //
27 walletType: Nothing(),
28 setWalletType: noop,
29 //
30 walletHdPath: '',
31 setWalletHdPath: noop,
32 //
33 wallet: Nothing(),
34 setWallet: noop,
35 //
36 urbitWallet: Nothing(),
37 setUrbitWallet: noop,
38 authMnemonic: Nothing(),
39 setAuthMnemonic: noop,
40 //
41 networkSeed: Nothing(),
42 setNetworkSeed: noop,
43 networkRevision: Nothing(),
44 setNetworkRevision: noop,
45 //
46 resetWallet: noop,
47 //
48 authToken: Nothing(),
49 setAuthToken: noop,
50 useLegacyTokenSigning: Nothing(),
51 setUseLegacyTokenSigning: noop,
52 skipLoginSigning: false,
53 setSkipLoginSigning: noop,
54 setFakeToken: noop,
57export const WalletContext = createContext(initialContext);
59const DEFAULT_WALLET_TYPE = WALLET_TYPES.TICKET;
61// NB (jtobin):
62//
63// Note that the 'wallet' prop has type depending on the 'walletType' prop.
64// These could be bound together in a single structure (so that
65// 'walletType' acted more explicitly as a data constructor of sorts) but
66// it doesn't necessarily help us much, since we branch on 'walletType'
67// before setting 'wallet'.
68//
69// Wallets are always wrapped in For most authentication
70// mechanisms, Maybe wraps a BIP32 HD wallet; for Ethereum private keys,
71// JSON keystore files, and Metamask authentication, it wraps an
72// 'EthereumWallet'.
73function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
74 const [walletType, _setWalletType] = useState(() =>
75 initialMnemonic.matchWith({
76 Nothing: () => DEFAULT_WALLET_TYPE,
77 Just: () => WALLET_TYPES.MNEMONIC,
78 })
79 );
80 const [walletHdPath, setWalletHdPath] = useState(DEFAULT_HD_PATH);
81 const [wallet, _setWallet] = useState(initialWallet);
82 const [urbitWallet, _setUrbitWallet] = useState(Nothing());
83 const [authMnemonic, setAuthMnemonic] = useState(initialMnemonic);
84 const [networkSeed, setNetworkSeed] = useState(Nothing());
85 const [networkRevision, setNetworkRevision] = useState(Nothing());
87 const [authToken, setAuthToken] = useState(Nothing());
88 // Allow users to skip signing the auth token on login
89 const [skipLoginSigning, setSkipLoginSigning] = useState(false);
91 const setFakeToken = () => {
92 setAuthToken(Just(randomHex(32)));
93 };
95 // See: https://github.com/urbit/bridge/issues/549#issuecomment-1048359617
96 // This is used for legacy compatibility; this flow should eventually be
97 // be removed after ~1 year.
98 const [useLegacyTokenSigning, setUseLegacyTokenSigning] = useState(false);
100 const { web3 } = useNetwork();
102 useEffect(() => {
103 (async () => {
104 if (
105 !Just.hasInstance(wallet) ||
106 !Just.hasInstance(web3) ||
107 NONCUSTODIAL_WALLETS.has(walletType)
108 ) {
109 return;
110 }
112 const _wallet = wallet.value;
113 const _web3 = web3.value;
114 if (skipLoginSigning) {
115 setFakeToken();
116 return;
117 }
119 const token = await getAuthToken({
120 wallet: _wallet,
121 walletType,
122 walletHdPath,
123 web3: _web3,
124 useLegacyTokenSigning,
125 });
127 setAuthToken(Just(token));
128 })();
129 }, [
130 wallet,
131 walletType,
132 walletHdPath,
133 web3,
134 useLegacyTokenSigning,
135 setAuthToken,
136 skipLoginSigning,
137 ]);
139 const setWalletType = useCallback(
140 walletType => {
141 if (!includes(WALLET_TYPES, walletType)) {
142 throw new Error(BRIDGE_ERROR.INVALID_WALLET_TYPE);
143 }
145 _setWalletType(walletType);
146 },
147 [_setWalletType]
148 );
150 const setWallet = useCallback(
151 wallet => {
152 // force that public addresses are derived for each wallet
153 // NOTE wallet is Maybe<> and .map is Maybe#map
154 wallet.map(wal => {
155 wal.address = wal.address || publicToAddress(wal.publicKey);
156 return wal;
157 });
159 _setWallet(wallet);
160 },
161 [_setWallet]
162 );
164 const setUrbitWallet = useCallback(
165 urbitWallet => {
166 if (Just.hasInstance(urbitWallet)) {
167 // when an urbit wallet is set, also derive
168 // a normal bip32 wallet using the ownership address
169 const wallet = walletFromMnemonic(
170 urbitWallet.value.ownership.seed,
171 DEFAULT_HD_PATH,
172 urbitWallet.value.meta.passphrase
173 );
175 setWallet(wallet);
176 } else {
177 setWallet(Nothing());
178 }
180 _setUrbitWallet(urbitWallet);
181 },
182 [setWallet]
183 );
185 const resetWallet = useCallback(() => {
186 _setWalletType(DEFAULT_WALLET_TYPE);
187 setWalletHdPath(DEFAULT_HD_PATH);
188 _setWallet(Nothing());
189 _setUrbitWallet(Nothing());
190 setAuthMnemonic(Nothing());
191 setNetworkSeed(Nothing());
192 setNetworkRevision(Nothing());
193 setUseLegacyTokenSigning(false);
194 }, [
195 _setWalletType,
196 setWalletHdPath,
197 _setWallet,
198 _setUrbitWallet,
199 setAuthMnemonic,
200 setNetworkSeed,
201 setNetworkRevision,
202 ]);
204 return {
205 //
206 walletType,
207 setWalletType,
208 //
209 walletHdPath,
210 setWalletHdPath,
211 //
212 wallet,
213 setWallet,
214 //
215 urbitWallet,
216 setUrbitWallet,
217 authMnemonic,
218 setAuthMnemonic,
219 //
220 networkSeed,
221 setNetworkSeed,
222 networkRevision,
223 setNetworkRevision,
224 //
225 resetWallet,
226 //
227 authToken,
228 setAuthToken,
229 useLegacyTokenSigning,
230 setUseLegacyTokenSigning,
231 skipLoginSigning,
232 setSkipLoginSigning,
233 setFakeToken,
234 };
237export function WalletProvider({ initialWallet, initialMnemonic, children }) {
238 const wallet = _useWallet(initialWallet, initialMnemonic);
240 return (
241 <WalletContext.Provider value={wallet}>{children}</WalletContext.Provider>
242 );
245// Hook version
246export function useWallet() {
247 return useContext(WalletContext);
250// HOC version
251export const withWallet = Component =>
252 forwardRef((props, ref) => (
253 <WalletContext.Consumer>
254 {wallet => <Component ref={ref} {...wallet} {...props} />}
255 </WalletContext.Consumer>
256 ));