diff --git a/.prettierrc.json b/.prettierrc.json index 22e8d44e..043b0cd7 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,6 +1,6 @@ { "semi": false, - "trailingComma": "es6", + "trailingComma": "es5", "singleQuote": true, "tabWidth": 2, "useTabs": false diff --git a/components/AssetInput/AssetInput.tsx b/components/AssetInput/AssetInput.tsx index 8ef6f251..4a86bfcc 100644 --- a/components/AssetInput/AssetInput.tsx +++ b/components/AssetInput/AssetInput.tsx @@ -1,28 +1,10 @@ -import { FC } from 'react' - -import { ChevronDownIcon } from '@chakra-ui/icons' -import { - Button, - forwardRef, - HStack, - IconButton, - Image, - Input, - Stack, - Text, - VStack, -} from '@chakra-ui/react' -// import { Asset } from 'types/blockchain' -import FallbackImage from 'components/FallbackImage' -import { useMultipleTokenBalance } from 'hooks/useTokenBalance' -import { useTokenInfo } from 'hooks/useTokenInfo' -import { useBaseTokenInfo } from 'hooks/useTokenInfo' -import { useTokenList } from 'hooks/useTokenList' +import { VStack, forwardRef } from '@chakra-ui/react' +import React, { useMemo } from 'react' +import { useTokenDollarValue } from 'hooks/useTokenDollarValue' +import WhaleInput from './WhaleInput' import { num } from 'libs/num' - -import AssetSelectModal from './AssetSelectModal' -import { useRecoilValue } from 'recoil' -import { walletState } from 'state/atoms/walletAtoms' +import { useBaseTokenInfo, useTokenInfo } from 'hooks/useTokenInfo' +import BalanceWithMaxNHalf from './BalanceWithMax' interface AssetInputProps { image?: boolean @@ -32,202 +14,90 @@ interface AssetInputProps { showList?: boolean onInputFocus?: () => void balance?: number + whalePrice?: number disabled?: boolean minMax?: boolean isSingleInput?: boolean hideToken?: string edgeTokenList?: string[] ignoreSlack?: boolean + hideMax?: boolean + hideDollarValue?: boolean + showBalanceSlider?: boolean + isBonding?: boolean + unbondingBalances?: { [key: string]: number } } -const AssetInput: FC = forwardRef( - ( - { - image = true, - token, - balance, - onChange, - value, - showList = true, - disabled, - minMax = true, - isSingleInput = false, - hideToken, - edgeTokenList, - ignoreSlack = false, - }, - ref - ) => { - const tokenInfo = useTokenInfo(token?.tokenSymbol) - const baseToken = useBaseTokenInfo() - const { status } = useRecoilValue(walletState) - const isConnected = status === `@wallet-state/connected` +const AssetInput = forwardRef((props: AssetInputProps, ref) => { + const { + balance, + disabled, + isSingleInput, + whalePrice, + token, + onChange, + ignoreSlack, + hideMax, + hideDollarValue, + } = props + const tokenInfo = useTokenInfo(token?.tokenSymbol) + const baseToken = useBaseTokenInfo() - const [tokenList] = useTokenList() - useMultipleTokenBalance(tokenList?.tokens?.map(({ symbol }) => symbol)) + const onMaxClick = () => { + const isTokenAndBaseTokenSame = tokenInfo?.symbol === baseToken?.symbol + onChange({ + ...token, + amount: + isTokenAndBaseTokenSame && !ignoreSlack + ? num(balance === 0 ? 0 : balance - 0.1).toFixed(6) + : num(balance).toFixed(6), + }) + } + const onHalfClick = () => { + onChange({ + ...token, + amount: num(balance === 0 ? 0 : balance / 2).toFixed(6), + }) + } + const maxDisabled = useMemo(() => { + return disabled || (!isSingleInput && !tokenInfo?.symbol) + }, [balance, disabled, isSingleInput, tokenInfo]) - return ( - - - - - onChange({ ...token, amount: target.value }) - } - /> + const numberOfTokens = useMemo( + () => `${token?.amount} ${token?.tokenSymbol}`, + [token] + ) - {minMax && ( - - - - - )} - - + const [tokenPrice] = + whalePrice !== null ? [whalePrice] : useTokenDollarValue(token?.tokenSymbol) - - - {showList ? ( - - {tokenInfo?.symbol ? ( - - {image && ( - logo-small} - /> - )} - - {tokenInfo?.symbol || 'Select Token'} - - - ) : ( - - {tokenInfo?.symbol || 'Select Token'} - - )} + const dollarValue = useMemo(() => { + return num(tokenPrice).times(token?.amount).dp(6).toString() + }, [tokenPrice, token?.amount]) - } - style={{ margin: 'unset' }} - /> - - ) : ( - - {image && ( - logo-small} - /> - )} + const balanceWithDecimals = useMemo( + () => + num(balance) + .dp(token?.decimals || 6) + .toString(), + [balance, token?.decimals] + ) - - {tokenInfo?.symbol || token?.tokenSymbol} - - - )} - - - - ) - } -) + return ( + + + + + ) +}) export default AssetInput diff --git a/components/AssetInput/AssetList.tsx b/components/AssetInput/AssetList.tsx index 1b25fa74..a635d6d1 100644 --- a/components/AssetInput/AssetList.tsx +++ b/components/AssetInput/AssetList.tsx @@ -1,18 +1,23 @@ import { FC, useMemo } from 'react' -import { background, Box, Button, HStack, Image, Text } from '@chakra-ui/react' +import { Box, Button, HStack, Image, Text } from '@chakra-ui/react' import FallbackImage from 'components/FallbackImage' import useFilter from 'hooks/useFilter' import { useMultipleTokenBalance } from 'hooks/useTokenBalance' import { useTokenList } from 'hooks/useTokenList' +import { + AMP_WHALE_TOKEN_SYMBOL, + B_WHALE_TOKEN_SYMBOL, +} from 'constants/bonding_contract' type AssetListProps = { - // assetList?: Asset[]; onChange: (token: any, isTokenChange?: boolean) => void search: string currentToken: string[] edgeTokenList: string[] amount?: number + isBonding?: boolean + unbondingBalances?: { [key: string]: number } } const AssetList: FC = ({ @@ -21,17 +26,26 @@ const AssetList: FC = ({ currentToken = [], amount, edgeTokenList = [], + isBonding = false, + unbondingBalances = null, }) => { const [tokenList] = useTokenList() - const [tokenBalance = []] = useMultipleTokenBalance( - tokenList?.tokens?.map(({ symbol }) => symbol) - ) + const tokens = isBonding + ? tokenList.tokens.filter((t) => + [AMP_WHALE_TOKEN_SYMBOL, B_WHALE_TOKEN_SYMBOL].includes(t.symbol) + ) + : tokenList.tokens + + const [tokenBalance = []] = + unbondingBalances !== null + ? [tokens?.map(({ symbol }) => unbondingBalances[symbol])] + : useMultipleTokenBalance(tokens?.map(({ symbol }) => symbol)) const tokensWithBalance = useMemo(() => { - if (tokenBalance.length == 0) return tokenList?.tokens + if (tokenBalance.length == 0) return tokens - return tokenList?.tokens + return tokens ?.map((token, index) => ({ ...token, balance: tokenBalance?.[index], diff --git a/components/AssetInput/AssetSelectModal.tsx b/components/AssetInput/AssetSelectModal.tsx index 04d0ca60..7cd8c909 100644 --- a/components/AssetInput/AssetSelectModal.tsx +++ b/components/AssetInput/AssetSelectModal.tsx @@ -23,6 +23,8 @@ interface AssetSelectModalProps { onChange: (asset: Asset, isTokenChange?: boolean) => void disabled: boolean amount?: number + isBonding?: boolean + unbondingBalances?: { [key: string]: number } } const AssetSelectModal: FC = ({ @@ -32,6 +34,8 @@ const AssetSelectModal: FC = ({ edgeTokenList = [], disabled, amount, + isBonding = false, + unbondingBalances = null, }) => { const { isOpen, onOpen, onClose } = useDisclosure() const [search, setSearch] = useState('') @@ -50,9 +54,7 @@ const AssetSelectModal: FC = ({ role="button" onClick={() => !disabled && onOpen()} justifyContent="space-between" - width={['full', '160px']} - // sx={{ 'button': {margin : 'unset'} }} - // style={{margin : "unset"}} + width={['full', 'fit-content']} > {children} @@ -75,11 +77,12 @@ const AssetSelectModal: FC = ({ diff --git a/components/AssetInput/BalanceSlider.tsx b/components/AssetInput/BalanceSlider.tsx new file mode 100644 index 00000000..e29465da --- /dev/null +++ b/components/AssetInput/BalanceSlider.tsx @@ -0,0 +1,40 @@ +import { + Slider, + SliderTrack, + SliderFilledTrack, + SliderThumb, + Box, +} from '@chakra-ui/react' + +type Props = { + balance: number + amount: number + onChange: (value: number) => void + show?: boolean +} + +const BalanceSlider = ({ balance, amount, onChange, show = true }: Props) => { + if (!show) return null + + return ( + + + + + + + + + ) +} + +export default BalanceSlider diff --git a/components/AssetInput/BalanceWithMax.tsx b/components/AssetInput/BalanceWithMax.tsx new file mode 100644 index 00000000..9bcf4789 --- /dev/null +++ b/components/AssetInput/BalanceWithMax.tsx @@ -0,0 +1,133 @@ +import React from 'react' +import { Box, Button, HStack, Text } from '@chakra-ui/react' + +type BalanceProps = { + balance: number | string +} + +const Balance = ({ balance }: BalanceProps) => { + return ( + + + Available: + + + {' '} + {balance} + + + ) +} + +type MaxButtonProps = { + disabled: boolean + onClick: () => void + hideMax?: boolean +} +type HalfButtonProps = { + disabled: boolean + onClick: () => void + hideHalf?: boolean +} + +const MaxButton = ({ disabled, onClick, hideMax = false }: MaxButtonProps) => { + if (hideMax) return null + return ( + + ) +} +const HalfButton = ({ + disabled, + onClick, + hideHalf = false, +}: HalfButtonProps) => { + if (hideHalf) return null + return ( + + ) +} + +type TokenToPriceProps = { + numberOfTokens: string + dollarValue: number | string + hide?: boolean +} +const TokenToPrice = ({ + numberOfTokens, + dollarValue, + hide = false, +}: TokenToPriceProps) => { + if (hide) return + + return ( + + + {numberOfTokens}= + + + ${dollarValue} + + + ) +} + +type BalanceWithMaxProps = { + balance: number | string + maxDisabled?: boolean + numberOfTokens: string + dollarValue: number | string + onMaxClick: () => void + onHalfClick: () => void + hideMax?: boolean + hideDollarValue?: boolean +} + +const BalanceWithMaxNHalf = ({ + balance, + maxDisabled, + numberOfTokens, + dollarValue, + onMaxClick, + onHalfClick, + hideMax, + hideDollarValue, +}: BalanceWithMaxProps) => { + return ( + + + + + + + ) +} + +export default BalanceWithMaxNHalf diff --git a/components/AssetInput/WhaleInput.tsx b/components/AssetInput/WhaleInput.tsx new file mode 100644 index 00000000..b01da6ef --- /dev/null +++ b/components/AssetInput/WhaleInput.tsx @@ -0,0 +1,178 @@ +import { FC } from 'react' + +import { ChevronDownIcon } from '@chakra-ui/icons' +import { + forwardRef, + HStack, + IconButton, + Image, + Input, + Stack, + Text, +} from '@chakra-ui/react' +import FallbackImage from 'components/FallbackImage' +import { useMultipleTokenBalance } from 'hooks/useTokenBalance' +import { useTokenInfo } from 'hooks/useTokenInfo' +import { useTokenList } from 'hooks/useTokenList' + +import AssetSelectModal from './AssetSelectModal' + +interface AssetInputProps { + image?: boolean + token: any + value: any + onChange: (value: any, isTokenChange?: boolean) => void + showList?: boolean + onInputFocus?: () => void + balance?: number + disabled?: boolean + minMax?: boolean + isSingleInput?: boolean + hideToken?: string + edgeTokenList?: string[] + ignoreSlack?: boolean + isBonding?: boolean + unbondingBalances?: { [key: string]: number } +} + +const AssetSelectTrigger = ({ tokenInfo, showIcon, symbol }) => { + const formatSymbol = symbol?.replace('-', '/') + + // if (!tokenInfo && !symbol) return null + + if (!tokenInfo?.symbol && !symbol) + return ( + + Select Token + + ) + + return ( + + {showIcon && ( + logo-small} + /> + )} + + {tokenInfo?.symbol || formatSymbol || 'Select Token'} + + + ) +} + +const AssetInput: FC = forwardRef( + ( + { + image = true, + token, + balance, + onChange, + value, + showList = true, + disabled, + minMax = true, + isSingleInput = false, + hideToken, + edgeTokenList, + ignoreSlack = false, + isBonding = false, + unbondingBalances = null, + }, + ref + ) => { + const tokenInfo = useTokenInfo(token?.tokenSymbol) + + const [tokenList] = useTokenList() + useMultipleTokenBalance(tokenList?.tokens?.map(({ symbol }) => symbol)) + + return ( + + + + { + console.log({ ...token, amount: target.value }) + onChange({ ...token, amount: target.value }) + }} + /> + + + + + + + + {showList && ( + } + style={{ margin: 'unset' }} + /> + )} + + + + + ) + } +) + +export default AssetInput diff --git a/components/Finder.tsx b/components/Finder.tsx index 687262c7..d0cbaf25 100644 --- a/components/Finder.tsx +++ b/components/Finder.tsx @@ -14,16 +14,16 @@ const getUrl = (chainId, txHash) => { switch (chainId) { case 'uni-3': return `https://testnet.mintscan.io/juno-testnet/txs/${txHash}` - break + case 'migaloo-1': + return `https://explorer.silknodes.io/migaloo/tx/${txHash}` + case 'narwhal-1': + return `https://testnet.mintscan.io/juno-testnet/txs/${txHash}` case 'pisco-1': return `https://finder.terra.money/testnet/tx/${txHash}` - break case 'juno-1': return `https://mintscan.io/juno/txs/${txHash}` - break case 'phoenix-1': return `https://finder.terra.money/mainnet/tx/${txHash}` - break case 'chihuahua-1': return `https://www.mintscan.io/chihuahua/txs/${txHash}` case 'injective-888': @@ -34,7 +34,6 @@ const getUrl = (chainId, txHash) => { return `https://mintscan.io/comdex/txs/${txHash}` default: return null - break } } diff --git a/components/Layout/AppLayout.tsx b/components/Layout/AppLayout.tsx index 43e59fcb..f70a397c 100644 --- a/components/Layout/AppLayout.tsx +++ b/components/Layout/AppLayout.tsx @@ -1,10 +1,5 @@ import { FC, ReactNode } from 'react' -import { - BrowserView, - isBrowser, - isMobile, - MobileView, -} from 'react-device-detect' +import { isMobile } from 'react-device-detect' import { Flex, useMediaQuery } from '@chakra-ui/react' import Navbar from 'components/Navbar' @@ -15,27 +10,14 @@ import Status from '../Status' import MobileNotSupportedModal from '../Wallet/Modal/MobileNotSupportedModal' import RadialGradient from './RadialGradient' -// import { useRouter } from "next/router"; - const AppLayout: FC = ({ children }) => { const { chainId } = useRecoilValue(walletState) const [isMobileView] = useMediaQuery('(max-width: 480px)') - // const router = useRouter() - - // useEffect(() => { - // router.replace("/swap") - // },[chainId]) - return ( <> {(isMobile || isMobileView) && } - + { diff --git a/components/Navbar/NavBondingDisabledMenu.json b/components/Navbar/NavBondingDisabledMenu.json new file mode 100644 index 00000000..426124e7 --- /dev/null +++ b/components/Navbar/NavBondingDisabledMenu.json @@ -0,0 +1,35 @@ +[ + { + "label": "Trade", + "isExternal" : false, + "children": [ + { + "label": "Swap", + "link": "/swap" + }, + { + "label": "Pools", + "link": "/pools" + } + ] + }, + { + "label": "Flashloan", + "isExternal" : false, + "children": [ + { + "label": "Flashloan", + "link": "/flashloan" + }, + { + "label": "Vaults", + "link": "/vaults" + } + ] + }, + { + "label": "Bridge", + "isExternal" : true, + "link": "https://tfm.com/bridge" + } +] diff --git a/components/Navbar/NavMenu.json b/components/Navbar/NavMenu.json index 0148c0ec..c46a461b 100644 --- a/components/Navbar/NavMenu.json +++ b/components/Navbar/NavMenu.json @@ -1,7 +1,13 @@ [ + { + "label": "Dashboard", + "isExternal" : false, + "link": "/dashboard" + }, { "label": "Trade", - "childs": [ + "isExternal" : false, + "children": [ { "label": "Swap", "link": "/swap" @@ -14,7 +20,8 @@ }, { "label": "Flashloan", - "childs": [ + "isExternal" : false, + "children": [ { "label": "Flashloan", "link": "/flashloan" @@ -26,7 +33,8 @@ ] }, { - "label": "Bridge", + "label": "Bridge", + "isExternal" : true, "link": "https://tfm.com/bridge" } ] diff --git a/components/Navbar/Navbar.tsx b/components/Navbar/Navbar.tsx index d0a84212..7ed2a11d 100644 --- a/components/Navbar/Navbar.tsx +++ b/components/Navbar/Navbar.tsx @@ -19,7 +19,6 @@ import { useChains } from 'hooks/useChainInfo' import { useRecoilState } from 'recoil' import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' - import Card from '../Card' import WalletModal from '../Wallet/Modal/Modal' import Wallet from '../Wallet/Wallet' @@ -27,6 +26,8 @@ import DrawerLink from './DrawerLink' import Logo from './Logo' import NavbarPopper from './NavbarPopper' import menuLinks from './NavMenu.json' +import bondingDisabledMenuLinks from './NavBondingDisabledMenu.json' +import { BONDING_ENABLED_CHAIN_IDS } from 'constants/bonding_contract' export const links = [ { @@ -45,19 +46,19 @@ export const links = [ label: 'Vaults', link: '/vaults', }, + { + label: 'Dashboard', + link: '/dashboard', + }, { label: 'Bridge', - link: 'https://tfm.com/bridge' + link: 'https://tfm.com/bridge', }, - // { - // label: "Chart", - // link: "/chart" - // }, ] - -const Navbar = ({ }) => { +const Navbar = ({}) => { const { disconnect } = useWallet() - const [{ key, chainId, network }, setWalletState] = useRecoilState(walletState) + const [{ key, chainId, network }, setWalletState] = + useRecoilState(walletState) const chains: Array = useChains() const { @@ -80,8 +81,8 @@ const Navbar = ({ }) => { disconnect() } - const currenChain = chains.find((row) => row.chainId === chainId) - const currentChainName = currenChain?.label.toLowerCase() + const currentChain = chains.find((row) => row.chainId === chainId) + const currentChainName = currentChain?.label.toLowerCase() return ( @@ -95,8 +96,16 @@ const Navbar = ({ }) => { - - {menuLinks.map((menu)=> ( + + {(BONDING_ENABLED_CHAIN_IDS.includes(chainId) + ? menuLinks + : bondingDisabledMenuLinks + ).map((menu) => ( + ))} @@ -108,6 +117,7 @@ const Navbar = ({ }) => { isOpenModal={isOpenModal} onOpenModal={onOpenModal} onCloseModal={onCloseModal} + onPrimaryButton={false} /> { const { onOpen, onClose, isOpen } = useDisclosure() const firstFieldRef = React.useRef(null) - const numberOfLinks = menu.childs?.length + const numberOfLinks = menu.children?.length const { asPath } = useRouter() const isActiveLink = useMemo(() => { - const [linkInAsPath] = (menu.childs ?? []).filter((item: { link: string }) => asPath.includes(item.link)) + // children defining sub menu items + const [linkInAsPath] = + menu?.children === undefined + ? [asPath.includes(menu.link)] + : (menu.children ?? []).filter((item: { link: string }) => + asPath.includes(item.link) + ) return !!linkInAsPath - },[asPath, menu]) + }, [asPath, menu]) - const openLink = (url:string): (() => void) => { + const openLink = (url: string): (() => void) => { return () => { - window.open(url, '_blank'); - }; - }; + window.open(url, '_blank') + } + } return ( window.location.assign(`/${currentChainName}${menu.link}`) + : onOpen + } onClose={onClose} > - {menu.label} + + {menu.label} + @@ -40,23 +69,26 @@ const NavbarPopper = ({ menu, currentChainName }) => { borderRadius="25px" backgroundColor="#1C1C1C" width="auto" - > - - - {menu.childs?.map(({ label, link }, index: number) => ( + + + {menu.children?.map(({ label, link }, index: number) => ( { href={`/${currentChainName}${link}`} /> - ))} diff --git a/components/Pages/BondingActions/Bond.tsx b/components/Pages/BondingActions/Bond.tsx new file mode 100644 index 00000000..71d6067d --- /dev/null +++ b/components/Pages/BondingActions/Bond.tsx @@ -0,0 +1,122 @@ +import React, { useEffect, useState } from 'react' +import { VStack } from '@chakra-ui/react' +import AssetInput from '../../AssetInput' +import { useRecoilState } from 'recoil' +import { bondingAtom } from './bondAtoms' +import { Controller, useForm } from 'react-hook-form' +import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' +import { AMP_WHALE_TOKEN_SYMBOL } from 'constants/bonding_contract' + +export interface LSDTokenBalances { + ampWHALE: number + bWHALE: number +} +export interface LSDTokenItemState { + tokenSymbol: string + amount: number + decimals: number + lsdToken: LSDToken +} +export enum LSDToken { + ampWHALE, + bWHALE, +} +const Bond = ({ liquidAmpWhale, liquidBWhale, whalePrice }) => { + const [currentBondState, setCurrentBondState] = + useRecoilState(bondingAtom) + const [{ status }, _] = useRecoilState(walletState) + + const isWalletConnected = status === WalletStatusType.connected + + const [tokenBalances, setLSDTokenBalances] = useState(null) + + useEffect(() => { + const newBalances: LSDTokenBalances = { + ampWHALE: liquidAmpWhale, + bWHALE: liquidBWhale, + } + setLSDTokenBalances(newBalances) + }, [liquidAmpWhale, liquidBWhale]) + + const onInputChange = (tokenSymbol: string | null, amount: number) => { + if (tokenSymbol) { + setCurrentBondState({ + ...currentBondState, + tokenSymbol: tokenSymbol, + amount: Number(amount), + }) + } else { + setCurrentBondState({ ...currentBondState, amount: Number(amount) }) + } + } + + useEffect(() => { + setCurrentBondState({ + tokenSymbol: AMP_WHALE_TOKEN_SYMBOL, + amount: 0, + decimals: 6, + lsdToken: LSDToken.ampWHALE, + }) + }, [isWalletConnected]) + + const { control } = useForm({ + mode: 'onChange', + defaultValues: { + currentBondState, + }, + }) + + return ( + + ( + { + switch (currentBondState.lsdToken) { + case LSDToken.ampWHALE: + return tokenBalances?.ampWHALE ?? 0 + case LSDToken.bWHALE: + return tokenBalances?.bWHALE ?? 0 + default: + return 0 + } + })()} + minMax={false} + disabled={false} + onChange={(value, isTokenChange) => { + onInputChange(value, 0) + field.onChange(value) + if (isTokenChange) { + let lsdToken = + value.tokenSymbol === AMP_WHALE_TOKEN_SYMBOL + ? LSDToken.ampWHALE + : LSDToken.bWHALE + setCurrentBondState({ + ...currentBondState, + tokenSymbol: value.tokenSymbol, + amount: value.amount, + lsdToken: lsdToken, + }) + } else { + setCurrentBondState({ + ...currentBondState, + amount: value.amount, + }) + } + }} + /> + )} + /> + + ) +} + +export default Bond diff --git a/components/Pages/BondingActions/BondingAcionTooltip.tsx b/components/Pages/BondingActions/BondingAcionTooltip.tsx new file mode 100644 index 00000000..de109d7a --- /dev/null +++ b/components/Pages/BondingActions/BondingAcionTooltip.tsx @@ -0,0 +1,50 @@ +import { InfoOutlineIcon } from '@chakra-ui/icons' +import { Box, Icon, Tooltip } from '@chakra-ui/react' +import { ActionType } from 'components/Pages/Dashboard/BondingOverview' + +const bondingActionText = (action: ActionType) => { + switch (action) { + case ActionType.bond: + return 'Bond tokens to earn rewards. Tokens are locked up.' + case ActionType.unbond: + return 'Unbonding duration of 14 days, no rewards during that period. Unable to stop or abort the process once initiated.' + case ActionType.withdraw: + return 'Withdrawing of tokens once unbonded.' + case ActionType.claim: + return ( + 'Unstable APR (depending on share, trading/flashloan volume, amp/bWHALE relation to WHALE etc.).\n' + + 'Fee passed forward after 21 days of not claiming, i.e. others can claim it.\nTime dependent multiplier, i.e. the longer you bond the more you get.' + ) + } +} + +export const BondingActionTooltip = ({ action }) => ( + + {bondingActionText(action)} + + } + bg="transparent" + hasArrow={false} + placement="bottom" + closeOnClick={false} + arrowSize={0} + > + + +) diff --git a/components/Pages/BondingActions/BondingActions.tsx b/components/Pages/BondingActions/BondingActions.tsx new file mode 100644 index 00000000..b6462b0d --- /dev/null +++ b/components/Pages/BondingActions/BondingActions.tsx @@ -0,0 +1,306 @@ +import { ArrowBackIcon } from '@chakra-ui/icons' +import { + Box, + Button, + HStack, + IconButton, + Text, + useDisclosure, + VStack, +} from '@chakra-ui/react' +import { useRecoilState } from 'recoil' +import { useChains } from 'hooks/useChainInfo' +import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' +import { ActionType } from '../Dashboard/BondingOverview' +import Bond, { LSDTokenItemState } from './Bond' +import Unbond from './Unbond' +import Withdraw from './Withdraw' +import { useRouter } from 'next/router' + +import { bondingAtom } from './bondAtoms' +import React, { useMemo } from 'react' +import WalletModal from '../../Wallet/Modal/Modal' +import useTransaction, { TxStep } from './hooks/useTransaction' +import { + AMP_WHALE_TOKEN_SYMBOL, + B_WHALE_TOKEN_SYMBOL, +} from 'constants/bonding_contract' +import Loader from '../../Loader' +import { useTokenBalance } from 'hooks/useTokenBalance' +import { + Config, + useConfig, + useDashboardData, +} from '../Dashboard/hooks/useDashboardData' +import { usePriceForOneToken } from 'features/swap/index' +import { BondingActionTooltip } from 'components/Pages/BondingActions/BondingAcionTooltip' + +export enum WhaleTokenType { + ampWHALE, + bWHALE, +} + +const BondingActions = ({ globalAction }) => { + const [{ chainId, client, address, status, network }, _] = + useRecoilState(walletState) + const isWalletConnected: boolean = status === WalletStatusType.connected + const chains: Array = useChains() + const currentChain = chains.find( + (row: { chainId: string }) => row.chainId === chainId + ) + const currentChainName = currentChain?.label.toLowerCase() + const { + isOpen: isOpenModal, + onOpen: onOpenModal, + onClose: onCloseModal, + } = useDisclosure() + + const router = useRouter() + + const { txStep, submit } = useTransaction() + + const [currentBondState, setCurrentBondState] = + useRecoilState(bondingAtom) + + const { balance: liquidAmpWhale } = useTokenBalance(AMP_WHALE_TOKEN_SYMBOL) + + const { balance: liquidBWhale } = useTokenBalance(B_WHALE_TOKEN_SYMBOL) + + const whalePrice = + usePriceForOneToken({ + tokenASymbol: 'WHALE', + tokenBSymbol: 'axlUSDC', + })[0] || 0 + + const { + bondedAmpWhale, + bondedBWhale, + unbondingAmpWhale, + unbondingBWhale, + withdrawableAmpWhale, + withdrawableBWhale, + bondingConfig, + filteredUnbondingRequests, + isLoading, + } = useDashboardData(client, address, network, chainId) + + const unbondingPeriodInNano = Number(bondingConfig?.unbonding_period) + const totalWithdrawable = withdrawableAmpWhale + withdrawableBWhale + + const buttonLabel = useMemo(() => { + if (!isWalletConnected) return 'Connect Wallet' + else if ( + currentBondState?.amount === 0 && + globalAction !== ActionType.withdraw + ) + return 'Enter Amount' + else if (totalWithdrawable === 0 && globalAction === ActionType.withdraw) + return 'No Withdrawals' + else return ActionType[globalAction] + }, [isWalletConnected, currentBondState, globalAction, totalWithdrawable]) + + const BondingActionButton = ({ action }) => { + const actionString = ActionType[action].toString() + const onClick = async () => { + setCurrentBondState({ ...currentBondState, amount: 0 }) + await router.push(`/${currentChainName}/dashboard/${actionString}`) + } + + return ( + + ) + } + const config: Config = useConfig(network, chainId) + + return ( + + + } + onClick={async () => { + await router.push(`/${currentChainName}/dashboard`) + setCurrentBondState({ ...currentBondState, amount: 0 }) + }} + /> + + + + {ActionType[globalAction]} + + + + ( + {isLoading && isWalletConnected ? ( + + + + + + ) : ( + + + + + + + + {(() => { + switch (globalAction) { + case ActionType.bond: + return ( + + ) + case ActionType.unbond: + return ( + + ) + case ActionType.withdraw: + return ( + + ) + } + })()} + + + + + )} + ) + + ) +} + +export default BondingActions diff --git a/components/Pages/BondingActions/Unbond.tsx b/components/Pages/BondingActions/Unbond.tsx new file mode 100644 index 00000000..b53552f4 --- /dev/null +++ b/components/Pages/BondingActions/Unbond.tsx @@ -0,0 +1,117 @@ +import React, { useEffect, useState, useCallback, useMemo } from 'react' +import { VStack } from '@chakra-ui/react' +import AssetInput from '../../AssetInput' +import { useRecoilState } from 'recoil' +import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' +import { Controller, useForm } from 'react-hook-form' +import { AMP_WHALE_TOKEN_SYMBOL } from 'constants/bonding_contract' +import { LSDToken, LSDTokenBalances, LSDTokenItemState } from './Bond' +import { bondingAtom } from './bondAtoms' + +const Unbond = ({ bondedAmpWhale, bondedBWhale, whalePrice }) => { + const [{ status }] = useRecoilState(walletState) + const [currentBondState, setCurrentBondState] = + useRecoilState(bondingAtom) + + const isWalletConnected = status === WalletStatusType.connected + + const [tokenBalances, setLSDTokenBalances] = useState(null) + + useEffect(() => { + setLSDTokenBalances({ + ampWHALE: bondedAmpWhale, + bWHALE: bondedBWhale, + }) + }, [bondedAmpWhale, bondedBWhale]) + + const onInputChange = useCallback( + (tokenSymbol: string | null, amount: number) => { + if (tokenSymbol) { + setCurrentBondState({ + ...currentBondState, + tokenSymbol: tokenSymbol, + amount: Number(amount), + }) + } else { + setCurrentBondState({ ...currentBondState, amount: Number(amount) }) + } + }, + [currentBondState] + ) + + useEffect(() => { + setCurrentBondState({ + tokenSymbol: AMP_WHALE_TOKEN_SYMBOL, + amount: 0, + decimals: 6, + lsdToken: LSDToken.ampWHALE, + }) + }, [isWalletConnected]) + + const { control } = useForm({ + mode: 'onChange', + defaultValues: { + currentBondState, + }, + }) + + const unbondingBalances = useMemo( + () => ({ ampWHALE: bondedAmpWhale, bWHALE: bondedBWhale }), + [bondedAmpWhale, bondedBWhale] + ) + + return ( + + ( + { + switch (currentBondState.lsdToken) { + case LSDToken.ampWHALE: + return tokenBalances?.ampWHALE ?? 0 + case LSDToken.bWHALE: + return tokenBalances?.bWHALE ?? 0 + default: + return 0 // or any other default value + } + })()} + minMax={false} + disabled={false} + onChange={(value, isTokenChange) => { + onInputChange(value, 0) + field.onChange(value) + if (isTokenChange) { + let lsdToken = + value.tokenSymbol === AMP_WHALE_TOKEN_SYMBOL + ? LSDToken.ampWHALE + : LSDToken.bWHALE + setCurrentBondState({ + ...currentBondState, + tokenSymbol: value.tokenSymbol, + amount: value.amount, + lsdToken: lsdToken, + }) + } else { + setCurrentBondState({ + ...currentBondState, + amount: value.amount, + }) + } + }} + /> + )} + /> + + ) +} + +export default Unbond diff --git a/components/Pages/BondingActions/Withdraw.tsx b/components/Pages/BondingActions/Withdraw.tsx new file mode 100644 index 00000000..dc4c3393 --- /dev/null +++ b/components/Pages/BondingActions/Withdraw.tsx @@ -0,0 +1,151 @@ +import { Box, HStack, Text, VStack } from '@chakra-ui/react' +import { WhaleTokenType } from './BondingActions' +import { useRecoilState } from 'recoil' +import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' +import { + calculateDurationString, + convertMicroDenomToDenom, + nanoToMilli, +} from 'util/conversion' +import { WhaleTooltip } from '../Dashboard/WhaleTooltip' +import { + Config, + useConfig, +} from 'components/Pages/Dashboard/hooks/useDashboardData' + +const Withdraw = ({ + unbondingAmpWhale, + unbondingBWhale, + withdrawableAmpWhale, + withdrawableBWhale, + filteredUnbondingRequests, + unbondingPeriodInNano, + whalePrice, +}) => { + const [{ status, chainId, network }, _] = useRecoilState(walletState) + + const isWalletConnected = status === WalletStatusType.connected + const config: Config = useConfig(network, chainId) + const ProgressBar = ({ percent }) => { + return ( + + + + ) + } + + const TokenBox = ({ label, ampWhale, bWhale }) => { + const dollarValue = ((ampWhale + bWhale) * whalePrice).toLocaleString() + return ( + + + + {isWalletConnected ? `$${dollarValue}` : 'n/a'} + + + ) + } + + const BoxComponent = ({ + whaleTokenType, + value, + timeUntilUnbondingInMilli, + }) => { + const durationString = calculateDurationString(timeUntilUnbondingInMilli) + return ( + + + + {value.toLocaleString()} {WhaleTokenType[whaleTokenType]} + + + ~ {durationString} + + + + + ) + } + + return ( + + + + + + {isWalletConnected && + filteredUnbondingRequests !== null && + filteredUnbondingRequests?.length > 0 && ( + + {filteredUnbondingRequests.map((type, index) => { + const currentTimeInMilli = Date.now() + const timeUntilUnbondingInMilli = + nanoToMilli(Number(type.timestamp) + unbondingPeriodInNano) - + currentTimeInMilli + return ( + + ) + })} + + )} + + ) +} + +export default Withdraw diff --git a/components/Pages/BondingActions/bondAtoms.ts b/components/Pages/BondingActions/bondAtoms.ts new file mode 100644 index 00000000..74ca6a60 --- /dev/null +++ b/components/Pages/BondingActions/bondAtoms.ts @@ -0,0 +1,13 @@ +import { atom } from 'recoil' +import { LSDTokenItemState } from './Bond' + +export const bondingAtom = atom({ + key: 'bondingToken', + default: { + tokenSymbol: null, + amount: 0, + decimals: 6, + lsdToken: null, + }, + effects_UNSTABLE: [], +}) diff --git a/components/Pages/BondingActions/hooks/bondTokens.ts b/components/Pages/BondingActions/hooks/bondTokens.ts new file mode 100644 index 00000000..f2a8bde3 --- /dev/null +++ b/components/Pages/BondingActions/hooks/bondTokens.ts @@ -0,0 +1,27 @@ +import { Wallet } from 'util/wallet-adapters' +import { coin } from '@cosmjs/stargate' +import { Config } from 'components/Pages/Dashboard/hooks/useDashboardData' + +export const bondTokens = async ( + client: Wallet, + address: string, + amount: number, + denom: string, + config: Config +) => { + const handleMsg = { + bond: { + asset: { + amount: amount.toString(), + info: { + native_token: { + denom: denom, + }, + }, + }, + }, + } + return client.execute(address, config.whale_lair_address, handleMsg, [ + coin(amount, denom), + ]) +} diff --git a/components/Pages/BondingActions/hooks/unbondTokens.ts b/components/Pages/BondingActions/hooks/unbondTokens.ts new file mode 100644 index 00000000..3e0e5284 --- /dev/null +++ b/components/Pages/BondingActions/hooks/unbondTokens.ts @@ -0,0 +1,24 @@ +import { Wallet } from 'util/wallet-adapters' +import { Config } from 'components/Pages/Dashboard/hooks/useDashboardData' + +export const unbondTokens = ( + client: Wallet, + address: string, + amount: number, + denom: string, + config: Config +) => { + const handleMsg = { + unbond: { + asset: { + amount: amount.toString(), + info: { + native_token: { + denom: denom, + }, + }, + }, + }, + } + return client.execute(address, config.whale_lair_address, handleMsg) +} diff --git a/components/Pages/BondingActions/hooks/useTransaction.tsx b/components/Pages/BondingActions/hooks/useTransaction.tsx new file mode 100644 index 00000000..203aaf70 --- /dev/null +++ b/components/Pages/BondingActions/hooks/useTransaction.tsx @@ -0,0 +1,308 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useMutation, useQuery } from 'react-query' + +import { useToast } from '@chakra-ui/react' +import Finder from 'components/Finder' +import { useRecoilValue } from 'recoil' +import { walletState } from 'state/atoms/walletAtoms' +import { ActionType } from '../../Dashboard/BondingOverview' +import { bondTokens } from './bondTokens' +import { unbondTokens } from './unbondTokens' +import { withdrawTokens } from './withdrawTokens' +import { convertDenomToMicroDenom } from 'util/conversion' +import { claimRewards } from '../../Dashboard/hooks/claimRewards' +import { + Config, + useConfig, +} from 'components/Pages/Dashboard/hooks/useDashboardData' + +export enum TxStep { + /** + * Idle + */ + Idle = 0, + /** + * Estimating fees + */ + Estimating = 1, + /** + * Ready to post transaction + */ + Ready = 2, + /** + * Signing transaction in Terra Station + */ + Posting = 3, + /** + * Broadcasting + */ + Broadcasting = 4, + /** + * Successful + */ + Successful = 5, + /** + * Failed + */ + Failed = 6, +} +export const useTransaction = () => { + const toast = useToast() + const { + chainId, + client, + address: senderAddress, + network, + } = useRecoilValue(walletState) + const [txStep, setTxStep] = useState(TxStep.Idle) + const [bondingAction, setBondingAction] = useState(null) + const [txHash, setTxHash] = useState(undefined) + const [error, setError] = useState(null) + const [buttonLabel, setButtonLabel] = useState(null) + const config: Config = useConfig(network, chainId) + + const { data: fee } = useQuery( + ['fee', error], + async () => { + setError(null) + setTxStep(TxStep.Estimating) + try { + const response = 0 //await client.simulate(senderAddress, debouncedMsgs, '') + if (!!buttonLabel) setButtonLabel(null) + setTxStep(TxStep.Ready) + return response + } catch (error) { + if ( + /insufficient funds/i.test(error.toString()) || + /Overflow: Cannot Sub with/i.test(error.toString()) + ) { + console.error(error) + setTxStep(TxStep.Idle) + setError('Insufficient Funds') + setButtonLabel('Insufficient Funds') + throw new Error('Insufficient Funds') + } else if (/account sequence mismatch/i.test(error?.toString())) { + setError('You have pending transaction') + setButtonLabel('You have pending transaction') + throw new Error('You have pending transaction') + } else { + console.error({ error }) + setTxStep(TxStep.Idle) + setError(error?.message) + throw Error(error?.message) + // setTxStep(TxStep.Idle) + // setError("Failed to execute transaction.") + // throw Error("Failed to execute transaction.") + } + } + }, + { + enabled: + txStep == TxStep.Idle && error == null && !!client && !!senderAddress, + refetchOnWindowFocus: false, + retry: false, + staleTime: 0, + onSuccess: () => { + setTxStep(TxStep.Ready) + }, + onError: () => { + setTxStep(TxStep.Idle) + }, + } + ) + + const { mutate } = useMutation( + (data: any) => { + const adjustedAmount = convertDenomToMicroDenom(data.amount, 6) + if (data.bondingAction === ActionType.bond) { + return bondTokens( + client, + senderAddress, + adjustedAmount, + data.denom, + config + ) + } else if (data.bondingAction === ActionType.unbond) { + return unbondTokens( + client, + senderAddress, + adjustedAmount, + data.denom, + config + ) + } else if (data.bondingAction === ActionType.withdraw) { + return withdrawTokens(client, senderAddress, data.denom, config) + } else { + return claimRewards(client, senderAddress, config) + } + }, + { + onMutate: () => { + setTxStep(TxStep.Posting) + }, + onError: (e) => { + let message: any = '' + console.error(e?.toString()) + setTxStep(TxStep.Failed) + if ( + /insufficient funds/i.test(e?.toString()) || + /Overflow: Cannot Sub with/i.test(e?.toString()) + ) { + setError('Insufficient Funds') + message = 'Insufficient Funds' + } else if (/Request rejected/i.test(e?.toString())) { + setError('User Denied') + message = 'User Denied' + } else if (/account sequence mismatch/i.test(e?.toString())) { + setError('You have pending transaction') + message = 'You have pending transaction' + } else if (/out of gas/i.test(e?.toString())) { + setError('Out of gas, try increasing gas limit on wallet.') + message = 'Out of gas, try increasing gas limit on wallet.' + } else if ( + /was submitted but was not yet found on the chain/i.test( + e?.toString() + ) + ) { + setError(e?.toString()) + message = ( + + {' '} + + ) + } else { + setError('Failed to execute transaction.') + message = 'Failed to execute transaction.' + } + + toast({ + title: (() => { + switch (bondingAction) { + case ActionType.bond: + return 'Bonding Failed.' + case ActionType.unbond: + return 'Unbonding Failed' + case ActionType.withdraw: + return 'Withdrawing Failed.' + case ActionType.claim: + return 'Claiming Failed.' + default: + return '' + } + })(), + description: message, + status: 'error', + duration: 9000, + position: 'top-right', + isClosable: true, + }) + }, + onSuccess: (data: any) => { + setTxStep(TxStep.Broadcasting) + setTxHash(data?.transactionHash) + toast({ + title: (() => { + switch (bondingAction) { + case ActionType.bond: + return 'Bonding Successful.' + case ActionType.unbond: + return 'Unbonding Successful.' + case ActionType.withdraw: + return 'Withdrawing Successful.' + case ActionType.claim: + return 'Claiming Successful.' + default: + return '' + } + })(), + description: ( + + {' '} + + ), + status: 'success', + duration: 9000, + position: 'top-right', + isClosable: true, + }) + }, + } + ) + + const { data: txInfo } = useQuery( + ['txInfo', txHash], + () => { + if (txHash == null) { + return + } + return client.getTx(txHash) + }, + { + enabled: txHash != null, + retry: true, + } + ) + + const reset = () => { + setError(null) + setTxHash(undefined) + setTxStep(TxStep.Idle) + } + + const submit = useCallback( + async ( + bondingAction: ActionType, + amount: number | null, + denom: string | null + ) => { + if (fee == null) { + return + } + await setBondingAction(bondingAction) + + mutate({ + fee, + bondingAction, + denom, + amount, + }) + }, + [fee, mutate] + ) + + useEffect(() => { + if (txInfo != null && txHash != null) { + if (txInfo?.code) { + setTxStep(TxStep.Failed) + } else { + setTxStep(TxStep.Successful) + } + } + }, [txInfo, txHash, error]) + + useEffect(() => { + if (error) { + setError(null) + } + + if (txStep != TxStep.Idle) { + setTxStep(TxStep.Idle) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) //debouncedMsgs + + return useMemo(() => { + return { + fee, + buttonLabel, + submit, + txStep, + txInfo, + txHash, + error, + reset, + } + }, [txStep, txInfo, txHash, error, reset, fee]) +} + +export default useTransaction diff --git a/components/Pages/BondingActions/hooks/withdrawTokens.ts b/components/Pages/BondingActions/hooks/withdrawTokens.ts new file mode 100644 index 00000000..2d4add8d --- /dev/null +++ b/components/Pages/BondingActions/hooks/withdrawTokens.ts @@ -0,0 +1,16 @@ +import { Wallet } from 'util/wallet-adapters' +import { Config } from 'components/Pages/Dashboard/hooks/useDashboardData' + +export const withdrawTokens = ( + client: Wallet, + address: string, + denom: string, + config: Config +) => { + const handleMsg = { + withdraw: { + denom: denom, + }, + } + return client.execute(address, config.whale_lair_address, handleMsg) +} diff --git a/components/Pages/BondingActions/index.ts b/components/Pages/BondingActions/index.ts new file mode 100644 index 00000000..346c218b --- /dev/null +++ b/components/Pages/BondingActions/index.ts @@ -0,0 +1 @@ +export { default } from './BondingActions' diff --git a/components/Pages/Dashboard/BondingOverview.tsx b/components/Pages/Dashboard/BondingOverview.tsx new file mode 100644 index 00000000..dd6adc97 --- /dev/null +++ b/components/Pages/Dashboard/BondingOverview.tsx @@ -0,0 +1,189 @@ +import { Box, Button, HStack, Text, VStack } from '@chakra-ui/react' + +import { Cell, Pie, PieChart } from 'recharts' + +import Loader from '../../Loader' +import { useRouter } from 'next/router' +import { WhaleTooltip } from './WhaleTooltip' + +export enum TokenType { + bonded, + liquid, + unbonding, + withdrawable, +} + +export enum ActionType { + buy, + bond, + unbond, + withdraw, + claim, +} + +export enum WhaleType { + ampWHALE, + bWHALE, + WHALE, +} + +const BondingOverview = ({ + isWalletConnected, + isLoading, + data, + whalePrice, + currentChainName, +}) => { + const borderRadius = '30px' + const router = useRouter() + const TokenBox = ({ tokenType }) => { + const { color, label } = data.find((e) => e.tokenType == tokenType) + + return ( + + + + + ) + } + + let aggregatedAssets = data?.reduce((acc, e) => acc + (e?.value ?? 0), 0) + + return ( + + {isLoading ? ( + + + + ) : ( + + + + {isWalletConnected ? ( + data?.map((_entry: any, index: number) => ( + + )) + ) : ( + + )} + + + + + Tokens + + {data?.map((e) => ( + + ))} + + + + {`Value($${(aggregatedAssets * Number(whalePrice)).toFixed(2)})`} + + {/*value equals the amount of the specific token type (liquid, bonded, unbonding, withdrawable)*/} + {data?.map( + (e: { + value: number | string + actionType: ActionType + tokenType: TokenType + }) => { + return ( + + ) + } + )} + + + + Actions + + {data?.map((e: { actionType: ActionType }) => ( + + ))} + + + )} + + ) +} +export default BondingOverview diff --git a/components/Pages/Dashboard/Dashboard.tsx b/components/Pages/Dashboard/Dashboard.tsx new file mode 100644 index 00000000..bb2184a5 --- /dev/null +++ b/components/Pages/Dashboard/Dashboard.tsx @@ -0,0 +1,186 @@ +import { FC, useEffect, useState } from 'react' +import { Flex, HStack, Text, VStack } from '@chakra-ui/react' +import BondingOverview, { ActionType, TokenType } from './BondingOverview' +import RewardsComponent from './RewardsComponent' +import { useRecoilState } from 'recoil' +import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' +import { BondingData } from './types/BondingData' +import { useTokenBalance } from 'hooks/useTokenBalance' +import { useChains } from 'hooks/useChainInfo' +import { useDashboardData } from './hooks/useDashboardData' +import { + AMP_WHALE_TOKEN_SYMBOL, + B_WHALE_TOKEN_SYMBOL, + WHALE_TOKEN_SYMBOL, +} from 'constants/bonding_contract' +import { usePriceForOneToken } from 'features/swap/index' + +const Dashboard: FC = () => { + const [{ chainId, status, client, address, network }] = + useRecoilState(walletState) + const isWalletConnected: boolean = status === WalletStatusType.connected + const chains: Array = useChains() + const currentChain = chains.find( + (row: { chainId: string }) => row.chainId === chainId + ) + const currentChainName = currentChain?.label.toLowerCase() + + const data: BondingData[] = [ + { + tokenType: TokenType.liquid, + value: null, + whale: null, + ampWhale: null, + bWhale: null, + color: '#244228', + label: 'Liquid', + actionType: ActionType.buy, + }, + { + tokenType: TokenType.bonded, + value: null, + whale: null, + ampWhale: null, + bWhale: null, + color: '#7CFB7D', + label: 'Bonded', + actionType: ActionType.bond, + }, + { + tokenType: TokenType.unbonding, + value: null, + whale: null, + ampWhale: null, + bWhale: null, + color: '#3273F6', + label: 'Unbonding', + actionType: ActionType.unbond, + }, + { + tokenType: TokenType.withdrawable, + value: null, + whale: null, + ampWhale: null, + bWhale: null, + color: '#173E84', + label: 'Withdrawable', + actionType: ActionType.withdraw, + }, + ] + + const [updatedData, setData] = useState(null) + + const setValues = ( + tokenType: TokenType, + value: number, + whale: number, + ampWhale: number, + bWhale: number + ) => { + const specificBondingData = data.find((e) => e.tokenType == tokenType) + specificBondingData.value = value + specificBondingData.whale = whale + specificBondingData.ampWhale = ampWhale + specificBondingData.bWhale = bWhale + } + + const setBondedTokens = function (ampWhale, bWhale) { + setValues(TokenType.bonded, ampWhale + bWhale, null, ampWhale, bWhale) + } + const setLiquidTokens = function (whale, ampWhale, bWhale) { + setValues( + TokenType.liquid, + whale + ampWhale + bWhale, + whale, + ampWhale, + bWhale + ) + } + + const setUnbondingTokens = function (ampWhale, bWhale) { + setValues(TokenType.unbonding, ampWhale + bWhale, null, ampWhale, bWhale) + } + + const setWithdrawableTokens = function (ampWhale, bWhale) { + setValues(TokenType.withdrawable, ampWhale + bWhale, null, ampWhale, bWhale) + } + + const whalePrice = + usePriceForOneToken({ + tokenASymbol: 'WHALE', + tokenBSymbol: 'axlUSDC', + })[0] || 0 + + const { balance: liquidWhale } = useTokenBalance(WHALE_TOKEN_SYMBOL) + const { balance: liquidAmpWhale } = useTokenBalance(AMP_WHALE_TOKEN_SYMBOL) + const { balance: liquidBWhale } = useTokenBalance(B_WHALE_TOKEN_SYMBOL) + + const { + feeDistributionConfig, + globalTotalBonded, + localTotalBonded, + bondedAmpWhale, + bondedBWhale, + unbondingAmpWhale, + unbondingBWhale, + withdrawableAmpWhale, + withdrawableBWhale, + weightInfo, + annualRewards, + currentEpoch, + claimable: claimableRewards, + globalAvailableRewards, + isLoading, + } = useDashboardData(client, address, network, chainId) + + useEffect(() => { + setBondedTokens(bondedAmpWhale, bondedBWhale) + setLiquidTokens(liquidWhale, liquidAmpWhale, liquidBWhale) + setUnbondingTokens(unbondingAmpWhale, unbondingBWhale) + setWithdrawableTokens(withdrawableAmpWhale, withdrawableBWhale) + setData(data) + }, [isWalletConnected, isLoading, liquidWhale, liquidAmpWhale, liquidBWhale]) + + return ( + + + + + + Bonding + + + + + + + + + + ) +} + +export default Dashboard diff --git a/components/Pages/Dashboard/RewardsComponent.tsx b/components/Pages/Dashboard/RewardsComponent.tsx new file mode 100644 index 00000000..1dd1c170 --- /dev/null +++ b/components/Pages/Dashboard/RewardsComponent.tsx @@ -0,0 +1,318 @@ +import React, { useEffect, useMemo, useState } from 'react' + +import { + Box, + Button, + HStack, + Image, + keyframes, + Text, + useDisclosure, + VStack, +} from '@chakra-ui/react' + +import { walletState } from 'state/atoms/walletAtoms' + +import { useRecoilState } from 'recoil' +import WalletModal from '../../Wallet/Modal/Modal' +import Loader from '../../Loader' +import { calculateRewardDurationString, nanoToMilli } from 'util/conversion' +import { ActionType } from './BondingOverview' +import useTransaction, { TxStep } from '../BondingActions/hooks/useTransaction' +import { BondingActionTooltip } from 'components/Pages/BondingActions/BondingAcionTooltip' + +const pulseAnimation = keyframes` + 0% { + transform: scale(0.99) translateX(0%); + background-color: #FAFD3C; + } + 25% { + transform: scale(1) translateX(0%); + background-color: #7CFB7D; + } + 50% { + transform: scale(0.99) translateX(0%); + background-color: #FAFD3C; + } + 75% { + transform: scale(1) translateX(0%); + background-color: #7CFB7D; + } + 100% { + transform: scale(0.99) translateX(0%); + background-color: #FAFD3C; + } +` + +const ProgressBar = ({ progress, currentEpochStartTimeInNano }) => { + const colors = ['#E43A1C', '#EE902E', '#FAFD3C', '#7CFB7D'] + const [isImminent, setImminent] = useState(false) + const [percent, setPercent] = useState(0) + + const currentDate: Date = new Date() + currentDate.setDate(currentDate.getDate() - 1) + const currentDateTimeMinusOneDay = currentDate.getTime() + const epochStartDateTime = new Date( + nanoToMilli(currentEpochStartTimeInNano) + ).getTime() + + useEffect(() => { + if (!isImminent) { + if (progress === 100) { + setImminent(true) + } + setPercent(progress) + } + if ( + (isImminent && currentDateTimeMinusOneDay < epochStartDateTime) || + currentEpochStartTimeInNano === 0 + ) { + setImminent(false) + } + }, [progress, currentDateTimeMinusOneDay]) + + return ( + 0 + ? 'transparent' + : 'whiteAlpha.400' + } + borderRadius="10px" + overflow="hidden" + position="relative" + > + + + ) +} + +const RewardsComponent = ({ + isWalletConnected, + isLoading, + whalePrice, + currentEpoch, + localTotalBonded, + globalTotalBonded, + feeDistributionConfig, + annualRewards, + globalAvailableRewards, + claimableRewards, + weightInfo, +}) => { + const [{ chainId }, _] = useRecoilState(walletState) + const { + isOpen: isOpenModal, + onOpen: onOpenModal, + onClose: onCloseModal, + } = useDisclosure() + + const epochDurationInMilli = nanoToMilli( + Number(feeDistributionConfig?.epoch_config?.duration) + ) + + const genesisStartTimeInNano = Number( + feeDistributionConfig?.epoch_config?.genesis_epoch ?? 0 + ) + + const localWeight = Number(weightInfo?.weight) + + const multiplierRatio = Math.max( + (localWeight || 0) / (localTotalBonded || 1), + 1 + ) + + const apr = + ((annualRewards || 0) / (globalTotalBonded || 1)) * 100 * multiplierRatio + + const { txStep, submit } = useTransaction() + + // TODO global constant? + const boxBg = '#1C1C1C' + // TODO global constant ? + const borderRadius = '30px' + const currentEpochStartDateTime = new Date( + nanoToMilli(Number(currentEpoch?.epoch?.start_time)) + ).getTime() + + const passedTimeSinceCurrentEpochStartedInMilli = + Date.now() - currentEpochStartDateTime + + const buttonLabel = useMemo(() => { + if (!isWalletConnected) return 'Connect Wallet' + else if (claimableRewards === 0) return 'No Rewards' + else return 'Claim' + }, [isWalletConnected, globalAvailableRewards]) + + const durationString = calculateRewardDurationString( + epochDurationInMilli - passedTimeSinceCurrentEpochStartedInMilli, + genesisStartTimeInNano + ) + + const progress = Math.min( + (passedTimeSinceCurrentEpochStartedInMilli / epochDurationInMilli) * 100, + 100 + ) + + return ( + <> + {isLoading ? ( + + + + + + ) : ( + + + + + WhiteWhale Logo + + WHALE + + + ${whalePrice.toFixed(6)} + + + + + Next rewards in + {isWalletConnected ? durationString : ''} + + + + + + + Rewards + + + + {isWalletConnected + ? `$${(claimableRewards * whalePrice).toFixed(2)}` + : 'n/a'} + + + + + Estimated APR + + + {isWalletConnected ? `${apr.toFixed(2)}%` : 'n/a'} + + + + + Multiplier + + + {isWalletConnected + ? `${((multiplierRatio - 1) * 100).toFixed(2)}%` + : 'n/a'} + + + + + + + )} + + ) +} + +export default RewardsComponent diff --git a/components/Pages/Dashboard/WhaleTooltip.tsx b/components/Pages/Dashboard/WhaleTooltip.tsx new file mode 100644 index 00000000..995a7c61 --- /dev/null +++ b/components/Pages/Dashboard/WhaleTooltip.tsx @@ -0,0 +1,145 @@ +import { Box, Divider, HStack, Text, VStack, Tooltip } from '@chakra-ui/react' +import React from 'react' +import { useEffect, useRef, useState } from 'react' +import { TokenType, WhaleType } from './BondingOverview' +import { BondingData } from './types/BondingData' + +export interface WhaleTooltipProps { + data: BondingData[] + withdrawableAmpWhale?: number + withdrawableBWhale?: number + label: string + isWalletConnected: boolean + tokenType: TokenType +} + +export const WhaleTooltip = ({ + data, + label, + isWalletConnected, + tokenType, + withdrawableAmpWhale, + withdrawableBWhale, +}: WhaleTooltipProps) => { + const { + whale = null, + ampWhale = null, + bWhale = null, + } = data?.find((e) => e.tokenType == tokenType) || {} + + const lsdTokenDetails = [ + { + type: WhaleType.bWHALE, + value: withdrawableBWhale ?? bWhale, + }, + { + type: WhaleType.ampWHALE, + value: withdrawableAmpWhale ?? ampWhale, + }, + ].sort((a, b) => b.value - a.value) + + const TokenDetail = ({ whaleType, value }) => { + return ( + + + {WhaleType[whaleType]} + + {isWalletConnected ? value : 'n/a'} + + ) + } + const textRef = useRef(null) + const [textWidth, setTextWidth] = useState(0) + + useEffect(() => { + setTextWidth(textRef.current.offsetWidth) + }, [whale, ampWhale, bWhale]) + + return ( + + {ampWhale === null && withdrawableAmpWhale == null ? ( + + {tokenType === TokenType.liquid + ? 'Liquid WHALE and LSD-WHALE Token Balances' + : tokenType === TokenType.bonded + ? 'Current amount of bonded LSD-WHALE token' + : tokenType === TokenType.unbonding + ? 'Current amount of unbonding LSD-WHALE token' + : tokenType === TokenType.withdrawable + ? 'Current amount of withdrawable LSD-WHALE token' + : null} + + ) : ( + <> + {tokenType === TokenType.liquid ? ( + <> + {' '} + + + + ) : null} + {lsdTokenDetails.map((e, index) => { + return ( + + + {index === 0 && ( + + )} + + ) + })} + + )} + + ) : null + } //displaying nothing when wallet disconnected + bg="transparent" + > + + + {label} + + + {label !== 'n/a' && ( +
+ )} + + + + ) +} diff --git a/components/Pages/Dashboard/hooks/claimRewards.ts b/components/Pages/Dashboard/hooks/claimRewards.ts new file mode 100644 index 00000000..57a8d80a --- /dev/null +++ b/components/Pages/Dashboard/hooks/claimRewards.ts @@ -0,0 +1,13 @@ +import { Wallet } from 'util/wallet-adapters' +import { Config } from 'components/Pages/Dashboard/hooks/useDashboardData' + +export const claimRewards = ( + client: Wallet, + address: string, + config: Config +) => { + const handleMsg = { + claim: {}, + } + return client.execute(address, config.fee_distributor_address, handleMsg) +} diff --git a/components/Pages/Dashboard/hooks/getBonded.ts b/components/Pages/Dashboard/hooks/getBonded.ts new file mode 100644 index 00000000..8512d7bf --- /dev/null +++ b/components/Pages/Dashboard/hooks/getBonded.ts @@ -0,0 +1,72 @@ +import { Wallet } from 'util/wallet-adapters' +import { JsonObject } from '@cosmjs/cosmwasm-stargate' +import { convertMicroDenomToDenom } from 'util/conversion' +import { Config } from './useDashboardData' + +interface NativeTokenInfo { + native_token: { + denom: string + } +} + +interface Asset { + amount: string + info: NativeTokenInfo +} + +interface BondedInfo { + bonded_assets: Asset[] + total_bonded: string +} + +const fetchBonded = async ( + client: Wallet, + address: string, + config: Config +): Promise => { + const result: JsonObject = await client.queryContractSmart( + config.whale_lair_address, + { + bonded: { address: address }, + } + ) + return result as BondedInfo +} + +export const getBonded = async ( + client: Wallet | null, + address: string | null, + config: Config +) => { + if (!client || !address) { + return null + } + + const bondedInfo = await fetchBonded(client, address, config) + + const totalBonded = bondedInfo?.total_bonded ?? 0 + + const localTotalBonded = Number(totalBonded) + + const bondedAmpWhale = bondedInfo + ? convertMicroDenomToDenom( + bondedInfo?.bonded_assets.find( + (asset) => + asset.info.native_token.denom === config.lsd_token.ampWHALE.denom + )?.amount, + config.lsd_token.ampWHALE.decimals + ) + : null + + const bondedBWhale = bondedInfo + ? convertMicroDenomToDenom( + bondedInfo?.bonded_assets.find( + (asset) => + asset.info.native_token.denom === config.lsd_token.bWHALE.denom + )?.amount, + config.lsd_token.bWHALE.decimals + ) + : null + + return { bondedAmpWhale, bondedBWhale, localTotalBonded } +} diff --git a/components/Pages/Dashboard/hooks/getBondingConfig.ts b/components/Pages/Dashboard/hooks/getBondingConfig.ts new file mode 100644 index 00000000..9c816cb2 --- /dev/null +++ b/components/Pages/Dashboard/hooks/getBondingConfig.ts @@ -0,0 +1,43 @@ +import { Wallet } from 'util/wallet-adapters' +import { JsonObject } from '@cosmjs/cosmwasm-stargate' +import { Config } from './useDashboardData' + +interface NativeToken { + denom: string +} + +export interface BondingAsset { + native_token: NativeToken +} + +export interface BondingContractConfig { + owner: string + unbonding_period: number + growth_rate: string + bonding_assets: BondingAsset[] +} + +export const getBondingConfig = async ( + client: Wallet | null, + config: Config +) => { + if (!client && !config) { + return null + } + const bondingConfig = await fetchConfig(client, config) + return { bondingConfig } +} + +export const fetchConfig = async ( + client: Wallet, + config: Config +): Promise => { + const result: JsonObject = await client.queryContractSmart( + config.whale_lair_address, + { + config: {}, + } + ) + + return result as BondingContractConfig +} diff --git a/components/Pages/Dashboard/hooks/getClaimable.ts b/components/Pages/Dashboard/hooks/getClaimable.ts new file mode 100644 index 00000000..70d84440 --- /dev/null +++ b/components/Pages/Dashboard/hooks/getClaimable.ts @@ -0,0 +1,70 @@ +import { Wallet } from 'util/wallet-adapters' +import { JsonObject } from '@cosmjs/cosmwasm-stargate' +import { convertMicroDenomToDenom } from 'util/conversion' +import { Config } from './useDashboardData' + +interface Epoch { + id: string + start_time: string + total: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] + available: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] + claimed: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] +} + +interface Data { + epochs: Epoch[] +} + +export const getClaimable = async ( + client: Wallet | null, + address: string, + config: Config +) => { + if (!client || !address) { + return null + } + + const data = await fetchClaimableData(client, address, config) + + const claimableAmounts = data?.epochs + .flatMap((e) => e.available.map((a) => a.amount)) + .reduce((acc, amount) => acc + parseFloat(amount), 0) + + const claimable = convertMicroDenomToDenom(claimableAmounts, 6) + return { claimable } +} +const fetchClaimableData = async ( + client: Wallet, + address: string, + config: Config +): Promise => { + const result: JsonObject = await client.queryContractSmart( + config.fee_distributor_address, + { + claimable: { address: address }, + } + ) + + return result as Data +} diff --git a/components/Pages/Dashboard/hooks/getClaimableEpochs.ts b/components/Pages/Dashboard/hooks/getClaimableEpochs.ts new file mode 100644 index 00000000..70541410 --- /dev/null +++ b/components/Pages/Dashboard/hooks/getClaimableEpochs.ts @@ -0,0 +1,83 @@ +import { Wallet } from 'util/wallet-adapters' +import { JsonObject } from '@cosmjs/cosmwasm-stargate' +import { convertMicroDenomToDenom } from 'util/conversion' +import { Config } from './useDashboardData' + +interface Epoch { + id: string + start_time: string + total: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] + available: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] + claimed: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] +} + +interface Data { + epochs: Epoch[] +} + +export const getClaimableEpochs = async (client: Wallet, config: Config) => { + if (!client) { + return null + } + + const data = await fetchClaimableEpoch(client, config) + + const rewardData = data?.epochs + .flatMap((e) => e.total.map((a) => a.amount)) + .reduce((acc, amount) => acc + parseFloat(amount), 0) + const globalAvailableRewards = convertMicroDenomToDenom(rewardData, 6) + + const getLastSevenEpochsAverage = (epochs: Epoch[]): number => { + const lastSevenEpochs = epochs.slice(-7) + const totalAmount = lastSevenEpochs + .flatMap((e) => e.total.map((a) => a.amount)) + .reduce((acc, amount) => acc + parseFloat(amount), 0) + + return totalAmount / lastSevenEpochs.length + } + + const extrapolateAnnualRewards = (dailyAverage: number): number => { + return convertMicroDenomToDenom(dailyAverage * 365, 6) + } + + const dailyAverageRewards = data?.epochs + ? getLastSevenEpochsAverage(data.epochs) + : 0 + const annualRewards = extrapolateAnnualRewards(dailyAverageRewards) + + return { globalAvailableRewards, annualRewards } +} + +export const fetchClaimableEpoch = async ( + client: Wallet, + config: Config +): Promise => { + const result: JsonObject = await client.queryContractSmart( + config.fee_distributor_address, + { + claimable_epochs: {}, + } + ) + return result as Data +} diff --git a/components/Pages/Dashboard/hooks/getCurrentEpoch.ts b/components/Pages/Dashboard/hooks/getCurrentEpoch.ts new file mode 100644 index 00000000..db88bda4 --- /dev/null +++ b/components/Pages/Dashboard/hooks/getCurrentEpoch.ts @@ -0,0 +1,60 @@ +import { Wallet } from 'util/wallet-adapters' +import { JsonObject } from '@cosmjs/cosmwasm-stargate' +import { Config } from './useDashboardData' + +export interface EpochData { + id: string + start_time: string + total: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] + available: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] + claimed: { + amount: string + info: { + native_token: { + denom: string + } + } + }[] +} + +export interface Epoch { + epoch: EpochData +} + +export const getCurrentEpoch = async (client: Wallet, config: Config) => { + if (!client) { + return null + } + + const currentEpoch = await fetchCurrentEpoch(client, config) + + return { currentEpoch } +} + +export const fetchCurrentEpoch = async ( + client: Wallet, + config: Config +): Promise => { + const result: JsonObject = await client.queryContractSmart( + config.fee_distributor_address, + { + current_epoch: {}, + } + ) + + return result as Epoch +} diff --git a/components/Pages/Dashboard/hooks/getEpochById.ts b/components/Pages/Dashboard/hooks/getEpochById.ts new file mode 100644 index 00000000..91845b33 --- /dev/null +++ b/components/Pages/Dashboard/hooks/getEpochById.ts @@ -0,0 +1,70 @@ +// import {Wallet} from "util/wallet-adapters"; +// import { +// FEE_DISTRIBUTOR_CONTRACT_ADDRESS +// } from "constants/bonding_contract"; +// import {JsonObject} from "@cosmjs/cosmwasm-stargate"; +// import {useQuery} from "react-query"; +// +// interface EpochData { +// id: string; +// start_time: string; +// total: { +// amount: string; +// info: { +// native_token: { +// denom: string; +// }; +// }; +// }[]; +// available: { +// amount: string; +// info: { +// native_token: { +// denom: string; +// }; +// }; +// }[]; +// claimed: { +// amount: string; +// info: { +// native_token: { +// denom: string; +// }; +// }; +// }[]; +// } +// interface Epoch { +// epoch: EpochData; +// } +// export const getEpochById = (client: Wallet | null, id: string ) => { +// const { +// data: currentEpoch, +// isLoading, +// refetch, +// } = useQuery( +// ['currentEpoch', client], +// () => { +// if (client) { +// return fetchEpochById(client, id); +// } else { +// return Promise.resolve(null); +// } +// }, +// { +// refetchOnMount:true, +// +// refetchIntervalInBackground: true, +// } +// ) +// +// return {currentEpoch, isLoading, refetch} +// +// } +// +// export const fetchEpochById = async (client: Wallet, id: string): Promise => { +// const result: JsonObject = await client.queryContractSmart(FEE_DISTRIBUTOR_CONTRACT_ADDRESS, { +// epoch: {id: id}, +// }); +// +// return result as Epoch; +// }; diff --git a/components/Pages/Dashboard/hooks/getFeeDistributorConfig.ts b/components/Pages/Dashboard/hooks/getFeeDistributorConfig.ts new file mode 100644 index 00000000..e8dfafef --- /dev/null +++ b/components/Pages/Dashboard/hooks/getFeeDistributorConfig.ts @@ -0,0 +1,45 @@ +import { Wallet } from 'util/wallet-adapters' +import { JsonObject } from '@cosmjs/cosmwasm-stargate' +import { Config } from './useDashboardData' + +export interface FeeDistributionConfig { + owner: string + bonding_contract_addr: string + fee_collector_addr: string + grace_period: string + epoch_config: { + duration: string + genesis_epoch: string + } + distribution_asset: { + native_token: { + denom: string + } + } +} + +export const getFeeDistributorConfig = async ( + client: Wallet, + config: Config +) => { + if (!client) { + return null + } + const feeDistributionConfig = await fetchConfig(client, config) + + return { feeDistributionConfig } +} + +const fetchConfig = async ( + client: Wallet, + config: Config +): Promise => { + const result: JsonObject = await client.queryContractSmart( + config.fee_distributor_address, + { + config: {}, + } + ) + + return result as FeeDistributionConfig +} diff --git a/components/Pages/Dashboard/hooks/getTotalBonded.ts b/components/Pages/Dashboard/hooks/getTotalBonded.ts new file mode 100644 index 00000000..9066a79f --- /dev/null +++ b/components/Pages/Dashboard/hooks/getTotalBonded.ts @@ -0,0 +1,50 @@ +import { Wallet } from 'util/wallet-adapters' +import { JsonObject } from '@cosmjs/cosmwasm-stargate' +import { convertMicroDenomToDenom } from 'util/conversion' +import { Config } from './useDashboardData' + +interface NativeTokenInfo { + native_token: { + denom: string + } +} + +interface Asset { + amount: string + info: NativeTokenInfo +} + +interface TotalBondedInfo { + bonded_assets: Asset[] + total_bonded: string +} + +export const getTotalBonded = async (client: Wallet, config: Config) => { + if (!client) { + return null + } + try { + const totalBondedInfo = await fetchTotalBonded(client, config) + + const globalTotalBonded = convertMicroDenomToDenom( + totalBondedInfo?.total_bonded || 0, + 6 + ) + + return { globalTotalBonded } + } catch (e) { + return 0 + } +} +const fetchTotalBonded = async ( + client: Wallet, + config: Config +): Promise => { + const result: JsonObject = await client.queryContractSmart( + config.whale_lair_address, + { + total_bonded: {}, + } + ) + return result as TotalBondedInfo +} diff --git a/components/Pages/Dashboard/hooks/getUnbonding.ts b/components/Pages/Dashboard/hooks/getUnbonding.ts new file mode 100644 index 00000000..2ef827cc --- /dev/null +++ b/components/Pages/Dashboard/hooks/getUnbonding.ts @@ -0,0 +1,102 @@ +import { Wallet } from 'util/wallet-adapters' +import { convertMicroDenomToDenom, nanoToMilli } from 'util/conversion' +import { Config } from './useDashboardData' +import { fetchConfig } from 'components/Pages/Dashboard/hooks/getBondingConfig' + +export interface UnbondingInfo { + total_amount: string + unbonding_requests: UnbondingRequest[] +} + +export interface UnbondingRequest { + asset: Asset + timestamp: string + weight: string +} + +interface Asset { + info: AssetInfo + amount: string +} + +interface AssetInfo { + native_token: NativeToken +} + +interface NativeToken { + denom: string +} + +export const getUnbonding = async ( + client: Wallet, + address: string, + config: Config +) => { + if (!client || !address) { + return null + } + + const unbondingInfos = await fetchUnbonding(client, address, config) + const bondingContractConfig = await fetchConfig(client, config) + + const unbondingPeriodInNano = Number(bondingContractConfig?.unbonding_period) + const currentTimeInNano = Date.now() * 1_000_000 + + // filtering out unbonding requests which have already finished, so they won't get shown + const filterUnbondingRequests = (unbondingRequests) => { + return unbondingRequests.filter( + (req) => Number(req.timestamp) + unbondingPeriodInNano > currentTimeInNano + ) + } + const filteredAmpWhaleUnbondingRequests = filterUnbondingRequests( + unbondingInfos?.[0]?.unbonding_requests + ) + const filteredBWhaleUnbondingRequests = filterUnbondingRequests( + unbondingInfos?.[1]?.unbonding_requests + ) + + const filteredUnbondingRequests: UnbondingRequest[] = [ + ...(filteredAmpWhaleUnbondingRequests || []), + ...(filteredBWhaleUnbondingRequests || []), + ].sort( + (a, b) => + new Date(nanoToMilli(Number(a.timestamp))).getTime() - + new Date(nanoToMilli(Number(b.timestamp))).getTime() + ) + + const unbondingAmpWhale = convertMicroDenomToDenom( + filteredAmpWhaleUnbondingRequests + ?.map((req) => req.asset.amount) + .reduce((accumulator: number, currentValue: string) => { + return accumulator + parseFloat(currentValue) + }, 0) || 0, + config.lsd_token.ampWHALE.decimals + ) + + const unbondingBWhale = convertMicroDenomToDenom( + filteredBWhaleUnbondingRequests + ?.map((req) => req.asset.amount) + .reduce((accumulator: number, currentValue: string) => { + return accumulator + parseFloat(currentValue) + }, 0) || 0, + config.lsd_token.bWHALE.decimals + ) + + return { unbondingAmpWhale, unbondingBWhale, filteredUnbondingRequests } +} + +const fetchUnbonding = async ( + client: Wallet, + address: string, + config: Config +): Promise => { + const results: UnbondingInfo[] = await Promise.all( + Object.entries(config.lsd_token).map(async ([key, token]) => { + return await client.queryContractSmart(config.whale_lair_address, { + unbonding: { address: address, denom: token.denom }, + }) + }) + ) + + return results +} diff --git a/components/Pages/Dashboard/hooks/getWeight.ts b/components/Pages/Dashboard/hooks/getWeight.ts new file mode 100644 index 00000000..36177839 --- /dev/null +++ b/components/Pages/Dashboard/hooks/getWeight.ts @@ -0,0 +1,43 @@ +import { Wallet } from 'util/wallet-adapters' +import { JsonObject } from '@cosmjs/cosmwasm-stargate' +import { Config } from './useDashboardData' + +export interface WeightInfo { + address: string + weight: string + global_weight: string + share: string + timestamp: string +} + +export const getWeight = async ( + client: Wallet, + address: string, + config: Config +) => { + if (!client || !address) { + return null + } + try { + const weightInfo = await fetchWeight(client, address, config) + + return { weightInfo } + } catch (e) { + return 0 + } +} + +const fetchWeight = async ( + client: Wallet, + address: string, + config: Config +): Promise => { + const result: JsonObject = await client.queryContractSmart( + config.whale_lair_address, + { + weight: { address: address }, + } + ) + + return result as WeightInfo +} diff --git a/components/Pages/Dashboard/hooks/getWithdrawable.ts b/components/Pages/Dashboard/hooks/getWithdrawable.ts new file mode 100644 index 00000000..781aaa0c --- /dev/null +++ b/components/Pages/Dashboard/hooks/getWithdrawable.ts @@ -0,0 +1,46 @@ +import { Wallet } from 'util/wallet-adapters' +import { convertMicroDenomToDenom } from 'util/conversion' +import { Config } from './useDashboardData' + +interface WithdrawableInfo { + withdrawable_amount: number +} + +export const getWithdrawable = async ( + client: Wallet, + address: string, + config: Config +) => { + if (!client || !address) { + return null + } + + const withdrawableInfos = await fetchWithdrawable(client, address, config) + + const withdrawableAmpWhale = convertMicroDenomToDenom( + withdrawableInfos?.[0]?.withdrawable_amount, + 6 + ) + const withdrawableBWhale = convertMicroDenomToDenom( + withdrawableInfos?.[1]?.withdrawable_amount, + 6 + ) + + return { withdrawableAmpWhale, withdrawableBWhale } +} + +const fetchWithdrawable = async ( + client: Wallet, + address: string, + config: Config +): Promise => { + const results = await Promise.all( + Object.entries(config.lsd_token).map(async ([key, token]) => { + return await client.queryContractSmart(config.whale_lair_address, { + withdrawable: { address: address, denom: token.denom }, + }) + }) + ) + + return results as WithdrawableInfo[] +} diff --git a/components/Pages/Dashboard/hooks/useDashboardData.ts b/components/Pages/Dashboard/hooks/useDashboardData.ts new file mode 100644 index 00000000..4dbad2ea --- /dev/null +++ b/components/Pages/Dashboard/hooks/useDashboardData.ts @@ -0,0 +1,164 @@ +import { useQueries } from 'react-query' +import { useEffect, useMemo, useState } from 'react' +import { debounce } from 'lodash' +import { getBonded } from 'components/Pages/Dashboard/hooks/getBonded' +import { getTotalBonded } from 'components/Pages/Dashboard/hooks/getTotalBonded' +import { getUnbonding } from 'components/Pages/Dashboard/hooks/getUnbonding' +import { getWithdrawable } from 'components/Pages/Dashboard/hooks/getWithdrawable' +import { getFeeDistributorConfig } from 'components/Pages/Dashboard/hooks/getFeeDistributorConfig' +import { getCurrentEpoch } from 'components/Pages/Dashboard/hooks/getCurrentEpoch' +import { getClaimableEpochs } from 'components/Pages/Dashboard/hooks/getClaimableEpochs' +import { getClaimable } from 'components/Pages/Dashboard/hooks/getClaimable' +import { getBondingConfig } from 'components/Pages/Dashboard/hooks/getBondingConfig' +import { getWeight } from 'components/Pages/Dashboard/hooks/getWeight' +import { DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL } from 'util/constants' + +export interface TokenDetails { + name: string + denom: string + decimals: number +} + +export interface Config { + whale_lair_address: string + fee_distributor_address: string + whale_base_token: TokenDetails + lsd_token: { + ampWHALE: TokenDetails + bWHALE: TokenDetails + } +} + +export const useConfig = (network: string, chainId: string) => { + const [config, setConfig] = useState(null) + + useEffect(() => { + if (network && chainId) { + // Only execute if network and chainId are defined + const fetchConfig = async () => { + try { + const response = await fetch( + `/${network}/${chainId}/bonding_config.json` + ) + const json: Config = await response.json() + setConfig(json) + } catch (error) { + console.error('Failed to load config:', error) + } + } + fetchConfig() + } + }, [network, chainId]) + + return config +} + +export const useDashboardData = (client, address, network, chainId) => { + const debouncedRefetch = useMemo( + () => debounce((refetchFunc) => refetchFunc(), 500), + [] + ) + const config: Config = useConfig(network, chainId) + + const queries = useQueries([ + { + queryKey: ['bonded', address, network, chainId], + queryFn: () => getBonded(client, address, config), + enabled: !!client && !!address && !!config, + refetchOnMount: 'always', + refetchInterval: DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL, + }, + { + queryKey: ['unbonding', address, network, chainId], + queryFn: () => getUnbonding(client, address, config), + enabled: !!client && !!address && !!config, + refetchOnMount: 'always', + refetchInterval: DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL, + }, + { + queryKey: ['withdrawable', address, network, chainId], + queryFn: () => getWithdrawable(client, address, config), + enabled: !!client && !!address && !!config, + refetchOnMount: 'always', + refetchInterval: DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL, + }, + { + queryKey: ['totalBonded', address, network, chainId], + queryFn: () => getTotalBonded(client, config), + enabled: !!client && !!config, + }, + { + queryKey: ['weightInfo', address, network, chainId], + queryFn: () => getWeight(client, address, config), + enabled: !!client && !!address && !!config, + }, + { + queryKey: ['feeDistributionConfig', address, network, chainId], + queryFn: () => getFeeDistributorConfig(client, config), + enabled: !!client && !!config, + }, + { + queryKey: ['currentEpoch', address, network, chainId], + queryFn: () => getCurrentEpoch(client, config), + enabled: !!client && !!config, + }, + { + queryKey: ['claimableEpochs', address, network, chainId], + queryFn: () => getClaimableEpochs(client, config), + enabled: !!client && !!config, + }, + { + queryKey: ['claimableRewards', address, network, chainId], + queryFn: () => getClaimable(client, address, config), + enabled: !!client && !!address && !!config, + }, + { + queryKey: ['bondingConfig', address, network, chainId], + queryFn: () => getBondingConfig(client, config), + enabled: !!client && !!config, + }, + ]) + + const isLoading = useMemo( + () => + queries.some( + (query) => + query.isLoading || query.data === null || query.data === undefined + ), + [queries] + ) + + const refetchAll = () => { + queries.forEach((query) => { + debouncedRefetch(query.refetch) + }) + } + + const data = useMemo(() => { + const bondedData = queries[0].data + const unbondingData = queries[1].data + const withdrawableData = queries[2].data + const totalBondedData = queries[3].data + const weightData = queries[4].data + const feeDistributionData = queries[5].data + const currentEpochData = queries[6].data + const claimableEpochsData = queries[7].data + const claimableRewardsData = queries[8].data + const bondingConfigData = queries[9].data + + return { + ...bondedData, + ...unbondingData, + ...withdrawableData, + ...totalBondedData, + ...weightData, + ...feeDistributionData, + ...currentEpochData, + ...claimableEpochsData, + ...claimableRewardsData, + ...bondingConfigData, + } + }, [queries]) + + return { ...data, isLoading, refetch: refetchAll } +} diff --git a/components/Pages/Dashboard/index.ts b/components/Pages/Dashboard/index.ts new file mode 100644 index 00000000..a7a17455 --- /dev/null +++ b/components/Pages/Dashboard/index.ts @@ -0,0 +1 @@ +export { default } from './Dashboard' diff --git a/components/Pages/Dashboard/types/BondingData.ts b/components/Pages/Dashboard/types/BondingData.ts new file mode 100644 index 00000000..f0281060 --- /dev/null +++ b/components/Pages/Dashboard/types/BondingData.ts @@ -0,0 +1,12 @@ +import { ActionType, TokenType } from '../BondingOverview' + +export type BondingData = { + color: string + actionType: ActionType + tokenType: TokenType + value: number + whale: number + ampWhale: number + bWhale: number + label: string +} diff --git a/components/Pages/ManageLiquidity/DepositForm.tsx b/components/Pages/ManageLiquidity/DepositForm.tsx index 8c327e75..a87c708a 100644 --- a/components/Pages/ManageLiquidity/DepositForm.tsx +++ b/components/Pages/ManageLiquidity/DepositForm.tsx @@ -1,32 +1,20 @@ import { useEffect, useMemo } from 'react' import { Controller, useForm } from 'react-hook-form' -import { InfoOutlineIcon } from '@chakra-ui/icons' -import { - Box, - Button, - HStack, - Spinner, - Text, - Tooltip, - VStack, -} from '@chakra-ui/react' +import { Button, HStack, Spinner, Text, VStack } from '@chakra-ui/react' import AssetInput from 'components/AssetInput' import { useTokenBalance } from 'hooks/useTokenBalance' -import { useBaseTokenInfo, useTokenInfo } from 'hooks/useTokenInfo' import { TxStep } from 'hooks/useTransaction' -import { fromChainAmount, num } from 'libs/num' +import { num } from 'libs/num' -import { WalletStatusType } from '../../../state/atoms/walletAtoms' -import { TokenItemState } from './lpAtoms' +import { WalletStatusType } from 'state/atoms/walletAtoms' +import { TokenItemState } from 'types/index' type Props = { connected: WalletStatusType tokenA: TokenItemState tokenB: TokenItemState tx: any - // resetForm: boolean - // setResetForm: (value: boolean) => void simulated: string | null onInputChange: (asset: TokenItemState, index: number) => void setReverse: (value: boolean) => void @@ -43,30 +31,19 @@ const DepositForm = ({ setReverse, reverse, }: Props) => { - const baseToken = useBaseTokenInfo() - - // const [[tokenABalance, tokenBBalance] = [], isLoading] = useMultipleTokenBalance([tokenA?.tokenSymbol, tokenB?.tokenSymbol]) - const { balance: tokenABalance, isLoading: tokanAloading } = useTokenBalance( - tokenA?.tokenSymbol - ) + const { balance: tokenABalance } = useTokenBalance(tokenA?.tokenSymbol) const { balance: tokenBBalance, isLoading: tokanBloading } = useTokenBalance( tokenB?.tokenSymbol ) - const tokenAInfo = useTokenInfo(tokenA?.tokenSymbol) - const tokenBInfo = useTokenInfo(tokenB?.tokenSymbol) - - const { control, handleSubmit, formState, setValue, getValues } = useForm({ + const { control, handleSubmit, setValue, getValues } = useForm({ mode: 'onChange', defaultValues: { token1: tokenA, token2: tokenB, - // slippage: String(DEFAULT_SLIPPAGE), }, }) - // const [resetForm, setResetForm] = useState(false) - const isInputDisabled = tx?.txStep == TxStep.Posting const isConnected = connected === `@wallet-state/connected` @@ -122,18 +99,6 @@ const DepositForm = ({ onSubmit={handleSubmit(tx?.submit)} > - - - Balance:{' '} - - {tokanAloading ? ( - - ) : ( - - {tokenABalance?.toFixed(6)} - - )} - ( ( {buttonLabel} - {/* {(tokenB?.tokenSymbol && Number(amountA.amount) > 0) && ( - - - - Fee - - - - - - - {fromChainAmount(tx?.fee)} {baseToken?.symbol} - - - )} */} - {tx?.error && !!!tx.buttonLabel && ( {' '} diff --git a/components/Pages/ManageLiquidity/ManageLiquidity.tsx b/components/Pages/ManageLiquidity/ManageLiquidity.tsx index 511bbe90..88077bee 100644 --- a/components/Pages/ManageLiquidity/ManageLiquidity.tsx +++ b/components/Pages/ManageLiquidity/ManageLiquidity.tsx @@ -18,7 +18,7 @@ import { TxStep } from 'hooks/useTransaction' import { NextRouter, useRouter } from 'next/router' import { usePoolsListQuery } from 'queries/usePoolsListQuery' import { useRecoilState, useRecoilValue } from 'recoil' -import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' +import { walletState } from 'state/atoms/walletAtoms' import useProvideLP from '../NewPosition/hooks/useProvideLP' import DepositForm from './DepositForm' @@ -27,7 +27,7 @@ import WithdrawForm from './WithdrawForm' const ManageLiquidity: FC = () => { const router: NextRouter = useRouter() - const chains = useChains() + const chains: Array = useChains() const { address, chainId, status } = useRecoilValue(walletState) const [reverse, setReverse] = useState(false) const [isTokenSet, setIsToken] = useState(false) @@ -37,17 +37,17 @@ const ManageLiquidity: FC = () => { const poolId = router.query.poolId as string const chainIdParam = router.query.chainId as string - const currenChain = chains.find((row) => row.chainId === chainId) + const currentChain = chains.find((row) => row.chainId === chainId) useEffect(() => { - if (currenChain) { + if (currentChain) { if (poolId) { const pools = poolList?.pools if (pools && !pools.find((pool: any) => pool.pool_id === poolId)) { - router.push(`/${currenChain.label.toLowerCase()}/pools`) + router.push(`/${currentChain.label.toLowerCase()}/pools`) } else { router.push( - `/${currenChain.label.toLowerCase()}/pools/manage_liquidity?poolId=${poolId}` + `/${currentChain.label.toLowerCase()}/pools/manage_liquidity?poolId=${poolId}` ) } } @@ -107,7 +107,7 @@ const ManageLiquidity: FC = () => { width={{ base: '100%', md: '700px' }} alignItems="center" padding={5} - // margin="auto" + margin="auto" > { { swap_address: swapAddress = null, lp_token: contract = null, - pool_assets = [], liquidity = {}, } = {}, - isLoading, ] = useQueryPoolLiquidity({ poolId }) const [token, setToken] = useState(tokenA) @@ -68,6 +57,8 @@ const WithdrawForm = ({ poolId, tokenA, connected }: Props) => { if (tx?.txStep === TxStep.Failed || tx?.txStep === TxStep.Success) tx.reset() + console.log({ value }) + setToken(value) } @@ -83,20 +74,6 @@ const WithdrawForm = ({ poolId, tokenA, connected }: Props) => { }} > - - - Balance:{' '} - - {/* {tokenBalance} */} - {isLoading ? ( - - ) : ( - - {Number(tokenBalance)?.toFixed(6)} - - )} - - { image={false} token={tokenA} showList={false} + hideDollarValue={true} onChange={onInputChange} /> @@ -123,22 +101,6 @@ const WithdrawForm = ({ poolId, tokenA, connected }: Props) => { {buttonLabel} - {/* {(Number(tx?.fee) > 0) && ( - - - - Fee - - - - - - - {fromChainAmount(tx?.fee)} {baseToken?.symbol} - - - )} */} - {tx?.error && !!!tx.buttonLabel && ( {' '} diff --git a/components/Pages/ManageLiquidity/hooks/createProvideMsgs.ts b/components/Pages/ManageLiquidity/hooks/createProvideMsgs.ts index 183ba8be..7e96ae65 100644 --- a/components/Pages/ManageLiquidity/hooks/createProvideMsgs.ts +++ b/components/Pages/ManageLiquidity/hooks/createProvideMsgs.ts @@ -1,4 +1,4 @@ -import { Coin, MsgExecuteContract } from '@terra-money/terra.js' +import { Coin, MsgExecuteContract } from '@terra-money/feather.js' import { Asset, Pool } from 'types/common' import { getTokenDenom, isNativeAsset } from '../../../../services/asset' diff --git a/components/Pages/ManageLiquidity/lpAtoms.ts b/components/Pages/ManageLiquidity/lpAtoms.ts index fbe182e6..aa2506ba 100644 --- a/components/Pages/ManageLiquidity/lpAtoms.ts +++ b/components/Pages/ManageLiquidity/lpAtoms.ts @@ -1,10 +1,5 @@ import { atom } from 'recoil' - -export type TokenItemState = { - tokenSymbol: string - amount: number - decimals: number -} +import { TokenItemState } from 'types' export const tokenLpAtom = atom<[TokenItemState, TokenItemState]>({ key: 'tokenLP', diff --git a/components/Pages/NewPosition/NewPosition.tsx b/components/Pages/NewPosition/NewPosition.tsx index 881e4b6f..33ff36d0 100644 --- a/components/Pages/NewPosition/NewPosition.tsx +++ b/components/Pages/NewPosition/NewPosition.tsx @@ -23,13 +23,13 @@ const NewPosition = () => { const { chainId, network, address, status } = useRecoilValue(walletState) const { simulated, tx } = useProvideLP({ reverse }) const router: NextRouter = useRouter() - const chains = useChains() + const chains: Array = useChains() const { data: poolList } = usePoolsListQuery() const chainIdParam = router.query.chainId as string const { from, to } = router.query - const currenChain = chains.find((row) => row.chainId === chainId) - const currentChainId = currenChain?.label.toLowerCase() + const currentChain = chains.find((row) => row.chainId === chainId) + const currentChainId = currentChain?.label.toLowerCase() const tokenList = useMemo(() => { let listObj = {} diff --git a/components/Pages/NewPosition/NewPositionForm.tsx b/components/Pages/NewPosition/NewPositionForm.tsx index 1a968cca..e351873d 100644 --- a/components/Pages/NewPosition/NewPositionForm.tsx +++ b/components/Pages/NewPosition/NewPositionForm.tsx @@ -2,25 +2,15 @@ import { FC, useEffect, useMemo } from 'react' import { Controller, useForm } from 'react-hook-form' -import { InfoOutlineIcon } from '@chakra-ui/icons' -import { - Box, - Button, - HStack, - Spinner, - Text, - Tooltip, - VStack, -} from '@chakra-ui/react' +import { Button, HStack, Spinner, Text, VStack } from '@chakra-ui/react' import AssetInput from 'components/AssetInput' import { useTokenBalance } from 'hooks/useTokenBalance' import { useBaseTokenInfo } from 'hooks/useTokenInfo' import { TxStep } from 'hooks/useTransaction' -import { fromChainAmount } from 'libs/num' import { usePoolsListQuery } from 'queries/usePoolsListQuery' -import { WalletStatusType } from '../../../state/atoms/walletAtoms' -import { TokenItemState } from '../ManageLiquidity/lpAtoms' +import { WalletStatusType } from 'state/atoms/walletAtoms' +import { TokenItemState } from 'types' type Props = { connected: WalletStatusType @@ -172,6 +162,7 @@ const NewPositionForm: FC = ({ // edgeTokenList={tokenAList} showList={false} hideToken={tokenB?.tokenSymbol} + hideDollarValue={true} // minMax={false} disabled={isInputDisabled} balance={tokenABalance} @@ -195,9 +186,9 @@ const NewPositionForm: FC = ({ {tokanBloading ? ( ) : ( - - {tokenBBalance} - + + {tokenBBalance} + )} = ({ // minMax={false} disabled={isInputDisabled} balance={tokenBBalance} + hideDollarValue={true} {...field} token={tokenB} onChange={(value, isTokenChange) => { @@ -234,7 +226,9 @@ const NewPositionForm: FC = ({ tx?.txStep == TxStep.Posting || tx?.txStep == TxStep.Broadcasting } - disabled={tx.txStep != TxStep.Ready || simulated == null || !isConnected} + disabled={ + tx.txStep != TxStep.Ready || simulated == null || !isConnected + } > {buttonLabel} diff --git a/components/Pages/NewPosition/hooks/useProvideLP.ts b/components/Pages/NewPosition/hooks/useProvideLP.ts index 99c11763..e4ab33bc 100644 --- a/components/Pages/NewPosition/hooks/useProvideLP.ts +++ b/components/Pages/NewPosition/hooks/useProvideLP.ts @@ -5,7 +5,6 @@ import { useQueryPoolLiquidity } from 'queries/useQueryPools' import { useMemo } from 'react' import { useRecoilValue } from 'recoil' import { walletState } from 'state/atoms/walletAtoms' -import { fromChainAmount } from 'libs/num' import { tokenLpAtom } from '../../ManageLiquidity/lpAtoms' import createLpMsg, { createLPExecuteMsgs } from '../createLPMsg' @@ -75,10 +74,7 @@ const useProvideLP = ({ reverse = false }) => { const tokenB = num(tokenBReserve) .div(10 ** tokenInfoB?.decimals) .toNumber() - const ratio = - reverse - ? num(tokenA).div(tokenB) - : num(tokenB).div(tokenA) + const ratio = reverse ? num(tokenA).div(tokenB) : num(tokenB).div(tokenA) const sim = num(normalizedValue).times(ratio.toNumber()).toFixed(decimals) return sim diff --git a/components/Pages/Pools/AllPoolsTable.tsx b/components/Pages/Pools/AllPoolsTable.tsx index 675a4d62..dcbf49fd 100644 --- a/components/Pages/Pools/AllPoolsTable.tsx +++ b/components/Pages/Pools/AllPoolsTable.tsx @@ -26,7 +26,6 @@ import { walletState } from 'state/atoms/walletAtoms' import Loader from '../../Loader' import Apr from './components/Apr' import PoolName from './components/PoolName' -import TotalLiq from './components/TotalLiq' import Volume from './components/Volume' import useIgnoreCoinhall from './hooks/useIgnoreCoinhall' import { Pool } from './types' @@ -99,12 +98,9 @@ const columns = [ {`Total Liquidity`} ), - cell: (info) => + cell: (info) => ( + {`$${formatPrice(info.getValue())}`} + ), }), columnHelper.accessor('cta', { header: '', @@ -184,9 +180,9 @@ const PoolsTable = ({ {header.isPlaceholder ? null : flexRender( - header.column.columnDef.header, - header.getContext() - )} + header.column.columnDef.header, + header.getContext() + )} ))} diff --git a/components/Pages/Pools/MyPoolsTable.tsx b/components/Pages/Pools/MyPoolsTable.tsx index 7ebf0ccb..52ff5876 100644 --- a/components/Pages/Pools/MyPoolsTable.tsx +++ b/components/Pages/Pools/MyPoolsTable.tsx @@ -8,6 +8,7 @@ import { Tbody, Td, Text, + Tfoot, Th, Thead, Tr, @@ -22,9 +23,7 @@ import { formatPrice } from 'libs/num' import Loader from '../../Loader' import Apr from './components/Apr' -import MyPosition from './components/MyPosition' import PoolName from './components/PoolName' -import TotalLiq from './components/TotalLiq' import Volume from './components/Volume' import useIgnoreCoinhall from './hooks/useIgnoreCoinhall' import { Pool } from './types' @@ -52,20 +51,6 @@ const columns = [ return {info.getValue()} }, }), - columnHelper.accessor('myPosition', { - header: () => ( - - {`My Position`} - - ), - cell: (info) => - - // cell: (info) => ${info.row.original?.myPosition}, - }), columnHelper.accessor('apr', { header: () => ( @@ -111,12 +96,17 @@ const columns = [ {`Total Liquidity`} ), - cell: (info) => + cell: (info) => ( + {`$${formatPrice(info.getValue())}`} + ), + }), + columnHelper.accessor('myPosition', { + header: () => ( + + {`My Position`} + + ), + cell: (info) => ${info.getValue()}, }), columnHelper.accessor('cta', { header: '', @@ -131,12 +121,16 @@ const columns = [ ] const PoolsTable = ({ + show, pools, isLoading, }: { + show: boolean pools: Pool[] isLoading: boolean }) => { + if (!show) return null + const datProvidedByCoinhall = useIgnoreCoinhall() const table = useReactTable({ @@ -179,9 +173,10 @@ const PoolsTable = ({ return ( + + + + + + + + + {table.getHeaderGroups().map((headerGroup, index) => ( {headerGroup.headers.map((header) => ( - {table.getRowModel().rows.map((row, index) => ( - + {row.getVisibleCells().map((cell) => ( - ))} ))} + + + + + + + + + + +
+ {header.isPlaceholder ? null : flexRender( @@ -207,18 +221,64 @@ const PoolsTable = ({
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ {datProvidedByCoinhall && ( + + {`data provided by`} + coinhall + + )} +
- {datProvidedByCoinhall && ( + {/* {datProvidedByCoinhall && ( {`data provided by`} coinhall - )} + )} */}
) } diff --git a/components/Pages/Pools/NewTable.tsx b/components/Pages/Pools/NewTable.tsx new file mode 100644 index 00000000..a122f59a --- /dev/null +++ b/components/Pages/Pools/NewTable.tsx @@ -0,0 +1,153 @@ +import { + Box, + Center, + HStack, + Wrap, + WrapItem, + Text, + VStack, + Button, + Fade, + Flex, +} from '@chakra-ui/react' +import React, { ReactNode } from 'react' +import PoolName from './components/PoolName' +import Loader from '../../Loader' +import Volume from './components/Volume' +import Apr from './components/Apr' +import { formatPrice } from 'libs/num' + +type Props = { + pools: any[] + show: boolean + isLoading: boolean +} + +type ItemProps = { + label: string + value: string + noBorder?: boolean + children?: ReactNode +} + +const Item = ({ + label, + value, + noBorder = false, + children = null, +}: ItemProps) => { + console.log({ children }) + return ( + + {label} + {children ? children : {value}} + + ) +} + +const NewTable = ({ pools, show, isLoading }: Props) => { + if (!show) return null + + if (isLoading) { + return ( + + + + ) + } + + return ( + + + {pools.map((pool) => ( + + + + + + + + + ) + } + /> + + + } + /> + + + My Positiion + ${pool?.myPosition} + + + + + {/* */} + + + + ))} + + + ) +} + +export default NewTable diff --git a/components/Pages/Pools/Pools.tsx b/components/Pages/Pools/Pools.tsx index 3d00b0db..fa81cb7e 100644 --- a/components/Pages/Pools/Pools.tsx +++ b/components/Pages/Pools/Pools.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react' -import { Box, Button, HStack, Text, VStack } from '@chakra-ui/react' +import { Box, HStack, Text, VStack } from '@chakra-ui/react' import { useCosmwasmClient } from 'hooks/useCosmwasmClient' import { useQueriesDataSelector } from 'hooks/useQueriesDataSelector' import { formatPrice, num } from 'libs/num' @@ -22,7 +22,7 @@ type Props = {} const commingSoonNetworks = ['chihuahua', 'injective', 'comdex'] const subqueryNetorks = ['injective'] const COMING_SOON = 'coming soon' -const NoPrice = ["ASH-BDOG", 'ASH-GDOG'] +const NoPrice = ['ASH-BDOG', 'ASH-GDOG'] const Pools: FC = () => { const [allPools, setAllPools] = useState([]) @@ -46,14 +46,19 @@ const Pools: FC = () => { ) const calcuateTotalLiq = (pool) => { - return NoPrice.includes(pool?.pool_id)? 'NA' : pool?.usdLiquidity || pool.liquidity?.available?.total?.dollarValue + return NoPrice.includes(pool?.pool_id) + ? 'NA' + : pool?.usdLiquidity || pool.liquidity?.available?.total?.dollarValue } const calculateMyPostion = (pool) => { - const totalLiq = calcuateTotalLiq(pool); - const {provided, total} = pool.liquidity?.available || {} - return num(provided?.tokenAmount).times(totalLiq).div(total?.tokenAmount).dp(6).toNumber() - + const totalLiq = calcuateTotalLiq(pool) + const { provided, total } = pool.liquidity?.available || {} + return num(provided?.tokenAmount) + .times(totalLiq) + .div(total?.tokenAmount) + .dp(6) + .toNumber() } const initPools = useCallback(async () => { @@ -76,7 +81,7 @@ const Pools: FC = () => { }) const _allPools = await Promise.all( _pools.map(async (pool) => { - const displayAssetOrder = pool.displayName.split('-') + const displayAssetOrder = pool.displayName?.split('-') const isUSDPool = STABLE_COIN_LIST.includes(pool?.pool_assets[0].symbol) || STABLE_COIN_LIST.includes(pool?.pool_assets[1].symbol) @@ -84,7 +89,7 @@ const Pools: FC = () => { const asset0Balance = pairInfos[0] / 10 ** pool.pool_assets[0].decimals const asset1Balance = pairInfos[1] / 10 ** pool.pool_assets[1].decimals let price = 0 - if (displayAssetOrder[0] === pool.assetOrder[0]) { + if (displayAssetOrder?.[0] === pool.assetOrder?.[0]) { price = asset0Balance === 0 ? 0 : asset1Balance / asset0Balance } else { price = asset1Balance === 0 ? 0 : asset0Balance / asset1Balance @@ -101,8 +106,8 @@ const Pools: FC = () => { volume24hr: showCommingSoon ? COMING_SOON : `$${formatPrice(pool.usdVolume24h)}`, - totalLiq: calcuateTotalLiq(pool), - myPosition : calculateMyPostion(pool), + totalLiq: calcuateTotalLiq(pool), + myPosition: calculateMyPostion(pool), liquidity: pool.liquidity, poolAssets: pool.pool_assets, // price: `${isUSDPool ? '$' : ''}${Number(price).toFixed(3)}`, @@ -114,7 +119,7 @@ const Pools: FC = () => { router.push( `/${chainIdParam}/pools/new_position?from=${asset1}&to=${asset2}` ) - } + }, } }) ) @@ -155,8 +160,6 @@ const Pools: FC = () => { const allPoolsForShown = allPools && allPools.filter((item) => !myPoolsId.includes(item.pool)) - console.log({myPools}) - return ( = () => { New Position */} - + diff --git a/components/Pages/Pools/components/MyPosition.tsx b/components/Pages/Pools/components/MyPosition.tsx index 01ce438b..b3cca405 100644 --- a/components/Pages/Pools/components/MyPosition.tsx +++ b/components/Pages/Pools/components/MyPosition.tsx @@ -4,35 +4,45 @@ import React from 'react' import { formatPrice, fromChainAmount } from 'libs/num' type Props = { - pair: string - myPositiionAmount: string | number - providedAssets: number[] + pair: string + myPositiionAmount: string | number + providedAssets: number[] } +const MyPosition = ({ pair, myPositiionAmount, providedAssets }: Props) => { + const [asset1Name, asset2Name] = pair.split('-') + const [asset1Amount, asset2Amount] = providedAssets || [] - -const MyPosition = ({ - pair, - myPositiionAmount, - providedAssets, -}: Props) => { - - const [asset1Name, asset2Name] = pair.split('-') - const [asset1Amount, asset2Amount] = providedAssets || [] - - return ( - - {myPositiionAmount !== 'NA' ? `$${formatPrice(myPositiionAmount)}` : myPositiionAmount} - - {asset1Name}: {fromChainAmount(asset1Amount, 6)} - {asset2Name}: {fromChainAmount(asset2Amount, 6)} - } padding="20px" borderRadius="20px" bg="blackAlpha.900" fontSize="xs" maxW="330px"> - - - - - - ) + return ( + + + {myPositiionAmount !== 'NA' + ? `$${formatPrice(myPositiionAmount)}` + : myPositiionAmount} + + + + {asset1Name}: {fromChainAmount(asset1Amount, 6)} + + + {asset2Name}: {fromChainAmount(asset2Amount, 6)} + +
+ } + padding="20px" + borderRadius="20px" + bg="blackAlpha.900" + fontSize="xs" + maxW="330px" + > + + + + + + ) } -export default MyPosition \ No newline at end of file +export default MyPosition diff --git a/components/Pages/Pools/components/PoolName.tsx b/components/Pages/Pools/components/PoolName.tsx index fcdf0ca0..15ad9988 100644 --- a/components/Pages/Pools/components/PoolName.tsx +++ b/components/Pages/Pools/components/PoolName.tsx @@ -9,13 +9,19 @@ type Props = { const PoolName = ({ poolId, token1Img, token2Img }: Props) => ( - - + + token1-img ( } /> - + token2-img { + const [tokenAReserve, tokenBReserve] = liquidity?.reserves?.total || [] + const [tokenA, tokenB] = poolId?.split('-') || [] + const [assetA, assetB] = poolAssets || [] - -const TotalLiq = ({ - poolId, - liquidity, - totalLiq, - poolAssets -}: Props) => { - const [tokenAReserve, tokenBReserve] = liquidity?.reserves?.total || [] - const [tokenA, tokenB] = poolId?.split('-') || [] - const [assetA, assetB] = poolAssets || [] - - return ( - - {/* {`$${formatPrice(totalLiq)}`} */} - { totalLiq !== 'NA' ? `$${formatPrice(totalLiq)}` : totalLiq} - + {/* {`$${formatPrice(totalLiq)}`} */} + + {totalLiq !== 'NA' ? `$${formatPrice(totalLiq)}` : totalLiq} + + - {tokenA}: {fromChainAmount(tokenBReserve, assetB?.decimals)} - {tokenB}: {fromChainAmount(tokenAReserve, assetA?.decimals)} - : + + {tokenA}: {fromChainAmount(tokenBReserve, assetB?.decimals)} + + + {tokenB}: {fromChainAmount(tokenAReserve, assetA?.decimals)} + + + ) : ( - {tokenA}: {fromChainAmount(tokenAReserve, assetA?.decimals)} - {tokenB}: {fromChainAmount(tokenBReserve, assetB?.decimals)} - } padding="20px" borderRadius="20px" bg="blackAlpha.900" fontSize="xs" maxW="330px"> - - - - - - ) + + {tokenA}: {fromChainAmount(tokenAReserve, assetA?.decimals)} + + + {tokenB}: {fromChainAmount(tokenBReserve, assetB?.decimals)} + + + ) + } + padding="20px" + borderRadius="20px" + bg="blackAlpha.900" + fontSize="xs" + maxW="330px" + > + + + + + + ) } -export default TotalLiq \ No newline at end of file +export default TotalLiq diff --git a/components/Pages/Pools/stakeView.ts b/components/Pages/Pools/stakeView.ts new file mode 100644 index 00000000..e68b9ae1 --- /dev/null +++ b/components/Pages/Pools/stakeView.ts @@ -0,0 +1,23 @@ +// import { atom } from 'recoil' +// +// +// export const stackViewAtom = atom({ +// key: 'stackView', +// // default: true, +// dangerouslyAllowMutability: true, +// effects_UNSTABLE: [ +// ({ onSet, setSelf }) => { +// const CACHE_KEY = `stackView` +// const savedValue = localStorage.getItem(CACHE_KEY) +// console.log('savedValue', savedValue) +// setSelf(savedValue === 'true' ? true : false) +// +// onSet((newValue, oldValue) => { +// localStorage.setItem( +// CACHE_KEY, +// newValue.toString() +// ) +// }) +// }, +// ], +// }) diff --git a/components/Pages/Swap/Swap.tsx b/components/Pages/Swap/Swap.tsx index 5e3b6553..81f95fd0 100644 --- a/components/Pages/Swap/Swap.tsx +++ b/components/Pages/Swap/Swap.tsx @@ -11,9 +11,10 @@ import { walletState } from 'state/atoms/walletAtoms' import defaultTokens from './defaultTokens.json' import useSwap from './hooks/useSwap' -import { TokenItemState, tokenSwapAtom } from './swapAtoms' +import { tokenSwapAtom } from './swapAtoms' import SwapForm from './SwapForm' import SwapSettings from './SwapSettings' +import { TokenItemState } from 'types' type SwapProps = { /* will be used if provided on first render instead of internal state */ @@ -27,7 +28,7 @@ const Swap: FC = ({}) => { const [resetForm, setResetForm] = useState(false) const { chainId, address, network, status } = useRecoilValue(walletState) - const chains = useChains() + const chains: Array = useChains() const { tx, simulated, state, path, minReceive } = useSwap({ reverse }) const { data: poolList } = usePoolsListQuery() const router = useRouter() @@ -148,7 +149,7 @@ const Swap: FC = ({}) => { return ( = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [resetForm, tx?.txStep]) - // const [[_, tokenBBalance] = [], isLoading] = useMultipleTokenBalance([tokenA?.tokenSymbol, tokenB?.tokenSymbol]) - - const { balance: tokenABalance, isLoading: tokanAloading } = useTokenBalance( - tokenA?.tokenSymbol - ) - const { balance: tokenBBalance, isLoading: tokanBloading } = useTokenBalance( - tokenB?.tokenSymbol - ) + const { balance: tokenABalance } = useTokenBalance(tokenA?.tokenSymbol) + const { balance: tokenBBalance } = useTokenBalance(tokenB?.tokenSymbol) const tokenAInfo = useTokenInfo(tokenA?.tokenSymbol) const tokenBInfo = useTokenInfo(tokenB?.tokenSymbol) @@ -107,9 +98,6 @@ const SwapForm: FC = ({ }, [tx?.buttonLabel, tokenB.tokenSymbol, connected, amountA, state?.error]) const onReverse = () => { - // setValue("tokenA", tokenB?.amount === 0 ? {...tokenB, amount : parseFloat(fromChainAmount(simulated?.amount))} : tokenB, { shouldValidate: true }) - // setValue("tokenB", tokenA, { shouldValidate: true }) - const A = { ...tokenB, amount: @@ -213,7 +201,7 @@ const SwapForm: FC = ({ borderRadius="30px" alignItems="flex-start" minH={400} - maxWidth={{ base: 'full', md: 600 }} + maxWidth={{ base: 'full', md: 650 }} gap={4} as="form" onSubmit={handleSubmit(tx?.submit)} @@ -222,24 +210,6 @@ const SwapForm: FC = ({ > - - - Balance:{' '} - - {tokanAloading ? ( - - ) : ( - - {tokenABalance?.toFixed(6)} - - )} - - @@ -435,18 +393,6 @@ const SwapForm: FC = ({ - {/* - - Fee - - - - - - - {fromChainAmount(tx?.fee)} {baseToken?.symbol} - */} - {minReceive && ( = ({ )} - {/* { - (tx?.error && !!!tx.buttonLabel) && ( {tx?.error} ) - } */} ) } diff --git a/components/Pages/Swap/defaultTokens.json b/components/Pages/Swap/defaultTokens.json index 1239d082..24ec6ac5 100644 --- a/components/Pages/Swap/defaultTokens.json +++ b/components/Pages/Swap/defaultTokens.json @@ -49,6 +49,16 @@ "tokenSymbol": "ATOM", "amount": 0 } + ], + "migaloo": [ + { + "tokenSymbol": "axlUSDC", + "amount": 0 + }, + { + "tokenSymbol": "WHALE", + "amount": 0 + } ] }, "testnet": { @@ -91,6 +101,16 @@ "tokenSymbol": "ATOM", "amount": 0 } + ], + "migaloo": [ + { + "tokenSymbol": "WHALE", + "amount": 0 + }, + { + "tokenSymbol": "WILLYz", + "amount": 0 + } ] } } diff --git a/components/Pages/Swap/hooks/createSwapMsg.ts b/components/Pages/Swap/hooks/createSwapMsg.ts index 6f757a19..950a619e 100644 --- a/components/Pages/Swap/hooks/createSwapMsg.ts +++ b/components/Pages/Swap/hooks/createSwapMsg.ts @@ -1,8 +1,7 @@ // import { toBase64 } from "@arthuryeti/terra"; // import { Coin } from 'hooks/useCosmWasmClient' -import { Coin } from '@cosmjs/launchpad' import { coin } from '@cosmjs/proto-signing' -import { LCDClient } from '@terra-money/terra.js' +import { LCDClient } from '@terra-money/feather.js' import { createAsset, isNativeAsset, toAsset } from 'services/asset' import { ReverseSimulationResponse, Route, SimulationResponse } from 'types' import { createExecuteMessage } from 'util/messages' diff --git a/components/Pages/Swap/hooks/useSimulate.ts b/components/Pages/Swap/hooks/useSimulate.ts index fcdab3fe..71085765 100644 --- a/components/Pages/Swap/hooks/useSimulate.ts +++ b/components/Pages/Swap/hooks/useSimulate.ts @@ -1,7 +1,8 @@ import { useMemo } from 'react' import { useQuery } from 'react-query' +import useDebounceValue from 'hooks/useDebounceValue' -import { Wallet } from '../../../../util/wallet-adapters' +import { Wallet } from 'util/wallet-adapters' type QuerySimulate = { client: Wallet @@ -35,15 +36,17 @@ const simulate = ({ client, msg, routerAddress }): Promise => { } const useSimulate = ({ client, msg, routerAddress }) => { + const debounseMsg = useDebounceValue(msg, 300) + const { data, isLoading, error } = useQuery( - ['simulation', msg], + ['simulation', debounseMsg], () => { if (msg == null) return - return simulate({ client, msg, routerAddress }) + return simulate({ client, msg: debounseMsg, routerAddress }) }, { - enabled: !!client && !!msg, + enabled: !!client && !!msg && !!debounseMsg, // onError: (err) => { // console.log({err : (err as any)?.code}) // } diff --git a/components/Pages/Swap/hooks/useSwap.ts b/components/Pages/Swap/hooks/useSwap.ts index eb375213..6af37680 100644 --- a/components/Pages/Swap/hooks/useSwap.ts +++ b/components/Pages/Swap/hooks/useSwap.ts @@ -54,7 +54,7 @@ const useSwap = ({ reverse }) => { num(simulated.amount).times(rate1).times(rate2).toFixed(tokenB?.decimals), tokenB?.decimals ) - }, [simulated, slippageToDecimal]) + }, [simulated, slippageToDecimal, tokenB?.decimals]) const tx = useTransaction({ enabled: !!executeMsg, @@ -77,7 +77,7 @@ const useSwap = ({ reverse }) => { minReceive, state: { error, isLoading }, }), - [tx, simulated, error, isLoading] + [tx, simulated, error, isLoading, minReceive, path] ) } diff --git a/components/Pages/Swap/hooks/useTokenSwap.tsx b/components/Pages/Swap/hooks/useTokenSwap.tsx index 4db059dc..2e161131 100644 --- a/components/Pages/Swap/hooks/useTokenSwap.tsx +++ b/components/Pages/Swap/hooks/useTokenSwap.tsx @@ -13,7 +13,7 @@ import { } from 'junoblocks' import { useQueryMatchingPoolForSwap } from 'queries/useQueryMatchingPoolForSwap' import { useRecoilValue, useSetRecoilState } from 'recoil' -import { directTokenSwap, passThroughTokenSwap } from 'services/swap' +import { passThroughTokenSwap } from 'services/swap' import { TransactionStatus, transactionStatusState, @@ -108,12 +108,12 @@ export const useTokenSwap = ({ /> )) - setTokenSwap(([tokenA, tokenB]) => [ + setTokenSwap(([aToken, bToken]) => [ { - ...tokenA, + ...aToken, amount: 0, }, - tokenB, + bToken, ]) refetchQueries() diff --git a/components/Pages/Swap/hooks/useTokenToTokenPrice.tsx b/components/Pages/Swap/hooks/useTokenToTokenPrice.tsx index 745a3699..870bbea2 100644 --- a/components/Pages/Swap/hooks/useTokenToTokenPrice.tsx +++ b/components/Pages/Swap/hooks/useTokenToTokenPrice.tsx @@ -1,12 +1,12 @@ import { useQuery } from 'react-query' import { useTokenInfo } from 'hooks/useTokenInfo' -import { tokenToTokenPriceQueryWithPools } from 'queries/tokenToTokenPriceQuery' -import { TokenInfo } from 'queries/usePoolsListQuery' import { useQueryMatchingPoolForSwap } from 'queries/useQueryMatchingPoolForSwap' import { useRecoilValue } from 'recoil' import { walletState } from 'state/atoms/walletAtoms' import { DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL } from 'util/constants' +import { TokenInfo } from 'queries/usePoolsListQuery' +import { tokenToTokenPriceQueryWithPools } from 'queries/tokenToTokenPriceQuery' type UseTokenPairsPricesArgs = { tokenASymbol: TokenInfo['symbol'] diff --git a/components/Pages/Swap/hooks/useTransaction.tsx b/components/Pages/Swap/hooks/useTransaction.tsx index da741391..4995b6a7 100644 --- a/components/Pages/Swap/hooks/useTransaction.tsx +++ b/components/Pages/Swap/hooks/useTransaction.tsx @@ -99,15 +99,7 @@ export const useTransaction = ({ setError('Insufficient Funds') setButtonLabel('Insufficient Funds') throw new Error('Insufficient Funds') - } - else if ( /Operation disabled, swap/i.test(error.toString())) { - console.error(error) - setTxStep(TxStep.Idle) - setError('Pair is disabled for swap') - setButtonLabel('Pair is disabled for swap') - throw new Error('Pair is disabled for swap') - } - else if (/Max spread assertion/i.test(error.toString())) { + } else if (/Max spread assertion/i.test(error.toString())) { console.error(error) setTxStep(TxStep.Idle) setError('Try increasing slippage') @@ -133,7 +125,6 @@ export const useTransaction = ({ debouncedMsgs != null && txStep == TxStep.Idle && error == null && - !!client && enabled, refetchOnWindowFocus: false, retry: false, @@ -206,7 +197,7 @@ export const useTransaction = ({ description: ( {' '} From: {tokenA.symbol} To: {tokenB.symbol}{' '} @@ -227,7 +218,7 @@ export const useTransaction = ({ if (txHash == null) { return } - return client.client.getTx(txHash) + return client.getTx(txHash) }, { enabled: txHash != null, diff --git a/components/Pages/Swap/swapAtoms.ts b/components/Pages/Swap/swapAtoms.ts index 5f19894c..577f12f4 100644 --- a/components/Pages/Swap/swapAtoms.ts +++ b/components/Pages/Swap/swapAtoms.ts @@ -1,10 +1,5 @@ import { atom } from 'recoil' - -export type TokenItemState = { - tokenSymbol: string - amount: number - decimals: number -} +import { TokenItemState } from 'types' export const tokenSwapAtom = atom<[TokenItemState, TokenItemState]>({ key: 'tokenSwap', diff --git a/components/Status.tsx b/components/Status.tsx index 1024e977..88ad192c 100644 --- a/components/Status.tsx +++ b/components/Status.tsx @@ -1,14 +1,14 @@ import React, { useMemo } from 'react' import { BsCircleFill } from 'react-icons/bs' -import { useMutation, useQuery, useQueryClient } from 'react-query' +import { useQuery } from 'react-query' -import { Box, HStack, Icon, Text } from '@chakra-ui/react' -import { useChainInfo, useChains } from 'hooks/useChainInfo' -import { useRecoilState, useRecoilValue } from 'recoil' +import { HStack, Icon, Text } from '@chakra-ui/react' +import { useChains } from 'hooks/useChainInfo' +import { useRecoilValue } from 'recoil' import { walletState } from 'state/atoms/walletAtoms' const Status = () => { - const chains = useChains() + const chains: Array = useChains() const { chainId } = useRecoilValue(walletState) const url = useMemo(() => { diff --git a/components/Vaults/ManagePoistion/DepositForm.tsx b/components/Vaults/ManagePoistion/DepositForm.tsx index 03abff55..6b2169db 100644 --- a/components/Vaults/ManagePoistion/DepositForm.tsx +++ b/components/Vaults/ManagePoistion/DepositForm.tsx @@ -1,22 +1,9 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' -import { InfoOutlineIcon } from '@chakra-ui/icons' -import { - Box, - Button, - HStack, - Spinner, - Text, - Tooltip, - useToast, - VStack, -} from '@chakra-ui/react' +import { Button, useToast, VStack } from '@chakra-ui/react' import AssetInput from 'components/AssetInput' import Finder from 'components/Finder' -import { useBaseTokenInfo } from 'hooks/useTokenInfo' -import { fromChainAmount } from 'libs/num' -import { useRouter } from 'next/router' -import { useRecoilState, useRecoilValue } from 'recoil' +import { useRecoilValue } from 'recoil' import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' import useDepost from '../hooks/useDeposit' @@ -35,7 +22,6 @@ type Props = { const DepositForm = ({ connected, - isLoading, balance, defaultToken, edgeTokenList = [], @@ -43,8 +29,6 @@ const DepositForm = ({ vaultAddress, refetch, }: Props) => { - const baseToken = useBaseTokenInfo() - const [token, setToken] = useState({ amount: 0, tokenSymbol: defaultToken, @@ -78,8 +62,6 @@ const DepositForm = ({ const buttonLabel = useMemo(() => { // TODO: Note for later, Select Token is commented if (connected !== `@wallet-state/connected`) return 'Connect Wallet' - // else if (!token?.tokenSymbol) - // return 'Select token' else if (!!!token?.amount) return 'Enter Amount' else if (tx?.buttonLabel) return tx?.buttonLabel else return 'Deposit' @@ -107,18 +89,6 @@ const DepositForm = ({ onSubmit={onSubmit} > - - - Balance:{' '} - - {isLoading ? ( - - ) : ( - - {balance?.toFixed(6)} - - )} - {buttonLabel} - - {/* - - - Fee - - - - - - - {fromChainAmount(tx?.fee)} {baseToken?.symbol} - - */} - - {/* { - (tx?.error && !!!tx.buttonLabel) && ( {tx?.error} ) - } */} ) } diff --git a/components/Vaults/ManagePoistion/ManagePosition.tsx b/components/Vaults/ManagePoistion/ManagePosition.tsx index 89a05100..1e99affa 100644 --- a/components/Vaults/ManagePoistion/ManagePosition.tsx +++ b/components/Vaults/ManagePoistion/ManagePosition.tsx @@ -25,8 +25,8 @@ import WithdrawForm from './WithdrawForm' const ManagePosition = () => { const router: NextRouter = useRouter() - const { vaults, isLoading, refetch: vaultsRefetch } = useVault() - const chains = useChains() + const { vaults, refetch: vaultsRefetch } = useVault() + const chains: Array = useChains() const params = new URLSearchParams(location.search) const { chainId, key, address, status } = useRecoilValue(walletState) const vaultId = params.get('vault') || 'JUNO' diff --git a/components/Vaults/ManagePoistion/WithdrawForm.tsx b/components/Vaults/ManagePoistion/WithdrawForm.tsx index d9cf7f25..e08cdfff 100644 --- a/components/Vaults/ManagePoistion/WithdrawForm.tsx +++ b/components/Vaults/ManagePoistion/WithdrawForm.tsx @@ -1,17 +1,10 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' -import { - Button, - HStack, - Spinner, - Text, - useToast, - VStack, -} from '@chakra-ui/react' +import { Button, useToast, VStack } from '@chakra-ui/react' import AssetInput from 'components/AssetInput' import Finder from 'components/Finder' import { fromChainAmount } from 'libs/num' -import { useRecoilState, useRecoilValue } from 'recoil' +import { useRecoilValue } from 'recoil' import { walletState, WalletStatusType } from 'state/atoms/walletAtoms' import { TxStep } from '../hooks/useTransaction' @@ -30,12 +23,10 @@ type Props = { const WithdrawForm = ({ connected, - isLoading, balance, defaultToken, vaultAddress, lpToken, - assetBlance, refetch, }: Props) => { const [token, setToken] = useState({ @@ -60,7 +51,7 @@ const WithdrawForm = ({ isClosable: true, }) }, - [token] + [refetch, chainId, toast] ) const { tx } = useWithdraw({ vaultAddress, lpToken, token, onSuccess }) @@ -83,7 +74,7 @@ const WithdrawForm = ({ setToken({ ...token, amount: 0 }) tx?.reset() } - }, [tx.txStep]) + }, [tx, token, setToken]) return ( - - - LP Balance:{' '} - - {isLoading ? ( - - ) : ( - - - {Number(fromChainAmount(balance))?.toFixed(6)} - - - ( {token?.tokenSymbol}:{' '} - {Number(fromChainAmount(assetBlance))?.toFixed(6)} ) - - - )} - setToken(value)} @@ -137,17 +109,6 @@ const WithdrawForm = ({ > {buttonLabel} - - {/* - - Fees - {fromChainAmount(tx?.fee)} - - */} - - {/* { - (tx?.error && !!!tx.buttonLabel) && ( {tx?.error} ) - } */} ) } diff --git a/components/Vaults/NewPosition.tsx b/components/Vaults/NewPosition.tsx index 0eb94522..ff824565 100644 --- a/components/Vaults/NewPosition.tsx +++ b/components/Vaults/NewPosition.tsx @@ -1,18 +1,7 @@ import { useEffect, useMemo } from 'react' import { ArrowBackIcon } from '@chakra-ui/icons' -import { - Box, - HStack, - IconButton, - Tab, - TabList, - TabPanel, - TabPanels, - Tabs, - Text, - VStack, -} from '@chakra-ui/react' +import { Box, HStack, IconButton, Text, VStack } from '@chakra-ui/react' import { useChains } from 'hooks/useChainInfo' import { useTokenBalance } from 'hooks/useTokenBalance' import { NextRouter, useRouter } from 'next/router' @@ -24,9 +13,9 @@ import DepositForm from './ManagePoistion/DepositForm' const NewPosition = () => { const router: NextRouter = useRouter() - const { vaults, isLoading, refetch: vaultsRefetch } = useVault() + const { vaults, refetch: vaultsRefetch } = useVault() const params = new URLSearchParams(location.search) - const chains = useChains() + const chains: Array = useChains() const { chainId, key, address, status } = useRecoilValue(walletState) const vaultId = params.get('vault') || 'JUNOX' diff --git a/components/Vaults/VaultName.tsx b/components/Vaults/VaultName.tsx index 2edd9b63..1ee74bf8 100644 --- a/components/Vaults/VaultName.tsx +++ b/components/Vaults/VaultName.tsx @@ -12,6 +12,7 @@ const VaultName = ({ vaultId, tokenImage }: Props) => ( vault token { tokenInfo, }), } - }, [amount, tokenInfo]) + }, [amount, tokenInfo, vaultAddress, address]) const tx = useTransaction({ isNative: tokenInfo?.native, diff --git a/components/Vaults/hooks/useTransaction.tsx b/components/Vaults/hooks/useTransaction.tsx index fb778ec5..17f50d42 100644 --- a/components/Vaults/hooks/useTransaction.tsx +++ b/components/Vaults/hooks/useTransaction.tsx @@ -84,6 +84,21 @@ export const useTransaction = ({ const [buttonLabel, setButtonLabel] = useState(null) const queryClient = useQueryClient() + const { data: txInfo } = useQuery( + ['txInfo', txHash], + () => { + if (txHash == null) { + return + } + + return client.getTx(txHash) + }, + { + enabled: txHash != null, + retry: true, + } + ) + const { data: fee } = useQuery( ['fee', amount, debouncedMsgs, error], async () => { @@ -222,21 +237,6 @@ export const useTransaction = ({ } ) - const { data: txInfo } = useQuery( - ['txInfo', txHash], - () => { - if (txHash == null) { - return - } - - return client.getTx(txHash) - }, - { - enabled: txHash != null, - retry: true, - } - ) - const reset = () => { setError(null) setTxHash(undefined) @@ -288,7 +288,7 @@ export const useTransaction = ({ error, reset, } - }, [txStep, txInfo, txHash, error, reset, fee]) + }, [txStep, txInfo, txHash, error, reset, fee, buttonLabel, submit]) } export default useTransaction diff --git a/components/Vaults/hooks/useWithdraw.tsx b/components/Vaults/hooks/useWithdraw.tsx index 99ee09ef..224eb2a2 100644 --- a/components/Vaults/hooks/useWithdraw.tsx +++ b/components/Vaults/hooks/useWithdraw.tsx @@ -46,7 +46,7 @@ const useWithdraw = ({ lpToken, }), } - }, [amount, tokenInfo]) + }, [amount, tokenInfo, vaultAddress, address, lpToken]) const tx = useTransaction({ isNative: false, diff --git a/components/Wallet/ChainSelect/ChainList/ChainItem.tsx b/components/Wallet/ChainSelect/ChainList/ChainItem.tsx index d0c9c698..d18951a5 100644 --- a/components/Wallet/ChainSelect/ChainList/ChainItem.tsx +++ b/components/Wallet/ChainSelect/ChainList/ChainItem.tsx @@ -4,8 +4,21 @@ import { useQueryClient } from 'react-query' import { HStack, Image, ListIcon, ListItem, Text } from '@chakra-ui/react' -function ChainItem({ chain, index, onChange, onClose, chainList, active }) { +function ChainItem({ + chain, + index, + onChange, + onClose, + chainList, + active, + walletState, +}) { const queryClient = useQueryClient() + if ( + walletState?.activeWallet == 'station' && + (chain?.chainId == 'injective-1' || chain?.chainId == 'comdex-1') + ) + return null return ( chain.chainId !== 'comdex-1' && chain.chainId !== 'injective-1' + // ); + // } + return ( {chains.map((chain, index) => ( @@ -19,6 +25,7 @@ function ChainList({ onChange, onClose, currentWalletState }) { onClose={onClose} chainList={chains} active={currentWalletState?.chainId === chain?.chainId} + walletState={currentWalletState} /> ))} diff --git a/components/Wallet/ChainSelect/ChainSelectPopoverContent.tsx b/components/Wallet/ChainSelect/ChainSelectPopoverContent.tsx index e13ba504..a4e4d9ba 100644 --- a/components/Wallet/ChainSelect/ChainSelectPopoverContent.tsx +++ b/components/Wallet/ChainSelect/ChainSelectPopoverContent.tsx @@ -12,8 +12,6 @@ import ChainList from './ChainList/ChainList' import NetworkForm from './NetworkForm/NetworkForm' function ChainSelectPopoverContent({ onChange, onClose, currentWalletState }) { - if (currentWalletState?.activeWallet === 'station') return null - return ( {connected || connectedWallet ? ( {denom} - {!isStation && } + ) : ( { onOpen={onOpen} onClose={onClose} > - + diff --git a/components/Wallet/ConnectedWalletWithDisconnect/ConnectedWallet/TruncatedAddress.tsx b/components/Wallet/ConnectedWalletWithDisconnect/ConnectedWallet/TruncatedAddress.tsx index e8ebdd9d..7e1ebb1f 100644 --- a/components/Wallet/ConnectedWalletWithDisconnect/ConnectedWallet/TruncatedAddress.tsx +++ b/components/Wallet/ConnectedWalletWithDisconnect/ConnectedWallet/TruncatedAddress.tsx @@ -7,21 +7,21 @@ import { walletState } from 'state/atoms/walletAtoms' import { truncate } from 'util/truncate' function TruncatedAddress({ connected }) { - const { address } = useRecoilValue(walletState) + const { address, chainId } = useRecoilValue(walletState) const connectedWallet = useConnectedWallet() - + // TODO: review, is this okay const truncatWalletAddress = (addr: string) => { const chainName = addr?.substring(0, addr.indexOf('1')) return connected ? `${chainName}${truncate(address, [0, 4])}` - : `${chainName}${truncate(connectedWallet.walletAddress, [0, 4])}` + : `${chainName}${truncate(connectedWallet.addresses[chainId], [0, 4])}` } return ( {connected ? truncatWalletAddress(address) - : truncatWalletAddress(connectedWallet.walletAddress)} + : truncatWalletAddress(connectedWallet.addresses[chainId])} ) } diff --git a/components/Wallet/Modal/CosmostationConnectButton.tsx b/components/Wallet/Modal/CosmostationConnectButton.tsx index 065ed705..85a66de6 100644 --- a/components/Wallet/Modal/CosmostationConnectButton.tsx +++ b/components/Wallet/Modal/CosmostationConnectButton.tsx @@ -3,20 +3,21 @@ import React, { useCallback } from 'react' import { Button, HStack, Text } from '@chakra-ui/react' import CosmostationWalletIcon from 'components/icons/CosmostationWalletIcon' import useConnectCosmostation from 'hooks/useConnectCosmostation' -import { useRecoilValue } from 'recoil' -import { walletState } from 'state/atoms/walletAtoms' function CosmostationConnectButton({ onCloseModal }) { const { setCosmostationAndConnect } = useConnectCosmostation() - const { chainId, activeWallet, network } = useRecoilValue(walletState) const setCosmostationMemo = useCallback(() => { setCosmostationAndConnect() onCloseModal() - }, [activeWallet, chainId, network]) + }, [onCloseModal, setCosmostationAndConnect]) return ( -