From 5497c79c88fe4667276a0f03300fa040256bbc45 Mon Sep 17 00:00:00 2001 From: emmaguo13 Date: Sat, 1 May 2021 15:15:56 -0700 Subject: [PATCH 01/14] confirm modal wip --- src/components/SwapForm/index.js | 97 +++++++++++++++++++++----------- src/components/SwapModal.js | 94 +++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 34 deletions(-) create mode 100644 src/components/SwapModal.js diff --git a/src/components/SwapForm/index.js b/src/components/SwapForm/index.js index e4b5c58..2e96b7e 100644 --- a/src/components/SwapForm/index.js +++ b/src/components/SwapForm/index.js @@ -1,4 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ +/* eslint-disable */ import { Box, Button, @@ -10,6 +11,7 @@ import { Text, useToast, Spinner, + useDisclosure, } from '@chakra-ui/react'; import debounce from 'debounce'; import { Controller, useForm } from 'react-hook-form'; @@ -17,6 +19,7 @@ import React, { useEffect, useState } from 'react'; import Select, { components } from 'react-select'; import FullPageSpinner from '../FullPageSpinner'; +import SwapModal from '../SwapModal'; import zeroXSwap from '../../hooks/use0xSwap'; import use0xPrice from '../../hooks/use0xPrice'; @@ -32,6 +35,8 @@ export default function SwapForm({ onboardState, web3, onboard }) { const [isLoading, setIsLoading] = useState(); const [sellAmount, setSellAmount] = useState(); + const { isOpen, onOpen, onClose } = useDisclosure(); + const [swapConfirmed, setSwapConfirmed] = useState(false); const toast = useToast(); const watchTokenIn = watch('tokenIn', ''); @@ -77,22 +82,26 @@ export default function SwapForm({ onboardState, web3, onboard }) { // Execute the swap const onSubmit = async (data) => { - const ready = await readyToTransact(); - if (!ready) return; - - const { amountIn, tokenIn, tokenOut } = data; - setIsLoading(true); - - zeroXSwap(Tokens.data[tokenIn.value], Tokens.data[tokenOut.value], amountIn, web3) - .then(() => { - setIsLoading(false); - toast(Toasts.success); - }) - .catch((err) => { - setIsLoading(false); - console.log(err); - toast(Toasts.error); - }); + onOpen() + const closed = await isOpen; + console.log(closed) + const ready = await readyToTransact(); + if (!ready) return; + + const { amountIn, tokenIn, tokenOut } = data; + setIsLoading(true); + + zeroXSwap(Tokens.data[tokenIn.value], Tokens.data[tokenOut.value], amountIn, web3) + .then(() => { + setIsLoading(false); + toast(Toasts.success); + }) + .catch((err) => { + setIsLoading(false); + console.log(err); + toast(Toasts.error); + }); + }; if (!onboard) { @@ -302,24 +311,44 @@ export default function SwapForm({ onboardState, web3, onboard }) { ) : null}
{onboardState.address ? ( - + <> + + + ) : ( + + + + + ); + } + \ No newline at end of file From 62c5ece1c33f7ea97631546ec360ec3e16a027fc Mon Sep 17 00:00:00 2001 From: Sehyun Chung Date: Sat, 1 May 2021 18:10:39 -0400 Subject: [PATCH 02/14] Add toasts for errors in Swap Form (#105) * add error check for api error * remove api error * adjust spacing in navbar * remove reset wallet state --- src/components/NavBar.js | 17 +++-- src/components/SwapForm/index.js | 121 +++++++++++++++++-------------- src/constants/toasts.js | 20 ++++- src/hooks/use0xPrice.js | 8 +- 4 files changed, 100 insertions(+), 66 deletions(-) diff --git a/src/components/NavBar.js b/src/components/NavBar.js index 81d16ed..e0f5451 100644 --- a/src/components/NavBar.js +++ b/src/components/NavBar.js @@ -6,19 +6,18 @@ import { Flex, Icon, IconButton, + Image, Link, Popover, PopoverContent, PopoverTrigger, + Spinner, Stack, Text, useColorModeValue, useDisclosure, - Spinner, - Image, } from '@chakra-ui/react'; import React from 'react'; - import AccountModal from './AccountModal'; const NAV_ITEMS = [ @@ -83,8 +82,14 @@ export default function Navbar({ address, balance, onboard, web3 }) { {address && onboard && ( - - + + <> {typeof balance === 'string' ? ( `${parseFloat(web3.utils.fromWei(balance, 'ether')).toPrecision(6)}` @@ -95,7 +100,7 @@ export default function Navbar({ address, balance, onboard, web3 }) { ETH - + )} diff --git a/src/components/SwapForm/index.js b/src/components/SwapForm/index.js index 2e96b7e..f265f95 100644 --- a/src/components/SwapForm/index.js +++ b/src/components/SwapForm/index.js @@ -54,7 +54,9 @@ export default function SwapForm({ onboardState, web3, onboard }) { const { data: zeroExQuote } = use0xPrice( Tokens.data[watchTokenIn.value], Tokens.data[watchTokenOut.value], - sellAmount + sellAmount, + // eslint-disable-next-line no-use-before-define + (error) => handleError(error) ); const { price, gasPrice, estimatedGas, exchanges } = @@ -80,28 +82,36 @@ export default function SwapForm({ onboardState, web3, onboard }) { return ready; } + const handleError = (error) => { + console.log('🚀 ~ file: SwapForm.js ~ line 92 ~ handleError ~ error', error); + if (error?.code === 4001) { + toast(Toasts.transactionReject); + } else { + toast(Toasts.error); + } + }; + // Execute the swap const onSubmit = async (data) => { - onOpen() + onOpen(); const closed = await isOpen; - console.log(closed) - const ready = await readyToTransact(); - if (!ready) return; - - const { amountIn, tokenIn, tokenOut } = data; - setIsLoading(true); - - zeroXSwap(Tokens.data[tokenIn.value], Tokens.data[tokenOut.value], amountIn, web3) - .then(() => { - setIsLoading(false); - toast(Toasts.success); - }) - .catch((err) => { - setIsLoading(false); - console.log(err); - toast(Toasts.error); - }); - + console.log(closed); + const ready = await readyToTransact(); + if (!ready) return; + + const { amountIn, tokenIn, tokenOut } = data; + setIsLoading(true); + + zeroXSwap(Tokens.data[tokenIn.value], Tokens.data[tokenOut.value], amountIn, web3) + .then(() => { + setIsLoading(false); + toast(Toasts.success); + }) + .catch((err) => { + setIsLoading(false); + console.log(err); + toast(Toasts.error); + }); }; if (!onboard) { @@ -312,42 +322,41 @@ export default function SwapForm({ onboardState, web3, onboard }) {
{onboardState.address ? ( <> - - + + ) : ( - - + ) : ( @@ -36,10 +36,10 @@ export default function AccountModal({ address, onboard }) { - Connected Wallet: {`${walletState.wallet.name}`} + Connected Wallet: {walletState.wallet.name} - {`${address?.substr(0, 8)}...${address?.substr(address.length - 6)}`} + {address?.substr(0, 8)}...{address?.substr(address.length - 6)} )}
- {/* {errors.amountIn ? 'Input amount is required' : null} */} ); From e8ae1c8633abe5cb5ecef251c6e91fe8702c9eda Mon Sep 17 00:00:00 2001 From: Joe Mo <49494600+yuzhoumo@users.noreply.github.com> Date: Tue, 18 May 2021 00:59:19 -0700 Subject: [PATCH 07/14] Add swap button messages and more error checks (#123) * Move token list back to main swap form file (introduced a bug that allowed same input/output token) * Update full page spinner text and alignment * Add swap button component and handle errors with in-button messages * Add error check for inputs that are too large (causes zero division in Big Decimal library) * Add default error message --- src/components/FullPageSpinner/index.js | 4 +- src/components/SwapForm/SwapButton.js | 33 +++++ src/components/SwapForm/SwapInfo.js | 2 +- src/components/SwapForm/TokenDropdown.js | 9 -- src/components/SwapForm/index.js | 152 ++++++++++------------- src/hooks/use0xPrice.js | 68 ++++++---- 6 files changed, 147 insertions(+), 121 deletions(-) create mode 100644 src/components/SwapForm/SwapButton.js diff --git a/src/components/FullPageSpinner/index.js b/src/components/FullPageSpinner/index.js index a9cd6cf..29cfc45 100644 --- a/src/components/FullPageSpinner/index.js +++ b/src/components/FullPageSpinner/index.js @@ -3,10 +3,10 @@ import { Spinner, Center, Text, VStack } from '@chakra-ui/react'; export default function FullPageSpinner() { return ( -
+
- Connecting to Metamask + Loading Application
); diff --git a/src/components/SwapForm/SwapButton.js b/src/components/SwapForm/SwapButton.js new file mode 100644 index 0000000..19dc5c7 --- /dev/null +++ b/src/components/SwapForm/SwapButton.js @@ -0,0 +1,33 @@ +import { Button } from '@chakra-ui/react'; +import React from 'react'; + +export default function SwapButton({ + disabled, + isLoading, + loadingText, + buttonText, + onClick, + type, +}) { + return ( + + ); +} diff --git a/src/components/SwapForm/SwapInfo.js b/src/components/SwapForm/SwapInfo.js index 501a1b9..c480e3d 100644 --- a/src/components/SwapForm/SwapInfo.js +++ b/src/components/SwapForm/SwapInfo.js @@ -18,7 +18,7 @@ export default function SwapInfo({ return Exchanges.data[exchanges[0]?.name]?.name; } if (exchanges.length === 0) { - return 'Weth <> Eth'; + return 'ETH <> WETH'; } return 'Split Routing'; }; diff --git a/src/components/SwapForm/TokenDropdown.js b/src/components/SwapForm/TokenDropdown.js index 1e327f8..853084c 100644 --- a/src/components/SwapForm/TokenDropdown.js +++ b/src/components/SwapForm/TokenDropdown.js @@ -2,8 +2,6 @@ import React from 'react'; import { HStack, Text } from '@chakra-ui/react'; import { components } from 'react-select'; -import Tokens from '../../constants/tokens'; -import { getTokenIconPNG32 } from '../../utils/getTokenIcon'; const { Option, SingleValue } = components; @@ -31,13 +29,6 @@ export const ValueOption = (props) => { ); }; -// display tokens -export const TokenArray = Tokens.tokens.map((symbol) => ({ - value: symbol, - label: symbol, - icon: getTokenIconPNG32(symbol), -})); - export const DropdownStyle = { menu: (provided) => ({ ...provided, diff --git a/src/components/SwapForm/index.js b/src/components/SwapForm/index.js index b85055f..1a67fa7 100644 --- a/src/components/SwapForm/index.js +++ b/src/components/SwapForm/index.js @@ -1,35 +1,21 @@ -import { - Box, - Button, - Center, - Flex, - Heading, - Input, - Text, - useToast, - Spinner, - useDisclosure, -} from '@chakra-ui/react'; -import debounce from 'debounce'; +import { Box, Center, Flex, Heading, Input, Text, useToast, Spinner } from '@chakra-ui/react'; +import { WarningIcon } from '@chakra-ui/icons'; import { Controller, useForm } from 'react-hook-form'; import React, { useEffect, useState } from 'react'; - +import debounce from 'debounce'; import Select from 'react-select'; +import { IconOption, ValueOption, DropdownStyle } from './TokenDropdown'; +import { getTokenIconPNG32 } from '../../utils/getTokenIcon'; import FullPageSpinner from '../FullPageSpinner'; -import SwapModal from '../SwapModal'; - +import SwapInfo from './SwapInfo'; +import SwapButton from './SwapButton'; +import Toasts from '../../constants/toasts'; +import Tokens from '../../constants/tokens'; import use0xSwap from '../../hooks/use0xSwap'; import use0xPrice from '../../hooks/use0xPrice'; -import Tokens from '../../constants/tokens'; -import Toasts from '../../constants/toasts'; - -import SwapInfo from './SwapInfo'; -import { IconOption, ValueOption, TokenArray, DropdownStyle } from './TokenDropdown'; - export default function SwapForm({ onboardState, web3, onboard }) { const { register, handleSubmit, watch, setValue, errors, control } = useForm(); - const [isLoading, setIsLoading] = useState(); const [sellAmount, setSellAmount] = useState(); const { isOpen, onOpen, onClose } = useDisclosure(); @@ -40,6 +26,21 @@ export default function SwapForm({ onboardState, web3, onboard }) { const watchTokenOut = watch('tokenOut', ''); const watchAmountIn = watch('amountIn', 0); + // Token dropdown values + const tokenArray = Tokens.tokens.map((symbol) => ({ + value: symbol, + label: symbol, + icon: getTokenIconPNG32(symbol), + })); + + // 0x API quote + const { data: zeroExQuote } = use0xPrice( + Tokens.data[watchTokenIn.value], + Tokens.data[watchTokenOut.value], + sellAmount + ); + + // Placeholder values const defaults = { price: , gasPrice: , @@ -48,25 +49,11 @@ export default function SwapForm({ onboardState, web3, onboard }) { sources: [], }; - const { data: zeroExQuote } = use0xPrice( - Tokens.data[watchTokenIn.value], - Tokens.data[watchTokenOut.value], - sellAmount - ); - - const { price, gasPrice, estimatedGas, exchanges } = + // Set quote values + const { price, gasPrice, estimatedGas, exchanges, apiError } = zeroExQuote === undefined ? defaults : zeroExQuote; - useEffect(() => { - if (watchAmountIn > 0 && watchTokenIn && watchTokenOut && price !== defaults.price) { - const n = watchAmountIn * price; - setValue('amountOut', n.toFixed(6).replace(/(0+)$/, '').replace(/\.$/, '')); - } - if (!watchAmountIn || watchAmountIn <= 0) { - setValue('amountOut', ''); - } - }, [price, watchAmountIn, watchTokenIn, watchTokenOut]); - + // Connect wallet function async function readyToTransact() { if (!onboardState.address) { const walletSelected = await onboard.walletSelect(); @@ -77,6 +64,35 @@ export default function SwapForm({ onboardState, web3, onboard }) { return ready; } + // SwapButton text function + const getButtonText = () => { + if (!watchAmountIn || watchAmountIn <= 0) return 'Enter amount in'; + if (!watchTokenIn || !watchTokenOut) return 'Select tokens'; + if (apiError) { + return ( + <> + + {apiError} + + ); + } + return 'Swap Tokens'; + }; + + // Set amount out + useEffect(() => { + if (watchAmountIn > 0 && watchTokenIn && watchTokenOut && price !== defaults.price) { + const n = watchAmountIn * price; + setValue('amountOut', n.toFixed(6).replace(/(0+)$/, '').replace(/\.$/, '')); + } + if (!watchAmountIn || watchAmountIn <= 0 || apiError) { + setValue('amountOut', ''); + } + }, [price, watchAmountIn, watchTokenIn, watchTokenOut]); + + // Loading screen + if (!onboard) return ; + // Execute the swap const onSubmit = async (data) => { onOpen(); @@ -99,10 +115,6 @@ export default function SwapForm({ onboardState, web3, onboard }) { }); }; - if (!onboard) { - return ; - } - return ( @@ -120,7 +132,7 @@ export default function SwapForm({ onboardState, web3, onboard }) { render={({ onChange, name, value, ref }) => ( { e.currentTarget.blur(); }} - onChange={debounce((event) => setSellAmount(event.target.value), 1500)} + onChange={debounce((event) => setSellAmount(event.target.value), 1000)} /> @@ -176,7 +188,7 @@ export default function SwapForm({ onboardState, web3, onboard }) { render={({ onChange, name, value, ref }) => (