on df0c310 help sign in
bridge/src/views/UrbitOS/NetworkKeys.tsx
442 lines 13 kB history raw
1import React, {
2 useCallback,
3 useEffect,
4 useMemo,
5 useRef,
6 useState,
7} from 'react';
8import { Just } from 'folktale/maybe';
9import { Grid, Button } from 'indigo-react';
10import * as azimuth from 'azimuth-js';
11import { randomHex } from 'web3-utils';
12import { Box, Checkbox, Icon, Row, Text } from '@tlon/indigo-react';
14import { usePointCache } from 'store/pointCache';
15import { useNetwork } from 'store/network';
16import { useWallet } from 'store/wallet';
17import { useRollerStore } from 'store/rollerStore';
19import useRoller from 'lib/useRoller';
20import { useLocalRouter } from 'lib/LocalRouter';
21import * as need from 'lib/need';
22import {
23 attemptNetworkSeedDerivation,
24 deriveNetworkKeys,
25 CRYPTO_SUITE_VERSION,
26} from 'lib/keys';
27import useEthereumTransaction from 'lib/useEthereumTransaction';
28import { GAS_LIMITS, ONE_SECOND } from 'lib/constants';
29import { addHexPrefix } from 'lib/utils/address';
30import { useSingleKeyfileGenerator } from 'lib/useKeyfileGenerator';
32import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
33import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
34import NoticeBox from 'components/NoticeBox';
36import { HexInput } from 'form/Inputs';
37import {
38 composeValidator,
39 buildCheckboxValidator,
40 buildHexValidator,
41} from 'form/validators';
42import BridgeForm from 'form/BridgeForm';
43import FormError from 'form/FormError';
44import Window from 'components/L2/Window/Window';
45import HeaderPane from 'components/L2/Window/HeaderPane';
46import BodyPane from 'components/L2/Window/BodyPane';
48import './NetworkKeys.scss';
49import { L1TxnType } from 'lib/types/PendingL1Transaction';
50import { PointField } from 'lib/types/Point';
52function useSetKeys(
53 manualNetworkSeed: string,
54 setManualNetworkSeed: (seed: string) => void
55) {
56 const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
57 const { point } = useRollerStore();
58 const { syncDetails, syncRekeyDate }: any = usePointCache();
59 const { contracts }: any = useNetwork();
61 const _contracts = need.contracts(contracts);
63 const networkRevision = Number(point.keyRevisionNumber);
64 const randomSeed = useRef<string | null>();
66 const {
67 generating: keyfileGenerating,
68 filename,
69 bind: keyfileBind,
70 } = useSingleKeyfileGenerator({ seed: manualNetworkSeed });
72 const buildNetworkSeed = useCallback(
73 async manualSeed => {
74 if (manualSeed !== undefined) {
75 setManualNetworkSeed(manualSeed);
76 return manualSeed;
77 } else {
78 const newNetworkRevision = networkRevision + 1;
79 console.log(`deriving seed with revision ${newNetworkRevision}`);
81 const networkSeed = await attemptNetworkSeedDerivation({
82 urbitWallet,
83 wallet,
84 authMnemonic,
85 details: point,
86 point: point.value,
87 authToken,
88 revision: newNetworkRevision,
89 });
91 if (Just.hasInstance(networkSeed)) {
92 return networkSeed.value;
93 }
95 randomSeed.current = randomSeed.current || randomHex(32); // 32 bytes
96 setManualNetworkSeed(randomSeed.current);
98 return randomSeed.current;
99 }
100 },
101 [
102 authMnemonic,
103 setManualNetworkSeed,
104 networkRevision,
105 urbitWallet,
106 wallet,
107 point,
108 authToken,
109 ]
110 );
112 const { completed: _completed, ...rest } = useEthereumTransaction(
113 useCallback(
114 async (manualSeed: string, isDiscontinuity: boolean) => {
115 const seed = await buildNetworkSeed(manualSeed);
116 const pair = deriveNetworkKeys(seed);
118 return azimuth.ecliptic.configureKeys(
119 _contracts,
120 point.value,
121 addHexPrefix(pair.crypt.public),
122 addHexPrefix(pair.auth.public),
123 CRYPTO_SUITE_VERSION,
124 isDiscontinuity
125 );
126 },
127 [_contracts, point, buildNetworkSeed]
128 ),
129 useCallback(
130 () => Promise.all([syncDetails(point.value), syncRekeyDate(point.value)]),
131 [point, syncDetails, syncRekeyDate]
132 ),
133 GAS_LIMITS.CONFIGURE_KEYS
134 );
136 // only treat the transaction as completed once we also have keys to download
137 const completed = _completed && !keyfileGenerating;
139 return {
140 completed,
141 filename,
142 keyfileBind,
143 ...rest,
144 };
147export default function UrbitOSNetworkKeys({
148 manualNetworkSeed,
149 setManualNetworkSeed,
150}: {
151 manualNetworkSeed: string;
152 setManualNetworkSeed: (seed: string) => void;
153}) {
154 const { pop }: any = useLocalRouter();
155 const { point, setLoading } = useRollerStore();
156 const {
157 configureNetworkKeys,
158 getPendingTransactions,
159 checkForUpdates,
160 } = useRoller();
161 const [breach, setBreach] = useState(false);
162 const [useCustomSeed, setUseCustomSeed] = useState(false);
163 const [l2Completed, setL2Completed] = useState(false);
165 const hasKeys = point.networkKeysSet;
167 const {
168 construct,
169 unconstruct,
170 completed,
171 inputsLocked,
172 bind,
173 keyfileBind,
174 txHashes,
175 } = useSetKeys(manualNetworkSeed, setManualNetworkSeed);
177 useEffect(() => {
178 if (completed) {
179 checkForUpdates({
180 point: point,
181 field: PointField.keyRevisionNumber,
182 message: `${point.patp}'s Network Keys have been set!`,
183 l1Txn: {
184 id: `${point.keyRevisionNumber}-network-keys-${point.value}`,
185 point: point.value,
186 type: L1TxnType.setNetworkKeys,
187 hash: txHashes[0],
188 time: new Date().getTime(),
189 },
190 intervalTime: ONE_SECOND,
191 });
192 }
193 }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
195 const validateForm = useCallback((values, errors) => {
196 if (values.useNetworkSeed && errors.networkSeed) {
197 return errors;
198 }
199 }, []);
201 const validate = useMemo(
202 () =>
203 composeValidator(
204 {
205 useNetworkSeed: buildCheckboxValidator(),
206 networkSeed: buildHexValidator(64), // 64 chars
207 useDiscontinuity: buildCheckboxValidator(),
208 },
209 validateForm
210 ),
211 [validateForm]
212 );
214 const onValues = useCallback(
215 ({ valid, values, form }) => {
216 if (valid) {
217 const networkSeed = useCustomSeed ? values.networkSeed : undefined;
218 construct(networkSeed, breach);
219 setManualNetworkSeed(networkSeed);
220 } else {
221 unconstruct();
222 }
224 if (!useCustomSeed && values.networkSeed) {
225 form.change('networkSeed', '');
226 setBreach(false);
227 setManualNetworkSeed('');
228 }
229 },
230 [
231 construct,
232 unconstruct,
233 breach,
234 setManualNetworkSeed,
235 setBreach,
236 useCustomSeed,
237 ]
238 );
240 const setNetworkKeys = useCallback(async () => {
241 setLoading(true);
242 try {
243 await configureNetworkKeys({
244 breach,
245 customNetworkSeed: manualNetworkSeed,
246 });
247 // TODO: just use the tx hash instead?
248 getPendingTransactions();
249 // TODO: this is just so we get visual feedback that the tx has gone through
250 // but this should be addressed with the new UI flow (-> download keyfile)
251 //
252 // A question here is how to deal with the modals/messages about the keys not being
253 // set since they are in pending in the Roller...
254 // we could inspect if there's a changeKeys in the local list of pending txs,...
255 //
256 checkForUpdates({
257 point: point,
258 message: `${point.patp}'s Network Keys have been set!`,
259 intervalTime: ONE_SECOND,
260 });
261 setTimeout(() => setL2Completed(true), ONE_SECOND);
262 } catch (error) {
263 // setError(error);
264 } finally {
265 setLoading(false);
266 }
267 }, [
268 breach,
269 manualNetworkSeed,
270 getPendingTransactions,
271 point,
272 configureNetworkKeys,
273 setLoading,
274 checkForUpdates,
275 ]);
277 const initialValues = useMemo(() => ({}), []);
279 const viewTitle = completed
280 ? 'Network Keys have been set'
281 : hasKeys
282 ? 'Set Network Keys'
283 : 'Initiate Network Keys';
285 const bootMessage = () => (
286 <Text as="p" textAlign={'center'}>
287 Download your new Network Key to boot your ship. Be sure to delete your
288 old pier before booting with this new key.
289 </Text>
290 );
292 const resetMessage = () => (
293 <>
294 <Text as="p" textAlign={'center'}>
295 Copy the contents of the Network Key file and run
296 </Text>
297 <Text
298 as="code"
299 mono
300 bg={'washedGray'}
301 padding={2}
302 borderRadius={2}
303 textAlign={'center'}>
304 |rekey 'keyfile_contents'
305 </Text>
306 <Text as="p" textAlign={'center'}>
307 using the dojo on your running ship.
308 </Text>
309 </>
310 );
312 const waitMessage = () => (
313 <Text as="p" textAlign={'center'}>
314 <strong>Important:</strong> you will need to wait until the next L2 batch
315 is submitted to the roller to boot your ship. If you do not wait, then you
316 will not be able to boot your ship.
317 </Text>
318 );
320 const buttonText = `${hasKeys ? 'Reset' : 'Set'} Network Keys`;
322 const isComplete = completed || l2Completed;
324 return (
325 <Window className="network-keys">
326 <HeaderPane>
327 <h5>{viewTitle}</h5>
328 </HeaderPane>
329 <BodyPane>
330 {isComplete && (
331 <Box className="flex-col col justify-between h-full">
332 <Box className="flex-col h-full justify-center">
333 <Icon icon="Download" className="download" />
334 {breach ? bootMessage() : resetMessage()}
335 {breach && point.isL2 ? waitMessage() : null}
336 </Box>
337 {point.isL2 ? (
338 <Grid.Item
339 full
340 as={Button}
341 solid
342 center
343 onClick={keyfileBind.download}>
344 Download Network Key
345 </Grid.Item>
346 ) : (
347 <Grid.Item
348 full
349 as={DownloadKeyfileButton}
350 solid
351 center
352 {...keyfileBind}
353 />
354 )}
355 </Box>
356 )}
358 {!isComplete && (
359 <BridgeForm
360 validate={validate}
361 onValues={onValues}
362 initialValues={initialValues}>
363 {() => (
364 <Box className="contents">
365 {!isComplete && (
366 <Box>
367 <Row
368 className="check-row"
369 onClick={() => setBreach(!breach)}>
370 <Checkbox
371 className="checkbox"
372 selected={breach}
373 disabled={inputsLocked}
374 />
375 Factory Reset
376 </Row>
377 <Box className="info-text">
378 Use if your ship is corrupted, you lost your files, or
379 <br />
380 you want to erase your data
381 </Box>
382 <Row
383 className="check-row"
384 onClick={() => setUseCustomSeed(!useCustomSeed)}>
385 <Checkbox
386 className="checkbox"
387 selected={useCustomSeed}
388 disabled={inputsLocked}
389 />
390 Custom Network Seed
391 </Row>
392 <Box className="info-text">
393 Enter your own custom network seed to derive from
394 </Box>
395 {useCustomSeed && (
396 <>
397 <Grid.Item full as={NoticeBox} className="mb2">
398 When using a custom network seed, you'll need to
399 download your Arvo keyfile immediately after this
400 transaction as Bridge does not store your seed.
401 </Grid.Item>
402 <Grid.Item
403 full
404 as={HexInput}
405 name="networkSeed"
406 label="Network Seed (32 bytes)"
407 disabled={inputsLocked}
408 className="mb5"
409 />
410 </>
411 )}
412 <Grid.Item full as={FormError} />
413 </Box>
414 )}
416 {point.isL2 ? (
417 <Grid.Item
418 as={Button}
419 full
420 className=""
421 center
422 solid
423 onClick={setNetworkKeys}>
424 {buttonText}
425 </Grid.Item>
426 ) : (
427 <Grid.Item
428 full
429 as={InlineEthereumTransaction}
430 {...bind}
431 label={buttonText}
432 onReturn={() => pop()}
433 />
434 )}
435 </Box>
436 )}
437 </BridgeForm>
438 )}
439 </BodyPane>
440 </Window>
441 );