on 6015de2 help sign in
bridge/src/components/InlineEthereumTransaction.js
420 lines 12 kB history raw
1import React, { useMemo, useCallback, useState } from 'react';
2import cn from 'classnames';
3import {
4 Grid,
5 ToggleInput,
6 ErrorText,
7 Flex,
8 LinkButton,
9 H5,
10 Text,
11} from 'indigo-react';
12import { toBN } from 'web3-utils';
14import { ReactComponent as InfoIcon } from 'assets/info.svg';
16import { useExploreTxUrls } from 'lib/explorer';
17import { hexify } from 'lib/txn';
18import { safeFromWei, safeToWei } from 'lib/lib';
20import { composeValidator, buildCheckboxValidator } from 'form/validators';
21import BridgeForm from 'form/BridgeForm';
22import Condition from 'form/Condition';
24import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
25import CopyButton from './copiable/CopyButton';
26import ProgressButton from './ProgressButton';
27import { convertToInt } from 'lib/convertToInt';
28import NeedFundsNotice from './NeedFundsNotice';
29import NoticeBox from './NoticeBox';
31import './InlineEthereumTransaction.scss';
33export default function InlineEthereumTransaction({
34 // from useEthereumTransaction.bind
35 initializing,
36 canSign,
37 generateAndSign,
38 signed,
39 fakeSigned,
40 broadcast,
41 broadcasted,
42 confirmed,
43 completed,
44 reset,
45 error,
46 gasPrice,
47 setGasPrice,
48 resetGasPrice,
49 txHashes,
50 nonce,
51 chainId,
52 needFunds,
53 signedTransactions,
54 confirmationProgress,
55 gasLimit,
56 unsignedTransactions,
57 finalCost,
59 // additional from parent
60 label = 'Generate & Sign Transaction',
61 className,
62 onReturn,
63}) {
64 // show receipt after successful broadcast
65 const [showReceipt, setShowReceipt] = useState(false);
66 const toggleShowReceipt = useCallback(() => setShowReceipt(!showReceipt), [
67 setShowReceipt,
68 showReceipt,
69 ]);
70 // show configure controls pre-broadcast
71 const showConfigureInput = !(signed || broadcasted || confirmed || completed);
72 // show the send/loading button while signed, broadcasting, or confirme
73 const showBroadcastButton = signed;
74 const showLoadingButton = broadcasted || confirmed;
75 const canBroadcast = signed && !needFunds;
76 // show signed tx only when signing (for offline usage)
77 const showSignedTx = signed;
79 const [showGasDetails, _setShowGasDetails] = useState(false);
80 const toggleGasDetails = useCallback(
81 () => _setShowGasDetails(!showGasDetails),
82 [_setShowGasDetails, showGasDetails]
83 );
85 const validate = useMemo(
86 () =>
87 composeValidator({
88 useAdvanced: buildCheckboxValidator(),
89 viewSigned: buildCheckboxValidator(),
90 }),
91 []
92 );
94 const onValues = useCallback(
95 ({ valid, values, form }) => {
96 if (!values.useAdvanced) {
97 resetGasPrice();
98 }
99 },
100 [resetGasPrice]
101 );
103 const renderPrimarySection = () => {
104 if (error) {
105 return (
106 <Grid.Item
107 className="eth-tx-button"
108 full
109 as={RestartButton}
110 solid
111 onClick={() => reset()}>
112 Reset Transaction
113 </Grid.Item>
114 );
115 } else if (completed) {
116 return (
117 <>
118 <Grid.Divider />
119 <Grid.Item
120 className="eth-tx-button"
121 full
122 as={RestartButton}
123 onClick={onReturn}>
124 Return
125 </Grid.Item>
126 <Grid.Divider />
127 {showReceipt && (
128 <TransactionReceipt
129 txHashes={txHashes}
130 finalCost={finalCost}
131 onClose={toggleShowReceipt}
132 />
133 )}
135 {!showReceipt && (
136 <Grid.Item
137 className="underline mv2 pointer"
138 full
139 onClick={toggleShowReceipt}>
140 View Receipt
141 </Grid.Item>
142 )}
143 </>
144 );
145 } else if (showBroadcastButton) {
146 return (
147 <Grid.Item
148 className="eth-tx-button"
149 full
150 as={ForwardButton}
151 solid
152 success
153 disabled={!canBroadcast}
154 onClick={() => broadcast()}>
155 Send Transaction
156 </Grid.Item>
157 );
158 } else if (showLoadingButton) {
159 return (
160 <Grid.Item
161 className="eth-tx-button"
162 full
163 as={ProgressButton}
164 success
165 disabled
166 progress={confirmationProgress}>
167 Sending Transaction
168 </Grid.Item>
169 );
170 } else {
171 return (
172 <Grid.Item
173 className="eth-tx-button"
174 full
175 as={GenerateButton}
176 onClick={generateAndSign}
177 disabled={!canSign}
178 loading={!canSign && initializing}>
179 {label}
180 </Grid.Item>
181 );
182 }
183 };
185 const serializedTxsHex = useMemo(
186 () =>
187 signedTransactions &&
188 signedTransactions.map(stx => hexify(stx.serialize())),
189 [signedTransactions]
190 );
192 const numTxs = useMemo(() => (unsignedTransactions || []).length || 1, [
193 unsignedTransactions,
194 ]);
195 const maxCost = useMemo(
196 () =>
197 safeFromWei(
198 safeToWei(
199 toBN(gasLimit)
200 .mul(toBN(gasPrice))
201 .mul(toBN(numTxs)),
202 'gwei'
203 ),
204 'ether'
205 ),
206 [gasLimit, gasPrice, numTxs]
207 );
209 const gasInfo = useMemo(() => {
210 const extra = numTxs === 1 ? '' : `* ${numTxs} txs`;
211 return showGasDetails
212 ? `${gasPrice} Gwei * ${gasLimit} ${extra} = ${maxCost} ETH`
213 : `${maxCost} ETH`;
214 }, [showGasDetails, maxCost, gasPrice, gasLimit, numTxs]);
216 return (
217 <Grid className={cn(className, 'mt1')}>
218 <BridgeForm validate={validate} onValues={onValues}>
219 {() => (
220 <>
221 {renderPrimarySection()}
223 {error && (
224 <Grid.Item className="mv1 eth-tx-button" full as={ErrorText}>
225 {error.message}
226 </Grid.Item>
227 )}
229 {needFunds && (
230 <Grid.Item
231 className="mt3 eth-tx-button"
232 full
233 as={NeedFundsNotice}
234 {...needFunds}
235 />
236 )}
238 {showConfigureInput && (
239 <>
240 <Grid.Item
241 className="eth-tx-button"
242 full
243 as={ToggleInput}
244 name="useAdvanced"
245 label="Advanced"
246 inverseLabel="Back to Defaults"
247 inverseColor="red3"
248 disabled={!showConfigureInput || initializing}
249 />
251 <Condition when="useAdvanced" is={true}>
252 <Grid.Item className="mb2 eth-tx-button" full></Grid.Item>
253 <Grid.Divider />
254 <Grid.Item
255 className="mt2 eth-tx-button"
256 full
257 as={Flex}
258 row
259 justify="between">
260 <Flex.Item as={H5}>Transaction Fee</Flex.Item>
262 <Flex.Item as={H5}>
263 {gasInfo}{' '}
264 <InfoIcon
265 className="pointer"
266 onClick={toggleGasDetails}
267 />
268 </Flex.Item>
269 </Grid.Item>
271 {/* TODO(shrugs): move to indigo/RangeInput */}
272 <Grid.Item
273 className="eth-tx-button"
274 full
275 as="input"
276 type="range"
277 min="1"
278 max="400"
279 value={gasPrice}
280 onChange={e =>
281 setGasPrice(convertToInt(e.target.value, 10))
282 }
283 />
284 <Grid.Item
285 className="f6 mt1 eth-tx-button"
286 full
287 as={Flex}
288 row
289 justify="between">
290 <Flex.Item as={Text}>Cheap</Flex.Item>
291 <Flex.Item as={Text}>Fast</Flex.Item>
292 </Grid.Item>
293 <Grid.Divider className="mt4" />
294 </Condition>
295 </>
296 )}
298 {showSignedTx && fakeSigned && (
299 <>
300 <Grid.Item className="mt2 eth-tx-button" full as={NoticeBox}>
301 Your wallet will sign the transaction upon sending it.
302 </Grid.Item>
303 </>
304 )}
306 {showSignedTx && !fakeSigned && (
307 <>
308 <Grid.Item
309 className="eth-tx-button"
310 full
311 as={ToggleInput}
312 name="viewSigned"
313 label="Signed Transaction"
314 inverseLabel="Hide"
315 disabled={!showSignedTx}
316 />
317 <Condition when="viewSigned" is={true}>
318 <SignedTransactionList
319 serializedTxsHex={serializedTxsHex}
320 maxCost={maxCost}
321 nonce={nonce}
322 />
323 </Condition>
324 </>
325 )}
326 </>
327 )}
328 </BridgeForm>
329 </Grid>
330 );
333function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
334 return serializedTxsHex.map((serializedTxHex, i) => (
335 <React.Fragment key={i}>
336 <Grid.Divider />
337 <Grid.Item
338 className="pv4 black f5 eth-tx-button"
339 full
340 as={Flex}
341 justify="between">
342 <Flex.Item>Nonce</Flex.Item>
343 <Flex.Item>{nonce + i}</Flex.Item>
344 </Grid.Item>
345 <Grid.Divider />
346 <Grid.Item
347 className="pv4 black f5 eth-tx-button"
348 full
349 as={Flex}
350 justify="between">
351 <Flex.Item>Transaction Cost</Flex.Item>
352 <Flex.Item>{maxCost} ETH</Flex.Item>
353 </Grid.Item>
354 <Grid.Divider />
355 <Grid.Item
356 className="mt3 mb2 eth-tx-button"
357 full
358 as={Flex}
359 justify="between">
360 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
361 <Flex.Item as={CopyButton} text={serializedTxHex} />
362 </Grid.Item>
363 <Grid.Item
364 className="mb4 f6 mono gray4 wrap eth-tx-button"
365 full
366 as="code">
367 {serializedTxHex}
368 </Grid.Item>
369 <Grid.Divider />
370 </React.Fragment>
371 ));
374function TransactionReceipt({ txHashes, finalCost, onClose }) {
375 const txUrls = useExploreTxUrls(txHashes);
376 return (
377 <>
378 <Grid.Item
379 className="pv2 black f5 eth-tx-button"
380 full
381 as={Flex}
382 justify="between">
383 <Flex.Item>Receipt</Flex.Item>
384 <Flex.Item onClick={onClose} className="underline pointer">
385 Close
386 </Flex.Item>
387 </Grid.Item>
388 <Grid.Item
389 className="pv2 black f5 eth-tx-button"
390 full
391 as={Flex}
392 justify="between">
393 <Flex.Item>Transaction Cost</Flex.Item>
394 <Flex.Item className="mono">{finalCost} ETH</Flex.Item>
395 </Grid.Item>
396 <Grid.Divider />
397 <Grid.Item className="pv4 eth-tx-button" full as={Flex} col>
398 <Flex.Item as={H5}>Receipt</Flex.Item>
400 {txHashes &&
401 txHashes.map((txHash, i) => (
402 <Flex.Item as={Flex} className="mv3">
403 <>
404 <Flex.Item
405 key={i}
406 flex
407 as="code"
408 className="f6 mono gray4 wrap ">
409 {txHash}
410 </Flex.Item>
411 <Flex.Item className="ml8" as={LinkButton} href={txUrls[i]}>
412 Etherscan↗
413 </Flex.Item>
414 </>
415 </Flex.Item>
416 ))}
417 </Grid.Item>
418 </>
419 );