From 77d568ea9d2fdbd6d906337de58ed5b26289292d Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Wed, 10 Jan 2024 13:57:22 +0700 Subject: [PATCH 1/4] fix(components): add total value for contract detail --- CHANGELOG.md | 1 + .../TotalValue.tsx} | 27 +- src/lib/components/links/GitHubLink.tsx | 12 +- src/lib/model/account.ts | 270 +++++++++++++++++ .../components/AccountHeader.tsx | 5 +- .../components/delegations/index.tsx | 4 +- src/lib/pages/account-details/data.ts | 273 +----------------- .../components/code-info/CodeTopInfo.tsx | 2 +- .../components/ContractTop.tsx | 117 ++++---- 9 files changed, 365 insertions(+), 346 deletions(-) rename src/lib/{pages/account-details/components/TotalAccountValue.tsx => components/TotalValue.tsx} (70%) create mode 100644 src/lib/model/account.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 60430d981..972ba6e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#717](https://github.com/alleslabs/celatone-frontend/pull/717) Add total value for contract detail - [#681](https://github.com/alleslabs/celatone-frontend/pull/681) Add Initia wallet - [#704](https://github.com/alleslabs/celatone-frontend/pull/704) Fix client undefined after disconnected - [#698](https://github.com/alleslabs/celatone-frontend/pull/698) Add ledger diff --git a/src/lib/pages/account-details/components/TotalAccountValue.tsx b/src/lib/components/TotalValue.tsx similarity index 70% rename from src/lib/pages/account-details/components/TotalAccountValue.tsx rename to src/lib/components/TotalValue.tsx index c207032f4..ed410ce5c 100644 --- a/src/lib/pages/account-details/components/TotalAccountValue.tsx +++ b/src/lib/components/TotalValue.tsx @@ -1,32 +1,35 @@ import { Flex, Heading, Skeleton, Text } from "@chakra-ui/react"; -import { useAccountTotalValue } from "../data"; +import { useAccountTotalValue } from "lib/model/account"; import type { BechAddr } from "lib/types"; import { formatPrice } from "lib/utils"; -interface TotalAccountValueProps { - accountAddress: BechAddr; +interface TotalValueProps { + address: BechAddr; + label?: string; + isCompact?: boolean; } -export const TotalAccountValue = ({ - accountAddress, -}: TotalAccountValueProps) => { - const { totalAccountValue, isLoading } = useAccountTotalValue(accountAddress); +export const TotalValue = ({ + address, + label = "Total Account Value", + isCompact = false, +}: TotalValueProps) => { + const { totalAccountValue, isLoading } = useAccountTotalValue(address); return ( - Total Account Value + {label} {isLoading ? ( diff --git a/src/lib/components/links/GitHubLink.tsx b/src/lib/components/links/GitHubLink.tsx index e71c54d68..12076b9fc 100644 --- a/src/lib/components/links/GitHubLink.tsx +++ b/src/lib/components/links/GitHubLink.tsx @@ -5,14 +5,20 @@ import { trackSocial } from "lib/amplitude"; interface GitHubLinkProps { github: string; + hasMinW?: boolean; } -export const GitHubLink = ({ github }: GitHubLinkProps) => { +export const GitHubLink = ({ github, hasMinW = false }: GitHubLinkProps) => { const [, , , org, repo] = github.split("/"); return ( - - GitHub: + + GitHub{!hasMinW && ":"} ; + isValidator: Option; + totalBonded: Option>; + totalDelegations: Option>; + delegations: Option; + totalUnbondings: Option>; + unbondings: Option; + totalRewards: Option>; + rewards: Option>; + redelegations: Option; + totalCommission: Option>; +} + +// ------------------------------------------// +// ----------------DELEGATIONS---------------// +// ------------------------------------------// + +const calBonded = ( + totalDelegations: Option>, + totalUnbondings: Option> +) => { + if (!totalDelegations || !totalUnbondings) return undefined; + + return Object.keys(totalDelegations).reduce>( + (total, denom) => ({ + ...total, + [denom]: addTokenWithValue( + totalUnbondings[denom], + totalDelegations[denom] + ), + }), + {} + ); +}; + +export const useAccountDelegationInfos = (address: BechAddr) => { + const { data: assetInfos, isLoading: isLoadingAssetInfos } = useAssetInfos({ + withPrices: true, + }); + const { data: movePoolInfos, isLoading: isLoadingMovePoolInfos } = + useMovePoolInfos({ + withPrices: true, + }); + + const { data: accountDelegations, isLoading: isLoadingAccountDelegations } = + useDelegationsByAddress(address); + + const isLoading = + isLoadingAccountDelegations || + isLoadingAssetInfos || + isLoadingMovePoolInfos; + + const data: UserDelegationsData = { + isLoading, + stakingParams: undefined, + isValidator: undefined, + totalBonded: undefined, + totalDelegations: undefined, + delegations: undefined, + totalUnbondings: undefined, + unbondings: undefined, + totalRewards: undefined, + rewards: undefined, + redelegations: undefined, + totalCommission: undefined, + }; + + if (accountDelegations) { + data.stakingParams = { + ...accountDelegations.stakingParams, + bondDenoms: accountDelegations.stakingParams.bondDenoms.map((denom) => + coinToTokenWithValue(denom, "0", assetInfos, movePoolInfos) + ), + }; + + data.isValidator = accountDelegations.isValidator; + + data.delegations = accountDelegations.delegations.map( + (raw) => ({ + validator: raw.validator, + balances: raw.balance + .map((coin) => + coinToTokenWithValue( + coin.denom, + coin.amount, + assetInfos, + movePoolInfos + ) + ) + .sort(compareTokenWithValues), + }) + ); + data.totalDelegations = data.delegations?.reduce< + Record + >( + (total, delegation) => + delegation.balances.reduce( + (acc, balance) => ({ + ...acc, + [balance.denom]: addTokenWithValue(acc[balance.denom], balance), + }), + total + ), + {} + ); + + data.unbondings = accountDelegations.unbondings.map((raw) => ({ + validator: raw.validator, + completionTime: raw.completionTime, + balances: raw.balance + .map((coin) => + coinToTokenWithValue( + coin.denom, + coin.amount, + assetInfos, + movePoolInfos + ) + ) + .sort(compareTokenWithValues), + })); + data.totalUnbondings = data.unbondings?.reduce< + Record + >( + (total, unbonding) => + unbonding.balances.reduce( + (acc, balance) => ({ + ...acc, + [balance.denom]: addTokenWithValue(acc[balance.denom], balance), + }), + total + ), + {} + ); + + data.rewards = accountDelegations.delegationRewards.rewards.reduce< + Record + >( + (prev, raw) => ({ + ...prev, + [raw.validator.validatorAddress]: raw.reward + .map((coin) => + coinToTokenWithValue( + coin.denom, + coin.amount, + assetInfos, + movePoolInfos + ) + ) + .sort(compareTokenWithValues), + }), + {} + ); + data.totalRewards = accountDelegations.delegationRewards.total.reduce< + Record + >( + (total, raw) => ({ + ...total, + [raw.denom]: coinToTokenWithValue( + raw.denom, + raw.amount, + assetInfos, + movePoolInfos + ), + }), + {} + ); + + data.redelegations = accountDelegations.redelegations.map( + (raw) => ({ + srcValidator: raw.srcValidator, + dstValidator: raw.dstValidator, + completionTime: raw.completionTime, + balances: raw.balance + .map((coin) => + coinToTokenWithValue( + coin.denom, + coin.amount, + assetInfos, + movePoolInfos + ) + ) + .sort(compareTokenWithValues), + }) + ); + + data.totalCommission = accountDelegations.commissions.reduce< + Record + >( + (commission, raw) => ({ + ...commission, + [raw.denom]: coinToTokenWithValue( + raw.denom, + raw.amount, + assetInfos, + movePoolInfos + ), + }), + {} + ); + + data.totalBonded = calBonded(data.totalDelegations, data.totalUnbondings); + } + + return data; +}; + +export const useAccountTotalValue = (address: BechAddr) => { + const defaultValue = big(0) as USD; + + const { + chainConfig: { + extra: { disableDelegation }, + }, + } = useCelatoneApp(); + const { + totalSupportedAssetsValue = defaultValue, + isLoading: isLoadingTotalSupportedAssetsValue, + } = useBalanceInfos(address); + const { + isLoading, + stakingParams, + totalBonded, + totalRewards, + totalCommission, + } = useAccountDelegationInfos(address); + + if (disableDelegation) + return { + totalAccountValue: totalSupportedAssetsValue, + isLoading: false, + }; + + if (isLoading || isLoadingTotalSupportedAssetsValue) + return { totalAccountValue: undefined, isLoading: true }; + + if (!stakingParams || !totalBonded || !totalRewards || !totalCommission) + return { totalAccountValue: undefined, isLoading: false }; + + return { + totalAccountValue: totalSupportedAssetsValue + .add(totalValueTokenWithValue(totalBonded, defaultValue)) + .add(totalValueTokenWithValue(totalRewards, defaultValue)) + .add(totalValueTokenWithValue(totalCommission, defaultValue)) as USD, + isLoading: false, + }; +}; diff --git a/src/lib/pages/account-details/components/AccountHeader.tsx b/src/lib/pages/account-details/components/AccountHeader.tsx index a56ed9bd4..03d2409f0 100644 --- a/src/lib/pages/account-details/components/AccountHeader.tsx +++ b/src/lib/pages/account-details/components/AccountHeader.tsx @@ -1,6 +1,7 @@ import { Flex, Heading, IconButton, Image, Text } from "@chakra-ui/react"; import { observer } from "mobx-react-lite"; +import { TotalValue } from "../../../components/TotalValue"; import { useMobile, useMoveConfig } from "lib/app-provider"; import { CopyLink } from "lib/components/CopyLink"; import { CustomIcon } from "lib/components/icon"; @@ -14,8 +15,6 @@ import { useAccountStore } from "lib/providers/store"; import type { AccountData } from "lib/services/account"; import type { HexAddr, BechAddr, Option } from "lib/types"; -import { TotalAccountValue } from "./TotalAccountValue"; - interface AccounHeaderProps { accountData: Option; accountAddress: BechAddr; @@ -182,7 +181,7 @@ export const AccountHeader = observer( )} - + ); diff --git a/src/lib/pages/account-details/components/delegations/index.tsx b/src/lib/pages/account-details/components/delegations/index.tsx index f3cb51b1f..7e89c1ec3 100644 --- a/src/lib/pages/account-details/components/delegations/index.tsx +++ b/src/lib/pages/account-details/components/delegations/index.tsx @@ -5,7 +5,7 @@ import { useEffect } from "react"; import { AmpEvent, track } from "lib/amplitude"; import { Loading } from "lib/components/Loading"; import { ErrorFetching } from "lib/components/state"; -import { useUserDelegationInfos } from "lib/pages/account-details/data"; +import { useAccountDelegationInfos } from "lib/model/account"; import type { BechAddr } from "lib/types"; import { getTokenLabel } from "lib/utils"; @@ -38,7 +38,7 @@ export const DelegationsSection = ({ rewards, redelegations, totalCommission, - } = useUserDelegationInfos(address); + } = useAccountDelegationInfos(address); useEffect(() => { onClose(); diff --git a/src/lib/pages/account-details/data.ts b/src/lib/pages/account-details/data.ts index 10676e00e..351f239e4 100644 --- a/src/lib/pages/account-details/data.ts +++ b/src/lib/pages/account-details/data.ts @@ -1,40 +1,18 @@ -import type { Big } from "big.js"; -import big from "big.js"; - -import { useCelatoneApp } from "lib/app-provider"; import { useCodeStore, useContractStore } from "lib/providers/store"; import { useAccountTableCounts } from "lib/services/accountService"; -import { useAssetInfos } from "lib/services/assetService"; -import { useBalanceInfos, useBalances } from "lib/services/balanceService"; +import { useBalances } from "lib/services/balanceService"; import { useCodesByAddress } from "lib/services/codeService"; import { useAdminContractsByAddress, useInstantiatedContractsByAddress, } from "lib/services/contractService"; -import { useDelegationsByAddress } from "lib/services/delegationService"; -import { useMovePoolInfos } from "lib/services/move"; import type { BechAddr, CodeInfo, ContractInfo, Nullish, Option, - TokenWithValue, - USD, } from "lib/types"; -import { - addTokenWithValue, - coinToTokenWithValue, - totalValueTokenWithValue, - compareTokenWithValues, -} from "lib/utils"; - -import type { - Delegation, - Redelegation, - StakingParams, - Unbonding, -} from "./types"; // ------------------------------------------// // ---------------TABLE COUNTS---------------// @@ -173,252 +151,3 @@ export const useAccountCodes = ( isLoading, }; }; - -// ------------------------------------------// -// ----------------DELEGATIONS---------------// -// ------------------------------------------// - -interface UserDelegationsData { - isLoading: boolean; - stakingParams: Option; - isValidator: Option; - totalBonded: Option>; - totalDelegations: Option>; - delegations: Option; - totalUnbondings: Option>; - unbondings: Option; - totalRewards: Option>; - rewards: Option>; - redelegations: Option; - totalCommission: Option>; -} - -const calBonded = ( - totalDelegations: Option>, - totalUnbondings: Option> -) => { - if (!totalDelegations || !totalUnbondings) return undefined; - - return Object.keys(totalDelegations).reduce>( - (total, denom) => ({ - ...total, - [denom]: addTokenWithValue( - totalUnbondings[denom], - totalDelegations[denom] - ), - }), - {} - ); -}; - -export const useUserDelegationInfos = (address: BechAddr) => { - const { data: assetInfos, isLoading: isLoadingAssetInfos } = useAssetInfos({ - withPrices: true, - }); - const { data: movePoolInfos, isLoading: isLoadingMovePoolInfos } = - useMovePoolInfos({ - withPrices: true, - }); - - const { data: accountDelegations, isLoading: isLoadingAccountDelegations } = - useDelegationsByAddress(address); - - const isLoading = - isLoadingAccountDelegations || - isLoadingAssetInfos || - isLoadingMovePoolInfos; - - const data: UserDelegationsData = { - isLoading, - stakingParams: undefined, - isValidator: undefined, - totalBonded: undefined, - totalDelegations: undefined, - delegations: undefined, - totalUnbondings: undefined, - unbondings: undefined, - totalRewards: undefined, - rewards: undefined, - redelegations: undefined, - totalCommission: undefined, - }; - - if (accountDelegations) { - data.stakingParams = { - ...accountDelegations.stakingParams, - bondDenoms: accountDelegations.stakingParams.bondDenoms.map((denom) => - coinToTokenWithValue(denom, "0", assetInfos, movePoolInfos) - ), - }; - - data.isValidator = accountDelegations.isValidator; - - data.delegations = accountDelegations.delegations.map( - (raw) => ({ - validator: raw.validator, - balances: raw.balance - .map((coin) => - coinToTokenWithValue( - coin.denom, - coin.amount, - assetInfos, - movePoolInfos - ) - ) - .sort(compareTokenWithValues), - }) - ); - data.totalDelegations = data.delegations?.reduce< - Record - >( - (total, delegation) => - delegation.balances.reduce( - (acc, balance) => ({ - ...acc, - [balance.denom]: addTokenWithValue(acc[balance.denom], balance), - }), - total - ), - {} - ); - - data.unbondings = accountDelegations.unbondings.map((raw) => ({ - validator: raw.validator, - completionTime: raw.completionTime, - balances: raw.balance - .map((coin) => - coinToTokenWithValue( - coin.denom, - coin.amount, - assetInfos, - movePoolInfos - ) - ) - .sort(compareTokenWithValues), - })); - data.totalUnbondings = data.unbondings?.reduce< - Record - >( - (total, unbonding) => - unbonding.balances.reduce( - (acc, balance) => ({ - ...acc, - [balance.denom]: addTokenWithValue(acc[balance.denom], balance), - }), - total - ), - {} - ); - - data.rewards = accountDelegations.delegationRewards.rewards.reduce< - Record - >( - (prev, raw) => ({ - ...prev, - [raw.validator.validatorAddress]: raw.reward - .map((coin) => - coinToTokenWithValue( - coin.denom, - coin.amount, - assetInfos, - movePoolInfos - ) - ) - .sort(compareTokenWithValues), - }), - {} - ); - data.totalRewards = accountDelegations.delegationRewards.total.reduce< - Record - >( - (total, raw) => ({ - ...total, - [raw.denom]: coinToTokenWithValue( - raw.denom, - raw.amount, - assetInfos, - movePoolInfos - ), - }), - {} - ); - - data.redelegations = accountDelegations.redelegations.map( - (raw) => ({ - srcValidator: raw.srcValidator, - dstValidator: raw.dstValidator, - completionTime: raw.completionTime, - balances: raw.balance - .map((coin) => - coinToTokenWithValue( - coin.denom, - coin.amount, - assetInfos, - movePoolInfos - ) - ) - .sort(compareTokenWithValues), - }) - ); - - data.totalCommission = accountDelegations.commissions.reduce< - Record - >( - (commission, raw) => ({ - ...commission, - [raw.denom]: coinToTokenWithValue( - raw.denom, - raw.amount, - assetInfos, - movePoolInfos - ), - }), - {} - ); - - data.totalBonded = calBonded(data.totalDelegations, data.totalUnbondings); - } - - return data; -}; - -export const useAccountTotalValue = (address: BechAddr) => { - const defaultValue = big(0) as USD; - - const { - chainConfig: { - extra: { disableDelegation }, - }, - } = useCelatoneApp(); - const { - totalSupportedAssetsValue = defaultValue, - isLoading: isLoadingTotalSupportedAssetsValue, - } = useBalanceInfos(address); - const { - isLoading, - stakingParams, - totalBonded, - totalRewards, - totalCommission, - } = useUserDelegationInfos(address); - - if (disableDelegation) - return { - totalAccountValue: totalSupportedAssetsValue, - isLoading: false, - }; - - if (isLoading || isLoadingTotalSupportedAssetsValue) - return { totalAccountValue: undefined, isLoading: true }; - - if (!stakingParams || !totalBonded || !totalRewards || !totalCommission) - return { totalAccountValue: undefined, isLoading: false }; - - return { - totalAccountValue: totalSupportedAssetsValue - .add(totalValueTokenWithValue(totalBonded, defaultValue)) - .add(totalValueTokenWithValue(totalRewards, defaultValue)) - .add(totalValueTokenWithValue(totalCommission, defaultValue)) as USD, - isLoading: false, - }; -}; diff --git a/src/lib/pages/code-details/components/code-info/CodeTopInfo.tsx b/src/lib/pages/code-details/components/code-info/CodeTopInfo.tsx index 7febee732..732a5d10f 100644 --- a/src/lib/pages/code-details/components/code-info/CodeTopInfo.tsx +++ b/src/lib/pages/code-details/components/code-info/CodeTopInfo.tsx @@ -72,7 +72,7 @@ export const CodeTopInfo = ({ codeId, codeDataState }: CodeTopInfoProps) => { /> diff --git a/src/lib/pages/contract-details/components/ContractTop.tsx b/src/lib/pages/contract-details/components/ContractTop.tsx index 18512d618..6d3ad56a3 100644 --- a/src/lib/pages/contract-details/components/ContractTop.tsx +++ b/src/lib/pages/contract-details/components/ContractTop.tsx @@ -18,6 +18,7 @@ import { EditContractDetailsModal, SaveContractDetailsModal, } from "lib/components/modal"; +import { TotalValue } from "lib/components/TotalValue"; import type { Contract } from "lib/services/contract"; import type { ContractLocalInfo } from "lib/stores/contract"; import type { @@ -172,11 +173,12 @@ export const ContractTop = ({ > - Contract Address: + Contract Address - - Label: + + Label {contract.label} @@ -200,65 +202,74 @@ export const ContractTop = ({ direction={{ base: "column", md: "row" }} gap={{ base: 0, md: 2 }} > - - Public Contract Name: + + Public Name {publicInfo.name} )} - {publicInfo?.github && } - - - {!isMobile && ( - + {publicInfo?.github && ( + )} - - - - {!isMobile && ( - - {contractLocalInfo && ( - } - /> - } - /> - )} - {renderSaveButton()} - - )} + {!isMobile && ( + + )} + + + {!isMobile && ( + + {contractLocalInfo && ( + } + /> + } + /> + )} + {renderSaveButton()} + + )} + + From a9e6817e85dca3aa6f1ad61ae26b8dea8a0c4d83 Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Wed, 10 Jan 2024 17:05:44 +0700 Subject: [PATCH 2/4] fix(components): fix contract top --- .../contract-details/components/ContractTop.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib/pages/contract-details/components/ContractTop.tsx b/src/lib/pages/contract-details/components/ContractTop.tsx index 6d3ad56a3..c13f6df47 100644 --- a/src/lib/pages/contract-details/components/ContractTop.tsx +++ b/src/lib/pages/contract-details/components/ContractTop.tsx @@ -128,19 +128,15 @@ export const ContractTop = ({ justify="space-between" mt={{ base: 3, md: 6 }} direction={{ base: "column", md: "row" }} - gap={{ md: 4 }} + gap={{ md: 8 }} > - + Date: Fri, 12 Jan 2024 15:42:52 +0700 Subject: [PATCH 3/4] fix(components): fix component import --- src/lib/pages/account-details/components/AccountHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/pages/account-details/components/AccountHeader.tsx b/src/lib/pages/account-details/components/AccountHeader.tsx index 03d2409f0..e7d6f1a43 100644 --- a/src/lib/pages/account-details/components/AccountHeader.tsx +++ b/src/lib/pages/account-details/components/AccountHeader.tsx @@ -1,7 +1,6 @@ import { Flex, Heading, IconButton, Image, Text } from "@chakra-ui/react"; import { observer } from "mobx-react-lite"; -import { TotalValue } from "../../../components/TotalValue"; import { useMobile, useMoveConfig } from "lib/app-provider"; import { CopyLink } from "lib/components/CopyLink"; import { CustomIcon } from "lib/components/icon"; @@ -11,6 +10,7 @@ import { RemoveSavedAccountModal, } from "lib/components/modal"; import { PrimaryNameMark } from "lib/components/PrimaryNameMark"; +import { TotalValue } from "lib/components/TotalValue"; import { useAccountStore } from "lib/providers/store"; import type { AccountData } from "lib/services/account"; import type { HexAddr, BechAddr, Option } from "lib/types"; From 5e0b2ea032083cfc7e6fe9f2da55f09b0716ae17 Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Sun, 14 Jan 2024 23:11:24 +0700 Subject: [PATCH 4/4] fix(components): fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23d776edc..faa007d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#717](https://github.com/alleslabs/celatone-frontend/pull/717) Add total value for contract detail - [#711](https://github.com/alleslabs/celatone-frontend/pull/711) Refactor assetInfos and add movePoolInfos to tx details - [#724](https://github.com/alleslabs/celatone-frontend/pull/724) Add stone-13 @@ -57,7 +58,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features -- [#717](https://github.com/alleslabs/celatone-frontend/pull/717) Add total value for contract detail - [#681](https://github.com/alleslabs/celatone-frontend/pull/681) Add Initia wallet - [#704](https://github.com/alleslabs/celatone-frontend/pull/704) Fix client undefined after disconnected - [#698](https://github.com/alleslabs/celatone-frontend/pull/698) Add ledger