on master help sign in
reskin InlineEthereumTransaction
97de700Liam Fitzgerald
src/assets/info.svgnew file+3
@@ -0,0 +1,3 @@
1+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2+<path d="M6 -5.24537e-07C2.68503 -8.1434e-07 8.1434e-07 2.68503 5.24537e-07 6C2.34733e-07 9.31497 2.68503 12 6 12C9.31497 12 12 9.31497 12 6C12 2.68503 9.31497 -2.34733e-07 6 -5.24537e-07ZM6 0.723491C8.91892 0.723491 11.2765 3.08732 11.2765 6C11.2765 8.91892 8.91892 11.2765 6 11.2765C3.08108 11.2765 0.723492 8.91892 0.723492 6C0.723492 3.08732 3.08108 0.723491 6 0.723491ZM5.57588 9.19335L6.42412 9.19335L6.42 5.22L5.58 5.22L5.57588 9.19335ZM6 2.75676C5.69127 2.75676 5.43867 3.00935 5.43867 3.31809C5.43867 3.62682 5.69127 3.87942 6 3.87942C6.30873 3.87942 6.56133 3.62682 6.56133 3.31809C6.56133 3.00936 6.30873 2.75676 6 2.75676Z" fill="#B1B2B3"/>
3+</svg>
src/components/InlineEthereumTransaction.js+84−24
@@ -1,4 +1,4 @@
1−import React, { useMemo, useCallback } from 'react';
1+import React, { useMemo, useCallback, useState } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import {
3import {
4 Grid,
4 Grid,
@@ -8,11 +8,16 @@ import {
8 LinkButton,
8 LinkButton,
9 H5,
9 H5,
10 Text,
10 Text,
11+ IconButton,
11} from 'indigo-react';
12} from 'indigo-react';
13+import { toBN } from 'web3-utils';
14+
15+import { ReactComponent as InfoIcon } from 'assets/info.svg';
12
16
13import { useExploreTxUrls } from 'lib/explorer';
17import { useExploreTxUrls } from 'lib/explorer';
14import { hexify } from 'lib/txn';
18import { hexify } from 'lib/txn';
15import pluralize from 'lib/pluralize';
19import pluralize from 'lib/pluralize';
20+import { safeFromWei, safeToWei } from 'lib/lib';
16
21
17import { composeValidator, buildCheckboxValidator } from 'form/validators';
22import { composeValidator, buildCheckboxValidator } from 'form/validators';
18import BridgeForm from 'form/BridgeForm';
23import BridgeForm from 'form/BridgeForm';
@@ -45,6 +50,8 @@ export default function InlineEthereumTransaction({
45 needFunds,
50 needFunds,
46 signedTransactions,
51 signedTransactions,
47 confirmationProgress,
52 confirmationProgress,
53+ gasLimit,
54+ unsignedTransactions,
48
55
49 // additional from parent
56 // additional from parent
50 label = 'Generate & Sign Transaction',
57 label = 'Generate & Sign Transaction',
@@ -52,7 +59,11 @@ export default function InlineEthereumTransaction({
52 onReturn,
59 onReturn,
53}) {
60}) {
54 // show receipt after successful broadcast
61 // show receipt after successful broadcast
55− const showReceipt = broadcasted || confirmed || completed;
62+ const [showReceipt, setShowReceipt] = useState(false);
63+ const toggleShowReceipt = useCallback(() => setShowReceipt(!showReceipt), [
64+ setShowReceipt,
65+ showReceipt,
66+ ]);
56 // show configure controls pre-broadcast
67 // show configure controls pre-broadcast
57 const showConfigureInput = !(signed || broadcasted || confirmed || completed);
68 const showConfigureInput = !(signed || broadcasted || confirmed || completed);
58 // show the send/loading button while signed, broadcasting, or confirme
69 // show the send/loading button while signed, broadcasting, or confirme
@@ -62,6 +73,12 @@ export default function InlineEthereumTransaction({
62 // show signed tx only when signing (for offline usage)
73 // show signed tx only when signing (for offline usage)
63 const showSignedTx = signed;
74 const showSignedTx = signed;
64
75
76+ const [showGasDetails, _setShowGasDetails] = useState(false);
77+ const toggleGasDetails = useCallback(
78+ () => _setShowGasDetails(!showGasDetails),
79+ [_setShowGasDetails, showGasDetails]
80+ );
81+
65 const validate = useMemo(
82 const validate = useMemo(
66 () =>
83 () =>
67 composeValidator({
84 composeValidator({
@@ -88,11 +105,7 @@ export default function InlineEthereumTransaction({
88 </Grid.Item>
105 </Grid.Item>
89 );
106 );
90 } else if (completed) {
107 } else if (completed) {
91− return (
108+ return undefined;
92− <Grid.Item full className="pv4 black f5">
93− Transaction Complete
94− </Grid.Item>
95− );
96 } else if (showBroadcastButton) {
109 } else if (showBroadcastButton) {
97 return (
110 return (
98 <Grid.Item
111 <Grid.Item
@@ -137,6 +150,29 @@ export default function InlineEthereumTransaction({
137 [signedTransactions]
150 [signedTransactions]
138 );
151 );
139
152
153+ const numTxs = useMemo(() => (unsignedTransactions || []).length || 1, [
154+ unsignedTransactions,
155+ ]);
156+ const maxCost = useMemo(
157+ () =>
158+ safeFromWei(
159+ safeToWei(
160+ toBN(gasLimit)
161+ .mul(toBN(gasPrice))
162+ .mul(toBN(numTxs)),
163+ 'gwei'
164+ )
165+ ),
166+ [gasLimit, gasPrice, numTxs]
167+ );
168+
169+ const gasInfo = useMemo(() => {
170+ const extra = numTxs === 1 ? '' : `* ${numTxs} TXN`;
171+ return showGasDetails
172+ ? `${gasPrice} Gwei * ${gasLimit} ${extra} = ${maxCost} ETH`
173+ : `${maxCost} ETH`;
174+ }, [showGasDetails, maxCost, gasPrice, gasLimit, numTxs]);
175+
140 return (
176 return (
141 <Grid className={cn(className, 'mt1')}>
177 <Grid className={cn(className, 'mt1')}>
142 <BridgeForm validate={validate} onValues={onValues}>
178 <BridgeForm validate={validate} onValues={onValues}>
@@ -172,6 +208,7 @@ export default function InlineEthereumTransaction({
172 />
208 />
173
209
174 <Condition when="useAdvanced" is={true}>
210 <Condition when="useAdvanced" is={true}>
211+ <Grid.Item full className="mb2"></Grid.Item>
175 <Grid.Divider />
212 <Grid.Divider />
176 <Grid.Item
213 <Grid.Item
177 full
214 full
@@ -180,8 +217,16 @@ export default function InlineEthereumTransaction({
180 justify="between"
217 justify="between"
181 className="mt2">
218 className="mt2">
182 <Flex.Item as={H5}>Gas Price</Flex.Item>
219 <Flex.Item as={H5}>Gas Price</Flex.Item>
220+
183− <Flex.Item as={H5}>{gasPrice} Gwei</Flex.Item>
221+ <Flex.Item as={H5}>
222+ {gasInfo}{' '}
223+ <InfoIcon
224+ className="pointer"
225+ onClick={toggleGasDetails}
226+ />
227+ </Flex.Item>
184 </Grid.Item>
228 </Grid.Item>
229+
185 {/* TODO(shrugs): move to indigo/RangeInput */}
230 {/* TODO(shrugs): move to indigo/RangeInput */}
186 <Grid.Item
231 <Grid.Item
187 full
232 full
@@ -228,21 +273,28 @@ export default function InlineEthereumTransaction({
228 </>
273 </>
229 )}
274 )}
230
275
231− {showReceipt && (
232− <>
233− <Grid.Divider />
234− <HashReceiptList txHashes={txHashes} />
235−
236− <Grid.Divider />
237− </>
238− )}
239−
240 {completed && (
276 {completed && (
241 <>
277 <>
242 <Grid.Item full as={RestartButton} onClick={onReturn}>
278 <Grid.Item full as={RestartButton} onClick={onReturn}>
243 Return
279 Return
244 </Grid.Item>
280 </Grid.Item>
245 <Grid.Divider />
281 <Grid.Divider />
282+ {showReceipt && (
283+ <TransactionReceipt
284+ txHashes={txHashes}
285+ maxCost={maxCost}
286+ onClose={toggleShowReceipt}
287+ />
288+ )}
289+
290+ {!showReceipt && (
291+ <Grid.Item
292+ className="underline mv2 pointer"
293+ full
294+ onClick={toggleShowReceipt}>
295+ View Receipt
296+ </Grid.Item>
297+ )}
246 </>
298 </>
247 )}
299 )}
248 </>
300 </>
@@ -278,34 +330,42 @@ function SignedTransactionList({ serializedTxsHex, nonce, gasPrice }) {
278 ));
330 ));
279}
331}
280
332
281−function HashReceiptList({ txHashes }) {
333+function TransactionReceipt({ txHashes, maxCost, onClose }) {
282 const txUrls = useExploreTxUrls(txHashes);
334 const txUrls = useExploreTxUrls(txHashes);
283− const header = pluralize(txHashes.length, 'Hash', 'Hashes');
284 return (
335 return (
285 <>
336 <>
337+ <Grid.Item full as={Flex} justify="between" className="pv2 black f5">
338+ <Flex.Item>Receipt</Flex.Item>
339+ <Flex.Item onClick={onClose} className="underline pointer">
340+ Close
341+ </Flex.Item>
342+ </Grid.Item>
343+ <Grid.Item full as={Flex} justify="between" className="pv2 black f5">
344+ <Flex.Item>Gas Price</Flex.Item>
345+ <Flex.Item className="mono">{maxCost} ETH</Flex.Item>
346+ </Grid.Item>
286 <Grid.Divider />
347 <Grid.Divider />
287 <Grid.Item full as={Flex} col className="pv4">
348 <Grid.Item full as={Flex} col className="pv4">
288− <Flex.Item as={H5}>Transaction {header}</Flex.Item>
349+ <Flex.Item as={H5}>Receipt</Flex.Item>
289
350
290 {txHashes &&
351 {txHashes &&
291 txHashes.map((txHash, i) => (
352 txHashes.map((txHash, i) => (
292− <Flex.Item as={Flex}>
353+ <Flex.Item as={Flex} className="mv3">
293 <>
354 <>
294 <Flex.Item
355 <Flex.Item
295 key={i}
356 key={i}
296 flex
357 flex
297 as="code"
358 as="code"
298− className="f6 mono gray4 wrap">
359+ className="f6 mono gray4 wrap ">
299 {txHash}
360 {txHash}
300 </Flex.Item>
361 </Flex.Item>
301− <Flex.Item as={LinkButton} href={txUrls[i]}>
362+ <Flex.Item className="ml8" as={LinkButton} href={txUrls[i]}>
302 Etherscan↗
363 Etherscan↗
303 </Flex.Item>
364 </Flex.Item>
304 </>
365 </>
305 </Flex.Item>
366 </Flex.Item>
306 ))}
367 ))}
307 </Grid.Item>
368 </Grid.Item>
308− <Grid.Divider />
309 </>
369 </>
310 );
370 );
311}
371}
src/lib/useEthereumTransaction.js+4−2
@@ -1,4 +1,4 @@
1−import { useCallback, useEffect, useState } from 'react';
1+import { useCallback, useEffect, useState, useMemo } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3import { toBN } from 'web3-utils';
3import { toBN } from 'web3-utils';
4
4
@@ -22,7 +22,7 @@ import { ensureFundsFor } from 'lib/tank';
22import useDeepEqualReference from 'lib/useDeepEqualReference';
22import useDeepEqualReference from 'lib/useDeepEqualReference';
23import useGasPrice from 'lib/useGasPrice';
23import useGasPrice from 'lib/useGasPrice';
24import timeout from 'lib/timeout';
24import timeout from 'lib/timeout';
25−import { safeToWei } from 'lib/lib';
25+import { safeToWei, safeFromWei } from 'lib/lib';
26
26
27const STATE = {
27const STATE = {
28 NONE: 'NONE',
28 NONE: 'NONE',
@@ -312,6 +312,8 @@ export default function useEthereumTransaction(
312 nonce,
312 nonce,
313 chainId,
313 chainId,
314 needFunds,
314 needFunds,
315+ gasLimit,
316+ unsignedTransactions,
315 });
317 });
316
318
317 return { ...values, bind: values };
319 return { ...values, bind: values };
src/views/UrbitOS/Home.js+1−2
@@ -60,9 +60,9 @@ export default function UrbitOSHome() {
60 <Grid.Item full className="mv7 f5">
60 <Grid.Item full className="mv7 f5">
61 Network
61 Network
62 </Grid.Item>
62 </Grid.Item>
63− <Grid.Divider />
64 {showSponsor && (
63 {showSponsor && (
65 <>
64 <>
65+ <Grid.Divider />
66 <Grid.Item
66 <Grid.Item
67 full
67 full
68 as={ForwardButton}
68 as={ForwardButton}
@@ -72,7 +72,6 @@ export default function UrbitOSHome() {
72 <span className="mono">{sponsor}</span>
72 <span className="mono">{sponsor}</span>
73 <span className="f7 bg-black white p1 ml2 r4">SPONSOR</span>
73 <span className="f7 bg-black white p1 ml2 r4">SPONSOR</span>
74 </Grid.Item>
74 </Grid.Item>
75− <Grid.Divider />
76 </>
75 </>
77 )}
76 )}
78
77
src/views/UrbitOS/NetworkingKeys.js+1−1
@@ -210,7 +210,7 @@ export default function UrbitOSNetworkingKeys() {
210 <>
210 <>
211 <Grid.Item
211 <Grid.Item
212 full
212 full
213− className="mb3"
213+ className="mv3"
214 as={DownloadKeyfileButton}
214 as={DownloadKeyfileButton}
215 solid
215 solid
216 {...keyfileBind}
216 {...keyfileBind}