on d317e13 help sign in
bridge/src/store/wallet.js
224 lines 5.8 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 } 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';
25export const WalletContext = createContext(null);
27const DEFAULT_WALLET_TYPE = WALLET_TYPES.TICKET;
29// NB (jtobin):
30//
31// Note that the 'wallet' prop has type depending on the 'walletType' prop.
32// These could be bound together in a single structure (so that
33// 'walletType' acted more explicitly as a data constructor of sorts) but
34// it doesn't necessarily help us much, since we branch on 'walletType'
35// before setting 'wallet'.
36//
37// Wallets are always wrapped in For most authentication
38// mechanisms, Maybe wraps a BIP32 HD wallet; for Ethereum private keys,
39// JSON keystore files, and Metamask authentication, it wraps an
40// 'EthereumWallet'.
41function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
42 const [walletType, _setWalletType] = useState(() =>
43 initialMnemonic.matchWith({
44 Nothing: () => DEFAULT_WALLET_TYPE,
45 Just: () => WALLET_TYPES.MNEMONIC,
46 })
47 );
48 const [walletHdPath, setWalletHdPath] = useState(DEFAULT_HD_PATH);
49 const [wallet, _setWallet] = useState(initialWallet);
50 const [urbitWallet, _setUrbitWallet] = useState(Nothing());
51 const [authMnemonic, setAuthMnemonic] = useState(initialMnemonic);
52 const [networkSeed, setNetworkSeed] = useState(Nothing());
53 const [networkRevision, setNetworkRevision] = useState(Nothing());
55 const [authToken, setAuthToken] = useState(Nothing());
56 // Allow users to skip signing the auth token on login
57 const [skipLoginSigning, setSkipLoginSigning] = useState(false);
59 const setFakeToken = () => {
60 setAuthToken(Just(randomHex(32)));
61 };
63 // See: https://github.com/urbit/bridge/issues/549#issuecomment-1048359617
64 // This is used for legacy compatibility; this flow should eventually be
65 // be removed after ~1 year.
66 const [useLegacyTokenSigning, setUseLegacyTokenSigning] = useState(false);
68 const { web3 } = useNetwork();
70 useEffect(() => {
71 (async () => {
72 if (
73 !Just.hasInstance(wallet) ||
74 !Just.hasInstance(web3) ||
75 NONCUSTODIAL_WALLETS.has(walletType)
76 ) {
77 return;
78 }
80 const _wallet = wallet.value;
81 const _web3 = web3.value;
82 if (skipLoginSigning) {
83 setFakeToken();
84 return;
85 }
87 const token = await getAuthToken({
88 wallet: _wallet,
89 walletType,
90 walletHdPath,
91 web3: _web3,
92 useLegacyTokenSigning,
93 });
95 setAuthToken(Just(token));
96 })();
97 }, [
98 wallet,
99 walletType,
100 walletHdPath,
101 web3,
102 useLegacyTokenSigning,
103 setAuthToken,
104 skipLoginSigning,
105 ]);
107 const setWalletType = useCallback(
108 walletType => {
109 if (!includes(WALLET_TYPES, walletType)) {
110 throw new Error(BRIDGE_ERROR.INVALID_WALLET_TYPE);
111 }
113 _setWalletType(walletType);
114 },
115 [_setWalletType]
116 );
118 const setWallet = useCallback(
119 wallet => {
120 // force that public addresses are derived for each wallet
121 // NOTE wallet is Maybe<> and .map is Maybe#map
122 wallet.map(wal => {
123 wal.address = wal.address || publicToAddress(wal.publicKey);
124 return wal;
125 });
127 _setWallet(wallet);
128 },
129 [_setWallet]
130 );
132 const setUrbitWallet = useCallback(
133 urbitWallet => {
134 if (Just.hasInstance(urbitWallet)) {
135 // when an urbit wallet is set, also derive
136 // a normal bip32 wallet using the ownership address
137 const wallet = walletFromMnemonic(
138 urbitWallet.value.ownership.seed,
139 DEFAULT_HD_PATH,
140 urbitWallet.value.meta.passphrase
141 );
143 setWallet(wallet);
144 } else {
145 setWallet(Nothing());
146 }
148 _setUrbitWallet(urbitWallet);
149 },
150 [setWallet]
151 );
153 const resetWallet = useCallback(() => {
154 _setWalletType(DEFAULT_WALLET_TYPE);
155 setWalletHdPath(DEFAULT_HD_PATH);
156 _setWallet(Nothing());
157 _setUrbitWallet(Nothing());
158 setAuthMnemonic(Nothing());
159 setNetworkSeed(Nothing());
160 setNetworkRevision(Nothing());
161 setUseLegacyTokenSigning(false);
162 }, [
163 _setWalletType,
164 setWalletHdPath,
165 _setWallet,
166 _setUrbitWallet,
167 setAuthMnemonic,
168 setNetworkSeed,
169 setNetworkRevision,
170 ]);
172 return {
173 //
174 walletType,
175 setWalletType,
176 //
177 walletHdPath,
178 setWalletHdPath,
179 //
180 wallet,
181 setWallet,
182 //
183 urbitWallet,
184 setUrbitWallet,
185 authMnemonic,
186 setAuthMnemonic,
187 //
188 networkSeed,
189 setNetworkSeed,
190 networkRevision,
191 setNetworkRevision,
192 //
193 resetWallet,
194 //
195 authToken,
196 setAuthToken,
197 useLegacyTokenSigning,
198 setUseLegacyTokenSigning,
199 skipLoginSigning,
200 setSkipLoginSigning,
201 setFakeToken,
202 };
205export function WalletProvider({ initialWallet, initialMnemonic, children }) {
206 const wallet = _useWallet(initialWallet, initialMnemonic);
208 return (
209 <WalletContext.Provider value={wallet}>{children}</WalletContext.Provider>
210 );
213// Hook version
214export function useWallet() {
215 return useContext(WalletContext);
218// HOC version
219export const withWallet = Component =>
220 forwardRef((props, ref) => (
221 <WalletContext.Consumer>
222 {wallet => <Component ref={ref} {...wallet} {...props} />}
223 </WalletContext.Consumer>
224 ));