on 75342bd help sign in
bridge/src/components/InlineEthereumTransaction.js
311 lines 8.6 kB history raw
1import React, { useMemo, useCallback } from 'react';
2import cn from 'classnames';
3import {
4 Grid,
5 ToggleInput,
6 ErrorText,
7 Flex,
8 LinkButton,
9 H5,
10 Text,
11} from 'indigo-react';
13import { useExploreTxUrl, useExploreTxUrls } from 'lib/explorer';
14import { hexify } from 'lib/txn';
15import pluralize from 'lib/pluralize';
17import { composeValidator, buildCheckboxValidator } from 'form/validators';
18import BridgeForm from 'form/BridgeForm';
19import Condition from 'form/Condition';
21import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
22import CopyButton from './CopyButton';
23import ProgressButton from './ProgressButton';
24import convertToInt from 'lib/convertToInt';
25import NeedFundsNotice from './NeedFundsNotice';
27export default function InlineEthereumTransaction({
28 // from useEthereumTransaction.bind
29 initializing,
30 canSign,
31 generateAndSign,
32 signed,
33 broadcast,
34 broadcasted,
35 confirmed,
36 completed,
37 reset,
38 error,
39 gasPrice,
40 setGasPrice,
41 resetGasPrice,
42 txHashes,
43 nonce,
44 chainId,
45 needFunds,
46 signedTransactions,
47 confirmationProgress,
49 // additional from parent
50 label = 'Generate & Sign Transaction',
51 className,
52 onReturn,
53}) {
54 // show receipt after successful broadcast
55 const showReceipt = broadcasted || confirmed || completed;
56 // show configure controls pre-broadcast
57 const showConfigureInput = !(signed || broadcasted || confirmed || completed);
58 // show the send/loading button while signed, broadcasting, or confirme
59 const showBroadcastButton = signed;
60 const showLoadingButton = broadcasted || confirmed;
61 const canBroadcast = signed && !needFunds;
62 // show signed tx only when signing (for offline usage)
63 const showSignedTx = signed;
65 const validate = useMemo(
66 () =>
67 composeValidator({
68 useAdvanced: buildCheckboxValidator(),
69 viewSigned: buildCheckboxValidator(),
70 }),
71 []
72 );
74 const onValues = useCallback(
75 ({ valid, values, form }) => {
76 if (!values.useAdvanced) {
77 resetGasPrice();
78 }
79 },
80 [resetGasPrice]
81 );
83 const renderPrimarySection = () => {
84 if (error) {
85 return (
86 <Grid.Item full as={RestartButton} solid onClick={() => reset()}>
87 Reset Transaction
88 </Grid.Item>
89 );
90 } else if (completed) {
91 return (
92 <Grid.Item full className="pv4 black f5">
93 Transaction Complete
94 </Grid.Item>
95 );
96 } else if (showBroadcastButton) {
97 return (
98 <Grid.Item
99 full
100 as={ForwardButton}
101 solid
102 success
103 disabled={!canBroadcast}
104 onClick={() => broadcast()}>
105 Send Transaction
106 </Grid.Item>
107 );
108 } else if (showLoadingButton) {
109 return (
110 <Grid.Item
111 full
112 as={ProgressButton}
113 success
114 disabled
115 progress={confirmationProgress}>
116 Sending Transaction
117 </Grid.Item>
118 );
119 } else {
120 return (
121 <Grid.Item
122 full
123 as={GenerateButton}
124 onClick={generateAndSign}
125 disabled={!canSign}
126 loading={!canSign && initializing}>
127 {label}
128 </Grid.Item>
129 );
130 }
131 };
133 const serializedTxsHex = useMemo(
134 () =>
135 signedTransactions &&
136 signedTransactions.map(stx => hexify(stx.serialize())),
137 [signedTransactions]
138 );
140 return (
141 <Grid className={cn(className, 'mt1')}>
142 <BridgeForm validate={validate} onValues={onValues}>
143 {() => (
144 <>
145 {renderPrimarySection()}
147 {error && (
148 <Grid.Item full as={ErrorText} className="mv1">
149 {error.message}
150 </Grid.Item>
151 )}
153 {needFunds && (
154 <Grid.Item
155 full
156 as={NeedFundsNotice}
157 className="mt3"
158 {...needFunds}
159 />
160 )}
162 {showConfigureInput && (
163 <>
164 <Grid.Item
165 full
166 as={ToggleInput}
167 name="useAdvanced"
168 label="Advanced"
169 inverseLabel="Back to Defaults"
170 inverseColor="red3"
171 disabled={!showConfigureInput || initializing}
172 />
174 <Condition when="useAdvanced" is={true}>
175 <Grid.Divider />
176 <Grid.Item
177 full
178 as={Flex}
179 row
180 justify="between"
181 className="mt2">
182 <Flex.Item as={H5}>Gas Price</Flex.Item>
183 <Flex.Item as={H5}>{gasPrice} Gwei</Flex.Item>
184 </Grid.Item>
185 {/* TODO(shrugs): move to indigo/RangeInput */}
186 <Grid.Item
187 full
188 as="input"
189 type="range"
190 min="1"
191 max="100"
192 value={gasPrice}
193 onChange={e =>
194 setGasPrice(convertToInt(e.target.value, 10))
195 }
196 />
197 <Grid.Item
198 full
199 as={Flex}
200 row
201 justify="between"
202 className="f6 mt1">
203 <Flex.Item as={Text}>Cheap</Flex.Item>
204 <Flex.Item as={Text}>Fast</Flex.Item>
205 </Grid.Item>
206 <Grid.Divider className="mt4" />
207 </Condition>
208 </>
209 )}
211 {showSignedTx && (
212 <>
213 <Grid.Item
214 full
215 as={ToggleInput}
216 name="viewSigned"
217 label="Signed Transaction"
218 inverseLabel="Hide"
219 disabled={!showSignedTx}
220 />
221 <Condition when="viewSigned" is={true}>
222 <SignedTransactionList
223 serializedTxsHex={serializedTxsHex}
224 gasPrice={gasPrice}
225 nonce={nonce}
226 />
227 </Condition>
228 </>
229 )}
231 {showReceipt && (
232 <>
233 <Grid.Divider />
234 <HashReceiptList txHashes={txHashes} />
236 <Grid.Divider />
237 </>
238 )}
240 {completed && (
241 <>
242 <Grid.Item full as={RestartButton} onClick={onReturn}>
243 Return
244 </Grid.Item>
245 <Grid.Divider />
246 </>
247 )}
248 </>
249 )}
250 </BridgeForm>
251 </Grid>
252 );
255function SignedTransactionList({ serializedTxsHex, nonce, gasPrice }) {
256 return serializedTxsHex.map((serializedTxHex, i) => (
257 <React.Fragment key="i">
258 <Grid.Divider />
259 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
260 <Flex.Item>Nonce</Flex.Item>
261 <Flex.Item>{nonce + i}</Flex.Item>
262 </Grid.Item>
263 <Grid.Divider />
264 <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
265 <Flex.Item>Gas Price</Flex.Item>
266 <Flex.Item>{gasPrice.toFixed()} Gwei</Flex.Item>
267 </Grid.Item>
268 <Grid.Divider />
269 <Grid.Item full as={Flex} justify="between" className="mt3 mb2">
270 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
271 <Flex.Item as={CopyButton} text={serializedTxHex} />
272 </Grid.Item>
273 <Grid.Item full as="code" className="mb4 f6 mono gray4 wrap">
274 {serializedTxHex}
275 </Grid.Item>
276 <Grid.Divider />
277 </React.Fragment>
278 ));
281function HashReceiptList({ txHashes }) {
282 const txUrls = useExploreTxUrls(txHashes);
283 const header = pluralize(txHashes.length, 'Hash', 'Hashes');
284 return (
285 <>
286 <Grid.Divider />
287 <Grid.Item full as={Flex} col className="pv4">
288 <Flex.Item as={H5}>Transaction {header}</Flex.Item>
290 {txHashes &&
291 txHashes.map((txHash, i) => (
292 <Flex.Item as={Flex}>
293 <>
294 <Flex.Item
295 key={i}
296 flex
297 as="code"
298 className="f6 mono gray4 wrap">
299 {txHash}
300 </Flex.Item>
301 <Flex.Item as={LinkButton} href={txUrls[i]}>
302 Etherscan↗
303 </Flex.Item>
304 </>
305 </Flex.Item>
306 ))}
307 </Grid.Item>
308 <Grid.Divider />
309 </>
310 );