on 97de700 help sign in
bridge/src/components/InlineEthereumTransaction.js
371 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 IconButton,
12} from 'indigo-react';
13import { toBN } from 'web3-utils';
15import { ReactComponent as InfoIcon } from 'assets/info.svg';
17import { useExploreTxUrls } from 'lib/explorer';
18import { hexify } from 'lib/txn';
19import pluralize from 'lib/pluralize';
20import { safeFromWei, safeToWei } from 'lib/lib';
22import { composeValidator, buildCheckboxValidator } from 'form/validators';
23import BridgeForm from 'form/BridgeForm';
24import Condition from 'form/Condition';
26import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
27import CopyButton from './CopyButton';
28import ProgressButton from './ProgressButton';
29import convertToInt from 'lib/convertToInt';
30import NeedFundsNotice from './NeedFundsNotice';
32export default function InlineEthereumTransaction({
33 // from useEthereumTransaction.bind
34 initializing,
35 canSign,
36 generateAndSign,
37 signed,
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,
56 // additional from parent
57 label = 'Generate & Sign Transaction',
58 className,
59 onReturn,
60}) {
61 // show receipt after successful broadcast
62 const [showReceipt, setShowReceipt] = useState(false);
63 const toggleShowReceipt = useCallback(() => setShowReceipt(!showReceipt), [
64 setShowReceipt,
65 showReceipt,
66 ]);
67 // show configure controls pre-broadcast
68 const showConfigureInput = !(signed || broadcasted || confirmed || completed);
69 // show the send/loading button while signed, broadcasting, or confirme
70 const showBroadcastButton = signed;
71 const showLoadingButton = broadcasted || confirmed;
72 const canBroadcast = signed && !needFunds;
73 // show signed tx only when signing (for offline usage)
74 const showSignedTx = signed;
76 const [showGasDetails, _setShowGasDetails] = useState(false);
77 const toggleGasDetails = useCallback(
78 () => _setShowGasDetails(!showGasDetails),
79 [_setShowGasDetails, showGasDetails]
80 );
82 const validate = useMemo(
83 () =>
84 composeValidator({
85 useAdvanced: buildCheckboxValidator(),
86 viewSigned: buildCheckboxValidator(),
87 }),
88 []
89 );
91 const onValues = useCallback(
92 ({ valid, values, form }) => {
93 if (!values.useAdvanced) {
94 resetGasPrice();
95 }
96 },
97 [resetGasPrice]
98 );
100 const renderPrimarySection = () => {
101 if (error) {
102 return (
103 <Grid.Item full as={RestartButton} solid onClick={() => reset()}>
104 Reset Transaction
105 </Grid.Item>
106 );
107 } else if (completed) {
108 return undefined;
109 } else if (showBroadcastButton) {
110 return (
111 <Grid.Item
112 full
113 as={ForwardButton}
114 solid
115 success
116 disabled={!canBroadcast}
117 onClick={() => broadcast()}>
118 Send Transaction
119 </Grid.Item>
120 );
121 } else if (showLoadingButton) {
122 return (
123 <Grid.Item
124 full
125 as={ProgressButton}
126 success
127 disabled
128 progress={confirmationProgress}>
129 Sending Transaction
130 </Grid.Item>
131 );
132 } else {
133 return (
134 <Grid.Item
135 full
136 as={GenerateButton}
137 onClick={generateAndSign}
138 disabled={!canSign}
139 loading={!canSign && initializing}>
140 {label}
141 </Grid.Item>
142 );
143 }
144 };
146 const serializedTxsHex = useMemo(
147 () =>
148 signedTransactions &&
149 signedTransactions.map(stx => hexify(stx.serialize())),
150 [signedTransactions]
151 );
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 );
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]);
176 return (
177 <Grid className={cn(className, 'mt1')}>
178 <BridgeForm validate={validate} onValues={onValues}>
179 {() => (
180 <>
181 {renderPrimarySection()}
183 {error && (
184 <Grid.Item full as={ErrorText} className="mv1">
185 {error.message}
186 </Grid.Item>
187 )}
189 {needFunds && (
190 <Grid.Item
191 full
192 as={NeedFundsNotice}
193 className="mt3"
194 {...needFunds}
195 />
196 )}
198 {showConfigureInput && (
199 <>
200 <Grid.Item
201 full
202 as={ToggleInput}
203 name="useAdvanced"
204 label="Advanced"
205 inverseLabel="Back to Defaults"
206 inverseColor="red3"
207 disabled={!showConfigureInput || initializing}
208 />
210 <Condition when="useAdvanced" is={true}>
211 <Grid.Item full className="mb2"></Grid.Item>
212 <Grid.Divider />
213 <Grid.Item
214 full
215 as={Flex}
216 row
217 justify="between"
218 className="mt2">
219 <Flex.Item as={H5}>Gas Price</Flex.Item>
221 <Flex.Item as={H5}>
222 {gasInfo}{' '}
223 <InfoIcon
224 className="pointer"
225 onClick={toggleGasDetails}
226 />
227 </Flex.Item>
228 </Grid.Item>
230 {/* TODO(shrugs): move to indigo/RangeInput */}
231 <Grid.Item
232 full
233 as="input"
234 type="range"
235 min="1"
236 max="100"
237 value={gasPrice}
238 onChange={e =>
239 setGasPrice(convertToInt(e.target.value, 10))
240 }
241 />
242 <Grid.Item
243 full
244 as={Flex}
245 row
246 justify="between"
247 className="f6 mt1">
248 <Flex.Item as={Text}>Cheap</Flex.Item>
249 <Flex.Item as={Text}>Fast</Flex.Item>
250 </Grid.Item>
251 <Grid.Divider className="mt4" />
252 </Condition>
253 </>
254 )}
256 {showSignedTx && (
257 <>
258 <Grid.Item
259 full
260 as={ToggleInput}
261 name="viewSigned"
262 label="Signed Transaction"
263 inverseLabel="Hide"
264 disabled={!showSignedTx}
265 />
266 <Condition when="viewSigned" is={true}>
267 <SignedTransactionList
268 serializedTxsHex={serializedTxsHex}
269 gasPrice={gasPrice}
270 nonce={nonce}
271 />
272 </Condition>
273 </>
274 )}
276 {completed && (
277 <>
278 <Grid.Item full as={RestartButton} onClick={onReturn}>
279 Return
280 </Grid.Item>
281 <Grid.Divider />
282 {showReceipt && (
283 <TransactionReceipt
284 txHashes={txHashes}
285 maxCost={maxCost}
286 onClose={toggleShowReceipt}
287 />
288 )}
290 {!showReceipt && (
291 <Grid.Item
292 className="underline mv2 pointer"
293 full
294 onClick={toggleShowReceipt}>
295 View Receipt
296 </Grid.Item>
297 )}
298 </>
299 )}
300 </>
301 )}
302 </BridgeForm>
303 </Grid>
304 );
307function SignedTransactionList({ serializedTxsHex, nonce, gasPrice }) {
308 return serializedTxsHex.map((serializedTxHex, i) => (
309 <React.Fragment key="i">
310 <Grid.Divider />
311 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
312 <Flex.Item>Nonce</Flex.Item>
313 <Flex.Item>{nonce + i}</Flex.Item>
314 </Grid.Item>
315 <Grid.Divider />
316 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
317 <Flex.Item>Gas Price</Flex.Item>
318 <Flex.Item>{gasPrice.toFixed()} Gwei</Flex.Item>
319 </Grid.Item>
320 <Grid.Divider />
321 <Grid.Item full as={Flex} justify="between" className="mt3 mb2">
322 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
323 <Flex.Item as={CopyButton} text={serializedTxHex} />
324 </Grid.Item>
325 <Grid.Item full as="code" className="mb4 f6 mono gray4 wrap">
326 {serializedTxHex}
327 </Grid.Item>
328 <Grid.Divider />
329 </React.Fragment>
330 ));
333function TransactionReceipt({ txHashes, maxCost, onClose }) {
334 const txUrls = useExploreTxUrls(txHashes);
335 return (
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>
347 <Grid.Divider />
348 <Grid.Item full as={Flex} col className="pv4">
349 <Flex.Item as={H5}>Receipt</Flex.Item>
351 {txHashes &&
352 txHashes.map((txHash, i) => (
353 <Flex.Item as={Flex} className="mv3">
354 <>
355 <Flex.Item
356 key={i}
357 flex
358 as="code"
359 className="f6 mono gray4 wrap ">
360 {txHash}
361 </Flex.Item>
362 <Flex.Item className="ml8" as={LinkButton} href={txUrls[i]}>
363 Etherscan↗
364 </Flex.Item>
365 </>
366 </Flex.Item>
367 ))}
368 </Grid.Item>
369 </>
370 );