on b6873da help sign in
bridge/src/components/InlineEthereumTransaction.js
354 lines 9.6 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} from 'indigo-react';
11import { toBN } from 'web3-utils';
13import { useExploreTxUrls } from 'lib/explorer';
14import { hexify } from 'lib/txn';
15import { safeFromWei, safeToWei } from 'lib/lib';
17import { composeValidator, buildCheckboxValidator } from 'form/validators';
18import BridgeForm from 'form/BridgeForm';
19import Condition from 'form/Condition';
21import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
22import ProgressButton from './ProgressButton';
23import NeedFundsNotice from './NeedFundsNotice';
24import NoticeBox from './NoticeBox';
25import FeeDropdown from './L2/Dropdowns/FeeDropdown';
26import CopyButton from './copiable/CopyButton';
28import './InlineEthereumTransaction.scss';
30export default function InlineEthereumTransaction({
31 // from useEthereumTransaction.bind
32 initializing,
33 canSign,
34 generateAndSign,
35 signed,
36 fakeSigned,
37 broadcast,
38 broadcasted,
39 confirmed,
40 completed,
41 reset,
42 error,
43 gasPrice,
44 setGasPrice,
45 resetGasPrice,
46 txHashes,
47 nonce,
48 chainId,
49 needFunds,
50 signedTransactions,
51 confirmationProgress,
52 gasLimit,
53 unsignedTransactions,
54 finalCost,
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 validate = useMemo(
77 () =>
78 composeValidator({
79 useAdvanced: buildCheckboxValidator(),
80 viewSigned: buildCheckboxValidator(),
81 }),
82 []
83 );
85 const onValues = useCallback(
86 ({ valid, values, form }) => {
87 if (!values.useAdvanced) {
88 resetGasPrice();
89 }
90 },
91 [resetGasPrice]
92 );
94 const renderPrimarySection = () => {
95 if (error) {
96 return (
97 <Grid.Item
98 className="eth-tx-button"
99 full
100 as={RestartButton}
101 solid
102 onClick={() => reset()}>
103 Reset Transaction
104 </Grid.Item>
105 );
106 } else if (completed) {
107 return (
108 <>
109 <Grid.Divider />
110 <Grid.Item
111 className="eth-tx-button"
112 full
113 as={RestartButton}
114 onClick={onReturn}>
115 Return
116 </Grid.Item>
117 <Grid.Divider />
118 {showReceipt && (
119 <TransactionReceipt
120 txHashes={txHashes}
121 finalCost={finalCost}
122 onClose={toggleShowReceipt}
123 />
124 )}
126 {!showReceipt && (
127 <Grid.Item
128 className="underline mv2 pointer"
129 full
130 onClick={toggleShowReceipt}>
131 View Receipt
132 </Grid.Item>
133 )}
134 </>
135 );
136 } else if (showBroadcastButton) {
137 return (
138 <Grid.Item
139 className="ph4 eth-tx-button"
140 full
141 as={ForwardButton}
142 solid
143 success
144 disabled={!canBroadcast}
145 onClick={() => broadcast()}>
146 Send Transaction
147 </Grid.Item>
148 );
149 } else if (showLoadingButton) {
150 return (
151 <Grid.Item
152 className="ph4 eth-tx-button"
153 full
154 as={ProgressButton}
155 success
156 disabled
157 progress={confirmationProgress}>
158 Sending Transaction
159 </Grid.Item>
160 );
161 } else {
162 return (
163 <Grid.Item
164 className="ph4 eth-tx-button"
165 full
166 as={GenerateButton}
167 onClick={generateAndSign}
168 disabled={!canSign}
169 loading={!canSign && initializing}>
170 {label}
171 </Grid.Item>
172 );
173 }
174 };
176 const serializedTxsHex = useMemo(
177 () =>
178 signedTransactions &&
179 signedTransactions.map(stx => hexify(stx.serialize())),
180 [signedTransactions]
181 );
183 const numTxs = useMemo(() => (unsignedTransactions || []).length || 1, [
184 unsignedTransactions,
185 ]);
186 const maxCost = useMemo(
187 () =>
188 safeFromWei(
189 safeToWei(
190 toBN(gasLimit)
191 .mul(toBN(gasPrice))
192 .mul(toBN(numTxs)),
193 'gwei'
194 ),
195 'ether'
196 ),
197 [gasLimit, gasPrice, numTxs]
198 );
200 return (
201 <Grid className={cn(className, 'mt1', 'inline-ethereum-tx')}>
202 <BridgeForm validate={validate} onValues={onValues}>
203 {() => (
204 <>
205 {showConfigureInput && (
206 <>
207 <Grid.Item
208 className="mt2 text eth-tx-button"
209 full
210 as={Flex}
211 row
212 justify="between">
213 <Flex.Item as={H5}>Gas Fee</Flex.Item>
214 <FeeDropdown setGasPrice={setGasPrice} />
215 </Grid.Item>
217 <Grid.Item
218 className="mt2 text eth-tx-button"
219 full
220 as={Flex}
221 row
222 justify="between">
223 <Flex.Item as={H5}>Max Transaction Cost</Flex.Item>
224 <Flex.Item as={H5}>{maxCost} ETH</Flex.Item>
225 </Grid.Item>
226 <Grid.Divider className="mt4" />
227 </>
228 )}
230 {renderPrimarySection()}
232 {error && (
233 <Grid.Item full as={ErrorText} className="mv1">
234 {error.message}
235 </Grid.Item>
236 )}
238 {needFunds && (
239 <Grid.Item
240 full
241 as={NeedFundsNotice}
242 className="mt3"
243 {...needFunds}
244 />
245 )}
247 {showSignedTx && fakeSigned && (
248 <>
249 <Grid.Item full as={NoticeBox} className="mt2">
250 Your wallet will sign the transaction upon sending it.
251 </Grid.Item>
252 </>
253 )}
255 {showSignedTx && !fakeSigned && (
256 <>
257 <Grid.Item
258 className="eth-tx-button text"
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 maxCost={maxCost}
270 nonce={nonce}
271 />
272 </Condition>
273 </>
274 )}
275 </>
276 )}
277 </BridgeForm>
278 </Grid>
279 );
282function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
283 return serializedTxsHex.map((serializedTxHex, i) => (
284 <React.Fragment key={i}>
285 <Grid.Divider />
286 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
287 <Flex.Item>Nonce</Flex.Item>
288 <Flex.Item>{nonce + i}</Flex.Item>
289 </Grid.Item>
290 <Grid.Divider />
291 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
292 <Flex.Item>Transaction Cost</Flex.Item>
293 <Flex.Item>{maxCost} ETH</Flex.Item>
294 </Grid.Item>
295 <Grid.Divider />
296 <Grid.Item full as={Flex} justify="between" className="mt3 mb2">
297 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
298 <Flex.Item as={CopyButton} text={serializedTxHex} />
299 </Grid.Item>
300 <Grid.Item full as="code" className="mb4 f6 mono gray4 wrap">
301 {serializedTxHex}
302 </Grid.Item>
303 <Grid.Divider />
304 </React.Fragment>
305 ));
308function TransactionReceipt({ txHashes, finalCost, onClose }) {
309 const txUrls = useExploreTxUrls(txHashes);
310 return (
311 <>
312 <Grid.Item
313 className="pv2 black f5 eth-tx-button"
314 full
315 as={Flex}
316 justify="between">
317 <Flex.Item>Receipt</Flex.Item>
318 <Flex.Item onClick={onClose} className="underline pointer">
319 Close
320 </Flex.Item>
321 </Grid.Item>
322 <Grid.Item
323 className="pv2 black f5 eth-tx-button"
324 full
325 as={Flex}
326 justify="between">
327 <Flex.Item>Transaction Cost</Flex.Item>
328 <Flex.Item className="mono">{finalCost} ETH</Flex.Item>
329 </Grid.Item>
330 <Grid.Divider />
331 <Grid.Item className="pv4 eth-tx-button" full as={Flex} col>
332 <Flex.Item as={H5}>Receipt</Flex.Item>
334 {txHashes &&
335 txHashes.map((txHash, i) => (
336 <Flex.Item as={Flex} className="mv3">
337 <>
338 <Flex.Item
339 key={i}
340 flex
341 as="code"
342 className="f6 mono gray4 wrap ">
343 {txHash}
344 </Flex.Item>
345 <Flex.Item className="ml8" as={LinkButton} href={txUrls[i]}>
346 Etherscan↗
347 </Flex.Item>
348 </>
349 </Flex.Item>
350 ))}
351 </Grid.Item>
352 </>
353 );