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