on 30bb32b help sign in
bridge/src/components/InlineEthereumTransaction.js
379 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 Button,
11} from 'indigo-react';
12import { toBN } from 'web3-utils';
14import { useExploreTxUrls } from 'lib/explorer';
15import { hexify } from 'lib/txn';
16import { safeFromWei, safeToWei } from 'lib/lib';
18import { composeValidator, buildCheckboxValidator } from 'form/validators';
19import BridgeForm from 'form/BridgeForm';
20import Condition from 'form/Condition';
22import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
23import ProgressButton from './ProgressButton';
24import NeedFundsNotice from './NeedFundsNotice';
25import NoticeBox from './NoticeBox';
26import FeeDropdown from './L2/Dropdowns/FeeDropdown';
27import CopyButton from './copiable/CopyButton';
29import './InlineEthereumTransaction.scss';
30import { useRollerStore } from 'store/rollerStore';
32export default function InlineEthereumTransaction({
33 // from useEthereumTransaction.bind
34 initializing,
35 canSign,
36 generateAndSign,
37 signed,
38 fakeSigned,
39 broadcast,
40 broadcasted,
41 confirmed,
42 completed,
43 reset,
44 error,
45 gasPrice,
46 setGasPrice,
47 resetGasPrice,
48 txHashes,
49 nonce,
50 chainId,
51 needFunds,
52 signedTransactions,
53 confirmationProgress,
54 gasLimit,
55 unsignedTransactions,
56 finalCost,
58 // additional from parent
59 label = 'Generate & Sign Transaction',
60 className,
61 onReturn,
62}) {
63 const { ethBalance } = useRollerStore();
64 // show receipt after successful broadcast
65 const [showReceipt, setShowReceipt] = useState(false);
66 const toggleShowReceipt = useCallback(() => setShowReceipt(!showReceipt), [
67 setShowReceipt,
68 showReceipt,
69 ]);
70 // show configure controls pre-broadcast
71 const showConfigureInput = !(signed || broadcasted || confirmed || completed);
72 // show the send/loading button while signed, broadcasting, or confirme
73 const showBroadcastButton = signed;
74 const showLoadingButton = broadcasted || confirmed;
75 const canBroadcast = signed && !needFunds;
76 // show signed tx only when signing (for offline usage)
77 const showSignedTx = signed;
79 const validate = useMemo(
80 () =>
81 composeValidator({
82 useAdvanced: buildCheckboxValidator(),
83 viewSigned: buildCheckboxValidator(),
84 }),
85 []
86 );
88 const onValues = useCallback(
89 ({ valid, values, form }) => {
90 if (!values.useAdvanced) {
91 resetGasPrice();
92 }
93 },
94 [resetGasPrice]
95 );
97 const serializedTxsHex = useMemo(
98 () =>
99 signedTransactions &&
100 signedTransactions.map(stx => hexify(stx.serialize())),
101 [signedTransactions]
102 );
104 const numTxs = useMemo(() => (unsignedTransactions || []).length || 1, [
105 unsignedTransactions,
106 ]);
107 const maxCost = useMemo(
108 () =>
109 safeFromWei(
110 safeToWei(
111 toBN(gasLimit)
112 .mul(toBN(gasPrice))
113 .mul(toBN(numTxs)),
114 'gwei'
115 ),
116 'ether'
117 ),
118 [gasLimit, gasPrice, numTxs]
119 );
121 const insufficientEth = useMemo(
122 () => Number(maxCost) > Number(ethBalance.toString()),
123 [maxCost, ethBalance]
124 );
126 const renderPrimarySection = () => {
127 if (error) {
128 return (
129 <Grid.Item
130 className="eth-tx-button"
131 full
132 as={RestartButton}
133 solid
134 center
135 onClick={() => reset()}>
136 Reset Transaction
137 </Grid.Item>
138 );
139 } else if (completed) {
140 return (
141 <>
142 <Grid.Divider />
143 <Grid.Item
144 className="eth-tx-button"
145 full
146 as={RestartButton}
147 center
148 onClick={onReturn}>
149 Return
150 </Grid.Item>
151 <Grid.Divider />
152 {showReceipt && (
153 <TransactionReceipt
154 txHashes={txHashes}
155 finalCost={finalCost}
156 onClose={toggleShowReceipt}
157 />
158 )}
160 {!showReceipt && (
161 <Grid.Item
162 className="underline mv2 pointer"
163 full
164 onClick={toggleShowReceipt}>
165 View Receipt
166 </Grid.Item>
167 )}
168 </>
169 );
170 } else if (insufficientEth) {
171 return (
172 <Grid.Item
173 className="ph4 eth-tx-button insufficient-eth"
174 full
175 as={Button}
176 solid
177 center
178 disabled>
179 Insufficient ETH
180 </Grid.Item>
181 );
182 } else if (showBroadcastButton) {
183 return (
184 <Grid.Item
185 className="ph4 eth-tx-button"
186 full
187 as={ForwardButton}
188 solid
189 success
190 center
191 disabled={!canBroadcast}
192 onClick={() => broadcast()}>
193 Send Transaction
194 </Grid.Item>
195 );
196 } else if (showLoadingButton) {
197 return (
198 <Grid.Item
199 className="ph4 eth-tx-button"
200 full
201 as={ProgressButton}
202 center
203 success
204 disabled
205 progress={confirmationProgress}>
206 Sending Transaction
207 </Grid.Item>
208 );
209 } else {
210 return (
211 <Grid.Item
212 className="ph4 eth-tx-button"
213 full
214 as={GenerateButton}
215 center
216 onClick={generateAndSign}
217 disabled={!canSign}
218 loading={!canSign && initializing}>
219 {label}
220 </Grid.Item>
221 );
222 }
223 };
225 return (
226 <Grid className={cn(className, 'mt1', 'inline-ethereum-tx')}>
227 <BridgeForm validate={validate} onValues={onValues}>
228 {() => (
229 <>
230 {showConfigureInput && (
231 <>
232 <Grid.Item
233 className="mt2 text eth-tx-button"
234 full
235 as={Flex}
236 row
237 justify="between">
238 <Flex.Item as={H5}>Gas Fee</Flex.Item>
239 <FeeDropdown setGasPrice={setGasPrice} />
240 </Grid.Item>
242 <Grid.Item
243 className="mt2 text eth-tx-button"
244 full
245 as={Flex}
246 row
247 justify="between">
248 <Flex.Item as={H5}>Max Transaction Cost</Flex.Item>
249 <Flex.Item as={H5}>{maxCost} ETH</Flex.Item>
250 </Grid.Item>
251 <Grid.Divider className="mt4" />
252 </>
253 )}
255 {renderPrimarySection()}
257 {error && (
258 <Grid.Item full as={ErrorText} className="mv1">
259 {error.message}
260 </Grid.Item>
261 )}
263 {needFunds && (
264 <Grid.Item
265 full
266 as={NeedFundsNotice}
267 className="mt3"
268 {...needFunds}
269 />
270 )}
272 {showSignedTx && fakeSigned && (
273 <>
274 <Grid.Item full as={NoticeBox} className="mt2">
275 Your wallet will sign the transaction upon sending it.
276 </Grid.Item>
277 </>
278 )}
280 {showSignedTx && !fakeSigned && (
281 <>
282 <Grid.Item
283 className="eth-tx-button text"
284 full
285 as={ToggleInput}
286 name="viewSigned"
287 label="Signed Transaction"
288 inverseLabel="Hide"
289 disabled={!showSignedTx}
290 />
291 <Condition when="viewSigned" is={true}>
292 <SignedTransactionList
293 serializedTxsHex={serializedTxsHex}
294 maxCost={maxCost}
295 nonce={nonce}
296 />
297 </Condition>
298 </>
299 )}
300 </>
301 )}
302 </BridgeForm>
303 </Grid>
304 );
307function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
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>Transaction Cost</Flex.Item>
318 <Flex.Item>{maxCost} ETH</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, finalCost, onClose }) {
334 const txUrls = useExploreTxUrls(txHashes);
335 return (
336 <>
337 <Grid.Item
338 className="pv2 black f5 eth-tx-button"
339 full
340 as={Flex}
341 justify="between">
342 <Flex.Item>Receipt</Flex.Item>
343 <Flex.Item onClick={onClose} className="underline pointer">
344 Close
345 </Flex.Item>
346 </Grid.Item>
347 <Grid.Item
348 className="pv2 black f5 eth-tx-button"
349 full
350 as={Flex}
351 justify="between">
352 <Flex.Item>Transaction Cost</Flex.Item>
353 <Flex.Item className="mono">{finalCost} ETH</Flex.Item>
354 </Grid.Item>
355 <Grid.Divider />
356 <Grid.Item className="pv4 eth-tx-button" full as={Flex} col>
357 <Flex.Item as={H5}>Receipt</Flex.Item>
359 {txHashes &&
360 txHashes.map((txHash, i) => (
361 <Flex.Item as={Flex} className="mv3">
362 <>
363 <Flex.Item
364 key={i}
365 flex
366 as="code"
367 className="f6 mono gray4 wrap ">
368 {txHash}
369 </Flex.Item>
370 <Flex.Item className="ml8" as={LinkButton} href={txUrls[i]}>
371 Etherscan↗
372 </Flex.Item>
373 </>
374 </Flex.Item>
375 ))}
376 </Grid.Item>
377 </>
378 );