diff --git a/apps/tangle-dapp/app/liquid-staking/overview/page.tsx b/apps/tangle-dapp/app/liquid-staking/overview/page.tsx deleted file mode 100644 index c2f9c8e9f..000000000 --- a/apps/tangle-dapp/app/liquid-staking/overview/page.tsx +++ /dev/null @@ -1,95 +0,0 @@ -'use client'; - -import { LsProtocolsTable, Typography } from '@webb-tools/webb-ui-components'; -import { FC, useEffect } from 'react'; - -import LsStakeCard from '../../../components/LiquidStaking/stakeAndUnstake/LsStakeCard'; -import LsUnstakeCard from '../../../components/LiquidStaking/stakeAndUnstake/LsUnstakeCard'; -import StatItem from '../../../components/StatItem'; -import { LsSearchParamKey } from '../../../constants/liquidStaking/types'; -import LsMyPoolsTable from '../../../containers/LsMyPoolsTable'; -import useNetworkStore from '../../../context/useNetworkStore'; -import { useLsStore } from '../../../data/liquidStaking/useLsStore'; -import useNetworkSwitcher from '../../../hooks/useNetworkSwitcher'; -import useSearchParamState from '../../../hooks/useSearchParamState'; -import getLsTangleNetwork from '../../../utils/liquidStaking/getLsTangleNetwork'; -import TabListItem from '../../restake/TabListItem'; -import TabsList from '../../restake/TabsList'; - -enum SearchParamAction { - STAKE = 'stake', - UNSTAKE = 'unstake', -} - -const LiquidStakingPage: FC = () => { - const [isStaking, setIsStaking] = useSearchParamState({ - defaultValue: true, - key: LsSearchParamKey.ACTION, - parser: (value) => value === SearchParamAction.STAKE, - stringify: (value) => - value ? SearchParamAction.STAKE : SearchParamAction.UNSTAKE, - }); - - const { selectedNetworkId } = useLsStore(); - const { network } = useNetworkStore(); - const { switchNetwork } = useNetworkSwitcher(); - - const lsTangleNetwork = getLsTangleNetwork(selectedNetworkId); - - // Sync the network with the selected liquid staking network on load. - // It might differ initially if the user navigates to the page and - // the active network differs from the default liquid staking network. - useEffect(() => { - if (lsTangleNetwork !== null && lsTangleNetwork.id !== network.id) { - switchNetwork(lsTangleNetwork, false); - } - }, [lsTangleNetwork, network.id, selectedNetworkId, switchNetwork]); - - return ( -
-
-
- - Tangle Liquid Staking - - - - Get Liquid Staking Tokens (LSTs) to earn & unleash restaking on - Tangle Mainnet via delegation. - -
- -
- -
-
- -
- - setIsStaking(true)}> - Stake - - - setIsStaking(false)} - > - Unstake - - - - {isStaking ? : } -
- - - - -
- ); -}; - -export default LiquidStakingPage; diff --git a/apps/tangle-dapp/app/liquid-staking/page.tsx b/apps/tangle-dapp/app/liquid-staking/page.tsx index 3f98607a4..e6870dea0 100644 --- a/apps/tangle-dapp/app/liquid-staking/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/page.tsx @@ -1,19 +1,25 @@ 'use client'; +import { + LsProtocolsTable, + TabContent, + TabsList as WebbTabsList, + TabsRoot, + TabTrigger, + Typography, +} from '@webb-tools/webb-ui-components'; import { FC, useEffect } from 'react'; -import { LsValidatorTable } from '../../components/LiquidStaking/LsValidatorTable'; import LsStakeCard from '../../components/LiquidStaking/stakeAndUnstake/LsStakeCard'; import LsUnstakeCard from '../../components/LiquidStaking/stakeAndUnstake/LsUnstakeCard'; -import UnstakeRequestsTable from '../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable'; +import StatItem from '../../components/StatItem'; import { LsSearchParamKey } from '../../constants/liquidStaking/types'; -import LsPoolsTable from '../../containers/LsPoolsTable'; +import LsMyPoolsTable from '../../containers/LsMyPoolsTable'; import useNetworkStore from '../../context/useNetworkStore'; import { useLsStore } from '../../data/liquidStaking/useLsStore'; import useNetworkSwitcher from '../../hooks/useNetworkSwitcher'; import useSearchParamState from '../../hooks/useSearchParamState'; import getLsTangleNetwork from '../../utils/liquidStaking/getLsTangleNetwork'; -import isLsParachainChainId from '../../utils/liquidStaking/isLsParachainChainId'; import TabListItem from '../restake/TabListItem'; import TabsList from '../restake/TabsList'; @@ -22,7 +28,12 @@ enum SearchParamAction { UNSTAKE = 'unstake', } -const LiquidStakingTokenPage: FC = () => { +enum Tab { + ALL_POOLS = 'All Pools', + MY_POOLS = 'My Pools', +} + +const LiquidStakingPage: FC = () => { const [isStaking, setIsStaking] = useSearchParamState({ defaultValue: true, key: LsSearchParamKey.ACTION, @@ -31,12 +42,11 @@ const LiquidStakingTokenPage: FC = () => { value ? SearchParamAction.STAKE : SearchParamAction.UNSTAKE, }); - const { selectedProtocolId, selectedNetworkId } = useLsStore(); + const { selectedNetworkId } = useLsStore(); const { network } = useNetworkStore(); const { switchNetwork } = useNetworkSwitcher(); const lsTangleNetwork = getLsTangleNetwork(selectedNetworkId); - const isParachainChain = isLsParachainChainId(selectedProtocolId); // Sync the network with the selected liquid staking network on load. // It might differ initially if the user navigates to the page and @@ -48,8 +58,29 @@ const LiquidStakingTokenPage: FC = () => { }, [lsTangleNetwork, network.id, selectedNetworkId, switchNetwork]); return ( -
-
+
+
+
+ + Tangle Liquid Staking + + + + Get Liquid Staking Tokens (LSTs) to earn & unleash restaking on + Tangle Mainnet via delegation. + +
+ +
+ +
+
+ +
setIsStaking(true)}> Stake @@ -66,19 +97,38 @@ const LiquidStakingTokenPage: FC = () => { {isStaking ? : }
-
- {isStaking ? ( - isParachainChain ? ( - - ) : ( - - ) - ) : ( - - )} -
+ +
+ {/* Tabs List on the left */} + + {Object.values(Tab).map((tab, idx) => { + return ( + + + {tab} + + + ); + })} + +
+ + {/* Tabs Content */} + + + + + + + +
); }; -export default LiquidStakingTokenPage; +export default LiquidStakingPage; diff --git a/apps/tangle-dapp/app/liquid-staking/useVaults.ts b/apps/tangle-dapp/app/liquid-staking/useVaults.ts deleted file mode 100644 index f30e4edee..000000000 --- a/apps/tangle-dapp/app/liquid-staking/useVaults.ts +++ /dev/null @@ -1,259 +0,0 @@ -'use client'; - -import { LiquidStakingToken } from '../../types/liquidStaking'; -import { Vault } from '../../types/liquidStaking'; - -export default function useVaults(): Vault[] { - return [ - { - lstToken: LiquidStakingToken.DOT, - name: 'Tangle Liquid Polkadot', - tvl: { - value: 2000, - valueInUSD: 20000, - }, - derivativeTokens: 5, - myStake: { - value: 1000, - valueInUSD: 10000, - }, - assets: [ - { - id: '31234', - token: 'tgDOT_A', - tvl: 5588.23, - apy: 10.12, - myStake: 10.12, - }, - { - id: '31235', - token: 'tgDOT_B', - tvl: 2044.12, - apy: 0, - myStake: 0, - }, - { - id: '31236', - token: 'tgDOT_C', - tvl: 123.12, - apy: 16, - myStake: 16, - }, - { - id: '31237', - token: 'tgDOT_D', - tvl: 6938.87, - apy: 100, - myStake: 100, - }, - { - id: '31238', - token: 'tgDOT_E', - tvl: 0, - apy: 0, - myStake: 0, - }, - ], - }, - { - lstToken: LiquidStakingToken.ASTR, - name: 'Tangle Liquid Astar', - tvl: { - value: 48, - valueInUSD: 480, - }, - derivativeTokens: 10, - myStake: { - value: 23.34, - valueInUSD: 233.4, - }, - assets: [ - { - id: '31234', - token: 'tgDOT_A', - tvl: 5588.23, - apy: 10.12, - myStake: 10.12, - }, - { - id: '31235', - token: 'tgDOT_B', - tvl: 2044.12, - apy: 0, - myStake: 0, - }, - { - id: '31236', - token: 'tgDOT_C', - tvl: 123.12, - apy: 16, - myStake: 16, - }, - { - id: '31237', - token: 'tgDOT_D', - tvl: 6938.87, - apy: 100, - myStake: 100, - }, - { - id: '31238', - token: 'tgDOT_E', - tvl: 0, - apy: 0, - myStake: 0, - }, - ], - }, - { - lstToken: LiquidStakingToken.PHA, - name: 'Tangle Liquid Phala', - tvl: { - value: 60.13, - valueInUSD: 601.3, - }, - derivativeTokens: 7, - myStake: { - value: 50, - valueInUSD: 500, - }, - assets: [ - { - id: '31234', - token: 'tgDOT_A', - tvl: 5588.23, - apy: 10.12, - myStake: 10.12, - }, - { - id: '31235', - token: 'tgDOT_B', - tvl: 2044.12, - apy: 0, - myStake: 0, - }, - { - id: '31236', - token: 'tgDOT_C', - tvl: 123.12, - apy: 16, - myStake: 16, - }, - { - id: '31237', - token: 'tgDOT_D', - tvl: 6938.87, - apy: 100, - myStake: 100, - }, - { - id: '31238', - token: 'tgDOT_E', - tvl: 0, - apy: 0, - myStake: 0, - }, - ], - }, - { - lstToken: LiquidStakingToken.GLMR, - name: 'Tangle Liquid Glimmer', - tvl: { - value: 0, - valueInUSD: 0, - }, - derivativeTokens: 0, - myStake: { - value: 0, - valueInUSD: 0, - }, - assets: [ - { - id: '31234', - token: 'tgDOT_A', - tvl: 5588.23, - apy: 10.12, - myStake: 10.12, - }, - { - id: '31235', - token: 'tgDOT_B', - tvl: 2044.12, - apy: 0, - myStake: 0, - }, - { - id: '31236', - token: 'tgDOT_C', - tvl: 123.12, - apy: 16, - myStake: 16, - }, - { - id: '31237', - token: 'tgDOT_D', - tvl: 6938.87, - apy: 100, - myStake: 100, - }, - { - id: '31238', - token: 'tgDOT_E', - tvl: 0, - apy: 0, - myStake: 0, - }, - ], - }, - { - lstToken: LiquidStakingToken.MANTA, - name: 'Tangle Liquid Manta', - tvl: { - value: 0, - valueInUSD: 0, - }, - derivativeTokens: 0, - myStake: { - value: 0, - valueInUSD: 0, - }, - assets: [ - { - id: '31234', - token: 'tgDOT_A', - tvl: 5588.23, - apy: 10.12, - myStake: 10.12, - }, - { - id: '31235', - token: 'tgDOT_B', - tvl: 2044.12, - apy: 0, - myStake: 0, - }, - { - id: '31236', - token: 'tgDOT_C', - tvl: 123.12, - apy: 16, - myStake: 16, - }, - { - id: '31237', - token: 'tgDOT_D', - tvl: 6938.87, - apy: 100, - myStake: 100, - }, - { - id: '31238', - token: 'tgDOT_E', - tvl: 0, - apy: 0, - myStake: 0, - }, - ], - }, - ]; -} diff --git a/apps/tangle-dapp/components/Breadcrumbs/utils.tsx b/apps/tangle-dapp/components/Breadcrumbs/utils.tsx index 49f1c4776..c104c9f7c 100644 --- a/apps/tangle-dapp/components/Breadcrumbs/utils.tsx +++ b/apps/tangle-dapp/components/Breadcrumbs/utils.tsx @@ -29,7 +29,6 @@ const BREADCRUMB_ICONS: Record JSX.Element> = { [PagePath.RESTAKE_OPERATOR]: TokenSwapFill, [PagePath.BRIDGE]: ShuffleLine, [PagePath.LIQUID_STAKING]: WaterDropletIcon, - [PagePath.LIQUID_STAKING_OVERVIEW]: WaterDropletIcon, }; const BREADCRUMB_LABELS: Partial> = { diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsInput.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsInput.tsx index a6b04a836..25827ef7f 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsInput.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsInput.tsx @@ -16,6 +16,7 @@ import useInputAmount from '../../../hooks/useInputAmount'; import formatBn from '../../../utils/formatBn'; import NetworkSelector from './NetworkSelector'; import ProtocolSelector from './ProtocolSelector'; +import SelectedPoolIndicator from './SelectedPoolIndicator'; export type LsInputProps = { id: string; @@ -132,6 +133,8 @@ const LsInput: FC = ({ setProtocolId={setProtocolId} isDerivativeVariant={isDerivativeVariant} /> + +
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsStakeCard.tsx index 0d219a35a..b7209e825 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsStakeCard.tsx @@ -27,6 +27,7 @@ import useLsPoolJoinTx from '../../../data/liquidStaking/tangle/useLsPoolJoinTx' import useLsExchangeRate, { ExchangeRateType, } from '../../../data/liquidStaking/useLsExchangeRate'; +import useLsPoolMembers from '../../../data/liquidStaking/useLsPoolMembers'; import { useLsStore } from '../../../data/liquidStaking/useLsStore'; import useLiquifierDeposit from '../../../data/liquifier/useLiquifierDeposit'; import useActiveAccountAddress from '../../../hooks/useActiveAccountAddress'; @@ -76,6 +77,22 @@ const LsStakeCard: FC = () => { const selectedProtocol = getLsProtocolDef(selectedProtocolId); const tryChangeNetwork = useLsChangeNetwork(); + const lsPoolMembers = useLsPoolMembers(); + + const actionText = useMemo(() => { + const defaultText = 'Stake'; + + if (lsPoolMembers === null) { + return defaultText; + } + + const isMember = lsPoolMembers.some( + ([poolId, accountAddress]) => + poolId === selectedPoolId && accountAddress === activeAccountAddress, + ); + + return isMember ? 'Increase Stake' : defaultText; + }, [activeAccountAddress, lsPoolMembers, selectedPoolId]); const isTangleNetwork = selectedNetworkId === LsNetworkId.TANGLE_LOCAL || @@ -257,7 +274,7 @@ const LsStakeCard: FC = () => { onClick={handleStakeClick} isFullWidth > - Stake + {actionText} ); diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectedPoolIndicator.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectedPoolIndicator.tsx new file mode 100644 index 000000000..a83e43b2f --- /dev/null +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectedPoolIndicator.tsx @@ -0,0 +1,39 @@ +import { Typography } from '@webb-tools/webb-ui-components'; +import { FC, useMemo } from 'react'; + +import useLsPools from '../../../data/liquidStaking/useLsPools'; +import { useLsStore } from '../../../data/liquidStaking/useLsStore'; +import getLsProtocolDef from '../../../utils/liquidStaking/getLsProtocolDef'; +import LsTokenIcon from '../../LsTokenIcon'; + +const SelectedPoolIndicator: FC = () => { + const { selectedPoolId, selectedProtocolId } = useLsStore(); + const lsPools = useLsPools(); + const selectedProtocol = getLsProtocolDef(selectedProtocolId); + + const selectedPool = useMemo(() => { + if (!(lsPools instanceof Map) || selectedPoolId === null) { + return null; + } + + return lsPools.get(selectedPoolId) ?? null; + }, [lsPools, selectedPoolId]); + + return ( +
+ + + {selectedPool === null ? ( + + Select a pool + + ) : ( + + {selectedPool.metadata}#{selectedPool.id} + + )} +
+ ); +}; + +export default SelectedPoolIndicator; diff --git a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx index d52de5717..7f238e267 100644 --- a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx +++ b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx @@ -60,9 +60,7 @@ const NetworkSelectionButton: FC = () => { // Disable network switching when in Liquid Staking page, // since it would have no effect there. - const isInLiquidStakingPage = - pathname.startsWith(PagePath.LIQUID_STAKING) && - !pathname.startsWith(PagePath.LIQUID_STAKING_OVERVIEW); + const isInLiquidStakingPage = pathname.startsWith(PagePath.LIQUID_STAKING); const isInBridgePath = useMemo( () => pathname.startsWith(PagePath.BRIDGE), diff --git a/apps/tangle-dapp/components/Sidebar/sidebarProps.ts b/apps/tangle-dapp/components/Sidebar/sidebarProps.ts index e58ad1e61..9ef5f1aa9 100644 --- a/apps/tangle-dapp/components/Sidebar/sidebarProps.ts +++ b/apps/tangle-dapp/components/Sidebar/sidebarProps.ts @@ -75,7 +75,7 @@ const SIDEBAR_STATIC_ITEMS: SideBarItemProps[] = [ }, { name: 'Liquid Stake', - href: PagePath.LIQUID_STAKING_OVERVIEW, + href: PagePath.LIQUID_STAKING, environments: ['development', 'staging', 'test'], isInternal: true, isNext: true, diff --git a/apps/tangle-dapp/containers/LsMyPoolsTable.tsx b/apps/tangle-dapp/containers/LsMyPoolsTable.tsx index 5dda1b769..2283759ae 100644 --- a/apps/tangle-dapp/containers/LsMyPoolsTable.tsx +++ b/apps/tangle-dapp/containers/LsMyPoolsTable.tsx @@ -205,44 +205,33 @@ const LsMyPoolsTable: FC = () => { enableSortingRemoval: false, }); - // Don't render if the user is not involved in any pools. - if (rows.length === 0) { - return; - } - return ( -
- - Your Pools - - - -
- - - 1)} - className="border-t-0 py-5" - /> - - - + +
+
+ + 1)} + className="border-t-0 py-5" + /> + + ); }; diff --git a/apps/tangle-dapp/containers/LsPoolsTable2/LsPoolsTable2.tsx b/apps/tangle-dapp/containers/LsPoolsTable2/LsPoolsTable2.tsx index 54dbc7fea..665df0f74 100644 --- a/apps/tangle-dapp/containers/LsPoolsTable2/LsPoolsTable2.tsx +++ b/apps/tangle-dapp/containers/LsPoolsTable2/LsPoolsTable2.tsx @@ -23,6 +23,7 @@ import TokenAmountCell from '../../components/tableCells/TokenAmountCell'; import pluralize from '../../utils/pluralize'; import { EMPTY_VALUE_PLACEHOLDER } from '../../constants'; import { ArrowRight } from '@webb-tools/icons'; +import { useLsStore } from '../../data/liquidStaking/useLsStore'; export interface LsPoolsTable2Props { pools: LsPool[]; @@ -31,79 +32,6 @@ export interface LsPoolsTable2Props { const COLUMN_HELPER = createColumnHelper(); -const POOL_COLUMNS = [ - COLUMN_HELPER.accessor('id', { - header: () => 'Name/id', - cell: (props) => ( - - {props.row.original.metadata}#{props.getValue()} - - ), - }), - COLUMN_HELPER.accessor('token', { - header: () => 'Token', - cell: (props) => ( - - {props.getValue()} - - ), - }), - COLUMN_HELPER.accessor('ownerAddress', { - header: () => 'Owner', - cell: (props) => ( - - ), - }), - COLUMN_HELPER.accessor('totalStaked', { - header: () => 'TVL', - // TODO: Decimals. - cell: (props) => , - }), - COLUMN_HELPER.accessor('apyPercentage', { - header: () => 'APY', - cell: (props) => { - const apy = props.getValue(); - - if (apy === undefined) { - return EMPTY_VALUE_PLACEHOLDER; - } - - return ( - - {getRoundedAmountString(props.getValue()) + '%'} - - ); - }, - }), - COLUMN_HELPER.display({ - id: 'actions', - header: () => 'Actions', - cell: (props) => ( -
- -
- ), - }), -]; - const LsPoolsTable2: FC = ({ pools, isShown }) => { const [sorting, setSorting] = useState([]); @@ -112,6 +40,8 @@ const LsPoolsTable2: FC = ({ pools, isShown }) => { pageSize: 5, }); + const { selectedPoolId, setSelectedPoolId } = useLsStore(); + const pagination = useMemo( () => ({ pageIndex, @@ -120,9 +50,87 @@ const LsPoolsTable2: FC = ({ pools, isShown }) => { [pageIndex, pageSize], ); + const columns = [ + COLUMN_HELPER.accessor('id', { + header: () => 'Name/id', + cell: (props) => ( + + {props.row.original.metadata}#{props.getValue()} + + ), + }), + COLUMN_HELPER.accessor('token', { + header: () => 'Token', + cell: (props) => ( + + {props.getValue()} + + ), + }), + COLUMN_HELPER.accessor('ownerAddress', { + header: () => 'Owner', + cell: (props) => ( + + ), + }), + COLUMN_HELPER.accessor('totalStaked', { + header: () => 'TVL', + // TODO: Decimals. + cell: (props) => , + }), + COLUMN_HELPER.accessor('apyPercentage', { + header: () => 'APY', + cell: (props) => { + const apy = props.getValue(); + + if (apy === undefined) { + return EMPTY_VALUE_PLACEHOLDER; + } + + return ( + + {getRoundedAmountString(props.getValue()) + '%'} + + ); + }, + }), + COLUMN_HELPER.display({ + id: 'actions', + cell: (props) => ( +
+ +
+ ), + }), + ]; + const table = useReactTable({ data: pools, - columns: POOL_COLUMNS, + columns: columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), diff --git a/apps/tangle-dapp/containers/LsPoolsTable2/LsProtocolsTable.tsx b/apps/tangle-dapp/containers/LsPoolsTable2/LsProtocolsTable.tsx index deb583576..8c149cd35 100644 --- a/apps/tangle-dapp/containers/LsPoolsTable2/LsProtocolsTable.tsx +++ b/apps/tangle-dapp/containers/LsPoolsTable2/LsProtocolsTable.tsx @@ -164,27 +164,21 @@ function LsProtocolsTable({ initialSorting = [] }: LsProtocolsTableProps) { ); return ( -
- - Liquid Staking Protocols - - -
- +
); } diff --git a/apps/tangle-dapp/types/index.ts b/apps/tangle-dapp/types/index.ts index dd77219e1..49e9ced5d 100755 --- a/apps/tangle-dapp/types/index.ts +++ b/apps/tangle-dapp/types/index.ts @@ -18,7 +18,6 @@ export enum PagePath { RESTAKE_STAKE = '/restake/stake', RESTAKE_OPERATOR = '/restake/operators', LIQUID_STAKING = '/liquid-staking', - LIQUID_STAKING_OVERVIEW = '/liquid-staking/overview', } export enum QueryParamKey {