on d72d3ca help sign in
bridge/src/components/InlineEthereumTransaction.jsx
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, // Type: GasPriceData
46 setGasPrice, // Type: (g: GasPriceData) => void
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.price))
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 );