diff --git a/src/assets/svg/arrow-down-icon.svg b/src/assets/svg/arrow-down-icon.svg new file mode 100644 index 00000000..fbfdf7b6 --- /dev/null +++ b/src/assets/svg/arrow-down-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/cog-icon.svg b/src/assets/svg/cog-icon.svg new file mode 100644 index 00000000..ae023348 --- /dev/null +++ b/src/assets/svg/cog-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/switch-swap-icon.svg b/src/assets/svg/switch-swap-icon.svg new file mode 100644 index 00000000..78bdbad6 --- /dev/null +++ b/src/assets/svg/switch-swap-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/warning-icon-purple.svg b/src/assets/svg/warning-icon-purple.svg new file mode 100644 index 00000000..e9f8882b --- /dev/null +++ b/src/assets/svg/warning-icon-purple.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Tailwind/InputFields/CurrencyInput.tsx b/src/components/Tailwind/InputFields/CurrencyInput.tsx index 6b07911e..f6afd2ba 100644 --- a/src/components/Tailwind/InputFields/CurrencyInput.tsx +++ b/src/components/Tailwind/InputFields/CurrencyInput.tsx @@ -5,6 +5,7 @@ import MaxButton from '../Buttons/MaxButton' import SelectButton from '../Buttons/SelectButton' import NumericalInput from 'components/NumericalInput' import TokenSelectModal from 'components/Tailwind/Modals/TokenSelectModal' +import { formatNumber, NumberFormat } from 'utils/formatNumber' import { useCurrencyBalance } from 'state/wallet/hooks' import { useActiveWeb3React } from 'hooks' @@ -17,6 +18,7 @@ interface TokenInputProps { showMax: boolean onSelectToken?: (token: Token) => void tokenList?: Token[] + balance?: string } const TokenInput = ({ @@ -27,15 +29,16 @@ const TokenInput = ({ showBalance, showMax, onSelectToken, - tokenList + tokenList, + balance }: TokenInputProps) => { - const { account } = useActiveWeb3React() - const balance = useCurrencyBalance(account ?? undefined, currency) const [showModal, setShowModal] = useState(false) + const { account } = useActiveWeb3React() + const currencyBalance = useCurrencyBalance(account ?? undefined, currency) const onMax = () => { if (balance) { - didChangeValue(balance.toExact()) + didChangeValue(balance) } } @@ -66,7 +69,10 @@ const TokenInput = ({
{showBalance && (
- Balance: {balance ? balance.toSignificant(6) : '-'} + Balance:{' '} + {balance + ? formatNumber(Number(balance), NumberFormat.long) || '-' + : currencyBalance?.toSignificant(6) || '-'}
)} return (
- Slippage tollerance: + Slippage tolerance:
diff --git a/src/constants/buttonStates.ts b/src/constants/buttonStates.ts new file mode 100644 index 00000000..4ee7f50e --- /dev/null +++ b/src/constants/buttonStates.ts @@ -0,0 +1,34 @@ +export enum ModalState { + NotConfirmed, + InProgress, + Successful +} + +export enum ButtonState { + Default, + EnterAmount, + Approving, + Approved, + Next, + Confirming, + InsufficientBalance, + Retry, + MaxCap, + Swap, + InsufficientLiquidity, + NotMinimum +} + +export enum SwapButtonState { + Default, + EnterAmount, + Approving, + Approved, + Next, + Confirming, + Retry, + MaxCap, + Swap, + InsufficientLiquidity, + InsufficientBalance +} diff --git a/src/constants/haloAbis/CurveFactory.json b/src/constants/haloAbis/CurveFactory.json new file mode 100644 index 00000000..a03e652a --- /dev/null +++ b/src/constants/haloAbis/CurveFactory.json @@ -0,0 +1,175 @@ +[ + { + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "curve", + "type": "address" + } + ], + "name": "NewCurve", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "curves", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_baseCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "_quoteCurrency", + "type": "address" + } + ], + "name": "getCurve", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "_baseCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "_quoteCurrency", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_baseWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_quoteWeight", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_baseAssimilator", + "type": "address" + }, + { + "internalType": "address", + "name": "_quoteAssimilator", + "type": "address" + } + ], + "name": "newCurve", + "outputs": [ + { + "internalType": "contract Curve", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/constants/haloAbis/Router.json b/src/constants/haloAbis/Router.json new file mode 100644 index 00000000..56c79d8e --- /dev/null +++ b/src/constants/haloAbis/Router.json @@ -0,0 +1,138 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_quoteCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "_origin", + "type": "address" + }, + { + "internalType": "address", + "name": "_target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_originAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minTargetAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_deadline", + "type": "uint256" + } + ], + "name": "originSwap", + "outputs": [ + { + "internalType": "uint256", + "name": "targetAmount_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_quoteCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "_origin", + "type": "address" + }, + { + "internalType": "address", + "name": "_target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_originAmount", + "type": "uint256" + } + ], + "name": "viewOriginSwap", + "outputs": [ + { + "internalType": "uint256", + "name": "targetAmount_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_quoteCurrency", + "type": "address" + }, + { + "internalType": "address", + "name": "_origin", + "type": "address" + }, + { + "internalType": "address", + "name": "_target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_targetAmount", + "type": "uint256" + } + ], + "name": "viewTargetSwap", + "outputs": [ + { + "internalType": "uint256", + "name": "originAmount_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/constants/tokenLists/halo-tokenlist.ts b/src/constants/tokenLists/halo-tokenlist.ts new file mode 100644 index 00000000..583e0ab7 --- /dev/null +++ b/src/constants/tokenLists/halo-tokenlist.ts @@ -0,0 +1,112 @@ +import { ChainId, Token } from '@sushiswap/sdk' +import { ethers } from 'ethers' +import { ChainAddressMap, USDC, ZERO_ADDRESS } from '../../constants' + +// Supported token symbols +export enum TokenSymbol { + USDC = 'USDC', + EURS = 'EURS', + GBP = 'GBP', + CHF = 'CHF', + TUSD = 'TUSD', + TCAD = 'TCAD', + TGBP = 'TGBP', + TAUD = 'TAUD', + XSGD = 'XSGD' +} + +export type AssimilatorAddressMap = { + [token in TokenSymbol]: string +} +// Router addresses +export const routerAddress: ChainAddressMap = { + [ChainId.MAINNET]: '0x585B52fE4712a74404abA83dEB09A0E087D80802', + [ChainId.KOVAN]: '0xa02dCeB15cc32249beC33C2808b4799a44F8B0D5', + [ChainId.MATIC]: '0x26f2860cdeB7cC785eE5d59a5Efb2D0D3842C39D' +} + +// USDC in between chains +export const haloUSDC: { [chainId in ChainId]?: Token } = { + [ChainId.MAINNET]: USDC, + [ChainId.KOVAN]: new Token(ChainId.KOVAN, '0x12513dd17ae75af37d9eb21124f98b04705be906', 6, 'USDC', 'USDC'), + [ChainId.MATIC]: new Token(ChainId.MATIC, '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', 6, 'USDC', 'USDC') +} + +// Token Lists +// Add tokens here to support, divided into different networks +const mainNetTokenList: Token[] = [ + USDC, + new Token(ChainId.MAINNET, '0x70e8dE73cE538DA2bEEd35d14187F6959a8ecA96', 6, 'XSGD', 'Xfers SGD') + // new Token(ChainId.MAINNET, '0x00000100F2A2bd000715001920eB70D229700085', 18, 'TCAD', 'True CAD'), + // new Token(ChainId.MAINNET, '0x00006100F7090010005F1bd7aE6122c3C2CF0090', 18, 'TAUD', 'True AUD'), + // new Token(ChainId.MAINNET, '0x00000000441378008EA67F4284A57932B1c000a5', 18, 'TGBP', 'True GBP'), + // new Token(ChainId.MAINNET, '0x0000000000085d4780B73119b644AE5ecd22b376', 18, 'TUSD', 'True USD') +] + +const kovanTokenList: Token[] = [ + haloUSDC[ChainId.KOVAN] as Token, + new Token(ChainId.KOVAN, '0x7bcFAF04C9BAD18e3A823740E0683A36426BB0Fe', 2, 'EURS', 'EURS Stasis Coin'), + new Token(ChainId.KOVAN, '0x6d2dCe898dC56B1F26B8053995E7096804cd3fD5', 18, 'GBP', 'GBP'), + new Token(ChainId.KOVAN, '0xE9958574866587c391735b7e7CE0D79432d3b9d0', 18, 'CHF', 'Jarvis Synthetic Swiss Franc') +] + +const polygonTokenList: Token[] = [ + haloUSDC[ChainId.MATIC] as Token, + new Token(ChainId.MATIC, '0x769434dcA303597C8fc4997Bf3DAB233e961Eda2', 6, 'XSGD', 'Xfers SGD') + // new Token(ChainId.MATIC, '0xe4F7761b541668f88d04fe9F2E9DF10CA613aEf7', 18, 'TAUD', 'Wrapped True AUD'), + // new Token(ChainId.MATIC, '0x6d3cC56DFC016151eE2613BdDe0e03Af9ba885CC', 18, 'TCAD', 'Wrapped True CAD'), + // new Token(ChainId.MATIC, '0x81A123f10C78216d32F8655eb1A88B5E9A3e9f2F', 18, 'TGBP', 'Wrapped True GBP'), + // new Token(ChainId.MATIC, '0x2e1AD108fF1D8C782fcBbB89AAd783aC49586756', 18, 'TUSD', 'Wrapped True USD') +] + +// allows switch of token list when changing networks +export const haloTokenList: { [chainId in ChainId]?: Token[] } = { + [ChainId.MAINNET]: mainNetTokenList, + [ChainId.KOVAN]: kovanTokenList, + [ChainId.MATIC]: polygonTokenList +} + +// Assimilators +// Add assimilators here to support. These are arranged per base currency per network +const mainNetAssimilators: AssimilatorAddressMap = { + [TokenSymbol.USDC]: ZERO_ADDRESS, + [TokenSymbol.EURS]: ZERO_ADDRESS, + [TokenSymbol.GBP]: ZERO_ADDRESS, + [TokenSymbol.CHF]: ZERO_ADDRESS, + [TokenSymbol.TUSD]: ZERO_ADDRESS, + [TokenSymbol.TAUD]: '0x17c3A12F68C95c637055ea65aA90D72813F430d4', + [TokenSymbol.TCAD]: '0x70bA0482FD6343e8fcbd2480C8b4C11d6c654DF5', + [TokenSymbol.TGBP]: '0x9Ec9C7215F936Ef0C5eFb8383a98354F5AcEFDd7', + [TokenSymbol.XSGD]: '0xCaE2502093413290bc0E5c2CfA1039C661103bf1' +} + +const kovanAssimilators: AssimilatorAddressMap = { + [TokenSymbol.USDC]: '0x4a6EF0be792F8C2Ff2f3477Fb9354d0Dbc7797f9', + [TokenSymbol.EURS]: '0x3Af71eC189cf9de106b7C4DAC269d6C6d3d37a97', + [TokenSymbol.GBP]: '0x6500ACbaF819C520aDA1B5C91cc8aFe0cD91008f', + [TokenSymbol.CHF]: '0xa6b02260754c506403E12e9b09211848F6BC9Cc0', + [TokenSymbol.TUSD]: ethers.constants.AddressZero, + [TokenSymbol.TAUD]: ethers.constants.AddressZero, + [TokenSymbol.TCAD]: ethers.constants.AddressZero, + [TokenSymbol.TGBP]: ethers.constants.AddressZero, + [TokenSymbol.XSGD]: ethers.constants.AddressZero +} + +const polygonAssimilators: AssimilatorAddressMap = { + [TokenSymbol.USDC]: '0xbf7455b83fd4c6dcF4f544e68DD38670b4Ff07D6', + [TokenSymbol.EURS]: ethers.constants.AddressZero, + [TokenSymbol.GBP]: ethers.constants.AddressZero, + [TokenSymbol.CHF]: ethers.constants.AddressZero, + [TokenSymbol.TUSD]: '0x7aBF5B0183631fb7537523c6627D8016408e1509', + [TokenSymbol.TAUD]: '0x1bF0990fDB4CABF88aB7f3D412691cE2B425F2Ef', + [TokenSymbol.TCAD]: '0x5710FFcED6aEd86820da398E2925DAf5738cd4ce', + [TokenSymbol.TGBP]: '0x57D63073C5d8c8f52C38779cf141365aC46aeD72', + [TokenSymbol.XSGD]: '0xB80c3d54BF3A0E25B927a216F48ecE07dB1173Ed' +} + +// Allows switching in between assimilators when chainging network to be used by the useSwapToken() hook +export const haloAssimilators: { [chainId in ChainId]?: AssimilatorAddressMap } = { + [ChainId.MAINNET]: mainNetAssimilators, + [ChainId.KOVAN]: kovanAssimilators, + [ChainId.MATIC]: polygonAssimilators +} diff --git a/src/halo-hooks/amm/useSwapToken.ts b/src/halo-hooks/amm/useSwapToken.ts new file mode 100644 index 00000000..0c3f365b --- /dev/null +++ b/src/halo-hooks/amm/useSwapToken.ts @@ -0,0 +1,264 @@ +import { useCallback, useEffect, useState } from 'react' +import ROUTER_ABI from 'constants/haloAbis/Router.json' +import ASSIMILATOR_ABI from 'constants/haloAbis/Assimilator.json' +import { formatUnits, parseUnits } from 'ethers/lib/utils' +import { ethers } from 'ethers' +import { getContract } from 'utils' +import { ChainId, Token } from '@sushiswap/sdk' +import { + AssimilatorAddressMap, + haloAssimilators, + haloUSDC, + routerAddress, + TokenSymbol +} from 'constants/tokenLists/halo-tokenlist' +import { useTransactionAdder } from 'state/transactions/hooks' +import { useActiveWeb3React } from '../../hooks' +import { ERC20_ABI } from 'constants/abis/erc20' +import { toFixed } from 'utils/formatNumber' +import { SwapButtonState } from 'constants/buttonStates' +import { useTime } from 'halo-hooks/useTime' + +export enum CurrencySide { + TO_CURRENCY = 'toCurrency', + FROM_CURRENCY = 'fromCurrency' +} + +export const useSwapToken = ( + toCurrency: Token, + fromCurrency: Token, + setButtonState: (state: SwapButtonState) => void +) => { + const { account, chainId, library } = useActiveWeb3React() + const [price, setPrice] = useState() + const { getFutureTime } = useTime() + const [toAmountBalance, setToAmountBalance] = useState('0') + const [fromAmountBalance, setFromAmountBalance] = useState('0') + const [toMinimumAmount, setToMinimumAmount] = useState() + const [fromMinimumAmount, setFromMinimumAmount] = useState() + const [allowance, setAllowance] = useState('0') + const addTransaction = useTransactionAdder() + + const toSafeValue = useCallback( + (value: string, currencySide: CurrencySide) => { + if (value.charAt(value.length - 1) === '.') { + return value.substring(0, value.length - 1) + } else { + return toFixed( + Number(value), + currencySide === CurrencySide.TO_CURRENCY ? fromCurrency.decimals : toCurrency.decimals + ) + } + }, + [fromCurrency.decimals, toCurrency.decimals] + ) + + const getCurrencyContract = useCallback( + async (address: string) => { + if (!library || !account) return + + return getContract(address, ERC20_ABI, library, true && account) + }, + [library, account] + ) + + const getTokenBalance = useCallback( + async (currencySide: CurrencySide) => { + const currencyContract = await getCurrencyContract( + currencySide === CurrencySide.TO_CURRENCY ? toCurrency.address : fromCurrency.address + ) + + if (!account || !toCurrency || !fromCurrency) { + setToAmountBalance('0.0') + setFromAmountBalance('0.0') + return + } + + try { + if (currencySide === CurrencySide.TO_CURRENCY) { + setToAmountBalance(formatUnits(await currencyContract?.balanceOf(account), toCurrency.decimals)) + } else { + setFromAmountBalance(formatUnits(await currencyContract?.balanceOf(account), fromCurrency.decimals)) + } + } catch (e) { + console.log(e) + } + }, + [account, getCurrencyContract, fromCurrency, toCurrency] + ) + + const getRouter = useCallback(async () => { + if (!library || !account || !chainId) return + + return getContract(routerAddress[chainId] as string, ROUTER_ABI, library, true && account) + }, [account, chainId, library]) + + const getPrice = useCallback(async () => { + if (!chainId || !library) return + + try { + const toTokenAssimilatorContract = getContract( + (haloAssimilators[chainId as ChainId] as AssimilatorAddressMap)[toCurrency.symbol as TokenSymbol], + ASSIMILATOR_ABI, + library + ) + + const fromTokenAssimilatorContract = getContract( + (haloAssimilators[chainId as ChainId] as AssimilatorAddressMap)[fromCurrency.symbol as TokenSymbol], + ASSIMILATOR_ABI, + library + ) + + const toCurrencyRate = await toTokenAssimilatorContract.getRate() + const fromCurrencyRate = await fromTokenAssimilatorContract.getRate() + + setPrice(fromCurrencyRate / toCurrencyRate) + } catch (e) { + console.log(e) + } + }, [chainId, library, fromCurrency.symbol, toCurrency.symbol]) + + const getMinimumAmount = useCallback( + async (amount: string, currencySide: CurrencySide) => { + // currencySide is the unknown + + const CurveContract = await getRouter() + + if (!CurveContract || !chainId) return + + const quoteAmount = parseUnits( + toSafeValue(amount, currencySide), + currencySide === CurrencySide.TO_CURRENCY ? fromCurrency.decimals : toCurrency.decimals + ) + try { + const res = + currencySide === CurrencySide.TO_CURRENCY + ? await CurveContract?.viewOriginSwap( + haloUSDC[chainId]?.address, + fromCurrency.address, + toCurrency.address, + quoteAmount + ) + : await CurveContract?.viewTargetSwap( + haloUSDC[chainId]?.address, + fromCurrency.address, + toCurrency.address, + quoteAmount + ) + currencySide === CurrencySide.TO_CURRENCY + ? setToMinimumAmount(formatUnits(res, toCurrency.decimals)) + : setFromMinimumAmount(formatUnits(res, fromCurrency.decimals)) + } catch (e) { + setButtonState(SwapButtonState.InsufficientLiquidity) + } + }, + [ + fromCurrency.address, + fromCurrency.decimals, + toCurrency.address, + toCurrency.decimals, + chainId, + getRouter, + toSafeValue, + setButtonState + ] + ) + + const fetchAllowance = useCallback(async () => { + if (account) { + const CurveContract = await getRouter() + const tokenContract = await getCurrencyContract(fromCurrency.address) + + try { + const allowance = await tokenContract?.allowance(account, CurveContract?.address) + setAllowance(String(allowance)) + } catch { + setAllowance('0') + } + } + }, [account, fromCurrency.address, getCurrencyContract, getRouter]) + + const approve = useCallback(async () => { + const CurveContract = await getRouter() + const tokenContract = await getCurrencyContract(fromCurrency.address) + + try { + const tx = await tokenContract?.approve(CurveContract?.address, ethers.constants.MaxUint256) + await tx.wait() + + return addTransaction(tx, { summary: 'Approve' }) + } catch (e) { + return e + } + }, [addTransaction, fromCurrency.address, getCurrencyContract, getRouter]) + + useEffect(() => { + if (account) { + fetchAllowance() + } + const refreshInterval = setInterval(fetchAllowance, 10000) + return () => clearInterval(refreshInterval) + }, [account, fetchAllowance]) + + const swapToken = useCallback( + async (amount: string, deadline?: number, slippage?: number) => { + if (!chainId || !library) return + + const CurveContract = await getRouter() + + const quoteAmount = parseUnits(amount, fromCurrency.decimals) + + const minimumAmountSwap = Number(toMinimumAmount) * (1 - (slippage ? slippage / 100 : 0.01)) + const parsedMinimumAmountSwap = parseUnits(toFixed(minimumAmountSwap, toCurrency.decimals), toCurrency.decimals) + + try { + const tx = await CurveContract?.originSwap( + haloUSDC[chainId]?.address, + fromCurrency.address, + toCurrency.address, + quoteAmount, + parsedMinimumAmountSwap, + deadline ? getFutureTime(deadline * 60) : getFutureTime(60) + ) + + await tx.wait() + + addTransaction(tx, { summary: `Swap to ${toCurrency.name}` }) + + return tx + } catch (e) { + console.log(e) + return null + } + }, + [ + addTransaction, + fromCurrency.address, + fromCurrency.decimals, + toCurrency.address, + toCurrency.name, + toCurrency.decimals, + getFutureTime, + chainId, + getRouter, + library, + toMinimumAmount + ] + ) + + return { + getPrice, + getMinimumAmount, + price, + toMinimumAmount, + fromMinimumAmount, + allowance, + approve, + swapToken, + fetchAllowance, + toSafeValue, + getTokenBalance, + toAmountBalance, + fromAmountBalance + } +} diff --git a/src/halo-hooks/useBridge.ts b/src/halo-hooks/useBridge.ts index 83f6c90d..130f32b8 100644 --- a/src/halo-hooks/useBridge.ts +++ b/src/halo-hooks/useBridge.ts @@ -8,11 +8,11 @@ import SECONDARY_BRIDGE_ABI from 'constants/haloAbis/SecondaryBridge.json' import TOKEN_ABI from 'constants/abis/erc20.json' import { getContract } from 'utils' import { BRIDGE_CONTRACTS, ORIGINAL_TOKEN_CHAIN_ID } from 'constants/bridge' -import { ButtonState } from 'pages/Tailwind/Bridge/BridgePanel' import { ApproveButtonState } from 'components/Tailwind/Buttons/ApproveButton' import { toNumber } from 'utils/formatNumber' import { formatEther, parseEther } from 'ethers/lib/utils' import ReactGA from 'react-ga' +import { ButtonState } from '../constants/buttonStates' interface BridgeProps { setButtonState: (buttonState: ButtonState) => void diff --git a/src/halo-hooks/useTime.ts b/src/halo-hooks/useTime.ts index 3bf206cf..5bb8f115 100644 --- a/src/halo-hooks/useTime.ts +++ b/src/halo-hooks/useTime.ts @@ -8,9 +8,8 @@ export const useTime = () => { (addMinutes = 300) => { if (currentBlockTime) { return currentBlockTime.add(addMinutes).toNumber() - } else { - return new Date().getTime() + addMinutes } + return new Date().getTime() + addMinutes }, [currentBlockTime] ) diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 915fc595..6bdd4a15 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -68,6 +68,7 @@ export default function App() { + diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 56e6791f..2fa29a22 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -330,6 +330,7 @@ export default function Swap() { cornerRadiusBottomNone={isExpertMode ? false : true} //containerBackground={'#101b31'} /> + {isExpertMode && !showWrap && ( diff --git a/src/pages/Tailwind/Bridge/BridgePanel.tsx b/src/pages/Tailwind/Bridge/BridgePanel.tsx index 6e5ebde5..29646078 100644 --- a/src/pages/Tailwind/Bridge/BridgePanel.tsx +++ b/src/pages/Tailwind/Bridge/BridgePanel.tsx @@ -18,25 +18,7 @@ import { Lock } from 'react-feather' import useBridge from 'halo-hooks/useBridge' import { useActiveWeb3React } from 'hooks' import { NETWORK_SUPPORTED_FEATURES } from '../../../constants/networks' - -export enum ButtonState { - Default, - EnterAmount, - Approving, - Approved, - Next, - Confirming, - InsufficientBalance, - NotMinimum, - Retry, - MaxCap -} - -enum ConfirmTransactionModalState { - NotConfirmed, - InProgress, - Successful -} +import { ButtonState, ModalState } from '../../../constants/buttonStates' const BridgePanel = () => { const { account, error, chainId } = useActiveWeb3React() @@ -44,7 +26,7 @@ const BridgePanel = () => { const [approveState, setApproveState] = useState(ApproveButtonState.NotApproved) const [showModal, setShowModal] = useState(false) const [buttonState, setButtonState] = useState(ButtonState.EnterAmount) - const [modalState, setModalState] = useState(ConfirmTransactionModalState.NotConfirmed) + const [modalState, setModalState] = useState(ModalState.NotConfirmed) const [chainToken, setChainToken] = useState(HALO) const [token, setToken] = useState(chainId ? HALO[chainId] : undefined) @@ -136,7 +118,7 @@ const BridgePanel = () => { } else { estimateDeposit(destinationChainId, inputValue) } - setModalState(ConfirmTransactionModalState.NotConfirmed) + setModalState(ModalState.NotConfirmed) setShowModal(true) } }} @@ -344,6 +326,7 @@ const BridgePanel = () => { setChainToken({ [chainId]: selectedToken }) } }} + balance={String(balance)} />
@@ -359,27 +342,27 @@ const BridgePanel = () => { confirmLogic={async () => { if (token && ORIGINAL_TOKEN_CHAIN_ID[token.address] !== chainId) { if (await burn(ethers.utils.parseEther(`${inputValue}`))) { - setModalState(ConfirmTransactionModalState.Successful) + setModalState(ModalState.Successful) setButtonStates() } else { setShowModal(false) - setModalState(ConfirmTransactionModalState.NotConfirmed) + setModalState(ModalState.NotConfirmed) setButtonState(ButtonState.Retry) } } else { if (await deposit(ethers.utils.parseEther(`${inputValue}`), destinationChainId)) { - setModalState(ConfirmTransactionModalState.Successful) + setModalState(ModalState.Successful) setButtonStates() } else { setShowModal(false) - setModalState(ConfirmTransactionModalState.NotConfirmed) + setModalState(ModalState.NotConfirmed) setButtonState(ButtonState.Retry) } } }} onDismiss={() => { setShowModal(false) - if (modalState === ConfirmTransactionModalState.NotConfirmed) setButtonState(ButtonState.Retry) + if (modalState === ModalState.NotConfirmed) setButtonState(ButtonState.Retry) }} onSuccessConfirm={() => setShowModal(false)} originChainId={chainId ?? ChainId.MAINNET} diff --git a/src/pages/Tailwind/Bridge/modals/BridgeTransactionModal.tsx b/src/pages/Tailwind/Bridge/modals/BridgeTransactionModal.tsx index a5d31e63..7d1a9b6e 100644 --- a/src/pages/Tailwind/Bridge/modals/BridgeTransactionModal.tsx +++ b/src/pages/Tailwind/Bridge/modals/BridgeTransactionModal.tsx @@ -8,6 +8,7 @@ import ArrowIcon from 'assets/svg/arrow-up-icon-large.svg' import SwitchIcon from 'assets/svg/switch-icon.svg' import { shortenAddress, getExplorerLink } from 'utils' import { calculateShuttleFee } from 'utils/bridge' +import { ModalState } from '../../../../constants/buttonStates' interface ConfirmTransactionModalProps { isVisible: boolean @@ -21,18 +22,12 @@ interface ConfirmTransactionModalProps { destinationChainId: ChainId tokenSymbol: string wrappedTokenSymbol: string - state: ConfirmTransactionModalState - setState: (state: ConfirmTransactionModalState) => void + state: ModalState + setState: (state: ModalState) => void successHash: string estimatedGas: string } -enum ConfirmTransactionModalState { - NotConfirmed, - InProgress, - Successful -} - interface InProgressContentProps { amount: string tokenSymbol: string @@ -130,12 +125,11 @@ const ConfirmTransactionModal = ({ title="Confirm" state={PrimaryButtonState.Enabled} onClick={async () => { - setState(ConfirmTransactionModalState.InProgress) + setState(ModalState.InProgress) try { await confirmLogic() - // setState(ConfirmTransactionModalState.Successful) } catch (e) { - setState(ConfirmTransactionModalState.NotConfirmed) + setState(ModalState.NotConfirmed) } }} /> @@ -196,7 +190,7 @@ const ConfirmTransactionModal = ({ state={PrimaryButtonState.Enabled} onClick={() => { onSuccessConfirm() - setState(ConfirmTransactionModalState.NotConfirmed) + setState(ModalState.NotConfirmed) }} />
@@ -208,17 +202,15 @@ const ConfirmTransactionModal = ({ { - setState(ConfirmTransactionModalState.NotConfirmed) + setState(ModalState.NotConfirmed) onDismiss() }} > - {state === ConfirmTransactionModalState.NotConfirmed && } - {state === ConfirmTransactionModalState.InProgress && ( + {state === ModalState.NotConfirmed && } + {state === ModalState.InProgress && ( )} - {state === ConfirmTransactionModalState.Successful && ( - - )} + {state === ModalState.Successful && } ) } diff --git a/src/pages/Tailwind/Swap/SwapDetails.tsx b/src/pages/Tailwind/Swap/SwapDetails.tsx new file mode 100644 index 00000000..31911b14 --- /dev/null +++ b/src/pages/Tailwind/Swap/SwapDetails.tsx @@ -0,0 +1,53 @@ +import React from 'react' +import { formatNumber, NumberFormat } from 'utils/formatNumber' + +interface SwapDetailsProps { + price?: number + toCurrency?: string + fromCurrency?: string + minimumReceived?: string + priceImpact?: string + liqProviderFee?: string +} + +export default function SwapDetails({ + price, + toCurrency, + fromCurrency, + minimumReceived +}: /* + priceImpact, + liqProviderFee + */ +SwapDetailsProps) { + return ( + <> +
+
Price
+
+ {price ? `${formatNumber(price, NumberFormat.long)} ${toCurrency}/${fromCurrency}` : '--'} +
+
+
+
Minimum Received
+
{minimumReceived ? `${minimumReceived} ${toCurrency}` : '--'}
+
+ + {/* + + // Leaving this for now since we might need this for future calculations. + // The math behind this is from the one that I showed you before. + +
+
Price Impact
+
{`<0.01%`}
+
+
+
Liquidity Provider Fee
+
Price
+
+ + */} + + ) +} diff --git a/src/pages/Tailwind/Swap/SwapPanel.tsx b/src/pages/Tailwind/Swap/SwapPanel.tsx new file mode 100644 index 00000000..7196492d --- /dev/null +++ b/src/pages/Tailwind/Swap/SwapPanel.tsx @@ -0,0 +1,463 @@ +import React, { useCallback, useState, useEffect } from 'react' +import { ChainId, Token } from '@sushiswap/sdk' +import { useWeb3React } from '@web3-react/core' +import CurrencyInput from 'components/Tailwind/InputFields/CurrencyInput' +import ConnectButton from 'components/Tailwind/Buttons/ConnectButton' +import SwapSettingsModal from './modals/SwapSettingsModal' +import SwapTransactionModal from './modals/SwapTransactionModal' +import SwapDetails from './SwapDetails' +import SwitchIcon from 'assets/svg/switch-swap-icon.svg' +import SettingsIcon from 'assets/svg/cog-icon.svg' +import { useWalletModalToggle } from '../../../state/application/hooks' +import { CurrencySide, useSwapToken } from 'halo-hooks/amm/useSwapToken' +import ApproveButton, { ApproveButtonState } from 'components/Tailwind/Buttons/ApproveButton' +import PrimaryButton, { PrimaryButtonState, PrimaryButtonType } from 'components/Tailwind/Buttons/PrimaryButton' +import RetryButton from 'components/Tailwind/Buttons/RetryButton' +import { haloTokenList } from 'constants/tokenLists/halo-tokenlist' +import { SwapButtonState, ModalState } from '../../../constants/buttonStates' +import { HALO } from '../../../constants' +import PageWarning from 'components/Tailwind/Layout/PageWarning' + +const SwapPanel = () => { + const { account, error, chainId } = useWeb3React() + + const [toCurrency, setToCurrency] = useState( + chainId ? (haloTokenList[chainId as ChainId] as Token[])[0] : (HALO[ChainId.MAINNET] as Token) + ) + const [fromCurrency, setFromCurrency] = useState( + chainId ? (haloTokenList[chainId as ChainId] as Token[])[1] : (HALO[ChainId.MAINNET] as Token) + ) + const [fromInputValue, setFromInputValue] = useState('') + const [toInputValue, setToInputValue] = useState('') + const [txDeadline, setTxDeadline] = useState(10) // 10 minutes + const [slippage, setSlippage] = useState(0.03) // 3% + const [approveState, setApproveState] = useState(ApproveButtonState.NotApproved) + const [showModal, setShowModal] = useState(false) + const [showSettingsModal, setShowSettingsModal] = useState(false) + const [buttonState, setButtonState] = useState(SwapButtonState.EnterAmount) + const [timeLeft, setTimeLeft] = useState(60) + const [isExpired, setIsExpired] = useState(false) + const [swapTransactionModalState, setSwapTransactionModalState] = useState(ModalState.NotConfirmed) + const [txhash, setTxhash] = useState('') + + const { + getPrice, + getMinimumAmount, + getTokenBalance, + toAmountBalance, + fromAmountBalance, + price, + toMinimumAmount, + fromMinimumAmount, + approve, + allowance, + swapToken + } = useSwapToken(toCurrency, fromCurrency, setButtonState) + + const handleApprove = useCallback(async () => { + try { + setApproveState(ApproveButtonState.Approving) + const txHash: any = await approve() + + // user rejected tx or didn't go thru + if (txHash.code === 4001 || !txHash) { + setApproveState(ApproveButtonState.NotApproved) + } + } catch (e) { + console.log(e) + } + }, [approve, setApproveState]) + + useEffect(() => { + if (!timeLeft) return + + const intervalId = setInterval(() => { + const decreaseTime = timeLeft - 1 + + setTimeLeft(decreaseTime) + if (decreaseTime === 0) { + setIsExpired(true) + } + }, 1000) + + return () => clearInterval(intervalId) + }, [timeLeft]) + + const updateBalances = useCallback(async () => { + await getTokenBalance(CurrencySide.TO_CURRENCY) + await getTokenBalance(CurrencySide.FROM_CURRENCY) + }, [getTokenBalance]) + + useEffect(() => { + getPrice() + }, [toCurrency, fromCurrency, getMinimumAmount, getPrice, fromInputValue]) + + useEffect(() => { + setToInputValue(toMinimumAmount || '') + }, [toMinimumAmount]) + + useEffect(() => { + setFromInputValue(fromMinimumAmount || '') + }, [fromMinimumAmount]) + + useEffect(() => { + if (chainId) { + setToCurrency((haloTokenList[chainId as ChainId] as Token[])[0]) + setFromCurrency((haloTokenList[chainId as ChainId] as Token[])[1]) + setToInputValue('') + setFromInputValue('') + } + }, [chainId]) + + useEffect(() => { + updateBalances() + }, [setToCurrency, setFromCurrency, toInputValue, fromInputValue, updateBalances]) + + useEffect(() => { + console.log(allowance) + if (allowance && Number(allowance) > 0) { + setApproveState(ApproveButtonState.Approved) + setButtonState(SwapButtonState.Swap) + } else { + setApproveState(ApproveButtonState.NotApproved) + } + }, [allowance]) + + const NotApproveContent = () => { + return ( +
+
+ { + if (fromInputValue && toInputValue) { + handleApprove() + } + }} + /> +
+
+ +
+
+ ) + } + + const SwapContent = () => { + return ( +
+ { + setTimeLeft(60) + setIsExpired(false) + if (fromInputValue && toInputValue && Number(fromInputValue) > 0 && Number(toInputValue) > 0) { + setButtonState(SwapButtonState.Confirming) + setShowModal(true) + } + }} + /> +
+ ) + } + + const ApprovingContent = () => { + return ( +
+
+ +
+
+ +
+
+ ) + } + + const ApprovedContent = () => { + return ( +
+
+ +
+
+ console.log('clicked')} /> +
+
+ ) + } + + const ConfirmingContent = () => { + return ( +
+ +
+ ) + } + + const EnterAmountContent = () => { + return ( +
+ +
+ ) + } + + const InsufficientBalanceContent = () => { + return ( +
+ +
+ ) + } + + const InsufficientLiquidityContent = () => { + return ( +
+ +
+ ) + } + + const RetryContent = () => { + return ( +
+ console.log('clicked')} /> +
+ ) + } + + const setButtonStates = useCallback(() => { + if (!allowance || Number(allowance) === 0) { + if (fromInputValue && parseFloat(fromInputValue) > 0 && toInputValue && parseFloat(toInputValue) > 0) { + setButtonState(SwapButtonState.Default) + setApproveState(ApproveButtonState.NotApproved) + } else if (parseFloat(fromInputValue) >= parseFloat(fromAmountBalance)) { + setButtonState(SwapButtonState.InsufficientBalance) + setApproveState(ApproveButtonState.NotApproved) + } else { + setButtonState(SwapButtonState.Default) + } + } else if (Number(allowance) > 0) { + if (parseFloat(fromInputValue) === 0 || parseFloat(toInputValue) === 0) { + setButtonState(SwapButtonState.Swap) + } + } + }, [allowance, fromInputValue, fromAmountBalance, toInputValue]) + + useEffect(() => { + setButtonStates() + }, [setButtonStates]) + + const CurrentButtonContent = () => { + if (approveState === ApproveButtonState.NotApproved) { + if (buttonState === SwapButtonState.Default) { + return + } else if (buttonState === SwapButtonState.InsufficientBalance) { + return + } else if (parseFloat(fromInputValue) === 0 || parseFloat(toInputValue) === 0) { + return + } else if (buttonState === SwapButtonState.InsufficientLiquidity) { + return + } + } else if (approveState === ApproveButtonState.Approving) { + return + } else if (approveState === ApproveButtonState.Approved) { + if (buttonState === SwapButtonState.Default) { + return + } else if (buttonState === SwapButtonState.Swap) { + return + } else if (buttonState === SwapButtonState.Confirming) { + return + } else if (buttonState === SwapButtonState.Retry) { + return + } else if (buttonState === SwapButtonState.InsufficientBalance) { + return + } else if (buttonState === SwapButtonState.InsufficientLiquidity) { + return + } + } + + return <> + } + + const MainContent = () => { + const toggleWalletModal = useWalletModalToggle() + + if (!account && !error) { + return ( +
+ toggleWalletModal()} /> +
+ ) + } + return ( + <> + + + + ) + } + + return ( + <> +
+ {account ? ( + <> +
+
+

From

+
+
+ Settings setShowSettingsModal(true)} /> +
+
+ +
+ { + if (approveState === ApproveButtonState.Approved) { + if (parseFloat(fromAmountBalance) >= parseFloat(val)) { + setButtonState(SwapButtonState.Swap) + } else if (parseFloat(fromAmountBalance) < parseFloat(val)) { + setButtonState(SwapButtonState.InsufficientBalance) + } + getMinimumAmount(val, CurrencySide.TO_CURRENCY) + } + + setFromInputValue(val) + }} + showBalance={true} + showMax={true} + tokenList={haloTokenList[chainId as ChainId] || []} + balance={fromAmountBalance} + onSelectToken={token => { + if (token !== toCurrency) { + setFromCurrency(token) + setToInputValue('') + setFromInputValue('') + updateBalances() + } + }} + /> +
+ +
+
+

Swap to

+
+
{ + const prevToInputValue = toInputValue + const prevToCurrency = toCurrency + setToInputValue(fromInputValue) + setFromInputValue(prevToInputValue) + setToCurrency(fromCurrency) + setFromCurrency(prevToCurrency) + updateBalances() + getMinimumAmount(prevToInputValue, CurrencySide.TO_CURRENCY) + }} + > + Switch +
+
+ +
+ { + if (approveState === ApproveButtonState.Approved) { + setButtonState(SwapButtonState.Swap) + } + getMinimumAmount(val, CurrencySide.FROM_CURRENCY) + setToInputValue(val) + }} + showBalance={true} + showMax={true} + tokenList={haloTokenList[chainId as ChainId] || []} + balance={toAmountBalance} + onSelectToken={token => { + if (token !== fromCurrency) { + setToInputValue('') + setFromInputValue('') + setToCurrency(token) + } + }} + /> +
+ + ) : ( + + )} + +
+ setShowSettingsModal(false)} + didChangeTxDeadline={val => setTxDeadline(Number(val))} + /> + { + const txn = await swapToken(fromInputValue, txDeadline, slippage) + if (txn) { + setTxhash(txn.hash) + setSwapTransactionModalState(ModalState.Successful) + } + + if (txn === 4001 || !txn) { + setSwapTransactionModalState(ModalState.NotConfirmed) + } + }} + onPriceUpdate={() => { + setTimeLeft(60) + getMinimumAmount(fromInputValue, CurrencySide.TO_CURRENCY) + getPrice() + }} + onDismiss={() => { + setButtonState(SwapButtonState.Swap) + setShowModal(false) + }} + isExpired={isExpired} + setIsExpired={setIsExpired} + timeLeft={timeLeft} + swapTransactionModalState={swapTransactionModalState} + setSwapTransactionModalState={setSwapTransactionModalState} + txnHash={txhash} + chainId={chainId as number} + /> + + ) +} + +export default SwapPanel diff --git a/src/pages/Tailwind/Swap/index.tsx b/src/pages/Tailwind/Swap/index.tsx index 5d620136..d2290298 100644 --- a/src/pages/Tailwind/Swap/index.tsx +++ b/src/pages/Tailwind/Swap/index.tsx @@ -1,8 +1,8 @@ import React from 'react' import PageWrapper from 'components/Tailwind/Layout/PageWrapper' import PageHeaderLeft from 'components/Tailwind/Layout/PageHeaderLeft' +import SwapPanel from './SwapPanel' import InfoCard from 'components/Tailwind/Cards/InfoCard' -import PageWarning from 'components/Tailwind/Layout/PageWarning' const Swap = () => { return ( @@ -17,10 +17,8 @@ const Swap = () => {
-
- +
+
diff --git a/src/pages/Tailwind/Swap/modals/SwapSettingsModal.tsx b/src/pages/Tailwind/Swap/modals/SwapSettingsModal.tsx new file mode 100644 index 00000000..dc4e1dd1 --- /dev/null +++ b/src/pages/Tailwind/Swap/modals/SwapSettingsModal.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react' +import BaseModal from 'components/Tailwind/Modals/BaseModal' +import SlippageTolerance from 'components/Tailwind/InputFields/SlippageTolerance' +import NumericalInput from 'components/NumericalInput' +import { HelpCircle as QuestionIcon } from 'react-feather' + +interface SwapSettingsModalProps { + txDeadline: number + isVisible: boolean + onSlippageChanged: (slippage: number) => void + onDismiss: () => void + didChangeTxDeadline: (newValue: string) => void +} + +const SwapSettingsModal = ({ + txDeadline, + isVisible, + onSlippageChanged, + onDismiss, + didChangeTxDeadline +}: SwapSettingsModalProps) => { + const [slippage, setSlippage] = useState(0.03) + + const dismissGracefully = () => { + onDismiss() + } + + return ( + +
+
Transaction settings
+
+ { + const convertedNewSlippage = Number(newSlippage) / 100 + setSlippage(convertedNewSlippage) + onSlippageChanged(convertedNewSlippage) + }} + /> +
+
+ Transaction deadline + +
+
+
+ { + didChangeTxDeadline(val) + }} + /> +
+ minutes +
+
+
+ ) +} + +export default SwapSettingsModal diff --git a/src/pages/Tailwind/Swap/modals/SwapTransactionModal.tsx b/src/pages/Tailwind/Swap/modals/SwapTransactionModal.tsx new file mode 100644 index 00000000..745f36dc --- /dev/null +++ b/src/pages/Tailwind/Swap/modals/SwapTransactionModal.tsx @@ -0,0 +1,187 @@ +import React from 'react' +import BaseModal from 'components/Tailwind/Modals/BaseModal' +import PrimaryButton, { PrimaryButtonState, PrimaryButtonType } from 'components/Tailwind/Buttons/PrimaryButton' +import SwapDetails from '../SwapDetails' +import { ChainId, Currency } from '@sushiswap/sdk' +import CurrencyLogo from 'components/CurrencyLogo' +import SpinnerIcon from 'assets/svg/spinner-icon-large.svg' +import ArrowIcon from 'assets/svg/arrow-up-icon-large.svg' +import ArrowDownIcon from 'assets/svg/arrow-down-icon.svg' +import WarningIcon from 'assets/svg/warning-icon-purple.svg' +import { getExplorerLink } from '../../../../utils' +import { ModalState } from 'constants/buttonStates' + +interface SwapTransactionModalProps { + isVisible: boolean + fromCurrency: Currency + toCurrency: Currency + fromAmount: string + toAmount: string + minimumAmount: string + price: number + onSwap: () => void + onPriceUpdate: () => void + onDismiss: () => void + isExpired: boolean + setIsExpired: (isExpired: boolean) => void + timeLeft: number + swapTransactionModalState: ModalState + setSwapTransactionModalState: (state: ModalState) => void + txnHash: string + chainId: ChainId +} + +const SwapTransactionModal = ({ + isVisible, + fromCurrency, + toCurrency, + fromAmount, + toAmount, + minimumAmount, + price, + onSwap, + onPriceUpdate, + onDismiss, + isExpired, + setIsExpired, + timeLeft, + swapTransactionModalState, + setSwapTransactionModalState, + txnHash, + chainId +}: SwapTransactionModalProps) => { + const dismissGracefully = () => { + setSwapTransactionModalState(ModalState.NotConfirmed) + setIsExpired(true) + onDismiss() + } + + const ConfirmContent = () => { + return ( + <> +
+
Confirm Swap
+
+
+ + {fromAmount} +
+ {fromCurrency.symbol} +
+
+ Swap +
+
+
+ + {toAmount} +
+ {toCurrency.symbol} +
+
+ Output is estimated. You will receive at least {minimumAmount} or the transaction will revert. +
+
+
+ warning + + {isExpired ? 'Price Update' : `Price valid for ${timeLeft.toString()}s`} + +
+
+ { + setIsExpired(false) + onPriceUpdate() + }} + /> +
+
+
+ +
+ { + setSwapTransactionModalState(ModalState.InProgress) + + try { + await onSwap() + } catch (e) { + setSwapTransactionModalState(ModalState.NotConfirmed) + } + setIsExpired(false) + }} + /> +
+ + ) + } + + const InProgressContent = () => { + return ( +
+
+ In progress... +
+
Waiting for confirmation
+
+ Swapping{' '} + + {fromAmount} {fromCurrency.symbol} + {' '} + for{' '} + + {' '} + {toAmount} {toCurrency.symbol}{' '} + +
+
Confirm this transaction in your wallet
+
+ ) + } + + const SuccessContent = () => { + return ( +
+
+ Confirmed +
+ +
Transaction Confirmed
+
+ + View on Etherscan + +
+
+ +
+
+ ) + } + + return ( + + {swapTransactionModalState === ModalState.NotConfirmed && } + {swapTransactionModalState === ModalState.InProgress && } + {swapTransactionModalState === ModalState.Successful && } + + ) +} + +export default SwapTransactionModal diff --git a/tailwind.config.js b/tailwind.config.js index 359a97c5..269d5fe2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -31,7 +31,8 @@ module.exports = { alternate: '#471bb2' }, success: { - DEFAULT: '#1BB233' + DEFAULT: '#1BB233', + alternate: '#56A86A' } }, maxWidth: {