on 976258c help sign in
bridge/src/components/InlineEthereumTransaction.js
370 lines 10 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,
53 finalCost,
55 // additional from parent
56 label = 'Generate & Sign Transaction',
57 className,
58 onReturn,
59}) {
60 // show receipt after successful broadcast
61 const [showReceipt, setShowReceipt] = useState(false);
62 const toggleShowReceipt = useCallback(() => setShowReceipt(!showReceipt), [
63 setShowReceipt,
64 showReceipt,
65 ]);
66 // show configure controls pre-broadcast
67 const showConfigureInput = !(signed || broadcasted || confirmed || completed);
68 // show the send/loading button while signed, broadcasting, or confirme
69 const showBroadcastButton = signed;
70 const showLoadingButton = broadcasted || confirmed;
71 const canBroadcast = signed && !needFunds;
72 // show signed tx only when signing (for offline usage)
73 const showSignedTx = signed;
75 const [showGasDetails, _setShowGasDetails] = useState(false);
76 const toggleGasDetails = useCallback(
77 () => _setShowGasDetails(!showGasDetails),
78 [_setShowGasDetails, showGasDetails]
79 );
81 const validate = useMemo(
82 () =>
83 composeValidator({
84 useAdvanced: buildCheckboxValidator(),
85 viewSigned: buildCheckboxValidator(),
86 }),
87 []
88 );
90 const onValues = useCallback(
91 ({ valid, values, form }) => {
92 if (!values.useAdvanced) {
93 resetGasPrice();
94 }
95 },
96 [resetGasPrice]
97 );
99 const renderPrimarySection = () => {
100 if (error) {
101 return (
102 <Grid.Item full as={RestartButton} solid onClick={() => reset()}>
103 Reset Transaction
104 </Grid.Item>
105 );
106 } else if (completed) {
107 return (
108 <>
109 <Grid.Divider />
110 <Grid.Item full as={RestartButton} onClick={onReturn}>
111 Return
112 </Grid.Item>
113 <Grid.Divider />
114 {showReceipt && (
115 <TransactionReceipt
116 txHashes={txHashes}
117 finalCost={finalCost}
118 onClose={toggleShowReceipt}
119 />
120 )}
122 {!showReceipt && (
123 <Grid.Item
124 className="underline mv2 pointer"
125 full
126 onClick={toggleShowReceipt}>
127 View Receipt
128 </Grid.Item>
129 )}
130 </>
131 );
132 } else if (showBroadcastButton) {
133 return (
134 <Grid.Item
135 full
136 as={ForwardButton}
137 solid
138 success
139 disabled={!canBroadcast}
140 onClick={() => broadcast()}>
141 Send Transaction
142 </Grid.Item>
143 );
144 } else if (showLoadingButton) {
145 return (
146 <Grid.Item
147 full
148 as={ProgressButton}
149 success
150 disabled
151 progress={confirmationProgress}>
152 Sending Transaction
153 </Grid.Item>
154 );
155 } else {
156 return (
157 <Grid.Item
158 full
159 as={GenerateButton}
160 onClick={generateAndSign}
161 disabled={!canSign}
162 loading={!canSign && initializing}>
163 {label}
164 </Grid.Item>
165 );
166 }
167 };
169 const serializedTxsHex = useMemo(
170 () =>
171 signedTransactions &&
172 signedTransactions.map(stx => hexify(stx.serialize())),
173 [signedTransactions]
174 );
176 const numTxs = useMemo(() => (unsignedTransactions || []).length || 1, [
177 unsignedTransactions,
178 ]);
179 const maxCost = useMemo(
180 () =>
181 safeFromWei(
182 safeToWei(
183 toBN(gasLimit)
184 .mul(toBN(gasPrice))
185 .mul(toBN(numTxs)),
186 'gwei'
187 ),
188 'ether'
189 ),
190 [gasLimit, gasPrice, numTxs]
191 );
193 const gasInfo = useMemo(() => {
194 const extra = numTxs === 1 ? '' : `* ${numTxs} txs`;
195 return showGasDetails
196 ? `${gasPrice} Gwei * ${gasLimit} ${extra} = ${maxCost} ETH`
197 : `${maxCost} ETH`;
198 }, [showGasDetails, maxCost, gasPrice, gasLimit, numTxs]);
200 return (
201 <Grid className={cn(className, 'mt1')}>
202 <BridgeForm validate={validate} onValues={onValues}>
203 {() => (
204 <>
205 {renderPrimarySection()}
207 {error && (
208 <Grid.Item full as={ErrorText} className="mv1">
209 {error.message}
210 </Grid.Item>
211 )}
213 {needFunds && (
214 <Grid.Item
215 full
216 as={NeedFundsNotice}
217 className="mt3"
218 {...needFunds}
219 />
220 )}
222 {showConfigureInput && (
223 <>
224 <Grid.Item
225 full
226 as={ToggleInput}
227 name="useAdvanced"
228 label="Advanced"
229 inverseLabel="Back to Defaults"
230 inverseColor="red3"
231 disabled={!showConfigureInput || initializing}
232 />
234 <Condition when="useAdvanced" is={true}>
235 <Grid.Item full className="mb2"></Grid.Item>
236 <Grid.Divider />
237 <Grid.Item
238 full
239 as={Flex}
240 row
241 justify="between"
242 className="mt2">
243 <Flex.Item as={H5}>Transaction Fee</Flex.Item>
245 <Flex.Item as={H5}>
246 {gasInfo}{' '}
247 <InfoIcon
248 className="pointer"
249 onClick={toggleGasDetails}
250 />
251 </Flex.Item>
252 </Grid.Item>
254 {/* TODO(shrugs): move to indigo/RangeInput */}
255 <Grid.Item
256 full
257 as="input"
258 type="range"
259 min="1"
260 max="400"
261 value={gasPrice}
262 onChange={e =>
263 setGasPrice(convertToInt(e.target.value, 10))
264 }
265 />
266 <Grid.Item
267 full
268 as={Flex}
269 row
270 justify="between"
271 className="f6 mt1">
272 <Flex.Item as={Text}>Cheap</Flex.Item>
273 <Flex.Item as={Text}>Fast</Flex.Item>
274 </Grid.Item>
275 <Grid.Divider className="mt4" />
276 </Condition>
277 </>
278 )}
280 {showSignedTx && (
281 <>
282 <Grid.Item
283 full
284 as={ToggleInput}
285 name="viewSigned"
286 label="Signed Transaction"
287 inverseLabel="Hide"
288 disabled={!showSignedTx}
289 />
290 <Condition when="viewSigned" is={true}>
291 <SignedTransactionList
292 serializedTxsHex={serializedTxsHex}
293 maxCost={maxCost}
294 nonce={nonce}
295 />
296 </Condition>
297 </>
298 )}
299 </>
300 )}
301 </BridgeForm>
302 </Grid>
303 );
306function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
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>Transaction Cost</Flex.Item>
317 <Flex.Item>{maxCost} ETH</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, finalCost, 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>Transaction Cost</Flex.Item>
344 <Flex.Item className="mono">{finalCost} 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 );