on master help sign in
bridge/src/components/InlineEthereumTransaction.jsx
375 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, gweiToWei } 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 toBN(gasLimit)
111 .mul(gweiToWei(gasPrice.price))
112 .mul(toBN(numTxs)),
113 'ether'
114 ),
115 [gasLimit, gasPrice, numTxs]
116 );
118 const insufficientEth = useMemo(
119 () => Number(maxCost) > Number(ethBalance.toString()),
120 [maxCost, ethBalance]
121 );
123 const renderPrimarySection = () => {
124 if (error) {
125 return (
126 <Grid.Item
127 className="eth-tx-button"
128 full
129 as={RestartButton}
130 solid
131 center
132 onClick={() => reset()}>
133 Reset Transaction
134 </Grid.Item>
135 );
136 } else if (completed) {
137 return (
138 <>
139 <Grid.Divider />
140 <Grid.Item
141 className="eth-tx-button"
142 full
143 as={RestartButton}
144 onClick={onReturn}>
145 Return
146 </Grid.Item>
147 <Grid.Divider />
148 {showReceipt && (
149 <TransactionReceipt
150 txHashes={txHashes}
151 finalCost={finalCost}
152 onClose={toggleShowReceipt}
153 />
154 )}
156 {!showReceipt && (
157 <Grid.Item
158 className="underline mv2 pointer"
159 full
160 onClick={toggleShowReceipt}>
161 View Receipt
162 </Grid.Item>
163 )}
164 </>
165 );
166 } else if (insufficientEth) {
167 return (
168 <Grid.Item
169 className="ph4 eth-tx-button insufficient-eth"
170 full
171 as={Button}
172 solid
173 center
174 disabled>
175 Insufficient ETH
176 </Grid.Item>
177 );
178 } else if (showBroadcastButton) {
179 return (
180 <Grid.Item
181 className="ph4 eth-tx-button"
182 full
183 as={ForwardButton}
184 solid
185 success
186 center
187 disabled={!canBroadcast}
188 onClick={() => broadcast()}>
189 Send Transaction
190 </Grid.Item>
191 );
192 } else if (showLoadingButton) {
193 return (
194 <Grid.Item
195 className="ph4 eth-tx-button"
196 full
197 as={ProgressButton}
198 center
199 success
200 disabled
201 progress={confirmationProgress}>
202 Sending Transaction
203 </Grid.Item>
204 );
205 } else {
206 return (
207 <Grid.Item
208 className="ph4 eth-tx-button"
209 full
210 as={GenerateButton}
211 center
212 onClick={generateAndSign}
213 disabled={!canSign}
214 loading={!canSign && initializing}>
215 {label}
216 </Grid.Item>
217 );
218 }
219 };
221 return (
222 <Grid className={cn(className, 'mt1', 'inline-ethereum-tx')}>
223 <BridgeForm validate={validate} onValues={onValues}>
224 {() => (
225 <>
226 {showConfigureInput && (
227 <>
228 <Grid.Item
229 className="mt2 text eth-tx-button"
230 full
231 as={Flex}
232 row
233 justify="between">
234 <Flex.Item as={H5}>Gas Fee</Flex.Item>
235 <FeeDropdown setGasPrice={setGasPrice} />
236 </Grid.Item>
238 <Grid.Item
239 className="mt2 text eth-tx-button"
240 full
241 as={Flex}
242 row
243 justify="between">
244 <Flex.Item as={H5}>Max Transaction Cost</Flex.Item>
245 <Flex.Item as={H5}>{maxCost} ETH</Flex.Item>
246 </Grid.Item>
247 <Grid.Divider className="mt4" />
248 </>
249 )}
251 {renderPrimarySection()}
253 {error && (
254 <Grid.Item full as={ErrorText} className="mv1">
255 {error.message}
256 </Grid.Item>
257 )}
259 {needFunds && (
260 <Grid.Item
261 full
262 as={NeedFundsNotice}
263 className="mt3"
264 {...needFunds}
265 />
266 )}
268 {showSignedTx && fakeSigned && (
269 <>
270 <Grid.Item full as={NoticeBox} className="mt2">
271 Your wallet will sign the transaction upon sending it.
272 </Grid.Item>
273 </>
274 )}
276 {showSignedTx && !fakeSigned && (
277 <>
278 <Grid.Item
279 className="eth-tx-button text"
280 full
281 as={ToggleInput}
282 name="viewSigned"
283 label="Signed Transaction"
284 inverseLabel="Hide"
285 disabled={!showSignedTx}
286 />
287 <Condition when="viewSigned" is={true}>
288 <SignedTransactionList
289 serializedTxsHex={serializedTxsHex}
290 maxCost={maxCost}
291 nonce={nonce}
292 />
293 </Condition>
294 </>
295 )}
296 </>
297 )}
298 </BridgeForm>
299 </Grid>
300 );
303function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
304 return serializedTxsHex.map((serializedTxHex, i) => (
305 <React.Fragment key={i}>
306 <Grid.Divider />
307 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
308 <Flex.Item>Nonce</Flex.Item>
309 <Flex.Item>{nonce + i}</Flex.Item>
310 </Grid.Item>
311 <Grid.Divider />
312 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
313 <Flex.Item>Transaction Cost</Flex.Item>
314 <Flex.Item>{maxCost} ETH</Flex.Item>
315 </Grid.Item>
316 <Grid.Divider />
317 <Grid.Item full as={Flex} justify="between" className="mt3 mb2">
318 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
319 <Flex.Item as={CopyButton} text={serializedTxHex} />
320 </Grid.Item>
321 <Grid.Item full as="code" className="mb4 f6 mono gray4 wrap">
322 {serializedTxHex}
323 </Grid.Item>
324 <Grid.Divider />
325 </React.Fragment>
326 ));
329function TransactionReceipt({ txHashes, finalCost, onClose }) {
330 const txUrls = useExploreTxUrls(txHashes);
331 return (
332 <>
333 <Grid.Item
334 className="pv2 black f5 eth-tx-button"
335 full
336 as={Flex}
337 justify="between">
338 <Flex.Item>Receipt</Flex.Item>
339 <Flex.Item onClick={onClose} className="underline pointer">
340 Close
341 </Flex.Item>
342 </Grid.Item>
343 <Grid.Item
344 className="pv2 black f5 eth-tx-button"
345 full
346 as={Flex}
347 justify="between">
348 <Flex.Item>Transaction Cost</Flex.Item>
349 <Flex.Item className="mono">{finalCost} ETH</Flex.Item>
350 </Grid.Item>
351 <Grid.Divider />
352 <Grid.Item className="pv4 eth-tx-button" full as={Flex} col>
353 <Flex.Item as={H5}>Receipt</Flex.Item>
355 {txHashes &&
356 txHashes.map((txHash, i) => (
357 <Flex.Item as={Flex} className="mv3">
358 <>
359 <Flex.Item
360 key={i}
361 flex
362 as="code"
363 className="f6 mono gray4 wrap ">
364 {txHash}
365 </Flex.Item>
366 <Flex.Item className="ml8" as={LinkButton} href={txUrls[i]}>
367 Etherscan↗
368 </Flex.Item>
369 </>
370 </Flex.Item>
371 ))}
372 </Grid.Item>
373 </>
374 );