diff --git a/web-ui/.env b/web-ui/.env index b4428c9da..e2ee1d560 100644 --- a/web-ui/.env +++ b/web-ui/.env @@ -11,13 +11,11 @@ NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_REGEN="https://lcd.regen-1.quicksilver.zone" NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_REGEN="https://rpc.regen-1.quicksilver.zone" NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_SOMMELIER="https://lcd.sommelier-3.quicksilver.zone" NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_SOMMELIER="https://rpc.sommelier-3.quicksilver.zone" +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_QUICKSILVER_API="https://lcd.quicksilver.zone" NEXT_PUBLIC_QUICKSILVER_DATA_API="https://data.quicksilver.zone" ZONE_URL="quicksilver.zone" -REACT_APP_WHITELISTED_ZONES="osmosis-1,stargaze-1,regen-1,cosmoshub-4,sommelier-3" -REACT_APP_ENABLE_UNBONDING="true" -REACT_APP_ENABLE_SET_INTENT="true" -REACT_APP_ENABLE_CLAIMS="true" APY_ZONES_ENDPOINT = "https://chains.cosmos.directory" NEXT_PUBLIC_OSMOSIS_API="https://api.osmosis.zone" NEXT_PUBLIC_WHITELISTED_DENOM="uatom,ustars,uosmo,usomm,uregen" @@ -26,4 +24,5 @@ NEXT_PUBLIC_COSMOSHUB_CHAIN_ID=cosmoshub-4 NEXT_PUBLIC_OSMOSIS_CHAIN_ID=osmosis-1 NEXT_PUBLIC_STARGAZE_CHAIN_ID=stargaze-1 NEXT_PUBLIC_REGEN_CHAIN_ID=regen-1 -NEXT_PUBLIC_SOMMELIER_CHAIN_ID=sommelier-3 \ No newline at end of file +NEXT_PUBLIC_SOMMELIER_CHAIN_ID=sommelier-3 +NEXT_PUBLIC_JUNO_CHAIN_ID=juno-1 \ No newline at end of file diff --git a/web-ui/.env.development b/web-ui/.env.development index d041ac509..53c6e26ed 100644 --- a/web-ui/.env.development +++ b/web-ui/.env.development @@ -14,10 +14,6 @@ # NEXT_PUBLIC_QUICKSILVER_API="https://lcd.test.quicksilver.zone" # NEXT_PUBLIC_QUICKSILVER_DATA_API="https://data.test.quicksilver.zone" # ZONE_URL="quicksilver.zone" -# REACT_APP_WHITELISTED_ZONES="osmosis-1,stargaze-1,regen-1,cosmoshub-4,sommelier-3" -# REACT_APP_ENABLE_UNBONDING="true" -# REACT_APP_ENABLE_SET_INTENT="true" -# REACT_APP_ENABLE_CLAIMS="true" # APY_ZONES_ENDPOINT = "https://chains.cosmos.directory" # NEXT_PUBLIC_OSMOSIS_API="https://api.osmosis.zone" # NEXT_PUBLIC_WHITELISTED_DENOM="uatom,ustars,uosmo,usomm,uregen" diff --git a/web-ui/.eslintrc.json b/web-ui/.eslintrc.json index a9b0e6a52..595dd805c 100644 --- a/web-ui/.eslintrc.json +++ b/web-ui/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": "next/babel", + "extends": "next/core-web-vitals", "plugins": ["import"], "rules": { "import/order": [ diff --git a/web-ui/bun.lockb b/web-ui/bun.lockb index b03ea87e9..a03b7aa27 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 d6e25e67b..be26d968f 100644 --- a/web-ui/components/Assets/assetsGrid.tsx +++ b/web-ui/components/Assets/assetsGrid.tsx @@ -1,10 +1,12 @@ -import { Box, VStack, Text, Divider, HStack, Flex, Grid, GridItem, Spinner } from '@chakra-ui/react'; +import { WarningIcon } from '@chakra-ui/icons'; +import { Box, VStack, Text, Divider, HStack, Flex, Grid, GridItem, Spinner, Tooltip } from '@chakra-ui/react'; import React from 'react'; +import { shiftDigits, formatQasset } from '@/utils'; + import QDepositModal from './modals/qTokenDepositModal'; import QWithdrawModal from './modals/qTokenWithdrawlModal'; -import { shiftDigits } from '@/utils'; interface AssetCardProps { assetName: string; @@ -48,12 +50,17 @@ type LiquidRewardsData = { errors: Errors; }; -const AssetCard: React.FC = ({ assetName, balance, apy, nativeAssetName, isWalletConnected, nonNative }) => { +function truncateToTwoDecimals(num: number) { + const multiplier = Math.pow(10, 2); + return Math.floor(num * multiplier) / multiplier; +} + +const AssetCard: React.FC = ({ assetName, balance, apy }) => { const calculateTotalBalance = (nonNative: LiquidRewardsData | undefined, nativeAssetName: string) => { if (!nonNative) { return '0'; } - const chainIds = ['osmosis-1', 'secret-1', 'umee-1', 'cosmoshub-4', 'stargaze-1', 'sommelier-3', 'regen-1']; + const chainIds = ['osmosis-1', 'secret-1', 'umee-1', 'cosmoshub-4', 'stargaze-1', 'sommelier-3', 'regen-1', 'juno-1']; let totalAmount = 0; chainIds.forEach((chainId) => { @@ -71,15 +78,15 @@ const AssetCard: React.FC = ({ assetName, balance, apy, nativeAs return shiftDigits(totalAmount.toString(), -6); // Adjust the shift as per your data's scale }; - const nativeAssets = nonNative?.assets['rhye-2'] - ? nonNative.assets['rhye-2'][0].Amount.find((amount) => amount.denom === `uq${nativeAssetName.toLowerCase()}`) - : undefined; + // const nativeAssets = nonNative?.assets['quicksilver-2'] + // ? nonNative.assets['quicksilver-2'][0].Amount.find((amount) => amount.denom === `uq${nativeAssetName.toLowerCase()}`) + // : undefined; - const formattedNonNativeBalance = calculateTotalBalance(nonNative, nativeAssetName); + // const formattedNonNativeBalance = calculateTotalBalance(nonNative, nativeAssetName); - const formattedNativebalance = nativeAssets ? shiftDigits(nativeAssets.amount, -6) : '0'; + // const formattedNativebalance = nativeAssets ? shiftDigits(nativeAssets.amount, -6) : '0'; - if (!balance || !apy) { + if (balance === undefined || balance === null || apy === undefined || apy === null) { return ( = ({ assetName, balance, apy, nativeAs ); } + return ( @@ -108,7 +116,7 @@ const AssetCard: React.FC = ({ assetName, balance, apy, nativeAs APY: - {shiftDigits(apy.toFixed(2), 2)}% + {truncateToTwoDecimals(Number(shiftDigits(apy, 2)))}% @@ -121,19 +129,18 @@ const AssetCard: React.FC = ({ assetName, balance, apy, nativeAs - {formattedNativebalance} + {balance.toString()} {assetName} - + {/* NON-NATIVE: - {formattedNonNativeBalance} - + */} @@ -148,9 +155,14 @@ const AssetCard: React.FC = ({ assetName, balance, apy, nativeAs const AssetsGrid: React.FC = ({ assets, isWalletConnected, nonNative }) => { return ( <> - - qAssets - + + + qAssets + + + + + {!isWalletConnected && ( = ({ assets, isWalletConnected, nonNa )} {isWalletConnected && ( - - - {assets.map((asset, index) => ( - - - - ))} - - + + {assets.map((asset, index) => ( + + + + ))} + )} ); diff --git a/web-ui/components/Assets/intents.tsx b/web-ui/components/Assets/intents.tsx index 0e7f7cdf4..5ebc66413 100644 --- a/web-ui/components/Assets/intents.tsx +++ b/web-ui/components/Assets/intents.tsx @@ -12,16 +12,17 @@ import { Spinner, SkeletonCircle, SkeletonText, + Center, } from '@chakra-ui/react'; - import { Key, useState } from 'react'; -import SignalIntentModal from './modals/signalIntentProcess'; - import { useIntentQuery, useValidatorLogos, useValidatorsQuery } from '@/hooks/useQueries'; import { networks as prodNetworks, testNetworks as devNetworks } from '@/state/chains/prod'; import { truncateString } from '@/utils'; +import SignalIntentModal from './modals/signalIntentProcess'; + + export interface StakingIntentProps { address: string; isWalletConnected: boolean; @@ -30,7 +31,7 @@ export interface StakingIntentProps { const StakingIntent: React.FC = ({ address, isWalletConnected }) => { const networks = process.env.NEXT_PUBLIC_CHAIN_ENV === 'mainnet' ? prodNetworks : devNetworks; - const chains = ['Cosmos', 'Osmosis', 'Stargaze', 'Regen', 'Sommelier']; + const chains = ['Cosmos', 'Osmosis', 'Stargaze', 'Regen', 'Sommelier', 'Juno']; const [currentChainIndex, setCurrentChainIndex] = useState(0); const [isSignalIntentModalOpen, setIsSignalIntentModalOpen] = useState(false); @@ -157,49 +158,54 @@ const StakingIntent: React.FC = ({ address, isWalletConnecte /> - - {validatorsWithDetails.map( - (validator: { logoUrl: string; moniker: string; percentage: string }, index: Key | null | undefined) => ( - - - {validator.logoUrl ? ( - {validator.moniker} - ) : ( - - )} - {validator.moniker ? ( - {truncateString(validator.moniker, 20)} - ) : ( - - )} + + {(validatorsWithDetails.length > 0 && + validatorsWithDetails.map( + (validator: { logoUrl: string; moniker: string; percentage: string }, index: Key | null | undefined) => ( + + + {validator.logoUrl ? ( + {validator.moniker} + ) : ( + + )} + {validator.moniker ? ( + {truncateString(validator.moniker, 20)} + ) : ( + + )} + + + {validator.percentage} + - - {validator.percentage} - - - ), + ), + )) || ( +
+ No intent set +
)}
diff --git a/web-ui/components/Assets/modals/qTokenDepositModal.tsx b/web-ui/components/Assets/modals/qTokenDepositModal.tsx index a01152de4..d9e5d937c 100644 --- a/web-ui/components/Assets/modals/qTokenDepositModal.tsx +++ b/web-ui/components/Assets/modals/qTokenDepositModal.tsx @@ -14,11 +14,11 @@ import { useToast, Spinner, } from '@chakra-ui/react'; -import { ibc } from '@chalabi/quicksilverjs'; import { StdFee, coins } from '@cosmjs/stargate'; import { ChainName } from '@cosmos-kit/core'; import { useChain, useManager } from '@cosmos-kit/react'; import BigNumber from 'bignumber.js'; +import { ibc } from 'quicksilverjs'; import { useState, useMemo, useEffect } from 'react'; import { ChooseChain } from '@/components/react/choose-chain'; @@ -42,7 +42,7 @@ const QDepositModal: React.FC = ({ token }) => { const [isLoading, setIsLoading] = useState(false); const chainOptions = useMemo(() => { - const desiredChains = ['osmosis', 'secretnetwork', 'umee']; + const desiredChains = ['osmosis', 'umee']; return chainRecords .filter((chainRecord) => desiredChains.includes(chainRecord.name)) .map((chainRecord) => ({ @@ -101,7 +101,7 @@ const QDepositModal: React.FC = ({ token }) => { const chain = chainName as ChainDenomMappingKeys; const chainDenoms = ibcDenomDepositMapping[chain]; - if (token in chainDenoms) { + if (chainDenoms && token in chainDenoms) { return chainDenoms[token as TokenKeys]; } diff --git a/web-ui/components/Assets/modals/qTokenWithdrawlModal.tsx b/web-ui/components/Assets/modals/qTokenWithdrawlModal.tsx index 2557f7bad..9b629104f 100644 --- a/web-ui/components/Assets/modals/qTokenWithdrawlModal.tsx +++ b/web-ui/components/Assets/modals/qTokenWithdrawlModal.tsx @@ -10,16 +10,15 @@ import { FormControl, FormLabel, Input, - Select, useDisclosure, useToast, Spinner, } from '@chakra-ui/react'; -import { ibc } from '@chalabi/quicksilverjs'; import { StdFee, coins } from '@cosmjs/stargate'; import { ChainName } from '@cosmos-kit/core'; import { useChain, useManager } from '@cosmos-kit/react'; import BigNumber from 'bignumber.js'; +import { ibc } from 'quicksilverjs'; import { useState, useMemo, useEffect } from 'react'; import { ChooseChain } from '@/components/react/choose-chain'; @@ -43,7 +42,7 @@ const QWithdrawModal: React.FC = ({ token }) => { const [isLoading, setIsLoading] = useState(false); const chainOptions = useMemo(() => { - const desiredChains = ['osmosis', 'secretnetwork', 'umee']; + const desiredChains = ['osmosis', 'umee']; return chainRecords .filter((chainRecord) => desiredChains.includes(chainRecord.name)) .map((chainRecord) => ({ @@ -69,16 +68,14 @@ const QWithdrawModal: React.FC = ({ token }) => { const chooseChain = ; - const fromChain = chainName; - const toChain = 'quicksilver'; + const fromChain = 'quicksilver'; + const toChain = chainName; const { transfer } = ibc.applications.transfer.v1.MessageComposer.withTypeUrl; - const { address, connect, status, message, wallet } = useChain(fromChain ?? ''); + const { address } = useChain(toChain ?? ''); const { address: qAddress } = useChain('quicksilver'); const { balance } = useIbcBalanceQuery(fromChain ?? '', address ?? ''); const { tx } = useTx(fromChain ?? ''); - const qckBalance = - balance?.balances.find((b) => b.denom === 'ibc/635CB83EF1DFE598B10A3E90485306FD0D47D34217A4BE5FD9977FA010A5367D')?.amount ?? ''; const onSubmitClick = async () => { setIsLoading(true); @@ -96,13 +93,13 @@ const QWithdrawModal: React.FC = ({ token }) => { // Function to get the correct IBC denom trace based on chain and token type ChainDenomMappingKeys = keyof typeof ibcDenomWithdrawMapping; - type TokenKeys = keyof (typeof ibcDenomWithdrawMapping)['osmosis']; + type TokenKeys = keyof (typeof ibcDenomWithdrawMapping)['quicksilver']; const getIbcDenom = (chainName: string, token: string) => { const chain = chainName as ChainDenomMappingKeys; const chainDenoms = ibcDenomWithdrawMapping[chain]; - if (token in chainDenoms) { + if (chainDenoms && token in chainDenoms) { return chainDenoms[token as TokenKeys]; } @@ -123,7 +120,7 @@ const QWithdrawModal: React.FC = ({ token }) => { } const ibcToken = { - denom: ibcDenom ?? '', + denom: 'u' + ibcDenom ?? '', amount: transferAmount, }; @@ -133,8 +130,8 @@ const QWithdrawModal: React.FC = ({ token }) => { const msg = transfer({ sourcePort, sourceChannel, - sender: address ?? '', - receiver: qAddress ?? '', + sender: qAddress ?? '', + receiver: address ?? '', token: ibcToken, timeoutHeight: undefined, //@ts-ignore diff --git a/web-ui/components/Assets/modals/qckDepositModal.tsx b/web-ui/components/Assets/modals/qckDepositModal.tsx index f66d67efc..6cef3519f 100644 --- a/web-ui/components/Assets/modals/qckDepositModal.tsx +++ b/web-ui/components/Assets/modals/qckDepositModal.tsx @@ -13,19 +13,19 @@ import { useDisclosure, Spinner, } from '@chakra-ui/react'; -import { ibc } from '@chalabi/quicksilverjs'; import { StdFee } from '@cosmjs/stargate'; import { ChainName } from '@cosmos-kit/core'; import { useChain, useManager } from '@cosmos-kit/react'; import BigNumber from 'bignumber.js'; import { assets, chains } from 'chain-registry'; +import { ibc } from 'quicksilverjs'; import { useState, useMemo, useEffect } from 'react'; import { ChooseChain } from '@/components/react/choose-chain'; import { handleSelectChainDropdown, ChainOption } from '@/components/types'; import { useTx } from '@/hooks'; import { useIbcBalanceQuery } from '@/hooks/useQueries'; -import { getCoin, getIbcInfo, shiftDigits } from '@/utils'; +import { getIbcInfo, shiftDigits } from '@/utils'; export function DepositModal() { const { isOpen, onOpen, onClose } = useDisclosure(); @@ -107,7 +107,8 @@ export function DepositModal() { sender: address ?? '', receiver: qAddress ?? '', token, - timeoutHeight: undefined, + //@ts-ignore + timeoutHeight: 0, //@ts-ignore timeoutTimestamp: timeoutInNanos, memo: '', diff --git a/web-ui/components/Assets/modals/qckWithdrawModal.tsx b/web-ui/components/Assets/modals/qckWithdrawModal.tsx index f010f0c78..171671e41 100644 --- a/web-ui/components/Assets/modals/qckWithdrawModal.tsx +++ b/web-ui/components/Assets/modals/qckWithdrawModal.tsx @@ -11,20 +11,18 @@ import { FormLabel, Input, useDisclosure, - useToast, Spinner, } from '@chakra-ui/react'; -import { ibc } from '@chalabi/quicksilverjs'; import { StdFee, coins } from '@cosmjs/stargate'; import { ChainName } from '@cosmos-kit/core'; import { useChain, useManager } from '@cosmos-kit/react'; import BigNumber from 'bignumber.js'; +import { ibc } from 'quicksilverjs'; import { useState, useMemo, useEffect } from 'react'; import { ChooseChain } from '@/components/react/choose-chain'; import { handleSelectChainDropdown, ChainOption } from '@/components/types'; import { useTx } from '@/hooks'; -import { useIbcBalanceQuery } from '@/hooks/useQueries'; import { getCoin, getIbcInfo } from '@/utils'; export function WithdrawModal() { diff --git a/web-ui/components/Assets/modals/signalIntentProcess.tsx b/web-ui/components/Assets/modals/signalIntentProcess.tsx index 052e2623c..5fcaa24fd 100644 --- a/web-ui/components/Assets/modals/signalIntentProcess.tsx +++ b/web-ui/components/Assets/modals/signalIntentProcess.tsx @@ -19,20 +19,19 @@ import { import { StdFee } from '@cosmjs/amino'; import { useChain } from '@cosmos-kit/react'; import styled from '@emotion/styled'; - import { assets } from 'chain-registry'; import { quicksilver } from 'quicksilverjs'; - import React, { useEffect, useState } from 'react'; +import { useTx } from '@/hooks'; + import { IntentMultiModal } from './intentMultiModal'; -import { useTx } from '@/hooks'; const ChakraModalContent = styled(ModalContent)` position: relative; background: none; - max-height: 320px; + max-height: 350px; &::before, &::after { z-index: -1; @@ -254,7 +253,7 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose return ( - + diff --git a/web-ui/components/Assets/portfolio.tsx b/web-ui/components/Assets/portfolio.tsx index 2a8467b2d..e8f9b49bb 100644 --- a/web-ui/components/Assets/portfolio.tsx +++ b/web-ui/components/Assets/portfolio.tsx @@ -1,6 +1,6 @@ -import { Progress, Flex, Text, VStack, HStack, Heading, Spinner, Tooltip, Grid, Center, Box } from '@chakra-ui/react'; +import { Progress, Flex, Text, VStack, HStack, Heading, Spinner, Tooltip, Grid, Box } from '@chakra-ui/react'; -import { abbreviateNumber, shiftDigits } from '@/utils'; +import { abbreviateNumber, shiftDigits, formatQasset } from '@/utils'; interface PortfolioItemInterface { title: string; @@ -16,8 +16,16 @@ interface MyPortfolioProps { totalValue: number; averageApy: number; totalYearlyYield: number; + isLoading: boolean; } -const MyPortfolio: React.FC = ({ portfolioItems, isWalletConnected, totalValue, averageApy, totalYearlyYield }) => { +const MyPortfolio: React.FC = ({ + portfolioItems, + isWalletConnected, + totalValue, + averageApy, + totalYearlyYield, + isLoading, +}) => { if (!isWalletConnected) { return ( = ({ portfolioItems, isWalletConne ); } - if (!totalValue) { + if (isLoading) { return ( = ({ portfolioItems, isWalletConne AVG APY: - - {shiftDigits(averageApy.toFixed(2), 2)}% - + {Number.isNaN(averageApy) && ( + + 0% + + )} + {Number.isFinite(averageApy) && ( + + {shiftDigits(averageApy.toFixed(2), 2)}% + + )} @@ -103,9 +118,15 @@ const MyPortfolio: React.FC = ({ portfolioItems, isWalletConne - + {totalValue === 0 && ( + + + You have no liquid staked assets. + + + )} - + {portfolioItems .filter((item) => Number(item.amount) > 0) .map((item) => ( @@ -116,6 +137,7 @@ const MyPortfolio: React.FC = ({ portfolioItems, isWalletConne progressBarColor={item.progressBarColor} amount={item.amount} qTokenPrice={item.qTokenPrice} + totalValue={totalValue} /> ))} @@ -131,9 +153,10 @@ interface PortfolioItemProps { progressBarColor: string; amount: string; qTokenPrice: number; + totalValue: number; } -const PortfolioItem: React.FC = ({ title, percentage, progressBarColor, amount, qTokenPrice }) => { +const PortfolioItem: React.FC = ({ title, percentage, progressBarColor, amount, qTokenPrice, totalValue }) => { const amountLength = amount.toString().length; const amountWidth = Math.min(Math.max(amountLength * 8, 90), 100); @@ -146,7 +169,7 @@ const PortfolioItem: React.FC = ({ title, percentage, progre - {title} + {formatQasset(title)} diff --git a/web-ui/components/Assets/quickbox.tsx b/web-ui/components/Assets/quickbox.tsx index 7080b130f..eecc6e029 100644 --- a/web-ui/components/Assets/quickbox.tsx +++ b/web-ui/components/Assets/quickbox.tsx @@ -1,27 +1,28 @@ -import { Box, Flex, Text, Button, VStack, useColorModeValue, HStack, SkeletonCircle, Spinner } from '@chakra-ui/react'; +import { Box, Flex, Text, VStack, HStack, SkeletonCircle, Spinner } from '@chakra-ui/react'; import { useChain } from '@cosmos-kit/react'; import { BsCoin } from 'react-icons/bs'; -import { DepositModal } from './modals/qckDepositModal'; -import { WithdrawModal } from './modals/qckWithdrawModal'; - import { defaultChainName } from '@/config'; import { useBalanceQuery } from '@/hooks/useQueries'; import { shiftDigits } from '@/utils'; +import { DepositModal } from './modals/qckDepositModal'; +import { WithdrawModal } from './modals/qckWithdrawModal'; + + interface QuickBoxProps { stakingApy?: number; } const QuickBox: React.FC = ({ stakingApy }) => { - const { address, isWalletConnected } = useChain(defaultChainName); + const { address } = useChain(defaultChainName); const { balance, isLoading } = useBalanceQuery(defaultChainName, address ?? ''); const tokenBalance = Number(shiftDigits(balance?.balance?.amount ?? '', -6)) .toFixed(2) .toString(); - if (!isWalletConnected) { + if (!address) { return ( @@ -89,15 +90,15 @@ const QuickBox: React.FC = ({ stakingApy }) => { {quickStakingApy()} - - ON QUICKSILVER: + + ON QUICKSILVER: {isLoading === true && !balance && } {!isLoading && balance && ( - - {tokenBalance} + + {tokenBalance} QCK )} - + diff --git a/web-ui/components/Assets/rewardsClaim.tsx b/web-ui/components/Assets/rewardsClaim.tsx index 52831396e..eaea88cb2 100644 --- a/web-ui/components/Assets/rewardsClaim.tsx +++ b/web-ui/components/Assets/rewardsClaim.tsx @@ -1,16 +1,12 @@ import { CloseIcon } from '@chakra-ui/icons'; -import { Box, Flex, Text, VStack, Button, HStack, Checkbox, Spinner } from '@chakra-ui/react'; -import { useState } from 'react'; - -import { useLiquidEpochQuery } from '@/hooks/useQueries'; +import { Box, Flex, Text, VStack, Button, HStack, Spinner } from '@chakra-ui/react'; +import { assets } from 'chain-registry'; +import { GenericAuthorization } from 'interchain-query/cosmos/authz/v1beta1/authz'; +import { quicksilver, cosmos } from 'quicksilverjs'; +import React, { useState } from 'react'; -import { quicksilver } from 'quicksilverjs'; import { useTx } from '@/hooks'; -import { StdFee } from '@cosmjs/amino'; -import { assets } from 'chain-registry'; -import { cosmos } from 'interchain-query'; -import { Grant, GenericAuthorization } from 'interchain-query/cosmos/authz/v1beta1/authz'; -import { MsgSubmitClaim } from 'quicksilverjs/types/codegen/quicksilver/participationrewards/v1/messages'; +import { useLiquidEpochQuery } from '@/hooks/useQueries'; interface RewardsClaimInterface { address: string; @@ -48,20 +44,18 @@ export const RewardsClaim: React.FC = ({ address, onClose const { grant } = cosmos.authz.v1beta1.MessageComposer.withTypeUrl; - const msgTypeUrl = '/quicksilver.participationrewards.v1.MsgSubmitClaim'; - const genericAuth = { - msg: msgTypeUrl, + msg: quicksilver.participationrewards.v1.MsgSubmitClaim.typeUrl, }; const binaryMessage = GenericAuthorization.encode(genericAuth).finish(); const msgGrant = grant({ granter: address, - grantee: 'quick1dv3v662kd3pp6pxfagck4zyysas82adsdhugaf', + grantee: 'quick1w5ennfhdqrpyvewf35sv3y3t8yuzwq29mrmyal', grant: { authorization: { - typeUrl: '/cosmos.authz.v1beta1.GenericAuthorization', + typeUrl: cosmos.authz.v1beta1.GenericAuthorization.typeUrl, value: binaryMessage, }, }, @@ -70,11 +64,11 @@ export const RewardsClaim: React.FC = ({ address, onClose const mainTokens = assets.find(({ chain_name }) => chain_name === 'quicksilver'); const mainDenom = mainTokens?.assets[0].base ?? 'uqck'; - const fee: StdFee = { + const fee = { amount: [ { denom: mainDenom, - amount: '5000', + amount: '50', }, ], gas: '500000', @@ -133,11 +127,11 @@ export const RewardsClaim: React.FC = ({ address, onClose } }; - const [autoClaimEnabled, setAutoClaimEnabled] = useState(false); + const [autoClaimEnabled, setAutoClaimEnabled] = useState(true); - const handleAutoClaimToggle = () => { - setAutoClaimEnabled(!autoClaimEnabled); - }; + // const handleAutoClaimToggle = () => { + // setAutoClaimEnabled(!autoClaimEnabled); + // }; const transactionHandler = autoClaimEnabled ? handleAutoClaimRewards : handleClaimRewards; @@ -147,13 +141,13 @@ export const RewardsClaim: React.FC = ({ address, onClose - Participation Rewards + Cross Chain Claims (XCC) is coming! - Claim your participation rewards. Rewards will be sent to your wallet at the next epoch. + Click the button below to set your authz grant for automatic cross chain claims. - = ({ address, onClose boxShadow: '0 0 0 3px #FF8000', }} isChecked={autoClaimEnabled} - onChange={handleAutoClaimToggle} + //onChange={handleAutoClaimToggle} colorScheme="orange" > Enable Automatic Claiming - - + */} + {/* + + + Claiming Disabled + + Reward claiming is disabled until a governance proposal updating the claiming parameter is passed. + + You may still enable automatic claiming in advance. + + + + } + isDisabled={autoClaimEnabled} + placement="top" + hasArrow + > */} + + + + {/* */} diff --git a/web-ui/components/Assets/unbondingTable.tsx b/web-ui/components/Assets/unbondingTable.tsx index 56dea1f73..74dbe9978 100644 --- a/web-ui/components/Assets/unbondingTable.tsx +++ b/web-ui/components/Assets/unbondingTable.tsx @@ -3,7 +3,7 @@ import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Text, Box, Flex, IconB import { useState } from 'react'; import { useUnbondingQuery } from '@/hooks/useQueries'; -import { shiftDigits } from '@/utils'; +import { shiftDigits, formatQasset } from '@/utils'; const statusCodes = new Map([ [2, 'QUEUED'], @@ -17,7 +17,7 @@ const formatDate = (dateString: string | number | Date) => { }; const formatDenom = (denom: string) => { - return denom.startsWith('u') ? denom.substring(1).toUpperCase() : denom.toUpperCase(); + return denom.startsWith('u') ? formatQasset(denom.substring(1).toUpperCase()) : denom.toUpperCase(); }; interface UnbondingAssetsTableProps { @@ -26,7 +26,7 @@ interface UnbondingAssetsTableProps { } const UnbondingAssetsTable: React.FC = ({ address, isWalletConnected }) => { - const chains = ['Cosmos', 'Stargaze', 'Osmosis', 'Regen', 'Sommelier']; + const chains = ['Cosmos', 'Stargaze', 'Osmosis', 'Regen', 'Sommelier', 'Juno']; const [currentChainIndex, setCurrentChainIndex] = useState(0); const currentChainName = chains[currentChainIndex]; @@ -41,6 +41,8 @@ const UnbondingAssetsTable: React.FC = ({ address, is newChainName = 'regen'; } else if (currentChainName === 'Sommelier') { newChainName = 'sommelier'; + } else if (currentChainName === 'Juno') { + newChainName = 'juno'; } else { // Default case newChainName = currentChainName; @@ -57,7 +59,65 @@ const UnbondingAssetsTable: React.FC = ({ address, is setCurrentChainIndex((prevIndex: number) => (prevIndex === chains.length - 1 ? 0 : prevIndex + 1)); }; const noUnbondingAssets = isWalletConnected && unbondingData?.withdrawals.length === 0; - + if (!isWalletConnected) { + return ( + + + + Unbonding Assets + + + } + onClick={handleLeftArrowClick} + aria-label="Previous chain" + variant="ghost" + _hover={{ bgColor: 'transparent', color: 'complimentary.900' }} + _active={{ + transform: 'scale(0.75)', + color: 'complimentary.800', + }} + color="white" + /> + + {chains[currentChainIndex]} + + } + onClick={handleRightArrowClick} + aria-label="Next chain" + variant="ghost" + _hover={{ bgColor: 'transparent', color: 'complimentary.900' }} + _active={{ + transform: 'scale(0.75)', + color: 'complimentary.800', + }} + color="white" + /> + + + + + + Wallet is not connected! Please connect your wallet to view your unbonding assets. + + + + + ); + } if (isLoading) { return ( @@ -187,6 +247,9 @@ const UnbondingAssetsTable: React.FC = ({ address, is Redemption Amount + + Epoch Number + Completion Time @@ -194,13 +257,16 @@ const UnbondingAssetsTable: React.FC = ({ address, is {unbondingData?.withdrawals.map((withdrawal, index) => ( - - + + {Number(shiftDigits(withdrawal.burn_amount.amount, -6))} {formatDenom(withdrawal.burn_amount.denom)} - {statusCodes.get(withdrawal.status)} - {withdrawal.amount.map((amt) => `${shiftDigits(amt.amount, -6)} ${formatDenom(amt.denom)}`).join(', ')} - + {statusCodes.get(withdrawal.status)} + + {withdrawal.amount.map((amt) => `${shiftDigits(amt.amount, -6)} ${formatDenom(amt.denom)}`).join(', ')} + + {withdrawal.epoch_number} + {withdrawal.status === 2 ? 'Pending' : withdrawal.status === 4 diff --git a/web-ui/components/Defi/defiBox.tsx b/web-ui/components/Defi/defiBox.tsx index 5c678681d..a13f985c1 100644 --- a/web-ui/components/Defi/defiBox.tsx +++ b/web-ui/components/Defi/defiBox.tsx @@ -17,9 +17,8 @@ import { Tooltip, Center, Spinner, - useBreakpointValue, } from '@chakra-ui/react'; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useDefiData } from '@/hooks/useQueries'; @@ -70,15 +69,28 @@ const DefiTable = () => { const handleFilterClick = (filter: string) => { setActiveFilter(filter); }; - const isMobile = useBreakpointValue({ base: true, sm: true, md: false }); + const [isMobile, setIsMobile] = useState(typeof window !== 'undefined' && window.innerWidth < 1274); + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < 1274); + }; + + window.addEventListener('resize', handleResize); + + // Clean up + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); const filteredData = defi ? defi.filter(filterCategories[activeFilter]) : []; type ProviderKey = 'osmosis' | 'ux' | 'shade'; const providerIcons: Record = { - osmosis: '/quicksilver/img/osmoIcon.svg', - ux: '/quicksilver/img/ux.png', - shade: '/quicksilver/img/shd.svg', + osmosis: '/img/osmoIcon.svg', + ux: '/img/ux.png', + shade: '/img/shd.svg', }; const isProviderKey = (key: string): key is ProviderKey => { @@ -166,7 +178,7 @@ const DefiTable = () => { ))} )} - + @@ -297,7 +309,7 @@ const DefiTable = () => { {' '} {/* Span across all columns */}
- No entries found for this category, please check back later! + Nothing here yet. Stay tuned!
diff --git a/web-ui/components/Governance/ProposalCard.tsx b/web-ui/components/Governance/ProposalCard.tsx index bd1e1d0cb..dbca96542 100644 --- a/web-ui/components/Governance/ProposalCard.tsx +++ b/web-ui/components/Governance/ProposalCard.tsx @@ -4,11 +4,12 @@ import { cosmos } from 'interchain-query'; import { Proposal } from 'interchain-query/cosmos/gov/v1/gov'; import React, { useMemo } from 'react'; -import { StatusBadge, VotedBadge } from './common'; import { Votes } from '@/hooks'; import { decodeUint8Arr, getPercentage } from '@/utils'; +import { StatusBadge, VotedBadge } from './common'; + enum VoteOption { YES = 'YES', NO = 'NO', diff --git a/web-ui/components/Governance/ProposalModal.tsx b/web-ui/components/Governance/ProposalModal.tsx index 29003d9b4..e0920ec96 100644 --- a/web-ui/components/Governance/ProposalModal.tsx +++ b/web-ui/components/Governance/ProposalModal.tsx @@ -14,19 +14,19 @@ import { Center, Divider, Heading, - useColorMode, } from '@chakra-ui/react'; import { cosmos } from 'interchain-query'; import { Proposal } from 'interchain-query/cosmos/gov/v1/gov'; import React, { useMemo, useState } from 'react'; import { PieChart } from 'react-minimal-pie-chart'; +import { Votes } from '@/hooks'; +import { decodeUint8Arr, exponentiate, formatDate, getCoin, getExponent, getPercentage } from '@/utils'; + import { VoteResult, TimeDisplay, VoteRatio, NewLineText, StatusBadge, VoteOption } from './common'; import { VoteColor } from './ProposalCard'; import { VoteModal } from './VoteModal'; -import { Votes } from '@/hooks'; -import { decodeUint8Arr, exponentiate, formatDate, getCoin, getExponent, getPercentage } from '@/utils'; const ProposalStatus = cosmos.gov.v1beta1.ProposalStatus; diff --git a/web-ui/components/Governance/VotingSection.tsx b/web-ui/components/Governance/VotingSection.tsx index 66c8d8b20..9da9083e2 100644 --- a/web-ui/components/Governance/VotingSection.tsx +++ b/web-ui/components/Governance/VotingSection.tsx @@ -14,6 +14,7 @@ import { Menu, MenuButton, MenuItem, + Image, } from '@chakra-ui/react'; import { ChainName } from '@cosmos-kit/core'; import { useChain } from '@cosmos-kit/react'; @@ -21,12 +22,13 @@ import { Proposal } from 'interchain-query/cosmos/gov/v1/gov'; import React, { useMemo, useState } from 'react'; import { FaSearch } from 'react-icons/fa'; +import { useVotingData } from '@/hooks'; +import { decodeUint8Arr } from '@/utils'; + import { DisconnectedContent, Loader } from './common'; import { ProposalCard } from './ProposalCard'; import { ProposalModal } from './ProposalModal'; -import { useVotingData } from '@/hooks'; -import { decodeUint8Arr } from '@/utils'; function RotateIcon({ isOpen }: { isOpen: boolean }) { return ( @@ -55,17 +57,18 @@ export const VotingSection = ({ chainName }: { chainName: ChainName }) => { return data.proposals.filter((proposal) => { const decodedContent = decodeUint8Arr(proposal.messages[0].value); + const contentToSearch = decodedContent.toLowerCase(); - const titleMatches = proposal.title.toLowerCase().includes(searchTerm.toLowerCase()); + const titleMatches = decodedContent.toLowerCase().includes(searchTerm.toLowerCase()); + const contentMatches = contentToSearch.includes(searchTerm.toLowerCase()); let periodMatches = true; let proposalMatches = true; - // Constants for proposal status (these values might be different in your application) - const VOTING_PERIOD_STATUS = 2; // Example value for 'Voting Period' - const PASSED_STATUS = 3; // Example value for 'Passed' - const REJECTED_STATUS = 4; // Example value for 'Rejected' + const VOTING_PERIOD_STATUS = 2; + const PASSED_STATUS = 3; + const REJECTED_STATUS = 4; // Filter by period switch (selectedPeriodOption) { @@ -278,6 +281,21 @@ export const VotingSection = ({ chainName }: { chainName: ChainName }) => { > {isLoading ? : content} + {address && ( + + + + )} {selectedProposal && ( diff --git a/web-ui/components/Governance/common.tsx b/web-ui/components/Governance/common.tsx index 690069fc5..dd54cdbd5 100644 --- a/web-ui/components/Governance/common.tsx +++ b/web-ui/components/Governance/common.tsx @@ -1,23 +1,9 @@ -import { - Badge, - Box, - Center, - Flex, - Icon, - Spinner, - Stack, - Text, - useColorModeValue, -} from '@chakra-ui/react'; +import { Badge, Box, Center, Flex, Icon, Spinner, Stack, Text, useColorModeValue } from '@chakra-ui/react'; import styled from '@emotion/styled'; import { cosmos } from 'interchain-query'; import { ProposalStatus } from 'interchain-query/cosmos/gov/v1beta1/gov'; import { IconType } from 'react-icons'; -import { - AiFillCheckCircle, - AiFillCloseCircle, - AiFillMinusCircle, -} from 'react-icons/ai'; +import { AiFillCheckCircle, AiFillCloseCircle, AiFillMinusCircle } from 'react-icons/ai'; import ReactMarkdown from 'react-markdown'; import { VoteColor } from './ProposalCard'; @@ -32,36 +18,20 @@ const MarkdownStyled = styled(ReactMarkdown)` `; export const Loader = () => ( -
+
); export const DisconnectedContent = () => ( -
+
Please connect your wallet to see the proposals
); -export const TimeDisplay = ({ - title, - time, -}: { - title: string; - time: string; -}) => ( +export const TimeDisplay = ({ title, time }: { title: string; time: string }) => ( {title} @@ -92,15 +62,7 @@ export const VoteRatio = ({ amount: string; token: string; }) => ( - + {type} {ratio} @@ -151,19 +113,11 @@ export const VoteResult = ({ voteOption }: { voteOption: number }) => { return ( <> - + You Voted - + {optionConfig.option} @@ -178,9 +132,7 @@ export const NewLineText = ({ text }: { text: string }) => { <> {text.split('\\n').map((str) => ( - - {str} - + {str} ))} diff --git a/web-ui/components/Staking/assetsAccordion.tsx b/web-ui/components/Staking/assetsAccordion.tsx index b55810d61..145a0b813 100644 --- a/web-ui/components/Staking/assetsAccordion.tsx +++ b/web-ui/components/Staking/assetsAccordion.tsx @@ -38,7 +38,7 @@ export const AssetsAccordian: React.FC = ({ selectedOption }; const renderAssets = () => { - if (Number(balance) > 0.000001) { + if (balance) { return ( {balanceDisplay} @@ -80,7 +80,7 @@ export const AssetsAccordian: React.FC = ({ selectedOption

- + Liquid Staked diff --git a/web-ui/components/Staking/infoBox.tsx b/web-ui/components/Staking/infoBox.tsx index 8ca2f5a1b..438b26ddd 100644 --- a/web-ui/components/Staking/infoBox.tsx +++ b/web-ui/components/Staking/infoBox.tsx @@ -159,7 +159,7 @@ export const InfoBox: React.FC = ({ selectedOption, displa Want to learn more about rewards, fees, and unbonding on Quicksilver? Check out the{' '} - + docs . diff --git a/web-ui/components/Staking/modals/stakingProcessModal.tsx b/web-ui/components/Staking/modals/stakingProcessModal.tsx index 38d943cb3..aaeb229cd 100644 --- a/web-ui/components/Staking/modals/stakingProcessModal.tsx +++ b/web-ui/components/Staking/modals/stakingProcessModal.tsx @@ -22,19 +22,16 @@ import { coins, StdFee } from '@cosmjs/amino'; import { useChain } from '@cosmos-kit/react'; import styled from '@emotion/styled'; import { bech32 } from 'bech32'; -import { assets } from 'chain-registry'; -import chains from 'chain-registry'; +import { assets, chains } from 'chain-registry'; import { cosmos } from 'interchain-query'; - import React, { useEffect, useState } from 'react'; -import { MultiModal } from './validatorSelectionModal'; +import { useTx } from '@/hooks'; import { useZoneQuery } from '@/hooks/useQueries'; - import { shiftDigits } from '@/utils'; -import { useTx } from '@/hooks'; +import { MultiModal } from './validatorSelectionModal'; const ChakraModalContent = styled(ModalContent)` position: relative; @@ -98,6 +95,8 @@ export const StakingProcessModal: React.FC = ({ isOpen, onClo newChainName = 'osmosistestnet'; } else if (selectedOption?.chainId === 'regen-redwood-1') { newChainName = 'regen'; + } else if (selectedOption?.chainId === 'sommelier-3') { + newChainName = 'sommelier'; } else { // Default case newChainName = selectedOption?.chainName; @@ -155,11 +154,11 @@ export const StakingProcessModal: React.FC = ({ isOpen, onClo const calculateIntents = () => { return selectedValidators.map((validator) => { // For each validator, calculate the weight based on whether default weights are used - const weight = useDefaultWeights ? defaultWeight : weights[validator.operatorAddress]; + const weight = useDefaultWeights ? defaultWeight : weights[validator.operatorAddress] || 0; return { address: validator.operatorAddress, - intent: weight.toFixed(4), // Ensure 4 decimal places + intent: weight.toFixed(4), }; }); }; @@ -167,6 +166,7 @@ export const StakingProcessModal: React.FC = ({ isOpen, onClo // Calculate defaultWeight as string useEffect(() => { setDefaultWeight(1 / numberOfValidators); + //eslint-disable-next-line react-hooks/exhaustive-deps }, [numberOfValidators]); const [useDefaultWeights, setUseDefaultWeights] = useState(true); @@ -182,6 +182,7 @@ export const StakingProcessModal: React.FC = ({ isOpen, onClo })); } } + //eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedValidators, weights, useDefaultWeights]); interface ValidatorsSelect { @@ -245,16 +246,23 @@ export const StakingProcessModal: React.FC = ({ isOpen, onClo }); const mainTokens = assets.find(({ chain_name }) => chain_name === newChainName); - const fees = chains.chains.find(({ chain_name }) => chain_name === newChainName)?.fees?.fee_tokens; + const fees = chains.find(({ chain_name }) => chain_name === newChainName)?.fees?.fee_tokens; const mainDenom = mainTokens?.assets[0].base ?? ''; - const fixedMinGasPrice = fees?.find(({ denom }) => denom === mainDenom)?.average_gas_price ?? ''; - const feeAmount = shiftDigits(fixedMinGasPrice, 6); + let feeAmount; + if (selectedOption?.chainName === 'sommelier') { + // Hardcoded value for sommelier-3 + feeAmount = '10000'; + } else { + // Default case + const fixedMinGasPrice = fees?.find(({ denom }) => denom === mainDenom)?.average_gas_price ?? ''; + feeAmount = shiftDigits(fixedMinGasPrice, 6).toString(); + } const fee: StdFee = { amount: [ { denom: mainDenom, - amount: feeAmount.toString(), + amount: feeAmount, }, ], gas: '500000', @@ -350,7 +358,7 @@ export const StakingProcessModal: React.FC = ({ isOpen, onClo return ( - + diff --git a/web-ui/components/Staking/modals/transferProcessModal.tsx b/web-ui/components/Staking/modals/transferProcessModal.tsx index bbb8b292e..d0be01147 100644 --- a/web-ui/components/Staking/modals/transferProcessModal.tsx +++ b/web-ui/components/Staking/modals/transferProcessModal.tsx @@ -15,26 +15,21 @@ import { StatNumber, Spinner, } from '@chakra-ui/react'; -import chains from '@chalabi/chain-registry'; - import { coins, StdFee } from '@cosmjs/amino'; - import styled from '@emotion/styled'; - +import chains from 'chain-registry'; import { assets } from 'chain-registry'; - +import { cosmos } from 'quicksilverjs'; import React, { useEffect, useState } from 'react'; -import { cosmos } from 'stridejs'; import { useTx } from '@/hooks'; import { useZoneQuery } from '@/hooks/useQueries'; - import { shiftDigits } from '@/utils'; const ChakraModalContent = styled(ModalContent)` position: relative; background: none; - max-height: 400px; + max-height: 450px; &::before, &::after { z-index: -1; @@ -81,6 +76,7 @@ interface StakingModalProps { }; address: string; isTokenized: boolean; + denom: string; } export const TransferProcessModal: React.FC = ({ @@ -90,6 +86,7 @@ export const TransferProcessModal: React.FC = ({ selectedValidator, address, isTokenized, + denom, }) => { useEffect(() => { if (isTokenized === true) { @@ -148,7 +145,8 @@ export const TransferProcessModal: React.FC = ({ const fees = chains.chains.find(({ chain_name }) => chain_name === newChainName)?.fees?.fee_tokens; const mainDenom = mainTokens?.assets[0].base ?? ''; const fixedMinGasPrice = fees?.find(({ denom }) => denom === mainDenom)?.high_gas_price ?? ''; - const feeAmount = shiftDigits(fixedMinGasPrice, 6); + const feeAmount = Number(fixedMinGasPrice) * 750000; + const sendFeeAmount = Number(fixedMinGasPrice) * 100000; const fee: StdFee = { amount: [ @@ -157,12 +155,41 @@ export const TransferProcessModal: React.FC = ({ amount: feeAmount.toString(), }, ], - gas: '200000', + gas: '750000', // test txs were using well in excess of 600k + }; + + // don't use the same fee for both txs, as a send is piddly! + const sendFee: StdFee = { + amount: [ + { + denom: mainDenom, + amount: sendFeeAmount.toString(), + }, + ], + gas: '100000', }; - const { tx } = useTx(newChainName ?? ''); + const { tx, responseEvents } = useTx(newChainName ?? ''); + const [combinedDenom, setCombinedDenom] = useState(); + + // prettier-ignore + useEffect(() => { + + const tokenizeSharesEvent = responseEvents?.find(event => event.type === 'tokenize_shares'); + + if (tokenizeSharesEvent) { + + const validatorValue = tokenizeSharesEvent.attributes.find(attr => attr.key === 'validator')?.value; + const shareRecordIdValue = tokenizeSharesEvent.attributes.find(attr => attr.key === 'share_record_id')?.value; + - const hanleTokenizeShares = async (event: React.MouseEvent) => { + if (validatorValue && shareRecordIdValue) { + setCombinedDenom(`${validatorValue}/${shareRecordIdValue}`); + } + } + }, [responseEvents]); + + const handleTokenizeShares = async (event: React.MouseEvent) => { event.preventDefault(); setIsSigning(true); setTransactionStatus('Pending'); @@ -182,16 +209,18 @@ export const TransferProcessModal: React.FC = ({ setIsSigning(false); } }; + const { send } = cosmos.bank.v1beta1.MessageComposer.withTypeUrl; let numericAmount = selectedValidator.tokenAmount; if (isNaN(Number(numericAmount)) || Number(numericAmount) <= 0) { numericAmount = '0'; } + const msgSend = send({ fromAddress: address ?? '', toAddress: zone?.depositAddress?.address ?? '', - amount: coins(numericAmount, zone?.baseDenom ?? ''), + amount: coins(numericAmount, denom ?? combinedDenom), }); const handleSend = async (event: React.MouseEvent) => { @@ -200,7 +229,7 @@ export const TransferProcessModal: React.FC = ({ setTransactionStatus('Pending'); try { const result = await tx([msgSend], { - fee, + fee: sendFee, onSuccess: () => { setStep(3); setTransactionStatus('Success'); @@ -218,7 +247,7 @@ export const TransferProcessModal: React.FC = ({ return ( - + @@ -302,7 +331,7 @@ export const TransferProcessModal: React.FC = ({ bgColor: 'rgba(255,128,0, 0.25)', color: 'complimentary.300', }} - onClick={hanleTokenizeShares} + onClick={handleTokenizeShares} > {isError ? 'Try Again' : isSigning ? : 'Tokenize Shares'} @@ -341,21 +370,6 @@ export const TransferProcessModal: React.FC = ({ Your q{selectedOption?.value} will arrive to your wallet in a few minutes. - diff --git a/web-ui/components/Staking/modals/validatorSelectionModal.tsx b/web-ui/components/Staking/modals/validatorSelectionModal.tsx index 640f2007e..6ae24d4c7 100644 --- a/web-ui/components/Staking/modals/validatorSelectionModal.tsx +++ b/web-ui/components/Staking/modals/validatorSelectionModal.tsx @@ -24,10 +24,11 @@ import { import React from 'react'; import { FaSearch } from 'react-icons/fa'; -import { ValidatorsTable } from './validatorTable'; import { useValidatorsQuery } from '@/hooks/useQueries'; import { useValidatorLogos } from '@/hooks/useQueries'; + +import { ValidatorsTable } from './validatorTable'; interface MultiModalProps { isOpen: boolean; onClose: () => void; diff --git a/web-ui/components/Staking/stakingBox.tsx b/web-ui/components/Staking/stakingBox.tsx index 3d7092c84..285b161db 100644 --- a/web-ui/components/Staking/stakingBox.tsx +++ b/web-ui/components/Staking/stakingBox.tsx @@ -30,11 +30,9 @@ import { import { Coin, StdFee } from '@cosmjs/amino'; import { useChain } from '@cosmos-kit/react'; import { quicksilver } from 'quicksilverjs'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { FaStar } from 'react-icons/fa'; -import StakingProcessModal from './modals/stakingProcessModal'; -import TransferProcessModal from './modals/transferProcessModal'; import { useTx } from '@/hooks'; import { @@ -46,8 +44,10 @@ import { useValidatorsQuery, useZoneQuery, } from '@/hooks/useQueries'; -import { getExponent } from '@/utils'; -import { shiftDigits } from '@/utils'; +import { getExponent, shiftDigits } from '@/utils'; + +import StakingProcessModal from './modals/stakingProcessModal'; +import TransferProcessModal from './modals/transferProcessModal'; type StakingBoxProps = { selectedOption: { @@ -87,7 +87,9 @@ export const StakingBox = ({ const { address: qAddress } = useChain('quicksilver'); const exp = getExponent(selectedOption.chainName); + const { balance, isLoading } = useBalanceQuery(selectedOption.chainName, address ?? ''); + const { balance: allBalances } = useAllBalancesQuery(selectedOption.chainName, address ?? ''); const { balance: qBalance } = useQBalanceQuery('quicksilver', qAddress ?? '', selectedOption.value.toLowerCase()); @@ -148,7 +150,7 @@ export const StakingBox = ({ amount: [ { denom: 'uqck', - amount: '7500', + amount: '50', }, ], gas: '500000', @@ -177,13 +179,19 @@ export const StakingBox = ({ const { delegations, delegationsIsError, delegationsIsLoading } = useNativeStakeQuery(selectedOption.chainName, address ?? ''); const delegationsResponse = delegations?.delegation_responses; - const nativeStakedAmount = delegationsResponse?.reduce((acc, delegationResponse) => { + const nativeStakedAmount = delegationsResponse?.reduce((acc: number, delegationResponse: { balance: { amount: any } }) => { const amount = Number(delegationResponse?.balance?.amount) || 0; return acc + amount; }, 0); const [useNativeStake, setUseNativeStake] = useState(false); + const hasTokenizedShares = (balances: any[]) => { + return balances.some((balance: { denom: string | string[] }) => balance.denom.includes('valoper')); + }; + + const hasTokenized = useMemo(() => hasTokenizedShares(allBalances?.balances || []), [allBalances]); + const handleSwitchChange = (event: { target: { checked: boolean | ((prevState: boolean) => boolean) } }) => { setUseNativeStake(event.target.checked); }; @@ -210,6 +218,7 @@ export const StakingBox = ({ moniker: string; tokenAmount: string; isTokenized: boolean; + denom: string; } const [selectedValidatorData, setSelectedValidatorData] = useState({ @@ -217,6 +226,7 @@ export const StakingBox = ({ moniker: '', tokenAmount: '', isTokenized: false, + denom: '', }); const isWalletConnected = !!address; @@ -229,6 +239,29 @@ export const StakingBox = ({ liquidStakeTooltip = 'Minimum amount to stake is 0.1'; } + const safeDelegationsResponse = delegationsResponse || []; + const safeAllBalances = allBalances?.balances || []; + + // Combine delegationsResponse with valoper entries from allBalances + const combinedDelegations = safeDelegationsResponse.concat( + safeAllBalances + .filter((balance) => balance.denom.includes('valoper')) + .map((balance) => { + const [validatorAddress, uniqueId] = balance.denom.split('/'); + return { + delegation: { + validator_address: validatorAddress, + unique_id: uniqueId, + }, + balance: { + amount: balance.amount, + }, + isTokenized: true, + denom: balance.denom, + }; + }), + ); + return ( @@ -278,25 +311,33 @@ export const StakingBox = ({ {selectedOption.name === 'Cosmos Hub' && ( - {!nativeStakedAmount && ( + {(nativeStakedAmount > 0 || hasTokenized) && ( 0 + ? `You currently have ${shiftDigits(nativeStakedAmount, -6)} ${ selectedOption.value - } natively staked to ${delegationsResponse?.length} validators. You can tokenize your shares and transfer them to quicksilver by clicking the switch and selecting a validator.` + } natively staked to ${delegationsResponse?.length} validators.` + : hasTokenized + ? 'You have tokenized shares available for transfer.' + : '' } > - - Use natively staked  + + Use staked  {selectedOption.value}? {delegationsIsLoading && } - {!delegationsIsLoading && !delegationsIsError && nativeStakedAmount && ( + {!delegationsIsLoading && !delegationsIsError && ( )} - - + )} - {nativeStakedAmount > 0 && ( - - - Use natively staked  - {selectedOption.value}? - - - - - - - - )} )} {!useNativeStake && ( @@ -397,7 +395,6 @@ export const StakingBox = ({ value={tokenAmount} type="text" onChange={(e) => { - // Allow any numeric input const validNumberPattern = /^\d*\.?\d*$/; if (validNumberPattern.test(e.target.value) || e.target.value === '') { setTokenAmount(e.target.value); @@ -542,27 +539,18 @@ export const StakingBox = ({ - + {/* Combine delegationsResponse with valoper entries from allBalances */} - {delegationsResponse - ?.concat( - allBalances?.balances - .filter((balance) => balance.denom.includes('valoper')) - .map((balance) => { - const [validatorAddress, uniqueId] = balance.denom.split('/'); - return { - delegation: { - validator_address: validatorAddress, - unique_id: uniqueId, // Including the unique ID - }, - balance: { - amount: balance.amount, - }, - isTokenized: true, - }; - }), - ) - .map((delegation, index) => { + {combinedDelegations.map( + ( + delegation: { + delegation: { validator_address: string | number; unique_id: any }; + balance: { amount: string | number }; + isTokenized: any; + denom: any; + }, + index: any, + ) => { const validator = validatorsData?.find((v) => v.address === delegation.delegation.validator_address); const uniqueKey = `${delegation.delegation.validator_address}-${delegation.delegation.unique_id}`; const isSelected = uniqueKey === selectedValidator; @@ -576,10 +564,11 @@ export const StakingBox = ({ onClick={() => { setSelectedValidator(uniqueKey); setSelectedValidatorData({ - operatorAddress: delegation.delegation.validator_address, + operatorAddress: delegation.delegation.validator_address.toString(), moniker: validator?.name ?? '', - tokenAmount: delegation.balance.amount, + tokenAmount: delegation.balance.amount.toString(), isTokenized: delegation.isTokenized, + denom: delegation.denom, }); }} _hover={{ bg: 'rgba(255, 128, 0, 0.25)' }} @@ -622,7 +611,8 @@ export const StakingBox = ({ ); - })} + }, + )} {isBottomVisible && ( )} @@ -731,7 +722,7 @@ export const StakingBox = ({ {address ? qAssets && Number(qAssets) !== 0 - ? `${qAssetsDisplay} ${selectedOption.value.toUpperCase()}` + ? `${qAssetsDisplay} q${selectedOption.value.toUpperCase()}` : `No q${selectedOption.value.toUpperCase()}` : '0'} diff --git a/web-ui/components/react/astronaut.tsx b/web-ui/components/react/astronaut.tsx index 0704133ad..946261f00 100644 --- a/web-ui/components/react/astronaut.tsx +++ b/web-ui/components/react/astronaut.tsx @@ -1,10 +1,5 @@ export const Astronaut = (props: any) => ( - + ( d="M207.344 119.9c.4.467 2.066 2.1 5 4.9-2.667 5.933-5.067 10.333-7.2 13.2-5 6.8-5.3 6.5-10 11.2l-7.9-3.8 20.1-25.5Z" fill="#839091" /> - + ( d="M180.844 148.9c1.933 5.933 3.933 13.767 6 23.5 3.7 17.5 2.9 36.9-4.1 39.9-2.8 1.2-5.4 3.6-16.1 0-7.2-2.4-22.134-12.133-44.8-29.2l59-34.2Z" fill="#D8DEDC" /> - + - + ( d="m79.844 106.3 14.8 30.1-20.1 6.9c-5.8-7.667-9.534-12.967-11.2-15.9-1.667-2.933-4.267-8.867-7.8-17.8l24.3-3.3Z" fill="#839091" /> - + = ({ chainName }) => { +const Header: React.FC<{ chainName: string }> = ({ chainName }) => { return ( - + - - + + ); }; + +export const DynamicHeaderSection = dynamic(() => Promise.resolve(Header), { + ssr: false, +}); diff --git a/web-ui/components/react/sideHeader.tsx b/web-ui/components/react/sideHeader.tsx index cbfff16ad..8f9afe9f0 100644 --- a/web-ui/components/react/sideHeader.tsx +++ b/web-ui/components/react/sideHeader.tsx @@ -21,10 +21,10 @@ import { import { keyframes } from '@emotion/react'; import { useRouter } from 'next/router'; import { useState, useEffect } from 'react'; -import { FaDiscord, FaTwitter, FaGithub, FaInfo } from 'react-icons/fa'; +import { FaDiscord, FaGithub, FaInfo } from 'react-icons/fa'; +import { FaXTwitter } from 'react-icons/fa6'; import { IoIosDocument } from 'react-icons/io'; -import { MdPrivacyTip } from 'react-icons/md'; -import { WalletButton } from '../wallet-button'; + import { DrawerControlProvider } from '@/state/chains/drawerControlProvider'; export const SideHeader = () => { @@ -34,13 +34,19 @@ export const SideHeader = () => { const [showSocialLinks, setShowSocialLinks] = useState(false); useEffect(() => { + const path = router.asPath.split('/')[1]; + setSelectedPage(path); + const handleRouteChange = (url: string) => { - const path = url.split('/quicksilver/')[1]; - setSelectedPage(path); + const newPath = url.split('/')[1]; + setSelectedPage(newPath); }; router.events.on('routeChangeComplete', handleRouteChange); - return () => router.events.off('routeChangeComplete', handleRouteChange); + + return () => { + router.events.off('routeChangeComplete', handleRouteChange); + }; }, [router]); const commonBoxShadowColor = 'rgba(255, 128, 0, 0.25)'; @@ -107,8 +113,9 @@ export const SideHeader = () => { alt="logo" h="75px" w="75px" + padding="5px" borderRadius="full" - src="/quicksilver/img/networks/quicksilver.svg" + src="/img/networks/quicksilver.svg" onClick={handleLogoClick} cursor="pointer" _hover={{ @@ -128,7 +135,7 @@ export const SideHeader = () => { QUICKSILVER - {['Airdrop', 'Assets', 'Defi', 'Governance', 'Staking'].map((item) => ( + {[/*'Airdrop', */ 'Assets', 'Defi', 'Governance', 'Staking'].map((item) => ( { ))} - - - + - - - - - - - - - + + + + + + + + + + + + + + + @@ -218,14 +229,14 @@ export const SideHeader = () => { }} > @@ -248,14 +259,14 @@ export const SideHeader = () => { }} > @@ -276,17 +287,17 @@ export const SideHeader = () => { }} > - + {/* { }} alt="DeFi" h="55px" - src="/quicksilver/img/airdrop.png" + src="/img/airdrop.png" /> - + */} { }} > @@ -346,17 +357,18 @@ export const SideHeader = () => { {showSocialLinks && ( - - router.push('/about')} - _hover={{ - cursor: 'pointer', - boxShadow: `0 0 15px 5px ${commonBoxShadowColor}, inset 0 0 50px 5px ${commonBoxShadowColor}`, - }} - > - - - + + + + + + + { transition: transitionStyle, }} > - + - + {/* router.push('/privacy-policy')} _hover={{ @@ -420,7 +432,7 @@ export const SideHeader = () => { > - + */} )} diff --git a/web-ui/components/react/wallet-connect.tsx b/web-ui/components/react/wallet-connect.tsx index ce55dd539..a44b924c2 100644 --- a/web-ui/components/react/wallet-connect.tsx +++ b/web-ui/components/react/wallet-connect.tsx @@ -1,23 +1,13 @@ import { Button, Icon, Stack, Text, useColorModeValue } from '@chakra-ui/react'; import { WalletStatus } from '@cosmos-kit/core'; -import { useChain, useWalletClient } from '@cosmos-kit/react'; -import React, { MouseEventHandler, ReactNode, useEffect } from 'react'; +import React, { MouseEventHandler, ReactNode } from 'react'; import { FiAlertTriangle } from 'react-icons/fi'; import { IoWallet } from 'react-icons/io5'; import { ConnectWalletType } from '../types'; -export const ConnectWalletButton = ({ buttonText, isLoading, isDisabled, icon }: ConnectWalletType) => { +export const ConnectWalletButton = ({ buttonText, isLoading, isDisabled, icon, onClickConnectBtn }: ConnectWalletType) => { const invertButtonTextColor = useColorModeValue('primary.50', 'primary.700'); - const { openView } = useChain('quicksilver'); - const { status, client } = useWalletClient(); - - useEffect(() => { - if (status === 'Done') { - client?.enable?.(['cosmoshub-4', 'osmosis-1', 'regen-1', 'sommelier-3', 'stargaze-1']); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [status]); return (