diff --git a/Dockerfile b/Dockerfile
index 96806e0..16df3ca 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Use the official Node.js v14.x image as the base image
-FROM node:14
+FROM node:16
# Set the working directory to /app
WORKDIR /app
diff --git a/components/chain-wallet-card.tsx b/components/apps/dashboard/chain-wallet-card.tsx
similarity index 94%
rename from components/chain-wallet-card.tsx
rename to components/apps/dashboard/chain-wallet-card.tsx
index 492fd5f..6305201 100644
--- a/components/chain-wallet-card.tsx
+++ b/components/apps/dashboard/chain-wallet-card.tsx
@@ -1,65 +1,65 @@
-import { Box, Button, Heading, HStack } from "@chakra-ui/react";
-import { ChainName, WalletStatus } from "@cosmos-kit/core";
-import { useChain } from "@cosmos-kit/react";
-import { useEffect } from "react";
-import { ConnectedShowAddress } from "./react";
-
-export const ChainWalletCard = ({
- chainName,
- type = "address-on-page",
- setGlobalStatus,
-}: {
- chainName: ChainName;
- type: string;
- setGlobalStatus?: (status: WalletStatus) => void;
-}) => {
- const { chain, status, address, openView } = useChain(chainName);
- useEffect(() => {
- if (status == "Connecting") {
- setGlobalStatus?.(WalletStatus.Connecting);
- } else if (status == "Connected") {
- setGlobalStatus?.(WalletStatus.Connected);
- } else {
- setGlobalStatus?.(WalletStatus.Disconnected);
- }
- }, [status]);
-
- switch (type) {
- case "address-in-modal":
- return (
-
-
- {chain.pretty_name}
-
-
-
- );
- case "address-on-page":
- return (
-
-
- {chain.pretty_name}
-
-
-
-
-
- );
- default:
- throw new Error("No such chain card type: " + type);
- }
-};
+import { Box, Button, Heading, HStack } from "@chakra-ui/react";
+import { ChainName, WalletStatus } from "@cosmos-kit/core";
+import { useChain } from "@cosmos-kit/react";
+import { useEffect } from "react";
+import { ConnectedShowAddress } from "./react";
+
+export const ChainWalletCard = ({
+ chainName,
+ type = "address-on-page",
+ setGlobalStatus,
+}: {
+ chainName: ChainName;
+ type: string;
+ setGlobalStatus?: (status: WalletStatus) => void;
+}) => {
+ const { chain, status, address, openView } = useChain(chainName);
+ useEffect(() => {
+ if (status == "Connecting") {
+ setGlobalStatus?.(WalletStatus.Connecting);
+ } else if (status == "Connected") {
+ setGlobalStatus?.(WalletStatus.Connected);
+ } else {
+ setGlobalStatus?.(WalletStatus.Disconnected);
+ }
+ }, [status]);
+
+ switch (type) {
+ case "address-in-modal":
+ return (
+
+
+ {chain.pretty_name}
+
+
+
+ );
+ case "address-on-page":
+ return (
+
+
+ {chain.pretty_name}
+
+
+
+
+
+ );
+ default:
+ throw new Error("No such chain card type: " + type);
+ }
+};
\ No newline at end of file
diff --git a/components/apps/dashboard.tsx b/components/apps/dashboard/dashboard.tsx
similarity index 91%
rename from components/apps/dashboard.tsx
rename to components/apps/dashboard/dashboard.tsx
index dc3af9a..5bf0377 100644
--- a/components/apps/dashboard.tsx
+++ b/components/apps/dashboard/dashboard.tsx
@@ -18,8 +18,8 @@ import {
import { useChain } from '@cosmos-kit/react';
import { IoWalletOutline } from 'react-icons/io5';
- import {ChainWalletCard} from '../chain-wallet-card';
-import { FaUserCircle } from "react-icons/fa";
+import {ChainWalletCard} from './chain-wallet-card';
+import { FaUserCircle } from "react-icons/fa";
const chainNames_1 = ['cosmoshub', 'osmosis'];
const chainNames_2 = ['stargaze', 'akash'];
@@ -61,7 +61,7 @@ export default function DashboardContent(){
+ {
+ await disconnect();
+ setGlobalStatus(WalletStatus.Disconnected);
+ }}
+ >
+ Disconnect
+
+
+ );
+ }
+
+ return (
+ connect()}
+ >
+ Connect Wallet
+
+ );
+ };
+
+ return (
+
+
+ Delegate Terps
+
+
+
+
+
+ {getGlobalButton()}
+
+
+
+
+
+
+
+
+
+
+ Built
+ With
+
+ Cosmology
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/components/apps/staking/react/address-card.tsx b/components/apps/staking/react/address-card.tsx
new file mode 100644
index 0000000..8e601bd
--- /dev/null
+++ b/components/apps/staking/react/address-card.tsx
@@ -0,0 +1,199 @@
+import {
+ Box,
+ Button,
+ Icon,
+ Image,
+ Text,
+ useClipboard,
+ useColorMode,
+ } from "@chakra-ui/react";
+ import { WalletStatus } from "@cosmos-kit/core";
+ import React, { ReactNode, useEffect, useState } from "react";
+ import { FaCheckCircle } from "react-icons/fa";
+ import { FiCopy } from "react-icons/fi";
+
+ import { CopyAddressType } from "../../../types";
+ import { handleChangeColorModeValue } from "./handleChangeColor";
+
+ const SIZES = {
+ lg: {
+ height: 12,
+ walletImageSize: 7,
+ icon: 5,
+ fontSize: "md",
+ },
+ md: {
+ height: 10,
+ walletImageSize: 6,
+ icon: 4,
+ fontSize: "sm",
+ },
+ sm: {
+ height: 7,
+ walletImageSize: 5,
+ icon: 3.5,
+ fontSize: "sm",
+ },
+ };
+
+ export function stringTruncateFromCenter(str: string, maxLength: number) {
+ const midChar = "…"; // character to insert into the center of the result
+
+ if (str.length <= maxLength) return str;
+
+ // length of beginning part
+ const left = Math.ceil(maxLength / 2);
+
+ // start index of ending part
+ const right = str.length - Math.floor(maxLength / 2) + 1;
+
+ return str.substring(0, left) + midChar + str.substring(right);
+ }
+
+ export const ConnectedShowAddress = ({
+ address,
+ walletIcon,
+ isLoading,
+ isRound,
+ size = "md",
+ maxDisplayLength,
+ }: CopyAddressType) => {
+ const { hasCopied, onCopy } = useClipboard(address ? address : "");
+ const [displayAddress, setDisplayAddress] = useState("");
+ const { colorMode } = useColorMode();
+ const defaultMaxLength = {
+ lg: 14,
+ md: 16,
+ sm: 18,
+ };
+
+ useEffect(() => {
+ if (!address) setDisplayAddress("address not identified yet");
+ if (address && maxDisplayLength)
+ setDisplayAddress(stringTruncateFromCenter(address, maxDisplayLength));
+ if (address && !maxDisplayLength)
+ setDisplayAddress(
+ stringTruncateFromCenter(
+ address,
+ defaultMaxLength[size as keyof typeof defaultMaxLength]
+ )
+ );
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [address]);
+
+ return (
+
+ {address && walletIcon && (
+
+
+
+ )}
+
+ {displayAddress}
+
+ {address && (
+
+ )}
+
+ );
+ };
+
+ export const CopyAddressBtn = ({
+ walletStatus,
+ connected,
+ }: {
+ walletStatus: WalletStatus;
+ connected: ReactNode;
+ }) => {
+ switch (walletStatus) {
+ case "Connected":
+ return <>{connected}>;
+ default:
+ return (
+
+
+
+ );
+ }
+ };
\ No newline at end of file
diff --git a/components/apps/staking/react/all-validators.tsx b/components/apps/staking/react/all-validators.tsx
new file mode 100644
index 0000000..00cc982
--- /dev/null
+++ b/components/apps/staking/react/all-validators.tsx
@@ -0,0 +1,369 @@
+import { Token } from './stats';
+import { IoArrowForward } from 'react-icons/io5';
+import {
+ Heading,
+ Table,
+ Thead,
+ Tbody,
+ Tr,
+ Th,
+ Td,
+ TableContainer,
+ Button,
+ Box,
+ Icon,
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+ ModalFooter,
+ ModalBody,
+ ModalCloseButton,
+ useDisclosure,
+ Stack,
+ Text,
+ Image,
+ useColorMode,
+ Center,
+} from '@chakra-ui/react';
+import {
+ DelegateWarning,
+ StatBox,
+ ValidatorDesc,
+ ValidatorInfo,
+} from './delegate-modal';
+import { useState } from 'react';
+import { exponentiate, getExponent } from './staking';
+import { useChain } from '@cosmos-kit/react';
+import { cosmos } from 'interchain';
+import { getStakeCoin,getGasCoin } from '../../../../config';
+import { StdFee } from '@cosmjs/amino';
+import type {
+ Validator,
+ DelegationResponse as Delegation,
+} from 'interchain/types/codegen/cosmos/staking/v1beta1/staking';
+import { TransactionResult } from '../../../../types';
+import { ChainName } from '@cosmos-kit/core';
+import BigNumber from 'bignumber.js';
+import {
+ useFeeEstimation,
+ useInputBox,
+ useTransactionToast,
+} from '../hooks';
+
+const { delegate } = cosmos.staking.v1beta1.MessageComposer.fromPartial;
+
+export const Thumbnail = ({
+ identity,
+ name,
+ thumbnailUrl,
+}: {
+ identity: string | undefined;
+ name: string | undefined;
+ thumbnailUrl: string;
+}) => {
+ return (
+ <>
+ {identity && thumbnailUrl ? (
+
+ ) : (
+
+ {name && name.trim().slice(0, 1).toUpperCase()}
+
+ )}
+ >
+ );
+};
+
+export type MaxAmountAndFee = {
+ maxAmount: string;
+ fee: StdFee;
+};
+
+const AllValidators = ({
+ validators,
+ balance,
+ delegations,
+ updateData,
+ unbondingDays,
+ chainName,
+ thumbnails,
+}: {
+ validators: Validator[];
+ balance: number;
+ delegations: Delegation[];
+ updateData: () => void;
+ unbondingDays: number;
+ chainName: ChainName;
+ thumbnails: {
+ [key: string]: string;
+ };
+}) => {
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ const { getSigningStargateClient, address } = useChain(chainName);
+ const { renderInputBox, amount, setAmount } = useInputBox(balance);
+ const [currentValidator, setCurrentValidator] = useState();
+ const [isDelegating, setIsDelegating] = useState(false);
+ const [isSimulating, setIsSimulating] = useState(false);
+ const [maxAmountAndFee, setMaxAmountAndFee] = useState();
+
+ const stakecoin = getStakeCoin(chainName);
+ const gascoin = getGasCoin(chainName);
+ const exp = getExponent(chainName);
+
+ const { colorMode } = useColorMode();
+ const { showToast } = useTransactionToast();
+ const { estimateFee } = useFeeEstimation(chainName);
+
+ const getDelegation = (validatorAddr: string, delegations: Delegation[]) => {
+ const delegation = delegations.filter(
+ (d) => d?.delegation?.validatorAddress === validatorAddr
+ );
+
+ if (delegation.length === 1) {
+ return exponentiate(delegation[0].balance!.amount, -exp);
+ }
+
+ return 0;
+ };
+
+ const onModalClose = () => {
+ setAmount('');
+ setIsDelegating(false);
+ onClose();
+ setIsSimulating(false);
+ };
+
+ const onDelegateClick = async () => {
+ setIsDelegating(true);
+
+ const stargateClient = await getSigningStargateClient();
+
+ if (!stargateClient || !address || !currentValidator?.operatorAddress) {
+ console.error('stargateClient undefined or address undefined.');
+ return;
+ }
+
+ const delegationAmount = new BigNumber(amount).shiftedBy(exp).toString();
+
+ const msg = delegate({
+ delegatorAddress: address,
+ validatorAddress: currentValidator.operatorAddress,
+ amount: {
+ amount: delegationAmount,
+ denom: stakecoin.base,
+ },
+ });
+
+ const isMaxAmountAndFeeExists =
+ maxAmountAndFee &&
+ new BigNumber(amount).isEqualTo(maxAmountAndFee.maxAmount);
+
+ try {
+ const fee = isMaxAmountAndFeeExists
+ ? maxAmountAndFee.fee
+ : await estimateFee(address, [msg]);
+ const res = await stargateClient.signAndBroadcast(address, [msg], fee);
+ showToast(res.code);
+ updateData();
+ setTimeout(() => {
+ onModalClose();
+ }, 1000);
+ } catch (error) {
+ console.log(error);
+ showToast(TransactionResult.Failed, error);
+ } finally {
+ stargateClient.disconnect();
+ setIsDelegating(false);
+ setMaxAmountAndFee(undefined);
+ }
+ };
+
+ const handleMaxClick = async () => {
+ if (!address || !currentValidator) return;
+
+ if (Number(balance) === 0) {
+ setAmount(0);
+ return;
+ }
+
+ setIsSimulating(true);
+
+ const delegationAmount = new BigNumber(balance).shiftedBy(exp).toString();
+ const msg = delegate({
+ delegatorAddress: address,
+ validatorAddress: currentValidator.operatorAddress,
+ amount: {
+ amount: delegationAmount,
+ denom: stakecoin.base,
+ },
+ });
+
+ try {
+ const fee = await estimateFee(address, [msg]);
+ const feeAmount = new BigNumber(fee.amount[0].amount).shiftedBy(-exp);
+ const balanceAfterFee = new BigNumber(balance)
+ .minus(feeAmount)
+ .toString();
+ setMaxAmountAndFee({ fee, maxAmount: balanceAfterFee });
+ setAmount(balanceAfterFee);
+ } catch (error) {
+ console.log(error);
+ } finally {
+ setIsSimulating(false);
+ }
+ };
+
+ const isAmountZero = new BigNumber(amount || 0).isEqualTo(0);
+
+ return (
+ <>
+
+ All Validators
+
+
+
+
+
+ Validator
+
+
+
+
+
+
+
+
+
+
+ {renderInputBox(
+ 'Amount to Delegate',
+ stakecoin.symbol,
+ handleMaxClick,
+ isSimulating
+ )}
+
+
+
+
+ Delegate
+
+
+
+
+
+
+
+
+
+ Validator |
+ Voting Power |
+ Commission |
+ APR |
+
+
+
+
+ {validators.map((validator: Validator, index: number) => (
+
+
+
+ {index + 1}
+
+ {validator?.description?.moniker}
+
+ |
+
+ {Math.floor(
+ exponentiate(validator.tokens, -exp)
+ ).toLocaleString()}
+
+
+ |
+
+ {validator.commission?.commissionRates?.rate &&
+ exponentiate(
+ validator.commission.commissionRates.rate,
+ -16
+ ).toFixed(0)}
+ %
+ |
+
+
+ {/* {validator.apr} */}
+ Live Data Coming Soon
+ {
+ onOpen();
+ setCurrentValidator(validator);
+ }}
+ color={
+ colorMode === 'light' ? 'purple.600' : 'purple.200'
+ }
+ >
+ Manage
+
+
+
+ |
+
+ ))}
+
+
+
+ >
+ );
+};
+
+export default AllValidators;
\ No newline at end of file
diff --git a/components/apps/staking/react/chain-dropdown.tsx b/components/apps/staking/react/chain-dropdown.tsx
new file mode 100644
index 0000000..3d0f60e
--- /dev/null
+++ b/components/apps/staking/react/chain-dropdown.tsx
@@ -0,0 +1,277 @@
+/* eslint-disable react-hooks/rules-of-hooks */
+import React from 'react';
+import {
+ Box,
+ Text,
+ Stack,
+ useColorModeValue,
+ Image,
+ Icon,
+ useBreakpointValue,
+ SystemStyleObject,
+ SkeletonCircle,
+ Skeleton
+} from '@chakra-ui/react';
+import { Searcher } from 'fast-fuzzy';
+import { FiChevronDown } from 'react-icons/fi';
+import {
+ AsyncSelect,
+ OptionProps,
+ chakraComponents,
+ GroupBase,
+ DropdownIndicatorProps,
+ PlaceholderProps
+} from 'chakra-react-select';
+import {
+ ChainOption,
+ ChangeChainDropdownType,
+ ChangeChainMenuType
+} from '../../../types';
+
+const SkeletonOptions = () => {
+ return (
+
+
+
+
+ );
+};
+
+const SelectOptions = ({ data, value, onChange }: ChangeChainMenuType) => {
+ const menuHeight = useBreakpointValue({ base: 60, md: 56 });
+ const customStyles = {
+ control: (provided: SystemStyleObject) => ({
+ ...provided,
+ height: 12
+ }),
+ menu: (provided: SystemStyleObject) => ({
+ ...provided,
+ h: menuHeight,
+ mt: 4,
+ mb: 0,
+ bg: useColorModeValue('white', 'gray.900'),
+ boxShadow: useColorModeValue('0 1px 5px #e3e3e3', '0 0px 4px #4b4b4b'),
+ borderRadius: '0.3rem'
+ }),
+ menuList: (provided: SystemStyleObject) => ({
+ ...provided,
+ h: menuHeight,
+ bg: 'transparent',
+ border: 'none',
+ borderRadius: 'none',
+ p: 2,
+ // For Firefox
+ scrollbarWidth: 'auto',
+ scrollbarColor: useColorModeValue(
+ 'rgba(0,0,0,0.3) rgba(0,0,0,0.2)',
+ 'rgba(255,255,255,0.2) rgba(255,255,255,0.1)'
+ ),
+ // For Chrome and other browsers except Firefox
+ '&::-webkit-scrollbar': {
+ width: '14px',
+ background: useColorModeValue(
+ 'rgba(220,220,220,0.1)',
+ 'rgba(60,60,60,0.1)'
+ ),
+ borderRadius: '3px'
+ },
+ '&::-webkit-scrollbar-thumb': {
+ background: useColorModeValue(
+ 'rgba(0,0,0,0.1)',
+ 'rgba(255,255,255,0.1)'
+ ),
+ borderRadius: '10px',
+ border: '3px solid transparent',
+ backgroundClip: 'content-box'
+ }
+ }),
+ clearIndicator: (provided: SystemStyleObject) => ({
+ ...provided,
+ borderRadius: 'full',
+ color: useColorModeValue('blackAlpha.600', 'whiteAlpha.600')
+ }),
+ dropdownIndicator: (provided: SystemStyleObject) => ({
+ ...provided,
+ bg: 'transparent',
+ pl: 1.5
+ }),
+ option: (
+ provided: SystemStyleObject,
+ state: { isSelected: boolean; isFocused: boolean }
+ ) => {
+ return {
+ ...provided,
+ borderRadius: 'lg',
+ h: 14,
+ color: 'inherit',
+ bg: useColorModeValue(
+ state.isSelected
+ ? state.isFocused
+ ? 'primary.200'
+ : 'primary.100'
+ : state.isFocused
+ ? 'blackAlpha.200'
+ : 'transparent',
+ state.isSelected
+ ? state.isFocused
+ ? 'primary.600'
+ : 'primary.500'
+ : state.isFocused
+ ? 'whiteAlpha.200'
+ : 'transparent'
+ ),
+ _notFirst: {
+ mt: 2
+ },
+ _active: {
+ bg: 'primary.50'
+ },
+ _disabled: { bg: 'transparent', _hover: { bg: 'transparent' } }
+ };
+ }
+ };
+ const IndicatorSeparator = () => {
+ return null;
+ };
+ const DropdownIndicator = ({
+ ...props
+ }: DropdownIndicatorProps>) => {
+ return (
+
+
+
+ );
+ };
+ const Placeholder = (props: PlaceholderProps) => {
+ if (props.hasValue) {
+ return (
+
+
+
+
+
+
+ {props.getValue()[0].label}
+
+
+
+ );
+ }
+ return ;
+ };
+ const CustomOption = ({
+ children,
+ ...props
+ }: OptionProps>) => {
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+ };
+
+ return (
+ option.isDisabled || false}
+ blurInputOnSelect={true}
+ controlShouldRenderValue={false}
+ loadingMessage={() => }
+ value={value}
+ defaultOptions={data}
+ loadOptions={(inputValue, callback) => {
+ const searcher = new Searcher(data, {
+ keySelector: (obj) => obj.label
+ });
+ callback(searcher.search(inputValue));
+ }}
+ onChange={onChange}
+ components={{
+ DropdownIndicator,
+ IndicatorSeparator,
+ Placeholder,
+ Option: CustomOption
+ }}
+ />
+ );
+};
+
+export const ChangeChainDropdown = ({
+ data,
+ selectedItem,
+ onChange
+}: ChangeChainDropdownType) => {
+ return (
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/components/apps/staking/react/choose-chain.tsx b/components/apps/staking/react/choose-chain.tsx
new file mode 100644
index 0000000..934a97a
--- /dev/null
+++ b/components/apps/staking/react/choose-chain.tsx
@@ -0,0 +1,33 @@
+import React,{ useState, useEffect } from 'react';
+import { ChangeChainDropdown } from './chain-dropdown';
+import {
+ ChooseChainInfo,
+ ChainOption,
+ handleSelectChainDropdown
+} from '../../../types';
+
+export function ChooseChain({
+ chainName,
+ chainInfos,
+ onChange
+}: {
+ chainName?: string;
+ chainInfos: ChooseChainInfo[];
+ onChange: handleSelectChainDropdown;
+}) {
+ const [selectedItem, setSelectedItem] = useState();
+ useEffect(() => {
+ if (chainName && chainInfos.length > 0)
+ setSelectedItem(
+ chainInfos.filter((options) => options.chainName === chainName)[0]
+ );
+ if (!chainName) setSelectedItem(undefined);
+ }, [chainInfos, chainName]);
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/components/apps/staking/react/delegate-modal.tsx b/components/apps/staking/react/delegate-modal.tsx
new file mode 100644
index 0000000..fb5d0a6
--- /dev/null
+++ b/components/apps/staking/react/delegate-modal.tsx
@@ -0,0 +1,162 @@
+import React,{ ReactElement } from 'react';
+import { Token } from './stats';
+import {
+ Flex,
+ Heading,
+ Stack,
+ Text,
+ Image,
+ AlertDescription,
+ Alert,
+ AlertIcon,
+ AlertTitle,
+ Box,
+ Stat,
+ StatLabel,
+ StatNumber,
+ ListItem,
+ UnorderedList,
+ useColorModeValue,
+ Center,
+} from '@chakra-ui/react';
+
+
+export const ValidatorInfo = ({
+ imgUrl,
+ name,
+ commission,
+ apr,
+}: {
+ imgUrl: string;
+ name: string;
+ commission: number | string;
+ apr: number;
+}) => (
+
+ {imgUrl ? (
+
+ ) : (
+
+ {name.slice(0, 1).toUpperCase()}
+
+ )}
+
+
+ {name}
+
+
+ Commission {commission}% | APR {apr}%
+
+
+
+);
+
+export const ValidatorDesc = ({ description }: { description: string }) => (
+ {description}
+);
+
+export const DelegateWarning = ({
+ unbondingDays,
+}: {
+ unbondingDays: number;
+}) => {
+ if (!unbondingDays) return <>>;
+
+ return (
+
+
+
+
+ Staking will lock your funds for {unbondingDays} days
+
+
+
+ You will need to undelegate in order for your staked assets to be liquid
+ again. This process will take {unbondingDays} days to complete.
+
+
+ );
+};
+
+export const UndelegateWarning = ({
+ unbondingDays,
+}: {
+ unbondingDays: number;
+}) => {
+ if (!unbondingDays) return <>>;
+
+ return (
+
+
+
+
+ Once the unbonding period begins you will:
+
+
+
+
+ not receive staking rewards
+ not be able to cancel the unbonding
+
+ need to wait {unbondingDays} days for the amount to be liquid
+
+
+
+
+ );
+};
+
+export const StatBox = ({
+ label,
+ number,
+ input,
+ token,
+}: {
+ label: string;
+ number?: number;
+ input?: ReactElement;
+ token: string;
+}) => {
+ return (
+
+
+ {label}
+ {input ? (
+ input
+ ) : (
+
+ {number}
+
+ )}
+
+
+ );
+};
\ No newline at end of file
diff --git a/components/apps/staking/react/handleChangeColor.tsx b/components/apps/staking/react/handleChangeColor.tsx
new file mode 100644
index 0000000..7c7ff46
--- /dev/null
+++ b/components/apps/staking/react/handleChangeColor.tsx
@@ -0,0 +1,9 @@
+// use for let color mode value fit Rules of Hooks
+export function handleChangeColorModeValue(
+ colorMode: string,
+ light: any,
+ dark: any
+ ) {
+ if (colorMode === "light") return light;
+ if (colorMode === "dark") return dark;
+ }
\ No newline at end of file
diff --git a/components/apps/staking/react/index.ts b/components/apps/staking/react/index.ts
new file mode 100644
index 0000000..66d228b
--- /dev/null
+++ b/components/apps/staking/react/index.ts
@@ -0,0 +1,7 @@
+export * from './wallet-connect';
+export * from './warn-block';
+export * from './user-card';
+export * from './address-card';
+export * from './staking';
+export * from './choose-chain';
+export * from './chain-dropdown';
diff --git a/components/apps/staking/react/my-validators.tsx b/components/apps/staking/react/my-validators.tsx
new file mode 100644
index 0000000..eea71ea
--- /dev/null
+++ b/components/apps/staking/react/my-validators.tsx
@@ -0,0 +1,735 @@
+import {
+ Heading,
+ Table,
+ Thead,
+ Tbody,
+ Tr,
+ Th,
+ Td,
+ TableContainer,
+ Button,
+ Box,
+ Icon,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay,
+ Stack,
+ useDisclosure,
+ Text,
+ Image,
+ useColorMode,
+ } from '@chakra-ui/react';
+ import { Token } from './stats';
+ import { IoArrowForward } from 'react-icons/io5';
+ import {
+ ValidatorInfo,
+ ValidatorDesc,
+ DelegateWarning,
+ StatBox,
+ UndelegateWarning,
+ } from './delegate-modal';
+ import { exponentiate, getExponent } from './staking';
+ import { decodeCosmosSdkDecFromProto } from '@cosmjs/stargate';
+ import React,{ useState } from 'react';
+ import { cosmos } from 'interchain';
+ import { useChain } from '@cosmos-kit/react';
+ import { getGasCoin, getStakeCoin } from '../../../../config';
+ import { MyValidator, TransactionResult } from '../../../../types';
+ import type {
+ Validator,
+ DelegationResponse as Delegation,
+ } from 'interchain/types/codegen/cosmos/staking/v1beta1/staking';
+ import type { DelegationDelegatorReward as Reward } from 'interchain/types/codegen/cosmos/distribution/v1beta1/distribution';
+ import { ChainName } from '@cosmos-kit/core';
+ import { MaxAmountAndFee, Thumbnail } from './all-validators';
+ import {
+ useFeeEstimation,
+ useInputBox,
+ useTransactionToast,
+ } from '../hooks';
+ import BigNumber from 'bignumber.js';
+
+ const { delegate } = cosmos.staking.v1beta1.MessageComposer.fromPartial;
+ const { undelegate } = cosmos.staking.v1beta1.MessageComposer.fromPartial;
+ const { beginRedelegate } = cosmos.staking.v1beta1.MessageComposer.fromPartial;
+
+ const isAmountZero = (amount: string | number | undefined) =>
+ new BigNumber(amount || 0).isEqualTo(0);
+
+ const MyValidators = ({
+ validators,
+ allValidator,
+ delegations,
+ rewards,
+ balance,
+ updateData,
+ unbondingDays,
+ chainName,
+ thumbnails,
+ }: {
+ validators: Validator[];
+ allValidator: Validator[];
+ delegations: Delegation[];
+ rewards: Reward[];
+ balance: number;
+ updateData: () => void;
+ unbondingDays: number;
+ chainName: ChainName;
+ thumbnails: {
+ [key: string]: string;
+ };
+ }) => {
+ const { getSigningStargateClient, address } = useChain(chainName);
+
+ const [isDelegating, setIsDelegating] = useState(false);
+ const [isUndelegating, setIsUndelegating] = useState(false);
+ const [isRedelegating, setIsRedelegating] = useState(false);
+ const [currentValidator, setCurrentValidator] = useState();
+ const [selectedValidator, setSelectedValidator] = useState();
+ const [isSimulating, setIsSimulating] = useState(false);
+ const [maxDelegateAmountAndFee, setMaxDelegateAmountAndFee] =
+ useState();
+
+ const gascoin = getGasCoin(chainName);
+ const stakecoin = getStakeCoin(chainName);
+
+ const exp = getExponent(chainName);
+
+ const { colorMode } = useColorMode();
+ const { showToast } = useTransactionToast();
+ const { estimateFee } = useFeeEstimation(chainName);
+
+ const {
+ renderInputBox: renderDelegateInputBox,
+ amount: delegateAmount,
+ setAmount: setDelegateAmount,
+ } = useInputBox(balance);
+
+ const {
+ renderInputBox: renderUndelegateInputBox,
+ amount: undelegateAmount,
+ setAmount: setUndelegateAmount,
+ setMax: setMaxUndelegateAmount,
+ } = useInputBox();
+
+ const {
+ renderInputBox: renderRedelegateInputBox,
+ amount: redelegateAmount,
+ setAmount: setRedelegateAmount,
+ setMax: setMaxRedelegateAmount,
+ } = useInputBox();
+
+ const {
+ isOpen: isValidatorModalOpen,
+ onOpen: onValidatorModalOpen,
+ onClose: onValidatorModalClose,
+ } = useDisclosure();
+
+ const {
+ isOpen: isDelegateModalOpen,
+ onOpen: onDelegateModalOpen,
+ onClose: onDelegateModalClose,
+ } = useDisclosure();
+
+ const {
+ isOpen: isUndelegateModalOpen,
+ onOpen: onUndelegateModalOpen,
+ onClose: onUndelegateModalClose,
+ } = useDisclosure();
+
+ const {
+ isOpen: isSelectValidatorModalOpen,
+ onOpen: onSelectValidatorModalOpen,
+ onClose: onSelectValidatorModalClose,
+ } = useDisclosure();
+
+ const {
+ isOpen: isRedelegateModalOpen,
+ onOpen: onRedelegateModalOpen,
+ onClose: onRedelegateModalClose,
+ } = useDisclosure();
+
+ const myValidators = validators.map((validator: Validator) => {
+ const delegation = delegations.filter(
+ (d) => d?.delegation?.validatorAddress === validator?.operatorAddress
+ )[0];
+
+ const delegatorReward = rewards.filter(
+ (r) => r.validatorAddress === validator?.operatorAddress
+ )[0];
+
+ const reward = delegatorReward.reward.find(
+ (item) => item.denom === gascoin.base
+ );
+
+ const rewardAmount =
+ delegatorReward.reward.length > 0
+ ? decodeCosmosSdkDecFromProto(reward ? reward.amount : '0').toString()
+ : 0;
+
+ return {
+ details: validator?.description?.details,
+ name: validator?.description?.moniker,
+ identity: validator?.description?.identity,
+ address: validator.operatorAddress,
+ staked: exponentiate(delegation.balance!.amount, -exp),
+ reward: Number(exponentiate(rewardAmount, -exp).toFixed(6)),
+ commission: validator?.commission?.commissionRates?.rate,
+ };
+ });
+
+ const closeDelegateModal = () => {
+ setDelegateAmount('');
+ setIsDelegating(false);
+ onDelegateModalClose();
+ };
+
+ const closeUndelegateModal = () => {
+ setUndelegateAmount('');
+ setIsUndelegating(false);
+ onUndelegateModalClose();
+ };
+
+ const closeRedelegateModal = () => {
+ setRedelegateAmount('');
+ setIsRedelegating(false);
+ onRedelegateModalClose();
+ };
+
+ const onDelegateClick = async () => {
+ setIsDelegating(true);
+
+ const stargateClient = await getSigningStargateClient();
+
+ if (!stargateClient || !address || !currentValidator?.address) {
+ console.error('stargateClient undefined or address undefined.');
+ return;
+ }
+
+ const amountToDelegate = (Number(delegateAmount) * 10 ** exp).toString();
+
+ const msg = delegate({
+ delegatorAddress: address,
+ validatorAddress: currentValidator.address,
+ amount: {
+ amount: amountToDelegate,
+ denom: stakecoin.base,
+ },
+ });
+
+ const isMaxAmountAndFeeExists =
+ maxDelegateAmountAndFee &&
+ new BigNumber(delegateAmount).isEqualTo(
+ maxDelegateAmountAndFee.maxAmount
+ );
+
+ try {
+ const fee = isMaxAmountAndFeeExists
+ ? maxDelegateAmountAndFee.fee
+ : await estimateFee(address, [msg]);
+ const res = await stargateClient.signAndBroadcast(address, [msg], fee);
+ showToast(res.code);
+ onValidatorModalClose();
+ updateData();
+ setTimeout(() => {
+ closeDelegateModal();
+ }, 1000);
+ } catch (error) {
+ console.log(error);
+ showToast(TransactionResult.Failed, error);
+ } finally {
+ stargateClient.disconnect();
+ setIsDelegating(false);
+ }
+ };
+
+ const handleMaxClick = async () => {
+ if (!address || !currentValidator) return;
+
+ if (Number(balance) === 0) {
+ setDelegateAmount(0);
+ return;
+ }
+
+ setIsSimulating(true);
+
+ const delegationAmount = new BigNumber(balance).shiftedBy(exp).toString();
+ const msg = delegate({
+ delegatorAddress: address,
+ validatorAddress: currentValidator.address,
+ amount: {
+ amount: delegationAmount,
+ denom: stakecoin.base,
+ },
+ });
+
+ try {
+ const fee = await estimateFee(address, [msg]);
+ const feeAmount = new BigNumber(fee.amount[0].amount).shiftedBy(-exp);
+ const balanceAfterFee = new BigNumber(balance)
+ .minus(feeAmount)
+ .toString();
+ setMaxDelegateAmountAndFee({ fee, maxAmount: balanceAfterFee });
+ setDelegateAmount(balanceAfterFee);
+ } catch (error) {
+ console.log(error);
+ } finally {
+ setIsSimulating(false);
+ }
+ };
+
+ const onUndelegateClick = async () => {
+ setIsUndelegating(true);
+
+ const stargateClient = await getSigningStargateClient();
+
+ if (!stargateClient || !address || !currentValidator?.address) {
+ console.error('stargateClient undefined or address undefined.');
+ return;
+ }
+
+ const amountToUndelegate = (
+ Number(undelegateAmount) *
+ 10 ** exp
+ ).toString();
+
+ const msg = undelegate({
+ delegatorAddress: address,
+ validatorAddress: currentValidator.address,
+ amount: {
+ amount: amountToUndelegate,
+ denom: stakecoin.base,
+ },
+ });
+
+ try {
+ const fee = await estimateFee(address, [msg]);
+ const res = await stargateClient.signAndBroadcast(address, [msg], fee);
+ showToast(res.code);
+ onValidatorModalClose();
+ updateData();
+ setTimeout(() => {
+ closeUndelegateModal();
+ }, 1000);
+ } catch (error) {
+ console.log(error);
+ showToast(TransactionResult.Failed, error);
+ } finally {
+ stargateClient.disconnect();
+ setIsUndelegating(false);
+ }
+ };
+
+ const onRedelegateClick = async () => {
+ setIsRedelegating(true);
+
+ const stargateClient = await getSigningStargateClient();
+
+ if (
+ !stargateClient ||
+ !address ||
+ !currentValidator?.address ||
+ !selectedValidator?.operatorAddress
+ ) {
+ console.error('stargateClient undefined or address undefined.');
+ return;
+ }
+
+ const amountToRedelegate = (
+ Number(redelegateAmount) *
+ 10 ** exp
+ ).toString();
+
+ const msg = beginRedelegate({
+ delegatorAddress: address,
+ validatorSrcAddress: currentValidator.address,
+ validatorDstAddress: selectedValidator.operatorAddress,
+ amount: {
+ denom: stakecoin.base,
+ amount: amountToRedelegate,
+ },
+ });
+
+ try {
+ const fee = await estimateFee(address, [msg]);
+ const res = await stargateClient.signAndBroadcast(address, [msg], fee);
+ showToast(res.code);
+ updateData();
+ setTimeout(() => {
+ closeRedelegateModal();
+ }, 1000);
+ } catch (error) {
+ console.log(error);
+ showToast(TransactionResult.Failed, error);
+ } finally {
+ stargateClient.disconnect();
+ setIsRedelegating(false);
+ }
+ };
+
+ return (
+ <>
+
+ My Validators
+
+
+
+
+
+ Validator
+
+
+
+
+
+
+
+
+
+
+
+ Undelegate
+
+ {
+ onSelectValidatorModalOpen();
+ onValidatorModalClose();
+ }}
+ mr={4}
+ >
+ Redelegate
+
+
+ Delegate
+
+
+
+
+
+
+
+
+ Delegate
+
+
+
+
+
+
+
+
+
+ {renderDelegateInputBox(
+ 'Amount to Delegate',
+ stakecoin.symbol,
+ handleMaxClick,
+ isSimulating
+ )}
+
+
+
+
+ Delegate
+
+
+
+
+
+
+
+
+ Undelegate
+
+
+
+
+
+
+
+ {renderUndelegateInputBox('Amount to Undelegate', stakecoin.symbol)}
+
+
+
+
+
+ Undelegate
+
+
+
+
+
+
+
+
+ Redelegate to
+
+
+
+
+
+
+
+ Validator |
+ Voting Power |
+ Commission |
+ APR |
+
+
+
+ {allValidator.map((validator: Validator, index: number) => (
+ {
+ onRedelegateModalOpen();
+ onSelectValidatorModalClose();
+ setSelectedValidator(validator);
+ }}
+ _hover={{
+ background:
+ colorMode === 'light' ? 'gray.100' : 'gray.800',
+ cursor: 'pointer',
+ }}
+ >
+
+
+ {index + 1}
+
+ {validator?.description?.moniker}
+
+ |
+
+ {Math.floor(
+ exponentiate(validator.tokens, -exp)
+ ).toLocaleString()}
+
+
+ |
+
+ {validator.commission?.commissionRates?.rate &&
+ exponentiate(
+ validator.commission.commissionRates.rate,
+ -16
+ ).toFixed(0)}
+ %
+ |
+
+
+ {/* {validator.apr} */}
+ Live Data Coming Soon
+
+ |
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ From
+
+ {currentValidator?.name}
+
+
+
+
+
+ To
+
+ {selectedValidator?.description?.moniker}
+
+
+ {renderRedelegateInputBox('Amount to Redelegate', stakecoin.symbol)}
+
+
+
+
+ Redelegate
+
+
+
+
+
+
+
+
+
+ Validator |
+ Amount Staked |
+ Claimable Rewards |
+
+
+
+ {myValidators.map((validator, index) => (
+
+
+
+ {index + 1}
+
+ {validator.name}
+
+ |
+
+ {validator.staked}
+
+ |
+
+
+
+ {validator.reward}
+
+
+ {
+ setCurrentValidator(validator);
+ setMaxUndelegateAmount(validator.staked);
+ setMaxRedelegateAmount(validator.staked);
+ onValidatorModalOpen();
+ }}
+ color={
+ colorMode === 'light' ? 'purple.600' : 'purple.200'
+ }
+ >
+ Manage
+
+
+
+ |
+
+ ))}
+
+
+
+ >
+ );
+ };
+
+ export default MyValidators;
\ No newline at end of file
diff --git a/components/apps/staking/react/staking.tsx b/components/apps/staking/react/staking.tsx
new file mode 100644
index 0000000..718318c
--- /dev/null
+++ b/components/apps/staking/react/staking.tsx
@@ -0,0 +1,350 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import React,{ useCallback, useEffect, useState } from 'react';
+import { useChain } from '@cosmos-kit/react';
+import { Box, SkeletonText } from '@chakra-ui/react';
+import { cosmos } from 'interchain';
+import BigNumber from 'bignumber.js';
+import { decodeCosmosSdkDecFromProto } from '@cosmjs/stargate';
+import Long from 'long';
+import type {
+ Validator,
+ DelegationResponse as Delegation,
+} from 'interchain/types/codegen/cosmos/staking/v1beta1/staking';
+import type { DelegationDelegatorReward as Reward } from 'interchain/types/codegen/cosmos/distribution/v1beta1/distribution';
+import Stats from './stats';
+import MyValidators from './my-validators';
+import AllValidators from './all-validators';
+import { getGasCoin, getStakeCoin } from '../../../../config';
+import { ChainName } from '@cosmos-kit/core';
+import { ImageSource } from '../../../../types';
+
+export const exponentiate = (num: number | string, exp: number) => {
+ return new BigNumber(num)
+ .multipliedBy(new BigNumber(10).exponentiatedBy(exp))
+ .toNumber();
+};
+
+export const getExponent = (chainName: string) => {
+ return getGasCoin(chainName).denom_units.find(
+ (unit) => unit.denom === getGasCoin(chainName).display
+ )?.exponent as number;
+};
+
+const splitIntoChunks = (arr: any[], chunkSize: number) => {
+ const res = [];
+ for (let i = 0; i < arr.length; i += chunkSize) {
+ const chunk = arr.slice(i, i + chunkSize);
+ res.push(chunk);
+ }
+ return res;
+};
+
+const convertChainName = (chainName: string) => {
+ if (chainName.endsWith('testnet')) {
+ return chainName.replace('testnet', '-testnet');
+ }
+
+ switch (chainName) {
+ case 'cosmoshub':
+ return 'cosmos';
+ case 'assetmantle':
+ return 'asset-mantle';
+ case 'cryptoorgchain':
+ return 'crypto-org';
+ case 'dig':
+ return 'dig-chain';
+ case 'gravitybridge':
+ return 'gravity-bridge';
+ case 'kichain':
+ return 'ki-chain';
+ case 'oraichain':
+ return 'orai-chain';
+ case 'terra':
+ return 'terra-classic';
+ default:
+ return chainName;
+ }
+};
+
+const isUrlValid = async (url: string) => {
+ const res = await fetch(url, { method: 'HEAD' });
+ const contentType = res?.headers?.get('Content-Type') || '';
+ return contentType.startsWith('image');
+};
+
+const getCosmostationUrl = (chainName: string, validatorAddr: string) => {
+ const cosmostationChainName = convertChainName(chainName);
+ return `https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/${cosmostationChainName}/moniker/${validatorAddr}.png`;
+};
+
+const addImageSource = async (
+ validator: Validator,
+ chainName: string
+): Promise => {
+ const url = getCosmostationUrl(chainName, validator.operatorAddress);
+ const isValid = await isUrlValid(url);
+ return { ...validator, imageSource: isValid ? 'cosmostation' : 'keybase' };
+};
+
+const getKeybaseUrl = (identity: string) => {
+ return `https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`;
+};
+
+const getImgUrls = async (validators: Validator[], chainName: string) => {
+ const validatorsWithImgSource = await Promise.all(
+ validators.map((validator) => addImageSource(validator, chainName))
+ );
+
+ // cosmostation urls
+ const cosmostationUrls = validatorsWithImgSource
+ .filter((validator) => validator.imageSource === 'cosmostation')
+ .map(({ operatorAddress }) => {
+ return {
+ address: operatorAddress,
+ url: getCosmostationUrl(chainName, operatorAddress),
+ };
+ });
+
+ // keybase urls
+ const keybaseIdentities = validatorsWithImgSource
+ .filter((validator) => validator.imageSource === 'keybase')
+ .map((validator) => ({
+ address: validator.operatorAddress,
+ identity: validator.description?.identity || '',
+ }));
+
+ const chunkedIdentities = splitIntoChunks(keybaseIdentities, 20);
+
+ let responses: any[] = [];
+
+ for (const chunk of chunkedIdentities) {
+ const thumbnailRequests = chunk.map(({ address, identity }) => {
+ if (!identity) return { address, url: '' };
+
+ return fetch(getKeybaseUrl(identity))
+ .then((response) => response.json())
+ .then((res) => ({
+ address,
+ url: res.them?.[0]?.pictures?.primary.url || '',
+ }));
+ });
+ responses = [...responses, await Promise.all(thumbnailRequests)];
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ }
+
+ const keybaseUrls = responses.flat();
+
+ const allUrls = [...cosmostationUrls, ...keybaseUrls].reduce(
+ (prev, cur) => ({ ...prev, [cur.address]: cur.url }),
+ {}
+ );
+
+ return allUrls;
+};
+
+interface StakingTokens {
+ balance: number;
+ rewards: Reward[];
+ totalReward: number;
+ staked: number;
+ delegations: Delegation[];
+ myValidators: Validator[];
+ allValidators: Validator[];
+ unbondingDays: number;
+ thumbnails: {
+ [key: string]: string;
+ };
+}
+
+export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
+ const { address, getRpcEndpoint } = useChain(chainName);
+ const [isLoading, setIsLoading] = useState(false);
+ const [data, setData] = useState({
+ balance: 0,
+ rewards: [],
+ totalReward: 0,
+ staked: 0,
+ delegations: [],
+ myValidators: [],
+ allValidators: [],
+ unbondingDays: 0,
+ thumbnails: {},
+ });
+
+ const gascoin = getGasCoin(chainName);
+ const stakecoin = getStakeCoin(chainName);
+ const exp = getExponent(chainName);
+
+ const getData = useCallback(async () => {
+ if (!address) {
+ setData({
+ balance: 0,
+ rewards: [],
+ totalReward: 0,
+ staked: 0,
+ delegations: [],
+ myValidators: [],
+ allValidators: [],
+ unbondingDays: 0,
+ thumbnails: {},
+ });
+ return;
+ }
+
+ setIsLoading(true);
+
+ let rpcEndpoint = await getRpcEndpoint();
+
+ if (!rpcEndpoint) {
+ console.log('no rpc endpoint — using a fallback');
+ rpcEndpoint = `https://rpc.cosmos.directory/${chainName}`;
+ }
+
+ // get RPC client
+ const client = await cosmos.ClientFactory.createRPCQueryClient({
+ rpcEndpoint:
+ typeof rpcEndpoint === 'string' ? rpcEndpoint : rpcEndpoint.url,
+ });
+
+ // AVAILABLE BALANCE
+ const { balance } = await client.cosmos.bank.v1beta1.balance({
+ address,
+ denom: stakecoin.base,
+ });
+
+ const amount = exponentiate(balance!.amount, -exp);
+
+ // MY VALIDATORS
+ const { validators: myValidators } =
+ await client.cosmos.staking.v1beta1.delegatorValidators({
+ delegatorAddr: address,
+ });
+
+ // REWARDS
+ const { rewards, total } =
+ await client.cosmos.distribution.v1beta1.delegationTotalRewards({
+ delegatorAddress: address,
+ });
+
+ const delegatorReward = total.find((item) => item.denom === gascoin.base);
+
+ const reward = decodeCosmosSdkDecFromProto(
+ delegatorReward ? delegatorReward.amount : '0'
+ ).toString();
+
+ const totalReward = Number(exponentiate(reward, -exp).toFixed(6));
+
+ // ALL VALIDATORS
+ const { validators } = await client.cosmos.staking.v1beta1.validators({
+ status: cosmos.staking.v1beta1.bondStatusToJSON(
+ cosmos.staking.v1beta1.BondStatus.BOND_STATUS_BONDED
+ ),
+ pagination: {
+ key: new Uint8Array(),
+ offset: Long.fromNumber(0),
+ limit: Long.fromNumber(200),
+ countTotal: false,
+ reverse: false,
+ },
+ });
+
+ const allValidators = validators.sort((a, b) =>
+ new BigNumber(b.tokens).minus(new BigNumber(a.tokens)).toNumber()
+ );
+
+ // DELEGATIONS
+ const { delegationResponses: delegations } =
+ await client.cosmos.staking.v1beta1.delegatorDelegations({
+ delegatorAddr: address,
+ });
+
+ const stakedAmount = delegations
+ .map((delegation) => exponentiate(delegation.balance!.amount, -exp))
+ .reduce((a, b) => a + b, 0);
+
+ // UNBONDING DAYS
+ const { params } = await client.cosmos.staking.v1beta1.params();
+ const unbondingDays = params?.unbondingTime
+ ? Number((params?.unbondingTime?.seconds.low / 86400).toFixed(0))
+ : 0;
+
+ // THUMBNAILS
+ let thumbnails = {};
+
+ const validatorThumbnails = localStorage.getItem(
+ `${chainName}-validator-thumbnails`
+ );
+
+ if (validatorThumbnails) {
+ thumbnails = JSON.parse(validatorThumbnails);
+ } else {
+ thumbnails = await getImgUrls(validators, chainName);
+ localStorage.setItem(
+ `${chainName}-validator-thumbnails`,
+ JSON.stringify(thumbnails)
+ );
+ }
+
+ setData({
+ rewards,
+ totalReward,
+ balance: amount,
+ staked: stakedAmount,
+ delegations,
+ myValidators,
+ allValidators,
+ unbondingDays,
+ thumbnails,
+ });
+ setIsLoading(false);
+ }, [address]);
+
+ useEffect(() => {
+ getData();
+ }, [getData]);
+
+ return (
+
+
+
+ {data.myValidators.length > 0 && (
+
+ )}
+ {data.allValidators.length > 0 && (
+
+ )}
+
+
+ );
+};
\ No newline at end of file
diff --git a/components/apps/staking/react/stats.tsx b/components/apps/staking/react/stats.tsx
new file mode 100644
index 0000000..b2c2dd4
--- /dev/null
+++ b/components/apps/staking/react/stats.tsx
@@ -0,0 +1,170 @@
+import {
+ Stat,
+ StatLabel,
+ StatNumber,
+ StatGroup,
+ Button,
+ useColorModeValue,
+ Text,
+ } from '@chakra-ui/react';
+ import { useChain } from '@cosmos-kit/react';
+ import { useState } from 'react';
+ import { cosmos } from 'interchain';
+ import { getStakeCoin, getGasCoin } from '../../../../config';
+ import type { DelegationDelegatorReward as Reward } from 'interchain/types/codegen/cosmos/distribution/v1beta1/distribution';
+ import { TransactionResult } from '../../../../types';
+ import { ChainName } from '@cosmos-kit/core';
+ import { useFeeEstimation, useTransactionToast } from '../hooks';
+import React from 'react';
+
+ export const Token = ({ token, color }: { token: string; color?: string }) => (
+
+ {token}
+
+ );
+
+ const Stats = ({
+ balance,
+ rewards,
+ staked,
+ totalReward,
+ updateData,
+ chainName,
+ }: {
+ balance: number;
+ rewards: Reward[];
+ staked: number;
+ totalReward: number;
+ updateData: () => void;
+ chainName: ChainName;
+ }) => {
+ const [isClaiming, setIsClaiming] = useState(false);
+ const { getSigningStargateClient, address } = useChain(chainName);
+ const { showToast } = useTransactionToast();
+ const { estimateFee } = useFeeEstimation(chainName);
+
+ const totalAmount = balance + staked + totalReward;
+ const stakecoin = getStakeCoin(chainName);
+ const gascoin = getGasCoin(chainName);
+
+ const onClaimClick = async () => {
+ setIsClaiming(true);
+
+ const stargateClient = await getSigningStargateClient();
+
+ if (!stargateClient || !address) {
+ console.error('stargateClient undefined or address undefined.');
+ return;
+ }
+
+ const { withdrawDelegatorReward } =
+ cosmos.distribution.v1beta1.MessageComposer.fromPartial;
+
+ const msgs = rewards.map(({ validatorAddress }) =>
+ withdrawDelegatorReward({
+ delegatorAddress: address,
+ validatorAddress,
+ })
+ );
+
+ try {
+ const fee = await estimateFee(address, msgs);
+ const res = await stargateClient.signAndBroadcast(address, msgs, fee);
+ showToast(res.code);
+ updateData();
+ } catch (error) {
+ console.log(error);
+ showToast(TransactionResult.Failed, error);
+ } finally {
+ stargateClient.disconnect();
+ setIsClaiming(false);
+ }
+ };
+
+ return (
+
+
+
+ Total {stakecoin.symbol} Amount
+
+
+ {totalAmount === 0 ? totalAmount : totalAmount.toFixed(6)}
+
+
+
+
+
+
+ Available Balance
+
+
+ {balance}
+
+
+
+
+
+ Staked Amount
+
+
+ {staked === 0 ? staked : staked.toFixed(6)}
+
+
+
+
+
+
+ Claimable Rewards
+
+
+ {totalReward}
+
+
+
+ Claim
+
+
+
+ );
+ };
+
+ export default Stats;
\ No newline at end of file
diff --git a/components/apps/staking/react/user-card.tsx b/components/apps/staking/react/user-card.tsx
new file mode 100644
index 0000000..e7d3c19
--- /dev/null
+++ b/components/apps/staking/react/user-card.tsx
@@ -0,0 +1,34 @@
+import { Box,Stack, Text } from "@chakra-ui/react";
+import React from "react";
+
+import { ConnectedUserCardType } from "../../../types";
+
+export const ConnectedUserInfo = ({
+ username,
+ icon,
+}: ConnectedUserCardType) => {
+ return (
+
+ {username && (
+ <>
+
+ {icon}
+
+
+ {username}
+
+ >
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/components/apps/staking/react/wallet-connect.tsx b/components/apps/staking/react/wallet-connect.tsx
new file mode 100644
index 0000000..2961580
--- /dev/null
+++ b/components/apps/staking/react/wallet-connect.tsx
@@ -0,0 +1,202 @@
+import { Button, Icon, Stack, Text, useColorModeValue } from "@chakra-ui/react";
+import { WalletStatus } from "@cosmos-kit/core";
+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,
+ onClickConnectBtn,
+}: ConnectWalletType) => {
+ return (
+
+
+ {buttonText ? buttonText : "Connect Wallet"}
+
+ );
+};
+
+export const Disconnected = ({
+ buttonText,
+ onClick,
+}: {
+ buttonText: string;
+ onClick: MouseEventHandler;
+}) => {
+ return (
+
+ );
+};
+
+export const Connected = ({
+ buttonText,
+ onClick,
+}: {
+ buttonText: string;
+ onClick: MouseEventHandler;
+}) => {
+ return (
+
+ );
+};
+
+export const Connecting = () => {
+ return ;
+};
+
+export const Rejected = ({
+ buttonText,
+ wordOfWarning,
+ onClick,
+}: {
+ buttonText: string;
+ wordOfWarning?: string;
+ onClick: MouseEventHandler;
+}) => {
+ const bg = useColorModeValue("orange.200", "orange.300");
+
+ return (
+
+
+ {wordOfWarning && (
+
+
+
+
+ Warning:
+
+ {wordOfWarning}
+
+
+ )}
+
+ );
+};
+
+export const Error = ({
+ buttonText,
+ wordOfWarning,
+ onClick,
+}: {
+ buttonText: string;
+ wordOfWarning?: string;
+ onClick: MouseEventHandler;
+}) => {
+ const bg = useColorModeValue("orange.200", "orange.300");
+
+ return (
+
+
+ {wordOfWarning && (
+
+
+
+
+ Warning:
+
+ {wordOfWarning}
+
+
+ )}
+
+ );
+};
+
+export const NotExist = ({
+ buttonText,
+ onClick,
+}: {
+ buttonText: string;
+ onClick: MouseEventHandler;
+}) => {
+ return (
+
+ );
+};
+
+export const WalletConnectComponent = ({
+ walletStatus,
+ disconnect,
+ connecting,
+ connected,
+ rejected,
+ error,
+ notExist,
+}: {
+ walletStatus: WalletStatus;
+ disconnect: ReactNode;
+ connecting: ReactNode;
+ connected: ReactNode;
+ rejected: ReactNode;
+ error: ReactNode;
+ notExist: ReactNode;
+}) => {
+ switch (walletStatus) {
+ case "Disconnected":
+ return <>{disconnect}>;
+ case "Connecting":
+ return <>{connecting}>;
+ case "Connected":
+ return <>{connected}>;
+ case "Rejected":
+ return <>{rejected}>;
+ case "Error":
+ return <>{error}>;
+ case "NotExist":
+ return <>{notExist}>;
+ default:
+ return <>{disconnect}>;
+ }
+};
\ No newline at end of file
diff --git a/components/apps/staking/react/warn-block.tsx b/components/apps/staking/react/warn-block.tsx
new file mode 100644
index 0000000..ccf67f3
--- /dev/null
+++ b/components/apps/staking/react/warn-block.tsx
@@ -0,0 +1,90 @@
+import { Box, Stack, Text, useColorModeValue } from "@chakra-ui/react";
+import { WalletStatus } from "@cosmos-kit/core";
+import React, { ReactNode } from "react";
+
+export const WarnBlock = ({
+ wordOfWarning,
+ icon,
+}: {
+ wordOfWarning?: string;
+ icon?: ReactNode;
+}) => {
+ return (
+
+
+
+ {icon}
+
+ {wordOfWarning}
+
+
+ );
+};
+
+export const RejectedWarn = ({
+ wordOfWarning,
+ icon,
+}: {
+ wordOfWarning?: string;
+ icon?: ReactNode;
+}) => {
+ return ;
+};
+
+export const ConnectStatusWarn = ({
+ walletStatus,
+ rejected,
+ error,
+}: {
+ walletStatus: WalletStatus;
+ rejected: ReactNode;
+ error: ReactNode;
+}) => {
+ switch (walletStatus) {
+ case "Rejected":
+ return <>{rejected}>;
+ case "Error":
+ return <>{error}>;
+ default:
+ return <>>;
+ }
+};
\ No newline at end of file
diff --git a/components/apps/store.ts b/components/apps/store.ts
index 3ed3505..f13e0aa 100644
--- a/components/apps/store.ts
+++ b/components/apps/store.ts
@@ -8,7 +8,7 @@ import {
KeyResponse,
OwnerResponse,
} from "@steak-enjoyers/badges.js/types/codegen/Hub.types";
-import create from "zustand";
+import {create} from "zustand";
import { Network, NetworkConfig, NETWORK_CONFIGS, PUBLIC_ACCOUNTS } from "./configs";
export type State = {
diff --git a/components/ecosystem/content.tsx b/components/ecosystem/content.tsx
index d42e0be..184a7bb 100644
--- a/components/ecosystem/content.tsx
+++ b/components/ecosystem/content.tsx
@@ -1,6 +1,7 @@
import { Button, Center } from '@chakra-ui/react';
import { networkData } from './networks';
-import React,{ useState, useEffect } from "react";
+import React, { useState, useEffect } from "react";
+import Link from 'next/link';
type Network = {
network: string;
@@ -23,31 +24,31 @@ const EcosystemContent = () => {
}
if (event.currentTarget.id === '2') {
- setAvailableNetwork(networkData.filter(function(item){
+ setAvailableNetwork(networkData.filter(function (item) {
return item.name === "dApp";
}));
}
if (event.currentTarget.id === '3') {
- setAvailableNetwork(networkData.filter(function(item){
+ setAvailableNetwork(networkData.filter(function (item) {
return item.name === "Explorer";
}));
}
if (event.currentTarget.id === '4') {
- setAvailableNetwork(networkData.filter(function(item){
+ setAvailableNetwork(networkData.filter(function (item) {
return item.name === "Wallet";
}));
}
if (event.currentTarget.id === '5') {
- setAvailableNetwork(networkData.filter(function(item){
+ setAvailableNetwork(networkData.filter(function (item) {
return item.name === "Defi";
}));
}
if (event.currentTarget.id === '6') {
- setAvailableNetwork(networkData.filter(function(item){
+ setAvailableNetwork(networkData.filter(function (item) {
return item.name === "Tools";
}));
}
@@ -58,75 +59,72 @@ const EcosystemContent = () => {
}, []);
return (
-
-
-
TerpNET ecosystem
-
- Discover the suite of applications, wallets, explorers and tools within the ecosystem
-
-
-
- All
-
DAPPS
-
EXPLORER
-
WALLETS
-
DEFI
-
TOOLS
+
+
+
TerpNET ecosystem
+
+ Discover the suite of applications, wallets, explorers and tools within the ecosystem
+
+
+
+ All
+ DAPPS
+ EXPLORER
+ WALLETS
+ DEFI
+ TOOLS
+
+
+
-
-
-
-
-
-)
+ )
}
export default EcosystemContent;
\ No newline at end of file
diff --git a/components/ecosystem/index.ts b/components/ecosystem/index.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/components/ecosystem/networks.tsx b/components/ecosystem/networks.tsx
index 8d46bf4..290edba 100644
--- a/components/ecosystem/networks.tsx
+++ b/components/ecosystem/networks.tsx
@@ -17,7 +17,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Network",
status: "Live",
- wikilink: "https://akash.network/"
+ wikilink: "https://agoric.com/"
},
{
network: "Akash Network",
@@ -33,7 +33,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Network",
status: "Live",
- wikilink: "https://akash.network/"
+ wikilink: "https://anoma.net/"
},
{
network: "Axelar Network ",
@@ -43,13 +43,29 @@ export const networkData: NetworkData[] = [
status: "Live",
wikilink: "https://axelar.network/"
},
+ {
+ network: "Archway Network ",
+ description: "EVM Intechain router",
+ icon: "",
+ name: "Network",
+ status: "Live",
+ wikilink: "https://axelar.network/"
+ },
+ {
+ network: "Babylon Network ",
+ description: "Bringing Bitcoin security to Cosmos and beyond",
+ icon: "",
+ name: "Network",
+ status: "Live",
+ wikilink: "https://www.babylonchain.io/"
+ },
{
network: "Bitsong ",
description: "Tokenizing music & fan interaction.",
icon: "",
name: "Network",
status: "Live",
- wikilink: "https://osmosis.zone/"
+ wikilink: "https://bitsong.io/"
},
{
network: "Bitcanna Network ",
@@ -65,7 +81,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Network",
status: "Live",
- wikilink: "https://nomic.io"
+ wikilink: "https://junonetwork.io"
},
{
network: "Nomic",
@@ -89,7 +105,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Network",
status: "Live",
- wikilink: "https://sentinel.co/"
+ wikilink: "https://secret.network/"
},
{
network: "Sentienel DVPN ",
@@ -129,7 +145,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Tools",
status: "Coming Soon",
- wikilink: "https://sifchain.network/"
+ wikilink: "https://interchaininfo.zone/"
},
{
network: "Abstract.io",
@@ -137,7 +153,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Tools",
status: "Coming Soon",
- wikilink: "https://sifchain.network/"
+ wikilink: "https://abstract.money/"
},
{
network: "Komple.io ",
@@ -145,7 +161,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Tools",
status: "Coming Soon",
- wikilink: "https://sifchain.network/"
+ wikilink: "https://komple.io/"
},
{
network: "Keplr",
@@ -161,7 +177,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Defi",
status: "Coming Soon",
- wikilink: "https://sifchain.network/"
+ wikilink: "https://www.wynddao.com//"
},
{
network: "Gelotto.io",
@@ -169,7 +185,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "Defi",
status: "Live TestNET",
- wikilink: "https://wallet.keplr.app/"
+ wikilink: "https://gelotto.io/"
},
{
network: "Leap",
@@ -272,6 +288,14 @@ export const networkData: NetworkData[] = [
wikilink: "https://spacestation.zone/"
},
// Community Developed Apps
+ {
+ network: "Area 52",
+ description: " interactive coding platform that teaches you all things CosmWasm, Rust smart contracts, and how to build and deploy your own multichain applications.",
+ icon: "",
+ name: "Tools",
+ status: "Live",
+ wikilink: "https://area-52.io/"
+ },
{
network: "Judging App: Legends of Hashish",
description: "Decentralised voting framework",
@@ -310,7 +334,7 @@ export const networkData: NetworkData[] = [
icon: "",
name: "dApp",
status: "Coming Soon",
- wikilink: "https://terp.network/stake"
+ wikilink: "https://terp.network/gov"
},
{
network: "Poap Badges",
diff --git a/components/homepage/terpEcosystem.tsx b/components/homepage/terpEcosystem.tsx
index 1ebdceb..e21ca18 100644
--- a/components/homepage/terpEcosystem.tsx
+++ b/components/homepage/terpEcosystem.tsx
@@ -9,6 +9,7 @@ import {
TERPNET_TWITTER_URL,TERPNET_YOUTUBE_URL
} from "../../config/defaults";
import { Icon } from "@chakra-ui/react";
+import Link from "next/link";
@@ -100,10 +101,10 @@ export const TerpEcosystem = () => {
@@ -126,13 +127,13 @@ export const TerpEcosystem = () => {
diff --git a/components/index.tsx b/components/index.tsx
index 9cd8c23..dbd491f 100644
--- a/components/index.tsx
+++ b/components/index.tsx
@@ -1,7 +1,5 @@
-export * from './types';
export * from './react';
export * from './features';
-export * from './chain-wallet-card'
export * from './wallet';
export * from './head';
export * from './main-menu'
diff --git a/components/layout/twoColumnLayout.tsx b/components/layout/twoColumnLayout.tsx
index 87e1052..0a8efe3 100644
--- a/components/layout/twoColumnLayout.tsx
+++ b/components/layout/twoColumnLayout.tsx
@@ -2,28 +2,49 @@ import React, { useState } from 'react';
import DashboardContent from '../apps/dashboard';
import { SideBarContent } from 'components/sidebar';
+
+
const TwoColumnLayout = () => {
- const [activeTab, setActiveTab] = useState(undefined);
+ const [activeTab, setActiveTab] = useState();
- const handleTabClick = (tab: React.SetStateAction) => {
+ const handleTabClick = (tab: string) => {
setActiveTab(tab);
};
+
return (
-
+
+ {activeTab !== undefined ? (
+ <>
{activeTab === 'dashboard' &&
}
{activeTab === 'badges' &&
Badges Content
}
{activeTab === 'bridge' &&
Bridge Content
}
- {activeTab === 'calendar' &&
Calendar Content
}
+ {activeTab === 'events' &&
Event Content
}
+ {activeTab === 'governance' &&
Governance Content
}
+ {activeTab === 'marketplace' &&
Marketplace Content
}
+ {activeTab === 'multisigs' &&
Multisigs Content
}
+ {activeTab === 'staking' &&
Staking Content
}
+ {activeTab === 'swap' &&
Swap Content
}
+ {activeTab === 'widgets' &&
Widgets Content
}
+ >
+ ) : (
+
+
+ {activeTab === 'dashboard' && }
+ {activeTab === 'badges' &&
Badges Content
}
+ {activeTab === 'bridge' && Bridge Content
}
+ {activeTab === 'events' && Event Content
}
{activeTab === 'governance' && Governance Content
}
{activeTab === 'marketplace' && Marketplace Content
}
{activeTab === 'multisigs' && Multisigs Content
}
{activeTab === 'staking' && Staking Content
}
{activeTab === 'swap' && Swap Content
}
{activeTab === 'widgets' && Widgets Content
}
-
- );
+
+ )}
+
+ );
};
export default TwoColumnLayout;
\ No newline at end of file
diff --git a/components/main-menu.tsx b/components/main-menu.tsx
index eeba7e0..70529c3 100644
--- a/components/main-menu.tsx
+++ b/components/main-menu.tsx
@@ -1,6 +1,3 @@
-
-
-
export const MainMenu = () => {
return (
diff --git a/components/react/address-card.tsx b/components/react/address-card.tsx
index 0ead568..4c54fdf 100644
--- a/components/react/address-card.tsx
+++ b/components/react/address-card.tsx
@@ -8,7 +8,7 @@ import {
useColorMode,
} from "@chakra-ui/react";
import { WalletStatus } from "@cosmos-kit/core";
- import React, { ReactNode, useEffect, useState } from "react";
+ import React, { ReactNode, useEffect, useMemo, useState } from "react";
import { FaCheckCircle } from "react-icons/fa";
import { FiCopy } from "react-icons/fi";
@@ -61,24 +61,29 @@ import {
const { hasCopied, onCopy } = useClipboard(address ? address : "");
const [displayAddress, setDisplayAddress] = useState("");
// const { colorMode } = useColorMode();
- const defaultMaxLength = {
- lg: 14,
- md: 16,
- sm: 18,
- };
+ const defaultMaxLength = useMemo(() => ({
+ xs: 12,
+ sm: 16,
+ md: 20,
+ lg: 24,
+ xl: 28,
+ "2xl": 32,
+ "3xl": 40,
+ "4xl": 48,
+ }), []);
- useEffect(() => {
- if (!address) setDisplayAddress("address not identified yet");
- if (address && maxDisplayLength)
- setDisplayAddress(stringTruncateFromCenter(address, maxDisplayLength));
- if (address && !maxDisplayLength)
- setDisplayAddress(
- stringTruncateFromCenter(
- address,
- defaultMaxLength[size as keyof typeof defaultMaxLength]
- )
- );
- }, [address]);
+ useEffect(() => {
+ if (!address) setDisplayAddress("address not identified yet");
+ if (address && maxDisplayLength)
+ setDisplayAddress(stringTruncateFromCenter(address, maxDisplayLength));
+ if (address && !maxDisplayLength)
+ setDisplayAddress(
+ stringTruncateFromCenter(
+ address,
+ defaultMaxLength[size as keyof typeof defaultMaxLength]
+ )
+ );
+ }, [address, size, maxDisplayLength, defaultMaxLength]);
return (
{
return (
-
-
![chain-icon]({props.icon})
-
{props.prettyName}
-
+
+
+
+
+
+ {props.prettyName}
+
+
);
-};
+};
\ No newline at end of file
diff --git a/components/react/modal.tsx b/components/react/modal.tsx
index 700f787..9bfc261 100644
--- a/components/react/modal.tsx
+++ b/components/react/modal.tsx
@@ -137,8 +137,7 @@ export const TailwindModal = ({
setCurrentView(ModalView.WalletList)}
- qrUri={qrWallet?.qrUrl}
- name={qrWallet?.walletInfo.prettyName}
+
/>
);
case ModalView.Error:
diff --git a/components/react/views/Error.tsx b/components/react/views/Error.tsx
index bdaa6c2..26d0558 100644
--- a/components/react/views/Error.tsx
+++ b/components/react/views/Error.tsx
@@ -50,7 +50,7 @@ export const Error = ({
/>
An error has occured
- Lorem ipsum dolor sit amet
+ Please try again!
{
return (
-
-
+
+
Select a Wallet
diff --git a/components/react/wallet-connect.tsx b/components/react/wallet-connect.tsx
index 683e923..07ecd12 100644
--- a/components/react/wallet-connect.tsx
+++ b/components/react/wallet-connect.tsx
@@ -35,7 +35,7 @@ export const ConnectWalletButton = ({
}}
onClick={onClickConnectBtn}
>
-
+
{buttonText ? buttonText : 'Connect Wallet'}
);
diff --git a/components/sidebar/sidebar.tsx b/components/sidebar/sidebar.tsx
index be47e54..7a22ad8 100644
--- a/components/sidebar/sidebar.tsx
+++ b/components/sidebar/sidebar.tsx
@@ -3,22 +3,32 @@ import { BsApp } from "react-icons/bs";
import { FaNetworkWired, FaTicketAlt } from "react-icons/fa";
import { FiCalendar, FiHome, FiSettings } from "react-icons/fi";
+type IconWrapperProps = {
+ icon: React.ElementType;
+};
+
const links = [
- { href: "/poap", label: "Badges", icon: FaTicketAlt },
- { href: "/bridge", label: "Bridge", icon: FaNetworkWired },
- { href: "/events", label: "Calendar", icon: FiCalendar },
+ { href: "/poap", label: "Badges (Coming Soon)", icon: FaTicketAlt, disabled: true },
+ { href: "/bridge", label: "Bridge (Coming Soon)", icon: FaNetworkWired, disabled: true},
+ { href: "/events", label: "Events (Coming Soon)", icon: FiCalendar, disabled: true },
{ href: "/dashboard", label: "Dashboard", icon: BsApp },
{ href: "/gov", label: "Governance", icon: FiHome },
- { href: "/merch", label: "Marketplace", icon: FiHome },
- { href: "/multi-sigs", label: "Multisigs", icon: FiHome },
+ { href: "/merch", label: "Store (Coming Soon)", icon: FiHome, disabled: true },
+ { href: "/multi-sigs", label: "Multi-Sig (Coming Soon)", icon: FiHome, disabled: true },
{ href: "/stake", label: "Staking", icon: FiHome },
- { href: "/swap", label: "Swap", icon: FiHome },
- { href: "/widgets", label: "Widgets", icon: FiHome },
+ { href: "/swap", label: "Swap ", icon: FiHome, disabled: true },
+ { href: "/widgets", label: "Widgets (Coming Soon)", icon: FiHome, disabled: true },
];
-const IconWrapper = ({ icon }) =>
;
+ const IconWrapper = ({ icon }: IconWrapperProps) =>
;
+
+ interface SidebarContentProps {
+ activeTab: string | undefined;
+ onTabClick: (tab: string) => void;
+ }
+
-export const SideBarContent = ({ activeTab, onTabClick }) => {
+export const SideBarContent = ({ activeTab, onTabClick }: SidebarContentProps) => {
return (
@@ -29,18 +39,20 @@ export const SideBarContent = ({ activeTab, onTabClick }) => {
{links.map((link) => (
-
-
onTabClick(link.label)}
- >
-
- {link.label}
-
+
+ onTabClick(link.label)}
+ pointerEvents={link.disabled ? "none" : "auto"}
+ opacity={link.disabled ? "0.5" : "1"}
+>
+
+ {link.label}
+
))}
diff --git a/components/types.tsx b/components/types.tsx
deleted file mode 100644
index 8c951c7..0000000
--- a/components/types.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import { MouseEventHandler, ReactElement, ReactNode } from "react";
-
-import { AmplitudeEvent } from "../config";
-
-export interface ChooseChainInfo {
- chainName: string
- chainRoute?: string
- label: string
- value: string
- icon?: string
- disabled?: boolean
-}
-
-export enum WalletStatus {
- NotInit = 'NotInit',
- Loading = 'Loading',
- Loaded = 'Loaded',
- NotExist = 'NotExist',
- Rejected = 'Rejected',
-}
-
-export interface ConnectWalletType {
- buttonText?: string
- isLoading?: boolean
- isDisabled?: boolean
- icon?: ReactNode
- onClickConnectBtn?: MouseEventHandler
-}
-
-export interface ConnectedUserCardType {
- walletIcon?: string
- username?: string
- icon?: ReactNode
-}
-
-export interface FeatureProps {
- title: string
- text: string
- href: string
-}
-
-export interface ChainCardProps {
- prettyName: string
- icon?: string
-
-}
-
-
-export type CopyAddressType = {
- address?: string;
- walletIcon?: string;
- isLoading?: boolean;
- maxDisplayLength?: number;
- isRound?: boolean;
- size?: string;
-};
-export type MainLayoutMenu = {
- label: string;
- link: string | MouseEventHandler;
- icon: string | ReactNode;
- iconSelected?: string;
- selectionTest?: RegExp;
- amplitudeEvent?: AmplitudeEvent;
-};
-
-/** PROPS */
-export interface InputProps {
- currentValue: T;
- onInput: (value: T) => void;
- autoFocus?: boolean;
- onFocus?: (e: any) => void;
- placeholder?: T;
-}
-
-export interface CustomClasses {
- className?: string;
-}
-
-export interface LoadingProps {
- isLoading?: boolean;
-}
-
-export interface Disableable {
- disabled?: boolean;
-}
-
-export type SortDirection = "ascending" | "descending";
-
-export interface Metric {
- label: string;
- value: string | ReactElement;
-}
-
-export interface MobileProps {
- isMobile?: boolean;
-}
-
-/** Should match settings in tailwind.config.js
- *
- * https://tailwindcss.com/docs/responsive-design
- */
-export const enum Breakpoint {
- SM = 640,
- MD = 768,
- LG = 1024,
- XLG = 1152,
- XL = 1280,
- XLHALF = 1408,
- XXL = 1536,
-}
-
-export enum VoteOption {
- YES = 'YES',
- NO = 'NO',
- NWV = 'NWV',
- ABSTAIN = 'ABSTAIN',
-}
-
-export enum TransactionResult {
- Success = 0,
- Failed = 1,
-}
diff --git a/components/wallet.tsx b/components/wallet.tsx
index 68bd1b6..8d08769 100644
--- a/components/wallet.tsx
+++ b/components/wallet.tsx
@@ -35,6 +35,10 @@ const buttons = {
},
};
+type WalletSectionProps = {
+ chainName?: string;
+ setChainName?: React.Dispatch>;
+};
export const WalletSection = () => {
const {
connect,
diff --git a/config/defaults.ts b/config/defaults.ts
index ce70f2d..0d0e0d1 100644
--- a/config/defaults.ts
+++ b/config/defaults.ts
@@ -1,4 +1,10 @@
-export const chainName = process.env.NEXT_PUBLIC_CHAIN ?? 'stargaze';;
+
+import { assets } from 'chain-registry';
+import { AssetList, Asset } from '@chain-registry/types';
+
+
+
+export const chainName = process.env.NEXT_PUBLIC_CHAIN ?? 'terpnetwork';;
export const TERPNET_TWITTER_URL = process.env.NEXT_PUBLIC_TERPNET_TWITTER_URL ?? 'https://twitter.com/terpculture';
export const TERPNET_YOUTUBE_URL = process.env.NEXT_PUBLIC_TERPNET_YOUTUBE_URL ?? 'https://youtube.com/terpnetwork';
export const TERPNET_ELEMENT_URL = process.env.NEXT_PUBLIC_TERPNET_ELEMENT_URL ?? 'https://matrix.to/#/!MIEDknobAODITdWMZi:matrix.org?via=matrix.org';
@@ -6,3 +12,20 @@ export const TERPNET_DISCORD_URL = process.env.NEXT_PUBLIC_TERPNET_DISCORD_URL ?
export const TERPNET_GITHUB_URL = process.env.NEXT_PUBLIC_TERPNET_GITHUB_URL ?? 'https://github.com/terpnetwork';
export const TERPNET_MEDIUM_URL = process.env.NEXT_PUBLIC_TERPNET_MEDIUM_URL ?? 'https://terpnetwork.medium.com';
export const TERPNET_REDDIT_URL = process.env.NEXT_PUBLIC_TERPNET_REDDIT_URL ?? 'https://www.reddit.com/r/terpnetwork/';
+
+
+
+export const defaultChainName = 'terpnetwork';
+
+export const getChainAssets = (chainName: string = defaultChainName) => {
+ return assets.find((chain) => chain.chain_name === chainName) as AssetList;
+};
+export const getStakeCoin = (chainName: string = defaultChainName) => {
+ const chainAssets = getChainAssets(chainName);
+ return chainAssets.assets[0] as Asset;
+ };
+
+ export const getGasCoin = (chainName: string = defaultChainName) => {
+ const chainAssets = getChainAssets(chainName);
+ return chainAssets.assets[1] as Asset;
+ };
diff --git a/config/index.ts b/config/index.ts
index 01a4b2d..2e19833 100644
--- a/config/index.ts
+++ b/config/index.ts
@@ -1,3 +1,2 @@
export * from './features';
export * from './defaults';
-export * from './user-analytics-v2';
diff --git a/config/user-analytics-v2.ts b/config/user-analytics-v2.ts
deleted file mode 100644
index 30da56c..0000000
--- a/config/user-analytics-v2.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-export type EventProperties = {
- fromToken: string;
- toToken: string;
- isOnHome: boolean;
- percentage: string;
- filteredBy: string;
- isFilterOn: boolean;
- sortedBy: string;
- sortedOn: "table-head" | "dropdown" | "table";
- sortDirection: string;
- isSingleAsset: boolean;
- unbondingPeriod: number;
- validatorName: string;
- validatorCommission: number;
- isOn: boolean;
- tokenName: string;
- tokenAmount: number;
- bridge: string;
- hasExternalUrl: boolean;
- avatar: "ammelia" | "wosmongton";
- };
-
- export type UserProperties = {
- isWalletConnected: boolean;
- connectedWallet: string;
- totalAssetsPrice: number;
- unbondedAssetsPrice: number;
- bondedAssetsPrice: number;
- stakedTerpPrice: number;
- terpBalance: number;
- thiolBalance: number;
- };
-
- export type AmplitudeEvent =
- | [
- eventName: string,
- eventProperties: Partial> | undefined
- ]
- | [eventName: string];
-
- export const EventName = {
- // Events in Swap UI and page
- Swap: {
- pageViewed: "Swap: Page viewed",
- maxClicked: "Swap: Max clicked",
- halfClicked: "Swap: Half clicked",
- inputEntered: "Swap: Input entered",
- switchClicked: "Swap: Switch clicked",
- dropdownAssetSelected: "Swap: Dropdown asset selected",
- },
- // Events in Sidebar UI
- Sidebar: {
- stakeClicked: "Sidebar: Stake clicked",
- voteClicked: "Sidebar: Vote clicked",
- infoClicked: "Sidebar: Info clicked",
- supportClicked: "Sidebar: Support clicked",
- },
- // Events in Topnav UI
- Topnav: {
- connectWalletClicked: "Topnav: Connect wallet clicked",
- signOutClicked: "Topnav: Sign out clicked",
- },
- // Events in Pools page
-
- // Events in Pool detail page
-
- // Events in assets page
- Assets: {
- pageViewed: "Assets: Page viewed",
- depositClicked: "Assets: Deposit clicked",
- withdrawClicked: "Assets: Withdraw clicked",
-
- assetsListFiltered: "Assets: Assets list filtered",
- assetsListSorted: "Assets: Assets list sorted",
- assetsListMoreClicked: "Assets: Assets list more clicked",
- assetsItemDepositClicked: "Assets: Assets item deposit clicked",
- assetsItemWithdrawClicked: "Assets: Assets item withdraw clicked",
- depositAssetStarted: "Deposit asset: Deposit started",
- depositAssetCompleted: "Deposit asset: Deposit completed",
- withdrawAssetStarted: "Withdraw asset: Withdraw started",
- withdrawAssetCompleted: "Withdraw asset: Withdraw completed",
- },
- // Events in profile modal
- ProfileModal: {
- selectAvatarClicked: "Profile Modal: Select Avatar clicked",
- qrCodeClicked: "Profile Modal: QR code clicked",
- logOutClicked: "Profile Modal: Log out clicked",
- copyWalletAddressClicked: "Profile Modal: Copy wallet address clicked",
- buyTokensClicked: "Profile Modal: Buy tokens clicked",
- blockExplorerLinkOutClicked:
- "Profile Modal: Block explorer link-out clicked",
- },
- };
\ No newline at end of file
diff --git a/hooks/use-dimension.ts b/hooks/use-dimension.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/hooks/window/use-window-size.ts b/hooks/window/use-window-size.ts
index 4278de1..dc09059 100644
--- a/hooks/window/use-window-size.ts
+++ b/hooks/window/use-window-size.ts
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
-import { Breakpoint } from "../../components/types";
+import { Breakpoint } from "../../types";
export interface WindowSize {
width: number;
diff --git a/package.json b/package.json
index ba376f3..515b502 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
},
"dependencies": {
"@amplitude/analytics-browser": "^1.10.3",
+ "@chain-registry/assets": "^1.14.0",
"@chakra-ui/icons": "2.0.12",
"@chakra-ui/react": "2.5.1",
"@cosmjs/cosmwasm-stargate": "0.29.5",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index b8ccc00..bb87dd5 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -32,9 +32,9 @@ function CreateCosmosApp({ Component, pageProps }: AppProps) {
projectId: 'a8510432ebb71e6948cfd6cde54b70f7',
relayUrl: 'wss://relay.walletconnect.org',
metadata: {
- name: 'CosmosKit Template',
+ name: 'Terp Network',
description: 'CosmosKit dapp template',
- url: 'https://docs.cosmoskit.com/',
+ url: 'https://terp.network/',
icons: [],
},
},
@@ -45,8 +45,7 @@ function CreateCosmosApp({ Component, pageProps }: AppProps) {
>
-
-
+
diff --git a/pages/bridge.tsx b/pages/bridge.tsx
index d706bff..a9f037c 100644
--- a/pages/bridge.tsx
+++ b/pages/bridge.tsx
@@ -1,9 +1,49 @@
import TwoColumnLayout from "components/layout/twoColumnLayout"
-
+import Head from 'next/head';
+import {
+ Box,
+ Divider,
+ Grid,
+ Heading,
+ Text,
+ Stack,
+ Container,
+ Link,
+ Button,
+ Flex,
+ Icon,
+ useColorMode,
+ useColorModeValue,
+} from '@chakra-ui/react';
const IBCBridgePage = () => {
return (
+
+
+
+ Create Cosmos App
+
+
+
+
+
+ IBC Transfer
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx
index bf766bf..979ac38 100644
--- a/pages/dashboard.tsx
+++ b/pages/dashboard.tsx
@@ -1,4 +1,4 @@
-import DashboardContent from "components/apps/dashboard";
+import DashboardContent from "components/apps/dashboard/dashboard";
import TwoColumnLayout from "components/layout/twoColumnLayout"
diff --git a/pages/gov.tsx b/pages/gov.tsx
index 703e1ef..6363601 100644
--- a/pages/gov.tsx
+++ b/pages/gov.tsx
@@ -17,26 +17,22 @@ import {
useColorModeValue,
} from '@chakra-ui/react';
import { BsFillMoonStarsFill, BsFillSunFill } from 'react-icons/bs';
-import {WalletSection} from '../components/wallet'
import {VotingSection} from '../components/apps/gov/react/vote'
import { useState } from 'react';
import { ChainName } from '@cosmos-kit/core';
+import { WalletSection } from "components/wallet";
export default function GovernancePage(){
const { colorMode, toggleColorMode } = useColorMode();
- const [chainName, setChainName] = useState
('osmosis');
+ const [chainName, setChainName] = useState('terpnetwork');
return (
-
-
-
+
Governance Dashboard
-
{chainName && }
diff --git a/pages/index.tsx b/pages/index.tsx
index 4f8f504..b669101 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -40,6 +40,7 @@ export default function Home() {
diff --git a/pages/poap.tsx b/pages/poap.tsx
index 6d1b46c..4600761 100644
--- a/pages/poap.tsx
+++ b/pages/poap.tsx
@@ -1,5 +1,4 @@
import TwoColumnLayout from "components/layout/twoColumnLayout"
-import PoapContent from "../components/apps/poap/poapDashboard";
import React from "react";
@@ -7,7 +6,10 @@ export default function PoapPage(){
return (
)
}
diff --git a/pages/posts/[slug].tsx b/pages/posts/[slug].tsx
index 8086bdd..7ca4ff5 100644
--- a/pages/posts/[slug].tsx
+++ b/pages/posts/[slug].tsx
@@ -64,6 +64,9 @@ const PostPage = ({ source, frontMatter }: PostPageProps): JSX.Element => {
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
+ if (!params) {
+ return { notFound: true};
+ }
const postFilePath = path.join(POSTS_PATH, `${params.slug}.mdx`);
const source = fs.readFileSync(postFilePath);
diff --git a/pages/stake.tsx b/pages/stake.tsx
new file mode 100644
index 0000000..0bccb9f
--- /dev/null
+++ b/pages/stake.tsx
@@ -0,0 +1,16 @@
+import StakingContent from "components/apps/staking";
+import TwoColumnLayout from "components/layout/twoColumnLayout"
+import React from "react";
+
+
+const DashboardPage = () => {
+ return (
+
+
+
+
+ )
+}
+
+
+export default DashboardPage;
\ No newline at end of file
diff --git a/styles/globals.css b/styles/globals.css
index d81ce89..3d9c324 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -36,9 +36,8 @@
height: 100%;
background-repeat: no-repeat;
background-size: cover;
- background: linear-gradient(-45deg, #C2C2C2, #F8F8F8, #9B4DCA, #F28705, #E8D095, #F28705, #9B4DCA, #F8F8F8, #C2C2C2);
- background-size: 400% 400%;
- animation: gradient 15s ease infinite;
+ background: linear-gradient(-45deg,#9B4DCA, #F28705, #c9b070, #F28705, #9B4DCA);
+ background-size: 100% 100%;
}
.frosted{
@@ -721,12 +720,57 @@
flex: 0 0 64px;
overflow-y: auto;
}
+.container {
+ position: relative;
+ z-index: 1;
+}
+
+.cover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ backdrop-filter: blur(8px);
+ z-index: -1;
+}
+
+.centered-component {
+ justify-content: center;
+ align-items: center;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.hidden {
+ visibility: hidden;
+}
.two-column-layout {
display: flex;
color: rgba(255, 255, 255, 0.89);
+ backdrop-filter: blur(10px) opacity(0.9);
+ background-color: rgba(5, 5, 6, 0.956);
+ position: relative;
+ pointer-events: none;
}
+.two-column-layout::before {
+ font-size: 24px;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+
+ z-index: 1;
+}
+
+.two-column-layout * {
+ pointer-events: auto;
+}
+
.left-sidebar {
flex: 0 0 64px;
color: transparent;
@@ -890,3 +934,35 @@ a:hover {
.token.variable {
color: #f6e05e;
}
+
+.wallet-list {
+ margin-top: 2rem;
+ text-align: center
+}
+
+@media (min-width: 640px) {
+ .wallet-list {
+ margin-top: 0.5rem;
+ text-align: left;
+
+ }
+}
+.flex-row-center {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ padding-left: 3px;
+}
+.medium-text {
+ font-weight: 500;
+ font-size: 1.5rem;
+ line-height: 1.5; /* this value can vary depending on your design */
+ color: #333333;
+}
+
+@media (prefers-color-scheme: dark) {
+ .medium-text {
+ color: #f8f8f8;
+ }
+}
\ No newline at end of file
diff --git a/types/chain.ts b/types/chain.ts
new file mode 100644
index 0000000..9f42b09
--- /dev/null
+++ b/types/chain.ts
@@ -0,0 +1,50 @@
+import { RefObject } from "react"
+
+export interface ChooseChainInfo {
+ chainName: string
+ chainRoute?: string
+ label: string
+ value: string
+ icon?: string
+ disabled?: boolean
+ }
+
+ export interface ChainCardProps {
+ prettyName: string
+ icon?: string
+
+ }
+
+ export interface OptionBase {
+ variant?: string;
+ colorScheme?: string;
+ isFixed?: boolean;
+ isDisabled?: boolean;
+ }
+
+ export interface ChainOption extends OptionBase {
+ isDisabled?: boolean;
+ label: string;
+ value: string;
+ icon?: string;
+ chainName: string;
+ chainRoute?: string;
+ id?: string;
+ }
+
+ export type handleSelectChainDropdown = (value: ChainOption | null) => void;
+
+export interface ChangeChainDropdownType {
+ data: ChainOption[];
+ selectedItem?: ChainOption;
+ onChange: handleSelectChainDropdown;
+ chainDropdownLoading?: boolean;
+}
+
+export interface ChangeChainMenuType {
+ data: ChainOption[];
+ value?: ChainOption;
+ onClose?: () => void;
+ onChange: handleSelectChainDropdown;
+ innerRef?: RefObject;
+}
\ No newline at end of file
diff --git a/types/common.ts b/types/common.ts
new file mode 100644
index 0000000..4ebf42f
--- /dev/null
+++ b/types/common.ts
@@ -0,0 +1,29 @@
+
+export enum TransactionResult {
+ Success = 0,
+ Failed = 1,
+ }
+
+
+export interface Balance {
+ denom: string;
+ symbol: string;
+ amount: string;
+ displayAmount: number;
+ logoUrl?: string;
+ }
+
+ export interface FeatureProps {
+ title: string
+ text: string
+ href: string
+ }
+
+ export type CopyAddressType = {
+ address?: string;
+ walletIcon?: string;
+ isLoading?: boolean;
+ maxDisplayLength?: number;
+ isRound?: boolean;
+ size?: string;
+ };
\ No newline at end of file
diff --git a/components/blog/index.tsx b/types/dashboard.ts
similarity index 100%
rename from components/blog/index.tsx
rename to types/dashboard.ts
diff --git a/types/gov.ts b/types/gov.ts
new file mode 100644
index 0000000..3c71418
--- /dev/null
+++ b/types/gov.ts
@@ -0,0 +1,8 @@
+
+
+export enum VoteOption {
+ YES = 'YES',
+ NO = 'NO',
+ NWV = 'NWV',
+ ABSTAIN = 'ABSTAIN',
+ }
\ No newline at end of file
diff --git a/types/index.ts b/types/index.ts
new file mode 100644
index 0000000..4a76a66
--- /dev/null
+++ b/types/index.ts
@@ -0,0 +1,8 @@
+export * from './chain'
+export * from './common'
+// export * from '.dashboard'
+export * from './gov'
+export * from './layout'
+export * from './post'
+export * from './staking'
+export * from './wallet'
\ No newline at end of file
diff --git a/types/layout.ts b/types/layout.ts
index 24e15a8..8ddda52 100644
--- a/types/layout.ts
+++ b/types/layout.ts
@@ -6,4 +6,18 @@ export interface MetaProps
* For the meta tag `og:type`
*/
type?: string;
+}
+
+export interface MobileProps {
+ isMobile?: boolean;
+}
+
+export const enum Breakpoint {
+ SM = 640,
+ MD = 768,
+ LG = 1024,
+ XLG = 1152,
+ XL = 1280,
+ XLHALF = 1408,
+ XXL = 1536,
}
\ No newline at end of file
diff --git a/types/staking.ts b/types/staking.ts
new file mode 100644
index 0000000..5302d4c
--- /dev/null
+++ b/types/staking.ts
@@ -0,0 +1,15 @@
+
+
+export type ImageSource = {
+ imageSource: 'cosmostation' | 'keybase';
+};
+
+export interface MyValidator {
+ details: string | undefined;
+ name: string | undefined;
+ address: string;
+ staked: number;
+ reward: number;
+ identity: string | undefined;
+ commission: string | undefined;
+}
\ No newline at end of file
diff --git a/types/wallet.ts b/types/wallet.ts
new file mode 100644
index 0000000..9de8cd3
--- /dev/null
+++ b/types/wallet.ts
@@ -0,0 +1,25 @@
+import { MouseEventHandler, ReactNode, RefObject } from "react";
+
+export enum WalletStatus {
+ NotInit = 'NotInit',
+ Loading = 'Loading',
+ Loaded = 'Loaded',
+ NotExist = 'NotExist',
+ Rejected = 'Rejected',
+ }
+
+ export interface ConnectWalletType {
+ buttonText?: string
+ isLoading?: boolean
+ isDisabled?: boolean
+ icon?: ReactNode
+ onClickConnectBtn?: MouseEventHandler
+ }
+
+ export interface ConnectedUserCardType {
+ walletIcon?: string
+ username?: string
+ icon?: ReactNode
+ }
+
+
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 051c350..19724ca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1226,6 +1226,14 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
+"@chain-registry/assets@^1.14.0":
+ version "1.14.0"
+ resolved "https://registry.yarnpkg.com/@chain-registry/assets/-/assets-1.14.0.tgz#0e7cd7c976ac1b6831d58648ec281878601d54ca"
+ integrity sha512-L8yAy227wXTFfwMfr14wEZLpnx52fkn6x+krU704F1YIeqHYPp5Y1KBzIOcdQ1oq45hhYwuHf7xoMwoXcd/sTQ==
+ dependencies:
+ "@babel/runtime" "^7.21.0"
+ "@chain-registry/types" "^0.16.0"
+
"@chain-registry/cosmostation@1.8.0":
version "1.8.0"
resolved "https://registry.npmjs.org/@chain-registry/cosmostation/-/cosmostation-1.8.0.tgz"