diff --git a/apps/tangle-dapp/app/liquid-staking/page.tsx b/apps/tangle-dapp/app/liquid-staking/page.tsx
index d6812958a..3f98607a4 100644
--- a/apps/tangle-dapp/app/liquid-staking/page.tsx
+++ b/apps/tangle-dapp/app/liquid-staking/page.tsx
@@ -1,6 +1,6 @@
'use client';
-import { FC } from 'react';
+import { FC, useEffect } from 'react';
import { LsValidatorTable } from '../../components/LiquidStaking/LsValidatorTable';
import LsStakeCard from '../../components/LiquidStaking/stakeAndUnstake/LsStakeCard';
@@ -8,8 +8,11 @@ import LsUnstakeCard from '../../components/LiquidStaking/stakeAndUnstake/LsUnst
import UnstakeRequestsTable from '../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable';
import { LsSearchParamKey } from '../../constants/liquidStaking/types';
import LsPoolsTable from '../../containers/LsPoolsTable';
+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';
@@ -28,10 +31,22 @@ const LiquidStakingTokenPage: FC = () => {
value ? SearchParamAction.STAKE : SearchParamAction.UNSTAKE,
});
- const { selectedProtocolId } = useLsStore();
+ const { selectedProtocolId, 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
+ // 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 (
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx
index 6ebb815d8..fb1083c54 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx
@@ -3,7 +3,7 @@ import { FC } from 'react';
import { twMerge } from 'tailwind-merge';
import { LS_DERIVATIVE_TOKEN_PREFIX } from '../../../constants/liquidStaking/constants';
-import { LsProtocolId, LsToken } from '../../../constants/liquidStaking/types';
+import { LsToken } from '../../../constants/liquidStaking/types';
import { ExchangeRateType } from '../../../data/liquidStaking/useLsExchangeRate';
import useLsExchangeRate from '../../../data/liquidStaking/useLsExchangeRate';
import DetailItem from './DetailItem';
@@ -11,15 +11,13 @@ import DetailItem from './DetailItem';
export type ExchangeRateDetailItemProps = {
type: ExchangeRateType;
token: LsToken;
- protocolId: LsProtocolId;
};
const ExchangeRateDetailItem: FC
= ({
type,
token,
- protocolId,
}) => {
- const { exchangeRate, isRefreshing } = useLsExchangeRate(type, protocolId);
+ const { exchangeRate, isRefreshing } = useLsExchangeRate(type);
const exchangeRateElement =
exchangeRate instanceof Error ? (
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/FeeDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/FeeDetailItem.tsx
index 1385ae19a..7e1488ae7 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/FeeDetailItem.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/FeeDetailItem.tsx
@@ -1,6 +1,7 @@
import { BN, BN_ZERO } from '@polkadot/util';
import { FC, useMemo } from 'react';
+import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants';
import { LsProtocolId } from '../../../constants/liquidStaking/types';
import formatBn from '../../../utils/formatBn';
import getLsProtocolDef from '../../../utils/liquidStaking/getLsProtocolDef';
@@ -36,6 +37,8 @@ const FeeDetailItem: FC = ({
// Propagate error or loading state.
if (!(feeAmount instanceof BN)) {
return feeAmount;
+ } else if (feeAmount.isZero()) {
+ return EMPTY_VALUE_PLACEHOLDER;
}
const formattedAmount = formatBn(feeAmount, protocol.decimals, {
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsAgnosticBalance.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsAgnosticBalance.tsx
index 8e185181c..58d11a69e 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsAgnosticBalance.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsAgnosticBalance.tsx
@@ -13,17 +13,14 @@ import { twMerge } from 'tailwind-merge';
import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants';
import { LS_DERIVATIVE_TOKEN_PREFIX } from '../../../constants/liquidStaking/constants';
-import {
- LsNetworkId,
- LsProtocolId,
-} from '../../../constants/liquidStaking/types';
+import { LsNetworkId } from '../../../constants/liquidStaking/types';
+import { useLsStore } from '../../../data/liquidStaking/useLsStore';
import formatBn from '../../../utils/formatBn';
import getLsProtocolDef from '../../../utils/liquidStaking/getLsProtocolDef';
import useLsAgnosticBalance from './useLsAgnosticBalance';
export type LsAgnosticBalanceProps = {
isNative?: boolean;
- protocolId: LsProtocolId;
tooltip?: string;
onlyShowTooltipWhenBalanceIsSet?: boolean;
onClick?: () => void;
@@ -31,14 +28,14 @@ export type LsAgnosticBalanceProps = {
const LsAgnosticBalance: FC = ({
isNative = true,
- protocolId,
tooltip,
onlyShowTooltipWhenBalanceIsSet = true,
onClick,
}) => {
const [isHovering, setIsHovering] = useState(false);
- const { balance, isRefreshing } = useLsAgnosticBalance(isNative, protocolId);
- const protocol = getLsProtocolDef(protocolId);
+ const { balance, isRefreshing } = useLsAgnosticBalance(isNative);
+ const { selectedProtocolId } = useLsStore();
+ const protocol = getLsProtocolDef(selectedProtocolId);
// Special case for liquid tokens on the `TgToken.sol` contract.
// See: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/src/lst/liquidtoken/TgToken.sol#L26
@@ -58,7 +55,6 @@ const LsAgnosticBalance: FC = ({
}
const formattedBalance = formatBn(balance, decimals, {
- fractionMaxLength: undefined,
includeCommas: true,
});
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsStakeCard.tsx
index 21e7b9cdf..35437dba8 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsStakeCard.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsStakeCard.tsx
@@ -13,7 +13,7 @@ import {
Input,
Typography,
} from '@webb-tools/webb-ui-components';
-import React, { FC, useCallback, useMemo } from 'react';
+import React, { FC, useCallback, useEffect, useMemo } from 'react';
import { z } from 'zod';
import { LS_DERIVATIVE_TOKEN_PREFIX } from '../../../constants/liquidStaking/constants';
@@ -22,11 +22,12 @@ import {
LsProtocolId,
LsSearchParamKey,
} from '../../../constants/liquidStaking/types';
+import useMintTx from '../../../data/liquidStaking/parachain/useMintTx';
+import useLsPoolJoinTx from '../../../data/liquidStaking/tangle/useLsPoolJoinTx';
import useLsExchangeRate, {
ExchangeRateType,
} from '../../../data/liquidStaking/useLsExchangeRate';
import { useLsStore } from '../../../data/liquidStaking/useLsStore';
-import useMintTx from '../../../data/liquidStaking/useMintTx';
import useLiquifierDeposit from '../../../data/liquifier/useLiquifierDeposit';
import useActiveAccountAddress from '../../../hooks/useActiveAccountAddress';
import useSearchParamState from '../../../hooks/useSearchParamState';
@@ -40,6 +41,7 @@ import LsFeeWarning from './LsFeeWarning';
import LsInput from './LsInput';
import TotalDetailItem from './TotalDetailItem';
import UnstakePeriodDetailItem from './UnstakePeriodDetailItem';
+import useLsChangeNetwork from './useLsChangeNetwork';
import useLsSpendingLimits from './useLsSpendingLimits';
const LsStakeCard: FC = () => {
@@ -54,10 +56,15 @@ const LsStakeCard: FC = () => {
selectedProtocolId,
setSelectedProtocolId,
selectedNetworkId,
- setSelectedNetworkId,
+ selectedPoolId,
} = useLsStore();
- const { execute: executeMintTx, status: mintTxStatus } = useMintTx();
+ const { execute: executeTanglePoolJoinTx, status: tanglePoolJoinTxStatus } =
+ useLsPoolJoinTx();
+
+ const { execute: executeParachainMintTx, status: parachainMintTxStatus } =
+ useMintTx();
+
const performLiquifierDeposit = useLiquifierDeposit();
const activeAccountAddress = useActiveAccountAddress();
@@ -67,6 +74,12 @@ const LsStakeCard: FC = () => {
);
const selectedProtocol = getLsProtocolDef(selectedProtocolId);
+ const tryChangeNetwork = useLsChangeNetwork();
+
+ const isTangleNetwork =
+ selectedNetworkId === LsNetworkId.TANGLE_LOCAL ||
+ selectedNetworkId === LsNetworkId.TANGLE_MAINNET ||
+ selectedNetworkId === LsNetworkId.TANGLE_TESTNET;
// TODO: Not loading the correct protocol for: '?amount=123000000000000000000&protocol=7&network=1&action=stake'. When network=1, it switches to protocol=5 on load. Could this be because the protocol is reset to its default once the network is switched?
useSearchParamSync({
@@ -82,16 +95,13 @@ const LsStakeCard: FC = () => {
value: selectedNetworkId,
parse: (value) => z.nativeEnum(LsNetworkId).parse(parseInt(value)),
stringify: (value) => value.toString(),
- setValue: setSelectedNetworkId,
+ setValue: tryChangeNetwork,
});
const {
exchangeRate: exchangeRateOrError,
isRefreshing: isRefreshingExchangeRate,
- } = useLsExchangeRate(
- ExchangeRateType.NativeToDerivative,
- selectedProtocolId,
- );
+ } = useLsExchangeRate(ExchangeRateType.NativeToDerivative);
// TODO: Properly handle the error state.
const exchangeRate =
@@ -105,9 +115,9 @@ const LsStakeCard: FC = () => {
if (
selectedProtocol.networkId === LsNetworkId.TANGLE_RESTAKING_PARACHAIN &&
- executeMintTx !== null
+ executeParachainMintTx !== null
) {
- executeMintTx({
+ executeParachainMintTx({
amount: fromAmount,
currency: selectedProtocol.currency,
});
@@ -116,8 +126,25 @@ const LsStakeCard: FC = () => {
performLiquifierDeposit !== null
) {
await performLiquifierDeposit(selectedProtocol.id, fromAmount);
+ } else if (
+ isTangleNetwork &&
+ executeTanglePoolJoinTx !== null &&
+ selectedPoolId !== null
+ ) {
+ executeTanglePoolJoinTx({
+ amount: fromAmount,
+ poolId: selectedPoolId,
+ });
}
- }, [executeMintTx, fromAmount, performLiquifierDeposit, selectedProtocol]);
+ }, [
+ executeParachainMintTx,
+ executeTanglePoolJoinTx,
+ fromAmount,
+ isTangleNetwork,
+ performLiquifierDeposit,
+ selectedProtocol,
+ selectedPoolId,
+ ]);
const toAmount = useMemo(() => {
if (fromAmount === null || exchangeRate === null) {
@@ -130,13 +157,15 @@ const LsStakeCard: FC = () => {
const canCallStake =
(fromAmount !== null &&
selectedProtocol.networkId === LsNetworkId.TANGLE_RESTAKING_PARACHAIN &&
- executeMintTx !== null) ||
+ executeParachainMintTx !== null) ||
(selectedProtocol.networkId === LsNetworkId.ETHEREUM_MAINNET_LIQUIFIER &&
- performLiquifierDeposit !== null);
+ performLiquifierDeposit !== null) ||
+ (isTangleNetwork &&
+ executeTanglePoolJoinTx !== null &&
+ selectedPoolId !== null);
const walletBalance = (
{
if (maxSpendable !== null) {
@@ -146,6 +175,11 @@ const LsStakeCard: FC = () => {
/>
);
+ // Reset the input amount when the network changes.
+ useEffect(() => {
+ setFromAmount(null);
+ }, [setFromAmount, selectedNetworkId]);
+
return (
<>
{
setProtocolId={setSelectedProtocolId}
minAmount={minSpendable ?? undefined}
maxAmount={maxSpendable ?? undefined}
- setNetworkId={setSelectedNetworkId}
+ setNetworkId={tryChangeNetwork}
/>
@@ -184,7 +218,6 @@ const LsStakeCard: FC = () => {
@@ -213,7 +246,10 @@ const LsStakeCard: FC = () => {
fromAmount === null ||
fromAmount.isZero()
}
- isLoading={mintTxStatus === TxStatus.PROCESSING}
+ isLoading={
+ parachainMintTxStatus === TxStatus.PROCESSING ||
+ tanglePoolJoinTxStatus === TxStatus.PROCESSING
+ }
loadingText="Processing"
onClick={handleStakeClick}
isFullWidth
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsUnstakeCard.tsx
index c6670437f..2afd1a6b2 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsUnstakeCard.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LsUnstakeCard.tsx
@@ -16,11 +16,12 @@ import {
LsProtocolId,
LsSearchParamKey,
} from '../../../constants/liquidStaking/types';
+import useRedeemTx from '../../../data/liquidStaking/parachain/useRedeemTx';
+import useLsPoolUnbondTx from '../../../data/liquidStaking/tangle/useLsPoolUnbondTx';
import useLsExchangeRate, {
ExchangeRateType,
} from '../../../data/liquidStaking/useLsExchangeRate';
import { useLsStore } from '../../../data/liquidStaking/useLsStore';
-import useRedeemTx from '../../../data/liquidStaking/useRedeemTx';
import useLiquifierUnlock from '../../../data/liquifier/useLiquifierUnlock';
import useActiveAccountAddress from '../../../hooks/useActiveAccountAddress';
import useSearchParamSync from '../../../hooks/useSearchParamSync';
@@ -34,27 +35,22 @@ import LsInput from './LsInput';
import SelectTokenModal from './SelectTokenModal';
import TotalDetailItem from './TotalDetailItem';
import UnstakePeriodDetailItem from './UnstakePeriodDetailItem';
-import UnstakeRequestSubmittedModal from './UnstakeRequestSubmittedModal';
+import useLsChangeNetwork from './useLsChangeNetwork';
import useLsSpendingLimits from './useLsSpendingLimits';
const LsUnstakeCard: FC = () => {
const [isSelectTokenModalOpen, setIsSelectTokenModalOpen] = useState(false);
const [fromAmount, setFromAmount] = useState(null);
const activeAccountAddress = useActiveAccountAddress();
+ const tryChangeNetwork = useLsChangeNetwork();
const {
selectedProtocolId,
setSelectedProtocolId,
selectedNetworkId,
- setSelectedNetworkId,
+ selectedPoolId,
} = useLsStore();
- const [didLiquifierUnlockSucceed, setDidLiquifierUnlockSucceed] =
- useState(false);
-
- const [isRequestSubmittedModalOpen, setIsRequestSubmittedModalOpen] =
- useState(false);
-
// TODO: Won't both of these hooks be attempting to update the same state?
useSearchParamSync({
key: LsSearchParamKey.PROTOCOL_ID,
@@ -64,11 +60,11 @@ const LsUnstakeCard: FC = () => {
setValue: setSelectedProtocolId,
});
- const {
- execute: executeRedeemTx,
- status: redeemTxStatus,
- txHash: redeemTxHash,
- } = useRedeemTx();
+ const { execute: executeParachainRedeemTx, status: parachainRedeemTxStatus } =
+ useRedeemTx();
+
+ const { execute: executeTangleUnbondTx, status: tangleUnbondTxStatus } =
+ useLsPoolUnbondTx();
const performLiquifierUnlock = useLiquifierUnlock();
@@ -82,10 +78,7 @@ const LsUnstakeCard: FC = () => {
const {
exchangeRate: exchangeRateOrError,
isRefreshing: isRefreshingExchangeRate,
- } = useLsExchangeRate(
- ExchangeRateType.DerivativeToNative,
- selectedProtocol.id,
- );
+ } = useLsExchangeRate(ExchangeRateType.DerivativeToNative);
// TODO: Properly handle the error state.
const exchangeRate =
@@ -99,6 +92,11 @@ const LsUnstakeCard: FC = () => {
stringify: (value) => value?.toString(),
});
+ const isTangleNetwork =
+ selectedNetworkId === LsNetworkId.TANGLE_LOCAL ||
+ selectedNetworkId === LsNetworkId.TANGLE_MAINNET ||
+ selectedNetworkId === LsNetworkId.TANGLE_TESTNET;
+
const handleUnstakeClick = useCallback(async () => {
// Cannot perform transaction: Amount not set.
if (fromAmount === null) {
@@ -107,9 +105,9 @@ const LsUnstakeCard: FC = () => {
if (
selectedProtocol.networkId === LsNetworkId.TANGLE_RESTAKING_PARACHAIN &&
- executeRedeemTx !== null
+ executeParachainRedeemTx !== null
) {
- executeRedeemTx({
+ return executeParachainRedeemTx({
amount: fromAmount,
currency: selectedProtocol.currency,
});
@@ -117,16 +115,28 @@ const LsUnstakeCard: FC = () => {
selectedProtocol.networkId === LsNetworkId.ETHEREUM_MAINNET_LIQUIFIER &&
performLiquifierUnlock !== null
) {
- setDidLiquifierUnlockSucceed(false);
-
- const success = await performLiquifierUnlock(
- selectedProtocol.id,
- fromAmount,
- );
+ return performLiquifierUnlock(selectedProtocol.id, fromAmount);
+ }
- setDidLiquifierUnlockSucceed(success);
+ if (
+ isTangleNetwork &&
+ executeTangleUnbondTx !== null &&
+ selectedPoolId !== null
+ ) {
+ return executeTangleUnbondTx({
+ points: fromAmount,
+ poolId: selectedPoolId,
+ });
}
- }, [executeRedeemTx, fromAmount, performLiquifierUnlock, selectedProtocol]);
+ }, [
+ executeParachainRedeemTx,
+ executeTangleUnbondTx,
+ fromAmount,
+ isTangleNetwork,
+ performLiquifierUnlock,
+ selectedPoolId,
+ selectedProtocol,
+ ]);
const toAmount = useMemo(() => {
if (fromAmount === null || exchangeRate === null) {
@@ -145,29 +155,28 @@ const LsUnstakeCard: FC = () => {
return [{ address: '0x123456' as any, amount: new BN(100), decimals: 18 }];
}, []);
- // Open the request submitted modal when the redeem
- // transaction is complete.
+ // Reset the input amount when the network changes.
useEffect(() => {
- if (redeemTxStatus === TxStatus.COMPLETE || didLiquifierUnlockSucceed) {
- setIsRequestSubmittedModalOpen(true);
- }
- }, [didLiquifierUnlockSucceed, redeemTxStatus]);
+ setFromAmount(null);
+ }, [setFromAmount, selectedNetworkId]);
const stakedWalletBalance = (
setFromAmount(maxSpendable)}
/>
);
- // TODO: Also check if the user has enough balance to unstake.
+ // TODO: Also check if the user has enough balance to unstake. Convert this into a self-executing function to break down the complexity of a one-liner.
const canCallUnstake =
(selectedProtocol.networkId === LsNetworkId.TANGLE_RESTAKING_PARACHAIN &&
- executeRedeemTx !== null) ||
+ executeParachainRedeemTx !== null) ||
(selectedProtocol.networkId === LsNetworkId.ETHEREUM_MAINNET_LIQUIFIER &&
- performLiquifierUnlock !== null);
+ performLiquifierUnlock !== null) ||
+ (isTangleNetwork &&
+ executeTangleUnbondTx !== null &&
+ selectedPoolId !== null);
return (
<>
@@ -175,7 +184,7 @@ const LsUnstakeCard: FC = () => {
{
@@ -239,7 +247,10 @@ const LsUnstakeCard: FC = () => {
fromAmount === null ||
fromAmount.isZero()
}
- isLoading={redeemTxStatus === TxStatus.PROCESSING}
+ isLoading={
+ parachainRedeemTxStatus === TxStatus.PROCESSING ||
+ tangleUnbondTxStatus === TxStatus.PROCESSING
+ }
loadingText="Processing"
onClick={handleUnstakeClick}
isFullWidth
@@ -253,12 +264,6 @@ const LsUnstakeCard: FC = () => {
onClose={() => setIsSelectTokenModalOpen(false)}
onTokenSelect={handleTokenSelect}
/>
-
- setIsRequestSubmittedModalOpen(false)}
- txHash={redeemTxHash}
- />
>
);
};
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/NetworkSelector.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/NetworkSelector.tsx
index fa176d2a3..3ff8d4a65 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/NetworkSelector.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/NetworkSelector.tsx
@@ -7,11 +7,16 @@ import {
DropdownMenuItem,
Typography,
} from '@webb-tools/webb-ui-components';
+import assert from 'assert';
import { FC } from 'react';
+import { IS_PRODUCTION_ENV } from '../../../constants/env';
import { LS_NETWORKS } from '../../../constants/liquidStaking/constants';
import { LsNetworkId } from '../../../constants/liquidStaking/types';
+import { NETWORK_FEATURE_MAP } from '../../../constants/networks';
+import { NetworkFeature } from '../../../types';
import getLsNetwork from '../../../utils/liquidStaking/getLsNetwork';
+import getLsTangleNetwork from '../../../utils/liquidStaking/getLsTangleNetwork';
import DropdownChevronIcon from './DropdownChevronIcon';
type NetworkSelectorProps = {
@@ -45,6 +50,26 @@ const NetworkSelector: FC = ({
);
+ // Filter out networks that don't support liquid staking yet.
+ const supportedLsNetworks = LS_NETWORKS.filter((network) => {
+ if (network.id === LsNetworkId.ETHEREUM_MAINNET_LIQUIFIER) {
+ return true;
+ }
+ // Exclude the local Tangle network in production.
+ else if (network.id === LsNetworkId.TANGLE_LOCAL && IS_PRODUCTION_ENV) {
+ return false;
+ }
+
+ // TODO: Obtain the Tangle network from the LS Network's properties instead.
+ const tangleNetwork = getLsTangleNetwork(network.id);
+
+ assert(tangleNetwork !== null);
+
+ return NETWORK_FEATURE_MAP[tangleNetwork.id].includes(
+ NetworkFeature.LsPools,
+ );
+ });
+
return setNetworkId !== undefined ? (
{base}
@@ -52,10 +77,10 @@ const NetworkSelector: FC = ({
- {LS_NETWORKS.map((network) => {
+ {supportedLsNetworks.map((network) => {
return (
- -
- setNetworkId(network.type)}>
+
-
+ setNetworkId(network.id)}>
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ProtocolSelector.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ProtocolSelector.tsx
index aa27a55b5..d249c58a0 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ProtocolSelector.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ProtocolSelector.tsx
@@ -7,7 +7,7 @@ import {
Typography,
} from '@webb-tools/webb-ui-components';
import { ScrollArea } from '@webb-tools/webb-ui-components/components/ScrollArea';
-import { FC } from 'react';
+import { FC, useCallback } from 'react';
import { LS_DERIVATIVE_TOKEN_PREFIX } from '../../../constants/liquidStaking/constants';
import {
@@ -35,19 +35,24 @@ const ProtocolSelector: FC
= ({
const protocol = getLsProtocolDef(selectedProtocolId);
const network = getLsNetwork(selectedNetworkId);
- const trySetProtocolId = (newProtocolId: LsProtocolId) => {
- return () => {
- if (setProtocolId === undefined) {
- return;
- }
+ const trySetProtocolId = useCallback(
+ (newProtocolId: LsProtocolId) => {
+ return () => {
+ if (setProtocolId === undefined) {
+ return;
+ }
- setProtocolId(newProtocolId);
- };
- };
+ setProtocolId(newProtocolId);
+ };
+ },
+ [setProtocolId],
+ );
return (
-
+
@@ -56,7 +61,9 @@ const ProtocolSelector: FC
= ({
{protocol.token}
- {setProtocolId !== undefined && }
+ {setProtocolId !== undefined && network.protocols.length > 1 && (
+
+ )}
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TotalDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TotalDetailItem.tsx
index 31041abb7..86a19cff2 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TotalDetailItem.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TotalDetailItem.tsx
@@ -30,7 +30,6 @@ const TotalDetailItem: FC = ({
isMinting
? ExchangeRateType.NativeToDerivative
: ExchangeRateType.DerivativeToNative,
- protocolId,
);
const protocol = getLsProtocolDef(protocolId);
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsAgnosticBalance.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsAgnosticBalance.ts
index 42f70963b..b6a017a82 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsAgnosticBalance.ts
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsAgnosticBalance.ts
@@ -4,11 +4,11 @@ import { erc20Abi } from 'viem';
import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants';
import LIQUIFIER_TG_TOKEN_ABI from '../../../constants/liquidStaking/liquifierTgTokenAbi';
-import {
- LsNetworkId,
- LsProtocolId,
-} from '../../../constants/liquidStaking/types';
-import useParachainBalances from '../../../data/liquidStaking/useParachainBalances';
+import { LsNetworkId } from '../../../constants/liquidStaking/types';
+import useBalances from '../../../data/balances/useBalances';
+import useParachainBalances from '../../../data/liquidStaking/parachain/useParachainBalances';
+import useLsPoolBalance from '../../../data/liquidStaking/tangle/useLsPoolBalance';
+import { useLsStore } from '../../../data/liquidStaking/useLsStore';
import usePolling from '../../../data/liquidStaking/usePolling';
import useContractReadOnce from '../../../data/liquifier/useContractReadOnce';
import useActiveAccountAddress from '../../../hooks/useActiveAccountAddress';
@@ -46,10 +46,13 @@ const createBalanceStateUpdater = (
};
};
-const useLsAgnosticBalance = (isNative: boolean, protocolId: LsProtocolId) => {
+const useLsAgnosticBalance = (isNative: boolean) => {
const activeAccountAddress = useActiveAccountAddress();
const evmAddress20 = useEvmAddress20();
const { nativeBalances, liquidBalances } = useParachainBalances();
+ const { free: tangleFreeBalance } = useBalances();
+ const { selectedProtocolId, selectedNetworkId } = useLsStore();
+ const tangleAssetBalance = useLsPoolBalance();
// TODO: Why not use the subscription hook variants (useContractRead) instead of manually utilizing usePolling?
const readErc20 = useContractReadOnce(erc20Abi);
@@ -61,7 +64,7 @@ const useLsAgnosticBalance = (isNative: boolean, protocolId: LsProtocolId) => {
const parachainBalances = isNative ? nativeBalances : liquidBalances;
const isAccountConnected = activeAccountAddress !== null;
- const protocol = getLsProtocolDef(protocolId);
+ const protocol = getLsProtocolDef(selectedProtocolId);
// Reset balance to a placeholder when the active account is
// disconnected, and to a loading state once an account is
@@ -75,7 +78,7 @@ const useLsAgnosticBalance = (isNative: boolean, protocolId: LsProtocolId) => {
if (isAccountConnected) {
setBalance(null);
}
- }, [isAccountConnected, isNative, protocolId]);
+ }, [isAccountConnected, isNative, selectedProtocolId]);
const erc20BalanceFetcher = useCallback(() => {
if (
@@ -115,6 +118,8 @@ const useLsAgnosticBalance = (isNative: boolean, protocolId: LsProtocolId) => {
effect: isAccountConnected ? erc20BalanceFetcher : null,
});
+ // Update balance to the parachain balance when the restaking
+ // parachain is the active network.
useEffect(() => {
if (
protocol.networkId !== LsNetworkId.TANGLE_RESTAKING_PARACHAIN ||
@@ -128,6 +133,38 @@ const useLsAgnosticBalance = (isNative: boolean, protocolId: LsProtocolId) => {
setBalance(createBalanceStateUpdater(newBalance));
}, [parachainBalances, protocol.token, protocol.networkId]);
+ const isLsTangleNetwork =
+ selectedNetworkId === LsNetworkId.TANGLE_LOCAL ||
+ selectedNetworkId === LsNetworkId.TANGLE_MAINNET ||
+ selectedNetworkId === LsNetworkId.TANGLE_TESTNET;
+
+ // Update the balance to the Tangle balance when the Tangle
+ // network is the active network.
+ useEffect(() => {
+ if (!isLsTangleNetwork) {
+ return;
+ }
+ // Relevant balance hasn't loaded yet or isn't available.
+ else if (
+ (isNative && tangleFreeBalance === null) ||
+ (!isNative && tangleAssetBalance === null)
+ ) {
+ return;
+ }
+
+ setBalance(
+ createBalanceStateUpdater(
+ isNative ? tangleFreeBalance : tangleAssetBalance,
+ ),
+ );
+ }, [
+ protocol.networkId,
+ tangleFreeBalance,
+ isLsTangleNetwork,
+ tangleAssetBalance,
+ isNative,
+ ]);
+
return { balance, isRefreshing };
};
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsChangeNetwork.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsChangeNetwork.ts
new file mode 100644
index 000000000..f467cc853
--- /dev/null
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsChangeNetwork.ts
@@ -0,0 +1,60 @@
+import { useWebbUI } from '@webb-tools/webb-ui-components';
+import assert from 'assert';
+import { useCallback } from 'react';
+
+import { LsNetworkId } from '../../../constants/liquidStaking/types';
+import { NETWORK_FEATURE_MAP } from '../../../constants/networks';
+import { useLsStore } from '../../../data/liquidStaking/useLsStore';
+import useNetworkSwitcher from '../../../hooks/useNetworkSwitcher';
+import { NetworkFeature } from '../../../types';
+import getLsNetwork from '../../../utils/liquidStaking/getLsNetwork';
+import getLsTangleNetwork from '../../../utils/liquidStaking/getLsTangleNetwork';
+
+const useLsChangeNetwork = () => {
+ const { selectedNetworkId, setSelectedNetworkId } = useLsStore();
+ const { switchNetwork } = useNetworkSwitcher();
+ const { notificationApi } = useWebbUI();
+
+ const tryChangeNetwork = useCallback(
+ async (newNetworkId: LsNetworkId) => {
+ // No need to change network if it's already selected.
+ if (selectedNetworkId === newNetworkId) {
+ return;
+ }
+
+ const lsNetwork = getLsNetwork(newNetworkId);
+
+ // Don't check connection to Ethereum mainnet liquifier;
+ // only verify RPC connection to Tangle networks.
+ if (lsNetwork.id === LsNetworkId.ETHEREUM_MAINNET_LIQUIFIER) {
+ setSelectedNetworkId(newNetworkId);
+
+ return;
+ }
+
+ const tangleNetwork = getLsTangleNetwork(newNetworkId);
+
+ assert(tangleNetwork !== null);
+
+ const networkFeatures = NETWORK_FEATURE_MAP[tangleNetwork.id];
+
+ if (!networkFeatures.includes(NetworkFeature.LsPools)) {
+ notificationApi({
+ message: 'Network does not support liquid staking yet',
+ variant: 'error',
+ });
+
+ return;
+ }
+
+ if (await switchNetwork(tangleNetwork, false)) {
+ setSelectedNetworkId(newNetworkId);
+ }
+ },
+ [notificationApi, selectedNetworkId, setSelectedNetworkId, switchNetwork],
+ );
+
+ return tryChangeNetwork;
+};
+
+export default useLsChangeNetwork;
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsFeePercentage.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsFeePercentage.ts
index 397c3a40e..db26b4d05 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsFeePercentage.ts
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsFeePercentage.ts
@@ -6,7 +6,7 @@ import {
LsNetworkId,
LsProtocolId,
} from '../../../constants/liquidStaking/types';
-import useParachainLsFees from '../../../data/liquidStaking/useParachainLsFees';
+import useParachainLsFees from '../../../data/liquidStaking/parachain/useParachainLsFees';
import useContractRead from '../../../data/liquifier/useContractRead';
import { ContractReadOptions } from '../../../data/liquifier/useContractReadOnce';
import getLsProtocolDef from '../../../utils/liquidStaking/getLsProtocolDef';
@@ -63,9 +63,18 @@ const useLsFeePercentage = (
? null
: Number(rawLiquifierFeeOrError) / 100;
- return protocol.networkId === LsNetworkId.TANGLE_RESTAKING_PARACHAIN
- ? parachainFee
- : liquifierFeePercentageOrError;
+ switch (protocol.networkId) {
+ case LsNetworkId.TANGLE_RESTAKING_PARACHAIN:
+ return parachainFee;
+ case LsNetworkId.ETHEREUM_MAINNET_LIQUIFIER:
+ return liquifierFeePercentageOrError;
+ // Tangle networks with the `lst` pallet have no fees for
+ // joining or leaving pools as of now.
+ case LsNetworkId.TANGLE_LOCAL:
+ case LsNetworkId.TANGLE_MAINNET:
+ case LsNetworkId.TANGLE_TESTNET:
+ return 0;
+ }
};
export default useLsFeePercentage;
diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts
index efe9f2d75..ed3ef1933 100644
--- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts
+++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts
@@ -20,7 +20,7 @@ const useLsSpendingLimits = (
isNative: boolean,
protocolId: LsProtocolId,
): LsSpendingLimits => {
- const { balance } = useLsAgnosticBalance(isNative, protocolId);
+ const { balance } = useLsAgnosticBalance(isNative);
const { result: existentialDepositAmount } = useApi(
useCallback((api) => api.consts.balances.existentialDeposit, []),
diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx
index b776a3efa..abc81f56c 100644
--- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx
+++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx
@@ -320,18 +320,19 @@ const UnstakeRequestsTable: FC = () => {
/>
)}
+ {/* TODO: Assert that the id is either parachain or liquifier, if it isn't then we might need to hide this unstake requests table and show a specific one for Tangle networks (LS pools). */}
{isLsParachainChainId(selectedProtocolId) ? (
- ) : (
+ ) : isLiquifierProtocolId(selectedProtocolId) ? (
- )}
+ ) : undefined}
)}
diff --git a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx
index 2fc775a9c..ddd5675db 100644
--- a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx
+++ b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx
@@ -16,7 +16,6 @@ import {
TooltipTrigger,
Typography,
} from '@webb-tools/webb-ui-components';
-import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks';
import { usePathname } from 'next/navigation';
import { type FC, useCallback, useMemo } from 'react';
import { twMerge } from 'tailwind-merge';
@@ -99,11 +98,12 @@ const NetworkSelectionButton: FC = () => {
// Network can't be switched from the Tangle Restaking Parachain while
// on liquid staking page.
else if (isInLiquidStakingPath) {
- const liquidStakingNetworkName = isLiquifierProtocolId(selectedProtocolId)
+ // Special case when the liquifier is selected.
+ const lsNetworkName = isLiquifierProtocolId(selectedProtocolId)
? IS_PRODUCTION_ENV
? 'Ethereum Mainnet'
: 'Sepolia Testnet'
- : TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.name;
+ : networkName;
const chainIconName = isLiquifierProtocolId(selectedProtocolId)
? 'ethereum'
@@ -115,7 +115,7 @@ const NetworkSelectionButton: FC = () => {
diff --git a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectorDropdown.tsx b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectorDropdown.tsx
index 6b12d7748..497ebdc04 100644
--- a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectorDropdown.tsx
+++ b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectorDropdown.tsx
@@ -30,14 +30,14 @@ export const NetworkSelectorDropdown: FC = ({
}) => {
return (
- {/* Mainnet network */}
+ {/* Tangle Mainnet */}
onNetworkChange(TANGLE_MAINNET_NETWORK)}
/>
- {/* Testnet network */}
+ {/* Tangle Testnet */}
= ({
- {/* Local dev network */}
+ {/* Tangle Local Dev */}
;
+export type LsParachainChainId =
+ | LsProtocolId.POLKADOT
+ | LsProtocolId.PHALA
+ | LsProtocolId.MOONBEAM
+ | LsProtocolId.ASTAR
+ | LsProtocolId.MANTA;
+
+export type LsTangleNetworkId =
+ | LsProtocolId.TANGLE_MAINNET
+ | LsProtocolId.TANGLE_TESTNET
+ | LsProtocolId.TANGLE_LOCAL;
export enum LsToken {
DOT = 'DOT',
@@ -39,6 +58,7 @@ export enum LsToken {
ASTAR = 'ASTR',
PHALA = 'PHALA',
TNT = 'TNT',
+ TTNT = 'tTNT',
LINK = 'LINK',
GRT = 'GRT',
LPT = 'LPT',
@@ -51,7 +71,12 @@ export type LsLiquifierProtocolToken =
| LsToken.LPT
| LsToken.POL;
-export type LsParachainToken = Exclude;
+export type LsParachainToken =
+ | LsToken.DOT
+ | LsToken.GLMR
+ | LsToken.MANTA
+ | LsToken.ASTAR
+ | LsToken.PHALA;
type ProtocolDefCommon = {
name: string;
@@ -62,10 +87,28 @@ type ProtocolDefCommon = {
};
export enum LsNetworkId {
+ TANGLE_LOCAL,
+ TANGLE_TESTNET,
+ TANGLE_MAINNET,
TANGLE_RESTAKING_PARACHAIN,
ETHEREUM_MAINNET_LIQUIFIER,
}
+export interface LsTangleNetworkDef extends ProtocolDefCommon {
+ networkId:
+ | LsNetworkId.TANGLE_MAINNET
+ | LsNetworkId.TANGLE_TESTNET
+ | LsNetworkId.TANGLE_LOCAL;
+ id: LsTangleNetworkId;
+ token: LsToken.TNT | LsToken.TTNT;
+ rpcEndpoint: string;
+ ss58Prefix:
+ | typeof TANGLE_MAINNET_NETWORK.ss58Prefix
+ | typeof TANGLE_TESTNET_NATIVE_NETWORK.ss58Prefix
+ | typeof TANGLE_LOCAL_DEV_NETWORK.ss58Prefix;
+ tangleNetwork: TangleNetwork;
+}
+
export interface LsParachainChainDef
extends ProtocolDefCommon {
networkId: LsNetworkId.TANGLE_RESTAKING_PARACHAIN;
@@ -88,7 +131,10 @@ export interface LsLiquifierProtocolDef extends ProtocolDefCommon {
unlocksContractAddress: HexString;
}
-export type LsProtocolDef = LsParachainChainDef | LsLiquifierProtocolDef;
+export type LsProtocolDef =
+ | LsParachainChainDef
+ | LsLiquifierProtocolDef
+ | LsTangleNetworkDef;
export type LsCardSearchParams = {
amount: BN;
@@ -132,7 +178,7 @@ export type LsParachainSimpleTimeUnit = {
};
export type LsNetwork = {
- type: LsNetworkId;
+ id: LsNetworkId;
networkName: string;
chainIconFileName: string;
defaultProtocolId: LsProtocolId;
diff --git a/apps/tangle-dapp/containers/Layout/FeedbackBanner.tsx b/apps/tangle-dapp/containers/Layout/FeedbackBanner.tsx
deleted file mode 100644
index 34db568db..000000000
--- a/apps/tangle-dapp/containers/Layout/FeedbackBanner.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-'use client';
-
-import { Transition } from '@headlessui/react';
-import { BoxLine } from '@webb-tools/icons';
-import { Banner } from '@webb-tools/webb-ui-components/components/Banner';
-import { GITHUB_BUG_REPORT_URL } from '@webb-tools/webb-ui-components/constants';
-import { FC, useCallback, useEffect, useState } from 'react';
-
-import useLocalStorage, { LocalStorageKey } from '../../hooks/useLocalStorage';
-
-const FeedbackBanner: FC = () => {
- // Initially, the banner is hidden until the value is
- // extracted from local storage.
- const [showBanner, setShowBanner] = useState(false);
-
- const {
- isSet: isBannerDismissalCacheSet,
- set: setCachedWasBannerDismissed,
- valueOpt: wasBannerDismissedOpt,
- } = useLocalStorage(LocalStorageKey.WAS_BANNER_DISMISSED);
-
- // If there is no cache key, show the banner by default.
- useEffect(() => {
- if (!isBannerDismissalCacheSet()) {
- setShowBanner(true);
- }
- }, [isBannerDismissalCacheSet, setShowBanner]);
-
- // If the banner was dismissed, do not show it to prevent
- // annoying the user.
- useEffect(() => {
- if (wasBannerDismissedOpt?.value === true) {
- setShowBanner(false);
- }
- }, [wasBannerDismissedOpt?.value]);
-
- const onCloseHandler = useCallback(() => {
- setShowBanner(false);
- setCachedWasBannerDismissed(true);
- }, [setCachedWasBannerDismissed]);
-
- return (
-
-
-
- );
-};
-
-export default FeedbackBanner;
diff --git a/apps/tangle-dapp/containers/Layout/Layout.tsx b/apps/tangle-dapp/containers/Layout/Layout.tsx
index 38abe88bd..2b62adebe 100644
--- a/apps/tangle-dapp/containers/Layout/Layout.tsx
+++ b/apps/tangle-dapp/containers/Layout/Layout.tsx
@@ -21,7 +21,6 @@ import { IS_PRODUCTION_ENV } from '../../constants/env';
import ApiDevStatsContainer from '../DebugMetricsContainer';
import WalletAndChainContainer from '../WalletAndChainContainer/WalletAndChainContainer';
import { WalletModalContainer } from '../WalletModalContainer';
-import FeedbackBanner from './FeedbackBanner';
// Some specific overrides for the social links for use in the
// footer in Tangle dApp, since it defaults to the Webb socials.
@@ -51,8 +50,6 @@ const Layout: FC> = ({
-
-
diff --git a/apps/tangle-dapp/containers/LsPoolsTable.tsx b/apps/tangle-dapp/containers/LsPoolsTable.tsx
index e4c3f4c63..03e54bbdf 100644
--- a/apps/tangle-dapp/containers/LsPoolsTable.tsx
+++ b/apps/tangle-dapp/containers/LsPoolsTable.tsx
@@ -158,7 +158,7 @@ const DEFAULT_PAGINATION_STATE: PaginationState = {
};
const LsPoolsTable: FC = () => {
- const { setSelectedParachainPoolId } = useLsStore();
+ const { setSelectedPoolId: setSelectedParachainPoolId } = useLsStore();
const [searchQuery, setSearchQuery] = useState('');
const [paginationState, setPaginationState] = useState
(
@@ -183,7 +183,7 @@ const LsPoolsTable: FC = () => {
const selectedRow = selectedRowIds.at(0);
assert(selectedRow !== undefined, 'One row must always be selected');
- setSelectedParachainPoolId(selectedRow);
+ setSelectedParachainPoolId(parseInt(selectedRow, 10));
setRowSelectionState(newSelectionState);
},
[rowSelectionState, setSelectedParachainPoolId],
diff --git a/apps/tangle-dapp/data/liquidStaking/adapters/tangleLocal.tsx b/apps/tangle-dapp/data/liquidStaking/adapters/tangleLocal.tsx
new file mode 100644
index 000000000..3dff97eeb
--- /dev/null
+++ b/apps/tangle-dapp/data/liquidStaking/adapters/tangleLocal.tsx
@@ -0,0 +1,26 @@
+import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config';
+import { TANGLE_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks';
+
+import {
+ LsNetworkId,
+ LsProtocolId,
+ LsTangleNetworkDef,
+ LsToken,
+} from '../../../constants/liquidStaking/types';
+import { CrossChainTimeUnit } from '../../../utils/CrossChainTime';
+
+const TANGLE_LOCAL = {
+ networkId: LsNetworkId.TANGLE_LOCAL,
+ id: LsProtocolId.TANGLE_LOCAL,
+ name: 'Tangle Local',
+ token: LsToken.TTNT,
+ chainIconFileName: 'tangle',
+ decimals: TANGLE_TOKEN_DECIMALS,
+ rpcEndpoint: TANGLE_LOCAL_DEV_NETWORK.wsRpcEndpoint,
+ timeUnit: CrossChainTimeUnit.POLKADOT_ERA,
+ unstakingPeriod: 14,
+ ss58Prefix: TANGLE_LOCAL_DEV_NETWORK.ss58Prefix,
+ tangleNetwork: TANGLE_LOCAL_DEV_NETWORK,
+} as const satisfies LsTangleNetworkDef;
+
+export default TANGLE_LOCAL;
diff --git a/apps/tangle-dapp/data/liquidStaking/adapters/tangleMainnet.tsx b/apps/tangle-dapp/data/liquidStaking/adapters/tangleMainnet.tsx
new file mode 100644
index 000000000..6e908966a
--- /dev/null
+++ b/apps/tangle-dapp/data/liquidStaking/adapters/tangleMainnet.tsx
@@ -0,0 +1,26 @@
+import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config';
+import { TANGLE_MAINNET_NETWORK } from '@webb-tools/webb-ui-components/constants/networks';
+
+import {
+ LsNetworkId,
+ LsProtocolId,
+ LsTangleNetworkDef,
+ LsToken,
+} from '../../../constants/liquidStaking/types';
+import { CrossChainTimeUnit } from '../../../utils/CrossChainTime';
+
+const TANGLE_MAINNET = {
+ networkId: LsNetworkId.TANGLE_MAINNET,
+ id: LsProtocolId.TANGLE_MAINNET,
+ name: 'Tangle',
+ token: LsToken.TNT,
+ chainIconFileName: 'tangle',
+ decimals: TANGLE_TOKEN_DECIMALS,
+ rpcEndpoint: TANGLE_MAINNET_NETWORK.wsRpcEndpoint,
+ timeUnit: CrossChainTimeUnit.POLKADOT_ERA,
+ unstakingPeriod: 14,
+ ss58Prefix: TANGLE_MAINNET_NETWORK.ss58Prefix,
+ tangleNetwork: TANGLE_MAINNET_NETWORK,
+} as const satisfies LsTangleNetworkDef;
+
+export default TANGLE_MAINNET;
diff --git a/apps/tangle-dapp/data/liquidStaking/adapters/tangleTestnet.tsx b/apps/tangle-dapp/data/liquidStaking/adapters/tangleTestnet.tsx
new file mode 100644
index 000000000..01fb3e36c
--- /dev/null
+++ b/apps/tangle-dapp/data/liquidStaking/adapters/tangleTestnet.tsx
@@ -0,0 +1,26 @@
+import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config';
+import { TANGLE_TESTNET_NATIVE_NETWORK } from '@webb-tools/webb-ui-components/constants/networks';
+
+import {
+ LsNetworkId,
+ LsProtocolId,
+ LsTangleNetworkDef,
+ LsToken,
+} from '../../../constants/liquidStaking/types';
+import { CrossChainTimeUnit } from '../../../utils/CrossChainTime';
+
+const TANGLE_TESTNET = {
+ networkId: LsNetworkId.TANGLE_TESTNET,
+ id: LsProtocolId.TANGLE_TESTNET,
+ name: 'Tangle',
+ token: LsToken.TNT,
+ chainIconFileName: 'tangle',
+ decimals: TANGLE_TOKEN_DECIMALS,
+ rpcEndpoint: TANGLE_TESTNET_NATIVE_NETWORK.wsRpcEndpoint,
+ timeUnit: CrossChainTimeUnit.POLKADOT_ERA,
+ unstakingPeriod: 14,
+ ss58Prefix: TANGLE_TESTNET_NATIVE_NETWORK.ss58Prefix,
+ tangleNetwork: TANGLE_TESTNET_NATIVE_NETWORK,
+} as const satisfies LsTangleNetworkDef;
+
+export default TANGLE_TESTNET;
diff --git a/apps/tangle-dapp/data/liquidStaking/useMintTx.ts b/apps/tangle-dapp/data/liquidStaking/parachain/useMintTx.ts
similarity index 88%
rename from apps/tangle-dapp/data/liquidStaking/useMintTx.ts
rename to apps/tangle-dapp/data/liquidStaking/parachain/useMintTx.ts
index 8a04cdbf7..a391960c0 100644
--- a/apps/tangle-dapp/data/liquidStaking/useMintTx.ts
+++ b/apps/tangle-dapp/data/liquidStaking/parachain/useMintTx.ts
@@ -6,12 +6,12 @@ import { Bytes } from '@polkadot/types';
import { BN } from '@polkadot/util';
import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks';
-import { TxName } from '../../constants';
+import { TxName } from '../../../constants';
import {
LsParachainCurrencyKey,
ParachainCurrency,
-} from '../../constants/liquidStaking/types';
-import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx';
+} from '../../../constants/liquidStaking/types';
+import { useSubstrateTxWithNotification } from '../../../hooks/useSubstrateTx';
export type MintTxContext = {
amount: BN;
diff --git a/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts b/apps/tangle-dapp/data/liquidStaking/parachain/useParachainBalances.ts
similarity index 89%
rename from apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts
rename to apps/tangle-dapp/data/liquidStaking/parachain/useParachainBalances.ts
index 5c4487957..865adf545 100644
--- a/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts
+++ b/apps/tangle-dapp/data/liquidStaking/parachain/useParachainBalances.ts
@@ -8,10 +8,10 @@ import { BN } from '@polkadot/util';
import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks';
import { useCallback, useMemo } from 'react';
-import { LsParachainToken } from '../../constants/liquidStaking/types';
-import useApiRx from '../../hooks/useApiRx';
-import useSubstrateAddress from '../../hooks/useSubstrateAddress';
-import isLsParachainToken from '../../utils/liquidStaking/isLsParachainToken';
+import { LsParachainToken } from '../../../constants/liquidStaking/types';
+import useApiRx from '../../../hooks/useApiRx';
+import useSubstrateAddress from '../../../hooks/useSubstrateAddress';
+import isLsParachainToken from '../../../utils/liquidStaking/isLsParachainToken';
const useParachainBalances = () => {
const activeSubstrateAddress = useSubstrateAddress();
diff --git a/apps/tangle-dapp/data/liquidStaking/useParachainLsFees.ts b/apps/tangle-dapp/data/liquidStaking/parachain/useParachainLsFees.ts
similarity index 85%
rename from apps/tangle-dapp/data/liquidStaking/useParachainLsFees.ts
rename to apps/tangle-dapp/data/liquidStaking/parachain/useParachainLsFees.ts
index b82a6577b..24b0811b4 100644
--- a/apps/tangle-dapp/data/liquidStaking/useParachainLsFees.ts
+++ b/apps/tangle-dapp/data/liquidStaking/parachain/useParachainLsFees.ts
@@ -2,8 +2,8 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u
import { useCallback } from 'react';
import { map } from 'rxjs';
-import useApiRx from '../../hooks/useApiRx';
-import permillToPercentage from '../../utils/permillToPercentage';
+import useApiRx from '../../../hooks/useApiRx';
+import permillToPercentage from '../../../utils/permillToPercentage';
const useParachainLsFees = () => {
return useApiRx(
diff --git a/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts b/apps/tangle-dapp/data/liquidStaking/parachain/useRedeemTx.ts
similarity index 87%
rename from apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts
rename to apps/tangle-dapp/data/liquidStaking/parachain/useRedeemTx.ts
index 9cfa6758f..8dbc610e6 100644
--- a/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts
+++ b/apps/tangle-dapp/data/liquidStaking/parachain/useRedeemTx.ts
@@ -5,12 +5,12 @@ import '@webb-tools/tangle-restaking-types';
import { BN } from '@polkadot/util';
import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks';
-import { TxName } from '../../constants';
+import { TxName } from '../../../constants';
import {
LsParachainCurrencyKey,
ParachainCurrency,
-} from '../../constants/liquidStaking/types';
-import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx';
+} from '../../../constants/liquidStaking/types';
+import { useSubstrateTxWithNotification } from '../../../hooks/useSubstrateTx';
export type RedeemTxContext = {
amount: BN;
diff --git a/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolBalance.ts b/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolBalance.ts
new file mode 100644
index 000000000..14a8f3c28
--- /dev/null
+++ b/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolBalance.ts
@@ -0,0 +1,49 @@
+import { BN_ZERO } from '@polkadot/util';
+import { useCallback, useMemo } from 'react';
+
+import useApiRx from '../../../hooks/useApiRx';
+import useNetworkFeatures from '../../../hooks/useNetworkFeatures';
+import useSubstrateAddress from '../../../hooks/useSubstrateAddress';
+import { NetworkFeature } from '../../../types';
+import { useLsStore } from '../useLsStore';
+
+const useLsPoolBalance = () => {
+ const substrateAddress = useSubstrateAddress();
+ const networkFeatures = useNetworkFeatures();
+ const isSupported = networkFeatures.includes(NetworkFeature.LsPools);
+ const { selectedPoolId } = useLsStore();
+
+ const { result: tanglePoolAssetAccountOpt } = useApiRx(
+ useCallback(
+ (api) => {
+ // The liquid staking pools functionality isn't available on the active
+ // network, the user hasn't selected a pool yet, or there is no active
+ // account.
+ if (
+ !isSupported ||
+ selectedPoolId === null ||
+ substrateAddress === null
+ ) {
+ return null;
+ }
+
+ return api.query.assets.account(selectedPoolId, substrateAddress);
+ },
+ [isSupported, selectedPoolId, substrateAddress],
+ ),
+ );
+
+ const derivativeBalanceOpt = useMemo(() => {
+ if (tanglePoolAssetAccountOpt === null) {
+ return null;
+ } else if (tanglePoolAssetAccountOpt.isNone) {
+ return BN_ZERO;
+ }
+
+ return tanglePoolAssetAccountOpt.unwrap().balance.toBn();
+ }, [tanglePoolAssetAccountOpt]);
+
+ return derivativeBalanceOpt;
+};
+
+export default useLsPoolBalance;
diff --git a/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolJoinTx.ts b/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolJoinTx.ts
new file mode 100644
index 000000000..5f7eb0bda
--- /dev/null
+++ b/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolJoinTx.ts
@@ -0,0 +1,28 @@
+import { BN } from '@polkadot/util';
+import { useCallback } from 'react';
+
+import { TxName } from '../../../constants';
+import {
+ SubstrateTxFactory,
+ useSubstrateTxWithNotification,
+} from '../../../hooks/useSubstrateTx';
+
+export type LsPoolJoinTxContext = {
+ poolId: number;
+ amount: BN;
+};
+
+const useLsPoolJoinTx = () => {
+ const substrateTxFactory: SubstrateTxFactory =
+ useCallback(async (api, _activeSubstrateAddress, { poolId, amount }) => {
+ return api.tx.lst.join(amount, poolId);
+ }, []);
+
+ // TODO: Add EVM support once precompile(s) for the `lst` pallet are implemented on Tangle.
+ return useSubstrateTxWithNotification(
+ TxName.LS_TANGLE_POOL_JOIN,
+ substrateTxFactory,
+ );
+};
+
+export default useLsPoolJoinTx;
diff --git a/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolUnbondTx.ts b/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolUnbondTx.ts
new file mode 100644
index 000000000..b72bd79a1
--- /dev/null
+++ b/apps/tangle-dapp/data/liquidStaking/tangle/useLsPoolUnbondTx.ts
@@ -0,0 +1,28 @@
+import { BN } from '@polkadot/util';
+import { useCallback } from 'react';
+
+import { TxName } from '../../../constants';
+import {
+ SubstrateTxFactory,
+ useSubstrateTxWithNotification,
+} from '../../../hooks/useSubstrateTx';
+
+export type LsPoolUnbondTxContext = {
+ poolId: number;
+ points: BN;
+};
+
+const useLsPoolUnbondTx = () => {
+ const substrateTxFactory: SubstrateTxFactory =
+ useCallback(async (api, activeSubstrateAddress, { poolId, points }) => {
+ return api.tx.lst.unbond({ Id: activeSubstrateAddress }, poolId, points);
+ }, []);
+
+ // TODO: Add EVM support once precompile(s) for the `lst` pallet are implemented on Tangle.
+ return useSubstrateTxWithNotification(
+ TxName.LS_TANGLE_POOL_UNBOND,
+ substrateTxFactory,
+ );
+};
+
+export default useLsPoolUnbondTx;
diff --git a/apps/tangle-dapp/data/liquidStaking/useLsExchangeRate.ts b/apps/tangle-dapp/data/liquidStaking/useLsExchangeRate.ts
index 334434f8a..9df9b0a57 100644
--- a/apps/tangle-dapp/data/liquidStaking/useLsExchangeRate.ts
+++ b/apps/tangle-dapp/data/liquidStaking/useLsExchangeRate.ts
@@ -8,13 +8,13 @@ import LIQUIFIER_TG_TOKEN_ABI from '../../constants/liquidStaking/liquifierTgTok
import {
LsNetworkId,
LsParachainCurrencyKey,
- LsProtocolId,
} from '../../constants/liquidStaking/types';
import useApiRx from '../../hooks/useApiRx';
import calculateBnRatio from '../../utils/calculateBnRatio';
import getLsProtocolDef from '../../utils/liquidStaking/getLsProtocolDef';
import useContractRead from '../liquifier/useContractRead';
import { ContractReadOptions } from '../liquifier/useContractReadOnce';
+import { useLsStore } from './useLsStore';
import usePolling from './usePolling';
export enum ExchangeRateType {
@@ -47,13 +47,11 @@ const computeExchangeRate = (
const MAX_BN_OPERATION_NUMBER = 2 ** 26 - 1;
-const useLsExchangeRate = (
- type: ExchangeRateType,
- protocolId: LsProtocolId,
-) => {
+const useLsExchangeRate = (type: ExchangeRateType) => {
const [exchangeRate, setExchangeRate] = useState(null);
+ const { selectedProtocolId, selectedNetworkId } = useLsStore();
- const protocol = getLsProtocolDef(protocolId);
+ const protocol = getLsProtocolDef(selectedProtocolId);
const { result: tokenPoolAmount } = useApiRx((api) => {
if (protocol.networkId !== LsNetworkId.TANGLE_RESTAKING_PARACHAIN) {
@@ -144,10 +142,24 @@ const useLsExchangeRate = (
}, [liquifierTotalShares, tgTokenTotalSupply, type]);
const fetch = useCallback(async () => {
- const promise =
- protocol.networkId === LsNetworkId.TANGLE_RESTAKING_PARACHAIN
- ? parachainExchangeRate
- : fetchLiquifierExchangeRate();
+ let promise: Promise;
+
+ switch (selectedNetworkId) {
+ case LsNetworkId.ETHEREUM_MAINNET_LIQUIFIER:
+ promise = fetchLiquifierExchangeRate();
+
+ break;
+ case LsNetworkId.TANGLE_RESTAKING_PARACHAIN:
+ promise = parachainExchangeRate;
+
+ break;
+ // Tangle networks with the `lst` pallet have a fixed exchange
+ // rate of 1:1.
+ case LsNetworkId.TANGLE_LOCAL:
+ case LsNetworkId.TANGLE_MAINNET:
+ case LsNetworkId.TANGLE_TESTNET:
+ promise = Promise.resolve(1);
+ }
const newExchangeRate = await promise;
@@ -158,7 +170,7 @@ const useLsExchangeRate = (
}
setExchangeRate(newExchangeRate);
- }, [fetchLiquifierExchangeRate, parachainExchangeRate, protocol]);
+ }, [fetchLiquifierExchangeRate, parachainExchangeRate, selectedNetworkId]);
// Pause or resume ERC20-based exchange rate fetching based
// on whether the requested protocol is a parachain or an ERC20 token.
diff --git a/apps/tangle-dapp/data/liquidStaking/useLsProtocolEntities.ts b/apps/tangle-dapp/data/liquidStaking/useLsProtocolEntities.ts
index 1bddab5a3..fa02539dc 100644
--- a/apps/tangle-dapp/data/liquidStaking/useLsProtocolEntities.ts
+++ b/apps/tangle-dapp/data/liquidStaking/useLsProtocolEntities.ts
@@ -74,6 +74,9 @@ const getDataType = (chain: LsProtocolId): LiquidStakingItem | null => {
case LsProtocolId.LIVEPEER:
case LsProtocolId.POLYGON:
case LsProtocolId.THE_GRAPH:
+ case LsProtocolId.TANGLE_MAINNET:
+ case LsProtocolId.TANGLE_TESTNET:
+ case LsProtocolId.TANGLE_LOCAL:
return null;
}
};
diff --git a/apps/tangle-dapp/data/liquidStaking/useLsStore.ts b/apps/tangle-dapp/data/liquidStaking/useLsStore.ts
index 7c6a97b74..3e7d6d279 100644
--- a/apps/tangle-dapp/data/liquidStaking/useLsStore.ts
+++ b/apps/tangle-dapp/data/liquidStaking/useLsStore.ts
@@ -7,13 +7,13 @@ type State = {
selectedNetworkId: LsNetworkId;
selectedProtocolId: LsProtocolId;
selectedNetworkEntities: Set;
- selectedParachainPoolId: string | null;
+ selectedPoolId: number | null;
};
type Actions = {
setSelectedProtocolId: (newProtocolId: State['selectedProtocolId']) => void;
setSelectedNetworkId: (newNetworkId: State['selectedNetworkId']) => void;
- setSelectedParachainPoolId: (parachainPoolId: string) => void;
+ setSelectedPoolId: (poolId: number) => void;
setSelectedNetworkEntities: (
selectedNetworkEntities: State['selectedNetworkEntities'],
@@ -23,14 +23,14 @@ type Actions = {
type Store = State & Actions;
export const useLsStore = create((set) => ({
- selectedParachainPoolId: null,
- selectedNetworkId: LsNetworkId.TANGLE_RESTAKING_PARACHAIN,
- selectedProtocolId: LsProtocolId.POLKADOT,
+ selectedPoolId: null,
selectedNetworkEntities: new Set(),
- setSelectedParachainPoolId: (selectedParachainPoolId) =>
- set({ selectedParachainPoolId }),
- setSelectedProtocolId: (selectedChainId) =>
- set({ selectedProtocolId: selectedChainId }),
+ // Default the selected network and protocol to the Tangle testnet,
+ // and tTNT, until liquid staking pools are deployed to mainnet.
+ selectedNetworkId: LsNetworkId.TANGLE_TESTNET,
+ selectedProtocolId: LsProtocolId.TANGLE_TESTNET,
+ setSelectedPoolId: (selectedPoolId) => set({ selectedPoolId }),
+ setSelectedProtocolId: (selectedProtocolId) => set({ selectedProtocolId }),
setSelectedNetworkEntities: (selectedNetworkEntities) =>
set({ selectedNetworkEntities }),
setSelectedNetworkId: (selectedNetworkId) => {
diff --git a/apps/tangle-dapp/hooks/useApiRx.ts b/apps/tangle-dapp/hooks/useApiRx.ts
index 45e66171a..dd2ecd3ff 100644
--- a/apps/tangle-dapp/hooks/useApiRx.ts
+++ b/apps/tangle-dapp/hooks/useApiRx.ts
@@ -32,7 +32,7 @@ export type ObservableFactory = (api: ApiRx) => Observable | null;
*/
function useApiRx(
factory: ObservableFactory,
- overrideRpcEndpoint?: string,
+ rpcEndpointOverride?: string,
) {
const [result, setResult] = useState(null);
const [isLoading, setLoading] = useState(true);
@@ -43,8 +43,8 @@ function useApiRx(
const { result: apiRx } = usePromise(
useCallback(
- () => getApiRx(overrideRpcEndpoint ?? rpcEndpoint),
- [overrideRpcEndpoint, rpcEndpoint],
+ () => getApiRx(rpcEndpointOverride ?? rpcEndpoint),
+ [rpcEndpointOverride, rpcEndpoint],
),
null,
);
diff --git a/apps/tangle-dapp/hooks/useLocalStorage.ts b/apps/tangle-dapp/hooks/useLocalStorage.ts
index ae4b9e110..25cc0c9fd 100644
--- a/apps/tangle-dapp/hooks/useLocalStorage.ts
+++ b/apps/tangle-dapp/hooks/useLocalStorage.ts
@@ -22,7 +22,6 @@ export enum LocalStorageKey {
PAYOUTS = 'payouts',
CUSTOM_RPC_ENDPOINT = 'customRpcEndpoint',
KNOWN_NETWORK_ID = 'knownNetworkId',
- WAS_BANNER_DISMISSED = 'wasBannerDismissed',
SERVICES_CACHE = 'servicesCache',
SUBSTRATE_WALLETS_METADATA = 'substrateWalletsMetadata',
BRIDGE_TX_QUEUE_BY_ACC = 'bridgeTxQueue',
@@ -72,15 +71,13 @@ export type LocalStorageValueOf =
? string
: T extends LocalStorageKey.KNOWN_NETWORK_ID
? number
- : T extends LocalStorageKey.WAS_BANNER_DISMISSED
- ? boolean
- : T extends LocalStorageKey.SUBSTRATE_WALLETS_METADATA
- ? SubstrateWalletsMetadataCache
- : T extends LocalStorageKey.BRIDGE_TX_QUEUE_BY_ACC
- ? TxQueueByAccount
- : T extends LocalStorageKey.LIQUID_STAKING_TABLE_DATA
- ? LiquidStakingTableData
- : never;
+ : T extends LocalStorageKey.SUBSTRATE_WALLETS_METADATA
+ ? SubstrateWalletsMetadataCache
+ : T extends LocalStorageKey.BRIDGE_TX_QUEUE_BY_ACC
+ ? TxQueueByAccount
+ : T extends LocalStorageKey.LIQUID_STAKING_TABLE_DATA
+ ? LiquidStakingTableData
+ : never;
export const getJsonFromLocalStorage = (
key: Key,
diff --git a/apps/tangle-dapp/hooks/useNetworkSwitcher.ts b/apps/tangle-dapp/hooks/useNetworkSwitcher.ts
index 9d79e26af..8d0516081 100644
--- a/apps/tangle-dapp/hooks/useNetworkSwitcher.ts
+++ b/apps/tangle-dapp/hooks/useNetworkSwitcher.ts
@@ -75,7 +75,7 @@ const useNetworkSwitcher = () => {
async (newNetwork: Network, isCustom: boolean) => {
// Already on the requested network.
if (network.id === newNetwork.id) {
- return;
+ return true;
}
// Test connection to the new network.
else if (!(await testRpcEndpointConnection(newNetwork.wsRpcEndpoint))) {
@@ -84,7 +84,7 @@ const useNetworkSwitcher = () => {
message: `Unable to connect to the requested network: ${newNetwork.wsRpcEndpoint}`,
});
- return;
+ return false;
}
if (activeWallet !== undefined) {
@@ -122,6 +122,8 @@ const useNetworkSwitcher = () => {
setIsCustom(isCustom);
setNetwork(newNetwork);
+
+ return true;
},
[
activeWallet,
diff --git a/apps/tangle-dapp/hooks/useSearchParamSync.ts b/apps/tangle-dapp/hooks/useSearchParamSync.ts
index 307ac80a3..413d3e725 100644
--- a/apps/tangle-dapp/hooks/useSearchParamSync.ts
+++ b/apps/tangle-dapp/hooks/useSearchParamSync.ts
@@ -11,15 +11,16 @@ export type UseSearchParamSyncOptions = {
setValue: (value: T) => unknown;
};
-const createHref = (newSearchParams: ReadonlyURLSearchParams): string => {
+const createHref = (newSearchParams?: ReadonlyURLSearchParams): string => {
const newUrl = new URL(window.location.href);
- for (const [key] of newUrl.searchParams) {
- newUrl.searchParams.delete(key);
- }
+ // Remove existing search params.
+ newUrl.search = '';
- for (const [key, value] of newSearchParams) {
- newUrl.searchParams.set(key, value);
+ if (newSearchParams !== undefined) {
+ for (const [key, value] of newSearchParams) {
+ newUrl.searchParams.set(key, value);
+ }
}
return newUrl.toString();
@@ -114,7 +115,7 @@ const useSearchParamSync = ({
}
const newSearchParams = updateSearchParam(key, stringifiedValue);
- const href = createHref(new ReadonlyURLSearchParams(newSearchParams));
+ const href = createHref(newSearchParams);
console.debug('Syncing URL search param', key, stringifiedValue, href);
router.push(href);
diff --git a/apps/tangle-dapp/hooks/useSubstrateTx.ts b/apps/tangle-dapp/hooks/useSubstrateTx.ts
index 7e4a30b79..184478a43 100644
--- a/apps/tangle-dapp/hooks/useSubstrateTx.ts
+++ b/apps/tangle-dapp/hooks/useSubstrateTx.ts
@@ -232,8 +232,7 @@ export function useSubstrateTxWithNotification(
overrideRpcEndpoint,
);
- const { notifyProcessing, notifySuccess, notifyError } =
- useTxNotification(txName);
+ const { notifyProcessing, notifySuccess, notifyError } = useTxNotification();
const execute = useCallback(
(context: Context) => {
diff --git a/apps/tangle-dapp/hooks/useTxNotification.tsx b/apps/tangle-dapp/hooks/useTxNotification.tsx
index 109654034..f4b5e522a 100644
--- a/apps/tangle-dapp/hooks/useTxNotification.tsx
+++ b/apps/tangle-dapp/hooks/useTxNotification.tsx
@@ -36,6 +36,8 @@ const SUCCESS_MESSAGES: Record = {
[TxName.LS_LIQUIFIER_APPROVE]: 'Liquifier approval successful',
[TxName.LS_LIQUIFIER_UNLOCK]: 'Liquifier unlock successful',
[TxName.LS_LIQUIFIER_WITHDRAW]: 'Liquifier withdrawal successful',
+ [TxName.LS_TANGLE_POOL_JOIN]: 'Joined liquid staking pool',
+ [TxName.LS_TANGLE_POOL_UNBOND]: 'Unbonded from liquid staking pool',
};
const makeKey = (txName: TxName): `${TxName}-tx-notification` =>
diff --git a/apps/tangle-dapp/utils/Optional.ts b/apps/tangle-dapp/utils/Optional.ts
index ac952ecb4..00ebd86ef 100644
--- a/apps/tangle-dapp/utils/Optional.ts
+++ b/apps/tangle-dapp/utils/Optional.ts
@@ -19,9 +19,9 @@
*
* @example
* ```
- * const value = new Optional(42);
+ * const valueOpt = Optional.new(42);
*
- * if (value.value !== null) {
+ * if (valueOpt.value !== null) {
* console.log(value.value);
* } else {
* console.log('Value is not present!');
@@ -29,6 +29,14 @@
* ```
*/
class Optional> {
+ static empty>(): Optional {
+ return new Optional();
+ }
+
+ static new>(value: T): Optional {
+ return new Optional(value);
+ }
+
readonly value: T | null;
constructor(value?: T) {
@@ -37,10 +45,18 @@ class Optional> {
map>(f: (value: T) => U): Optional {
if (this.value === null) {
- return new Optional();
+ return Optional.empty();
+ }
+
+ return Optional.new(f(this.value));
+ }
+
+ unwrapOrThrow(): T {
+ if (this.value === null) {
+ throw new Error('Value is not present!');
}
- return new Optional(f(this.value));
+ return this.value;
}
get isPresent(): boolean {
diff --git a/apps/tangle-dapp/utils/formatBn.ts b/apps/tangle-dapp/utils/formatBn.ts
index c1294f9ab..a70e40386 100644
--- a/apps/tangle-dapp/utils/formatBn.ts
+++ b/apps/tangle-dapp/utils/formatBn.ts
@@ -29,7 +29,7 @@ const DEFAULT_FORMAT_OPTIONS: FormatOptions = {
trimTrailingZeroes: true,
};
-// TODO: Break this function down into smaller local functions for improved legibility and modularity, since its logic is getting complex. Consider making it functional instead of modifying the various variables: Return {integerPart, fractionalPart} per transformation/function, so that it can be easily chainable monad-style.
+// TODO: Break this function down into smaller local functions for improved legibility and modularity, since its logic is getting complex. Consider making it functional instead of modifying the various variables: Return {integerPart, fractionalPart} per transformation/function, so that it can be easily chainable monad-style. Also, prefer usage of Decimal.js instead of BN for better decimal handling without needing to manually handle the edge cases.
function formatBn(
amount: BN,
decimals: number,
diff --git a/apps/tangle-dapp/utils/liquidStaking/getLsNetwork.ts b/apps/tangle-dapp/utils/liquidStaking/getLsNetwork.ts
index 811ebc96a..9969d0fe3 100644
--- a/apps/tangle-dapp/utils/liquidStaking/getLsNetwork.ts
+++ b/apps/tangle-dapp/utils/liquidStaking/getLsNetwork.ts
@@ -1,6 +1,9 @@
import {
LS_ETHEREUM_MAINNET_LIQUIFIER,
+ LS_TANGLE_LOCAL,
+ LS_TANGLE_MAINNET,
LS_TANGLE_RESTAKING_PARACHAIN,
+ LS_TANGLE_TESTNET,
} from '../../constants/liquidStaking/constants';
import { LsNetwork, LsNetworkId } from '../../constants/liquidStaking/types';
@@ -10,6 +13,12 @@ const getLsNetwork = (networkId: LsNetworkId): LsNetwork => {
return LS_ETHEREUM_MAINNET_LIQUIFIER;
case LsNetworkId.TANGLE_RESTAKING_PARACHAIN:
return LS_TANGLE_RESTAKING_PARACHAIN;
+ case LsNetworkId.TANGLE_MAINNET:
+ return LS_TANGLE_MAINNET;
+ case LsNetworkId.TANGLE_TESTNET:
+ return LS_TANGLE_TESTNET;
+ case LsNetworkId.TANGLE_LOCAL:
+ return LS_TANGLE_LOCAL;
}
};
diff --git a/apps/tangle-dapp/utils/liquidStaking/getLsProtocolDef.ts b/apps/tangle-dapp/utils/liquidStaking/getLsProtocolDef.ts
index 685d7f852..a47aade40 100644
--- a/apps/tangle-dapp/utils/liquidStaking/getLsProtocolDef.ts
+++ b/apps/tangle-dapp/utils/liquidStaking/getLsProtocolDef.ts
@@ -1,6 +1,11 @@
import assert from 'assert';
import { LS_PROTOCOLS } from '../../constants/liquidStaking/constants';
+import {
+ LsLiquifierProtocolId,
+ LsTangleNetworkDef,
+ LsTangleNetworkId,
+} from '../../constants/liquidStaking/types';
import {
LsLiquifierProtocolDef,
LsParachainChainDef,
@@ -10,7 +15,11 @@ import {
type IdToDefMap = T extends LsParachainChainId
? LsParachainChainDef
- : LsLiquifierProtocolDef;
+ : T extends LsTangleNetworkId
+ ? LsTangleNetworkDef
+ : T extends LsLiquifierProtocolId
+ ? LsLiquifierProtocolDef
+ : never;
const getLsProtocolDef = (id: T): IdToDefMap => {
const result = LS_PROTOCOLS.find((def) => def.id === id);
diff --git a/apps/tangle-dapp/utils/liquidStaking/getLsTangleNetwork.ts b/apps/tangle-dapp/utils/liquidStaking/getLsTangleNetwork.ts
new file mode 100644
index 000000000..6d0924c7b
--- /dev/null
+++ b/apps/tangle-dapp/utils/liquidStaking/getLsTangleNetwork.ts
@@ -0,0 +1,31 @@
+import {
+ Network,
+ TANGLE_LOCAL_DEV_NETWORK,
+ TANGLE_MAINNET_NETWORK,
+ TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK,
+ TANGLE_RESTAKING_PARACHAIN_TESTNET_NETWORK,
+ TANGLE_TESTNET_NATIVE_NETWORK,
+} from '@webb-tools/webb-ui-components/constants/networks';
+
+import { IS_PRODUCTION_ENV } from '../../constants/env';
+import { LsNetworkId } from '../../constants/liquidStaking/types';
+
+// TODO: Obtain the Tangle network directly from the adapter's `tangleNetwork` property instead of using this helper method.
+const getLsTangleNetwork = (networkId: LsNetworkId): Network | null => {
+ switch (networkId) {
+ case LsNetworkId.TANGLE_MAINNET:
+ return TANGLE_MAINNET_NETWORK;
+ case LsNetworkId.TANGLE_TESTNET:
+ return TANGLE_TESTNET_NATIVE_NETWORK;
+ case LsNetworkId.TANGLE_LOCAL:
+ return TANGLE_LOCAL_DEV_NETWORK;
+ case LsNetworkId.TANGLE_RESTAKING_PARACHAIN:
+ return IS_PRODUCTION_ENV
+ ? TANGLE_RESTAKING_PARACHAIN_TESTNET_NETWORK
+ : TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK;
+ case LsNetworkId.ETHEREUM_MAINNET_LIQUIFIER:
+ return null;
+ }
+};
+
+export default getLsTangleNetwork;
diff --git a/libs/webb-ui-components/src/constants/networks.ts b/libs/webb-ui-components/src/constants/networks.ts
index b6f7db91c..9781dafa2 100644
--- a/libs/webb-ui-components/src/constants/networks.ts
+++ b/libs/webb-ui-components/src/constants/networks.ts
@@ -99,7 +99,7 @@ export const TANGLE_LOCAL_DEV_NETWORK = {
id: NetworkId.TANGLE_LOCAL_DEV,
substrateChainId: SubstrateChainId.TangleLocalNative,
evmChainId: EVMChainId.TangleLocalEVM,
- name: 'Local endpoint',
+ name: 'Tangle Local Dev',
tokenSymbol: TANGLE_TESTNET_NATIVE_TOKEN_SYMBOL,
nodeType: 'standalone',
subqueryEndpoint: 'http://localhost:4000/graphql',