on master help sign in
feat: manual network seed + discontinuity
commit0f31fcbaec73989b9f7e91cb96df5e5d76f4b872 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateWed 17 Jul 2019, 14:43 (7 years ago) parentabbbcbc
4 files changed+183−114
src/components/InlineEthereumTransaction.js+15−6 src/lib/useEthereumTransaction.js+14−3 src/lib/useInputs.js+1−1 src/views/Admin/AdminNetworkingKeys.js+153−104
src/components/InlineEthereumTransaction.js+15−6
@@ -1,4 +1,4 @@
1−import React from 'react';
1+import React, { useEffect } from 'react';
22import cn from 'classnames';
33import {
44 Grid,
@@ -30,12 +30,14 @@ export default function InlineEthereumTransaction({
3030 error,
3131 gasPrice,
3232 setGasPrice,
33+ resetGasPrice,
3334 txHash,
3435 nonce,
3536 chainId,
3637 signedTransaction,
3738
3839 // additional from parent
40+ label = 'Generate & Sign Transaction',
3941 className,
4042 onReturn,
4143}) {
@@ -50,7 +52,7 @@ export default function InlineEthereumTransaction({
5052 const [advancedInput, { data: advancedOpen }] = useCheckboxInput({
5153 name: 'advanced',
5254 label: 'Advanced Configuration',
53− inverseLabel: 'Hide Advanced Configuration',
55+ inverseLabel: 'Cancel Advanced Configuration',
5456 disabled: !showConfigureInput,
5557 });
5658
@@ -64,6 +66,13 @@ export default function InlineEthereumTransaction({
6466 disabled: !showSignedTx,
6567 });
6668
69+ // reset gas price when closing advanced configuration
70+ useEffect(() => {
71+ if (!advancedOpen) {
72+ resetGasPrice();
73+ }
74+ }, [advancedOpen, resetGasPrice]);
75+
6776 const renderPrimaryButton = () => {
6877 if (error) {
6978 return (
@@ -101,7 +110,7 @@ export default function InlineEthereumTransaction({
101110 onClick={generateAndSign}
102111 disabled={!canSign}
103112 loading={!canSign && initializing}>
104− Generate & Sign Transaction
113+ {label}
105114 </Grid.Item>
106115 );
107116 }
@@ -119,9 +128,8 @@ export default function InlineEthereumTransaction({
119128
120129 {showConfigureInput && (
121130 <>
122− {!advancedOpen ? (
123− <Grid.Item full as={ToggleInput} {...advancedInput} />
124− ) : (
131+ <Grid.Item full as={ToggleInput} {...advancedInput} />
132+ {advancedOpen && (
125133 <>
126134 <Grid.Item full as={Flex} row justify="between" className="mt2">
127135 <Flex.Item as={H5}>Gas Price</Flex.Item>
@@ -152,6 +160,7 @@ export default function InlineEthereumTransaction({
152160 <Grid.Item full as={HelpText} className="mt1">
153161 Chain ID: {chainId}
154162 </Grid.Item>
163+ <Grid.Divider className="mt3" />
155164 </>
156165 )}
157166 </>
src/lib/useEthereumTransaction.js+14−3
@@ -47,6 +47,7 @@ export default function useEthereumTransaction(
4747 const [chainId, setChainId] = useState();
4848 const [nonce, setNonce] = useState();
4949 const [gasPrice, setGasPrice] = useState(initialGasPrice); // gwei
50+ const [averageGasPrice, setAverageGasPrice] = useState(gasPrice); // gwei
5051 const [gasLimit] = useState(initialGasLimit);
5152 const [unsignedTransaction, setUnsignedTransaction] = useState();
5253 const [signedTransaction, setSignedTransaction] = useState();
@@ -54,6 +55,7 @@ export default function useEthereumTransaction(
5455
5556 const initializing = nonce === undefined || chainId === undefined;
5657 const constructed = !!unsignedTransaction;
58+ const isDefaultState = state === STATE.NONE;
5759 const signed = state === STATE.SIGNED;
5860 const broadcasted = state === STATE.BROADCASTED;
5961 const confirmed = state === STATE.CONFIRMED;
@@ -126,10 +128,15 @@ export default function useEthereumTransaction(
126128 setUnsignedTransaction(undefined);
127129 setTxHash(undefined);
128130 setSignedTransaction(undefined);
129− setGasPrice(initialGasPrice);
131+ setGasPrice(averageGasPrice);
130132 setState(STATE.NONE);
131133 setError(undefined);
132− }, [initialGasPrice, setError]);
134+ }, [averageGasPrice, setError]);
135+
136+ const resetGasPrice = useCallback(() => setGasPrice(averageGasPrice), [
137+ setGasPrice,
138+ averageGasPrice,
139+ ]);
133140
134141 useLifecycle(() => {
135142 (async () => {
@@ -143,7 +150,9 @@ export default function useEthereumTransaction(
143150
144151 setNonce(nonce);
145152 setChainId(chainId);
146− setGasPrice(parseInt(fromWei(estimatedGasPrice, 'gwei'), 10));
153+ const gasPrice = parseInt(fromWei(estimatedGasPrice, 'gwei'), 10);
154+ setAverageGasPrice(gasPrice);
155+ setGasPrice(gasPrice);
147156 } catch (error) {
148157 setError(error);
149158 }
@@ -151,6 +160,7 @@ export default function useEthereumTransaction(
151160 });
152161
153162 const values = {
163+ isDefaultState,
154164 initializing,
155165 construct,
156166 constructed,
@@ -168,6 +178,7 @@ export default function useEthereumTransaction(
168178 signedTransaction,
169179 gasPrice,
170180 setGasPrice,
181+ resetGasPrice,
171182 nonce,
172183 chainId,
173184 };
src/lib/useInputs.js+1−1
@@ -71,7 +71,7 @@ export function useHexInput({ length, ...rest }) {
7171 {
7272 type: 'text', // or password
7373 autoComplete: 'off',
74− placeholder: EXAMPLE_PRIVATE_KEY,
74+ placeholder: `e.g. ${EXAMPLE_PRIVATE_KEY}`,
7575 validators: useMemo(
7676 () => [
7777 validateHexString,
src/views/Admin/AdminNetworkingKeys.js+153−104
@@ -1,6 +1,14 @@
11import React, { useCallback, useEffect, useState } from 'react';
22import { Just, Nothing } from 'folktale/maybe';
3−import { Grid, Text, H5, Flex } from 'indigo-react';
3+import {
4+ Grid,
5+ Text,
6+ H5,
7+ Flex,
8+ ToggleInput,
9+ Input,
10+ CheckboxInput,
11+} from 'indigo-react';
412import * as azimuth from 'azimuth-js';
513import { randomHex } from 'web3-utils';
614
@@ -23,11 +31,12 @@ import { addHexPrefix } from 'lib/wallet';
2331
2432import ViewHeader from 'components/ViewHeader';
2533import MiniBackButton from 'components/MiniBackButton';
26−import { BootArvoButton, ForwardButton } from 'components/Buttons';
34+import { BootArvoButton } from 'components/Buttons';
2735import FooterButton from 'components/FooterButton';
2836import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
2937import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
3038import NoticeBox from 'components/NoticeBox';
39+import { useHexInput, useCheckboxInput } from 'lib/useInputs';
3140
3241const chainKeyProp = name => d =>
3342 d[name] === ZERO_KEY ? Nothing() : Just(d[name]);
@@ -50,13 +59,15 @@ function useSetKeys() {
5059 const _point = need.point(pointCursor);
5160 const _contracts = need.contracts(contracts);
5261
53− const [isDiscontinuity] = useState(false);
54− // TODO: set discontinuity with radio check?
5562 const [ndNetworkSeed, setNdNetworkSeed] = useState();
5663
57− const { construct, broadcasting, confirmed, bind } = useEthereumTransaction(
58− GAS_LIMITS.CONFIGURE_KEYS
59− );
64+ const {
65+ isDefaultState,
66+ construct: _construct,
67+ broadcasting,
68+ confirmed,
69+ bind,
70+ } = useEthereumTransaction(GAS_LIMITS.CONFIGURE_KEYS);
6071
6172 // sync point details after success
6273 useEffect(() => {
@@ -65,54 +76,56 @@ function useSetKeys() {
6576 }
6677 }, [_point, confirmed, syncOwnedPoint]);
6778
68− const setKeys = useCallback(async () => {
69− const details = need.details(getDetails(_point));
70− const networkRevision = parseInt(details.keyRevisionNumber, 10);
71−
72− console.log('setting revision to ', networkRevision + 1);
79+ const construct = useCallback(
80+ async (manualSeed, isDiscontinuity) => {
81+ const details = need.details(getDetails(_point));
82+ const networkRevision = parseInt(details.keyRevisionNumber, 10);
83+
84+ let _seed = manualSeed;
85+ if (!_seed) {
86+ const networkSeed = await attemptNetworkSeedDerivation({
87+ urbitWallet,
88+ wallet,
89+ authMnemonic,
90+ details,
91+ revision: networkRevision + 1,
92+ });
93+
94+ _seed = networkSeed.matchWith({
95+ Nothing: () => {
96+ const ndSeed = randomHex(64);
97+ setNdNetworkSeed(ndSeed);
98+ return ndSeed;
99+ },
100+ Just: p => p.value,
101+ });
102+ }
103+ const pair = deriveNetworkKeys(_seed);
104+
105+ const txn = azimuth.ecliptic.configureKeys(
106+ _contracts,
107+ _point,
108+ addHexPrefix(pair.crypt.public),
109+ addHexPrefix(pair.auth.public),
110+ CRYPTO_SUITE_VERSION,
111+ isDiscontinuity ? 1 : 0
112+ );
73113
74− const seed = await attemptNetworkSeedDerivation({
75− urbitWallet,
76− wallet,
77− authMnemonic,
78− details,
79− revision: networkRevision + 1,
80− });
81−
82− const _networkSeed = seed.matchWith({
83− Nothing: () => {
84− const ndSeed = randomHex(64);
85− setNdNetworkSeed(ndSeed);
86− return ndSeed;
87− },
88− Just: p => p.value,
89− });
90−
91− const pair = deriveNetworkKeys(_networkSeed);
92−
93− const txn = azimuth.ecliptic.configureKeys(
114+ _construct(txn);
115+ },
116+ [
117+ _construct,
94118 _contracts,
95119 _point,
96− addHexPrefix(pair.crypt.public),
97− addHexPrefix(pair.auth.public),
98− CRYPTO_SUITE_VERSION,
99− isDiscontinuity
100− );
101−
102− construct(txn);
103− }, [
104− _contracts,
105− _point,
106− authMnemonic,
107− construct,
108− getDetails,
109− isDiscontinuity,
110− urbitWallet,
111− wallet,
112− ]);
120+ authMnemonic,
121+ getDetails,
122+ urbitWallet,
123+ wallet,
124+ ]
125+ );
113126
114127 return {
115− setKeys,
128+ isDefaultState,
116129 construct,
117130 broadcasting,
118131 confirmed,
@@ -134,19 +147,62 @@ export default function AdminNetworkingKeys() {
134147 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
135148 });
136149
137− const [didRequestSetKeys, _setDidRequestSetKeys] = useState(false);
150+ const [showNetworkSeedInput, { data: showNetworkSeed }] = useCheckboxInput({
151+ name: 'shownetworkseed',
152+ label: 'Use Custom Network Seed',
153+ inverseLabel: 'Back to Derived Network Seed',
154+ initialValue: false,
155+ });
156+ const [
157+ networkSeedInput,
158+ { pass: validNetworkSeed, data: networkSeed },
159+ { setValue: setNetworkSeed },
160+ ] = useHexInput({
161+ name: 'networkseed',
162+ label: 'Network Seed (64 bytes)',
163+ length: 32, // 64 bytes
164+ });
165+
166+ const [
167+ discontinuityInput,
168+ { pass: validDiscontinuity, data: isDiscontinuity },
169+ ] = useCheckboxInput({
170+ name: 'discontinuity',
171+ label: 'Trigger New Continuity Era',
172+ length: 64,
173+ initialValue: false,
174+ });
175+
138176 const {
139− setKeys,
177+ isDefaultState,
178+ construct,
140179 broadcasting,
141180 confirmed,
142181 ndNetworkSeed,
143182 bind,
144183 } = useSetKeys();
145184
146− const setDidRequestSetKeys = useCallback(() => {
147− setKeys();
148− _setDidRequestSetKeys(true);
149− }, [setKeys, _setDidRequestSetKeys]);
185+ useEffect(() => {
186+ if (
187+ (!showNetworkSeed || (showNetworkSeed && validNetworkSeed)) &&
188+ validDiscontinuity
189+ ) {
190+ construct(networkSeed, isDiscontinuity);
191+ }
192+ }, [
193+ construct,
194+ isDiscontinuity,
195+ networkSeed,
196+ showNetworkSeed,
197+ validDiscontinuity,
198+ validNetworkSeed,
199+ ]);
200+
201+ useEffect(() => {
202+ if (!showNetworkSeed) {
203+ setNetworkSeed('');
204+ }
205+ }, [setNetworkSeed, showNetworkSeed]);
150206
151207 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
152208
@@ -166,33 +222,6 @@ export default function AdminNetworkingKeys() {
166222 return 'Networking';
167223 };
168224
169− const renderButton = () => {
170− if (!didRequestSetKeys) {
171− return (
172− <Grid.Item
173− full
174− as={ForwardButton}
175− solid
176− onClick={() => setDidRequestSetKeys(true)}>
177− {hasKeys ? 'Reset' : 'Set'} Networking Keys
178− </Grid.Item>
179− );
180− }
181−
182− if (confirmed) {
183− return (
184− <Grid.Item
185− full
186− as={DownloadKeyfileButton}
187− solid
188− networkSeed={ndNetworkSeed}
189− />
190− );
191− }
192−
193− return null;
194− };
195−
196225 const renderNetworkKeySection = (title, key) => (
197226 <>
198227 <Grid.Item full as={H5} className="mt3 gray4">
@@ -279,32 +308,52 @@ export default function AdminNetworkingKeys() {
279308 {renderTitle()}
280309 </Grid.Item>
281310
282− {!didRequestSetKeys && (
283− <Grid.Item full as={Text} className="mb3">
284− {hasKeys
285− ? 'Here are your public keys that authenticate your Arvo.'
286− : 'Set your networking keys to authenticate with Arvo.'}
287− </Grid.Item>
288− )}
311+ <Grid.Item full as={Text} className="mb3">
312+ {hasKeys
313+ ? 'Here are your public keys that authenticate your Arvo.'
314+ : 'Set your networking keys to authenticate with Arvo.'}
315+ </Grid.Item>
289316
290317 {confirmed && (
291− <Grid.Item full as={NoticeBox} className="mb3">
292− You need this keyfile to authenticate with Arvo.
293− </Grid.Item>
318+ <>
319+ <Grid.Item full as={NoticeBox} className="mb3">
320+ You need this keyfile to authenticate with Arvo.
321+ </Grid.Item>
322+ <Grid.Item
323+ full
324+ as={DownloadKeyfileButton}
325+ solid
326+ networkSeed={ndNetworkSeed}
327+ />
328+ </>
294329 )}
295330
296− {renderButton()}
297−
298− {didRequestSetKeys && (
299− <Grid.Item
300− full
301− as={InlineEthereumTransaction}
302− {...bind}
303− onReturn={() => pop()}
304− />
331+ {!confirmed && (
332+ <>
333+ <Grid.Item full as={ToggleInput} {...showNetworkSeedInput} />
334+ {showNetworkSeed && (
335+ <>
336+ <Grid.Item full as={NoticeBox} className="mb2">
337+ When using a custom network seed, you'll need to download your
338+ Arvo keyfile immediately after this transaction is
339+ confirmed—Multipass does not store your seed.
340+ </Grid.Item>
341+ <Grid.Item full as={Input} {...networkSeedInput} />
342+ </>
343+ )}
344+ <Grid.Item full as={CheckboxInput} {...discontinuityInput} />
345+ </>
305346 )}
306347
307− {!didRequestSetKeys && renderDetails()}
348+ <Grid.Item
349+ full
350+ as={InlineEthereumTransaction}
351+ {...bind}
352+ label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
353+ onReturn={() => pop()}
354+ />
355+
356+ {isDefaultState && renderDetails()}
308357
309358 {confirmed && (
310359 <>
@@ -314,7 +363,7 @@ export default function AdminNetworkingKeys() {
314363 )}
315364 </Grid>
316365
317− {!didRequestSetKeys && (
366+ {isDefaultState && (
318367 <FooterButton onClick={goRelocate} disabled>
319368 Relocate
320369 </FooterButton>