on master help sign in
Network keys success message
commitaa8e0b21023576518845304cc25ee02f1a732552 authorWill Galebach <willbach@gmail.com> dateThu 16 Dec 2021, 21:59 (5 years ago) parenteb4d5a9
11 files changed+241−261
src/components/DownloadKeyfileButton.js+1−1 src/components/L2/PendingTransaction.tsx−20 src/components/NetworkKeys.js+40−65 src/lib/types/Point.ts+2−2 src/lib/useKeyfileGenerator.ts+13−36 src/lib/useMultikeyFileGenerator.ts+11−11 src/lib/useRoller.ts+20−8 src/views/Point/Point.tsx+1−3 src/views/UrbitOS/Home.tsx+2−2 src/views/UrbitOS/NetworkKeys.scss+19 src/views/UrbitOS/NetworkKeys.tsx+132−113
src/components/DownloadKeyfileButton.js+1−1
@@ -13,7 +13,7 @@ export default function DownloadKeyfileButton({
1313
1414 // from caller
1515 className,
16− children = 'Download Arvo Keyfile',
16+ children = 'Download Keyfile',
1717 ...rest
1818}) {
1919 return (
src/components/L2/PendingTransaction.tsx−20
@@ -1,29 +1,9 @@
11import { Box, Row, Icon } from '@tlon/indigo-react';
2−import { L1TxnType } from 'lib/types/PendingL1Transaction';
32import { useExploreTxUrl } from 'lib/explorer';
43import LayerIndicator from './LayerIndicator';
54
65import './PendingTransaction.scss';
76
8−export const getPendingL1Title = (type?: L1TxnType) =>
9− type === L1TxnType.setNetworkKeys
10− ? 'Network Keys Configured'
11− : type === L1TxnType.managementProxy
12− ? 'Management Address Changed'
13− : type === L1TxnType.transferProxy
14− ? 'Transfer Proxy Changed'
15− : type === L1TxnType.spawnProxy
16− ? 'Spawn Proxy Changed'
17− : type === L1TxnType.spawnPoint
18− ? 'Spawn Point'
19− : type === L1TxnType.acceptTransfer
20− ? 'Accept Transfer'
21− : type === L1TxnType.migrate
22− ? 'Migrate to Layer 2'
23− : type === L1TxnType.migrateSpawn
24− ? 'Set Spawn Proxy to Layer 2'
25− : '';
26−
277interface Props {
288 title: string;
299 nextRoll?: string;
src/components/NetworkKeys.js+40−65
@@ -1,14 +1,11 @@
11import React from 'react';
22import { Grid, Flex, H5 } from 'indigo-react';
3−import { Just, Nothing } from 'folktale/maybe';
43
54import { formatDotsWithTime } from 'lib/dateFormat';
6−import { segmentNetworkKey, CURVE_ZERO_ADDR } from 'lib/keys';
5+import { segmentNetworkKey } from 'lib/keys';
76
87import { usePointCache } from 'store/pointCache';
9−
10−const chainKeyProp = name => d =>
11− d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
8+import { useRollerStore } from 'store/rollerStore';
129
1310const renderNetworkKey = key => {
1411 const segments = segmentNetworkKey(key);
@@ -20,40 +17,38 @@ const renderNetworkKey = key => {
2017 return segments.join('\n');
2118};
2219
23−export default function NetworkKeys({ point }) {
24− const { getDetails, getRekeyDate } = usePointCache();
25−
26− const details = getDetails(point);
20+export default function NetworkKeys() {
21+ const { getRekeyDate } = usePointCache();
22+ const { point } = useRollerStore();
2723
28− const hasKeys = details.matchWith({
29− Nothing: () => false,
30− Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
31− // we actually don't mind the default NaN behavior of parseInt here,
32− // since NaN > 0 === false and that's a reasonable result
33− });
24+ const {
25+ networkKeysSet,
26+ keyRevisionNumber,
27+ continuityNumber,
28+ cryptoSuiteVersion,
29+ authenticationKey,
30+ encryptionKey,
31+ } = point;
3432
3533 const renderNetworkKeySection = (title, key) => (
3634 <>
3735 <Grid.Item full as={H5} className="mt3 gray4">
3836 {title}
3937 </Grid.Item>
40− {key.matchWith({
41− Nothing: () => (
42− <Grid.Item full as="code" className="f5 mono gray4">
43− Not set
38+ {key ? (
39+ <>
40+ <Grid.Item full as="div" className="f5 mono">
41+ 0x
4442 </Grid.Item>
45− ),
46− Just: ({ value: key }) => (
47− <>
48− <Grid.Item full as="div" className="f5 mono">
49− 0x
50− </Grid.Item>
51− <Grid.Item full as="code" className="f5 mono wrap">
52− {renderNetworkKey(key)}
53− </Grid.Item>
54− </>
55− ),
56− })}
43+ <Grid.Item full as="code" className="f5 mono wrap">
44+ {renderNetworkKey(key)}
45+ </Grid.Item>
46+ </>
47+ ) : (
48+ <Grid.Item full as="code" className="f5 mono gray4">
49+ Not set
50+ </Grid.Item>
51+ )}
5752 </>
5853 );
5954
@@ -62,26 +57,18 @@ export default function NetworkKeys({ point }) {
6257 <Flex.Item style={{ minWidth: 192, marginBottom: 8 }} as={H5}>
6358 {title}
6459 </Flex.Item>
65− {value.matchWith({
66− Nothing: () => (
67− <Flex.Item as={H5} className="f5 gray4">
68− Not set
69− </Flex.Item>
70− ),
71− Just: ({ value }) => <Flex.Item as={H5}>{value}</Flex.Item>,
72− })}
60+ {value === 'Not set' ? (
61+ <Flex.Item as={H5} className="f5 gray4">
62+ Not set
63+ </Flex.Item>
64+ ) : (
65+ <Flex.Item as={H5}>{value}</Flex.Item>
66+ )}
7367 </Flex.Item>
7468 );
7569
76− const revisionNumber = details
77− .map(d => d.keyRevisionNumber)
78− .matchWith({
79− Nothing: () => 0,
80− Just: ({ value }) => value,
81− });
82−
83− const revisionTime = hasKeys
84− ? ` at ${getRekeyDate(point)
70+ const revisionTime = networkKeysSet
71+ ? ` at ${getRekeyDate(point.value)
8572 .map(date =>
8673 date.matchWith({
8774 Ok: r => formatDotsWithTime(r.value),
@@ -98,25 +85,13 @@ export default function NetworkKeys({ point }) {
9885 <>
9986 <h5 className="fw-bold">Network Keys</h5>
10087 <h5 className="gray4">
101− Revision {revisionNumber} {revisionTime}
88+ Revision {keyRevisionNumber} {revisionTime}
10289 </h5>
103− {renderNetworkKeySection(
104− 'Authentication Key',
105− details.chain(chainKeyProp('authenticationKey'))
106− )}
107− {renderNetworkKeySection(
108− 'Encryption Key',
109− details.chain(chainKeyProp('encryptionKey'))
110− )}
90+ {renderNetworkKeySection('Authentication Key', authenticationKey)}
91+ {renderNetworkKeySection('Encryption Key', encryptionKey)}
11192 <Grid.Item full as={Flex} col justify="between" className="mt3">
112− {renderDetail(
113− 'Continuity Era',
114− details.map(d => d.continuityNumber)
115− )}
116− {renderDetail(
117− 'Crypto Suite Version',
118− details.map(d => d.cryptoSuiteVersion)
119− )}
93+ {renderDetail('Continuity Era', continuityNumber)}
94+ {renderDetail('Crypto Suite Version', cryptoSuiteVersion)}
12095 </Grid.Item>
12196 </>
12297 );
src/lib/types/Point.ts+2−2
@@ -85,7 +85,7 @@ export default class Point {
8585 escapeRequested: boolean;
8686 escapeRequestedTo: string;
8787 hasSponsor: boolean;
88− keyRevisionNumber: string;
88+ keyRevisionNumber: number;
8989 managementProxy: string;
9090 owner: string;
9191 spawnProxy: string;
@@ -148,7 +148,7 @@ export default class Point {
148148 this.escapeRequested = details.escapeRequested;
149149 this.escapeRequestedTo = details.escapeRequestedTo;
150150 this.hasSponsor = details.hasSponsor;
151− this.keyRevisionNumber = details.keyRevisionNumber;
151+ this.keyRevisionNumber = Number(details.keyRevisionNumber);
152152 this.managementProxy = details.managementProxy;
153153 this.owner = details.owner;
154154 this.spawnProxy = details.spawnProxy;
src/lib/useKeyfileGenerator.ts+13−36
@@ -1,13 +1,10 @@
11import { useCallback, useMemo, useEffect, useState } from 'react';
22import { Just, Nothing } from 'folktale/maybe';
33import saveAs from 'file-saver';
4−import ob from 'urbit-ob';
54
6−import { usePointCache } from 'store/pointCache';
75import { useWallet } from 'store/wallet';
8−import { usePointCursor } from 'store/pointCursor';
6+import { useRollerStore } from 'store/rollerStore';
97
10−import * as need from 'lib/need';
118import { generateCode } from 'lib/networkCode';
129
1310import {
@@ -16,8 +13,6 @@ import {
1613 deriveNetworkKeys,
1714 compileNetworkKey,
1815} from './keys';
19−import useCurrentPermissions from './useCurrentPermissions';
20−import { convertToInt } from './convertToInt';
2116import { stripSigPrefix } from 'form/formatters';
2217
2318// overrideable defaults:
@@ -32,13 +27,9 @@ interface useKeyfileGeneratorArgs {
3227 * @deprecated
3328 * Use `useMultikeyFileGenerator` instead
3429 */
35−export default function useKeyfileGenerator({
36− seed,
37− point,
38−}: useKeyfileGeneratorArgs) {
30+export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
3931 const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
40− const { pointCursor }: any = usePointCursor();
41− const { syncDetails, getDetails }: any = usePointCache();
32+ const { point } = useRollerStore();
4233
4334 const [notice, setNotice] = useState('Deriving networking keys...');
4435 const [downloaded, setDownloaded] = useState(false);
@@ -46,22 +37,11 @@ export default function useKeyfileGenerator({
4637 const [keyfile, setKeyfile] = useState<boolean | string>(false);
4738 const [code, setCode] = useState(false);
4839
49− const _point = point || need.point(pointCursor);
50− useEffect(() => {
51− syncDetails(_point);
52− }, [_point, syncDetails]);
53−
54− const details = getDetails(_point);
55−
56− const networkRevision = details.matchWith({
57− Just: ({ value }) => convertToInt(value.keyRevisionNumber, 10),
58− Nothing: () => 0,
59− });
60− const { isOwner, isManagementProxy } = useCurrentPermissions();
40+ const networkRevision = Number(point.keyRevisionNumber);
41+ const canManage = point.isOwner || point.isManagementProxy;
6142
6243 const hasNetworkKeys = networkRevision > 0;
63− const available =
64− (isOwner || isManagementProxy) && hasNetworkKeys && !!keyfile;
44+ const available = canManage && hasNetworkKeys && !!keyfile;
6545
6646 const generate = useCallback(async () => {
6747 if (!hasNetworkKeys) {
@@ -73,17 +53,15 @@ export default function useKeyfileGenerator({
7353 return;
7454 }
7555
76− const _details = need.details(details);
77−
7856 const networkSeed = seed
7957 ? Just(seed)
8058 : await attemptNetworkSeedDerivation({
8159 urbitWallet,
8260 wallet,
8361 authMnemonic,
84− details: _details,
62+ details: point,
8563 authToken,
86− point: _point,
64+ point: point.value,
8765 revision: networkRevision,
8866 });
8967
@@ -100,7 +78,7 @@ export default function useKeyfileGenerator({
10078
10179 const pair = deriveNetworkKeys(_networkSeed);
10280
103− if (!keysMatchChain(pair, _details)) {
81+ if (!keysMatchChain(pair, point)) {
10482 setGenerating(false);
10583 setNotice('Derived networking keys do not match on-chain details.');
10684 console.log(`keys do not match details for revision ${networkRevision}`);
@@ -109,7 +87,7 @@ export default function useKeyfileGenerator({
10987
11088 setNotice('');
11189 setCode(generateCode(pair));
112− setKeyfile(compileNetworkKey(pair, _point, networkRevision));
90+ setKeyfile(compileNetworkKey(pair, point.value, networkRevision));
11391 setGenerating(false);
11492 }, [
11593 networkRevision,
@@ -119,14 +97,13 @@ export default function useKeyfileGenerator({
11997 wallet,
12098 authMnemonic,
12199 setCode,
122− details,
123− _point,
100+ point,
124101 authToken,
125102 ]);
126103
127104 const filename = useMemo(() => {
128− return `${stripSigPrefix(ob.patp(_point))}-${networkRevision}.key`;
129− }, [_point, networkRevision]);
105+ return `${stripSigPrefix(point.patp)}-${networkRevision}.key`;
106+ }, [point, networkRevision]);
130107
131108 const download = useCallback(() => {
132109 if (typeof keyfile !== 'string') {
src/lib/useMultikeyFileGenerator.ts+11−11
@@ -18,6 +18,7 @@ import { generateCode } from './networkCode';
1818import { L1Point } from './types/L1Point';
1919import useCurrentPermissions from './useCurrentPermissions';
2020import { stripSigPrefix } from 'form/formatters';
21+import { useRollerStore } from 'store/rollerStore';
2122
2223interface useMultikeyFileGeneratorArgs {
2324 seed?: string;
@@ -39,6 +40,7 @@ export default function useMultikeyFileGenerator({
3940 const { pointCursor }: any = usePointCursor();
4041 const { syncDetails, getDetails }: any = usePointCache();
4142 const { isOwner, isManagementProxy } = useCurrentPermissions();
43+ const rollerStore = useRollerStore();
4244
4345 const [notice, setNotice] = useState('Deriving networking keys...');
4446 const [downloaded, setDownloaded] = useState(false);
@@ -53,20 +55,18 @@ export default function useMultikeyFileGenerator({
5355 const details = getDetails(_point);
5456 const _details: L1Point | null = useMemo(
5557 () =>
56− details.matchWith({
57− Just: d => need.details(d),
58− Nothing: () => null,
59− }),
60− [details]
58+ rollerStore?.point?.isL2
59+ ? rollerStore.point
60+ : details.matchWith({
61+ Just: (d: any) => need.details(d),
62+ Nothing: () => null,
63+ }),
64+ [details, rollerStore.point]
6165 );
6266
6367 const currentRevision: number = useMemo(
64− () =>
65− details.matchWith({
66− Just: ({ value }) => convertToInt(value.keyRevisionNumber, 10),
67− Nothing: () => 0,
68− }),
69− [details]
68+ () => convertToInt(_details?.keyRevisionNumber || 0, 10),
69+ [_details]
7070 );
7171 const nextRevision = useMemo(() => currentRevision + 1, [currentRevision]);
7272
src/lib/useRoller.ts+20−8
@@ -30,6 +30,7 @@ import {
3030 getTimeToNextBatch,
3131 registerProxyAddress,
3232 isL2Spawn,
33+ isL2,
3334} from './utils/roller';
3435import { ETH_ZERO_ADDR, ROLLER_HOSTS, TEN_SECONDS } from './constants';
3536
@@ -60,6 +61,7 @@ interface UpdateParams {
6061 notify?: boolean;
6162 field?: PointField;
6263 l1Txn?: PendingL1Txn;
64+ intervalTime?: number;
6365}
6466
6567const inviteTemplate = (
@@ -191,8 +193,9 @@ export default function useRoller() {
191193 const initPoint = useCallback(
192194 async (point: string | number) => {
193195 const _wallet = wallet.getOrElse(null);
196+ const _contracts = contracts.getOrElse(null);
194197
195− if (!_wallet) {
198+ if (!_wallet || !_contracts) {
196199 return EMPTY_POINT;
197200 }
198201
@@ -202,7 +205,10 @@ export default function useRoller() {
202205 const l2Quota = isL2Spawn(rawDetails?.dominion)
203206 ? await api.getRemainingQuota(pointNum)
204207 : 0;
205− const details = toL1Details(rawDetails);
208+
209+ const details = isL2(rawDetails?.dominion)
210+ ? toL1Details(rawDetails)
211+ : await azimuth.azimuth.getPoint(_contracts, point);
206212
207213 return new Point({
208214 value: pointNum,
@@ -239,7 +245,14 @@ export default function useRoller() {
239245 );
240246
241247 const checkForUpdates = useCallback(
242− async ({ point, message, notify = true, field, l1Txn }: UpdateParams) => {
248+ async ({
249+ point,
250+ message,
251+ notify = true,
252+ field,
253+ l1Txn,
254+ intervalTime = TEN_SECONDS,
255+ }: UpdateParams) => {
243256 if (l1Txn) storePendingL1Txn(l1Txn);
244257
245258 const interval = setInterval(async () => {
@@ -262,7 +275,7 @@ export default function useRoller() {
262275 );
263276 }
264277 }
265− }, TEN_SECONDS);
278+ }, intervalTime);
266279 },
267280 [points, initPoint, updatePoint, storePendingL1Txn, deletePendingL1Txn]
268281 );
@@ -604,8 +617,7 @@ export default function useRoller() {
604617 }
605618 const _wallet = wallet.getOrElse(null);
606619 const _web3 = web3.getOrElse(null);
607− const _details = getDetails(point.value);
608− if (!_wallet || !_web3 || !_details || point.isDefault) {
620+ if (!_wallet || !_web3 || point.isDefault) {
609621 // not using need because we want a custom error
610622 throw new Error('Internal Error: Missing Wallet/Details');
611623 }
@@ -627,9 +639,9 @@ export default function useRoller() {
627639 urbitWallet,
628640 wallet,
629641 authMnemonic,
630− details: _details,
631− point: point.value,
642+ details: point,
632643 authToken,
644+ point: point.value,
633645 revision: nextRevision,
634646 });
635647 const txHash = await submitL2Transaction({
src/views/Point/Point.tsx+1−3
@@ -21,9 +21,7 @@ import useSeenMissingKeys from 'lib/useSeenMissingKeys';
2121import { useHasNetworkKeysSet } from 'lib/useHasNetworkKeysSet';
2222
2323import LoadingOverlay from 'components/L2/LoadingOverlay';
24−import PendingTransaction, {
25− getPendingL1Title,
26−} from 'components/L2/PendingTransaction';
24+import PendingTransaction from 'components/L2/PendingTransaction';
2725import Modal from 'components/L2/Modal';
2826import './Point.scss';
2927import { TransactionRow } from 'views/Transactions/TransactionRow';
src/views/UrbitOS/Home.tsx+2−2
@@ -30,7 +30,7 @@ export default function UrbitOSHome() {
3030 const toggleShowKeys = useCallback(() => setShowKeys(s => !s), [setShowKeys]);
3131
3232 // The initial key revision number is 0, and increments when set
33− const hasSetNetworkKeys = point.keyRevisionNumber !== '0';
33+ const hasSetNetworkKeys = Number(point.keyRevisionNumber) > 0;
3434
3535 const goNetworkKeys = useCallback(() => push(names.NETWORKING_KEYS), [
3636 names,
@@ -118,7 +118,7 @@ export default function UrbitOSHome() {
118118
119119 <Modal show={showKeys} hide={() => setShowKeys(false)}>
120120 <Box className="show-keys-modal-content">
121− <Grid.Item full as={NetworkKeys} point={point.value} />
121+ <Grid.Item full as={NetworkKeys} />
122122 </Box>
123123 </Modal>
124124 </Window>
src/views/UrbitOS/NetworkKeys.scss+19
@@ -4,6 +4,25 @@
44 font-size: 14px;
55
66 .body-pane {
7+ height: 360px;
8+
9+ .download-message {
10+ align-items: center;
11+ width: 350px;
12+ //styleName: Sans/UI + Control;
13+ font-family: Inter;
14+ line-height: 16px;
15+ letter-spacing: 0em;
16+ text-align: center;
17+ margin: 90px auto 40px;
18+
19+ .download {
20+ height: 24px;
21+ width: 24px;
22+ margin-bottom: 22px;
23+ }
24+ }
25+
726 .contents {
827 height: 448px;
928 width: 100%;
src/views/UrbitOS/NetworkKeys.tsx+132−113
@@ -9,9 +9,8 @@ import { Just } from 'folktale/maybe';
99import { Grid, Button } from 'indigo-react';
1010import * as azimuth from 'azimuth-js';
1111import { randomHex } from 'web3-utils';
12−import { Box, Checkbox, Row } from '@tlon/indigo-react';
12+import { Box, Checkbox, Icon, Row } from '@tlon/indigo-react';
1313
14−import { usePointCursor } from 'store/pointCursor';
1514import { usePointCache } from 'store/pointCache';
1615import { useNetwork } from 'store/network';
1716import { useWallet } from 'store/wallet';
@@ -26,14 +25,13 @@ import {
2625 CRYPTO_SUITE_VERSION,
2726} from 'lib/keys';
2827import useEthereumTransaction from 'lib/useEthereumTransaction';
29−import { GAS_LIMITS } from 'lib/constants';
28+import { GAS_LIMITS, ONE_SECOND } from 'lib/constants';
3029import { addHexPrefix } from 'lib/utils/address';
3130import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
3231
3332import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
3433import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
3534import NoticeBox from 'components/NoticeBox';
36−import AlertBox from 'components/AlertBox';
3735
3836import { HexInput } from 'form/Inputs';
3937import {
@@ -43,7 +41,6 @@ import {
4341} from 'form/validators';
4442import BridgeForm from 'form/BridgeForm';
4543import FormError from 'form/FormError';
46−import { convertToInt } from 'lib/convertToInt';
4744import Window from 'components/L2/Window/Window';
4845import HeaderPane from 'components/L2/Window/HeaderPane';
4946import BodyPane from 'components/L2/Window/BodyPane';
@@ -56,19 +53,16 @@ function useSetKeys(
5653 setManualNetworkSeed: (seed: string) => void
5754) {
5855 const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
59− const { pointCursor }: any = usePointCursor();
60− const { syncDetails, syncRekeyDate, getDetails }: any = usePointCache();
56+ const { point } = useRollerStore();
57+ const { syncDetails, syncRekeyDate }: any = usePointCache();
6158 const { contracts }: any = useNetwork();
6259
63− const _point = need.point(pointCursor);
6460 const _contracts = need.contracts(contracts);
65− const _details = need.details(getDetails(_point));
6661
67− const networkRevision = convertToInt(_details.keyRevisionNumber, 10);
62+ const networkRevision = Number(point.keyRevisionNumber);
6863 const randomSeed = useRef<string | null>();
6964
7065 const {
71− available: keyfileAvailable,
7266 generating: keyfileGenerating,
7367 filename,
7468 bind: keyfileBind,
@@ -87,8 +81,8 @@ function useSetKeys(
8781 urbitWallet,
8882 wallet,
8983 authMnemonic,
90− details: _details,
91− point: _point,
84+ details: point,
85+ point: point.value,
9286 authToken,
9387 revision: newNetworkRevision,
9488 });
@@ -104,13 +98,12 @@ function useSetKeys(
10498 }
10599 },
106100 [
107− _details,
108101 authMnemonic,
109102 setManualNetworkSeed,
110103 networkRevision,
111104 urbitWallet,
112105 wallet,
113− _point,
106+ point,
114107 authToken,
115108 ]
116109 );
@@ -123,24 +116,24 @@ function useSetKeys(
123116
124117 return azimuth.ecliptic.configureKeys(
125118 _contracts,
126− _point,
119+ point.value,
127120 addHexPrefix(pair.crypt.public),
128121 addHexPrefix(pair.auth.public),
129122 CRYPTO_SUITE_VERSION,
130123 isDiscontinuity
131124 );
132125 },
133− [_contracts, _point, buildNetworkSeed]
126+ [_contracts, point, buildNetworkSeed]
134127 ),
135128 useCallback(
136− () => Promise.all([syncDetails(_point), syncRekeyDate(_point)]),
137− [_point, syncDetails, syncRekeyDate]
129+ () => Promise.all([syncDetails(point.value), syncRekeyDate(point.value)]),
130+ [point, syncDetails, syncRekeyDate]
138131 ),
139132 GAS_LIMITS.CONFIGURE_KEYS
140133 );
141134
142135 // only treat the transaction as completed once we also have keys to download
143− const completed = _completed && keyfileAvailable && !keyfileGenerating;
136+ const completed = _completed && !keyfileGenerating;
144137
145138 return {
146139 completed,
@@ -166,6 +159,7 @@ export default function UrbitOSNetworkKeys({
166159 } = useRoller();
167160 const [breach, setBreach] = useState(false);
168161 const [useCustomSeed, setUseCustomSeed] = useState(false);
162+ const [l2Completed, setL2Completed] = useState(false);
169163
170164 const hasKeys = point.networkKeysSet;
171165
@@ -191,6 +185,7 @@ export default function UrbitOSNetworkKeys({
191185 hash: txHashes[0],
192186 time: new Date().getTime(),
193187 },
188+ intervalTime: ONE_SECOND,
194189 });
195190 }
196191 }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps
@@ -259,8 +254,9 @@ export default function UrbitOSNetworkKeys({
259254 checkForUpdates({
260255 point: point.value,
261256 message: `${point.patp}'s Network Keys have been set!`,
257+ intervalTime: ONE_SECOND,
262258 });
263− pop();
259+ setTimeout(() => setL2Completed(true), ONE_SECOND);
264260 } catch (error) {
265261 // setError(error);
266262 } finally {
@@ -271,7 +267,6 @@ export default function UrbitOSNetworkKeys({
271267 manualNetworkSeed,
272268 getPendingTransactions,
273269 point,
274− pop,
275270 configureNetworkKeys,
276271 setLoading,
277272 checkForUpdates,
@@ -279,10 +274,18 @@ export default function UrbitOSNetworkKeys({
279274
280275 const initialValues = useMemo(() => ({}), []);
281276
282− const viewTitle = completed ? 'Network Keys are now set' : 'Set Network Keys';
277+ const viewTitle = completed
278+ ? 'Network Keys have been set'
279+ : hasKeys
280+ ? 'Set Network Keys'
281+ : 'Initiate Network Keys';
283282
284283 const usageMessage =
285− 'You need this keyfile to authenticate with your OS. Please Download.';
284+ 'With your new network keys, you also have a new keyfile. Download your new keyfile to boot your OS.';
285+
286+ const buttonText = `${hasKeys ? 'Reset' : 'Set'} Network Keys`;
287+
288+ const isComplete = completed || l2Completed;
286289
287290 return (
288291 <Window className="network-keys">
@@ -290,98 +293,114 @@ export default function UrbitOSNetworkKeys({
290293 <h5>{viewTitle}</h5>
291294 </HeaderPane>
292295 <BodyPane>
293− {completed && (
294− <>
295− <Grid.Item
296− full
297− className="mb3"
298− as={DownloadKeyfileButton}
299− solid
300− left
301− {...keyfileBind}
302− />
303− <Grid.Item full as={AlertBox} className="mb8">
304− {usageMessage}
305− </Grid.Item>
306− </>
296+ {isComplete && (
297+ <Box className="flex-col col justify-between h-full">
298+ <Box className="flex-col download-message">
299+ <Icon icon="Download" className="download" />
300+ <Box>{usageMessage}</Box>
301+ </Box>
302+ {point.isL2 ? (
303+ <Grid.Item
304+ full
305+ as={Button}
306+ solid
307+ center
308+ onClick={keyfileBind.download}>
309+ Download Keyfile
310+ </Grid.Item>
311+ ) : (
312+ <Grid.Item
313+ full
314+ as={DownloadKeyfileButton}
315+ solid
316+ center
317+ {...keyfileBind}
318+ />
319+ )}
320+ </Box>
307321 )}
308− <BridgeForm
309− validate={validate}
310− onValues={onValues}
311− initialValues={initialValues}>
312− {() => (
313− <Box className="contents">
314− {!completed && (
315− <Box>
316− <Row className="check-row" onClick={() => setBreach(!breach)}>
317− <Checkbox
318− className="checkbox"
319− selected={breach}
320− disabled={inputsLocked}
321− />
322− Factory Reset
323− </Row>
324− <Box className="info-text">
325− Use if your ship is corrupted, you lost your files, or
326− <br />
327− you want to erase your data
328− </Box>
329− <Row
330− className="check-row"
331− onClick={() => setUseCustomSeed(!useCustomSeed)}>
332− <Checkbox
333− className="checkbox"
334− selected={useCustomSeed}
335− disabled={inputsLocked}
336− />
337− Custom Network Seed
338− </Row>
339− <Box className="info-text">
340− Enter your own custom network seed to derive from
341− </Box>
342− {useCustomSeed && (
343− <>
344− <Grid.Item full as={NoticeBox} className="mb2">
345− When using a custom network seed, you'll need to
346− download your Arvo keyfile immediately after this
347− transaction as Bridge does not store your seed.
348− </Grid.Item>
349− <Grid.Item
350− full
351− as={HexInput}
352− name="networkSeed"
353− label="Network Seed (32 bytes)"
322+
323+ {!isComplete && (
324+ <BridgeForm
325+ validate={validate}
326+ onValues={onValues}
327+ initialValues={initialValues}>
328+ {() => (
329+ <Box className="contents">
330+ {!isComplete && (
331+ <Box>
332+ <Row
333+ className="check-row"
334+ onClick={() => setBreach(!breach)}>
335+ <Checkbox
336+ className="checkbox"
337+ selected={breach}
354338 disabled={inputsLocked}
355− className="mb5"
356339 />
357− </>
358− )}
359− <Grid.Item full as={FormError} />
360− </Box>
361− )}
362−
363− {point.isL2 ? (
364− <Grid.Item
365− as={Button}
366− full
367− className=""
368− center
369− solid
370− onClick={setNetworkKeys}>
371− {'Reset Network Keys'}
372− </Grid.Item>
373− ) : (
374− <Grid.Item
375− full
376− as={InlineEthereumTransaction}
377− {...bind}
378− label={`${hasKeys ? 'Reset' : 'Set'} Network Keys`}
379− onReturn={() => pop()}
380− />
381− )}
382− </Box>
383− )}
384− </BridgeForm>
340+ Factory Reset
341+ </Row>
342+ <Box className="info-text">
343+ Use if your ship is corrupted, you lost your files, or
344+ <br />
345+ you want to erase your data
346+ </Box>
347+ <Row
348+ className="check-row"
349+ onClick={() => setUseCustomSeed(!useCustomSeed)}>
350+ <Checkbox
351+ className="checkbox"
352+ selected={useCustomSeed}
353+ disabled={inputsLocked}
354+ />
355+ Custom Network Seed
356+ </Row>
357+ <Box className="info-text">
358+ Enter your own custom network seed to derive from
359+ </Box>
360+ {useCustomSeed && (
361+ <>
362+ <Grid.Item full as={NoticeBox} className="mb2">
363+ When using a custom network seed, you'll need to
364+ download your Arvo keyfile immediately after this
365+ transaction as Bridge does not store your seed.
366+ </Grid.Item>
367+ <Grid.Item
368+ full
369+ as={HexInput}
370+ name="networkSeed"
371+ label="Network Seed (32 bytes)"
372+ disabled={inputsLocked}
373+ className="mb5"
374+ />
375+ </>
376+ )}
377+ <Grid.Item full as={FormError} />
378+ </Box>
379+ )}
380+
381+ {point.isL2 ? (
382+ <Grid.Item
383+ as={Button}
384+ full
385+ className=""
386+ center
387+ solid
388+ onClick={setNetworkKeys}>
389+ {buttonText}
390+ </Grid.Item>
391+ ) : (
392+ <Grid.Item
393+ full
394+ as={InlineEthereumTransaction}
395+ {...bind}
396+ label={buttonText}
397+ onReturn={() => pop()}
398+ />
399+ )}
400+ </Box>
401+ )}
402+ </BridgeForm>
403+ )}
385404 </BodyPane>
386405 </Window>
387406 );