on 148df77 help sign in
bridge/src/components/InlineEthereumTransaction.js
369 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,
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 (
107 <>
108 <Grid.Divider />
109 <Grid.Item full as={RestartButton} onClick={onReturn}>
110 Return
111 </Grid.Item>
112 <Grid.Divider />
113 {showReceipt && (
114 <TransactionReceipt
115 txHashes={txHashes}
116 maxCost={maxCost}
117 onClose={toggleShowReceipt}
118 />
119 )}
121 {!showReceipt && (
122 <Grid.Item
123 className="underline mv2 pointer"
124 full
125 onClick={toggleShowReceipt}>
126 View Receipt
127 </Grid.Item>
128 )}
129 </>
130 );
131 } else if (showBroadcastButton) {
132 return (
133 <Grid.Item
134 full
135 as={ForwardButton}
136 solid
137 success
138 disabled={!canBroadcast}
139 onClick={() => broadcast()}>
140 Send Transaction
141 </Grid.Item>
142 );
143 } else if (showLoadingButton) {
144 return (
145 <Grid.Item
146 full
147 as={ProgressButton}
148 success
149 disabled
150 progress={confirmationProgress}>
151 Sending Transaction
152 </Grid.Item>
153 );
154 } else {
155 return (
156 <Grid.Item
157 full
158 as={GenerateButton}
159 onClick={generateAndSign}
160 disabled={!canSign}
161 loading={!canSign && initializing}>
162 {label}
163 </Grid.Item>
164 );
165 }
166 };
168 const serializedTxsHex = useMemo(
169 () =>
170 signedTransactions &&
171 signedTransactions.map(stx => hexify(stx.serialize())),
172 [signedTransactions]
173 );
175 const numTxs = useMemo(() => (unsignedTransactions || []).length || 1, [
176 unsignedTransactions,
177 ]);
178 const maxCost = useMemo(
179 () =>
180 safeFromWei(
181 safeToWei(
182 toBN(gasLimit)
183 .mul(toBN(gasPrice))
184 .mul(toBN(numTxs)),
185 'gwei'
186 ),
187 'eth'
188 ),
189 [gasLimit, gasPrice, numTxs]
190 );
192 const gasInfo = useMemo(() => {
193 const extra = numTxs === 1 ? '' : `* ${numTxs} TXN`;
194 return showGasDetails
195 ? `${gasPrice} Gwei * ${gasLimit} ${extra} = ${maxCost} ETH`
196 : `${maxCost} ETH`;
197 }, [showGasDetails, maxCost, gasPrice, gasLimit, numTxs]);
199 return (
200 <Grid className={cn(className, 'mt1')}>
201 <BridgeForm validate={validate} onValues={onValues}>
202 {() => (
203 <>
204 {renderPrimarySection()}
206 {error && (
207 <Grid.Item full as={ErrorText} className="mv1">
208 {error.message}
209 </Grid.Item>
210 )}
212 {needFunds && (
213 <Grid.Item
214 full
215 as={NeedFundsNotice}
216 className="mt3"
217 {...needFunds}
218 />
219 )}
221 {showConfigureInput && (
222 <>
223 <Grid.Item
224 full
225 as={ToggleInput}
226 name="useAdvanced"
227 label="Advanced"
228 inverseLabel="Back to Defaults"
229 inverseColor="red3"
230 disabled={!showConfigureInput || initializing}
231 />
233 <Condition when="useAdvanced" is={true}>
234 <Grid.Item full className="mb2"></Grid.Item>
235 <Grid.Divider />
236 <Grid.Item
237 full
238 as={Flex}
239 row
240 justify="between"
241 className="mt2">
242 <Flex.Item as={H5}>Transaction Fee</Flex.Item>
244 <Flex.Item as={H5}>
245 {gasInfo}{' '}
246 <InfoIcon
247 className="pointer"
248 onClick={toggleGasDetails}
249 />
250 </Flex.Item>
251 </Grid.Item>
253 {/* TODO(shrugs): move to indigo/RangeInput */}
254 <Grid.Item
255 full
256 as="input"
257 type="range"
258 min="1"
259 max="100"
260 value={gasPrice}
261 onChange={e =>
262 setGasPrice(convertToInt(e.target.value, 10))
263 }
264 />
265 <Grid.Item
266 full
267 as={Flex}
268 row
269 justify="between"
270 className="f6 mt1">
271 <Flex.Item as={Text}>Cheap</Flex.Item>
272 <Flex.Item as={Text}>Fast</Flex.Item>
273 </Grid.Item>
274 <Grid.Divider className="mt4" />
275 </Condition>
276 </>
277 )}
279 {showSignedTx && (
280 <>
281 <Grid.Item
282 full
283 as={ToggleInput}
284 name="viewSigned"
285 label="Signed Transaction"
286 inverseLabel="Hide"
287 disabled={!showSignedTx}
288 />
289 <Condition when="viewSigned" is={true}>
290 <SignedTransactionList
291 serializedTxsHex={serializedTxsHex}
292 maxCost={maxCost}
293 nonce={nonce}
294 />
295 </Condition>
296 </>
297 )}
298 </>
299 )}
300 </BridgeForm>
301 </Grid>
302 );
305function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
306 return serializedTxsHex.map((serializedTxHex, i) => (
307 <React.Fragment key={i}>
308 <Grid.Divider />
309 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
310 <Flex.Item>Nonce</Flex.Item>
311 <Flex.Item>{nonce + i}</Flex.Item>
312 </Grid.Item>
313 <Grid.Divider />
314 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
315 <Flex.Item>Transaction Cost</Flex.Item>
316 <Flex.Item>{maxCost} ETH</Flex.Item>
317 </Grid.Item>
318 <Grid.Divider />
319 <Grid.Item full as={Flex} justify="between" className="mt3 mb2">
320 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
321 <Flex.Item as={CopyButton} text={serializedTxHex} />
322 </Grid.Item>
323 <Grid.Item full as="code" className="mb4 f6 mono gray4 wrap">
324 {serializedTxHex}
325 </Grid.Item>
326 <Grid.Divider />
327 </React.Fragment>
328 ));
331function TransactionReceipt({ txHashes, maxCost, onClose }) {
332 const txUrls = useExploreTxUrls(txHashes);
333 return (
334 <>
335 <Grid.Item full as={Flex} justify="between" className="pv2 black f5">
336 <Flex.Item>Receipt</Flex.Item>
337 <Flex.Item onClick={onClose} className="underline pointer">
338 Close
339 </Flex.Item>
340 </Grid.Item>
341 <Grid.Item full as={Flex} justify="between" className="pv2 black f5">
342 <Flex.Item>Transaction Cost</Flex.Item>
343 <Flex.Item className="mono">{maxCost} ETH</Flex.Item>
344 </Grid.Item>
345 <Grid.Divider />
346 <Grid.Item full as={Flex} col className="pv4">
347 <Flex.Item as={H5}>Receipt</Flex.Item>
349 {txHashes &&
350 txHashes.map((txHash, i) => (
351 <Flex.Item as={Flex} className="mv3">
352 <>
353 <Flex.Item
354 key={i}
355 flex
356 as="code"
357 className="f6 mono gray4 wrap ">
358 {txHash}
359 </Flex.Item>
360 <Flex.Item className="ml8" as={LinkButton} href={txUrls[i]}>
361 Etherscan↗
362 </Flex.Item>
363 </>
364 </Flex.Item>
365 ))}
366 </Grid.Item>
367 </>
368 );