on master help sign in
feat: implement network key cycling
commit1a6981cec39f39a3ec19c1001d68e8c7221304d3 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateTue 16 Jul 2019, 20:03 (7 years ago) parent633e7b8
21 files changed+318−128
src/components/Buttons.js+3−1 src/components/DownloadKeyfileButton.js+13−11 src/components/InlineEthereumTransaction.js+18−4 src/indigo-react/components/Button.js+9−6 src/indigo-react/components/ToggleInput.js+3−2 src/lib/constants.js+4 src/lib/explorer.js+28 src/lib/invite.js+2−3 src/lib/keys.js+7−7 src/lib/useEthereumTransaction.js+3−6 src/lib/useInputs.js+1−1 src/lib/useKeyfileGenerator.js+42−30 src/lib/usePermissionsForPoint.js+5−1 src/lib/walletgen.js+2−2 src/views/Admin/AdminEditPermissions.js+3−5 src/views/Admin/AdminHome.js−1 src/views/Admin/AdminNetworkingKeys.js+145−39 src/views/Admin/AdminSetProxy.js+22−7 src/views/Admin/Reticket/ReticketConfirm.js+7 src/views/Invite/InviteEmail.js+1−1 src/views/Point.js−1
src/components/Buttons.js+3−1
@@ -6,7 +6,9 @@ import Blinky from './Blinky';
66export const ForwardButton = ({ loading, ...props }) => (
77 <Button accessory={loading ? <Blinky /> : '->'} {...props} />
88);
9−export const DownloadButton = props => <Button accessory="↓" {...props} />;
9+export const DownloadButton = ({ loading, ...props }) => (
10+ <Button accessory={loading ? <Blinky /> : '↓'} {...props} />
11+);
1012export const RestartButton = props => <Button accessory="↺" {...props} />;
1113export const GenerateButton = props => (
1214 <Button accessory="○" solid {...props} />
src/components/DownloadKeyfileButton.js+13−11
@@ -1,29 +1,31 @@
11import React from 'react';
22
33import { DownloadButton } from 'components/Buttons';
4−import Blinky from 'components/Blinky';
54
65import { usePointCursor } from 'store/pointCursor';
76
87import useKeyfileGenerator from 'lib/useKeyfileGenerator';
98import * as need from 'lib/need';
109
11−export default function DownloadKeyfileButton({ className, ...rest }) {
10+export default function DownloadKeyfileButton({
11+ networkSeed,
12+ className,
13+ ...rest
14+}) {
1215 const { pointCursor } = usePointCursor();
13− const point = need.point(pointCursor);
14− const {
15− generating,
16− available,
17− downloaded,
18− generateAndDownload,
19− } = useKeyfileGenerator(point);
16+ const _point = need.point(pointCursor);
17+
18+ const { generating, available, downloaded, download } = useKeyfileGenerator(
19+ _point,
20+ networkSeed
21+ );
2022
2123 return (
2224 <DownloadButton
2325 className={className}
2426 disabled={downloaded || !available}
25− accessory={generating ? <Blinky /> : undefined}
26− onClick={generateAndDownload}
27+ loading={generating}
28+ onClick={download}
2729 {...rest}>
2830 Download Arvo Keyfile
2931 </DownloadButton>
src/components/InlineEthereumTransaction.js+18−4
@@ -1,4 +1,5 @@
11import React from 'react';
2+import cn from 'classnames';
23import {
34 Grid,
45 ToggleInput,
@@ -10,6 +11,8 @@ import {
1011import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
1112import { useCheckboxInput } from 'lib/useInputs';
1213import Blinky from './Blinky';
14+import { hexify } from 'lib/txn';
15+import { useExploreTxUrl } from 'lib/explorer';
1316
1417export default function InlineEthereumTransaction({
1518 // from useEthereumTransaction.bind
@@ -24,14 +27,17 @@ export default function InlineEthereumTransaction({
2427 error,
2528 setGasPrice,
2629 txHash,
30+ signedTransaction,
2731
28− // additional
32+ // additional from parent
2933 className,
3034 onReturn,
3135}) {
36+ const exploreHashUrl = useExploreTxUrl(txHash);
3237 const [configureInput, { data: configurationOpen }] = useCheckboxInput({
3338 name: 'configure',
3439 label: 'Configure Transaction',
40+ inverseLabel: 'Cancel Configure Transaction',
3541 disabled: signed || broadcasted || confirmed,
3642 });
3743
@@ -41,6 +47,7 @@ export default function InlineEthereumTransaction({
4147 ] = useCheckboxInput({
4248 name: 'viewsigned',
4349 label: 'View Signed Transaction',
50+ inverseLabel: 'Hide Signed Transaction',
4451 disabled: !signed,
4552 });
4653
@@ -105,7 +112,7 @@ export default function InlineEthereumTransaction({
105112 };
106113
107114 return (
108− <Grid className={className}>
115+ <Grid className={cn(className, 'mt1')}>
109116 {renderButton()}
110117
111118 {error && (
@@ -119,7 +126,14 @@ export default function InlineEthereumTransaction({
119126 )}
120127
121128 {showSignedTx && (
122− <Grid.Item full as={ToggleInput} {...viewSignedTransaction} />
129+ <>
130+ <Grid.Item full as={ToggleInput} {...viewSignedTransaction} />
131+ {signedTransactionOpen && (
132+ <Grid.Item full as="code" className="f6 mono gray4 wrap">
133+ {hexify(signedTransaction.serialize())}
134+ </Grid.Item>
135+ )}
136+ </>
123137 )}
124138
125139 {showReceipt && (
@@ -128,7 +142,7 @@ export default function InlineEthereumTransaction({
128142 <Grid.Item full as={Flex} col className="pv4">
129143 <Flex.Item as={Flex} row justify="between">
130144 <Flex.Item as={H5}>Transaction Hash</Flex.Item>
131− <Flex.Item as={LinkButton} href="https://google.com">
145+ <Flex.Item as={LinkButton} href={exploreHashUrl}>
132146 Etherscan↗
133147 </Flex.Item>
134148 </Flex.Item>
src/indigo-react/components/Button.js+9−6
@@ -17,11 +17,6 @@ export default function Button({
1717 children,
1818 ...rest
1919}) {
20− const textColor = {
21− white: solid,
22− black: !solid && !disabled,
23− gray4: !solid && disabled,
24− };
2520 return (
2621 <Grid
2722 as={As}
@@ -48,7 +43,15 @@ export default function Button({
4843 }}
4944 onClick={!disabled && onClick ? onClick : undefined}
5045 {...rest}>
51− <Grid.Item full as={Flex} justify="between" className={cn(textColor)}>
46+ <Grid.Item
47+ full
48+ as={Flex}
49+ justify="between"
50+ className={cn({
51+ white: solid,
52+ black: !solid && !disabled,
53+ gray4: !solid && disabled,
54+ })}>
5255 <span>{children}</span>
5356 <div className={cn('pl4')}>{accessory}</div>
5457 </Grid.Item>
src/indigo-react/components/ToggleInput.js+3−2
@@ -8,6 +8,7 @@ export default function ToggleInput({
88 // visuals
99 name,
1010 label,
11+ inverseLabel,
1112 className,
1213
1314 // callbacks
@@ -55,12 +56,12 @@ export default function ToggleInput({
5556 <Flex.Item
5657 flex
5758 as="label"
58− className={cn('f6 pv1 lh-tall us-none flex-row align-center', {
59+ className={cn('f6 pv2 lh-tall us-none flex-row align-center', {
5960 clickable: !disabled,
6061 })}
6162 htmlFor={name}>
6263 <LinkButton disabled={disabled} className="f5">
63− {bind.checked ? `Hide ${label}` : label}
64+ {bind.checked ? inverseLabel : label}
6465 </LinkButton>
6566 </Flex.Item>
6667 </Flex>
src/lib/constants.js+4
@@ -17,6 +17,9 @@ const GALAXY_ENTROPY_BITS = 384;
1717
1818const SEED_ENTROPY_BITS = 128;
1919
20+const ZERO_KEY =
21+ '0x0000000000000000000000000000000000000000000000000000000000000000';
22+
2023//TODO move into azimuth-js
2124const GAS_LIMITS = {
2225 SPAWN: 250000,
@@ -83,4 +86,5 @@ export {
8386 GALAXY_ENTROPY_BITS,
8487 SEED_ENTROPY_BITS,
8588 ZOD,
89+ ZERO_KEY,
8690};
src/lib/explorer.jsnew file+28
@@ -0,0 +1,28 @@
1+import { useNetwork } from 'store/network';
2+import { NETWORK_TYPES } from './network';
3+
4+function useEtherscanDomain() {
5+ const { networkType } = useNetwork();
6+
7+ switch (networkType) {
8+ case NETWORK_TYPES.ROPSTEN:
9+ return 'https://ropsten.etherscan.io';
10+ case NETWORK_TYPES.OFFLINE:
11+ case NETWORK_TYPES.MAINNET:
12+ case NETWORK_TYPES.LOCAL:
13+ default:
14+ return 'https://etherscan.io';
15+ }
16+}
17+
18+export function useExploreTxUrl(txHash) {
19+ const domain = useEtherscanDomain();
20+
21+ return `${domain}/tx/${txHash}`;
22+}
23+
24+export function useExploreAddressUrl(address) {
25+ const domain = useEtherscanDomain();
26+
27+ return `${domain}/address/${address}`;
28+}
src/lib/invite.js+2−3
@@ -1,6 +1,5 @@
11import Tx from 'ethereumjs-tx';
22import * as azimuth from 'azimuth-js';
3−import * as kg from 'urbit-key-generation/dist';
43import * as wg from './walletgen';
54import * as tank from './tank';
65
@@ -8,7 +7,7 @@ import JSZip from 'jszip';
87import saveAs from 'file-saver';
98
109import { hexify } from './txn';
11−import { deriveNetworkSeedFromUrbitWallet } from './keys';
10+import { deriveNetworkSeedFromUrbitWallet, deriveNetworkKeys } from './keys';
1211import { addHexPrefix } from './wallet';
1312import { sendAndAwaitAll } from './txn';
1413import { GAS_LIMITS } from './constants';
@@ -132,7 +131,7 @@ export async function reticketPointBetweenWallets({
132131 Just: p => p.value,
133132 });
134133
135− const networkKeys = kg.deriveNetworkKeys(networkSeed);
134+ const networkKeys = deriveNetworkKeys(networkSeed);
136135
137136 const keysTx = azimuth.ecliptic.configureKeys(
138137 contracts,
src/lib/keys.js+7−7
@@ -84,12 +84,12 @@ export const deriveNetworkSeedFromUrbitWallet = async (
8484 urbitWallet,
8585 revision = 1
8686) => {
87− return deriveNetworkSeedFromSeed(urbitWallet.management.seed, revision);
87+ return await deriveNetworkSeedFromSeed(urbitWallet.management.seed, revision);
8888};
8989
9090/**
9191 * @param {Maybe<any>} wallet
92− * @param {Maybe<string>} authMnemonic
92+ * @param {string} authMnemonic
9393 * @param {object} details
9494 * @param {number} revision
9595 * @return {Promise<Maybe<string>>}
@@ -100,9 +100,12 @@ export const deriveNetworkSeedFromMnemonic = async (
100100 details,
101101 revision = 1
102102) => {
103+ const isOwner = eqAddr(wallet.address, details.owner);
103104 const isManagementProxy = eqAddr(wallet.address, details.managementProxy);
104105
105− if (isManagementProxy) {
106+ // the network seed is only derivable from mnemonic if the derived
107+ // management seed or owner equals the record we have on chain
108+ if (isOwner || isManagementProxy) {
106109 return await deriveNetworkSeedFromSeed(authMnemonic, revision);
107110 }
108111
@@ -115,10 +118,7 @@ export const deriveNetworkSeedFromMnemonic = async (
115118 * @return {Promise<Maybe<string>>}
116119 */
117120const deriveNetworkSeedFromSeed = async (seed, revision) => {
118− const networkSeed = await kg.deriveNetworkSeed(seed, '', revision);
119− // apparently deriveNetworkSeed can return empty string
120− // and perhaps even malformed
121− return networkSeed === '' ? Nothing() : Just(networkSeed);
121+ return Just(await kg.deriveNetworkSeed(seed, '', revision));
122122};
123123
124124/**
src/lib/useEthereumTransaction.js+3−6
@@ -1,5 +1,4 @@
11import { useCallback, useEffect, useState } from 'react';
2−import { Just } from 'folktale/maybe';
32
43import { useNetwork } from 'store/network';
54import { useWallet } from 'store/wallet';
@@ -8,10 +7,9 @@ import {
87 signTransaction,
98 sendSignedTransaction,
109 waitForTransactionConfirm,
11− hexify,
1210} from './txn';
1311import * as need from './need';
14−import { toHex, toWei, fromWei } from 'web3-utils';
12+import { fromWei } from 'web3-utils';
1513
1614const STATE = {
1715 NONE: 'NONE',
@@ -66,9 +64,6 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
6664 const generateAndSign = useCallback(async () => {
6765 try {
6866 setError(undefined);
69− // TODO: set varaibles
70− // txn.gas = toHex(gasLimit);
71− // txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
7267
7368 const txn = await signTransaction({
7469 wallet: _wallet,
@@ -160,6 +155,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
160155 error,
161156 inputsLocked,
162157 txHash,
158+ signedTransaction,
163159 bind: {
164160 constructed,
165161 canSign,
@@ -172,6 +168,7 @@ export default function useEthereumTransaction(initialGasLimit = 0) {
172168 error,
173169 setGasPrice,
174170 txHash,
171+ signedTransaction,
175172 },
176173 };
177174}
src/lib/useInputs.js+1−1
@@ -1,4 +1,4 @@
1−import React, { useCallback, useState, useMemo } from 'react';
1+import React, { useCallback, useMemo } from 'react';
22import { AccessoryIcon, useForm } from 'indigo-react';
33import { identity } from 'lodash';
44import * as bip39 from 'bip39';
src/lib/useKeyfileGenerator.js+42−30
@@ -1,4 +1,5 @@
1−import { useCallback, useState } from 'react';
1+import { useCallback, useEffect, useState } from 'react';
2+import { Just, Nothing } from 'folktale/maybe';
23import saveAs from 'file-saver';
34import * as ob from 'urbit-ob';
45
@@ -15,55 +16,67 @@ import {
1516} from './keys';
1617import usePermissionsForPoint from './usePermissionsForPoint';
1718
18−export default function useKeyfileGenerator(point) {
19+export default function useKeyfileGenerator(point, manualNetworkSeed) {
1920 const { getDetails } = usePointCache();
2021 const { urbitWallet, wallet, authMnemonic } = useWallet();
2122
2223 const [downloaded, setDownloaded] = useState(false);
2324 const [generating, setGenerating] = useState(false);
24− const [keyfile, setKeyfile] = useState();
25+ const [keyfile, setKeyfile] = useState(false);
2526
26− const details = need.details(getDetails(point));
27− const networkRevision = parseInt(details.keyRevisionNumber, 10);
27+ const _address = need.wallet(wallet).address;
28+ const _details = need.details(getDetails(point));
29+
30+ const networkRevision = parseInt(_details.keyRevisionNumber, 10);
31+ const { isOwner, isManagementProxy } = usePermissionsForPoint(
32+ _address,
33+ point
34+ );
2835
29− const { isOwner, isManagementProxy } = usePermissionsForPoint(point);
3036 const hasNetworkingKeys = networkRevision > 0;
31− const available = (isOwner || isManagementProxy) && hasNetworkingKeys;
37+ const available =
38+ (isOwner || isManagementProxy) && hasNetworkingKeys && keyfile;
3239
3340 const generate = useCallback(async () => {
3441 setGenerating(true);
3542
36− const networkSeed = await attemptNetworkSeedDerivation({
37− urbitWallet,
38− wallet,
39− authMnemonic,
40− details,
41− revision: networkRevision,
42− });
43+ console.log('generating for network revision', networkRevision);
44+
45+ const networkSeed = manualNetworkSeed
46+ ? Just(manualNetworkSeed)
47+ : await attemptNetworkSeedDerivation({
48+ urbitWallet,
49+ wallet,
50+ authMnemonic,
51+ details: _details,
52+ revision: networkRevision,
53+ });
4354
44− const _networkSeed = need.value(networkSeed, () => {
55+ if (Nothing.hasInstance(networkSeed)) {
4556 setGenerating(false);
46− throw new Error('Could not derive networking seed.');
47− });
57+ console.log('nondeterministic seed!');
58+ // TODO: tell user their seed is nondeterministic
59+ return;
60+ }
61+
62+ const _networkSeed = networkSeed.value;
4863
4964 const pair = deriveNetworkKeys(_networkSeed);
5065
51− if (!keysMatchChain(pair, details)) {
66+ if (!keysMatchChain(pair, _details)) {
5267 setGenerating(false);
53− throw new Error(
54− 'Derived networking keys do not match public keys on chain.'
55− );
68+ console.log('Derived networking keys do not match public keys on chain.');
69+ // TODO: tell usrs their seed is nondeterministic
5670 }
5771
5872 setKeyfile(compileNetworkingKey(pair, point, networkRevision));
5973 setGenerating(false);
6074 }, [
61− setGenerating,
62− setKeyfile,
75+ manualNetworkSeed,
6376 urbitWallet,
6477 wallet,
6578 authMnemonic,
66− details,
79+ _details,
6780 networkRevision,
6881 point,
6982 ]);
@@ -77,17 +90,16 @@ export default function useKeyfileGenerator(point) {
7790 // TODO: ^ unifiy "remove tilde" calls
7891 );
7992 setDownloaded(true);
80− }, [point, keyfile, networkRevision, setDownloaded]);
93+ }, [keyfile, point, networkRevision]);
8194
82− const generateAndDownload = useCallback(async () => {
83− await generate();
84− download();
85− }, [generate, download]);
95+ useEffect(() => {
96+ generate();
97+ }, [generate]);
8698
8799 return {
88100 generating,
89101 available,
90102 downloaded,
91− generateAndDownload,
103+ download,
92104 };
93105}
src/lib/usePermissionsForPoint.js+5−1
@@ -38,15 +38,19 @@ export default function usePermissionsForPoint(address, point) {
3838
3939 const canManage = isOwner || isManagementProxy;
4040 const canTransfer = isOwner || isTransferProxy;
41−
4241 const canSpawn = (isStar || isGalaxy) && (isOwner || isSpawnProxy);
4342 const canVote = isGalaxy && (isOwner || isVotingProxy);
4443
4544 return {
45+ isPlanet,
46+ isStar,
47+ isGalaxy,
48+ //
4649 isOwner,
4750 isActiveOwner,
4851 isTransferProxy,
4952 isManagementProxy,
53+ //
5054 canManage,
5155 canTransfer,
5256 canSpawn,
src/lib/walletgen.js+2−2
@@ -55,12 +55,12 @@ export const generateWallet = async (point, ticket, boot) => {
5555 boot: boot,
5656 };
5757
58− const wallet = await kg.generateWallet(config);
59−
6058 // This is here to notify anyone who opens console because the thread
6159 // hangs, blocking UI updates so this cannot be done in the UI
6260 console.log('Generating Wallet for point address: ', point);
6361
62+ const wallet = await kg.generateWallet(config);
63+
6464 return wallet;
6565};
6666
src/views/Admin/AdminEditPermissions.js+3−5
@@ -1,4 +1,4 @@
1−import React, { useState, useCallback } from 'react';
1+import React, { useCallback } from 'react';
22import { Just, Nothing } from 'folktale/maybe';
33import * as need from 'lib/need';
44import { Grid } from 'indigo-react';
@@ -9,7 +9,6 @@ import { matchBlinkyDate } from 'components/Blinky';
99
1010import { ETH_ZERO_ADDR } from 'lib/wallet';
1111
12−import useLifecycle from 'lib/useLifecycle';
1312import { useNetwork } from 'store/network';
1413import { useWallet } from 'store/wallet';
1514import { usePointCursor } from 'store/pointCursor';
@@ -21,7 +20,6 @@ import { useLocalRouter } from 'lib/LocalRouter';
2120import capitalize from 'lib/capitalize';
2221import { eqAddr } from 'lib/wallet';
2322import MiniBackButton from 'components/MiniBackButton';
24−import FooterButton from 'components/FooterButton';
2523
2624export default function AdminEditPermissions() {
2725 const { pop, push, names } = useLocalRouter();
@@ -36,11 +34,11 @@ export default function AdminEditPermissions() {
3634 const pointSize = azimuth.getPointSize(point);
3735 const isParent = pointSize !== azimuth.PointSize.Planet;
3836 const isSenate = pointSize === azimuth.PointSize.Galaxy;
39− const userAddress = need.wallet(wallet).address;
37+ const address = need.wallet(wallet).address;
4038
4139 const details = need.details(getDetails(point));
4240 const networkRevision = parseInt(details.keyRevisionNumber, 10);
43− const { canManage, isOwner } = usePermissionsForPoint(userAddress, point);
41+ const { canManage, isOwner } = usePermissionsForPoint(address, point);
4442
4543 const goSetProxy = useCallback(
4644 proxyType => push(names.SET_PROXY, { proxyType }),
src/views/Admin/AdminHome.js−1
@@ -10,7 +10,6 @@ import { usePointCursor } from 'store/pointCursor';
1010
1111import usePermissionsForPoint from 'lib/usePermissionsForPoint';
1212
13−import FooterButton from 'components/FooterButton';
1413import ViewHeader from 'components/ViewHeader';
1514import { useLocalRouter } from 'lib/LocalRouter';
1615import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
src/views/Admin/AdminNetworkingKeys.js+145−39
@@ -1,25 +1,38 @@
1−import React, { useCallback, useState } from 'react';
2−import { Nothing } from 'folktale/maybe';
1+import React, { useCallback, useEffect, useState } from 'react';
2+import { Just, Nothing } from 'folktale/maybe';
33import { Grid, Text, H5, Flex } from 'indigo-react';
4+import * as azimuth from 'azimuth-js';
5+import { randomHex } from 'web3-utils';
46
57import { usePointCursor } from 'store/pointCursor';
68import { usePointCache } from 'store/pointCache';
79
810import { useLocalRouter } from 'lib/LocalRouter';
911import * as need from 'lib/need';
10−import { segmentNetworkKey } from 'lib/keys';
12+import {
13+ segmentNetworkKey,
14+ attemptNetworkSeedDerivation,
15+ deriveNetworkKeys,
16+} from 'lib/keys';
1117import { formatDotsWithTime } from 'lib/dateFormat';
1218
1319import ViewHeader from 'components/ViewHeader';
1420import MiniBackButton from 'components/MiniBackButton';
15−import {
16− GenerateButton,
17− BootArvoButton,
18− ForwardButton,
19−} from 'components/Buttons';
21+import { BootArvoButton, ForwardButton } from 'components/Buttons';
2022import FooterButton from 'components/FooterButton';
2123import WarningBox from 'components/WarningBox';
2224import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
25+import useEthereumTransaction from 'lib/useEthereumTransaction';
26+import { GAS_LIMITS, ZERO_KEY } from 'lib/constants';
27+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
28+import { addHexPrefix } from 'lib/wallet';
29+import { useNetwork } from 'store/network';
30+import { useWallet } from 'store/wallet';
31+
32+const CRYPTO_SUITE_VERSION = 1;
33+
34+const chainKeyProp = name => d =>
35+ d[name] === ZERO_KEY ? Nothing() : Just(d[name]);
2336
2437const renderNetworkKey = key => {
2538 const segments = segmentNetworkKey(key);
@@ -32,14 +45,81 @@ const renderNetworkKey = key => {
3245};
3346
3447function useSetKeys() {
35− const setting = false;
36− const didSet = false;
37− const setKeys = useCallback(async () => {}, []);
48+ const { urbitWallet, wallet, authMnemonic } = useWallet();
49+ const { pointCursor } = usePointCursor();
50+ const { syncOwnedPoint, getDetails } = usePointCache();
51+ const { contracts } = useNetwork();
52+ const _point = need.point(pointCursor);
53+ const _contracts = need.contracts(contracts);
54+
55+ const [isDiscontinuity] = useState(false);
56+ // TODO: set discontinuity with radio check?
57+ const [ndNetworkSeed, setNdNetworkSeed] = useState();
58+
59+ const { construct, broadcasting, confirmed, bind } = useEthereumTransaction(
60+ GAS_LIMITS.CONFIGURE_KEYS
61+ );
62+
63+ // sync point details after success
64+ useEffect(() => {
65+ if (confirmed) {
66+ syncOwnedPoint(_point);
67+ }
68+ }, [_point, confirmed, syncOwnedPoint]);
69+
70+ const setKeys = useCallback(async () => {
71+ const details = need.details(getDetails(_point));
72+ const networkRevision = parseInt(details.keyRevisionNumber, 10);
73+
74+ console.log('setting revision to ', networkRevision + 1);
75+
76+ const seed = await attemptNetworkSeedDerivation({
77+ urbitWallet,
78+ wallet,
79+ authMnemonic,
80+ details,
81+ revision: networkRevision + 1,
82+ });
83+
84+ const _networkSeed = seed.matchWith({
85+ Nothing: () => {
86+ const ndSeed = randomHex(64);
87+ setNdNetworkSeed(ndSeed);
88+ return ndSeed;
89+ },
90+ Just: p => p.value,
91+ });
92+
93+ const pair = deriveNetworkKeys(_networkSeed);
94+
95+ const txn = azimuth.ecliptic.configureKeys(
96+ _contracts,
97+ _point,
98+ addHexPrefix(pair.crypt.public),
99+ addHexPrefix(pair.auth.public),
100+ CRYPTO_SUITE_VERSION,
101+ isDiscontinuity
102+ );
103+
104+ construct(txn);
105+ }, [
106+ _contracts,
107+ _point,
108+ authMnemonic,
109+ construct,
110+ getDetails,
111+ isDiscontinuity,
112+ urbitWallet,
113+ wallet,
114+ ]);
38115
39116 return {
40− setting,
41− didSet,
42117 setKeys,
118+ construct,
119+ broadcasting,
120+ confirmed,
121+ ndNetworkSeed,
122+ bind,
43123 };
44124}
45125
@@ -55,23 +135,29 @@ export default function AdminNetworkingKeys() {
55135 Nothing: () => false,
56136 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
57137 });
58− const [configuring, setConfiguring] = useState(false);
59138
60− const { setKeys, setting, didSet } = useSetKeys();
139+ const [didRequestSetKeys, _setDidRequestSetKeys] = useState(false);
140+ const {
141+ setKeys,
142+ broadcasting,
143+ confirmed,
144+ ndNetworkSeed,
145+ bind,
146+ } = useSetKeys();
61147
62− const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
63− const configureTransaction = useCallback(() => setConfiguring(true), [
64− setConfiguring,
65− ]);
148+ const setDidRequestSetKeys = useCallback(() => {
149+ setKeys();
150+ _setDidRequestSetKeys(true);
151+ }, [setKeys, _setDidRequestSetKeys]);
66152
67− const inDefaultState = !configuring && !setting && !didSet;
153+ const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
68154
69155 const renderTitle = () => {
70− if (didSet) {
156+ if (confirmed) {
71157 return 'Networking keys are now set. Download your Keyfile to authenticate Arvo.';
72158 }
73159
74− if (setting) {
160+ if (broadcasting) {
75161 return 'Setting Network Keys...';
76162 }
77163
@@ -83,19 +169,30 @@ export default function AdminNetworkingKeys() {
83169 };
84170
85171 const renderButton = () => {
86− if (didSet) {
87− return <Grid.Item full as={DownloadKeyfileButton} solid />;
172+ if (!didRequestSetKeys) {
173+ return (
174+ <Grid.Item
175+ full
176+ as={ForwardButton}
177+ solid
178+ onClick={() => setDidRequestSetKeys(true)}>
179+ {hasKeys ? 'Reset' : 'Set'} Networking Keys
180+ </Grid.Item>
181+ );
88182 }
89183
90− if (configuring || setting) {
91− return <Grid.Item full as={GenerateButton} solid onClick={setKeys} />;
184+ if (confirmed) {
185+ return (
186+ <Grid.Item
187+ full
188+ as={DownloadKeyfileButton}
189+ solid
190+ networkSeed={ndNetworkSeed}
191+ />
192+ );
92193 }
93194
94− return (
95− <Grid.Item full as={ForwardButton} solid onClick={configureTransaction}>
96− {hasKeys ? 'Reset' : 'Set'} Networking Keys
97− </Grid.Item>
98− );
195+ return null;
99196 };
100197
101198 const renderNetworkKeySection = (title, key) => (
@@ -105,7 +202,7 @@ export default function AdminNetworkingKeys() {
105202 </Grid.Item>
106203 {key.matchWith({
107204 Nothing: () => (
108− <Grid.Item full as={Text} className="f5 gray4">
205+ <Grid.Item full as="code" className="f5 gray4">
109206 Unset
110207 </Grid.Item>
111208 ),
@@ -144,11 +241,11 @@ export default function AdminNetworkingKeys() {
144241 <>
145242 {renderNetworkKeySection(
146243 'Authentication',
147− details.map(d => d.authenticationKey)
244+ details.chain(chainKeyProp('authenticationKey'))
148245 )}
149246 {renderNetworkKeySection(
150247 'Encryption',
151− details.map(d => d.encryptionKey)
248+ details.chain(chainKeyProp('encryptionKey'))
152249 )}
153250 <Grid.Item full as={Flex} row justify="between" className="mt3">
154251 {renderDetail('Revision', details.map(d => d.keyRevisionNumber))}
@@ -184,7 +281,7 @@ export default function AdminNetworkingKeys() {
184281 {renderTitle()}
185282 </Grid.Item>
186283
187− {inDefaultState && (
284+ {!didRequestSetKeys && (
188285 <Grid.Item full as={Text} className="mb3">
189286 {hasKeys
190287 ? 'Here are your public keys that authenticate your Arvo.'
@@ -192,7 +289,7 @@ export default function AdminNetworkingKeys() {
192289 </Grid.Item>
193290 )}
194291
195− {didSet && (
292+ {confirmed && (
196293 <Grid.Item full as={WarningBox} className="mb3">
197294 You need this keyfile to authenticate with Arvo.
198295 </Grid.Item>
@@ -200,9 +297,18 @@ export default function AdminNetworkingKeys() {
200297
201298 {renderButton()}
202299
203− {inDefaultState && renderDetails()}
300+ {didRequestSetKeys && (
301+ <Grid.Item
302+ full
303+ as={InlineEthereumTransaction}
304+ {...bind}
305+ onReturn={() => pop()}
306+ />
307+ )}
308+
309+ {!didRequestSetKeys && renderDetails()}
204310
205− {didSet && (
311+ {confirmed && (
206312 <>
207313 <Grid.Item full as={BootArvoButton} disabled />
208314 <Grid.Divider />
@@ -210,7 +316,7 @@ export default function AdminNetworkingKeys() {
210316 )}
211317 </Grid>
212318
213− {inDefaultState && (
319+ {!didRequestSetKeys && (
214320 <FooterButton onClick={goRelocate} disabled>
215321 Relocate
216322 </FooterButton>
src/views/Admin/AdminSetProxy.js+22−7
@@ -24,7 +24,7 @@ import MiniBackButton from 'components/MiniBackButton';
2424import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
2525import { GAS_LIMITS } from 'lib/constants';
2626
27−const proxyFromDetails = (details, proxyType) => {
27+const proxyFromDetails = (details, contracts, proxyType) => {
2828 switch (proxyType) {
2929 case PROXY_TYPE.MANAGEMENT:
3030 return details.managementProxy;
@@ -33,13 +33,17 @@ const proxyFromDetails = (details, proxyType) => {
3333 case PROXY_TYPE.TRANSFER:
3434 return details.transferProxy;
3535 case PROXY_TYPE.VOTING:
36− return details.votingProxy;
36+ if (eqAddr(details.votingProxy, contracts.delegatedSending.address)) {
37+ return `${details.votingProxy} (invites contract)`;
38+ } else {
39+ return details.votingProxy;
40+ }
3741 default:
3842 throw new Error(`Unknown proxyType: ${proxyType}`);
3943 }
4044};
4145
42−function useSetProxy(proxyType, address) {
46+function useSetProxy(proxyType) {
4347 const { contracts } = useNetwork();
4448 const { pointCursor } = usePointCursor();
4549 const { syncOwnedPoint } = usePointCache();
@@ -109,8 +113,11 @@ export default function AdminSetProxy() {
109113 const { data, pop } = useLocalRouter();
110114 const { getDetails } = usePointCache();
111115 const { pointCursor } = usePointCursor();
112− const point = need.point(pointCursor);
113− const details = need.details(getDetails(point));
116+ const { contracts } = useNetwork();
117+
118+ const _point = need.point(pointCursor);
119+ const _details = need.details(getDetails(_point));
120+ const _contracts = need.contracts(contracts);
114121
115122 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
116123
@@ -129,26 +136,34 @@ export default function AdminSetProxy() {
129136 }
130137 }, [pass, address, construct]);
131138
132− const proxyAddress = proxyFromDetails(details, data.proxyType);
139+ const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
133140 const isProxySet = !eqAddr(ETH_ZERO_ADDR, proxyAddress);
134141
142+ const proxyAddressLabel = `${
143+ confirmed ? 'New' : 'Current'
144+ } ${properProxyType} Address`;
145+
135146 return (
136147 <Grid>
137148 <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
149+
138150 <Grid.Item full as={ViewHeader}>
139151 {properProxyType} Address
140152 </Grid.Item>
153+
141154 <Grid.Item full as={Text} className="mb4 f5">
142155 {proxyTypeToHumanDescription(data.proxyType)}
143156 </Grid.Item>
157+
144158 <Grid.Item
145159 full
146160 as={Text}
147161 className={cn('f6 mb1', {
148162 green3: confirmed,
149163 })}>
150− {confirmed ? 'New' : 'Current'} {properProxyType} Address
164+ {proxyAddressLabel}
151165 </Grid.Item>
166+
152167 <Grid.Item full as={Flex} row justify="between" align="center">
153168 <Flex.Item
154169 flex
src/views/Admin/Reticket/ReticketConfirm.js+7
@@ -28,6 +28,7 @@ export default function ReticketConfirm({ newWallet, setNewWallet }) {
2828 // to read the disclaimer text
2929 //TODO also check we can pay for the transactions?
3030 useLifecycle(() => {
31+ let mounted = true;
3132 (async () => {
3233 if (Just.hasInstance(newWallet)) {
3334 // skip if already computed (hitting the back button for example)
@@ -38,8 +39,14 @@ export default function ReticketConfirm({ newWallet, setNewWallet }) {
3839 if (isDevelopment) {
3940 console.log(`The new ticket for ${name} is ${wal.ticket}`);
4041 }
42+ if (!mounted) {
43+ return;
44+ }
45+
4146 setGeneratedWallet(Just(wal));
4247 })();
48+
49+ return () => (mounted = false);
4350 });
4451
4552 const goDownload = useCallback(() => push(names.DOWNLOAD), [push, names]);
src/views/Invite/InviteEmail.js+1−1
@@ -121,7 +121,7 @@ export default function InviteEmail() {
121121 const [hovered, setHovered] = useSetState();
122122 const [invites, addInvite, clearInvites] = useSetState();
123123 const [receipts, addReceipt, clearReceipts] = useSetState();
124− const [errors, addError, clearErrors] = useSetState();
124+ const [errors, addError] = useSetState();
125125
126126 // manage general state that affects the whole form
127127 const [status, setStatus] = useState(STATUS.INPUT);
src/views/Point.js−1
@@ -19,7 +19,6 @@ import { useSyncOwnedPoints } from 'lib/useSyncPoints';
1919import Actions from './Point/Actions';
2020import { useWallet } from 'store/wallet';
2121import usePermissionsForPoint from 'lib/usePermissionsForPoint';
22−import MiniBackButton from 'components/MiniBackButton';
2322
2423export default function Point() {
2524 const { push, names } = useHistory();