on master help sign in
Redesign L1 transactions
commit715f8b079aa9a32611cd70631cb9b9a8a740716a authorWill Galebach <willbach@gmail.com> dateThu 30 Sep 2021, 19:23 (5 years ago) parentdd02fcd
9 files changed+193−195
src/components/InlineEthereumTransaction.js+27−160 src/components/InlineEthereumTransaction.scss+11−1 src/components/L2/Dropdowns/FeeDropdown.scss+32 src/components/L2/Dropdowns/FeeDropdown.tsx+62 src/lib/getSuggestedGasPrice.js+37−17 src/lib/reticket.js+3−1 src/lib/useGasPrice.js+14−9 src/lib/utils/roller.ts+4−3 src/views/Point/MigrateL2.tsx+3−4
src/components/InlineEthereumTransaction.js+27−160
@@ -1,34 +1,21 @@
11import React, { useMemo, useCallback, useState } from 'react';
22import cn from 'classnames';
3−import {
4− Grid,
5− ToggleInput,
6− ErrorText,
7− Flex,
8− LinkButton,
9− H5,
10− Text,
11−} from 'indigo-react';
3+import { Grid, ErrorText, Flex, LinkButton, H5, Text } from 'indigo-react';
124import { toBN } from 'web3-utils';
135
14−import { ReactComponent as InfoIcon } from 'assets/info.svg';
15−
166import { useExploreTxUrls } from 'lib/explorer';
17−import { hexify } from 'lib/txn';
187import { safeFromWei, safeToWei } from 'lib/lib';
198
209import { composeValidator, buildCheckboxValidator } from 'form/validators';
2110import BridgeForm from 'form/BridgeForm';
22−import Condition from 'form/Condition';
2311
2412import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
25−import CopyButton from './copiable/CopyButton';
2613import ProgressButton from './ProgressButton';
27−import { convertToInt } from 'lib/convertToInt';
2814import NeedFundsNotice from './NeedFundsNotice';
2915import NoticeBox from './NoticeBox';
3016
3117import './InlineEthereumTransaction.scss';
18+import FeeDropdown from './L2/Dropdowns/FeeDropdown';
3219
3320export default function InlineEthereumTransaction({
3421 // from useEthereumTransaction.bind
@@ -47,10 +34,7 @@ export default function InlineEthereumTransaction({
4734 setGasPrice,
4835 resetGasPrice,
4936 txHashes,
50− nonce,
51− chainId,
5237 needFunds,
53− signedTransactions,
5438 confirmationProgress,
5539 gasLimit,
5640 unsignedTransactions,
@@ -76,12 +60,6 @@ export default function InlineEthereumTransaction({
7660 // show signed tx only when signing (for offline usage)
7761 const showSignedTx = signed;
7862
79− const [showGasDetails, _setShowGasDetails] = useState(false);
80− const toggleGasDetails = useCallback(
81− () => _setShowGasDetails(!showGasDetails),
82− [_setShowGasDetails, showGasDetails]
83− );
84−
8563 const validate = useMemo(
8664 () =>
8765 composeValidator({
@@ -182,13 +160,6 @@ export default function InlineEthereumTransaction({
182160 }
183161 };
184162
185− const serializedTxsHex = useMemo(
186− () =>
187− signedTransactions &&
188− signedTransactions.map(stx => hexify(stx.serialize())),
189− [signedTransactions]
190− );
191−
192163 const numTxs = useMemo(() => (unsignedTransactions || []).length || 1, [
193164 unsignedTransactions,
194165 ]);
@@ -206,18 +177,36 @@ export default function InlineEthereumTransaction({
206177 [gasLimit, gasPrice, numTxs]
207178 );
208179
209− const gasInfo = useMemo(() => {
210− const extra = numTxs === 1 ? '' : `* ${numTxs} txs`;
211− return showGasDetails
212− ? `${gasPrice} Gwei * ${gasLimit} ${extra} = ${maxCost} ETH`
213− : `${maxCost} ETH`;
214− }, [showGasDetails, maxCost, gasPrice, gasLimit, numTxs]);
215−
216180 return (
217181 <Grid className={cn(className, 'mt1')}>
218182 <BridgeForm validate={validate} onValues={onValues}>
219183 {() => (
220184 <>
185+ {showConfigureInput && (
186+ <>
187+ <Grid.Item
188+ className="mt2 text eth-tx-button"
189+ full
190+ as={Flex}
191+ row
192+ justify="between">
193+ <Flex.Item as={H5}>Gas Fee</Flex.Item>
194+ <FeeDropdown setGasPrice={setGasPrice} />
195+ </Grid.Item>
196+
197+ <Grid.Item
198+ className="mt2 text eth-tx-button"
199+ full
200+ as={Flex}
201+ row
202+ justify="between">
203+ <Flex.Item as={H5}>Max Transaction Cost</Flex.Item>
204+ <Flex.Item as={H5}>{maxCost} ETH</Flex.Item>
205+ </Grid.Item>
206+ <Grid.Divider className="mt4" />
207+ </>
208+ )}
209+
221210 {renderPrimarySection()}
222211
223212 {error && (
@@ -235,66 +224,6 @@ export default function InlineEthereumTransaction({
235224 />
236225 )}
237226
238− {showConfigureInput && (
239− <>
240− <Grid.Item
241− className="eth-tx-button"
242− full
243− as={ToggleInput}
244− name="useAdvanced"
245− label="Advanced"
246− inverseLabel="Back to Defaults"
247− inverseColor="red3"
248− disabled={!showConfigureInput || initializing}
249− />
250−
251− <Condition when="useAdvanced" is={true}>
252− <Grid.Item className="mb2 eth-tx-button" full></Grid.Item>
253− <Grid.Divider />
254− <Grid.Item
255− className="mt2 eth-tx-button"
256− full
257− as={Flex}
258− row
259− justify="between">
260− <Flex.Item as={H5}>Transaction Fee</Flex.Item>
261−
262− <Flex.Item as={H5}>
263− {gasInfo}{' '}
264− <InfoIcon
265− className="pointer"
266− onClick={toggleGasDetails}
267− />
268− </Flex.Item>
269− </Grid.Item>
270−
271− {/* TODO(shrugs): move to indigo/RangeInput */}
272− <Grid.Item
273− className="eth-tx-button"
274− full
275− as="input"
276− type="range"
277− min="1"
278− max="400"
279− value={gasPrice}
280− onChange={e =>
281− setGasPrice(convertToInt(e.target.value, 10))
282− }
283− />
284− <Grid.Item
285− className="f6 mt1 eth-tx-button"
286− full
287− as={Flex}
288− row
289− justify="between">
290− <Flex.Item as={Text}>Cheap</Flex.Item>
291− <Flex.Item as={Text}>Fast</Flex.Item>
292− </Grid.Item>
293− <Grid.Divider className="mt4" />
294− </Condition>
295− </>
296− )}
297−
298227 {showSignedTx && fakeSigned && (
299228 <>
300229 <Grid.Item className="mt2 eth-tx-button" full as={NoticeBox}>
@@ -302,27 +231,6 @@ export default function InlineEthereumTransaction({
302231 </Grid.Item>
303232 </>
304233 )}
305−
306− {showSignedTx && !fakeSigned && (
307− <>
308− <Grid.Item
309− className="eth-tx-button"
310− full
311− as={ToggleInput}
312− name="viewSigned"
313− label="Signed Transaction"
314− inverseLabel="Hide"
315− disabled={!showSignedTx}
316− />
317− <Condition when="viewSigned" is={true}>
318− <SignedTransactionList
319− serializedTxsHex={serializedTxsHex}
320− maxCost={maxCost}
321− nonce={nonce}
322− />
323− </Condition>
324− </>
325− )}
326234 </>
327235 )}
328236 </BridgeForm>
@@ -330,47 +238,6 @@ export default function InlineEthereumTransaction({
330238 );
331239}
332240
333−function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
334− return serializedTxsHex.map((serializedTxHex, i) => (
335− <React.Fragment key={i}>
336− <Grid.Divider />
337− <Grid.Item
338− className="pv4 black f5 eth-tx-button"
339− full
340− as={Flex}
341− justify="between">
342− <Flex.Item>Nonce</Flex.Item>
343− <Flex.Item>{nonce + i}</Flex.Item>
344− </Grid.Item>
345− <Grid.Divider />
346− <Grid.Item
347− className="pv4 black f5 eth-tx-button"
348− full
349− as={Flex}
350− justify="between">
351− <Flex.Item>Transaction Cost</Flex.Item>
352− <Flex.Item>{maxCost} ETH</Flex.Item>
353− </Grid.Item>
354− <Grid.Divider />
355− <Grid.Item
356− className="mt3 mb2 eth-tx-button"
357− full
358− as={Flex}
359− justify="between">
360− <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
361− <Flex.Item as={CopyButton} text={serializedTxHex} />
362− </Grid.Item>
363− <Grid.Item
364− className="mb4 f6 mono gray4 wrap eth-tx-button"
365− full
366− as="code">
367− {serializedTxHex}
368− </Grid.Item>
369− <Grid.Divider />
370− </React.Fragment>
371− ));
372−}
373−
374241function TransactionReceipt({ txHashes, finalCost, onClose }) {
375242 const txUrls = useExploreTxUrls(txHashes);
376243 return (
src/components/InlineEthereumTransaction.scss+11−1
@@ -1,5 +1,15 @@
11@import 'style/shared/variables.scss';
22
3−.eth-tx-button {
3+span.eth-tx-button {
44 border-radius: $medium-border-radius;
5+ font-size: 14px;
6+ padding-top: 8px;
7+ padding-bottom: 8px;
8+ line-height: 16px;
9+ vertical-align: middle;
10+}
11+
12+.eth-tx-button.text > h5 {
13+ font-size: 14px;
14+ font-weight: 500;
515}
src/components/L2/Dropdowns/FeeDropdown.scssnew file+32
@@ -0,0 +1,32 @@
1+@import 'style/shared/variables.scss';
2+
3+.fee-dropdown {
4+ font-size: 14px;
5+ position: relative;
6+
7+ .selector {
8+ min-width: 150px;
9+ }
10+
11+ .content-border {
12+ z-index: 3;
13+
14+ .dropdown-content {
15+ .prices {
16+ .price {
17+ display: flex;
18+ flex-direction: row;
19+ justify-content: space-between;
20+ align-items: center;
21+ cursor: pointer;
22+
23+ padding: 8px 12px 8px 16px;
24+
25+ &:hover {
26+ opacity: 0.8;
27+ }
28+ }
29+ }
30+ }
31+ }
32+}
src/components/L2/Dropdowns/FeeDropdown.tsxnew file+62
@@ -0,0 +1,62 @@
1+import React, { useCallback, useEffect, useState } from 'react';
2+import { Box, Row } from '@tlon/indigo-react';
3+import { DEFAULT_GAS_PRICE_GWEI } from 'lib/constants';
4+import useGasPrice from 'lib/useGasPrice';
5+
6+import Dropdown from './Dropdown';
7+import './FeeDropdown.scss';
8+
9+const PRICE_LABELS = ['Fast', 'Average', 'Slow'];
10+
11+export interface GasPrice {
12+ price: number;
13+ wait: number;
14+}
15+
16+const formatPriceWait = ({ price, wait }: GasPrice) =>
17+ `${price} gwei (${Math.round(wait * 100) / 100} min)`;
18+
19+const FeeDropdown = ({ setGasPrice }: { setGasPrice: (g: number) => void }) => {
20+ const { suggestedGasPrices } = useGasPrice(DEFAULT_GAS_PRICE_GWEI);
21+
22+ const [open, setOpen] = useState<boolean>(false);
23+ const [selected, setSelected] = useState<GasPrice>(
24+ suggestedGasPrices.average
25+ );
26+
27+ useEffect(() => {
28+ setSelected(suggestedGasPrices.average);
29+ }, [suggestedGasPrices]);
30+
31+ const selectPrice = useCallback(
32+ (value: GasPrice) => () => {
33+ setSelected(value);
34+ setGasPrice(value.price);
35+ setOpen(false);
36+ },
37+ [setGasPrice, setSelected, setOpen]
38+ );
39+
40+ return (
41+ <Dropdown
42+ className="fee-dropdown"
43+ open={open}
44+ value={formatPriceWait(selected)}
45+ toggleOpen={() => setOpen(!open)}>
46+ <Box className="prices">
47+ {Object.values(suggestedGasPrices).map(
48+ (value: GasPrice, ind: number) => (
49+ <Row
50+ className="price"
51+ onClick={selectPrice(value)}
52+ key={value.wait}>
53+ {PRICE_LABELS[ind]}: {formatPriceWait(value)}
54+ </Row>
55+ )
56+ )}
57+ </Box>
58+ </Dropdown>
59+ );
60+};
61+
62+export default FeeDropdown;
src/lib/getSuggestedGasPrice.js+37−17
@@ -1,12 +1,32 @@
11import { NETWORK_TYPES } from './network';
22import { DEFAULT_GAS_PRICE_GWEI, MAX_GAS_PRICE_GWEI } from './constants';
33
4+// ethgasstation returns values in floating point, one order of magitude
5+// more than gwei. see: https://docs.ethgasstation.info
6+// we don't want to charge users more than the gas tank funds
7+const minGas = gas => Math.min(Math.ceil(gas / 10), MAX_GAS_PRICE_GWEI);
8+
9+export const defaultGasValues = value => ({
10+ fast: {
11+ price: value,
12+ wait: 1,
13+ },
14+ average: {
15+ price: value,
16+ wait: 1,
17+ },
18+ low: {
19+ price: value,
20+ wait: 1,
21+ },
22+});
23+
424export default async function getSuggestedGasPrice(networkType) {
525 switch (networkType) {
626 case NETWORK_TYPES.ROPSTEN:
7− return 10;
27+ return defaultGasValues(10);
828 case NETWORK_TYPES.OFFLINE:
9− return DEFAULT_GAS_PRICE_GWEI;
29+ return defaultGasValues(DEFAULT_GAS_PRICE_GWEI);
1030 case NETWORK_TYPES.LOCAL:
1131 default:
1232 try {
@@ -23,22 +43,22 @@ export default async function getSuggestedGasPrice(networkType) {
2343
2444 const json = await response.json();
2545
26− if (json && json.status !== '1') {
27− console.warn('suggested gas price warning', json.message);
28− }
29−
30− const suggestedGasPrice = parseInt(
31− json && json.result && json.result.ProposeGasPrice
32− );
33− if (isNaN(suggestedGasPrice)) {
34− throw new Error('strange gas price response', json);
35− }
36−
37− // safeguard against obscene suggestions
38− return Math.min(suggestedGasPrice, MAX_GAS_PRICE_GWEI);
46+ return {
47+ fast: {
48+ price: minGas(json.fast),
49+ wait: json.fastWait,
50+ },
51+ average: {
52+ price: minGas(json.average),
53+ wait: json.avgWait,
54+ },
55+ low: {
56+ price: minGas(json.safeLow),
57+ wait: json.safeLowWait,
58+ },
59+ };
3960 } catch (e) {
40− console.warn('error fetching gas price', e);
41− return DEFAULT_GAS_PRICE_GWEI;
61+ return defaultGasValues(DEFAULT_GAS_PRICE_GWEI);
4262 }
4363 }
4464}
src/lib/reticket.js+3−1
@@ -149,7 +149,9 @@ export async function reticketPointBetweenWallets({
149149
150150 progress(TRANSACTION_PROGRESS.SIGNING);
151151
152− const gasPriceGwei = (await getSuggestedGasPrice(networkType)).toFixed();
152+ const gasPriceGwei = (
153+ await getSuggestedGasPrice(networkType)
154+ ).average.price.toFixed();
153155 const chainId = await web3.eth.net.getId();
154156 const gasPriceWeiBN = toBN(safeToWei(gasPriceGwei, 'gwei'));
155157 let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
src/lib/useGasPrice.js+14−9
@@ -2,7 +2,9 @@ import { useCallback, useEffect, useState } from 'react';
22
33import { useNetwork } from 'store/network';
44
5−import getSuggestedGasPrice from 'lib/getSuggestedGasPrice';
5+import getSuggestedGasPrices, {
6+ defaultGasValues,
7+} from 'lib/getSuggestedGasPrice';
68import { DEFAULT_GAS_PRICE_GWEI } from './constants';
79
810/**
@@ -14,33 +16,36 @@ import { DEFAULT_GAS_PRICE_GWEI } from './constants';
1416 */
1517export default function useGasPrice(initialGasPrice = DEFAULT_GAS_PRICE_GWEI) {
1618 const { networkType } = useNetwork();
17− const [suggestedGasPrice, setSuggestedGasPrice] = useState(initialGasPrice); // gwei
19+ const [suggestedGasPrices, setSuggestedGasPrices] = useState(
20+ defaultGasValues(initialGasPrice)
21+ ); // fast, average, low. In gwei
1822 const [gasPrice, setGasPrice] = useState(initialGasPrice); // gwei
1923
2024 useEffect(() => {
2125 let mounted = true;
2226
2327 (async () => {
24− const suggestedGasPrice = await getSuggestedGasPrice(networkType);
28+ const suggestedGasPrices = await getSuggestedGasPrices(networkType);
2529
2630 if (!mounted) {
2731 return;
2832 }
2933
3034 // TODO: BN
31− setSuggestedGasPrice(suggestedGasPrice);
32− setGasPrice(suggestedGasPrice);
35+ setSuggestedGasPrices(suggestedGasPrices);
36+ setGasPrice(suggestedGasPrices.average.price);
3337 })();
3438
3539 return () => (mounted = true);
3640 }, [networkType]);
3741
38− const resetGasPrice = useCallback(() => setGasPrice(suggestedGasPrice), [
39− suggestedGasPrice,
40− ]);
42+ const resetGasPrice = useCallback(
43+ () => setGasPrice(suggestedGasPrices.average.price),
44+ [suggestedGasPrices]
45+ );
4146
4247 return {
43− suggestedGasPrice,
48+ suggestedGasPrices,
4449 gasPrice,
4550 setGasPrice,
4651 resetGasPrice,
src/lib/utils/roller.ts+4−3
@@ -1,6 +1,6 @@
1−import { signTransactionHash } from 'lib/authToken';
2−import { Invite } from 'lib/types/Invite';
31import { Just, Nothing } from 'folktale/maybe';
2+import { Invite } from 'lib/types/Invite';
3+import { signTransactionHash } from 'lib/authToken';
44import { randomHex } from 'web3-utils';
55
66import RollerRPCAPI, {
@@ -113,13 +113,14 @@ export const transferPointRequest = async (
113113 api: RollerRPCAPI,
114114 _wallet: any,
115115 _point: Ship,
116+ planet: number,
116117 proxy: string,
117118 nonce: number,
118119 address: string,
119120 reset?: boolean
120121) => {
121122 const from = {
122− ship: _point, // ship to transfer
123+ ship: planet, //planet to transfer
123124 proxy,
124125 };
125126
src/views/Point/MigrateL2.tsx+3−4
@@ -201,10 +201,9 @@ export default function MigrateL2() {
201201 {star && (
202202 <Box>
203203 Transferring this point will allow you to conduct all
204− transactions on Layer 2 and is irreversible. Setting the spawn
205− proxy of a star to the Layer 2 contract is reversible, will
206− allow you to create planet invites for free, but will require
207− you to do all other transactions on Layer 1.
204+ transactions on Layer 2. Setting the spawn proxy of a star to
205+ Layer 2 will allow you to create planet invites for free, but
206+ will require you to do all other transactions on Layer 1.
208207 </Box>
209208 )}
210209 </Box>