on master help sign in
feat: ethereum transaction UX improvements
e5702f5Matt Condon (shrugs)
package-lock.json+13
@@ -4827,6 +4827,14 @@
4827 "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
4827 "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
4828 "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
4828 "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
4829 },
4829 },
4830+ "copy-to-clipboard": {
4831+ "version": "3.2.0",
4832+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz",
4833+ "integrity": "sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==",
4834+ "requires": {
4835+ "toggle-selection": "^1.0.6"
4836+ }
4837+ },
4830 "core-js-compat": {
4838 "core-js-compat": {
4831 "version": "3.1.3",
4839 "version": "3.1.3",
4832 "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.3.tgz",
4840 "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.3.tgz",
@@ -17178,6 +17186,11 @@
17178 "repeat-string": "^1.6.1"
17186 "repeat-string": "^1.6.1"
17179 }
17187 }
17180 },
17188 },
17189+ "toggle-selection": {
17190+ "version": "1.0.6",
17191+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
17192+ "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
17193+ },
17181 "tough-cookie": {
17194 "tough-cookie": {
17182 "version": "2.4.3",
17195 "version": "2.4.3",
17183 "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
17196 "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
package.json+1
@@ -26,6 +26,7 @@
26 "bip39": "^2.5.0",
26 "bip39": "^2.5.0",
27 "bn.js": "^4.11.8",
27 "bn.js": "^4.11.8",
28 "classnames": "^2.2.6",
28 "classnames": "^2.2.6",
29+ "copy-to-clipboard": "^3.2.0",
29 "ethereum-blockies-base64": "^1.0.2",
30 "ethereum-blockies-base64": "^1.0.2",
30 "ethereumjs-tx": "^1.3.7",
31 "ethereumjs-tx": "^1.3.7",
31 "file-saver": "^2.0.0",
32 "file-saver": "^2.0.0",
src/components/CopyButton.jsnew file+42
@@ -0,0 +1,42 @@
1+import React, { useCallback, useEffect, useRef, useState } from 'react';
2+import { LinkButton } from 'indigo-react';
3+import cn from 'classnames';
4+import copy from 'copy-to-clipboard';
5+
6+const COPY_DELAY = 2 * 1000; // ms
7+const renderDefaultCopyText = didCopy => (didCopy ? 'Copied!' : 'Copy');
8+
9+export default function CopyButton({
10+ as: As = LinkButton,
11+ text,
12+ children = renderDefaultCopyText,
13+ className,
14+ ...rest
15+}) {
16+ const [didCopy, setDidCopy] = useState(false);
17+
18+ // what we really want here is a useDebouncedCallback from use-debounced
19+ // but that's a relatively large dep for a single simple usage so ¯\_(ツ)_/¯
20+ const listener = useRef(); // <NodeJS.Listener>
21+ useEffect(() => () => clearTimeout(listener.current), []);
22+
23+ const doCopy = useCallback(() => {
24+ copy(text);
25+ setDidCopy(true);
26+
27+ clearTimeout(listener.current);
28+ listener.current = setTimeout(() => setDidCopy(false), COPY_DELAY);
29+ }, [text]);
30+
31+ return (
32+ <As
33+ onClick={doCopy}
34+ className={cn(className, {
35+ black: !didCopy,
36+ green3: didCopy,
37+ })}
38+ {...rest}>
39+ {children(didCopy)}
40+ </As>
41+ );
42+}
src/components/InlineEthereumTransaction.js+57−21
@@ -20,6 +20,7 @@ import WarningBox from './WarningBox';
20import { composeValidator, buildCheckboxValidator } from 'form/validators';
20import { composeValidator, buildCheckboxValidator } from 'form/validators';
21import BridgeForm from 'form/BridgeForm';
21import BridgeForm from 'form/BridgeForm';
22import Condition from 'form/Condition';
22import Condition from 'form/Condition';
23+import CopyButton from './CopyButton';
23
24
24export default function InlineEthereumTransaction({
25export default function InlineEthereumTransaction({
25 // from useEthereumTransaction.bind
26 // from useEthereumTransaction.bind
@@ -78,7 +79,7 @@ export default function InlineEthereumTransaction({
78 [resetGasPrice]
79 [resetGasPrice]
79 );
80 );
80
81
81− const renderPrimaryButton = () => {
82+ const renderPrimarySection = () => {
82 if (error) {
83 if (error) {
83 return (
84 return (
84 <Grid.Item full as={RestartButton} solid onClick={() => reset()}>
85 <Grid.Item full as={RestartButton} solid onClick={() => reset()}>
@@ -87,12 +88,9 @@ export default function InlineEthereumTransaction({
87 );
88 );
88 } else if (completed) {
89 } else if (completed) {
89 return (
90 return (
90− <>
91+ <Grid.Item full className="pv4 black f5">
91− <Grid.Divider />
92+ Transaction Complete
92− <Grid.Item full as={RestartButton} onClick={onReturn}>
93− Return
94− </Grid.Item>
93+ </Grid.Item>
95− </>
96 );
94 );
97 } else if (showBroadcastButton) {
95 } else if (showBroadcastButton) {
98 return (
96 return (
@@ -121,12 +119,17 @@ export default function InlineEthereumTransaction({
121 }
119 }
122 };
120 };
123
121
122+ const serializedTxHex = useMemo(
123+ () => signedTransaction && hexify(signedTransaction.serialize()),
124+ [signedTransaction]
125+ );
126+
124 return (
127 return (
125 <Grid className={cn(className, 'mt1')}>
128 <Grid className={cn(className, 'mt1')}>
126 <BridgeForm validate={validate} onValues={onValues}>
129 <BridgeForm validate={validate} onValues={onValues}>
127 {({ handleSubmit }) => (
130 {({ handleSubmit }) => (
128 <>
131 <>
129− {renderPrimaryButton()}
132+ {renderPrimarySection()}
130
133
131 {error && (
134 {error && (
132 <Grid.Item full as={ErrorText} className="mt1">
135 <Grid.Item full as={ErrorText} className="mt1">
@@ -149,12 +152,14 @@ export default function InlineEthereumTransaction({
149 full
152 full
150 as={ToggleInput}
153 as={ToggleInput}
151 name="useAdvanced"
154 name="useAdvanced"
152− label="Advanced Configuration"
155+ label="Advanced"
153− inverseLabel="Cancel Advanced Configuration"
156+ inverseLabel="Back to Defaults"
157+ inverseColor="red3"
154 disabled={!showConfigureInput || initializing}
158 disabled={!showConfigureInput || initializing}
155 />
159 />
156
160
157 <Condition when="useAdvanced" is={true}>
161 <Condition when="useAdvanced" is={true}>
162+ <Grid.Divider />
158 <Grid.Item
163 <Grid.Item
159 full
164 full
160 as={Flex}
165 as={Flex}
@@ -183,13 +188,7 @@ export default function InlineEthereumTransaction({
183 <Flex.Item as={Text}>Cheap</Flex.Item>
188 <Flex.Item as={Text}>Cheap</Flex.Item>
184 <Flex.Item as={Text}>Fast</Flex.Item>
189 <Flex.Item as={Text}>Fast</Flex.Item>
185 </Grid.Item>
190 </Grid.Item>
186− <Grid.Item full as={HelpText} className="mt3">
187− Nonce: {nonce}
188− </Grid.Item>
189− <Grid.Item full as={HelpText} className="mt1">
190− Chain ID: {chainId}
191− </Grid.Item>
192− <Grid.Divider className="mt3" />
191+ <Grid.Divider className="mt4" />
193 </Condition>
192 </Condition>
194 </>
193 </>
195 )}
194 )}
@@ -200,14 +199,42 @@ export default function InlineEthereumTransaction({
200 full
199 full
201 as={ToggleInput}
200 as={ToggleInput}
202 name="viewSigned"
201 name="viewSigned"
203− label="View Signed Transaction"
202+ label="Signed Transaction"
204− inverseLabel="Hide Signed Transaction"
203+ inverseLabel="Hide"
205 disabled={!showSignedTx}
204 disabled={!showSignedTx}
206 />
205 />
207 <Condition when="viewSigned" is={true}>
206 <Condition when="viewSigned" is={true}>
207+ <Grid.Divider />
208+ <Grid.Item
209+ full
210+ as={Flex}
211+ justify="between"
212+ className="pv4 black f5">
213+ <Flex.Item>Nonce</Flex.Item>
214+ <Flex.Item>{nonce}</Flex.Item>
215+ </Grid.Item>
216+ <Grid.Divider />
217+ <Grid.Item
218+ full
219+ as={Flex}
220+ justify="between"
221+ className="pv4 black f5">
222+ <Flex.Item>Gas Price</Flex.Item>
223+ <Flex.Item>{gasPrice.toFixed()} Gwei</Flex.Item>
224+ </Grid.Item>
225+ <Grid.Divider />
226+ <Grid.Item
227+ full
228+ as={Flex}
229+ justify="between"
230+ className="mt3 mb2">
231+ <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
232+ <Flex.Item as={CopyButton} text={serializedTxHex} />
233+ </Grid.Item>
208− <Grid.Item full as="code" className="f6 mono gray4 wrap">
234+ <Grid.Item full as="code" className="mb4 f6 mono gray4 wrap">
209− {hexify(signedTransaction.serialize())}
235+ {serializedTxHex}
210 </Grid.Item>
236 </Grid.Item>
237+ <Grid.Divider />
211 </Condition>
238 </Condition>
212 </>
239 </>
213 )}
240 )}
@@ -232,6 +259,15 @@ export default function InlineEthereumTransaction({
232 <Grid.Divider />
259 <Grid.Divider />
233 </>
260 </>
234 )}
261 )}
262+
263+ {completed && (
264+ <>
265+ <Grid.Item full as={RestartButton} onClick={onReturn}>
266+ Return
267+ </Grid.Item>
268+ <Grid.Divider />
269+ </>
270+ )}
235 </>
271 </>
236 )}
272 )}
237 </BridgeForm>
273 </BridgeForm>
src/form/Inputs.js+2−3
@@ -16,9 +16,8 @@ const PLACEHOLDER_HD_PATH = DEFAULT_HD_PATH;
16const PLACEHOLDER_MNEMONIC =
16const PLACEHOLDER_MNEMONIC =
17 'example crew supreme gesture quantum web media hazard theory mercy wing kitten';
17 'example crew supreme gesture quantum web media hazard theory mercy wing kitten';
18const PLACEHOLDER_TICKET = '~sampel-ticlyt-migfun-falmel';
18const PLACEHOLDER_TICKET = '~sampel-ticlyt-migfun-falmel';
19−const PLACEHOLDER_ADDRESS = '0x12345abcdeDB11D175F123F6891AA64F01c24F7d';
19+const PLACEHOLDER_ADDRESS = '0x';
20−const PLACEHOLDER_PRIVATE_KEY =
20+const PLACEHOLDER_PRIVATE_KEY = '0x';
21− '0x12345abcdee6beb2f323fab48b432925c9785808d33a6ca6d7ba00b45e9370c3';
22const PLACEHOLDER_EMAIL = 'email@example.com';
21const PLACEHOLDER_EMAIL = 'email@example.com';
23
22
24const formatPat = buildFormatter([downcase, ensurePatFormat]);
23const formatPat = buildFormatter([downcase, ensurePatFormat]);
src/indigo-react/components/LinkButton.js+2−2
@@ -19,9 +19,9 @@ export default function LinkButton({
19 }),
19 }),
20 }}
20 }}
21 className={cn(
21 className={cn(
22− 'pointer underline',
22+ 'us-none pointer underline',
23 {
23 {
24− black: !disabled,
24+ // NOTE: inherit styling from parent otherwise
25 gray4: disabled,
25 gray4: disabled,
26 },
26 },
27 className
27 className
src/indigo-react/components/ToggleInput.js+7−1
@@ -10,6 +10,7 @@ export default function ToggleInput({
10 name,
10 name,
11 label,
11 label,
12 inverseLabel,
12 inverseLabel,
13+ inverseColor = 'black',
13 className,
14 className,
14
15
15 //
16 //
@@ -52,7 +53,12 @@ export default function ToggleInput({
52 clickable: !disabled,
53 clickable: !disabled,
53 })}
54 })}
54 htmlFor={name}>
55 htmlFor={name}>
56+ <LinkButton
55− <LinkButton disabled={disabled} className="f5">
57+ disabled={disabled}
58+ className={cn('f5', {
59+ black: !input.checked,
60+ [inverseColor]: input.checked,
61+ })}>
56 {input.checked ? inverseLabel : label}
62 {input.checked ? inverseLabel : label}
57 </LinkButton>
63 </LinkButton>
58 </Flex.Item>
64 </Flex.Item>
src/lib/useSuggestedGasPrice.js → src/lib/getSuggestedGasPrice.jsrenamed+10−17
@@ -1,13 +1,13 @@
1−import { useState, useEffect } from 'react';
2import { NETWORK_TYPES } from './network';
1import { NETWORK_TYPES } from './network';
3import { DEFAULT_GAS_PRICE_GWEI } from './constants';
2import { DEFAULT_GAS_PRICE_GWEI } from './constants';
4
3
5−async function getSuggestedGasPrice(networkType) {
4+export default async function getSuggestedGasPrice(networkType) {
6 switch (networkType) {
5 switch (networkType) {
7 case NETWORK_TYPES.ROPSTEN:
6 case NETWORK_TYPES.ROPSTEN:
8 return 10;
7 return 10;
9 case NETWORK_TYPES.OFFLINE:
8 case NETWORK_TYPES.OFFLINE:
10 return DEFAULT_GAS_PRICE_GWEI;
9 return DEFAULT_GAS_PRICE_GWEI;
10+ case NETWORK_TYPES.LOCAL:
11 default:
11 default:
12 try {
12 try {
13 const response = await fetch(
13 const response = await fetch(
@@ -17,24 +17,17 @@ async function getSuggestedGasPrice(networkType) {
17 cache: 'no-cache',
17 cache: 'no-cache',
18 }
18 }
19 );
19 );
20+
20 const json = await response.json();
21 const json = await response.json();
21− return Math.min(json.fast / 10, 20); // to gwei
22+
23+ // ethgasstation returns values in floating point, one order of magitude
24+ // more than gwei. see: https://docs.ethgasstation.info
25+ const suggestedGasPrice = json.fast / 10; // to gwei
26+
27+ // we don't want to charge users more than our default 20 gwei
28+ return Math.min(suggestedGasPrice, DEFAULT_GAS_PRICE_GWEI);
22 } catch (e) {
29 } catch (e) {
23 return DEFAULT_GAS_PRICE_GWEI;
30 return DEFAULT_GAS_PRICE_GWEI;
24 }
31 }
25 }
32 }
26}
33}
27−
28−function useSuggestedGasPrice(networkType) {
29− const [gasPrice, _setGasPrice] = useState(DEFAULT_GAS_PRICE_GWEI);
30−
31− useEffect(() => {
32− (async () => {
33− _setGasPrice(await getSuggestedGasPrice(networkType));
34− })();
35− }, [networkType]);
36−
37− return { gasPrice };
38−}
39−
40−export { getSuggestedGasPrice, useSuggestedGasPrice };
src/lib/reticket.js+1−1
@@ -10,7 +10,7 @@ import {
10} from './keys';
10} from './keys';
11import { addHexPrefix } from './wallet';
11import { addHexPrefix } from './wallet';
12import { sendAndAwaitAll } from './txn';
12import { sendAndAwaitAll } from './txn';
13−import { getSuggestedGasPrice } from './useSuggestedGasPrice';
13+import getSuggestedGasPrice from './getSuggestedGasPrice';
14import { GAS_LIMITS } from './constants';
14import { GAS_LIMITS } from './constants';
15import { toWei } from 'web3-utils';
15import { toWei } from 'web3-utils';
16
16
src/lib/useEthereumTransaction.js+10−34
@@ -1,22 +1,22 @@
1import { useCallback, useEffect, useState } from 'react';
1import { useCallback, useEffect, useState } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3−import { fromWei, toWei } from 'web3-utils';
3+import { toWei } from 'web3-utils';
4
4
5import { useNetwork } from 'store/network';
5import { useNetwork } from 'store/network';
6import { useWallet } from 'store/wallet';
6import { useWallet } from 'store/wallet';
7import { usePointCursor } from 'store/pointCursor';
7import { usePointCursor } from 'store/pointCursor';
8−import { useSuggestedGasPrice } from 'lib/useSuggestedGasPrice';
9
8
10−import { GAS_LIMITS } from './constants';
9+import { GAS_LIMITS, DEFAULT_GAS_PRICE_GWEI } from './constants';
11import {
10import {
12 signTransaction,
11 signTransaction,
13 sendSignedTransaction,
12 sendSignedTransaction,
14 waitForTransactionConfirm,
13 waitForTransactionConfirm,
15 hexify,
14 hexify,
16} from './txn';
15} from './txn';
17−import * as need from './need';
16+import * as need from 'lib/need';
18−import { ensureFundsFor } from './tank';
17+import { ensureFundsFor } from 'lib/tank';
19import useDeepEqualReference from './useDeepEqualReference';
18import useDeepEqualReference from './useDeepEqualReference';
19+import useGasPrice from './useGasPrice';
20
20
21const STATE = {
21const STATE = {
22 NONE: 'NONE',
22 NONE: 'NONE',
@@ -37,7 +37,7 @@ export default function useEthereumTransaction(
37 transactionBuilder,
37 transactionBuilder,
38 refetch,
38 refetch,
39 initialGasLimit = GAS_LIMITS.DEFAULT,
39 initialGasLimit = GAS_LIMITS.DEFAULT,
40− initialGasPrice = 20
40+ initialGasPrice = DEFAULT_GAS_PRICE_GWEI
41) {
41) {
42 const { wallet, walletType, walletHdPath } = useWallet();
42 const { wallet, walletType, walletHdPath } = useWallet();
43 const { web3, networkType } = useNetwork();
43 const { web3, networkType } = useNetwork();
@@ -59,9 +59,7 @@ export default function useEthereumTransaction(
59 const [state, setState] = useState(STATE.NONE);
59 const [state, setState] = useState(STATE.NONE);
60 const [chainId, setChainId] = useState();
60 const [chainId, setChainId] = useState();
61 const [nonce, setNonce] = useState();
61 const [nonce, setNonce] = useState();
62− const [gasPrice, setGasPrice] = useState(initialGasPrice); // gwei
62+ const { gasPrice, setGasPrice, resetGasPrice } = useGasPrice(initialGasPrice);
63− const [suggestedGasPrice, setSuggestedGasPrice] = useState(gasPrice); // gwei
64− const { gasPrice: estimatedGasPrice } = useSuggestedGasPrice();
65 const [gasLimit] = useState(initialGasLimit);
63 const [gasLimit] = useState(initialGasLimit);
66 const [unsignedTransaction, setUnsignedTransaction] = useState();
64 const [unsignedTransaction, setUnsignedTransaction] = useState();
67 const [signedTransaction, setSignedTransaction] = useState();
65 const [signedTransaction, setSignedTransaction] = useState();
@@ -179,25 +177,11 @@ export default function useEthereumTransaction(
179 setSignedTransaction(undefined);
177 setSignedTransaction(undefined);
180 setNonce(undefined);
178 setNonce(undefined);
181 setChainId(undefined);
179 setChainId(undefined);
182− setGasPrice(suggestedGasPrice);
180+ resetGasPrice();
183 setState(STATE.NONE);
181 setState(STATE.NONE);
184 setError(undefined);
182 setError(undefined);
185 setNeedFunds(undefined);
183 setNeedFunds(undefined);
186− }, [suggestedGasPrice, setError]);
184+ }, [resetGasPrice, setError]);
187−
188− const resetGasPrice = useCallback(() => setGasPrice(suggestedGasPrice), [
189− setGasPrice,
190− suggestedGasPrice,
191− ]);
192−
193− useEffect(() => {
194− const gasPrice = parseInt(
195− fromWei(estimatedGasPrice.toString(), 'gwei'),
196− 10
197− );
198− setSuggestedGasPrice(gasPrice);
199− setGasPrice(gasPrice);
200− }, [estimatedGasPrice]);
201
185
202 useEffect(() => {
186 useEffect(() => {
203 let mounted = true;
187 let mounted = true;
@@ -226,15 +210,7 @@ export default function useEthereumTransaction(
226 })();
210 })();
227
211
228 return () => (mounted = false);
212 return () => (mounted = false);
229− }, [
213+ }, [_wallet, _web3, setError, nonce, chainId, networkType, initialGasPrice]);
230− _wallet,
231− _web3,
232− setError,
233− nonce,
234− chainId,
235− networkType,
236− estimatedGasPrice,
237− ]);
238
214
239 useEffect(() => {
215 useEffect(() => {
240 let mounted = true;
216 let mounted = true;
src/lib/useGasPrice.jsnew file+48
@@ -0,0 +1,48 @@
1+import { useCallback, useEffect, useState } from 'react';
2+
3+import { useNetwork } from 'store/network';
4+
5+import getSuggestedGasPrice from 'lib/getSuggestedGasPrice';
6+import { DEFAULT_GAS_PRICE_GWEI } from './constants';
7+
8+/**
9+ * handles all of the state of components that manage gas prices
10+ * + simple get/set
11+ * + lazy fetching optimal gas from oracle
12+ * + resetting to oracle (or sync) default
13+ * @param {Number} initialGasPrice the initial gas price available instantly
14+ */
15+export default function useGasPrice(initialGasPrice = DEFAULT_GAS_PRICE_GWEI) {
16+ const { networkType } = useNetwork();
17+ const [suggestedGasPrice, setSuggestedGasPrice] = useState(initialGasPrice); // gwei
18+ const [gasPrice, setGasPrice] = useState(initialGasPrice); // gwei
19+
20+ useEffect(() => {
21+ let mounted = true;
22+
23+ (async () => {
24+ const suggestedGasPrice = await getSuggestedGasPrice(networkType);
25+
26+ if (!mounted) {
27+ return;
28+ }
29+
30+ // TODO: BN
31+ setSuggestedGasPrice(suggestedGasPrice);
32+ setGasPrice(suggestedGasPrice);
33+ })();
34+
35+ return () => (mounted = true);
36+ }, [networkType]);
37+
38+ const resetGasPrice = useCallback(() => setGasPrice(suggestedGasPrice), [
39+ suggestedGasPrice,
40+ ]);
41+
42+ return {
43+ suggestedGasPrice,
44+ gasPrice,
45+ setGasPrice,
46+ resetGasPrice,
47+ };
48+}
src/views/Invite/InviteEmail.js+4−4
@@ -9,7 +9,7 @@ import cn from 'classnames';
9import * as ob from 'urbit-ob';
9import * as ob from 'urbit-ob';
10import * as azimuth from 'azimuth-js';
10import * as azimuth from 'azimuth-js';
11import {
11import {
12− Grid,
12+Grid,
13 Flex,
13 Flex,
14 IconButton,
14 IconButton,
15 HelpText,
15 HelpText,
@@ -35,7 +35,6 @@ import {
35} from 'lib/txn';
35} from 'lib/txn';
36import * as tank from 'lib/tank';
36import * as tank from 'lib/tank';
37import { MIN_PLANET, GAS_LIMITS } from 'lib/constants';
37import { MIN_PLANET, GAS_LIMITS } from 'lib/constants';
38−import { useSuggestedGasPrice } from 'lib/useSuggestedGasPrice';
39import * as need from 'lib/need';
38import * as need from 'lib/need';
40import * as wg from 'lib/walletgen';
39import * as wg from 'lib/walletgen';
41import useSetState from 'lib/useSetState';
40import useSetState from 'lib/useSetState';
@@ -57,6 +56,7 @@ import { FORM_ERROR } from 'final-form';
57import SubmitButton from 'form/SubmitButton';
56import SubmitButton from 'form/SubmitButton';
58import FormError from 'form/FormError';
57import FormError from 'form/FormError';
59import { WARNING, onlyHasWarning } from 'form/helpers';
58import { WARNING, onlyHasWarning } from 'form/helpers';
59+import useGasPrice from 'lib/useGasPrice';
60
60
61const INITIAL_VALUES = { emails: [''] };
61const INITIAL_VALUES = { emails: [''] };
62
62
@@ -106,7 +106,7 @@ export default function InviteEmail() {
106 const { pointCursor } = usePointCursor();
106 const { pointCursor } = usePointCursor();
107 const point = need.point(pointCursor);
107 const point = need.point(pointCursor);
108 const { getHasReceived, sendMail } = useMailer();
108 const { getHasReceived, sendMail } = useMailer();
109− const { gasPrice } = useSuggestedGasPrice(networkType);
109+ const { gasPrice } = useGasPrice();
110
110
111 const cachedEmails = useRef({});
111 const cachedEmails = useRef({});
112
112
@@ -245,7 +245,7 @@ export default function InviteEmail() {
245 nonce: nonce + i,
245 nonce: nonce + i,
246 // TODO: ^ make a useTransactionSigner to encapsulate this logic
246 // TODO: ^ make a useTransactionSigner to encapsulate this logic
247 txn: inviteTx,
247 txn: inviteTx,
248− gasPrice: gasPrice.toString(),
248+ gasPrice: gasPrice.toString(), // expects string gwei
249 gasLimit: GAS_LIMIT.toString(),
249 gasLimit: GAS_LIMIT.toString(),
250 });
250 });
251
251