on 98d950d help sign in
bridge/src/components/InlineEthereumTransaction.js
378 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 onClick={onReturn}>
148 Return
149 </Grid.Item>
150 <Grid.Divider />
151 {showReceipt && (
152 <TransactionReceipt
153 txHashes={txHashes}
154 finalCost={finalCost}
155 onClose={toggleShowReceipt}
156 />
157 )}
159 {!showReceipt && (
160 <Grid.Item
161 className="underline mv2 pointer"
162 full
163 onClick={toggleShowReceipt}>
164 View Receipt
165 </Grid.Item>
166 )}
167 </>
168 );
169 } else if (insufficientEth) {
170 return (
171 <Grid.Item
172 className="ph4 eth-tx-button insufficient-eth"
173 full
174 as={Button}
175 solid
176 center
177 disabled>
178 Insufficient ETH
179 </Grid.Item>
180 );
181 } else if (showBroadcastButton) {
182 return (
183 <Grid.Item
184 className="ph4 eth-tx-button"
185 full
186 as={ForwardButton}
187 solid
188 success
189 center
190 disabled={!canBroadcast}
191 onClick={() => broadcast()}>
192 Send Transaction
193 </Grid.Item>
194 );
195 } else if (showLoadingButton) {
196 return (
197 <Grid.Item
198 className="ph4 eth-tx-button"
199 full
200 as={ProgressButton}
201 center
202 success
203 disabled
204 progress={confirmationProgress}>
205 Sending Transaction
206 </Grid.Item>
207 );
208 } else {
209 return (
210 <Grid.Item
211 className="ph4 eth-tx-button"
212 full
213 as={GenerateButton}
214 center
215 onClick={generateAndSign}
216 disabled={!canSign}
217 loading={!canSign && initializing}>
218 {label}
219 </Grid.Item>
220 );
221 }
222 };
224 return (
225 <Grid className={cn(className, 'mt1', 'inline-ethereum-tx')}>
226 <BridgeForm validate={validate} onValues={onValues}>
227 {() => (
228 <>
229 {showConfigureInput && (
230 <>
231 <Grid.Item
232 className="mt2 text eth-tx-button"
233 full
234 as={Flex}
235 row
236 justify="between">
237 <Flex.Item as={H5}>Gas Fee</Flex.Item>
238 <FeeDropdown setGasPrice={setGasPrice} />
239 </Grid.Item>
241 <Grid.Item
242 className="mt2 text eth-tx-button"
243 full
244 as={Flex}
245 row
246 justify="between">
247 <Flex.Item as={H5}>Max Transaction Cost</Flex.Item>
248 <Flex.Item as={H5}>{maxCost} ETH</Flex.Item>
249 </Grid.Item>
250 <Grid.Divider className="mt4" />
251 </>
252 )}
254 {renderPrimarySection()}
256 {error && (
257 <Grid.Item full as={ErrorText} className="mv1">
258 {error.message}
259 </Grid.Item>
260 )}
262 {needFunds && (
263 <Grid.Item
264 full
265 as={NeedFundsNotice}
266 className="mt3"
267 {...needFunds}
268 />
269 )}
271 {showSignedTx && fakeSigned && (
272 <>
273 <Grid.Item full as={NoticeBox} className="mt2">
274 Your wallet will sign the transaction upon sending it.
275 </Grid.Item>
276 </>
277 )}
279 {showSignedTx && !fakeSigned && (
280 <>
281 <Grid.Item
282 className="eth-tx-button text"
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
337 className="pv2 black f5 eth-tx-button"
338 full
339 as={Flex}
340 justify="between">
341 <Flex.Item>Receipt</Flex.Item>
342 <Flex.Item onClick={onClose} className="underline pointer">
343 Close
344 </Flex.Item>
345 </Grid.Item>
346 <Grid.Item
347 className="pv2 black f5 eth-tx-button"
348 full
349 as={Flex}
350 justify="between">
351 <Flex.Item>Transaction Cost</Flex.Item>
352 <Flex.Item className="mono">{finalCost} ETH</Flex.Item>
353 </Grid.Item>
354 <Grid.Divider />
355 <Grid.Item className="pv4 eth-tx-button" full as={Flex} col>
356 <Flex.Item as={H5}>Receipt</Flex.Item>
358 {txHashes &&
359 txHashes.map((txHash, i) => (
360 <Flex.Item as={Flex} className="mv3">
361 <>
362 <Flex.Item
363 key={i}
364 flex
365 as="code"
366 className="f6 mono gray4 wrap ">
367 {txHash}
368 </Flex.Item>
369 <Flex.Item className="ml8" as={LinkButton} href={txUrls[i]}>
370 Etherscan↗
371 </Flex.Item>
372 </>
373 </Flex.Item>
374 ))}
375 </Grid.Item>
376 </>
377 );