diff --git a/.github/workflows/devWebUiDeploy.yaml b/.github/workflows/devWebUiDeploy.yaml index 20c46e83e..3ace0d25c 100644 --- a/.github/workflows/devWebUiDeploy.yaml +++ b/.github/workflows/devWebUiDeploy.yaml @@ -3,12 +3,12 @@ name: Build & Deploy web-ui with dev environment to Github Pages on: push: branches: - - bunDev + - bunDev2 paths: - web-ui/** pull_request: branches: - - bunDev + - bunDev2 paths: - web-ui/** diff --git a/web-ui/.env.development b/web-ui/.env.development index b4428c9da..d041ac509 100644 --- a/web-ui/.env.development +++ b/web-ui/.env.development @@ -1,29 +1,29 @@ -NEXT_PUBLIC_CHAIN_ENV="mainnet" -NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_QUICKSILVER="https://lcd.quicksilver.zone/" -NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_QUICKSILVER="https://rpc.quicksilver.zone/" -NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_COSMOSHUB=https://lcd.cosmoshub-4.quicksilver.zone -NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_COSMOSHUB=https://rpc.cosmoshub-4.quicksilver.zone -NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_OSMOSIS="https://lcd.osmosis-1.quicksilver.zone" -NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_OSMOSIS="https://rpc.osmosis-1.quicksilver.zone" -NEXT_PUBLIC_MAINNET_LCD_ENDPOINT_STARGAZE="https://lcd.stargaze-1.quicksilver.zone" -NEXT_PUBLIC_MAINNET_RPC_ENDPOINT_STARGAZE="https://rpc.stargaze-1.quicksilver.zone" -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_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" -NEXT_PUBLIC_WHITELISTED_ZONES="osmosis-1,stargaze-1,regen-1,cosmoshub-4,sommelier-3" -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_CHAIN_ENV="testnet" +# NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_QUICKSILVER="https://lcd.test.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_QUICKSILVER="https://rpc.test.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_COSMOSHUB=https://lcd.provider.test.quicksilver.zone +# NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_COSMOSHUB=https://rpc.provider.test.quicksilver.zone +# NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_OSMOSIS="https://lcd.osmo-test-5.test.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_OSMOSIS="https://rpc.osmo-test-5.test.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_STARGAZE="https://lcd.elgafar-1.test.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_STARGAZE="https://rpc.elgafar-1.test.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_REGEN="https://lcd.regen-redwood-1.test.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_REGEN="https://rpc.regen-redwood-1.test.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_LCD_ENDPOINT_SOMMELIER="https://lcd.sommelier-3.quicksilver.zone" +# NEXT_PUBLIC_TESTNET_RPC_ENDPOINT_SOMMELIER="https://rpc.sommelier-3.quicksilver.zone" +# 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" +# NEXT_PUBLIC_WHITELISTED_ZONES="osmosis-1,stargaze-1,regen-1,cosmoshub-4,sommelier-3" +# NEXT_PUBLIC_COSMOSHUB_CHAIN_ID=provider +# NEXT_PUBLIC_OSMOSIS_CHAIN_ID=osmo-test-5 +# NEXT_PUBLIC_STARGAZE_CHAIN_ID=elgafar-1 +# NEXT_PUBLIC_REGEN_CHAIN_ID=regen-redwood-1 +# NEXT_PUBLIC_SOMMELIER_CHAIN_ID=sommelier-3 \ No newline at end of file diff --git a/web-ui/README.md b/web-ui/README.md index 5cce4673c..a10bfc611 100644 --- a/web-ui/README.md +++ b/web-ui/README.md @@ -43,22 +43,13 @@ Please ensure your IDE is configured to use Typescript v4.9.3 **Mobile Menu** -- connect wallet button - - graphic elements -- font size / style / decorations - **DevOps** - make onboarding networks seamless -**Assets Page** - -- claim rewards claim.test.quicksilver.zone/address/current \*/epoch - -- intent query - -claim rewards - rewards get claimed at epoch after your submit tx its not immediate +**Blockers** -no solid way to track rewards rn +- main net reward current & epoch queries +- update grantee address in query & tx diff --git a/web-ui/bun.lockb b/web-ui/bun.lockb index 5dfe1432c..b03ea87e9 100755 Binary files a/web-ui/bun.lockb and b/web-ui/bun.lockb differ diff --git a/web-ui/components/Airdrop/airdropSection.tsx b/web-ui/components/Airdrop/airdropSection.tsx index e1558a4f9..b99f3cac0 100644 --- a/web-ui/components/Airdrop/airdropSection.tsx +++ b/web-ui/components/Airdrop/airdropSection.tsx @@ -1,11 +1,9 @@ -import { useAccordionStyles } from '@chakra-ui/accordion'; -import { CheckIcon, ChevronDownIcon, ChevronRightIcon, InfoOutlineIcon } from '@chakra-ui/icons'; +import { ChevronDownIcon, ChevronRightIcon, InfoOutlineIcon } from '@chakra-ui/icons'; import { Accordion, AccordionItem, AccordionButton, AccordionPanel, - AccordionIcon, Box, Button, Flex, @@ -14,12 +12,10 @@ import { Tooltip, VStack, HStack, - useColorModeValue, Icon, Badge, useDisclosure, } from '@chakra-ui/react'; -import { useState } from 'react'; interface AirdropAccordionItemProps { index: number; @@ -67,8 +63,6 @@ const AirdropAccordionItem: React.FC = ({ index, defa }; const AirdropSection = () => { - const { isOpen, onToggle } = useDisclosure(); - return isBeta ? ( // What to render if isBeta is true { left: 0, right: 0, bottom: 0, - backgroundImage: "url('/quicksilver/img/underConstruction.png')", + backgroundSize: 'contain', backgroundPosition: 'center', backdropFilter: 'blur(10px)', diff --git a/web-ui/components/Assets/assetsGrid.tsx b/web-ui/components/Assets/assetsGrid.tsx index 5cc20c208..d6e25e67b 100644 --- a/web-ui/components/Assets/assetsGrid.tsx +++ b/web-ui/components/Assets/assetsGrid.tsx @@ -1,4 +1,4 @@ -import { Box, SimpleGrid, VStack, Text, Button, Divider, useColorModeValue, HStack, Flex, Grid, GridItem, Spinner } from '@chakra-ui/react'; +import { Box, VStack, Text, Divider, HStack, Flex, Grid, GridItem, Spinner } from '@chakra-ui/react'; import React from 'react'; import QDepositModal from './modals/qTokenDepositModal'; @@ -31,10 +31,6 @@ type Amount = { amount: string; }; -type Asset = { - [key: string]: Amount[]; -}; - type Errors = { Errors: any; }; @@ -52,20 +48,36 @@ type LiquidRewardsData = { errors: Errors; }; -type UseLiquidRewardsQueryReturnType = { - liquidRewards: LiquidRewardsData | undefined; - isLoading: boolean; - isError: boolean; -}; - const AssetCard: React.FC = ({ assetName, balance, apy, nativeAssetName, isWalletConnected, nonNative }) => { - const nonNativeBalance = nonNative?.assets['quicksilver-2'] - ? nonNative.assets['quicksilver-2'][0].Amount.find((amount) => amount.denom === `uq${nativeAssetName.toLowerCase()}`) + 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']; + let totalAmount = 0; + + chainIds.forEach((chainId) => { + const assetsInChain = nonNative?.assets[chainId]; + if (assetsInChain) { + assetsInChain.forEach((asset: any) => { + const assetAmount = asset.Amount.find((amount: { denom: string }) => amount.denom === `uq${nativeAssetName.toLowerCase()}`); + if (assetAmount) { + totalAmount += parseInt(assetAmount.amount, 10); // assuming amount is a string + } + }); + } + }); + + 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 formattedNonNativeBalance = nonNativeBalance - ? shiftDigits(nonNativeBalance.amount, -6) // Assuming the amount is in micro units - : '0'; + const formattedNonNativeBalance = calculateTotalBalance(nonNative, nativeAssetName); + + const formattedNativebalance = nativeAssets ? shiftDigits(nativeAssets.amount, -6) : '0'; if (!balance || !apy) { return ( @@ -109,7 +121,7 @@ const AssetCard: React.FC = ({ assetName, balance, apy, nativeAs - {balance} + {formattedNativebalance} diff --git a/web-ui/components/Assets/intents.tsx b/web-ui/components/Assets/intents.tsx index 2174ce468..0e7f7cdf4 100644 --- a/web-ui/components/Assets/intents.tsx +++ b/web-ui/components/Assets/intents.tsx @@ -1,12 +1,26 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons'; -import { Box, Flex, Text, Button, IconButton, VStack, Image, Heading, SlideFade, Spinner } from '@chakra-ui/react'; -import { color } from 'framer-motion'; -import { useState } from 'react'; +import { + Box, + Flex, + Text, + Button, + IconButton, + VStack, + Image, + Heading, + SlideFade, + Spinner, + SkeletonCircle, + SkeletonText, +} from '@chakra-ui/react'; + +import { Key, useState } from 'react'; import SignalIntentModal from './modals/signalIntentProcess'; -import { useIntentQuery } from '@/hooks/useQueries'; +import { useIntentQuery, useValidatorLogos, useValidatorsQuery } from '@/hooks/useQueries'; import { networks as prodNetworks, testNetworks as devNetworks } from '@/state/chains/prod'; +import { truncateString } from '@/utils'; export interface StakingIntentProps { address: string; @@ -14,10 +28,6 @@ export interface StakingIntentProps { } const StakingIntent: React.FC = ({ address, isWalletConnected }) => { - const validators = [ - { name: '', logo: '', percentage: '' }, - { name: '', logo: '', percentage: '' }, - ]; const networks = process.env.NEXT_PUBLIC_CHAIN_ENV === 'mainnet' ? prodNetworks : devNetworks; const chains = ['Cosmos', 'Osmosis', 'Stargaze', 'Regen', 'Sommelier']; @@ -29,7 +39,38 @@ const StakingIntent: React.FC = ({ address, isWalletConnecte const currentNetwork = networks[currentChainIndex]; - const { intent, isLoading, isError } = useIntentQuery(currentNetwork.chainName, address ?? ''); + const { validatorsData } = useValidatorsQuery(currentNetwork.chainName); + const { data: validatorLogos } = useValidatorLogos(currentNetwork.chainName, validatorsData || []); + + const { intent, refetch } = useIntentQuery(currentNetwork.chainName, address ?? ''); + + interface ValidatorDetails { + moniker: string; + logoUrl: string | undefined; + } + + interface ValidatorMap { + [valoper_address: string]: ValidatorDetails; + } + + const validatorsMap: ValidatorMap = + validatorsData?.reduce((map: ValidatorMap, validatorInfo) => { + map[validatorInfo.address] = { + moniker: validatorInfo.name, + logoUrl: validatorLogos?.[validatorInfo.address], + }; + return map; + }, {}) || {}; + + const validatorsWithDetails = + intent?.data?.intent.intents.map((validatorIntent: { valoper_address: string; weight: string }) => { + const validatorDetails = validatorsMap[validatorIntent.valoper_address]; + return { + moniker: validatorDetails?.moniker, + logoUrl: validatorDetails?.logoUrl, + percentage: `${(parseFloat(validatorIntent.weight) * 100).toFixed(2)}%`, + }; + }) || []; const handleLeftArrowClick = () => { setCurrentChainIndex((prevIndex) => (prevIndex === 0 ? networks.length - 1 : prevIndex - 1)); @@ -78,7 +119,12 @@ const StakingIntent: React.FC = ({ address, isWalletConnecte Edit Intent - + @@ -111,17 +157,50 @@ const StakingIntent: React.FC = ({ address, isWalletConnecte /> - - {validators.map((validator, index) => ( - - - {validator.name} + + {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} - - - ))} + ), + )} diff --git a/web-ui/components/Assets/modals/intentMultiModal.tsx b/web-ui/components/Assets/modals/intentMultiModal.tsx index 553cab4a8..15a7c9394 100644 --- a/web-ui/components/Assets/modals/intentMultiModal.tsx +++ b/web-ui/components/Assets/modals/intentMultiModal.tsx @@ -25,7 +25,7 @@ import React from 'react'; import { FaSearch } from 'react-icons/fa'; import { ValidatorsTable } from '@/components/Staking/modals/validatorTable'; -import { useValidatorsQuery, useZoneQuery } from '@/hooks/useQueries'; +import { useValidatorsQuery } from '@/hooks/useQueries'; import { useValidatorLogos } from '@/hooks/useQueries'; interface MultiModalProps { @@ -44,13 +44,12 @@ export const IntentMultiModal: React.FC = ({ selectedChainName, selectedValidators, setSelectedValidators, - selectedChainId, }) => { const [searchTerm, setSearchTerm] = React.useState(''); - const { validatorsData, isLoading, isError } = useValidatorsQuery(selectedChainName); + const { validatorsData, isLoading } = useValidatorsQuery(selectedChainName); - const { data: logos, isLoading: isFetchingLogos } = useValidatorLogos(selectedChainName, validatorsData || []); + const { data: logos } = useValidatorLogos(selectedChainName, validatorsData || []); const validators = validatorsData; const handleValidatorClick = (validator: { name: string; operatorAddress: string }) => { @@ -66,17 +65,6 @@ export const IntentMultiModal: React.FC = ({ }); }; - const handleQuickSelect = (count: number) => { - if (!validatorsData || !validators) return; - - const topValidators = validators.slice(0, count).map((validator) => ({ - name: validator.name, - operatorAddress: validator.address, - })); - - setSelectedValidators(topValidators); - }; - const handleSearchChange = (event: React.ChangeEvent) => { setSearchTerm(event.target.value.toLowerCase()); }; @@ -179,8 +167,13 @@ export const IntentMultiModal: React.FC = ({ onClick={onClose} h="30px" w="25%" + _active={{ + transform: 'scale(0.95)', + color: 'complimentary.800', + }} _hover={{ - bgColor: '#181818', + bgColor: 'rgba(255,128,0, 0.25)', + color: 'complimentary.300', }} > Return diff --git a/web-ui/components/Assets/modals/qckDepositModal.tsx b/web-ui/components/Assets/modals/qckDepositModal.tsx index 279d6a332..f66d67efc 100644 --- a/web-ui/components/Assets/modals/qckDepositModal.tsx +++ b/web-ui/components/Assets/modals/qckDepositModal.tsx @@ -11,7 +11,6 @@ import { FormLabel, Input, useDisclosure, - useToast, Spinner, } from '@chakra-ui/react'; import { ibc } from '@chalabi/quicksilverjs'; @@ -30,7 +29,6 @@ import { getCoin, getIbcInfo, shiftDigits } from '@/utils'; export function DepositModal() { const { isOpen, onOpen, onClose } = useDisclosure(); - const toast = useToast(); const [chainName, setChainName] = useState('osmosis'); const { chainRecords, getChainLogo } = useManager(); @@ -67,17 +65,14 @@ export function DepositModal() { const toChain = 'quicksilver'; const { transfer } = ibc.applications.transfer.v1.MessageComposer.withTypeUrl; - const { address, connect, status, message, wallet } = useChain(fromChain ?? ''); + const { address } = useChain(fromChain ?? ''); 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); - const coin = getCoin(fromChain ?? ''); const transferAmount = new BigNumber(amount).shiftedBy(6).toString(); const mainTokens = assets.find(({ chain_name }) => chain_name === chainName); diff --git a/web-ui/components/Assets/modals/qckWithdrawModal.tsx b/web-ui/components/Assets/modals/qckWithdrawModal.tsx index 3f5a01567..f010f0c78 100644 --- a/web-ui/components/Assets/modals/qckWithdrawModal.tsx +++ b/web-ui/components/Assets/modals/qckWithdrawModal.tsx @@ -10,7 +10,6 @@ import { FormControl, FormLabel, Input, - Select, useDisclosure, useToast, Spinner, @@ -30,7 +29,6 @@ import { getCoin, getIbcInfo } from '@/utils'; export function WithdrawModal() { const { isOpen, onOpen, onClose } = useDisclosure(); - const toast = useToast(); const [chainName, setChainName] = useState('osmosis'); const { chainRecords, getChainLogo } = useManager(); @@ -67,9 +65,9 @@ export function WithdrawModal() { const toChain = chainName; const { transfer } = ibc.applications.transfer.v1.MessageComposer.withTypeUrl; - const { address, connect, status, message, wallet } = useChain(toChain ?? ''); + const { address } = useChain(toChain ?? ''); const { address: qAddress } = useChain('quicksilver'); - const { balance } = useIbcBalanceQuery(fromChain ?? '', qAddress ?? ''); + const { tx } = useTx(fromChain ?? ''); const onSubmitClick = async () => { diff --git a/web-ui/components/Assets/modals/signalIntentProcess.tsx b/web-ui/components/Assets/modals/signalIntentProcess.tsx index e6eb87b59..052e2623c 100644 --- a/web-ui/components/Assets/modals/signalIntentProcess.tsx +++ b/web-ui/components/Assets/modals/signalIntentProcess.tsx @@ -12,18 +12,17 @@ import { Button, Stat, StatLabel, - Toast, Spinner, Input, Grid, } from '@chakra-ui/react'; -import { coins, StdFee } from '@cosmjs/amino'; +import { 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 { quicksilver } from 'quicksilverjs'; -import { ValidatorIntent } from 'quicksilverjs/types/codegen/quicksilver/interchainstaking/v1/interchainstaking'; + import React, { useEffect, useState } from 'react'; import { IntentMultiModal } from './intentMultiModal'; @@ -64,7 +63,7 @@ interface StakingModalProps { isOpen: boolean; onClose: () => void; children?: React.ReactNode; - + refetch: () => void; selectedOption?: { name: string; value: string; @@ -74,7 +73,12 @@ interface StakingModalProps { }; } -export const SignalIntentModal: React.FC = ({ isOpen, onClose, selectedOption }) => { +interface Intent { + valoperAddress: string; + weight: string; +} + +export const SignalIntentModal: React.FC = ({ isOpen, onClose, selectedOption, refetch }) => { const [step, setStep] = React.useState(1); const getProgressColor = (circleStep: number) => { if (step >= circleStep) return 'complimentary.900'; @@ -84,29 +88,13 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose const [isSigning, setIsSigning] = useState(false); const [isError, setIsError] = useState(false); - let newChainName: string | undefined; - if (selectedOption?.chainId === 'provider') { - newChainName = 'rsprovidertestnet'; - } else if (selectedOption?.chainId === 'elgafar-1') { - newChainName = 'stargazetestnet'; - } else if (selectedOption?.chainId === 'osmo-test-5') { - newChainName = 'osmosistestnet'; - } else if (selectedOption?.chainId === 'regen-redwood-1') { - newChainName = 'regen'; - } else { - // Default case - newChainName = selectedOption?.chainName; - } - - const { address, getSigningStargateClient } = useChain(newChainName || ''); + const { address } = useChain('quicksilver' || ''); const labels = ['Choose validators', `Set weights`, `Sign & Submit`, `Receive q${selectedOption?.value}`]; const [isModalOpen, setModalOpen] = useState(false); const [selectedValidators, setSelectedValidators] = React.useState<{ name: string; operatorAddress: string }[]>([]); - const [resp, setResp] = useState(''); - const advanceStep = () => { if (selectedValidators.length > 0) { setStep((prevStep) => prevStep + 1); @@ -114,7 +102,7 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose }; const retreatStep = () => { - if (step === 3 && check) { + if (step === 3) { setStep(1); // If on step 3 and checkbox is checked, go back to step 1 } else { setStep((prevStep) => Math.max(prevStep - 1, 1)); // Otherwise, go to the previous step @@ -124,14 +112,9 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose const totalWeights = 1; const numberOfValidators = selectedValidators.length; - // Calculate the weight for each validator - const weightPerValidator = numberOfValidators ? (totalWeights / numberOfValidators).toFixed(4) : '0'; - const [weights, setWeights] = useState<{ [key: string]: number }>({}); - const [totalWeight, setTotalWeight] = useState('0'); const [isCustomValid, setIsCustomValid] = useState(true); - const [defaultWeight, setDefaultWeight] = useState(0); useEffect(() => { // Update the state when selectedValidators changes @@ -148,55 +131,54 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose // Update the total weight as string const newTotalWeight = Object.values({ ...weights, [validatorName]: value }).reduce((acc, val) => acc + val, 0); - setTotalWeight(newTotalWeight.toString()); setIsCustomValid(newTotalWeight === 100); // Validation for custom weights }; - // Calculate defaultWeight as string - useEffect(() => { - setDefaultWeight(1 / numberOfValidators); - }, [numberOfValidators]); + // Calculate default weight per validator + const weightPerValidator = (totalWeights / numberOfValidators).toFixed(4); - const [useDefaultWeights, setUseDefaultWeights] = useState(true); + // Initialize intents array + let intents: Intent[] = []; - const intents: ValidatorIntent[] = selectedValidators.map((validator) => ({ - valoperAddress: validator.operatorAddress, - weight: (useDefaultWeights ? defaultWeight : weights[validator.operatorAddress]).toString() || '0', - })); + // Assign default or custom weight to each validator + selectedValidators.forEach((validator, index) => { + const customWeight = weights[validator.operatorAddress]; + const weight = customWeight !== undefined ? (customWeight / 100).toFixed(4) : weightPerValidator; + intents.push({ + valoperAddress: validator.operatorAddress, + weight: weight, + }); + }); - const valToByte = (val: number) => { - if (val > 1) { - val = 1; - } - if (val < 0) { - val = 0; - } - return Math.abs(val * 200); - }; + // Calculate the total assigned weight + const totalAssignedWeight = intents.reduce((sum, intent) => sum + parseFloat(intent.weight), 0); - const addValidator = (valAddr: string, weight: number) => { - let { words } = bech32.decode(valAddr); - let wordsUint8Array = new Uint8Array(bech32.fromWords(words)); - let weightByte = valToByte(weight); - return Buffer.concat([Buffer.from([weightByte]), wordsUint8Array]); - }; + // If the total weight is not equal to 1, adjust the last validator's weight + if (totalAssignedWeight !== 1 && intents.length > 0) { + const lastValidatorWeight = parseFloat(intents[intents.length - 1].weight); + const remainingWeight = (1 - (totalAssignedWeight - lastValidatorWeight)).toFixed(4); + intents[intents.length - 1].weight = remainingWeight; + } + + // Create formatted intents string + const formattedIntentsString = intents.map((intent) => `${intent.weight}${intent.valoperAddress}`).join(','); - let memoBuffer = Buffer.alloc(0); + const remainingWeight = (1 - totalAssignedWeight).toFixed(4); - if (intents.length > 0) { - intents.forEach((val) => { - memoBuffer = Buffer.concat([memoBuffer, addValidator(val.valoperAddress, Number(val.weight))]); + // Assign the remaining weight to the last validator + if (selectedValidators.length > 0) { + const lastValidator = selectedValidators[selectedValidators.length - 1]; + intents.push({ + valoperAddress: lastValidator.operatorAddress, + weight: remainingWeight, }); - memoBuffer = Buffer.concat([Buffer.from([0x02, memoBuffer.length]), memoBuffer]); } - let memo = memoBuffer.length > 0 && selectedValidators.length > 0 ? memoBuffer.toString('base64') : ''; - const { signalIntent } = quicksilver.interchainstaking.v1.MessageComposer.withTypeUrl; const msgSignalIntent = signalIntent({ chainId: selectedOption?.chainId ?? '', - intents: memo, + intents: formattedIntentsString, fromAddress: address ?? '', }); @@ -213,25 +195,23 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose gas: '500000', }; - const { tx } = useTx(newChainName ?? ''); - - const [transactionStatus, setTransactionStatus] = useState('Pending'); + const { tx } = useTx('quicksilver' ?? ''); const handleSignalIntent = async (event: React.MouseEvent) => { event.preventDefault(); setIsSigning(true); - setTransactionStatus('Pending'); + try { const result = await tx([msgSignalIntent], { fee, onSuccess: () => { - setStep(4); - setTransactionStatus('Success'); + refetch(); + onClose(); }, }); } catch (error) { console.error('Transaction failed', error); - setTransactionStatus('Failed'); + setIsError(true); } finally { setIsSigning(false); @@ -243,14 +223,12 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose setStep(1); setIsError(false); setIsSigning(false); - setUseDefaultWeights(true); }, [selectedOption?.chainName]); const [isCustomWeight, setIsCustomWeight] = useState(false); const handleCustomWeightMode = () => { setIsCustomWeight(true); - setUseDefaultWeights(false); }; const handleNextInCustomWeightMode = () => { @@ -260,22 +238,11 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose } }; - const [check, setCheck] = useState(false); - - const handleCheck = () => { - setCheck(!check); - }; - const handleStepOneButtonClick = () => { // Check if only one validator is selected if (selectedValidators.length === 1) { - setUseDefaultWeights(true); setStep(3); // Skip directly to step 3 - } else if (check) { - // If checkbox is checked, skip directly to step 3 - setStep(3); } else { - // If checkbox is not checked, consider the state of selectedValidators if (selectedValidators.length === 0) { setModalOpen(true); } else { @@ -378,12 +345,17 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose = ({ isOpen, onClose - @@ -537,8 +535,13 @@ export const SignalIntentModal: React.FC = ({ isOpen, onClose - - - - - {!isLoading && - liquidRewards?.assets?.['rhye-2']?.map((assetGroup) => - assetGroup.Amount.map((asset, index) => ( - - {Number(shiftDigits(asset.amount, -6)).toLocaleString()} {asset.denom.toUpperCase().slice(1)} - - )), - )} - {isLoading && Loading rewards...} - - - - - + + Claim your participation rewards. Rewards will be sent to your wallet at the next epoch. + + + + + Enable Automatic Claiming + + + + - - - - - Participation Rewards - - - More information about participation rewards... - - - - + ); }; diff --git a/web-ui/components/Assets/unbondingTable.tsx b/web-ui/components/Assets/unbondingTable.tsx index a6d1292c6..56dea1f73 100644 --- a/web-ui/components/Assets/unbondingTable.tsx +++ b/web-ui/components/Assets/unbondingTable.tsx @@ -157,7 +157,6 @@ const UnbondingAssetsTable: React.FC = ({ address, is backdropFilter="blur(50px)" bgColor="rgba(255,255,255,0.1)" h="sm" - p={4} borderRadius="lg" flexDirection="column" justifyContent="center" @@ -174,7 +173,7 @@ const UnbondingAssetsTable: React.FC = ({ address, is You have no unbonding assets. ) : ( - + diff --git a/web-ui/components/Defi/defiBox.tsx b/web-ui/components/Defi/defiBox.tsx index 15b3dedd9..5c678681d 100644 --- a/web-ui/components/Defi/defiBox.tsx +++ b/web-ui/components/Defi/defiBox.tsx @@ -1,9 +1,8 @@ -import { ChevronDownIcon, ExternalLinkIcon } from '@chakra-ui/icons'; +import { ChevronDownIcon, ChevronUpIcon, ExternalLinkIcon } from '@chakra-ui/icons'; import { Box, Button, Flex, - Heading, Table, Thead, Tbody, @@ -13,10 +12,9 @@ import { Text, Select, Stack, - useColorModeValue, - ButtonGroup, - HStack, + Image, Link, + Tooltip, Center, Spinner, useBreakpointValue, @@ -24,15 +22,8 @@ import { import React, { useState } from 'react'; import { useDefiData } from '@/hooks/useQueries'; + type ActionButtonTitle = 'Add Liquidity' | 'Borrow' | 'Lend' | 'Mint Stablecoin' | 'Vaults'; -interface DefiAsset { - id: string; - assetPair: string; - apy: number; - tvl: string; - provider: string; - action: string; -} const actionTitles: Record = { 'add-liquidity': 'Add Liquidity', @@ -48,8 +39,12 @@ interface DefiData { tvl: number; provider: string; action: string; + link: string; } +type SortOrder = 'asc' | 'desc'; +type SortableColumn = 'apy' | 'tvl'; + const filterCategories: Record boolean> = { All: () => true, 'Borrowing & Lending': (data: DefiData) => data.action === 'Borrow' || data.action === 'Lend', @@ -59,7 +54,7 @@ const filterCategories: Record boolean> = { }; const formatApy = (apy: number) => { - return `${(apy * 100).toFixed(2)}%`; // Converts to percentage and formats to 2 decimal places + return `${(apy * 100).toFixed(2)}%`; }; const DefiTable = () => { @@ -75,9 +70,49 @@ const DefiTable = () => { const handleFilterClick = (filter: string) => { setActiveFilter(filter); }; - const isMobile = useBreakpointValue({ base: true, 1013: true, md: false }); + const isMobile = useBreakpointValue({ base: true, sm: true, md: false }); 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', + }; + + const isProviderKey = (key: string): key is ProviderKey => { + return key in providerIcons; + }; + + const [sortColumn, setSortColumn] = useState(null); + const [sortOrder, setSortOrder] = useState('asc'); + + const sortData = (data: DefiData[], column: SortableColumn | null, order: SortOrder) => { + if (!column) return data; + return [...data].sort((a, b) => { + let comparison = 0; + if (column === 'apy' || column === 'tvl') { + comparison = a[column] - b[column]; + } else { + comparison = a[column] > b[column] ? 1 : -1; + } + + return order === 'asc' ? comparison : -comparison; + }); + }; + + const handleSort = (column: SortableColumn) => { + if (sortColumn === column) { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + } else { + setSortColumn(column); + setSortOrder('asc'); + } + }; + + const sortedData = sortData(filteredData, sortColumn, sortOrder); + return ( {isMobile ? ( @@ -135,14 +170,60 @@ const DefiTable = () => {
- - + - )} {defi && - filteredData.map((asset, index) => ( + sortedData.map((asset, index) => ( - @@ -178,14 +258,33 @@ const DefiTable = () => { {formatApy(asset.apy)} -
- Asset Pair - - APY + Asset Pair handleSort('apy')} + style={{ cursor: 'pointer' }} + > + APY{' '} + {sortColumn === 'apy' ? ( + sortOrder === 'asc' ? ( + + ) : ( + + ) + ) : ( + + )} - TVL + handleSort('tvl')} + > + TVL{' '} + {sortColumn === 'tvl' ? ( + sortOrder === 'asc' ? ( + + ) : ( + + ) + ) : ( + + )} Provider @@ -160,17 +241,16 @@ const DefiTable = () => { {' '} {/* Span across all columns */}
- +
+ - {asset.assetPair} - {asset.tvl} + ${asset.tvl.toLocaleString()} - {asset.provider} + + {isProviderKey(asset.provider.toLowerCase()) && ( + +
+ {asset.provider} +
+
+ )}
- diff --git a/web-ui/components/Governance/ProposalModal.tsx b/web-ui/components/Governance/ProposalModal.tsx index 1ff614e27..29003d9b4 100644 --- a/web-ui/components/Governance/ProposalModal.tsx +++ b/web-ui/components/Governance/ProposalModal.tsx @@ -15,7 +15,6 @@ import { Divider, Heading, useColorMode, - useColorModeValue, } from '@chakra-ui/react'; import { cosmos } from 'interchain-query'; import { Proposal } from 'interchain-query/cosmos/gov/v1/gov'; @@ -52,7 +51,6 @@ export const ProposalModal = ({ }) => { const [showMore, setShowMore] = useState(false); const voteModalControl = useDisclosure(); - const { colorMode } = useColorMode(); const coin = getCoin(chainName); const exponent = getExponent(chainName); @@ -190,8 +188,13 @@ export const ProposalModal = ({ diff --git a/web-ui/components/Governance/VoteModal.tsx b/web-ui/components/Governance/VoteModal.tsx index f7055fd45..6741a19f6 100644 --- a/web-ui/components/Governance/VoteModal.tsx +++ b/web-ui/components/Governance/VoteModal.tsx @@ -126,8 +126,13 @@ export const VoteModal: React.FC = ({ modalControl, chainName, u {selectedValidators.length > 1 && ( - @@ -592,8 +649,13 @@ export const StakingProcessModal: React.FC = ({ isOpen, onClo