Skip to content

Commit

Permalink
Merge pull request #717 from alleslabs/feat/cfe-271-contract-detail-t…
Browse files Browse the repository at this point in the history
…otal-value

fix(components): add total value for contract detail
  • Loading branch information
songwongtp committed Jan 15, 2024
2 parents 22bf50c + 5e0b2ea commit 8dfde92
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 354 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<Flex
p={4}
p={isCompact ? 3 : 4}
direction="column"
border="2px solid"
borderColor="gray.700"
borderRadius={4}
>
<Text variant="body2" fontWeight={500} color="text.dark">
Total Account Value
{label}
</Text>
{isLoading ? (
<Skeleton
mt={1}
h={8}
w="full"
h={5}
borderRadius={2}
startColor="gray.500"
endColor="gray.700"
Expand All @@ -37,7 +40,7 @@ export const TotalAccountValue = ({
variant="h6"
color={
!totalAccountValue || totalAccountValue.eq(0)
? "text.dark"
? "text.disabled"
: "text.main"
}
>
Expand Down
12 changes: 9 additions & 3 deletions src/lib/components/links/GitHubLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Flex gap={{ base: 0, md: 2 }} direction={{ base: "column", md: "row" }}>
<Text fontWeight={500} color="text.dark" variant="body2">
GitHub:
<Text
fontWeight={500}
color="text.dark"
variant="body2"
minW={hasMinW ? 32 : "auto"}
>
GitHub{!hasMinW && ":"}
</Text>
<a
href={github}
Expand Down
270 changes: 270 additions & 0 deletions src/lib/model/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import type { Big } from "big.js";
import big from "big.js";

import { useCelatoneApp } from "lib/app-provider";
import type {
Delegation,
Redelegation,
StakingParams,
Unbonding,
} from "lib/pages/account-details/types";
import { useAssetInfos } from "lib/services/assetService";
import { useBalanceInfos } from "lib/services/balanceService";
import { useDelegationsByAddress } from "lib/services/delegationService";
import { useMovePoolInfos } from "lib/services/move";
import type { Option, BechAddr, USD, TokenWithValue } from "lib/types";
import {
addTokenWithValue,
coinToTokenWithValue,
compareTokenWithValues,
totalValueTokenWithValue,
} from "lib/utils";

interface UserDelegationsData {
isLoading: boolean;
stakingParams: Option<StakingParams>;
isValidator: Option<boolean>;
totalBonded: Option<Record<string, TokenWithValue>>;
totalDelegations: Option<Record<string, TokenWithValue>>;
delegations: Option<Delegation[]>;
totalUnbondings: Option<Record<string, TokenWithValue>>;
unbondings: Option<Unbonding[]>;
totalRewards: Option<Record<string, TokenWithValue>>;
rewards: Option<Record<string, TokenWithValue[]>>;
redelegations: Option<Redelegation[]>;
totalCommission: Option<Record<string, TokenWithValue>>;
}

// ------------------------------------------//
// ----------------DELEGATIONS---------------//
// ------------------------------------------//

const calBonded = (
totalDelegations: Option<Record<string, TokenWithValue>>,
totalUnbondings: Option<Record<string, TokenWithValue>>
) => {
if (!totalDelegations || !totalUnbondings) return undefined;

return Object.keys(totalDelegations).reduce<Record<string, TokenWithValue>>(
(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<Delegation>(
(raw) => ({
validator: raw.validator,
balances: raw.balance
.map((coin) =>
coinToTokenWithValue(
coin.denom,
coin.amount,
assetInfos,
movePoolInfos
)
)
.sort(compareTokenWithValues),
})
);
data.totalDelegations = data.delegations?.reduce<
Record<string, TokenWithValue>
>(
(total, delegation) =>
delegation.balances.reduce(
(acc, balance) => ({
...acc,
[balance.denom]: addTokenWithValue(acc[balance.denom], balance),
}),
total
),
{}
);

data.unbondings = accountDelegations.unbondings.map<Unbonding>((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<string, TokenWithValue>
>(
(total, unbonding) =>
unbonding.balances.reduce(
(acc, balance) => ({
...acc,
[balance.denom]: addTokenWithValue(acc[balance.denom], balance),
}),
total
),
{}
);

data.rewards = accountDelegations.delegationRewards.rewards.reduce<
Record<string, TokenWithValue[]>
>(
(prev, raw) => ({
...prev,
[raw.validator.validatorAddress]: raw.reward
.map<TokenWithValue>((coin) =>
coinToTokenWithValue(
coin.denom,
coin.amount,
assetInfos,
movePoolInfos
)
)
.sort(compareTokenWithValues),
}),
{}
);
data.totalRewards = accountDelegations.delegationRewards.total.reduce<
Record<string, TokenWithValue>
>(
(total, raw) => ({
...total,
[raw.denom]: coinToTokenWithValue(
raw.denom,
raw.amount,
assetInfos,
movePoolInfos
),
}),
{}
);

data.redelegations = accountDelegations.redelegations.map<Redelegation>(
(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<string, TokenWithValue>
>(
(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<Big>;

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<Big>,
isLoading: false,
};
};
5 changes: 2 additions & 3 deletions src/lib/pages/account-details/components/AccountHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ 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";

import { TotalAccountValue } from "./TotalAccountValue";

interface AccounHeaderProps {
accountData: Option<AccountData>;
accountAddress: BechAddr;
Expand Down Expand Up @@ -182,7 +181,7 @@ export const AccountHeader = observer(
)}
</Flex>
<Flex mt={{ base: 4, lg: 0 }} w={{ base: "full", lg: "auto" }}>
<TotalAccountValue accountAddress={accountAddress} />
<TotalValue address={accountAddress} />
</Flex>
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -38,7 +38,7 @@ export const DelegationsSection = ({
rewards,
redelegations,
totalCommission,
} = useUserDelegationInfos(address);
} = useAccountDelegationInfos(address);

useEffect(() => {
onClose();
Expand Down
Loading

2 comments on commit 8dfde92

@vercel
Copy link

@vercel vercel bot commented on 8dfde92 Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 8dfde92 Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.