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