diff --git a/web-ui/.env b/web-ui/.env index 638c38600..17302de3c 100644 --- a/web-ui/.env +++ b/web-ui/.env @@ -15,13 +15,15 @@ NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_JUNO="https://lcd.juno-1.quicksilver.zone" NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_JUNO="https://rpc.juno-1.quicksilver.zone" NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_DYDX="https://lcd.dydx-mainnet-1.quicksilver.zone" NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_DYDX="https://rpc.dydx-mainnet-1.quicksilver.zone" +NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_SAGA="https://lcd.ssc-1.quicksilver.zone/" +NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_SAGA="https://rpc.ssc-1.quicksilver.zone/" NEXT_PUBLIC_QUICKSILVER_API="https://lcd.quicksilver.zone" NEXT_PUBLIC_QUICKSILVER_DATA_API="https://data.quicksilver.zone" ZONE_URL="quicksilver.zone" APY_ZONES_ENDPOINT = "https://chains.cosmos.directory" NEXT_PUBLIC_OSMOSIS_API="https://api.osmosis.zone" -NEXT_PUBLIC_WHITELISTED_DENOM="uatom,ustars,uosmo,usomm,uregen,ujuno,udydx" -NEXT_PUBLIC_WHITELISTED_ZONES="osmosis-1,stargaze-1,regen-1,cosmoshub-4,sommelier-3,juno-1,dydx-mainnet-1" +NEXT_PUBLIC_WHITELISTED_DENOM="uatom,ustars,uosmo,usomm,uregen,ujuno,udydx,usaga" +NEXT_PUBLIC_WHITELISTED_ZONES="osmosis-1,stargaze-1,regen-1,cosmoshub-4,sommelier-3,juno-1,dydx-mainnet-1,ssc-1" NEXT_PUBLIC_COSMOSHUB_CHAIN_ID=cosmoshub-4 NEXT_PUBLIC_OSMOSIS_CHAIN_ID=osmosis-1 NEXT_PUBLIC_STARGAZE_CHAIN_ID=stargaze-1 @@ -29,4 +31,5 @@ NEXT_PUBLIC_REGEN_CHAIN_ID=regen-1 NEXT_PUBLIC_SOMMELIER_CHAIN_ID=sommelier-3 NEXT_PUBLIC_JUNO_CHAIN_ID=juno-1 NEXT_PUBLIC_DYDX_CHAIN_ID=dydx-mainnet-1 +NEXT_PUBLIC_DYDX_CHAIN_ID=ssc-1 NEXT_PRIVATE_WALLET_CONNECT_TOKEN="41a0749c331d209190beeac1c2530c90" \ No newline at end of file diff --git a/web-ui/bun.lockb b/web-ui/bun.lockb index 77d7b3349..3098dde1f 100755 Binary files a/web-ui/bun.lockb and b/web-ui/bun.lockb differ diff --git a/web-ui/components/Assets/assetsGrid.tsx b/web-ui/components/Assets/assetsGrid.tsx index d0e5fde3d..5884fe78f 100644 --- a/web-ui/components/Assets/assetsGrid.tsx +++ b/web-ui/components/Assets/assetsGrid.tsx @@ -17,16 +17,12 @@ import { } from '@chakra-ui/react'; import React, { useEffect, useState } from 'react'; - - import { shiftDigits, formatQasset, formatNumber } from '@/utils'; import QDepositModal from './modals/qTokenDepositModal'; import QWithdrawModal from './modals/qTokenWithdrawlModal'; - - interface AssetCardProps { address: string; assetName: string; @@ -88,6 +84,7 @@ const AssetCard: React.FC = ({ address, assetName, balance, apy, 'regen-1': 'regen', 'juno-1': 'juno', 'dydx-mainnet-1': 'dydx', + 'ssc-1': 'saga', }; const getChainName = (chainId: string) => { @@ -179,38 +176,35 @@ const AssetCard: React.FC = ({ address, assetName, balance, apy, - - On Quicksilver - - {!balance || !liquidRewards ? ( - - ) : ( - - {formatNumber(parseFloat(balance))} {assetName} - - )} - - {!balance || !liquidRewards ?( - <> - - - - ) : ( - Number(balance) > 0 && ( - <> - - Redeem For - - - {formatNumber(parseFloat(balance) / Number(redemptionRates))} {assetName.replace('q', '')} - - - ) - )} - + + On Quicksilver + + {!balance || !liquidRewards ? ( + + ) : ( + + {formatNumber(parseFloat(balance))} {assetName} + + )} + + {!balance || !liquidRewards ? ( + <> + + + + ) : ( + Number(balance) > 0 && ( + <> + + Redeem For + + + {formatNumber(parseFloat(balance) / Number(redemptionRates))} {assetName.replace('q', '')} + + + ) + )} + - + - - Interchain - - {!balance || !liquidRewards || !interchainBalance ? ( - - ) : ( - - {formatNumber(parseFloat(interchainBalance))} {assetName} - - )} - - {!balance || !liquidRewards || !interchainBalance ? ( - <> - - - - ) : ( - Number(interchainBalance) > 0 && ( - <> - - Redeem For - - - {formatNumber(parseFloat(interchainBalance) / Number(redemptionRates))} {assetName.replace('q', '')} - - - ) - )} - + + Interchain + + {!balance || !liquidRewards || !interchainBalance ? ( + + ) : ( + + {formatNumber(parseFloat(interchainBalance))} {assetName} + + )} + + {!balance || !liquidRewards || !interchainBalance ? ( + <> + + + + ) : ( + Number(interchainBalance) > 0 && ( + <> + + Redeem For + + + {formatNumber(parseFloat(interchainBalance) / Number(redemptionRates))} {assetName.replace('q', '')} + + + ) + )} + = ({ address, isWalletConnected }) => { const networks = process.env.NEXT_PUBLIC_CHAIN_ENV === 'mainnet' ? prodNetworks : devNetworks; - const chains = ['Cosmos', 'Osmosis', 'Dydx', 'Stargaze', 'Regen', 'Sommelier', 'Juno']; + const chains = ['Cosmos', 'Osmosis', 'Dydx', 'Stargaze', 'Regen', 'Sommelier', 'Juno', 'Saga']; const [currentChainIndex, setCurrentChainIndex] = useState(0); const [isBottomVisible, setIsBottomVisible] = useState(true); @@ -55,8 +53,6 @@ const StakingIntent: React.FC = ({ address, isWalletConnecte const { intent, refetch } = useIntentQuery(currentNetwork.chainName, address ?? ''); - - interface ValidatorDetails { moniker: string; logoUrl: string | undefined; diff --git a/web-ui/components/Assets/modals/qTokenDepositModal.tsx b/web-ui/components/Assets/modals/qTokenDepositModal.tsx index b26c95855..9a50129cd 100644 --- a/web-ui/components/Assets/modals/qTokenDepositModal.tsx +++ b/web-ui/components/Assets/modals/qTokenDepositModal.tsx @@ -30,7 +30,7 @@ import { handleSelectChainDropdown, ChainOption, ChooseChainInfo } from '@/compo import { useTx } from '@/hooks'; import { useFeeEstimation } from '@/hooks/useFeeEstimation'; import { ibcDenomDepositMapping } from '@/state/chains/prod'; -import { getCoin, getIbcInfo } from '@/utils'; +import { getCoin, getExponent, getIbcInfo } from '@/utils'; export interface QDepositModalProps { token: string; @@ -90,8 +90,8 @@ const QDepositModal: React.FC = ({ token, isOpen, onClose, i const onSubmitClick = async () => { setIsLoading(true); - - const transferAmount = new BigNumber(amount).shiftedBy(6).toString(); + const exp = token === 'qDYDX' ? 18 : 6; + const transferAmount = new BigNumber(amount).shiftedBy(exp).toString(); const { source_port, source_channel } = getIbcInfo(fromChain ?? '', toChain ?? ''); @@ -160,7 +160,9 @@ const QDepositModal: React.FC = ({ token, isOpen, onClose, i - Deposit {token} Tokens + + Deposit {token} Tokens + {/* Chain Selection Dropdown */} @@ -205,7 +207,13 @@ const QDepositModal: React.FC = ({ token, isOpen, onClose, i size="xs" _active={{ transform: 'scale(0.95)', color: 'complimentary.800' }} _hover={{ bgColor: 'transparent', color: 'complimentary.400' }} - onClick={() => setAmount(BigNumber(parseFloat(maxAmount) / 2).toFixed(6).toString())} + onClick={() => + setAmount( + BigNumber(parseFloat(maxAmount) / 2) + .toFixed(6) + .toString(), + ) + } > Half diff --git a/web-ui/components/Assets/modals/qTokenWithdrawlModal.tsx b/web-ui/components/Assets/modals/qTokenWithdrawlModal.tsx index 865540633..d307575a0 100644 --- a/web-ui/components/Assets/modals/qTokenWithdrawlModal.tsx +++ b/web-ui/components/Assets/modals/qTokenWithdrawlModal.tsx @@ -31,7 +31,7 @@ import { useTx } from '@/hooks'; import { useFeeEstimation } from '@/hooks/useFeeEstimation'; import { useIbcBalanceQuery } from '@/hooks/useQueries'; import { ibcDenomWithdrawMapping } from '@/state/chains/prod'; -import { getCoin, getIbcInfo } from '@/utils'; +import { getCoin, getExponent, getIbcInfo } from '@/utils'; interface QDepositModalProps { max: string; @@ -88,8 +88,9 @@ const QWithdrawModal: React.FC = ({ max, token, isOpen, onCl const onSubmitClick = async () => { setIsLoading(true); + const exp = token === 'qDYDX' ? 18 : 6; - const transferAmount = new BigNumber(amount).shiftedBy(6).toString(); + const transferAmount = new BigNumber(amount).shiftedBy(exp).toString(); const { source_port, source_channel } = getIbcInfo(fromChain ?? '', toChain ?? ''); @@ -159,7 +160,9 @@ const QWithdrawModal: React.FC = ({ max, token, isOpen, onCl - Withdraw {token} Tokens + + Withdraw {token} Tokens + {/* Chain Selection Dropdown */} @@ -196,7 +199,7 @@ const QWithdrawModal: React.FC = ({ max, token, isOpen, onCl placeholder="Enter amount" /> - + diff --git a/web-ui/components/Assets/portfolio.tsx b/web-ui/components/Assets/portfolio.tsx index 8dbd925f7..b58668f00 100644 --- a/web-ui/components/Assets/portfolio.tsx +++ b/web-ui/components/Assets/portfolio.tsx @@ -116,10 +116,10 @@ const MyPortfolio: React.FC = ({ {isLoading && ( - - - - + + + + )} {totalValue === 0 && ( @@ -185,7 +185,7 @@ interface PortfolioItemProps { const PortfolioItem: React.FC = ({ title, amount, qTokenPrice, index }) => { const tokenValue = Number(amount) * qTokenPrice; - const imgType = title === 'qATOM' ? 'svg' : 'png'; + const imgType = title === 'qATOM' || title === 'qSAGA' ? 'svg' : 'png'; return ( = ({ address, isWalletConnected }) => { - const chains = ['Cosmos', 'Stargaze', 'Osmosis', 'Regen', 'Sommelier', 'Juno', 'Dydx']; + const chains = ['Cosmos', 'Stargaze', 'Osmosis', 'Regen', 'Sommelier', 'Juno', 'Dydx', 'Saga']; const [currentChainIndex, setCurrentChainIndex] = useState(0); // Switcher lets us use a pretty name for the chain in the UI, but query the chain by its actual name. @@ -56,6 +56,8 @@ const UnbondingAssetsTable: React.FC = ({ address, is newChainName = 'juno'; } else if (currentChainName === 'Dydx') { newChainName = 'dydx'; + } else if (currentChainName === 'Saga') { + newChainName = 'saga'; } else { // Default case newChainName = currentChainName; diff --git a/web-ui/components/Staking/networkSelectButton.tsx b/web-ui/components/Staking/networkSelectButton.tsx index cb6ff9bf2..d07ff155a 100644 --- a/web-ui/components/Staking/networkSelectButton.tsx +++ b/web-ui/components/Staking/networkSelectButton.tsx @@ -80,7 +80,6 @@ export const NetworkSelect: React.FC = ({ buttonTextColor = 'wh _active={{ bgColor: 'rgba(255,128,0, 0.25)', }} - _focus={{ bgColor: 'rgba(255,128,0, 0.25)', }} diff --git a/web-ui/hooks/useGrpcQueryClient.ts b/web-ui/hooks/useGrpcQueryClient.ts index 4850c0c91..26daf293a 100644 --- a/web-ui/hooks/useGrpcQueryClient.ts +++ b/web-ui/hooks/useGrpcQueryClient.ts @@ -18,6 +18,7 @@ export const useGrpcQueryClient = (chainName: string) => { osmosis: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_OSMOSIS : process.env.NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_OSMOSIS, juno: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_JUNO : process.env.NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_JUNO, dydx: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_DYDX : process.env.NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_DYDX, + saga: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_SAGA : process.env.NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_SAGA, }; grpcEndpoint = endpoints[chainName]; @@ -34,5 +35,6 @@ export const useGrpcQueryClient = (chainName: string) => { return { grpcQueryClient: grpcQueryClientQuery.data, + grpcQueryClientError: grpcQueryClientQuery.error, }; }; diff --git a/web-ui/hooks/useRpcQueryClient.ts b/web-ui/hooks/useRpcQueryClient.ts index 90c22bbed..27e06ec2f 100644 --- a/web-ui/hooks/useRpcQueryClient.ts +++ b/web-ui/hooks/useRpcQueryClient.ts @@ -1,3 +1,4 @@ +import { saga } from '@chain-registry/assets'; import { HttpEndpoint } from '@cosmjs/stargate'; import { useQuery } from '@tanstack/react-query'; import { cosmos } from 'interchain-query'; @@ -20,6 +21,7 @@ export const useRpcQueryClient = (chainName: string) => { osmosis: env === 'testnet' ? process.env.TESTNET_RPC_ENDPOINT_OSMOSIS : process.env.MAINNET_RPC_ENDPOINT_OSMOSIS, juno: env === 'testnet' ? process.env.TESTNET_RPC_ENDPOINT_JUNO : process.env.MAINNET_RPC_ENDPOINT_JUNO, dydx: env === 'testnet' ? process.env.TESTNET_RPC_ENDPOINT_DYDX : process.env.MAINNET_RPC_ENDPOINT_DYDX, + saga: env === 'testnet' ? process.env.TESTNET_RPC_ENDPOINT_SAGA : process.env.MAINNET_RPC_ENDPOINT_SAGA, }; rpcEndpoint = endpoints[chainName]; diff --git a/web-ui/package.json b/web-ui/package.json index 0a78ab208..504cfef56 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -8,10 +8,12 @@ "dev": "next dev", "start": "next start", "lint": "next lint --fix", - "export": "next export" + "export": "next export", + "remove": "rm -rf node_modules/ bun.lockb $HOME/.bun/install/cache/", + "update-deps": "bunx npm-check-updates --root --format group -i" }, "dependencies": { - "@chain-registry/assets": "^1.26.0", + "@chain-registry/assets": "^1.42.0", "@chakra-ui/icons": "^2.0.12", "@chakra-ui/react": "2.8.2", "@chakra-ui/system": "^2.1.3", @@ -29,13 +31,13 @@ "@interchain-ui/react": "1.10.0", "@osmonauts/lcd": "^1.0.3", "@radix-ui/react-icons": "^1.3.0", - "@skip-router/core": "2.0.5", + "@skip-router/core": "2.4.1", "@tanstack/react-query": "4.36.1", "@tanstack/react-query-devtools": "4.36.1", "@types/crypto-js": "^4.2.1", "@vercel/speed-insights": "^1.0.10", "bech32": "^2.0.0", - "chain-registry": "1.33.17", + "chain-registry": "1.41.0", "chakra-react-select": "^4.7.6", "cosmjs-types": "0.5.0", "crypto-js": "^4.2.0", diff --git a/web-ui/pages/_app.tsx b/web-ui/pages/_app.tsx index d99029beb..e65cf6697 100644 --- a/web-ui/pages/_app.tsx +++ b/web-ui/pages/_app.tsx @@ -68,6 +68,7 @@ function QuickApp({ Component, pageProps }: AppProps) { env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_OSMOSIS : process.env.NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_OSMOSIS, juno: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_JUNO : process.env.NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_JUNO, dydx: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_DYDX : process.env.NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_DYDX, + saga: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_SAGA : process.env.NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_SAGA, }; const lcdEndpoints = { @@ -86,6 +87,7 @@ function QuickApp({ Component, pageProps }: AppProps) { env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_OSMOSIS : process.env.NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_OSMOSIS, juno: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_JUNO : process.env.NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_JUNO, dydx: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_DYDX : process.env.NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_DYDX, + saga: env === 'testnet' ? process.env.NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_SAGA : process.env.NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_SAGA, }; const modalThemeOverrides: ThemeCustomizationProps = { @@ -217,7 +219,6 @@ function QuickApp({ Component, pageProps }: AppProps) { return ( - - +
diff --git a/web-ui/pages/assets.tsx b/web-ui/pages/assets.tsx index 95667088d..0ded3ffff 100644 --- a/web-ui/pages/assets.tsx +++ b/web-ui/pages/assets.tsx @@ -1,3 +1,4 @@ +import { saga } from '@chain-registry/assets'; import { Box, Container, Flex, SlideFade, Spacer, Text, Center } from '@chakra-ui/react'; import { useChain } from '@cosmos-kit/react'; import dynamic from 'next/dynamic'; @@ -31,8 +32,8 @@ export interface PortfolioItemInterface { function Home() { const { address } = useChain('quicksilver'); - const tokens = ['atom', 'osmo', 'stars', 'regen', 'somm', 'juno', 'dydx']; - const getExponent = (denom: string) => ['qdydx', 'aqdydx'].includes(denom) ? 18 : 6; + const tokens = ['atom', 'osmo', 'stars', 'regen', 'somm', 'juno', 'dydx', 'saga']; + const getExponent = (denom: string) => (['qdydx', 'aqdydx'].includes(denom) ? 18 : 6); const { grpcQueryClient } = useGrpcQueryClient('quicksilver'); @@ -44,8 +45,6 @@ function Home() { const { liquidRewards, refetch: liquidRefetch } = useLiquidRewardsQuery(address ?? ''); const { authData, authError, authRefetch } = useAuthChecker(address ?? ''); - - const refetchAll = () => { qRefetch(); liquidRefetch(); @@ -60,6 +59,7 @@ function Home() { const SOMMELIER_CHAIN_ID = process.env.NEXT_PUBLIC_SOMMELIER_CHAIN_ID; const JUNO_CHAIN_ID = process.env.NEXT_PUBLIC_JUNO_CHAIN_ID; const DYDX_CHAIN_ID = process.env.NEXT_PUBLIC_DYDX_CHAIN_ID; + const SAGA_CHAIN_ID = process.env.NEXT_PUBLIC_SAGA_CHAIN_ID; const tokenToChainIdMap: { [key: string]: string | undefined } = useMemo(() => { return { @@ -70,43 +70,55 @@ function Home() { somm: SOMMELIER_CHAIN_ID, juno: JUNO_CHAIN_ID, dydx: DYDX_CHAIN_ID, + saga: SAGA_CHAIN_ID, }; - }, [COSMOSHUB_CHAIN_ID, OSMOSIS_CHAIN_ID, STARGAZE_CHAIN_ID, REGEN_CHAIN_ID, SOMMELIER_CHAIN_ID, JUNO_CHAIN_ID, DYDX_CHAIN_ID]); + }, [ + COSMOSHUB_CHAIN_ID, + OSMOSIS_CHAIN_ID, + STARGAZE_CHAIN_ID, + REGEN_CHAIN_ID, + SOMMELIER_CHAIN_ID, + JUNO_CHAIN_ID, + DYDX_CHAIN_ID, + SAGA_CHAIN_ID, + ]); function getChainIdForToken(tokenToChainIdMap: { [x: string]: any }, baseToken: string) { return tokenToChainIdMap[baseToken.toLowerCase()] || null; } -const nonNative = liquidRewards?.assets; -const portfolioItems: PortfolioItemInterface[] = useMemo(() => { - if (!qbalance || !APYs || !redemptionRates || isLoadingAll || !liquidRewards) return []; - - // Flatten nonNative assets into a single array and accumulate amounts for each denom - const amountsMap = new Map(); - Object.values(nonNative || {}).flat().flatMap(reward => reward.Amount).forEach(({ denom, amount }) => { - const currentAmount = amountsMap.get(denom) || 0; - amountsMap.set(denom, currentAmount + Number(amount)); - }); - - // Map over the accumulated results to create portfolio items - return Array.from(amountsMap.entries()).map(([denom, amount]) => { - const normalizedDenom = denom.slice(2); - const chainId = getChainIdForToken(tokenToChainIdMap, normalizedDenom); - const tokenPriceInfo = tokenPrices?.find((info) => info.token === normalizedDenom); - const redemptionRate = chainId && redemptionRates[chainId] ? redemptionRates[chainId].current : 1; - const qTokenPrice = tokenPriceInfo ? tokenPriceInfo.price * redemptionRate : 0; - const exp = getExponent(denom); - const normalizedAmount = shiftDigits(amount, -exp); - - return { - title: 'q' + normalizedDenom.toUpperCase(), - amount: normalizedAmount.toString(), - qTokenPrice: qTokenPrice, - chainId: chainId ?? '', - }; - }); -// eslint-disable-next-line react-hooks/exhaustive-deps -}, [qbalance, APYs, redemptionRates, isLoadingAll, liquidRewards, nonNative, tokenToChainIdMap, tokenPrices, refetchAll]); - + const nonNative = liquidRewards?.assets; + const portfolioItems: PortfolioItemInterface[] = useMemo(() => { + if (!qbalance || !APYs || !redemptionRates || isLoadingAll || !liquidRewards) return []; + + // Flatten nonNative assets into a single array and accumulate amounts for each denom + const amountsMap = new Map(); + Object.values(nonNative || {}) + .flat() + .flatMap((reward) => reward.Amount) + .forEach(({ denom, amount }) => { + const currentAmount = amountsMap.get(denom) || 0; + amountsMap.set(denom, currentAmount + Number(amount)); + }); + + // Map over the accumulated results to create portfolio items + return Array.from(amountsMap.entries()).map(([denom, amount]) => { + const normalizedDenom = denom.slice(2); + const chainId = getChainIdForToken(tokenToChainIdMap, normalizedDenom); + const tokenPriceInfo = tokenPrices?.find((info) => info.token === normalizedDenom); + const redemptionRate = chainId && redemptionRates[chainId] ? redemptionRates[chainId].current : 1; + const qTokenPrice = tokenPriceInfo ? tokenPriceInfo.price * redemptionRate : 0; + const exp = getExponent(denom); + const normalizedAmount = shiftDigits(amount, -exp); + + return { + title: 'q' + normalizedDenom.toUpperCase(), + amount: normalizedAmount.toString(), + qTokenPrice: qTokenPrice, + chainId: chainId ?? '', + }; + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [qbalance, APYs, redemptionRates, isLoadingAll, liquidRewards, nonNative, tokenToChainIdMap, tokenPrices, refetchAll]); const totalPortfolioValue = useMemo( () => portfolioItems.reduce((acc, item) => acc + Number(item.amount) * item.qTokenPrice, 0), @@ -125,8 +137,6 @@ const portfolioItems: PortfolioItemInterface[] = useMemo(() => { [portfolioItems, APYs], ); - - const [showRewardsClaim, setShowRewardsClaim] = useState(false); const [userClosedRewardsClaim, setUserClosedRewardsClaim] = useState(false); @@ -146,32 +156,30 @@ const portfolioItems: PortfolioItemInterface[] = useMemo(() => { // Data for the assets grid // the query return `qbalance` is an array of quicksilver staked assets held by the user // assetsData maps over the assets in qbalance and returns the name, balance, apy, native asset denom, and redemption rate. - const qtokens = useMemo(() => ['qatom', 'qosmo', 'qstars', 'qregen', 'qsomm', 'qjuno', 'qdydx'], []); - -const assetsData = useMemo(() => { - return qtokens.map((token) => { - - const baseToken = token.substring(1).toLowerCase(); - - - const asset = qbalance?.find(a => a.denom.substring(2).toLowerCase() === baseToken); - const apyAsset = qtokens.find(a => a.substring(1).toLowerCase() === baseToken); - const chainId = apyAsset ? getChainIdForToken(tokenToChainIdMap, baseToken) : undefined; - - const apy = (chainId && chainId !== 'dydx-mainnet-1' && APYs && APYs.hasOwnProperty(chainId)) ? APYs[chainId] : 0; - const redemptionRate = chainId && redemptionRates && redemptionRates[chainId] ? redemptionRates[chainId].current || 1 : 1; - const exp = apyAsset ? getExponent(apyAsset) : 0; - - return { - name: token.toUpperCase(), - balance: asset ? shiftDigits(Number(asset.amount), -exp).toString() : "0", - apy: parseFloat(((apy * 100) / 100).toFixed(4)), - native: baseToken.toUpperCase(), - redemptionRates: redemptionRate.toString(), - }; - }); -// eslint-disable-next-line react-hooks/exhaustive-deps -}, [qtokens, qbalance, tokenToChainIdMap, APYs, redemptionRates, refetchAll]); + const qtokens = useMemo(() => ['qatom', 'qosmo', 'qstars', 'qregen', 'qsomm', 'qjuno', 'qdydx', 'qsaga'], []); + + const assetsData = useMemo(() => { + return qtokens.map((token) => { + const baseToken = token.substring(1).toLowerCase(); + + const asset = qbalance?.find((a) => a.denom.substring(2).toLowerCase() === baseToken); + const apyAsset = qtokens.find((a) => a.substring(1).toLowerCase() === baseToken); + const chainId = apyAsset ? getChainIdForToken(tokenToChainIdMap, baseToken) : undefined; + + const apy = chainId && chainId !== 'dydx-mainnet-1' && APYs && APYs.hasOwnProperty(chainId) ? APYs[chainId] : 0; + const redemptionRate = chainId && redemptionRates && redemptionRates[chainId] ? redemptionRates[chainId].current || 1 : 1; + const exp = apyAsset ? getExponent(apyAsset) : 0; + + return { + name: token.toUpperCase(), + balance: asset ? shiftDigits(Number(asset.amount), -exp).toString() : '0', + apy: parseFloat(((apy * 100) / 100).toFixed(4)), + native: baseToken.toUpperCase(), + redemptionRates: redemptionRate.toString(), + }; + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [qtokens, qbalance, tokenToChainIdMap, APYs, redemptionRates, refetchAll]); const showAssetsGrid = qbalance && qbalance.length > 0 && !qIsLoading && !qIsError; diff --git a/web-ui/public/img/networks/qsaga.svg b/web-ui/public/img/networks/qsaga.svg new file mode 100644 index 000000000..1b40f83b7 --- /dev/null +++ b/web-ui/public/img/networks/qsaga.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web-ui/public/img/networks/saga.svg b/web-ui/public/img/networks/saga.svg new file mode 100644 index 000000000..7648f719f --- /dev/null +++ b/web-ui/public/img/networks/saga.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web-ui/state/chains/prod.ts b/web-ui/state/chains/prod.ts index 4cc30a5ff..15f8dadde 100644 --- a/web-ui/state/chains/prod.ts +++ b/web-ui/state/chains/prod.ts @@ -7,6 +7,7 @@ export const ibcDenomWithdrawMapping = { qSOMM: 'qsomm', qJUNO: 'qjuno', qDYDX: 'qdydx', + qSAGA: 'qsaga' } }; @@ -18,7 +19,8 @@ export const ibcDenomWithdrawMapping = { qREGEN: 'ibc/79A676508A2ECA1021EDDC7BB9CF70CEEC9514C478DA526A5A8B3E78506C2206', qSOMM: 'ibc/EAF76AD1EEF7B16D167D87711FB26ABE881AC7D9F7E6D0CF313D5FA530417208', qJUNO: 'ibc/B4E18E61E1505C2F371B621E49B09E983F6A138F251A7B5286A6BDF739FD0D54', - qDYDX: '' + qDYDX: 'ibc/273C593E51ACE56F1F2BDB3E03A5CB81BB208B894BCAA642676A32C3454E8C27', + qSAGA: 'ibc/F2D400F2728E9DA06EAE2AFAB289931A69EDDA5A661578C66A3177EDFE3C0D13' }, umee: { qATOM: 'ibc/454725EA4029BAA99C293904336DE9A4B84E2BF7D83B9C56EE6B03E8A65FB5A1', @@ -27,7 +29,8 @@ export const ibcDenomWithdrawMapping = { qREGEN: 'ibc/16F0C7E49C2FE3A99E92A20DBCF4006B38ABC4E29F7F37829AD40F2C585BE835', qSOMM: 'ibc/ACF9DA139FE5BC8F95AC4A12B0B6D7710274DEDAC57284B881BEE1896F40642D', qJUNO: 'ibc/CA0BEF2524A37205009210EFCFB09585FBA9648C5F065FA078944A5C6704E8DC', - qDYDX: '' + qDYDX: 'ibc/41F3C94FAB3FB2D6D2B1F130A78697B07D729D1F50DA132C18F7963413A2DCF6', + qSAGA: 'ibc/9B4BDA7382D0CF8C48A9D7496449D626DDF99AF640325978B5BD1AD4A9ED274C' }, }; @@ -55,7 +58,7 @@ export const networks = [ name: 'Dydx', chainName: 'dydx', chainId: 'dydx-mainnet-1', - }, + }, { value: 'STARS', logo: '/img/networks/stargaze.svg', @@ -88,6 +91,15 @@ export const networks = [ chainName: 'juno', chainId: 'juno-1', }, + { + value: 'SAGA', + logo: '/img/networks/saga.svg', + qlogo: '/img/networks/qsaga.svg', + name: 'Saga', + chainName: 'saga', + chainId: 'ssc-1', + }, + ]; export const testNetworks = [ diff --git a/web-ui/utils/maths.ts b/web-ui/utils/maths.ts index 7063dfebd..564edfa0e 100644 --- a/web-ui/utils/maths.ts +++ b/web-ui/utils/maths.ts @@ -37,7 +37,7 @@ export const formatNumber = (num: number) => { const endIndex = decimalPlaces > 0 ? dotIndex + decimalPlaces + 1 : dotIndex; return numStr.substring(0, endIndex); }; - + if (num < 1) { return truncate(num, 3); }