diff --git a/apps/tangle-dapp/src/containers/RestakeBalancesTable.tsx b/apps/tangle-dapp/src/containers/AssetsAndBalancesTable.tsx similarity index 78% rename from apps/tangle-dapp/src/containers/RestakeBalancesTable.tsx rename to apps/tangle-dapp/src/containers/AssetsAndBalancesTable.tsx index 4277527e2e..4d8513b180 100644 --- a/apps/tangle-dapp/src/containers/RestakeBalancesTable.tsx +++ b/apps/tangle-dapp/src/containers/AssetsAndBalancesTable.tsx @@ -20,7 +20,7 @@ import { Typography, } from '@webb-tools/webb-ui-components'; import { TableVariant } from '@webb-tools/webb-ui-components/components/Table/types'; -import { FC, useMemo, useState } from 'react'; +import { FC, useCallback, useMemo, useState } from 'react'; import LsTokenIcon from '../components/LsTokenIcon'; import StatItem from '../components/StatItem'; @@ -39,10 +39,18 @@ import { TableStatus } from '../components'; import useRestakeRewardConfig from '../data/restake/useRestakeRewardConfig'; import useRestakeDelegatorInfo from '../data/restake/useRestakeDelegatorInfo'; import useIsAccountConnected from '../hooks/useIsAccountConnected'; - -export type RestakeBalanceRow = { - name: string; - token: string; +import useLsPools from '../data/liquidStaking/useLsPools'; +import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config'; + +enum RowType { + ASSET, + LS_POOL, +} + +type Row = { + type: RowType; + name?: string; + tokenSymbol: string; tvl: BN; tvlInUsd?: number; available: BN; @@ -55,31 +63,35 @@ export type RestakeBalanceRow = { apyFractional?: number; }; -const COLUMN_HELPER = createColumnHelper(); +const COLUMN_HELPER = createColumnHelper(); const COLUMNS = [ - COLUMN_HELPER.accessor('name', { + COLUMN_HELPER.accessor('tokenSymbol', { header: () => 'Asset', sortDescFirst: true, - sortingFn: sortByLocaleCompare((row) => row.name), - cell: (props) => ( + sortingFn: sortByLocaleCompare((row) => row.name ?? row.tokenSymbol), + cell: (props) => { + const name = props.row.original.name; +
- - {props.getValue()} - + {name !== undefined && ( + + {name} + + )} - {props.row.original.token} + {props.getValue()}
-
- ), + ; + }, }), COLUMN_HELPER.accessor('available', { header: () => 'Available', @@ -98,7 +110,7 @@ const COLUMNS = [ return ( @@ -123,7 +135,7 @@ const COLUMNS = [ return ( @@ -150,7 +162,7 @@ const COLUMNS = [ return ( @@ -236,27 +248,21 @@ const COLUMNS = [ }), ]; -const RestakeBalancesTable: FC = () => { +const AssetsAndBalancesTable: FC = () => { const [sorting, setSorting] = useState([]); const { balances } = useRestakeBalances(); const { assetMap } = useRestakeAssetMap(); const { rewardConfig } = useRestakeRewardConfig(); const { delegatorInfo } = useRestakeDelegatorInfo(); + const allPools = useLsPools(); const isAccountConnected = useIsAccountConnected(); - const restakeBalanceRows = useMemo(() => { - return Object.entries(balances).flatMap(([assetId, balance]) => { - const assetDetails: (typeof assetMap)[string] | undefined = - assetMap[assetId]; - - if (assetDetails === undefined) { - return []; - } - + const getTotalLockedInAsset = useCallback( + (assetId: number) => { const deposited = delegatorInfo?.deposits[assetId].amount; const delegated = delegatorInfo?.delegations.find((delegation) => { - return delegation.assetId === assetId; + return delegation.assetId === assetId.toString(); }); const depositedBn = @@ -267,34 +273,66 @@ const RestakeBalancesTable: FC = () => { ? BN_ZERO : new BN(delegated.amountBonded.toString()); + return depositedBn.add(delegatedBn); + }, + [delegatorInfo?.delegations, delegatorInfo?.deposits], + ); + + const assetRows = useMemo(() => { + return Object.entries(balances).flatMap(([assetId, balance]) => { + const assetDetails: (typeof assetMap)[string] | undefined = + assetMap[assetId]; + + if (assetDetails === undefined) { + return []; + } + return { + type: RowType.ASSET, name: assetDetails.name, // TODO: Calculate by issuance of asset. tvl: BN_ZERO, available: new BN(balance.balance.toString()), - // TODO: Calculate by deposit amount in restaking of asset. - locked: delegatedBn.add(depositedBn), + locked: getTotalLockedInAsset(parseInt(assetId)), // TODO: This won't work because reward config is PER VAULT not PER ASSET. But isn't each asset its own vault? apyFractional: rewardConfig.configs[assetId]?.apy, - token: assetDetails.symbol, + tokenSymbol: assetDetails.symbol, iconName: 'tnt', decimals: assetDetails.decimals, - } as RestakeBalanceRow; + } satisfies Row; }); - }, [ - assetMap, - balances, - delegatorInfo?.delegations, - delegatorInfo?.deposits, - rewardConfig.configs, - ]); - - const rows = useMemo(() => { + }, [assetMap, balances, getTotalLockedInAsset, rewardConfig.configs]); + + const lsPoolRows = useMemo(() => { + if (!(allPools instanceof Map)) { + return []; + } + + const pools = Array.from(allPools.values()); + + return pools.map((pool) => { + const tokenSymbol = `${pool.name ?? 'Pool'}#${pool.id}`.toUpperCase(); + + return { + type: RowType.LS_POOL, + tokenSymbol, + tvl: pool.totalStaked, + // TODO: Should be 'my stake'. + available: pool.totalStaked, + locked: getTotalLockedInAsset(pool.id), + iconName: 'tnt', + decimals: TANGLE_TOKEN_DECIMALS, + apyFractional: pool.apyPercentage, + } satisfies Row; + }); + }, [allPools, getTotalLockedInAsset]); + + const rows = useMemo(() => { // Sort by highest available balance (descending). - return [...restakeBalanceRows].sort((a, b) => { + return [...assetRows, ...lsPoolRows].sort((a, b) => { return b.available.cmp(a.available); }); - }, [restakeBalanceRows]); + }, [assetRows, lsPoolRows]); const table = useReactTable({ data: rows, @@ -330,4 +368,4 @@ const RestakeBalancesTable: FC = () => { return ; }; -export default RestakeBalancesTable; +export default AssetsAndBalancesTable; diff --git a/apps/tangle-dapp/src/pages/account.tsx b/apps/tangle-dapp/src/pages/account.tsx index eeb33c6268..7aec83058d 100644 --- a/apps/tangle-dapp/src/pages/account.tsx +++ b/apps/tangle-dapp/src/pages/account.tsx @@ -2,7 +2,7 @@ import { Typography } from '@webb-tools/webb-ui-components/typography/Typography import { FC } from 'react'; import AccountSummaryCard from '../components/account/AccountSummaryCard'; -import RestakeBalancesTable from '../containers/RestakeBalancesTable'; +import AssetsAndBalancesTable from '../containers/AssetsAndBalancesTable'; import PointsReminder from '../components/account/PointsReminder'; const AccountPage: FC = () => { @@ -18,7 +18,7 @@ const AccountPage: FC = () => { Assets & Balances - + ); }; diff --git a/apps/tangle-dapp/src/pages/liquid-staking/index.tsx b/apps/tangle-dapp/src/pages/liquid-staking/index.tsx index cebda98494..1f05d723ae 100644 --- a/apps/tangle-dapp/src/pages/liquid-staking/index.tsx +++ b/apps/tangle-dapp/src/pages/liquid-staking/index.tsx @@ -65,8 +65,6 @@ const LiquidStakingPage: FC = () => { setIsStakingInStore(isStaking); }, [isStaking, setIsStakingInStore]); - // Sync the URL param state of whether liquid staking or unstaking with - // the Zustand store state. useEffect(() => { setIsStaking(isStakingInStore); }, [isStakingInStore, setIsStaking]);